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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: observatory_pub_packages/polymer/src/build/script_compactor.dart
===================================================================
--- observatory_pub_packages/polymer/src/build/script_compactor.dart (revision 0)
+++ observatory_pub_packages/polymer/src/build/script_compactor.dart (working copy)
@@ -0,0 +1,874 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Transfomer that combines multiple dart script tags into a single one.
+library polymer.src.build.script_compactor;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:html5lib/dom.dart' show Document, Element, Text;
+import 'package:html5lib/dom_parsing.dart';
+import 'package:html5lib/parser.dart' show parseFragment;
+import 'package:analyzer/src/generated/ast.dart';
+import 'package:analyzer/src/generated/element.dart' hide Element;
+import 'package:analyzer/src/generated/element.dart' as analyzer show Element;
+import 'package:barback/barback.dart';
+import 'package:code_transformers/messages/build_logger.dart';
+import 'package:path/path.dart' as path;
+import 'package:source_span/source_span.dart';
+import 'package:smoke/codegen/generator.dart';
+import 'package:smoke/codegen/recorder.dart';
+import 'package:code_transformers/resolver.dart';
+import 'package:code_transformers/src/dart_sdk.dart';
+import 'package:template_binding/src/mustache_tokens.dart' show MustacheTokens;
+
+import 'package:polymer_expressions/expression.dart' as pe;
+import 'package:polymer_expressions/parser.dart' as pe;
+import 'package:polymer_expressions/visitor.dart' as pe;
+
+import 'common.dart';
+import 'import_inliner.dart' show ImportInliner; // just for docs.
+import 'messages.dart';
+
+/// Combines Dart script tags into a single script tag, and creates a new Dart
+/// file that calls the main function of each of the original script tags.
+///
+/// This transformer assumes that all script tags point to external files. To
+/// support script tags with inlined code, use this transformer after running
+/// [ImportInliner] on an earlier phase.
+///
+/// Internally, this transformer will convert each script tag into an import
+/// statement to a library, and then uses `initPolymer` (see polymer.dart) to
+/// process `@initMethod` and `@CustomTag` annotations in those libraries.
+class ScriptCompactor extends Transformer {
+ final Resolvers resolvers;
+ final TransformOptions options;
+
+ ScriptCompactor(this.options, {String sdkDir})
+ // TODO(sigmund): consider restoring here a resolver that uses the real
+ // SDK once the analyzer is lazy and only an resolves what it needs:
+ //: resolvers = new Resolvers(sdkDir != null ? sdkDir : dartSdkDirectory);
+ : resolvers = new Resolvers.fromMock({
+ // The list of types below is derived from:
+ // * types we use via our smoke queries, including HtmlElement and
+ // types from `_typeHandlers` (deserialize.dart)
+ // * types that are used internally by the resolver (see
+ // _initializeFrom in resolver.dart).
+ 'dart:core': '''
+ library dart.core;
+ class Object {}
+ class Function {}
+ class StackTrace {}
+ class Symbol {}
+ class Type {}
+
+ class String extends Object {}
+ class bool extends Object {}
+ class num extends Object {}
+ class int extends num {}
+ class double extends num {}
+ class DateTime extends Object {}
+ class Null extends Object {}
+
+ class Deprecated extends Object {
+ final String expires;
+ const Deprecated(this.expires);
+ }
+ const Object deprecated = const Deprecated("next release");
+ class _Override { const _Override(); }
+ const Object override = const _Override();
+ class _Proxy { const _Proxy(); }
+ const Object proxy = const _Proxy();
+
+ class List<V> extends Object {}
+ class Map<K, V> extends Object {}
+ ''',
+ 'dart:html': '''
+ library dart.html;
+ class HtmlElement {}
+ ''',
+ });
+
+
+
+ /// Only run on entry point .html files.
+ // TODO(nweiz): This should just take an AssetId when barback <0.13.0 support
+ // is dropped.
+ Future<bool> isPrimary(idOrAsset) {
+ var id = idOrAsset is AssetId ? idOrAsset : idOrAsset.id;
+ return new Future.value(options.isHtmlEntryPoint(id));
+ }
+
+ Future apply(Transform transform) =>
+ new _ScriptCompactor(transform, options, resolvers).apply();
+}
+
+/// Helper class mainly use to flatten the async code.
+class _ScriptCompactor extends PolymerTransformer {
+ final TransformOptions options;
+ final Transform transform;
+ final BuildLogger logger;
+ final AssetId docId;
+ final AssetId bootstrapId;
+
+ /// HTML document parsed from [docId].
+ Document document;
+
+ /// List of ids for each Dart entry script tag (the main tag and any tag
+ /// included on each custom element definition).
+ List<AssetId> entryLibraries;
+
+ /// Whether we are using the experimental bootstrap logic.
+ bool experimentalBootstrap;
+
+ /// Initializers that will register custom tags or invoke `initMethod`s.
+ final List<_Initializer> initializers = [];
+
+ /// Attributes published on a custom-tag. We make these available via
+ /// reflection even if @published was not used.
+ final Map<String, List<String>> publishedAttributes = {};
+
+ /// Hook needed to access the analyzer within barback transformers.
+ final Resolvers resolvers;
+
+ /// Resolved types used for analyzing the user's sources and generating code.
+ _ResolvedTypes types;
+
+ /// The resolver instance associated with a single run of this transformer.
+ Resolver resolver;
+
+ /// Code generator used to create the static initialization for smoke.
+ final generator = new SmokeCodeGenerator();
+
+ _SubExpressionVisitor expressionVisitor;
+
+ _ScriptCompactor(Transform transform, options, this.resolvers)
+ : transform = transform,
+ options = options,
+ logger = new BuildLogger(
+ transform, convertErrorsToWarnings: !options.releaseMode,
+ detailsUri: 'http://goo.gl/5HPeuP'),
+ docId = transform.primaryInput.id,
+ bootstrapId = transform.primaryInput.id.addExtension('_bootstrap.dart');
+
+ Future apply() =>
+ _loadDocument()
+ .then(_loadEntryLibraries)
+ .then(_processHtml)
+ .then(_emitNewEntrypoint)
+ .then((_) {
+ // Write out the logs collected by our [BuildLogger].
+ if (options.injectBuildLogsInOutput) return logger.writeOutput();
+ });
+
+ /// Loads the primary input as an html document.
+ Future _loadDocument() =>
+ readPrimaryAsHtml(transform, logger).then((doc) { document = doc; });
+
+ /// Populates [entryLibraries] as a list containing the asset ids of each
+ /// library loaded on a script tag. The actual work of computing this is done
+ /// in an earlier phase and emited in the `entrypoint._data` asset.
+ Future _loadEntryLibraries(_) =>
+ transform.readInputAsString(docId.addExtension('._data')).then((data) {
+ var map = JSON.decode(data);
+ experimentalBootstrap = map['experimental_bootstrap'];
+ entryLibraries = map['script_ids']
+ .map((id) => new AssetId.deserialize(id))
+ .toList();
+ return Future.forEach(entryLibraries, logger.addLogFilesFromAsset);
+ });
+
+ /// Removes unnecessary script tags, and identifies the main entry point Dart
+ /// script tag (if any).
+ void _processHtml(_) {
+ for (var tag in document.querySelectorAll('script')) {
+ var src = tag.attributes['src'];
+ if (src == 'packages/polymer/boot.js') {
+ tag.remove();
+ continue;
+ }
+ if (tag.attributes['type'] == 'application/dart') {
+ logger.warning(INTERNAL_ERROR_UNEXPECTED_SCRIPT, span: tag.sourceSpan);
+ }
+ }
+ }
+
+ /// Emits the main HTML and Dart bootstrap code for the application. If there
+ /// were not Dart entry point files, then this simply emits the original HTML.
+ Future _emitNewEntrypoint(_) {
+ // If we don't find code, there is nothing to do.
+ if (entryLibraries.isEmpty) return null;
+ return _initResolver()
+ .then(_extractUsesOfMirrors)
+ .then(_emitFiles)
+ .whenComplete(() {
+ if (resolver != null) resolver.release();
+ });
+ }
+
+ /// Load a resolver that computes information for every library in
+ /// [entryLibraries], then use it to initialize the [recorder] (for import
+ /// resolution) and to resolve specific elements (for analyzing the user's
+ /// code).
+ Future _initResolver() {
+ // We include 'polymer.dart' to simplify how we do resolution below. This
+ // way we can assume polymer is there, even if the user didn't include an
+ // import to it. If not, the polymer build will fail with an error when
+ // trying to create _ResolvedTypes below.
+ var libsToLoad = [new AssetId('polymer', 'lib/polymer.dart')]
+ ..addAll(entryLibraries);
+ return resolvers.get(transform, libsToLoad).then((r) {
+ resolver = r;
+ types = new _ResolvedTypes(resolver);
+ });
+ }
+
+ /// Inspects the entire program to find out anything that polymer accesses
+ /// using mirrors and produces static information that can be used to replace
+ /// the mirror-based loader and the uses of mirrors through the `smoke`
+ /// package. This includes:
+ ///
+ /// * visiting entry-libraries to extract initializers,
+ /// * visiting polymer-expressions to extract getters and setters,
+ /// * looking for published fields of custom elements, and
+ /// * looking for event handlers and callbacks of change notifications.
+ ///
+ void _extractUsesOfMirrors(_) {
+ // Generate getters and setters needed to evaluate polymer expressions, and
+ // extract information about published attributes.
+ expressionVisitor = new _SubExpressionVisitor(generator, logger);
+ new _HtmlExtractor(logger, generator, publishedAttributes,
+ expressionVisitor).visit(document);
+
+ // Create a recorder that uses analyzer data to feed data to [generator].
+ var recorder = new Recorder(generator,
+ (lib) => resolver.getImportUri(lib, from: bootstrapId).toString());
+
+ // Process all classes and top-level functions to include initializers,
+ // register custom elements, and include special fields and methods in
+ // custom element classes.
+ var functionsSeen = new Set<FunctionElement>();
+ var classesSeen = new Set<ClassElement>();
+ for (var id in entryLibraries) {
+ var lib = resolver.getLibrary(id);
+ for (var fun in _visibleTopLevelMethodsOf(lib)) {
+ if (functionsSeen.contains(fun)) continue;
+ functionsSeen.add(fun);
+ _processFunction(fun, id);
+ }
+
+ for (var cls in _visibleClassesOf(lib)) {
+ if (classesSeen.contains(cls)) continue;
+ classesSeen.add(cls);
+ _processClass(cls, id, recorder);
+ }
+ }
+ }
+
+ /// Process a class ([cls]). If it contains an appropriate [CustomTag]
+ /// annotation, we include an initializer to register this class, and make
+ /// sure to include everything that might be accessed or queried from them
+ /// using the smoke package. In particular, polymer uses smoke for the
+ /// following:
+ /// * invoke #registerCallback on custom elements classes, if present.
+ /// * query for methods ending in `*Changed`.
+ /// * query for methods with the `@ObserveProperty` annotation.
+ /// * query for non-final properties labeled with `@published`.
+ /// * read declarations of properties named in the `attributes` attribute.
+ /// * read/write the value of published properties .
+ /// * invoke methods in event handlers.
+ _processClass(ClassElement cls, AssetId id, Recorder recorder) {
+ if (!_hasPolymerMixin(cls)) return;
+
+ // Check whether the class has a @CustomTag annotation. Typically we expect
+ // a single @CustomTag, but it's possible to have several.
+ var tagNames = [];
+ for (var meta in cls.node.metadata) {
+ var tagName = _extractTagName(meta, cls);
+ if (tagName != null) tagNames.add(tagName);
+ }
+
+ if (cls.isPrivate && tagNames.isNotEmpty) {
+ var name = tagNames.first;
+ logger.error(PRIVATE_CUSTOM_TAG.create(
+ {'name': name, 'class': cls.name}),
+ span: _spanForNode(cls, cls.node.name));
+ return;
+ }
+
+ // Include #registerCallback if it exists. Note that by default lookupMember
+ // and query will also add the corresponding getters and setters.
+ recorder.lookupMember(cls, 'registerCallback');
+
+ // Include methods that end with *Changed.
+ recorder.runQuery(cls, new QueryOptions(
+ includeFields: false, includeProperties: false,
+ includeInherited: true, includeMethods: true,
+ includeUpTo: types.htmlElementElement,
+ matches: (n) => n.endsWith('Changed') && n != 'attributeChanged'));
+
+ // Include methods marked with @ObserveProperty.
+ recorder.runQuery(cls, new QueryOptions(
+ includeFields: false, includeProperties: false,
+ includeInherited: true, includeMethods: true,
+ includeUpTo: types.htmlElementElement,
+ withAnnotations: [types.observePropertyElement]));
+
+ // Include @published and @observable properties.
+ // Symbols in @published are used when resolving bindings on published
+ // attributes, symbols for @observable are used via path observers when
+ // implementing *Changed an @ObserveProperty.
+ // TODO(sigmund): consider including only those symbols mentioned in
+ // *Changed and @ObserveProperty instead.
+ recorder.runQuery(cls, new QueryOptions(
+ includeUpTo: types.htmlElementElement,
+ withAnnotations: [types.publishedElement, types.observableElement,
+ types.computedPropertyElement]));
+
+ // Include @ComputedProperty and process their expressions
+ var computed = [];
+ recorder.runQuery(cls, new QueryOptions(
+ includeUpTo: types.htmlElementElement,
+ withAnnotations: [types.computedPropertyElement]),
+ results: computed);
+ _processComputedExpressions(computed);
+
+ for (var tagName in tagNames) {
+ // Include an initializer that will call Polymer.register
+ initializers.add(new _CustomTagInitializer(id, tagName, cls.displayName));
+
+ // Include also properties published via the `attributes` attribute.
+ var attrs = publishedAttributes[tagName];
+ if (attrs == null) continue;
+ for (var attr in attrs) {
+ recorder.lookupMember(cls, attr, recursive: true,
+ includeUpTo: types.htmlElementElement);
+ }
+ }
+ }
+
+ /// Determines if [cls] or a supertype has a mixin of the Polymer class.
+ bool _hasPolymerMixin(ClassElement cls) {
+ while (cls != types.htmlElementElement) {
+ for (var m in cls.mixins) {
+ if (m.element == types.polymerClassElement) return true;
+ }
+ if (cls.supertype == null) return false;
+ cls = cls.supertype.element;
+ }
+ return false;
+ }
+
+ /// If [meta] is [CustomTag], extract the name associated with the tag.
+ String _extractTagName(Annotation meta, ClassElement cls) {
+ if (meta.element != types.customTagConstructor) return null;
+ return _extractFirstAnnotationArgument(meta, 'CustomTag', cls);
+ }
+
+ /// Extract the first argument of an annotation and validate that it's type is
+ /// String. For instance, return "bar" from `@Foo("bar")`.
+ String _extractFirstAnnotationArgument(Annotation meta, String name,
+ analyzer.Element context) {
+
+ // Read argument from the AST
+ var args = meta.arguments.arguments;
+ if (args == null || args.length == 0) {
+ logger.warning(MISSING_ANNOTATION_ARGUMENT.create({'name': name}),
+ span: _spanForNode(context, meta));
+ return null;
+ }
+
+ var lib = context;
+ while (lib is! LibraryElement) lib = lib.enclosingElement;
+ var res = resolver.evaluateConstant(lib, args[0]);
+ if (!res.isValid || res.value.type != types.stringType) {
+ logger.warning(INVALID_ANNOTATION_ARGUMENT.create({'name': name}),
+ span: _spanForNode(context, args[0]));
+ return null;
+ }
+ return res.value.stringValue;
+ }
+
+ /// Adds the top-level [function] as an initalizer if it's marked with
+ /// `@initMethod`.
+ _processFunction(FunctionElement function, AssetId id) {
+ bool initMethodFound = false;
+ for (var meta in function.metadata) {
+ var e = meta.element;
+ if (e is PropertyAccessorElement &&
+ e.variable == types.initMethodElement) {
+ initMethodFound = true;
+ break;
+ }
+ }
+ if (!initMethodFound) return;
+ if (function.isPrivate) {
+ logger.error(PRIVATE_INIT_METHOD.create({'name': function.displayName}),
+ span: _spanForNode(function, function.node.name));
+ return;
+ }
+ initializers.add(new _InitMethodInitializer(id, function.displayName));
+ }
+
+ /// Process members that are annotated with `@ComputedProperty` and records
+ /// the accessors of their expressions.
+ _processComputedExpressions(List<analyzer.Element> computed) {
+ var constructor = types.computedPropertyElement.constructors.first;
+ for (var member in computed) {
+ for (var meta in member.node.metadata) {
+ if (meta.element != constructor) continue;
+ var expr = _extractFirstAnnotationArgument(
+ meta, 'ComputedProperty', member);
+ if (expr == null) continue;
+ expressionVisitor.run(pe.parse(expr), true,
+ _spanForNode(member.enclosingElement, meta.arguments.arguments[0]));
+ }
+ }
+ }
+
+ /// Writes the final output for the bootstrap Dart file and entrypoint HTML
+ /// file.
+ void _emitFiles(_) {
+ StringBuffer code = new StringBuffer()..writeln(MAIN_HEADER);
+ Map<AssetId, String> prefixes = {};
+ int i = 0;
+ for (var id in entryLibraries) {
+ var url = assetUrlFor(id, bootstrapId, logger);
+ if (url == null) continue;
+ code.writeln("import '$url' as i$i;");
+ if (options.injectBuildLogsInOutput) {
+ code.writeln("import 'package:polymer/src/build/log_injector.dart';");
+ }
+ prefixes[id] = 'i$i';
+ i++;
+ }
+
+ // Include smoke initialization.
+ generator.writeImports(code);
+ generator.writeTopLevelDeclarations(code);
+ code.writeln('\nvoid main() {');
+ code.write(' useGeneratedCode(');
+ generator.writeStaticConfiguration(code);
+ code.writeln(');');
+
+ if (options.injectBuildLogsInOutput) {
+ var buildUrl = "${path.basename(docId.path)}$LOG_EXTENSION";
+ code.writeln(" new LogInjector().injectLogsFromUrl('$buildUrl');");
+ }
+
+ if (experimentalBootstrap) {
+ code.write(' startPolymer([');
+ } else {
+ code.write(' configureForDeployment([');
+ }
+
+ // Include initializers to switch from mirrors_loader to static_loader.
+ if (!initializers.isEmpty) {
+ code.writeln();
+ for (var init in initializers) {
+ var initCode = init.asCode(prefixes[init.assetId]);
+ code.write(" $initCode,\n");
+ }
+ code.writeln(' ]);');
+ } else {
+ if (experimentalBootstrap) logger.warning(NO_INITIALIZATION);
+ code.writeln(']);');
+ }
+ if (!experimentalBootstrap) {
+ code.writeln(' i${entryLibraries.length - 1}.main();');
+ }
+
+ // End of main().
+ code.writeln('}');
+ transform.addOutput(new Asset.fromString(bootstrapId, code.toString()));
+
+
+ // Emit the bootstrap .dart file
+ var srcUrl = path.url.basename(bootstrapId.path);
+ document.body.nodes.add(parseFragment(
+ '<script type="application/dart" src="$srcUrl"></script>'));
+
+ // Add the styles for the logger widget.
+ if (options.injectBuildLogsInOutput) {
+ document.head.append(parseFragment(
+ '<link rel="stylesheet" type="text/css"'
+ ' href="packages/polymer/src/build/log_injector.css">'));
+ }
+
+ transform.addOutput(new Asset.fromString(docId, document.outerHtml));
+ }
+
+ _spanForNode(analyzer.Element context, AstNode node) {
+ var file = resolver.getSourceFile(context);
+ return file.span(node.offset, node.end);
+ }
+}
+
+abstract class _Initializer {
+ AssetId get assetId;
+ String get symbolName;
+ String asCode(String prefix);
+}
+
+class _InitMethodInitializer implements _Initializer {
+ final AssetId assetId;
+ final String methodName;
+ String get symbolName => methodName;
+ _InitMethodInitializer(this.assetId, this.methodName);
+
+ String asCode(String prefix) => "$prefix.$methodName";
+}
+
+class _CustomTagInitializer implements _Initializer {
+ final AssetId assetId;
+ final String tagName;
+ final String typeName;
+ String get symbolName => typeName;
+ _CustomTagInitializer(this.assetId, this.tagName, this.typeName);
+
+ String asCode(String prefix) =>
+ "() => Polymer.register('$tagName', $prefix.$typeName)";
+}
+
+const MAIN_HEADER = """
+library app_bootstrap;
+
+import 'package:polymer/polymer.dart';
+""";
+
+
+/// An html visitor that:
+/// * finds all polymer expressions and records the getters and setters that
+/// will be needed to evaluate them at runtime.
+/// * extracts all attributes declared in the `attribute` attributes of
+/// polymer elements.
+class _HtmlExtractor extends TreeVisitor {
+ final Map<String, List<String>> publishedAttributes;
+ final SmokeCodeGenerator generator;
+ final _SubExpressionVisitor expressionVisitor;
+ final BuildLogger logger;
+ bool _inTemplate = false;
+ bool _inPolymerJs = false;
+
+ _HtmlExtractor(this.logger, this.generator, this.publishedAttributes,
+ this.expressionVisitor);
+
+ void visitElement(Element node) {
+ var lastInPolymerJs = _inPolymerJs;
+ if (node.localName == 'template'
+ && node.attributes['is'] == 'auto-binding') {
+ _inPolymerJs = true;
+ }
+
+ if (_inTemplate) _processNormalElement(node);
+
+ if (node.localName == 'polymer-element') {
+ // Detect Polymer JS elements, the current logic is any element with only
+ // non-dart script tags.
+ var scripts = node.querySelectorAll('script');
+ _inPolymerJs = scripts.isNotEmpty &&
+ scripts.every((s) => s.attributes['type'] != 'application/dart');
+ _processPolymerElement(node);
+ _processNormalElement(node);
+ }
+
+ if (node.localName == 'template') {
+ var last = _inTemplate;
+ _inTemplate = true;
+ super.visitElement(node);
+ _inTemplate = last;
+ } else {
+ super.visitElement(node);
+ }
+ _inPolymerJs = lastInPolymerJs;
+ }
+
+ void visitText(Text node) {
+ // Nothing here applies if inside a polymer js element
+ if (!_inTemplate || _inPolymerJs) return;
+ var bindings = _Mustaches.parse(node.data);
+ if (bindings == null) return;
+ for (var e in bindings.expressions) {
+ _addExpression(e, false, false, node.sourceSpan);
+ }
+ }
+
+ /// Registers getters and setters for all published attributes.
+ void _processPolymerElement(Element node) {
+ // Nothing here applies if inside a polymer js element
+ if (_inPolymerJs) return;
+
+ var tagName = node.attributes['name'];
+ var value = node.attributes['attributes'];
+ if (value != null) {
+ publishedAttributes[tagName] =
+ value.split(ATTRIBUTES_REGEX).map((a) => a.trim()).toList();
+ }
+ }
+
+ /// Produces warnings for misuses of on-foo event handlers, and for instanting
+ /// custom tags incorrectly.
+ void _processNormalElement(Element node) {
+ // Nothing here applies if inside a polymer js element
+ if (_inPolymerJs) return;
+
+ var tag = node.localName;
+ var isCustomTag = isCustomTagName(tag) || node.attributes['is'] != null;
+
+ // Event handlers only allowed inside polymer-elements
+ node.attributes.forEach((name, value) {
+ var bindings = _Mustaches.parse(value);
+ if (bindings == null) return;
+ var isEvent = false;
+ var isTwoWay = false;
+ if (name is String) {
+ name = name.toLowerCase();
+ isEvent = name.startsWith('on-');
+ isTwoWay = !isEvent && bindings.isWhole && (isCustomTag ||
+ tag == 'input' && (name == 'value' || name =='checked') ||
+ tag == 'select' && (name == 'selectedindex' || name == 'value') ||
+ tag == 'textarea' && name == 'value');
+ }
+ for (var exp in bindings.expressions) {
+ _addExpression(exp, isEvent, isTwoWay, node.sourceSpan);
+ }
+ });
+ }
+
+ void _addExpression(String stringExpression, bool inEvent, bool isTwoWay,
+ SourceSpan span) {
+
+ if (inEvent) {
+ if (stringExpression.startsWith('@')) {
+ logger.warning(AT_EXPRESSION_REMOVED, span: span);
+ return;
+ }
+
+ if (stringExpression == '') return;
+ if (stringExpression.startsWith('_')) {
+ logger.warning(NO_PRIVATE_EVENT_HANDLERS, span: span);
+ return;
+ }
+ generator.addGetter(stringExpression);
+ generator.addSymbol(stringExpression);
+ }
+ expressionVisitor.run(pe.parse(stringExpression), isTwoWay, span);
+ }
+}
+
+/// A polymer-expression visitor that records every getter and setter that will
+/// be needed to evaluate a single expression at runtime.
+class _SubExpressionVisitor extends pe.RecursiveVisitor {
+ final SmokeCodeGenerator generator;
+ final BuildLogger logger;
+ bool _includeSetter;
+ SourceSpan _currentSpan;
+
+ _SubExpressionVisitor(this.generator, this.logger);
+
+ /// Visit [exp], and record getters and setters that are needed in order to
+ /// evaluate it at runtime. [includeSetter] is only true if this expression
+ /// occured in a context where it could be updated, for example in two-way
+ /// bindings such as `<input value={{exp}}>`.
+ void run(pe.Expression exp, bool includeSetter, span) {
+ _currentSpan = span;
+ _includeSetter = includeSetter;
+ visit(exp);
+ }
+
+ /// Adds a getter and symbol for [name], and optionally a setter.
+ _add(String name) {
+ if (name.startsWith('_')) {
+ logger.warning(NO_PRIVATE_SYMBOLS_IN_BINDINGS, span: _currentSpan);
+ return;
+ }
+ generator.addGetter(name);
+ generator.addSymbol(name);
+ if (_includeSetter) generator.addSetter(name);
+ }
+
+ void preVisitExpression(e) {
+ // For two-way bindings the outermost expression may be updated, so we need
+ // both the getter and the setter, but we only need the getter for
+ // subexpressions. We exclude setters as soon as we go deeper in the tree,
+ // except when we see a filter (that can potentially be a two-way
+ // transformer).
+ if (e is pe.BinaryOperator && e.operator == '|') return;
+ _includeSetter = false;
+ }
+
+ visitIdentifier(pe.Identifier e) {
+ if (e.value != 'this') _add(e.value);
+ super.visitIdentifier(e);
+ }
+
+ visitGetter(pe.Getter e) {
+ _add(e.name);
+ super.visitGetter(e);
+ }
+
+ visitInvoke(pe.Invoke e) {
+ _includeSetter = false; // Invoke is only valid as an r-value.
+ if (e.method != null) _add(e.method);
+ super.visitInvoke(e);
+ }
+}
+
+/// Parses and collects information about bindings found in polymer templates.
+class _Mustaches {
+ /// Each expression that appears within `{{...}}` and `[[...]]`.
+ final List<String> expressions;
+
+ /// Whether the whole text returned by [parse] was a single expression.
+ final bool isWhole;
+
+ _Mustaches(this.isWhole, this.expressions);
+
+ static _Mustaches parse(String text) {
+ if (text == null || text.isEmpty) return null;
+ // Use template-binding's parser, but provide a delegate function factory to
+ // save the expressions without parsing them as [PropertyPath]s.
+ var tokens = MustacheTokens.parse(text, (s) => () => s);
+ if (tokens == null) return null;
+ var length = tokens.length;
+ bool isWhole = length == 1 && tokens.getText(length) == '' &&
+ tokens.getText(0) == '';
+ var expressions = new List(length);
+ for (int i = 0; i < length; i++) {
+ expressions[i] = tokens.getPrepareBinding(i)();
+ }
+ return new _Mustaches(isWhole, expressions);
+ }
+}
+
+/// Holds types that are used in queries
+class _ResolvedTypes {
+ /// Element representing `HtmlElement`.
+ final ClassElement htmlElementElement;
+
+ /// Element representing `String`.
+ final InterfaceType stringType;
+
+ /// Element representing `Polymer`.
+ final ClassElement polymerClassElement;
+
+ /// Element representing the constructor of `@CustomTag`.
+ final ConstructorElement customTagConstructor;
+
+ /// Element representing the type of `@published`.
+ final ClassElement publishedElement;
+
+ /// Element representing the type of `@observable`.
+ final ClassElement observableElement;
+
+ /// Element representing the type of `@ObserveProperty`.
+ final ClassElement observePropertyElement;
+
+ /// Element representing the type of `@ComputedProperty`.
+ final ClassElement computedPropertyElement;
+
+ /// Element representing the `@initMethod` annotation.
+ final TopLevelVariableElement initMethodElement;
+
+
+ factory _ResolvedTypes(Resolver resolver) {
+ // Load class elements that are used in queries for codegen.
+ var polymerLib = resolver.getLibrary(
+ new AssetId('polymer', 'lib/polymer.dart'));
+ if (polymerLib == null) _definitionError('the polymer library');
+
+ var htmlLib = resolver.getLibraryByUri(Uri.parse('dart:html'));
+ if (htmlLib == null) _definitionError('the "dart:html" library');
+
+ var coreLib = resolver.getLibraryByUri(Uri.parse('dart:core'));
+ if (coreLib == null) _definitionError('the "dart:core" library');
+
+ var observeLib = resolver.getLibrary(
+ new AssetId('observe', 'lib/src/metadata.dart'));
+ if (observeLib == null) _definitionError('the observe library');
+
+ var initMethodElement = null;
+ for (var unit in polymerLib.parts) {
+ if (unit.uri == 'src/loader.dart') {
+ initMethodElement = unit.topLevelVariables.firstWhere(
+ (t) => t.displayName == 'initMethod');
+ break;
+ }
+ }
+ var customTagConstructor =
+ _lookupType(polymerLib, 'CustomTag').constructors.first;
+ var publishedElement = _lookupType(polymerLib, 'PublishedProperty');
+ var observableElement = _lookupType(observeLib, 'ObservableProperty');
+ var observePropertyElement = _lookupType(polymerLib, 'ObserveProperty');
+ var computedPropertyElement = _lookupType(polymerLib, 'ComputedProperty');
+ var polymerClassElement = _lookupType(polymerLib, 'Polymer');
+ var htmlElementElement = _lookupType(htmlLib, 'HtmlElement');
+ var stringType = _lookupType(coreLib, 'String').type;
+ if (initMethodElement == null) _definitionError('@initMethod');
+
+ return new _ResolvedTypes.internal(htmlElementElement, stringType,
+ polymerClassElement, customTagConstructor, publishedElement,
+ observableElement, observePropertyElement, computedPropertyElement,
+ initMethodElement);
+ }
+
+ _ResolvedTypes.internal(this.htmlElementElement, this.stringType,
+ this.polymerClassElement, this.customTagConstructor,
+ this.publishedElement, this.observableElement,
+ this.observePropertyElement, this.computedPropertyElement,
+ this.initMethodElement);
+
+ static _lookupType(LibraryElement lib, String typeName) {
+ var result = lib.getType(typeName);
+ if (result == null) _definitionError(typeName);
+ return result;
+ }
+
+ static _definitionError(name) {
+ throw new StateError("Internal error in polymer-builder: couldn't find "
+ "definition of $name.");
+ }
+}
+
+/// Retrieves all classses that are visible if you were to import [lib]. This
+/// includes exported classes from other libraries.
+List<ClassElement> _visibleClassesOf(LibraryElement lib) {
+ var result = [];
+ result.addAll(lib.units.expand((u) => u.types));
+ for (var e in lib.exports) {
+ var exported = e.exportedLibrary.units.expand((u) => u.types).toList();
+ _filter(exported, e.combinators);
+ result.addAll(exported);
+ }
+ return result;
+}
+
+/// Retrieves all top-level methods that are visible if you were to import
+/// [lib]. This includes exported methods from other libraries too.
+List<FunctionElement> _visibleTopLevelMethodsOf(LibraryElement lib) {
+ var result = [];
+ result.addAll(lib.units.expand((u) => u.functions));
+ for (var e in lib.exports) {
+ var exported = e.exportedLibrary.units.expand((u) => u.functions).toList();
+ _filter(exported, e.combinators);
+ result.addAll(exported);
+ }
+ return result;
+}
+
+/// Filters [elements] that come from an export, according to its show/hide
+/// combinators. This modifies [elements] in place.
+void _filter(List<analyzer.Element> elements,
+ List<NamespaceCombinator> combinators) {
+ for (var c in combinators) {
+ if (c is ShowElementCombinator) {
+ var show = c.shownNames.toSet();
+ elements.retainWhere((e) => show.contains(e.displayName));
+ } else if (c is HideElementCombinator) {
+ var hide = c.hiddenNames.toSet();
+ elements.removeWhere((e) => hide.contains(e.displayName));
+ }
+ }
+}
« 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