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

Side by Side Diff: observatory_pub_packages/polymer/src/build/script_compactor.dart

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, 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 /// Transfomer that combines multiple dart script tags into a single one.
6 library polymer.src.build.script_compactor;
7
8 import 'dart:async';
9 import 'dart:convert';
10
11 import 'package:html5lib/dom.dart' show Document, Element, Text;
12 import 'package:html5lib/dom_parsing.dart';
13 import 'package:html5lib/parser.dart' show parseFragment;
14 import 'package:analyzer/src/generated/ast.dart';
15 import 'package:analyzer/src/generated/element.dart' hide Element;
16 import 'package:analyzer/src/generated/element.dart' as analyzer show Element;
17 import 'package:barback/barback.dart';
18 import 'package:code_transformers/messages/build_logger.dart';
19 import 'package:path/path.dart' as path;
20 import 'package:source_span/source_span.dart';
21 import 'package:smoke/codegen/generator.dart';
22 import 'package:smoke/codegen/recorder.dart';
23 import 'package:code_transformers/resolver.dart';
24 import 'package:code_transformers/src/dart_sdk.dart';
25 import 'package:template_binding/src/mustache_tokens.dart' show MustacheTokens;
26
27 import 'package:polymer_expressions/expression.dart' as pe;
28 import 'package:polymer_expressions/parser.dart' as pe;
29 import 'package:polymer_expressions/visitor.dart' as pe;
30
31 import 'common.dart';
32 import 'import_inliner.dart' show ImportInliner; // just for docs.
33 import 'messages.dart';
34
35 /// Combines Dart script tags into a single script tag, and creates a new Dart
36 /// file that calls the main function of each of the original script tags.
37 ///
38 /// This transformer assumes that all script tags point to external files. To
39 /// support script tags with inlined code, use this transformer after running
40 /// [ImportInliner] on an earlier phase.
41 ///
42 /// Internally, this transformer will convert each script tag into an import
43 /// statement to a library, and then uses `initPolymer` (see polymer.dart) to
44 /// process `@initMethod` and `@CustomTag` annotations in those libraries.
45 class ScriptCompactor extends Transformer {
46 final Resolvers resolvers;
47 final TransformOptions options;
48
49 ScriptCompactor(this.options, {String sdkDir})
50 // TODO(sigmund): consider restoring here a resolver that uses the real
51 // SDK once the analyzer is lazy and only an resolves what it needs:
52 //: resolvers = new Resolvers(sdkDir != null ? sdkDir : dartSdkDirectory);
53 : resolvers = new Resolvers.fromMock({
54 // The list of types below is derived from:
55 // * types we use via our smoke queries, including HtmlElement and
56 // types from `_typeHandlers` (deserialize.dart)
57 // * types that are used internally by the resolver (see
58 // _initializeFrom in resolver.dart).
59 'dart:core': '''
60 library dart.core;
61 class Object {}
62 class Function {}
63 class StackTrace {}
64 class Symbol {}
65 class Type {}
66
67 class String extends Object {}
68 class bool extends Object {}
69 class num extends Object {}
70 class int extends num {}
71 class double extends num {}
72 class DateTime extends Object {}
73 class Null extends Object {}
74
75 class Deprecated extends Object {
76 final String expires;
77 const Deprecated(this.expires);
78 }
79 const Object deprecated = const Deprecated("next release");
80 class _Override { const _Override(); }
81 const Object override = const _Override();
82 class _Proxy { const _Proxy(); }
83 const Object proxy = const _Proxy();
84
85 class List<V> extends Object {}
86 class Map<K, V> extends Object {}
87 ''',
88 'dart:html': '''
89 library dart.html;
90 class HtmlElement {}
91 ''',
92 });
93
94
95
96 /// Only run on entry point .html files.
97 // TODO(nweiz): This should just take an AssetId when barback <0.13.0 support
98 // is dropped.
99 Future<bool> isPrimary(idOrAsset) {
100 var id = idOrAsset is AssetId ? idOrAsset : idOrAsset.id;
101 return new Future.value(options.isHtmlEntryPoint(id));
102 }
103
104 Future apply(Transform transform) =>
105 new _ScriptCompactor(transform, options, resolvers).apply();
106 }
107
108 /// Helper class mainly use to flatten the async code.
109 class _ScriptCompactor extends PolymerTransformer {
110 final TransformOptions options;
111 final Transform transform;
112 final BuildLogger logger;
113 final AssetId docId;
114 final AssetId bootstrapId;
115
116 /// HTML document parsed from [docId].
117 Document document;
118
119 /// List of ids for each Dart entry script tag (the main tag and any tag
120 /// included on each custom element definition).
121 List<AssetId> entryLibraries;
122
123 /// Whether we are using the experimental bootstrap logic.
124 bool experimentalBootstrap;
125
126 /// Initializers that will register custom tags or invoke `initMethod`s.
127 final List<_Initializer> initializers = [];
128
129 /// Attributes published on a custom-tag. We make these available via
130 /// reflection even if @published was not used.
131 final Map<String, List<String>> publishedAttributes = {};
132
133 /// Hook needed to access the analyzer within barback transformers.
134 final Resolvers resolvers;
135
136 /// Resolved types used for analyzing the user's sources and generating code.
137 _ResolvedTypes types;
138
139 /// The resolver instance associated with a single run of this transformer.
140 Resolver resolver;
141
142 /// Code generator used to create the static initialization for smoke.
143 final generator = new SmokeCodeGenerator();
144
145 _SubExpressionVisitor expressionVisitor;
146
147 _ScriptCompactor(Transform transform, options, this.resolvers)
148 : transform = transform,
149 options = options,
150 logger = new BuildLogger(
151 transform, convertErrorsToWarnings: !options.releaseMode,
152 detailsUri: 'http://goo.gl/5HPeuP'),
153 docId = transform.primaryInput.id,
154 bootstrapId = transform.primaryInput.id.addExtension('_bootstrap.dart');
155
156 Future apply() =>
157 _loadDocument()
158 .then(_loadEntryLibraries)
159 .then(_processHtml)
160 .then(_emitNewEntrypoint)
161 .then((_) {
162 // Write out the logs collected by our [BuildLogger].
163 if (options.injectBuildLogsInOutput) return logger.writeOutput();
164 });
165
166 /// Loads the primary input as an html document.
167 Future _loadDocument() =>
168 readPrimaryAsHtml(transform, logger).then((doc) { document = doc; });
169
170 /// Populates [entryLibraries] as a list containing the asset ids of each
171 /// library loaded on a script tag. The actual work of computing this is done
172 /// in an earlier phase and emited in the `entrypoint._data` asset.
173 Future _loadEntryLibraries(_) =>
174 transform.readInputAsString(docId.addExtension('._data')).then((data) {
175 var map = JSON.decode(data);
176 experimentalBootstrap = map['experimental_bootstrap'];
177 entryLibraries = map['script_ids']
178 .map((id) => new AssetId.deserialize(id))
179 .toList();
180 return Future.forEach(entryLibraries, logger.addLogFilesFromAsset);
181 });
182
183 /// Removes unnecessary script tags, and identifies the main entry point Dart
184 /// script tag (if any).
185 void _processHtml(_) {
186 for (var tag in document.querySelectorAll('script')) {
187 var src = tag.attributes['src'];
188 if (src == 'packages/polymer/boot.js') {
189 tag.remove();
190 continue;
191 }
192 if (tag.attributes['type'] == 'application/dart') {
193 logger.warning(INTERNAL_ERROR_UNEXPECTED_SCRIPT, span: tag.sourceSpan);
194 }
195 }
196 }
197
198 /// Emits the main HTML and Dart bootstrap code for the application. If there
199 /// were not Dart entry point files, then this simply emits the original HTML.
200 Future _emitNewEntrypoint(_) {
201 // If we don't find code, there is nothing to do.
202 if (entryLibraries.isEmpty) return null;
203 return _initResolver()
204 .then(_extractUsesOfMirrors)
205 .then(_emitFiles)
206 .whenComplete(() {
207 if (resolver != null) resolver.release();
208 });
209 }
210
211 /// Load a resolver that computes information for every library in
212 /// [entryLibraries], then use it to initialize the [recorder] (for import
213 /// resolution) and to resolve specific elements (for analyzing the user's
214 /// code).
215 Future _initResolver() {
216 // We include 'polymer.dart' to simplify how we do resolution below. This
217 // way we can assume polymer is there, even if the user didn't include an
218 // import to it. If not, the polymer build will fail with an error when
219 // trying to create _ResolvedTypes below.
220 var libsToLoad = [new AssetId('polymer', 'lib/polymer.dart')]
221 ..addAll(entryLibraries);
222 return resolvers.get(transform, libsToLoad).then((r) {
223 resolver = r;
224 types = new _ResolvedTypes(resolver);
225 });
226 }
227
228 /// Inspects the entire program to find out anything that polymer accesses
229 /// using mirrors and produces static information that can be used to replace
230 /// the mirror-based loader and the uses of mirrors through the `smoke`
231 /// package. This includes:
232 ///
233 /// * visiting entry-libraries to extract initializers,
234 /// * visiting polymer-expressions to extract getters and setters,
235 /// * looking for published fields of custom elements, and
236 /// * looking for event handlers and callbacks of change notifications.
237 ///
238 void _extractUsesOfMirrors(_) {
239 // Generate getters and setters needed to evaluate polymer expressions, and
240 // extract information about published attributes.
241 expressionVisitor = new _SubExpressionVisitor(generator, logger);
242 new _HtmlExtractor(logger, generator, publishedAttributes,
243 expressionVisitor).visit(document);
244
245 // Create a recorder that uses analyzer data to feed data to [generator].
246 var recorder = new Recorder(generator,
247 (lib) => resolver.getImportUri(lib, from: bootstrapId).toString());
248
249 // Process all classes and top-level functions to include initializers,
250 // register custom elements, and include special fields and methods in
251 // custom element classes.
252 var functionsSeen = new Set<FunctionElement>();
253 var classesSeen = new Set<ClassElement>();
254 for (var id in entryLibraries) {
255 var lib = resolver.getLibrary(id);
256 for (var fun in _visibleTopLevelMethodsOf(lib)) {
257 if (functionsSeen.contains(fun)) continue;
258 functionsSeen.add(fun);
259 _processFunction(fun, id);
260 }
261
262 for (var cls in _visibleClassesOf(lib)) {
263 if (classesSeen.contains(cls)) continue;
264 classesSeen.add(cls);
265 _processClass(cls, id, recorder);
266 }
267 }
268 }
269
270 /// Process a class ([cls]). If it contains an appropriate [CustomTag]
271 /// annotation, we include an initializer to register this class, and make
272 /// sure to include everything that might be accessed or queried from them
273 /// using the smoke package. In particular, polymer uses smoke for the
274 /// following:
275 /// * invoke #registerCallback on custom elements classes, if present.
276 /// * query for methods ending in `*Changed`.
277 /// * query for methods with the `@ObserveProperty` annotation.
278 /// * query for non-final properties labeled with `@published`.
279 /// * read declarations of properties named in the `attributes` attribute.
280 /// * read/write the value of published properties .
281 /// * invoke methods in event handlers.
282 _processClass(ClassElement cls, AssetId id, Recorder recorder) {
283 if (!_hasPolymerMixin(cls)) return;
284
285 // Check whether the class has a @CustomTag annotation. Typically we expect
286 // a single @CustomTag, but it's possible to have several.
287 var tagNames = [];
288 for (var meta in cls.node.metadata) {
289 var tagName = _extractTagName(meta, cls);
290 if (tagName != null) tagNames.add(tagName);
291 }
292
293 if (cls.isPrivate && tagNames.isNotEmpty) {
294 var name = tagNames.first;
295 logger.error(PRIVATE_CUSTOM_TAG.create(
296 {'name': name, 'class': cls.name}),
297 span: _spanForNode(cls, cls.node.name));
298 return;
299 }
300
301 // Include #registerCallback if it exists. Note that by default lookupMember
302 // and query will also add the corresponding getters and setters.
303 recorder.lookupMember(cls, 'registerCallback');
304
305 // Include methods that end with *Changed.
306 recorder.runQuery(cls, new QueryOptions(
307 includeFields: false, includeProperties: false,
308 includeInherited: true, includeMethods: true,
309 includeUpTo: types.htmlElementElement,
310 matches: (n) => n.endsWith('Changed') && n != 'attributeChanged'));
311
312 // Include methods marked with @ObserveProperty.
313 recorder.runQuery(cls, new QueryOptions(
314 includeFields: false, includeProperties: false,
315 includeInherited: true, includeMethods: true,
316 includeUpTo: types.htmlElementElement,
317 withAnnotations: [types.observePropertyElement]));
318
319 // Include @published and @observable properties.
320 // Symbols in @published are used when resolving bindings on published
321 // attributes, symbols for @observable are used via path observers when
322 // implementing *Changed an @ObserveProperty.
323 // TODO(sigmund): consider including only those symbols mentioned in
324 // *Changed and @ObserveProperty instead.
325 recorder.runQuery(cls, new QueryOptions(
326 includeUpTo: types.htmlElementElement,
327 withAnnotations: [types.publishedElement, types.observableElement,
328 types.computedPropertyElement]));
329
330 // Include @ComputedProperty and process their expressions
331 var computed = [];
332 recorder.runQuery(cls, new QueryOptions(
333 includeUpTo: types.htmlElementElement,
334 withAnnotations: [types.computedPropertyElement]),
335 results: computed);
336 _processComputedExpressions(computed);
337
338 for (var tagName in tagNames) {
339 // Include an initializer that will call Polymer.register
340 initializers.add(new _CustomTagInitializer(id, tagName, cls.displayName));
341
342 // Include also properties published via the `attributes` attribute.
343 var attrs = publishedAttributes[tagName];
344 if (attrs == null) continue;
345 for (var attr in attrs) {
346 recorder.lookupMember(cls, attr, recursive: true,
347 includeUpTo: types.htmlElementElement);
348 }
349 }
350 }
351
352 /// Determines if [cls] or a supertype has a mixin of the Polymer class.
353 bool _hasPolymerMixin(ClassElement cls) {
354 while (cls != types.htmlElementElement) {
355 for (var m in cls.mixins) {
356 if (m.element == types.polymerClassElement) return true;
357 }
358 if (cls.supertype == null) return false;
359 cls = cls.supertype.element;
360 }
361 return false;
362 }
363
364 /// If [meta] is [CustomTag], extract the name associated with the tag.
365 String _extractTagName(Annotation meta, ClassElement cls) {
366 if (meta.element != types.customTagConstructor) return null;
367 return _extractFirstAnnotationArgument(meta, 'CustomTag', cls);
368 }
369
370 /// Extract the first argument of an annotation and validate that it's type is
371 /// String. For instance, return "bar" from `@Foo("bar")`.
372 String _extractFirstAnnotationArgument(Annotation meta, String name,
373 analyzer.Element context) {
374
375 // Read argument from the AST
376 var args = meta.arguments.arguments;
377 if (args == null || args.length == 0) {
378 logger.warning(MISSING_ANNOTATION_ARGUMENT.create({'name': name}),
379 span: _spanForNode(context, meta));
380 return null;
381 }
382
383 var lib = context;
384 while (lib is! LibraryElement) lib = lib.enclosingElement;
385 var res = resolver.evaluateConstant(lib, args[0]);
386 if (!res.isValid || res.value.type != types.stringType) {
387 logger.warning(INVALID_ANNOTATION_ARGUMENT.create({'name': name}),
388 span: _spanForNode(context, args[0]));
389 return null;
390 }
391 return res.value.stringValue;
392 }
393
394 /// Adds the top-level [function] as an initalizer if it's marked with
395 /// `@initMethod`.
396 _processFunction(FunctionElement function, AssetId id) {
397 bool initMethodFound = false;
398 for (var meta in function.metadata) {
399 var e = meta.element;
400 if (e is PropertyAccessorElement &&
401 e.variable == types.initMethodElement) {
402 initMethodFound = true;
403 break;
404 }
405 }
406 if (!initMethodFound) return;
407 if (function.isPrivate) {
408 logger.error(PRIVATE_INIT_METHOD.create({'name': function.displayName}),
409 span: _spanForNode(function, function.node.name));
410 return;
411 }
412 initializers.add(new _InitMethodInitializer(id, function.displayName));
413 }
414
415 /// Process members that are annotated with `@ComputedProperty` and records
416 /// the accessors of their expressions.
417 _processComputedExpressions(List<analyzer.Element> computed) {
418 var constructor = types.computedPropertyElement.constructors.first;
419 for (var member in computed) {
420 for (var meta in member.node.metadata) {
421 if (meta.element != constructor) continue;
422 var expr = _extractFirstAnnotationArgument(
423 meta, 'ComputedProperty', member);
424 if (expr == null) continue;
425 expressionVisitor.run(pe.parse(expr), true,
426 _spanForNode(member.enclosingElement, meta.arguments.arguments[0]));
427 }
428 }
429 }
430
431 /// Writes the final output for the bootstrap Dart file and entrypoint HTML
432 /// file.
433 void _emitFiles(_) {
434 StringBuffer code = new StringBuffer()..writeln(MAIN_HEADER);
435 Map<AssetId, String> prefixes = {};
436 int i = 0;
437 for (var id in entryLibraries) {
438 var url = assetUrlFor(id, bootstrapId, logger);
439 if (url == null) continue;
440 code.writeln("import '$url' as i$i;");
441 if (options.injectBuildLogsInOutput) {
442 code.writeln("import 'package:polymer/src/build/log_injector.dart';");
443 }
444 prefixes[id] = 'i$i';
445 i++;
446 }
447
448 // Include smoke initialization.
449 generator.writeImports(code);
450 generator.writeTopLevelDeclarations(code);
451 code.writeln('\nvoid main() {');
452 code.write(' useGeneratedCode(');
453 generator.writeStaticConfiguration(code);
454 code.writeln(');');
455
456 if (options.injectBuildLogsInOutput) {
457 var buildUrl = "${path.basename(docId.path)}$LOG_EXTENSION";
458 code.writeln(" new LogInjector().injectLogsFromUrl('$buildUrl');");
459 }
460
461 if (experimentalBootstrap) {
462 code.write(' startPolymer([');
463 } else {
464 code.write(' configureForDeployment([');
465 }
466
467 // Include initializers to switch from mirrors_loader to static_loader.
468 if (!initializers.isEmpty) {
469 code.writeln();
470 for (var init in initializers) {
471 var initCode = init.asCode(prefixes[init.assetId]);
472 code.write(" $initCode,\n");
473 }
474 code.writeln(' ]);');
475 } else {
476 if (experimentalBootstrap) logger.warning(NO_INITIALIZATION);
477 code.writeln(']);');
478 }
479 if (!experimentalBootstrap) {
480 code.writeln(' i${entryLibraries.length - 1}.main();');
481 }
482
483 // End of main().
484 code.writeln('}');
485 transform.addOutput(new Asset.fromString(bootstrapId, code.toString()));
486
487
488 // Emit the bootstrap .dart file
489 var srcUrl = path.url.basename(bootstrapId.path);
490 document.body.nodes.add(parseFragment(
491 '<script type="application/dart" src="$srcUrl"></script>'));
492
493 // Add the styles for the logger widget.
494 if (options.injectBuildLogsInOutput) {
495 document.head.append(parseFragment(
496 '<link rel="stylesheet" type="text/css"'
497 ' href="packages/polymer/src/build/log_injector.css">'));
498 }
499
500 transform.addOutput(new Asset.fromString(docId, document.outerHtml));
501 }
502
503 _spanForNode(analyzer.Element context, AstNode node) {
504 var file = resolver.getSourceFile(context);
505 return file.span(node.offset, node.end);
506 }
507 }
508
509 abstract class _Initializer {
510 AssetId get assetId;
511 String get symbolName;
512 String asCode(String prefix);
513 }
514
515 class _InitMethodInitializer implements _Initializer {
516 final AssetId assetId;
517 final String methodName;
518 String get symbolName => methodName;
519 _InitMethodInitializer(this.assetId, this.methodName);
520
521 String asCode(String prefix) => "$prefix.$methodName";
522 }
523
524 class _CustomTagInitializer implements _Initializer {
525 final AssetId assetId;
526 final String tagName;
527 final String typeName;
528 String get symbolName => typeName;
529 _CustomTagInitializer(this.assetId, this.tagName, this.typeName);
530
531 String asCode(String prefix) =>
532 "() => Polymer.register('$tagName', $prefix.$typeName)";
533 }
534
535 const MAIN_HEADER = """
536 library app_bootstrap;
537
538 import 'package:polymer/polymer.dart';
539 """;
540
541
542 /// An html visitor that:
543 /// * finds all polymer expressions and records the getters and setters that
544 /// will be needed to evaluate them at runtime.
545 /// * extracts all attributes declared in the `attribute` attributes of
546 /// polymer elements.
547 class _HtmlExtractor extends TreeVisitor {
548 final Map<String, List<String>> publishedAttributes;
549 final SmokeCodeGenerator generator;
550 final _SubExpressionVisitor expressionVisitor;
551 final BuildLogger logger;
552 bool _inTemplate = false;
553 bool _inPolymerJs = false;
554
555 _HtmlExtractor(this.logger, this.generator, this.publishedAttributes,
556 this.expressionVisitor);
557
558 void visitElement(Element node) {
559 var lastInPolymerJs = _inPolymerJs;
560 if (node.localName == 'template'
561 && node.attributes['is'] == 'auto-binding') {
562 _inPolymerJs = true;
563 }
564
565 if (_inTemplate) _processNormalElement(node);
566
567 if (node.localName == 'polymer-element') {
568 // Detect Polymer JS elements, the current logic is any element with only
569 // non-dart script tags.
570 var scripts = node.querySelectorAll('script');
571 _inPolymerJs = scripts.isNotEmpty &&
572 scripts.every((s) => s.attributes['type'] != 'application/dart');
573 _processPolymerElement(node);
574 _processNormalElement(node);
575 }
576
577 if (node.localName == 'template') {
578 var last = _inTemplate;
579 _inTemplate = true;
580 super.visitElement(node);
581 _inTemplate = last;
582 } else {
583 super.visitElement(node);
584 }
585 _inPolymerJs = lastInPolymerJs;
586 }
587
588 void visitText(Text node) {
589 // Nothing here applies if inside a polymer js element
590 if (!_inTemplate || _inPolymerJs) return;
591 var bindings = _Mustaches.parse(node.data);
592 if (bindings == null) return;
593 for (var e in bindings.expressions) {
594 _addExpression(e, false, false, node.sourceSpan);
595 }
596 }
597
598 /// Registers getters and setters for all published attributes.
599 void _processPolymerElement(Element node) {
600 // Nothing here applies if inside a polymer js element
601 if (_inPolymerJs) return;
602
603 var tagName = node.attributes['name'];
604 var value = node.attributes['attributes'];
605 if (value != null) {
606 publishedAttributes[tagName] =
607 value.split(ATTRIBUTES_REGEX).map((a) => a.trim()).toList();
608 }
609 }
610
611 /// Produces warnings for misuses of on-foo event handlers, and for instanting
612 /// custom tags incorrectly.
613 void _processNormalElement(Element node) {
614 // Nothing here applies if inside a polymer js element
615 if (_inPolymerJs) return;
616
617 var tag = node.localName;
618 var isCustomTag = isCustomTagName(tag) || node.attributes['is'] != null;
619
620 // Event handlers only allowed inside polymer-elements
621 node.attributes.forEach((name, value) {
622 var bindings = _Mustaches.parse(value);
623 if (bindings == null) return;
624 var isEvent = false;
625 var isTwoWay = false;
626 if (name is String) {
627 name = name.toLowerCase();
628 isEvent = name.startsWith('on-');
629 isTwoWay = !isEvent && bindings.isWhole && (isCustomTag ||
630 tag == 'input' && (name == 'value' || name =='checked') ||
631 tag == 'select' && (name == 'selectedindex' || name == 'value') ||
632 tag == 'textarea' && name == 'value');
633 }
634 for (var exp in bindings.expressions) {
635 _addExpression(exp, isEvent, isTwoWay, node.sourceSpan);
636 }
637 });
638 }
639
640 void _addExpression(String stringExpression, bool inEvent, bool isTwoWay,
641 SourceSpan span) {
642
643 if (inEvent) {
644 if (stringExpression.startsWith('@')) {
645 logger.warning(AT_EXPRESSION_REMOVED, span: span);
646 return;
647 }
648
649 if (stringExpression == '') return;
650 if (stringExpression.startsWith('_')) {
651 logger.warning(NO_PRIVATE_EVENT_HANDLERS, span: span);
652 return;
653 }
654 generator.addGetter(stringExpression);
655 generator.addSymbol(stringExpression);
656 }
657 expressionVisitor.run(pe.parse(stringExpression), isTwoWay, span);
658 }
659 }
660
661 /// A polymer-expression visitor that records every getter and setter that will
662 /// be needed to evaluate a single expression at runtime.
663 class _SubExpressionVisitor extends pe.RecursiveVisitor {
664 final SmokeCodeGenerator generator;
665 final BuildLogger logger;
666 bool _includeSetter;
667 SourceSpan _currentSpan;
668
669 _SubExpressionVisitor(this.generator, this.logger);
670
671 /// Visit [exp], and record getters and setters that are needed in order to
672 /// evaluate it at runtime. [includeSetter] is only true if this expression
673 /// occured in a context where it could be updated, for example in two-way
674 /// bindings such as `<input value={{exp}}>`.
675 void run(pe.Expression exp, bool includeSetter, span) {
676 _currentSpan = span;
677 _includeSetter = includeSetter;
678 visit(exp);
679 }
680
681 /// Adds a getter and symbol for [name], and optionally a setter.
682 _add(String name) {
683 if (name.startsWith('_')) {
684 logger.warning(NO_PRIVATE_SYMBOLS_IN_BINDINGS, span: _currentSpan);
685 return;
686 }
687 generator.addGetter(name);
688 generator.addSymbol(name);
689 if (_includeSetter) generator.addSetter(name);
690 }
691
692 void preVisitExpression(e) {
693 // For two-way bindings the outermost expression may be updated, so we need
694 // both the getter and the setter, but we only need the getter for
695 // subexpressions. We exclude setters as soon as we go deeper in the tree,
696 // except when we see a filter (that can potentially be a two-way
697 // transformer).
698 if (e is pe.BinaryOperator && e.operator == '|') return;
699 _includeSetter = false;
700 }
701
702 visitIdentifier(pe.Identifier e) {
703 if (e.value != 'this') _add(e.value);
704 super.visitIdentifier(e);
705 }
706
707 visitGetter(pe.Getter e) {
708 _add(e.name);
709 super.visitGetter(e);
710 }
711
712 visitInvoke(pe.Invoke e) {
713 _includeSetter = false; // Invoke is only valid as an r-value.
714 if (e.method != null) _add(e.method);
715 super.visitInvoke(e);
716 }
717 }
718
719 /// Parses and collects information about bindings found in polymer templates.
720 class _Mustaches {
721 /// Each expression that appears within `{{...}}` and `[[...]]`.
722 final List<String> expressions;
723
724 /// Whether the whole text returned by [parse] was a single expression.
725 final bool isWhole;
726
727 _Mustaches(this.isWhole, this.expressions);
728
729 static _Mustaches parse(String text) {
730 if (text == null || text.isEmpty) return null;
731 // Use template-binding's parser, but provide a delegate function factory to
732 // save the expressions without parsing them as [PropertyPath]s.
733 var tokens = MustacheTokens.parse(text, (s) => () => s);
734 if (tokens == null) return null;
735 var length = tokens.length;
736 bool isWhole = length == 1 && tokens.getText(length) == '' &&
737 tokens.getText(0) == '';
738 var expressions = new List(length);
739 for (int i = 0; i < length; i++) {
740 expressions[i] = tokens.getPrepareBinding(i)();
741 }
742 return new _Mustaches(isWhole, expressions);
743 }
744 }
745
746 /// Holds types that are used in queries
747 class _ResolvedTypes {
748 /// Element representing `HtmlElement`.
749 final ClassElement htmlElementElement;
750
751 /// Element representing `String`.
752 final InterfaceType stringType;
753
754 /// Element representing `Polymer`.
755 final ClassElement polymerClassElement;
756
757 /// Element representing the constructor of `@CustomTag`.
758 final ConstructorElement customTagConstructor;
759
760 /// Element representing the type of `@published`.
761 final ClassElement publishedElement;
762
763 /// Element representing the type of `@observable`.
764 final ClassElement observableElement;
765
766 /// Element representing the type of `@ObserveProperty`.
767 final ClassElement observePropertyElement;
768
769 /// Element representing the type of `@ComputedProperty`.
770 final ClassElement computedPropertyElement;
771
772 /// Element representing the `@initMethod` annotation.
773 final TopLevelVariableElement initMethodElement;
774
775
776 factory _ResolvedTypes(Resolver resolver) {
777 // Load class elements that are used in queries for codegen.
778 var polymerLib = resolver.getLibrary(
779 new AssetId('polymer', 'lib/polymer.dart'));
780 if (polymerLib == null) _definitionError('the polymer library');
781
782 var htmlLib = resolver.getLibraryByUri(Uri.parse('dart:html'));
783 if (htmlLib == null) _definitionError('the "dart:html" library');
784
785 var coreLib = resolver.getLibraryByUri(Uri.parse('dart:core'));
786 if (coreLib == null) _definitionError('the "dart:core" library');
787
788 var observeLib = resolver.getLibrary(
789 new AssetId('observe', 'lib/src/metadata.dart'));
790 if (observeLib == null) _definitionError('the observe library');
791
792 var initMethodElement = null;
793 for (var unit in polymerLib.parts) {
794 if (unit.uri == 'src/loader.dart') {
795 initMethodElement = unit.topLevelVariables.firstWhere(
796 (t) => t.displayName == 'initMethod');
797 break;
798 }
799 }
800 var customTagConstructor =
801 _lookupType(polymerLib, 'CustomTag').constructors.first;
802 var publishedElement = _lookupType(polymerLib, 'PublishedProperty');
803 var observableElement = _lookupType(observeLib, 'ObservableProperty');
804 var observePropertyElement = _lookupType(polymerLib, 'ObserveProperty');
805 var computedPropertyElement = _lookupType(polymerLib, 'ComputedProperty');
806 var polymerClassElement = _lookupType(polymerLib, 'Polymer');
807 var htmlElementElement = _lookupType(htmlLib, 'HtmlElement');
808 var stringType = _lookupType(coreLib, 'String').type;
809 if (initMethodElement == null) _definitionError('@initMethod');
810
811 return new _ResolvedTypes.internal(htmlElementElement, stringType,
812 polymerClassElement, customTagConstructor, publishedElement,
813 observableElement, observePropertyElement, computedPropertyElement,
814 initMethodElement);
815 }
816
817 _ResolvedTypes.internal(this.htmlElementElement, this.stringType,
818 this.polymerClassElement, this.customTagConstructor,
819 this.publishedElement, this.observableElement,
820 this.observePropertyElement, this.computedPropertyElement,
821 this.initMethodElement);
822
823 static _lookupType(LibraryElement lib, String typeName) {
824 var result = lib.getType(typeName);
825 if (result == null) _definitionError(typeName);
826 return result;
827 }
828
829 static _definitionError(name) {
830 throw new StateError("Internal error in polymer-builder: couldn't find "
831 "definition of $name.");
832 }
833 }
834
835 /// Retrieves all classses that are visible if you were to import [lib]. This
836 /// includes exported classes from other libraries.
837 List<ClassElement> _visibleClassesOf(LibraryElement lib) {
838 var result = [];
839 result.addAll(lib.units.expand((u) => u.types));
840 for (var e in lib.exports) {
841 var exported = e.exportedLibrary.units.expand((u) => u.types).toList();
842 _filter(exported, e.combinators);
843 result.addAll(exported);
844 }
845 return result;
846 }
847
848 /// Retrieves all top-level methods that are visible if you were to import
849 /// [lib]. This includes exported methods from other libraries too.
850 List<FunctionElement> _visibleTopLevelMethodsOf(LibraryElement lib) {
851 var result = [];
852 result.addAll(lib.units.expand((u) => u.functions));
853 for (var e in lib.exports) {
854 var exported = e.exportedLibrary.units.expand((u) => u.functions).toList();
855 _filter(exported, e.combinators);
856 result.addAll(exported);
857 }
858 return result;
859 }
860
861 /// Filters [elements] that come from an export, according to its show/hide
862 /// combinators. This modifies [elements] in place.
863 void _filter(List<analyzer.Element> elements,
864 List<NamespaceCombinator> combinators) {
865 for (var c in combinators) {
866 if (c is ShowElementCombinator) {
867 var show = c.shownNames.toSet();
868 elements.retainWhere((e) => show.contains(e.displayName));
869 } else if (c is HideElementCombinator) {
870 var hide = c.hiddenNames.toSet();
871 elements.removeWhere((e) => hide.contains(e.displayName));
872 }
873 }
874 }
OLDNEW
« no previous file with comments | « observatory_pub_packages/polymer/src/build/runner.dart ('k') | observatory_pub_packages/polymer/src/build/utils.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698