svn merge -r 26292:26492 https://dart.googlecode.com/svn/branches/bleeding_edge trunk
git-svn-id: http://dart.googlecode.com/svn/trunk@26504 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 9206a32..f850b03 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -14,6 +14,5 @@
input_api,
output_api,
json_url='http://dart-status.appspot.com/current?format=json')
- # TODO(ricow): reenable when status page is back in shape
- # results.extend(status_check)
+ results.extend(status_check)
return results
diff --git a/README.dart-sdk b/README.dart-sdk
index c0c4b0d..04e9d4b 100644
--- a/README.dart-sdk
+++ b/README.dart-sdk
@@ -1,4 +1,4 @@
-The Dark SDK is a set of tools and libraries for the Dart programming language.
+The Dart SDK is a set of tools and libraries for the Dart programming language.
You can find information about Dart online at dartlang.org.
diff --git a/pkg/analyzer_experimental/bin/analyzer.dart b/pkg/analyzer_experimental/bin/analyzer.dart
index 5fed0a8..119376f 100644
--- a/pkg/analyzer_experimental/bin/analyzer.dart
+++ b/pkg/analyzer_experimental/bin/analyzer.dart
@@ -8,6 +8,7 @@
library analyzer;
import 'dart:async';
+import 'dart:convert';
import 'dart:io';
import 'package:analyzer_experimental/src/generated/engine.dart';
@@ -80,7 +81,7 @@
// read line from stdin
Stream cmdLine = stdin
.transform(new StringDecoder())
- .transform(new LineTransformer());
+ .transform(new LineSplitter());
var subscription = cmdLine.listen((String line) {
// may be finish
if (line.isEmpty) {
diff --git a/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart b/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart
index 22e0d2a..a3745ec 100644
--- a/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart
+++ b/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart
@@ -150,6 +150,13 @@
/// Cached previous token for calculating preceding whitespace.
Token previousToken;
+ /// A flag to indicate that a newline should be emitted before the next token.
+ bool needsNewline = false;
+
+ /// A flag to indicate that user introduced newlines should be emitted before
+ /// the next token.
+ bool preservePrecedingNewlines = false;
+
/// Initialize a newly created visitor to write source code representing
/// the visited nodes to the given [writer].
SourceVisitor(FormatterOptions options, this.lineInfo) :
@@ -161,49 +168,59 @@
}
visitAnnotation(Annotation node) {
- emitToken(node.atSign);
+ token(node.atSign);
visit(node.name);
visitPrefixed('.', node.constructorName);
visit(node.arguments);
}
visitArgumentDefinitionTest(ArgumentDefinitionTest node) {
- emitToken(node.question);
+ token(node.question);
visit(node.identifier);
}
visitArgumentList(ArgumentList node) {
- emitToken(node.leftParenthesis);
+ token(node.leftParenthesis);
visitList(node.arguments, ', ');
- emitToken(node.rightParenthesis);
+ token(node.rightParenthesis);
}
visitAsExpression(AsExpression node) {
visit(node.expression);
- emitToken(node.asOperator, prefix: ' ', suffix: ' ');
+ space();
+ token(node.asOperator);
+ space();
visit(node.type);
}
visitAssertStatement(AssertStatement node) {
- emitToken(node.keyword, suffix: ' (');
+ token(node.keyword);
+ space();
+ token(node.leftParenthesis);
visit(node.condition);
- emitToken(node.semicolon, prefix: ')');
+ token(node.rightParenthesis);
+ token(node.semicolon);
}
visitAssignmentExpression(AssignmentExpression node) {
visit(node.leftHandSide);
- emitToken(node.operator, prefix: ' ', suffix: ' ');
+ space();
+ token(node.operator);
+ space();
visit(node.rightHandSide);
}
visitBinaryExpression(BinaryExpression node) {
visit(node.leftOperand);
- emitToken(node.operator, prefix: ' ', suffix: ' ');
+ space();
+ token(node.operator);
+ space();
visit(node.rightOperand);
}
visitBlock(Block node) {
- emitToken(node.leftBracket);
+ token(node.leftBracket);
+ needsNewline = true;
indent();
for (var stmt in node.statements) {
@@ -211,11 +228,9 @@
}
unindent();
- newline();
- print('}');
-//TODO(pquitslund): make this work
-// emitToken(node.rightBracket);
- previousToken = node.rightBracket;
+ preservePrecedingNewlines = true;
+ needsNewline = true;
+ token(node.rightBracket);
}
visitBlockFunctionBody(BlockFunctionBody node) {
@@ -223,13 +238,15 @@
}
visitBooleanLiteral(BooleanLiteral node) {
- emitToken(node.literal);
+ token(node.literal);
}
visitBreakStatement(BreakStatement node) {
- emitToken(node.keyword);
+ preservePrecedingNewlines = true;
+ token(node.keyword);
visitPrefixed(' ', node.label);
- emitToken(node.semicolon);
+ token(node.semicolon);
+ needsNewline = true;
}
visitCascadeExpression(CascadeExpression node) {
@@ -241,28 +258,34 @@
visitPrefixed('on ', node.exceptionType);
if (node.catchKeyword != null) {
if (node.exceptionType != null) {
- print(' ');
+ space();
}
- print('catch (');
+ token(node.catchKeyword);
+ space();
+ token(node.leftParenthesis);
visit(node.exceptionParameter);
visitPrefixed(', ', node.stackTraceParameter);
- print(') ');
+ token(node.rightParenthesis);
+ space();
} else {
- print(' ');
+ space();
}
visit(node.body);
- newline();
+ needsNewline = true;
}
visitClassDeclaration(ClassDeclaration node) {
- emitToken(node.abstractKeyword, suffix: ' ');
- emitToken(node.classKeyword, suffix: ' ');
+ preservePrecedingNewlines = true;
+ modifier(node.abstractKeyword);
+ token(node.classKeyword);
+ space();
visit(node.name);
visit(node.typeParameters);
visitPrefixed(' ', node.extendsClause);
visitPrefixed(' ', node.withClause);
visitPrefixed(' ', node.implementsClause);
- emitToken(node.leftBracket, prefix: ' ');
+ space();
+ token(node.leftBracket);
indent();
for (var i = 0; i < node.members.length; i++) {
@@ -271,21 +294,26 @@
unindent();
- emitToken(node.rightBracket, minNewlines: 1);
+ emitPrecedingNewlines(node.rightBracket, min: 1);
+ token(node.rightBracket);
}
visitClassTypeAlias(ClassTypeAlias node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.name);
visit(node.typeParameters);
- print(' = ');
+ space();
+ token(node.equals);
+ space();
if (node.abstractKeyword != null) {
- print('abstract ');
+ token(node.abstractKeyword);
+ space();
}
visit(node.superclass);
visitPrefixed(' ', node.withClause);
visitPrefixed(' ', node.implementsClause);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitComment(Comment node) => null;
@@ -293,31 +321,40 @@
visitCommentReference(CommentReference node) => null;
visitCompilationUnit(CompilationUnit node) {
+
+ // Cache EOF for leading whitespace calculation
+ var start = node.beginToken.previous;
+ if (start != null && start.type is TokenType_EOF) {
+ previousToken = start;
+ }
+
var scriptTag = node.scriptTag;
var directives = node.directives;
visit(scriptTag);
- var prefix = scriptTag == null ? '' : ' ';
- visitPrefixedList(prefix, directives, ' ');
- //prefix = scriptTag == null && directives.isEmpty ? '' : ' ';
- prefix = '';
- visitPrefixedList(prefix, node.declarations);
+ visitList(directives);
+ visitList(node.declarations);
- //TODO(pquitslund): move this?
- newline();
+ // Handle trailing whitespace
+ preservePrecedingNewlines = true;
+ token(node.endToken /* EOF */);
}
visitConditionalExpression(ConditionalExpression node) {
visit(node.condition);
- print(' ? ');
+ space();
+ token(node.question);
+ space();
visit(node.thenExpression);
- print(' : ');
+ space();
+ token(node.colon);
+ space();
visit(node.elseExpression);
}
visitConstructorDeclaration(ConstructorDeclaration node) {
- emitToken(node.externalKeyword, suffix: ' ');
- emitToken(node.constKeyword, suffix: ' ');
- emitToken(node.factoryKeyword, suffix: ' ');
+ modifier(node.externalKeyword);
+ modifier(node.constKeyword);
+ modifier(node.factoryKeyword);
visit(node.returnType);
visitPrefixed('.', node.name);
visit(node.parameters);
@@ -327,9 +364,12 @@
}
visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
- emitToken(node.keyword, suffix: '.');
+ token(node.keyword);
+ token(node.period);
visit(node.fieldName);
- print(' = ');
+ space();
+ token(node.equals);
+ space();
visit(node.expression);
}
@@ -339,150 +379,179 @@
}
visitContinueStatement(ContinueStatement node) {
- emitToken(node.keyword);
+ token(node.keyword);
visitPrefixed(' ', node.label);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitDeclaredIdentifier(DeclaredIdentifier node) {
- emitToken(node.keyword, suffix: ' ');
- visitSuffixed(node.type, ' ');
+ token(node.keyword);
+ space();
+ visit(node.type);
+ //TODO(pquitslund): avoiding visitSuffixed(..) but we can do better
+ if (node.type != null) {
+ space();
+ }
visit(node.identifier);
}
visitDefaultFormalParameter(DefaultFormalParameter node) {
visit(node.parameter);
if (node.separator != null) {
- print(' ');
- print(node.separator.lexeme);
+ space();
+ token(node.separator);
visitPrefixed(' ', node.defaultValue);
}
}
visitDoStatement(DoStatement node) {
- emitToken(node.doKeyword, suffix: ' ');
+ token(node.doKeyword);
+ space();
visit(node.body);
- emitToken(node.whileKeyword, prefix: ' ', suffix: ' (');
+ space();
+ token(node.whileKeyword);
+ space();
+ token(node.leftParenthesis);
visit(node.condition);
- emitToken(node.semicolon, prefix: ')');
+ token(node.rightParenthesis);
+ token(node.semicolon);
}
visitDoubleLiteral(DoubleLiteral node) {
- print(node.literal.lexeme);
+ token(node.literal);
}
visitEmptyFunctionBody(EmptyFunctionBody node) {
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitEmptyStatement(EmptyStatement node) {
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitExportDirective(ExportDirective node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.uri);
visitPrefixedList(' ', node.combinators, ' ');
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitExpressionFunctionBody(ExpressionFunctionBody node) {
- emitToken(node.functionDefinition, suffix: ' ');
+ token(node.functionDefinition);
+ space();
visit(node.expression);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitExpressionStatement(ExpressionStatement node) {
visit(node.expression);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitExtendsClause(ExtendsClause node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.superclass);
}
visitFieldDeclaration(FieldDeclaration node) {
- emitToken(node.keyword, suffix: ' ');
+ needsNewline = true;
+ preservePrecedingNewlines = true;
+ modifier(node.keyword);
visit(node.fields);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitFieldFormalParameter(FieldFormalParameter node) {
- emitToken(node.keyword, suffix: ' ');
- visitSuffixed(node.type, ' ');
- print('this.');
+ token(node.keyword);
+ space();
+ visit(node.type);
+ space();
+ token(node.thisToken);
+ token(node.period);
visit(node.identifier);
visit(node.parameters);
}
visitForEachStatement(ForEachStatement node) {
- print('for (');
+ token(node.forKeyword);
+ space();
+ token(node.leftParenthesis);
visit(node.loopVariable);
- print(' in ');
+ space();
+ token(node.inKeyword);
+ space();
visit(node.iterator);
- print(') ');
+ token(node.rightParenthesis);
+ space();
visit(node.body);
}
visitFormalParameterList(FormalParameterList node) {
var groupEnd = null;
- print('(');
+ token(node.leftParenthesis);
var parameters = node.parameters;
var size = parameters.length;
for (var i = 0; i < size; i++) {
var parameter = parameters[i];
if (i > 0) {
- print(', ');
+ append(', ');
}
if (groupEnd == null && parameter is DefaultFormalParameter) {
if (identical(parameter.kind, ParameterKind.NAMED)) {
groupEnd = '}';
- print('{');
+ append('{');
} else {
groupEnd = ']';
- print('[');
+ append('[');
}
}
parameter.accept(this);
}
if (groupEnd != null) {
- print(groupEnd);
+ append(groupEnd);
}
- print(')');
+ token(node.rightParenthesis);
}
visitForStatement(ForStatement node) {
+ token(node.forKeyword);
+ space();
+ token(node.leftParenthesis);
var initialization = node.initialization;
- print('for (');
if (initialization != null) {
visit(initialization);
} else {
visit(node.variables);
}
- print(';');
+ token(node.leftSeparator);
visitPrefixed(' ', node.condition);
- print(';');
+ token(node.rightSeparator);
visitPrefixedList(' ', node.updaters, ', ');
- print(') ');
+ token(node.leftParenthesis);
+ space();
visit(node.body);
}
visitFunctionDeclaration(FunctionDeclaration node) {
+ needsNewline = true;
+ preservePrecedingNewlines = true;
visitSuffixed(node.returnType, ' ');
- emitToken(node.propertyKeyword, suffix: ' ');
+ token(node.propertyKeyword, followedBy: space);
visit(node.name);
visit(node.functionExpression);
}
visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
visit(node.functionDeclaration);
- print(';');
+ // TODO(pquitslund): fix and handle in function body
+ append(';');
}
visitFunctionExpression(FunctionExpression node) {
visit(node.parameters);
- print(' ');
+ space();
visit(node.body);
}
@@ -492,12 +561,13 @@
}
visitFunctionTypeAlias(FunctionTypeAlias node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visitSuffixed(node.returnType, ' ');
visit(node.name);
visit(node.typeParameters);
visit(node.parameters);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
@@ -507,81 +577,96 @@
}
visitHideCombinator(HideCombinator node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visitList(node.hiddenNames, ', ');
}
visitIfStatement(IfStatement node) {
- emitToken(node.ifKeyword);
- print(' (');
+ preservePrecedingNewlines = true;
+ token(node.ifKeyword);
+ space();
+ token(node.leftParenthesis);
visit(node.condition);
- print(') ');
+ token(node.rightParenthesis);
+ space();
visit(node.thenStatement);
- visitPrefixed(' else ', node.elseStatement);
+ //visitPrefixed(' else ', node.elseStatement);
+ if (node.elseStatement != null) {
+ space();
+ token(node.elseKeyword);
+ space();
+ visit(node.elseStatement);
+ }
+ needsNewline = true;
}
-
+
visitImplementsClause(ImplementsClause node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visitList(node.interfaces, ', ');
}
visitImportDirective(ImportDirective node) {
- emitToken(node.keyword, suffix: ' ');
+ preservePrecedingNewlines = true;
+ token(node.keyword);
+ space();
visit(node.uri);
visitPrefixed(' as ', node.prefix);
visitPrefixedList(' ', node.combinators, ' ');
- emitToken(node.semicolon);
+ token(node.semicolon);
+ needsNewline = true;
}
visitIndexExpression(IndexExpression node) {
if (node.isCascaded) {
- print('..');
+ token(node.period);
} else {
visit(node.target);
}
- print('[');
+ token(node.leftBracket);
visit(node.index);
- print(']');
+ token(node.rightBracket);
}
visitInstanceCreationExpression(InstanceCreationExpression node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.constructorName);
visit(node.argumentList);
}
visitIntegerLiteral(IntegerLiteral node) {
- print(node.literal.lexeme);
+ token(node.literal);
}
visitInterpolationExpression(InterpolationExpression node) {
if (node.rightBracket != null) {
- print('\${');
+ token(node.leftBracket);
visit(node.expression);
- print('}');
+ token(node.rightBracket);
} else {
- print('\$');
+ token(node.leftBracket);
visit(node.expression);
}
}
visitInterpolationString(InterpolationString node) {
- print(node.contents.lexeme);
+ token(node.contents);
}
visitIsExpression(IsExpression node) {
visit(node.expression);
- if (node.notOperator == null) {
- print(' is ');
- } else {
- print(' is! ');
- }
+ space();
+ token(node.isOperator);
+ token(node.notOperator);
+ space();
visit(node.type);
}
visitLabel(Label node) {
visit(node.label);
- print(':');
+ token(node.colon);
}
visitLabeledStatement(LabeledStatement node) {
@@ -590,49 +675,51 @@
}
visitLibraryDirective(LibraryDirective node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.name);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitLibraryIdentifier(LibraryIdentifier node) {
- print(node.name);
+ append(node.name);
}
visitListLiteral(ListLiteral node) {
if (node.modifier != null) {
- print(node.modifier.lexeme);
- print(' ');
+ token(node.modifier);
+ space();
}
visit(node.typeArguments);
- print('[');
+ token(node.leftBracket);
visitList(node.elements, ', ');
- print(']');
+ token(node.rightBracket);
}
visitMapLiteral(MapLiteral node) {
- if (node.modifier != null) {
- print(node.modifier.lexeme);
- print(' ');
- }
+ modifier(node.modifier);
visitSuffixed(node.typeArguments, ' ');
- print('{');
+ token(node.leftBracket);
visitList(node.entries, ', ');
- print('}');
+ token(node.rightBracket);
}
visitMapLiteralEntry(MapLiteralEntry node) {
visit(node.key);
- print(' : ');
+ space();
+ token(node.separator);
+ space();
visit(node.value);
}
visitMethodDeclaration(MethodDeclaration node) {
- emitToken(node.externalKeyword, suffix: ' ');
- emitToken(node.modifierKeyword, suffix: ' ');
+ needsNewline = true;
+ preservePrecedingNewlines = true;
+ modifier(node.externalKeyword);
+ modifier(node.modifierKeyword);
visitSuffixed(node.returnType, ' ');
- emitToken(node.propertyKeyword, suffix: ' ');
- emitToken(node.operatorKeyword, suffix: ' ');
+ modifier(node.propertyKeyword);
+ modifier(node.operatorKeyword);
visit(node.name);
if (!node.isGetter) {
visit(node.parameters);
@@ -642,7 +729,7 @@
visitMethodInvocation(MethodInvocation node) {
if (node.isCascaded) {
- print('..');
+ token(node.period);
} else {
visitSuffixed(node.target, '.');
}
@@ -656,107 +743,114 @@
}
visitNativeClause(NativeClause node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.name);
}
visitNativeFunctionBody(NativeFunctionBody node) {
- emitToken(node.nativeToken, suffix: ' ');
+ token(node.nativeToken);
+ space();
visit(node.stringLiteral);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitNullLiteral(NullLiteral node) {
- emitToken(node.literal);
+ token(node.literal);
}
visitParenthesizedExpression(ParenthesizedExpression node) {
- emitToken(node.leftParenthesis);
+ token(node.leftParenthesis);
visit(node.expression);
- emitToken(node.rightParenthesis);
+ token(node.rightParenthesis);
}
visitPartDirective(PartDirective node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.uri);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitPartOfDirective(PartOfDirective node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.libraryName);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
visitPostfixExpression(PostfixExpression node) {
visit(node.operand);
- print(node.operator.lexeme);
+ token(node.operator);
}
visitPrefixedIdentifier(PrefixedIdentifier node) {
visit(node.prefix);
- print('.');
+ token(node.period);
visit(node.identifier);
}
visitPrefixExpression(PrefixExpression node) {
- emitToken(node.operator);
+ token(node.operator);
visit(node.operand);
}
visitPropertyAccess(PropertyAccess node) {
if (node.isCascaded) {
- print('..');
+ token(node.operator);
} else {
visit(node.target);
- print('.');
+ token(node.operator);
}
visit(node.propertyName);
}
visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
- emitToken(node.keyword);
+ token(node.keyword);
visitPrefixed('.', node.constructorName);
visit(node.argumentList);
}
visitRethrowExpression(RethrowExpression node) {
- emitToken(node.keyword);
+ token(node.keyword);
}
visitReturnStatement(ReturnStatement node) {
+ preservePrecedingNewlines = true;
var expression = node.expression;
if (expression == null) {
- emitToken(node.keyword, minNewlines: 1);
- emitToken(node.semicolon);
+ token(node.keyword);
+ token(node.semicolon);
} else {
- emitToken(node.keyword, suffix: ' ', minNewlines: 1);
+ token(node.keyword);
+ space();
expression.accept(this);
- emitToken(node.semicolon);
+ token(node.semicolon);
}
}
visitScriptTag(ScriptTag node) {
- print(node.scriptTag.lexeme);
+ token(node.scriptTag);
}
visitShowCombinator(ShowCombinator node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visitList(node.shownNames, ', ');
}
visitSimpleFormalParameter(SimpleFormalParameter node) {
- emitToken(node.keyword, suffix: ' ');
+ modifier(node.keyword);
visitSuffixed(node.type, ' ');
visit(node.identifier);
}
visitSimpleIdentifier(SimpleIdentifier node) {
- emitToken(node.token);
+ token(node.token);
}
visitSimpleStringLiteral(SimpleStringLiteral node) {
- emitToken(node.literal);
+ token(node.literal);
}
visitStringInterpolation(StringInterpolation node) {
@@ -764,42 +858,50 @@
}
visitSuperConstructorInvocation(SuperConstructorInvocation node) {
- emitToken(node.keyword);
+ token(node.keyword);
visitPrefixed('.', node.constructorName);
visit(node.argumentList);
}
visitSuperExpression(SuperExpression node) {
- emitToken(node.keyword);
+ token(node.keyword);
}
visitSwitchCase(SwitchCase node) {
+ preservePrecedingNewlines = true;
visitSuffixedList(node.labels, ' ', ' ');
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.expression);
- print(':');
+ token(node.colon);
indent();
+ needsNewline = true;
visitList(node.statements);
unindent();
}
visitSwitchDefault(SwitchDefault node) {
+ preservePrecedingNewlines = true;
visitSuffixedList(node.labels, ' ', ' ');
- emitToken(node.keyword, suffix: ': ');
+ token(node.keyword);
+ token(node.colon);
+ space();
visitList(node.statements, ' ');
}
visitSwitchStatement(SwitchStatement node) {
- emitToken(node.keyword);
- print(' (');
+ token(node.keyword);
+ space();
+ token(node.leftParenthesis);
visit(node.expression);
- print(') ');
- emitToken(node.leftBracket);
+ token(node.rightParenthesis);
+ space();
+ token(node.leftBracket);
indent();
visitList(node.members);
unindent();
- emitToken(node.rightBracket);
- newline();
+ token(node.rightBracket);
+ needsNewline = true;
}
visitSymbolLiteral(SymbolLiteral node) {
@@ -807,29 +909,34 @@
}
visitThisExpression(ThisExpression node) {
- emitToken(node.keyword);
+ token(node.keyword);
}
visitThrowExpression(ThrowExpression node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visit(node.expression);
}
visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
- visitSuffixed(node.variables, ';');
+ preservePrecedingNewlines = true;
+ visit(node.variables);
+ token(node.semicolon);
}
visitTryStatement(TryStatement node) {
- emitToken(node.tryKeyword, suffix: ' ');
+ preservePrecedingNewlines = true;
+ token(node.tryKeyword);
+ space();
visit(node.body);
visitPrefixedList(' ', node.catchClauses, ' ');
visitPrefixed(' finally ', node.finallyClause);
}
visitTypeArgumentList(TypeArgumentList node) {
- emitToken(node.leftBracket);
+ token(node.leftBracket);
visitList(node.arguments, ', ');
- emitToken(node.rightBracket);
+ token(node.rightBracket);
}
visitTypeName(TypeName node) {
@@ -843,36 +950,47 @@
}
visitTypeParameterList(TypeParameterList node) {
- emitToken(node.leftBracket);
+ token(node.leftBracket);
visitList(node.typeParameters, ', ');
- emitToken(node.rightBracket);
+ token(node.rightBracket);
}
visitVariableDeclaration(VariableDeclaration node) {
visit(node.name);
- visitPrefixed(' = ', node.initializer);
+ if (node.initializer != null) {
+ space();
+ token(node.equals);
+ space();
+ visit(node.initializer);
+ }
}
visitVariableDeclarationList(VariableDeclarationList node) {
- emitToken(node.keyword, suffix: ' ');
+ token(node.keyword);
+ space();
visitSuffixed(node.type, ' ');
visitList(node.variables, ', ');
}
visitVariableDeclarationStatement(VariableDeclarationStatement node) {
visit(node.variables);
- emitToken(node.semicolon);
+ token(node.semicolon);
+ needsNewline = true;
}
visitWhileStatement(WhileStatement node) {
- emitToken(node.keyword, suffix: ' (');
+ token(node.keyword);
+ space();
+ token(node.leftParenthesis);
visit(node.condition);
- print(') ');
+ token(node.rightParenthesis);
+ space();
visit(node.body);
}
visitWithClause(WithClause node) {
- emitToken(node.withKeyword, suffix: ' ');
+ token(node.withKeyword);
+ space();
visitList(node.mixinTypes, ', ');
}
@@ -888,7 +1006,7 @@
visitSuffixed(ASTNode node, String suffix) {
if (node != null) {
node.accept(this);
- print(suffix);
+ append(suffix);
}
}
@@ -896,7 +1014,7 @@
/// it is non-null.
visitPrefixed(String prefix, ASTNode node) {
if (node != null) {
- print(prefix);
+ append(prefix);
node.accept(this);
}
}
@@ -905,7 +1023,7 @@
/// body is not empty.
visitPrefixedBody(String prefix, FunctionBody body) {
if (body is! EmptyFunctionBody) {
- print(prefix);
+ append(prefix);
}
visit(body);
}
@@ -916,7 +1034,7 @@
var size = nodes.length;
for (var i = 0; i < size; i++) {
if (i > 0) {
- print(separator);
+ append(separator);
}
nodes[i].accept(this);
}
@@ -930,11 +1048,11 @@
if (size > 0) {
for (var i = 0; i < size; i++) {
if (i > 0) {
- print(separator);
+ append(separator);
}
nodes[i].accept(this);
}
- print(suffix);
+ append(suffix);
}
}
}
@@ -945,10 +1063,10 @@
if (nodes != null) {
var size = nodes.length;
if (size > 0) {
- print(prefix);
+ append(prefix);
for (var i = 0; i < size; i++) {
if (i > 0 && separator != null) {
- print(separator);
+ append(separator);
}
nodes[i].accept(this);
}
@@ -956,37 +1074,50 @@
}
}
-
- /// Emit the given [token], if it's non-null, preceded by any detected
- /// newlines or a minimum as specified by [minNewlines], printing a [prefix]
- /// before and a [suffix] after.
- emitToken(Token token, {String prefix, String suffix,
- int minNewlines: 0}) {
+ /// Emit the given [modifier] if it's non null, followed by non-breaking
+ /// whitespace.
+ modifier(Token modifier) {
+ token(modifier, followedBy: space);
+ }
+
+ token(Token token, {followedBy(), int minNewlines: 0}) {
if (token != null) {
- print(prefix);
- emitPrecedingNewlines(token, min: minNewlines);
- print(token.lexeme);
- print(suffix);
- }
+ if (needsNewline) {
+ minNewlines = max(1, minNewlines);
+ }
+ if (preservePrecedingNewlines || minNewlines > 0) {
+ var emitted = emitPrecedingNewlines(token, min: minNewlines);
+ preservePrecedingNewlines = false;
+ if (emitted > 0) {
+ needsNewline = false;
+ }
+ }
+ append(token.lexeme);
+ if (followedBy != null) {
+ followedBy();
+ }
+ previousToken = token;
+ }
+ }
+
+ /// Emit a non-breakable space.
+ space() {
+ //TODO(pquitslund): replace with a proper space token
+ append(' ');
}
- /// Print the given [string] to the source writer if it's non-null.
- print(String string) {
+ /// Emit a breakable space
+ breakableSpace() {
+ //Implement
+ }
+
+ /// Append the given [string] to the source writer if it's non-null.
+ append(String string) {
if (string != null) {
writer.print(string);
}
}
-
- /// Emit a newline.
- newline() {
- writer.newline();
- }
-
- /// Emit [n] newlines.
- newlines(n) {
- writer.newlines(n);
- }
-
+
/// Indent.
indent() {
writer.indent();
@@ -998,18 +1129,19 @@
}
/// Emit any detected newlines or a minimum as specified by [minNewlines].
- emitPrecedingNewlines(Token token, {min: 0}) {
+ int emitPrecedingNewlines(Token token, {min: 0}) {
var comment = token.precedingComments;
var currentToken = comment != null ? comment : token;
var lines = max(min, countNewlinesBetween(previousToken, currentToken));
- newlines(lines);
+ writer.newlines(lines);
while (comment != null) {
- print(comment.toString().trim());
- newline();
+ append(comment.toString().trim());
+ writer.newline();
comment = comment.next;
}
previousToken = token;
+ return lines;
}
/// Count the blanks between these two nodes.
diff --git a/pkg/analyzer_experimental/test/services/formatter_test.dart b/pkg/analyzer_experimental/test/services/formatter_test.dart
index 3f21446..2ad1d13 100644
--- a/pkg/analyzer_experimental/test/services/formatter_test.dart
+++ b/pkg/analyzer_experimental/test/services/formatter_test.dart
@@ -21,9 +21,13 @@
test('CU (1)', () {
expectCUFormatsTo(
- 'class A {\n'
- '}',
'class A {\n'
+ ' var z;\n'
+ ' inc(int x) => ++x;\n'
+ '}\n',
+ 'class A {\n'
+ ' var z;\n'
+ ' inc(int x) => ++x;\n'
'}\n'
);
});
@@ -31,7 +35,7 @@
test('CU (2)', () {
expectCUFormatsTo(
'class A { \n'
- '}',
+ '}\n',
'class A {\n'
'}\n'
);
@@ -42,19 +46,78 @@
'class A {\n'
' }',
'class A {\n'
- '}\n'
+ '}'
);
});
test('CU (4)', () {
expectCUFormatsTo(
' class A {\n'
- '}',
+ '}\n',
'class A {\n'
'}\n'
);
});
+ test('CU (5)', () {
+ expectCUFormatsTo(
+ 'class A { int meaningOfLife() => 42; }',
+ 'class A {\n'
+ ' int meaningOfLife() => 42;\n'
+ '}'
+ );
+ });
+
+
+// test('CU - comments', () {
+// expectCUFormatsTo(
+// 'library foo;\n'
+// '\n'
+// '//comment one\n\n'
+// '//comment two\n\n'
+// 'class C {\n}\n',
+// 'library foo;\n'
+// '\n'
+// '//comment one\n\n'
+// '//comment two\n\n'
+// 'class C {\n}\n'
+// );
+// });
+
+ test('CU - top level', () {
+ expectCUFormatsTo(
+ '\n\n'
+ 'foo() {\n'
+ '}\n'
+ 'bar() {\n'
+ '}\n',
+ '\n\n'
+ 'foo() {\n'
+ '}\n'
+ 'bar() {\n'
+ '}\n'
+ );
+ expectCUFormatsTo(
+ 'const A = 42;\n'
+ 'final foo = 32;\n',
+ 'const A = 42;\n'
+ 'final foo = 32;\n'
+ );
+ });
+
+ test('CU - imports', () {
+ expectCUFormatsTo(
+ 'import "dart:io";\n\n'
+ 'import "package:unittest/unittest.dart";\n'
+ 'foo() {\n'
+ '}\n',
+ 'import "dart:io";\n\n'
+ 'import "package:unittest/unittest.dart";\n'
+ 'foo() {\n'
+ '}\n'
+ );
+ });
+
test('CU w/class decl comment', () {
expectCUFormatsTo(
'import "foo";\n\n'
@@ -64,29 +127,64 @@
'import "foo";\n\n'
'//Killer class\n'
'class A {\n'
- '}\n'
+ '}'
);
});
-
+ test('CU (method body)', () {
+ expectCUFormatsTo(
+ 'class A {\n'
+ ' foo(path) {\n'
+ ' var buffer = new StringBuffer();\n'
+ ' var file = new File(path);\n'
+ ' return file;\n'
+ ' }\n'
+ '}\n',
+ 'class A {\n'
+ ' foo(path) {\n'
+ ' var buffer = new StringBuffer();\n'
+ ' var file = new File(path);\n'
+ ' return file;\n'
+ ' }\n'
+ '}\n'
+ );
+ expectCUFormatsTo(
+ 'class A {\n'
+ ' foo(files) {\n'
+ ' for (var file in files) {\n'
+ ' print(file);\n'
+ ' }\n'
+ ' }\n'
+ '}\n',
+ 'class A {\n'
+ ' foo(files) {\n'
+ ' for (var file in files) {\n'
+ ' print(file);\n'
+ ' }\n'
+ ' }\n'
+ '}\n'
+ );
+ });
+
test('CU (method indent)', () {
expectCUFormatsTo(
'class A {\n'
'void x(){\n'
'}\n'
- '}',
+ '}\n',
'class A {\n'
' void x() {\n'
' }\n'
'}\n'
- );
+ );
});
test('CU (method indent - 2)', () {
expectCUFormatsTo(
'class A {\n'
- ' static bool x(){ return true; }\n'
- ' }',
+ ' static bool x(){\n'
+ 'return true; }\n'
+ ' }\n',
'class A {\n'
' static bool x() {\n'
' return true;\n'
@@ -99,7 +197,7 @@
expectCUFormatsTo(
'class A {\n'
' int x() => 42 + 3 ; \n'
- ' }',
+ ' }\n',
'class A {\n'
' int x() => 42 + 3;\n'
'}\n'
@@ -110,10 +208,12 @@
expectCUFormatsTo(
'class A {\n'
' int x() { \n'
- 'if (true) {return 42;\n'
- '} else { return 13; }\n'
+ 'if (true) {\n'
+ 'return 42;\n'
+ '} else {\n'
+ 'return 13;\n }\n'
' }'
- '}',
+ '}\n',
'class A {\n'
' int x() {\n'
' if (true) {\n'
@@ -192,7 +292,7 @@
'case "fig":\n'
'print("bleh");\n'
'break;\n'
- '}\n',
+ '}',
'switch (fruit) {\n'
' case "apple":\n'
' print("delish");\n'
@@ -200,7 +300,7 @@
' case "fig":\n'
' print("bleh");\n'
' break;\n'
- '}\n'
+ '}'
);
});
@@ -217,12 +317,12 @@
'doSomething();\n'
'} catch (e) {\n'
'print(e);\n'
- '}\n',
+ '}',
'try {\n'
' doSomething();\n'
'} catch (e) {\n'
' print(e);\n'
- '}\n'
+ '}'
);
});
diff --git a/pkg/barback/lib/barback.dart b/pkg/barback/lib/barback.dart
index a211873..e37bd1b 100644
--- a/pkg/barback/lib/barback.dart
+++ b/pkg/barback/lib/barback.dart
@@ -6,9 +6,10 @@
export 'src/asset.dart';
export 'src/asset_id.dart';
+export 'src/asset_set.dart';
export 'src/barback.dart';
export 'src/build_result.dart';
-export 'src/errors.dart';
+export 'src/errors.dart' hide flattenAggregateExceptions;
export 'src/package_provider.dart';
export 'src/transform.dart' show Transform;
export 'src/transform_logger.dart';
diff --git a/pkg/barback/lib/src/asset_cascade.dart b/pkg/barback/lib/src/asset_cascade.dart
index 341480d..e8e9355 100644
--- a/pkg/barback/lib/src/asset_cascade.dart
+++ b/pkg/barback/lib/src/asset_cascade.dart
@@ -10,10 +10,10 @@
import 'asset.dart';
import 'asset_id.dart';
import 'asset_node.dart';
+import 'asset_set.dart';
import 'build_result.dart';
import 'cancelable_future.dart';
import 'errors.dart';
-import 'change_batch.dart';
import 'package_graph.dart';
import 'phase.dart';
import 'transformer.dart';
@@ -83,31 +83,14 @@
/// last began.
var _newChanges = false;
+ /// Returns all currently-available output assets from this cascade.
+ AssetSet get availableOutputs => _phases.last.availableOutputs;
+
/// Creates a new [AssetCascade].
///
- /// It loads source assets within [package] using [provider] and then uses
- /// [transformerPhases] to generate output files from them.
- //TODO(rnystrom): Better way of specifying transformers and their ordering.
- AssetCascade(this.graph, this.package,
- Iterable<Iterable<Transformer>> transformerPhases) {
- // Flatten the phases to a list so we can traverse backwards to wire up
- // each phase to its next.
- var phases = transformerPhases.toList();
-
- // Each phase writes its outputs as inputs to the next phase after it.
- // Add a phase at the end for the final outputs of the last phase.
- phases.add([]);
-
- Phase nextPhase = null;
- for (var transformers in phases.reversed) {
- nextPhase = new Phase(this, _phases.length, transformers.toList(),
- nextPhase);
- nextPhase.onDirty.listen((_) {
- _newChanges = true;
- _waitForProcess();
- });
- _phases.insert(0, nextPhase);
- }
+ /// It loads source assets within [package] using [provider].
+ AssetCascade(this.graph, this.package) {
+ _addPhase(new Phase(this, []));
}
/// Gets the asset identified by [id].
@@ -128,7 +111,7 @@
// * If [id] has never been generated and all active transformers provide
// metadata about the file names of assets it can emit, we can prove that
// none of them can emit [id] and fail early.
- return _phases.last.getInput(id).then((node) {
+ return _phases.last.getOutput(id).then((node) {
// If the requested asset is available, we can just return it.
if (node != null && node.state.isAvailable) return node;
@@ -190,11 +173,42 @@
});
}
+ /// Sets this cascade's transformer phases to [transformers].
+ void updateTransformers(Iterable<Iterable<Transformer>> transformers) {
+ transformers = transformers.toList();
+
+ for (var i = 0; i < transformers.length; i++) {
+ if (_phases.length > i) {
+ _phases[i].updateTransformers(transformers[i]);
+ continue;
+ }
+
+ _addPhase(_phases.last.addPhase(transformers[i]));
+ }
+
+ if (transformers.length < _phases.length) {
+ for (var i = transformers.length; i < _phases.length; i++) {
+ // TODO(nweiz): actually remove phases rather than emptying them of
+ // transformers.
+ _phases[i].updateTransformers([]);
+ }
+ }
+ }
+
void reportError(BarbackException error) {
_accumulatedErrors.add(error);
_errorsController.add(error);
}
+ /// Add [phase] to the end of [_phases] and watch its [onDirty] stream.
+ void _addPhase(Phase phase) {
+ phase.onDirty.listen((_) {
+ _newChanges = true;
+ _waitForProcess();
+ });
+ _phases.add(phase);
+ }
+
/// Starts the build process asynchronously if there is work to be done.
///
/// Returns a future that completes with the background processing is done.
diff --git a/pkg/barback/lib/src/asset_forwarder.dart b/pkg/barback/lib/src/asset_forwarder.dart
new file mode 100644
index 0000000..7dc74dc
--- /dev/null
+++ b/pkg/barback/lib/src/asset_forwarder.dart
@@ -0,0 +1,53 @@
+// 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.
+
+library barback.asset_forwarder;
+
+import 'dart:async';
+
+import 'asset_node.dart';
+
+/// A wrapper for an [AssetNode] that forwards events to a new node.
+///
+/// A forwarder is used when a class wants to forward an [AssetNode] that it
+/// gets as an input, but also wants to have control over when that node is
+/// marked as removed. The forwarder can be closed, thus removing its output
+/// node, without the original node having been removed.
+class AssetForwarder {
+ /// The subscription on the input node.
+ StreamSubscription _subscription;
+
+ /// The controller for the output node.
+ final AssetNodeController _controller;
+
+ /// The node to which events are forwarded.
+ AssetNode get node => _controller.node;
+
+ AssetForwarder(AssetNode node)
+ : _controller = new AssetNodeController(node.id, node.transform) {
+ _subscription = node.onStateChange.listen((state) {
+ if (state.isAvailable) {
+ _controller.setAvailable(node.asset);
+ } else if (state.isDirty) {
+ _controller.setDirty();
+ } else {
+ assert(state.isRemoved);
+ close();
+ }
+ });
+
+ if (node.state.isAvailable) {
+ _controller.setAvailable(node.asset);
+ } else if (node.state.isRemoved) {
+ close();
+ }
+ }
+
+ /// Closes the forwarder and marks [node] as removed.
+ void close() {
+ if (_controller.node.state.isRemoved) return;
+ _subscription.cancel();
+ _controller.setRemoved();
+ }
+}
diff --git a/pkg/barback/lib/src/asset_id.dart b/pkg/barback/lib/src/asset_id.dart
index d9cef32..c4ed1d8 100644
--- a/pkg/barback/lib/src/asset_id.dart
+++ b/pkg/barback/lib/src/asset_id.dart
@@ -4,9 +4,6 @@
library barback.asset_id;
-import 'dart:async';
-import 'dart:io';
-
import 'package:path/path.dart' as pathos;
/// AssetIDs always use POSIX style paths regardless of the host platform.
diff --git a/pkg/barback/lib/src/asset_node.dart b/pkg/barback/lib/src/asset_node.dart
index 587f2bd..edc7408 100644
--- a/pkg/barback/lib/src/asset_node.dart
+++ b/pkg/barback/lib/src/asset_node.dart
@@ -9,7 +9,6 @@
import 'asset.dart';
import 'asset_id.dart';
import 'errors.dart';
-import 'phase.dart';
import 'transform_node.dart';
/// Describes the current state of an asset as part of a transformation graph.
diff --git a/pkg/barback/lib/src/asset_set.dart b/pkg/barback/lib/src/asset_set.dart
index b432425..08c2db9 100644
--- a/pkg/barback/lib/src/asset_set.dart
+++ b/pkg/barback/lib/src/asset_set.dart
@@ -4,9 +4,7 @@
library barback.asset_set;
-import 'dart:async';
import 'dart:collection';
-import 'dart:io';
import 'asset.dart';
import 'asset_id.dart';
diff --git a/pkg/barback/lib/src/barback.dart b/pkg/barback/lib/src/barback.dart
index 3dcb23e..313d42c 100644
--- a/pkg/barback/lib/src/barback.dart
+++ b/pkg/barback/lib/src/barback.dart
@@ -8,10 +8,12 @@
import 'asset.dart';
import 'asset_id.dart';
+import 'asset_set.dart';
import 'build_result.dart';
import 'errors.dart';
import 'package_graph.dart';
import 'package_provider.dart';
+import 'transformer.dart';
/// A general-purpose asynchronous build dependency graph manager.
///
@@ -88,4 +90,19 @@
/// Removes [removed] from the graph's known set of source assets.
void removeSources(Iterable<AssetId> removed) =>
_graph.removeSources(removed);
-}
\ No newline at end of file
+
+ /// Gets all output assets.
+ ///
+ /// If a build is currently in progress, waits until it completes. The
+ /// returned future will complete with a [BarbackException] if the build is
+ /// not successful.
+ Future<AssetSet> getAllAssets() => _graph.getAllAssets();
+
+ /// Sets the transformer phases for [package]'s assets to [transformers].
+ ///
+ /// To the extent that [transformers] is similar to the previous transformer
+ /// phases for [package], the existing asset graph will be preserved.
+ void updateTransformers(String package,
+ Iterable<Iterable<Transformer>> transformers) =>
+ _graph.updateTransformers(package, transformers);
+}
diff --git a/pkg/barback/lib/src/build_result.dart b/pkg/barback/lib/src/build_result.dart
index 89ee681..2cb305c 100644
--- a/pkg/barback/lib/src/build_result.dart
+++ b/pkg/barback/lib/src/build_result.dart
@@ -8,6 +8,7 @@
import 'package:stack_trace/stack_trace.dart';
+import 'errors.dart';
import 'utils.dart';
/// An event indicating that the cascade has finished building all assets.
@@ -23,7 +24,7 @@
bool get succeeded => errors.isEmpty;
BuildResult(Iterable<BarbackException> errors)
- : errors = errors.toSet();
+ : errors = flattenAggregateExceptions(errors).toSet();
/// Creates a build result indicating a successful build.
///
diff --git a/pkg/barback/lib/src/change_batch.dart b/pkg/barback/lib/src/change_batch.dart
index aa2a1c4..8f98995 100644
--- a/pkg/barback/lib/src/change_batch.dart
+++ b/pkg/barback/lib/src/change_batch.dart
@@ -4,7 +4,6 @@
library barback.change_batch;
-import 'asset.dart';
import 'asset_id.dart';
/// Represents a batch of source asset changes: additions, removals and
diff --git a/pkg/barback/lib/src/errors.dart b/pkg/barback/lib/src/errors.dart
index f356066..a240bb4 100644
--- a/pkg/barback/lib/src/errors.dart
+++ b/pkg/barback/lib/src/errors.dart
@@ -5,12 +5,12 @@
library barback.errors;
import 'dart:async';
-import 'dart:io';
import 'package:stack_trace/stack_trace.dart';
import 'asset_id.dart';
import 'transformer.dart';
+import 'utils.dart';
/// Error thrown when an asset with [id] cannot be found.
class AssetNotFoundException implements Exception {
@@ -21,10 +21,54 @@
String toString() => "Could not find asset $id.";
}
+/// Replaces any occurrences of [AggregateException] in [errors] with the list
+/// of errors it contains.
+Iterable<BarbackException> flattenAggregateExceptions(
+ Iterable<BarbackException> errors) {
+ return errors.expand((error) {
+ if (error is! AggregateException) return [error];
+ return error.errors;
+ });
+}
+
/// The interface for exceptions from the barback graph or its transformers.
///
/// These exceptions are never produced by programming errors in barback.
-abstract class BarbackException implements Exception {}
+abstract class BarbackException implements Exception {
+ /// Takes a collection of [BarbackExceptions] and returns a single exception
+ /// that contains them all.
+ ///
+ /// If [errors] is empty, returns `null`. If it only has one error, that
+ /// error is returned. Otherwise, an [AggregateException] is returned.
+ static BarbackException aggregate(Iterable<BarbackException> errors) {
+ if (errors.isEmpty) return null;
+ if (errors.length == 1) return errors.single;
+ return new AggregateException(errors);
+ }
+}
+
+/// An error that wraps a collection of other [BarbackException]s.
+///
+/// It implicitly flattens any [AggregateException]s that occur in the list of
+/// exceptions it wraps.
+class AggregateException implements BarbackException {
+ final Set<BarbackException> errors;
+
+ AggregateException(Iterable<BarbackException> errors)
+ : errors = flattenAggregateExceptions(errors).toSet();
+
+ String toString() {
+ var buffer = new StringBuffer();
+ buffer.writeln("Multiple errors occurred:\n");
+
+ for (var error in errors) {
+ buffer.writeln(prefixLines(error.toString(),
+ prefix: " ", firstPrefix: "- "));
+ }
+
+ return buffer.toString();
+ }
+}
/// Error thrown when two or more transformers both output an asset with [id].
class AssetCollisionException implements BarbackException {
@@ -67,34 +111,49 @@
"same package (${transform.primaryId.package}).";
}
-/// Error wrapping an exception thrown by a transform.
-class TransformerException implements BarbackException {
- /// The transform that threw the exception.
- final TransformInfo transform;
-
+/// Base class for an error that wraps another.
+abstract class _WrappedException implements BarbackException {
/// The wrapped exception.
final error;
- TransformerException(this.transform, this.error);
+ _WrappedException(this.error);
- String toString() => "Transform $transform threw error: $error\n" +
- new Trace.from(getAttachedStackTrace(error)).terse.toString();
+ String get _message;
+
+ String toString() {
+ var result = "$_message: $error";
+
+ var stack = getAttachedStackTrace(error);
+ if (stack != null) {
+ result = "$result\n${new Trace.from(stack).terse}";
+ }
+
+ return result;
+ }
+}
+
+/// Error wrapping an exception thrown by a transform.
+class TransformerException extends _WrappedException {
+ /// The transform that threw the exception.
+ final TransformInfo transform;
+
+ TransformerException(this.transform, error)
+ : super(error);
+
+ String get _message => "Transform $transform threw error";
}
/// Error thrown when a source asset [id] fails to load.
///
/// This can be thrown either because the source asset was expected to exist and
/// did not or because reading it failed somehow.
-class AssetLoadException implements BarbackException {
+class AssetLoadException extends _WrappedException {
final AssetId id;
- /// The wrapped exception.
- final error;
+ AssetLoadException(this.id, error)
+ : super(error);
- AssetLoadException(this.id, this.error);
-
- String toString() => "Failed to load source asset $id: $error\n"
- "${new Trace.from(getAttachedStackTrace(error)).terse}";
+ String get _message => "Failed to load source asset $id";
}
/// Information about a single transform in the barback graph.
diff --git a/pkg/barback/lib/src/package_graph.dart b/pkg/barback/lib/src/package_graph.dart
index c46ed55..a402a27 100644
--- a/pkg/barback/lib/src/package_graph.dart
+++ b/pkg/barback/lib/src/package_graph.dart
@@ -6,15 +6,14 @@
import 'dart:async';
-import 'package:stack_trace/stack_trace.dart';
-
-import 'asset.dart';
import 'asset_cascade.dart';
import 'asset_id.dart';
import 'asset_node.dart';
+import 'asset_set.dart';
import 'build_result.dart';
import 'errors.dart';
import 'package_provider.dart';
+import 'transformer.dart';
import 'utils.dart';
/// The collection of [AssetCascade]s for an entire application.
@@ -55,12 +54,17 @@
Stream<BarbackException> get errors => _errors;
Stream<BarbackException> _errors;
+ /// The most recent error emitted from a cascade's result stream.
+ ///
+ /// This is used to pipe an unexpected error from a build to the resulting
+ /// [Future] returned by [getAllAssets].
+ var _lastUnexpectedError;
+
/// Creates a new [PackageGraph] that will transform assets in all packages
/// made available by [provider].
PackageGraph(this.provider) {
for (var package in provider.packages) {
- var cascade = new AssetCascade(this, package,
- provider.getTransformers(package));
+ var cascade = new AssetCascade(this, package);
// The initial result for each cascade is "success" since the cascade
// doesn't start building until some source in that graph is updated.
_cascadeResults[package] = new BuildResult.success();
@@ -76,7 +80,10 @@
// errors, the result will automatically be considered a success.
_resultsController.add(new BuildResult(unionAll(
_cascadeResults.values.map((result) => result.errors))));
- }, onError: _resultsController.addError);
+ }, onError: (error) {
+ _lastUnexpectedError = error;
+ _resultsController.addError(error);
+ });
}
_errors = mergeStreams(_cascades.values.map((cascade) => cascade.errors));
@@ -95,6 +102,38 @@
return new Future.value(null);
}
+ /// Gets all output assets.
+ ///
+ /// If a build is currently in progress, waits until it completes. The
+ /// returned future will complete with an error if the build is not
+ /// successful.
+ Future<AssetSet> getAllAssets() {
+ if (_cascadeResults.values.contains(null)) {
+ // A build is still ongoing, so wait for it to complete and try again.
+ return results.first.then((_) => getAllAssets());
+ }
+
+ // If an unexpected error occurred, complete with that.
+ if (_lastUnexpectedError != null) {
+ var error = _lastUnexpectedError;
+ _lastUnexpectedError = null;
+ return new Future.error(error);
+ }
+
+ // If the build completed with an error, complete the future with it.
+ var errors = unionAll(
+ _cascadeResults.values.map((result) => result.errors));
+ if (errors.isNotEmpty) {
+ return new Future.error(BarbackException.aggregate(errors));
+ }
+
+ // Otherwise, return all of the final output assets.
+ var assets = unionAll(_cascades.values.map(
+ (cascade) => cascade.availableOutputs.toSet()));
+
+ return new Future.value(new AssetSet.from(assets));
+ }
+
/// Adds [sources] to the graph's known set of source assets.
///
/// Begins applying any transforms that can consume any of the sources. If a
@@ -118,4 +157,10 @@
cascade.removeSources(ids);
});
}
+
+ void updateTransformers(String package,
+ Iterable<Iterable<Transformer>> transformers) {
+ _cascadeResults[package] = null;
+ _cascades[package].updateTransformers(transformers);
+ }
}
diff --git a/pkg/barback/lib/src/package_provider.dart b/pkg/barback/lib/src/package_provider.dart
index 031caf8..349785c 100644
--- a/pkg/barback/lib/src/package_provider.dart
+++ b/pkg/barback/lib/src/package_provider.dart
@@ -8,7 +8,6 @@
import 'asset.dart';
import 'asset_id.dart';
-import 'transformer.dart';
/// API for locating and accessing packages on disk.
///
@@ -21,12 +20,6 @@
/// dependencies.
Iterable<String> get packages;
- /// Returns the list of transformer phases that are applicable to [package].
- ///
- /// The phases will be run in sequence, with the outputs of one pipelined into
- /// the next. All [Transformer]s in a single phase will be run in parallel.
- Iterable<Iterable<Transformer>> getTransformers(String package);
-
/// Loads an asset from disk.
///
/// This should be re-entrant; it may be called multiple times with the same
diff --git a/pkg/barback/lib/src/phase.dart b/pkg/barback/lib/src/phase.dart
index 86444ad..690126a 100644
--- a/pkg/barback/lib/src/phase.dart
+++ b/pkg/barback/lib/src/phase.dart
@@ -7,14 +7,13 @@
import 'dart:async';
import 'dart:collection';
-import 'asset.dart';
import 'asset_cascade.dart';
import 'asset_id.dart';
import 'asset_node.dart';
import 'asset_set.dart';
import 'errors.dart';
+import 'phase_input.dart';
import 'stream_pool.dart';
-import 'transform_node.dart';
import 'transformer.dart';
import 'utils.dart';
@@ -34,47 +33,16 @@
/// The cascade that owns this phase.
final AssetCascade cascade;
- /// This phase's position relative to the other phases. Zero-based.
- final int _index;
-
/// The transformers that can access [inputs].
///
/// Their outputs will be available to the next phase.
- final List<Transformer> _transformers;
+ final Set<Transformer> _transformers;
- /// The inputs that are available for transforms in this phase to consume.
+ /// The inputs for this phase.
///
/// For the first phase, these will be the source assets. For all other
/// phases, they will be the outputs from the previous phase.
- final _inputs = new Map<AssetId, AssetNode>();
-
- /// The transforms currently applicable to assets in [inputs], indexed by
- /// the ids of their primary inputs.
- ///
- /// These are the transforms that have been "wired up": they represent a
- /// repeatable transformation of a single concrete set of inputs. "dart2js"
- /// is a transformer. "dart2js on web/main.dart" is a transform.
- final _transforms = new Map<AssetId, Set<TransformNode>>();
-
- /// Controllers for assets that aren't consumed by transforms in this phase.
- ///
- /// These assets are passed to the next phase unmodified. They need
- /// intervening controllers to ensure that the outputs can be marked dirty
- /// when determining whether transforms apply, and removed if they do.
- final _passThroughControllers = new Map<AssetId, AssetNodeController>();
-
- /// Futures that will complete once the transformers that can consume a given
- /// asset are determined.
- ///
- /// Whenever an asset is added or modified, we need to asynchronously
- /// determine which transformers can use it as their primary input. We can't
- /// start processing until we know which transformers to run, and this allows
- /// us to wait until we do.
- var _adjustTransformersFutures = new Map<AssetId, Future>();
-
- /// New asset nodes that were added while [_adjustTransformers] was still
- /// being run on an old version of that asset.
- var _pendingNewInputs = new Map<AssetId, AssetNode>();
+ final _inputs = new Map<AssetId, PhaseInput>();
/// A map of output ids to the asset node outputs for those ids and the
/// transforms that produced those asset nodes.
@@ -97,18 +65,25 @@
/// A controller whose stream feeds into [_onDirtyPool].
///
- /// This is used whenever an input is added, changed, or removed. It's
- /// sometimes redundant with the events collected from [_transforms], but this
- /// stream is necessary for new and removed inputs, and the transform stream
- /// is necessary for modified secondary inputs.
+ /// This is used whenever an input is added or transforms are changed.
final _onDirtyController = new StreamController.broadcast(sync: true);
/// The phase after this one.
///
/// Outputs from this phase will be passed to it.
- final Phase _next;
+ Phase get next => _next;
+ Phase _next;
- Phase(this.cascade, this._index, this._transformers, this._next) {
+ /// Returns all currently-available output assets for this phase.
+ AssetSet get availableOutputs {
+ return new AssetSet.from(_outputs.values
+ .map((queue) => queue.first)
+ .where((node) => node.state.isAvailable)
+ .map((node) => node.asset));
+ }
+
+ Phase(this.cascade, Iterable<Transformer> transformers)
+ : _transformers = transformers.toSet() {
_onDirtyPool.add(_onDirtyController.stream);
}
@@ -123,51 +98,13 @@
/// removed and re-created. The phase will automatically handle updated assets
/// using the [AssetNode.onStateChange] stream.
void addInput(AssetNode node) {
- // We remove [node.id] from [inputs] as soon as the node is removed rather
- // than at the same time [node.id] is removed from [_transforms] so we don't
- // have to wait on [_adjustTransformers]. It's important that [inputs] is
- // always up-to-date so that the [AssetCascade] can look there for available
- // assets.
- _inputs[node.id] = node;
- node.whenRemoved.then((_) => _inputs.remove(node.id));
+ if (_inputs.containsKey(node.id)) _inputs[node.id].remove();
- if (!_adjustTransformersFutures.containsKey(node.id)) {
- _transforms[node.id] = new Set<TransformNode>();
- _adjustTransformers(node);
- return;
- }
-
- // If an input is added while the same input is still being processed,
- // that means that the asset was removed and recreated while
- // [_adjustTransformers] was being run on the old value. We have to wait
- // until that finishes, then run it again on whatever the newest version
- // of that asset is.
-
- // We may already be waiting for the existing [_adjustTransformers] call to
- // finish. If so, all we need to do is change the node that will be loaded
- // after it completes.
- var containedKey = _pendingNewInputs.containsKey(node.id);
- _pendingNewInputs[node.id] = node;
- if (containedKey) return;
-
- // If we aren't already waiting, start doing so.
- _adjustTransformersFutures[node.id].then((_) {
- assert(!_adjustTransformersFutures.containsKey(node.id));
- assert(_pendingNewInputs.containsKey(node.id));
- _transforms[node.id] = new Set<TransformNode>();
- _adjustTransformers(_pendingNewInputs.remove(node.id));
- }, onError: (_) {
- // If there was a programmatic error while processing the old input,
- // we don't want to just ignore it; it may have left the system in an
- // inconsistent state. We also don't want to top-level it, so we
- // ignore it here but don't start processing the new input. That way
- // when [process] is called, the error will be piped through its
- // return value.
- }).catchError((e) {
- // If our code above has a programmatic error, ensure it will be piped
- // through [process] by putting it into [_adjustTransformersFutures].
- _adjustTransformersFutures[node.id] = new Future.error(e);
- });
+ var input = new PhaseInput(this, node, _transformers);
+ _inputs[node.id] = input;
+ input.input.whenRemoved.then((_) => _inputs.remove(node.id));
+ _onDirtyPool.add(input.onDirty);
+ _onDirtyController.add(null);
}
/// Gets the asset node for an input [id].
@@ -175,189 +112,60 @@
/// If an input with that ID cannot be found, returns null.
Future<AssetNode> getInput(AssetId id) {
return newFuture(() {
- if (id.package == cascade.package) return _inputs[id];
- return cascade.graph.getAssetNode(id);
+ if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
+ if (_inputs.containsKey(id)) return _inputs[id].input;
+ return null;
});
}
- /// Asynchronously determines which transformers can consume [node] as a
- /// primary input and creates transforms for them.
+ /// Gets the asset node for an output [id].
///
- /// This ensures that if [node] is modified or removed during or after the
- /// time it takes to adjust its transformers, they're appropriately
- /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures].
- void _adjustTransformers(AssetNode node) {
- // Mark the phase as dirty. This may not actually end up creating any new
- // transforms, but we want adding or removing a source asset to consistently
- // kick off a build, even if that build does nothing.
+ /// If an output with that ID cannot be found, returns null.
+ Future<AssetNode> getOutput(AssetId id) {
+ return newFuture(() {
+ if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
+ if (!_outputs.containsKey(id)) return null;
+ return _outputs[id].first;
+ });
+ }
+
+ /// Set this phase's transformers to [transformers].
+ void updateTransformers(Iterable<Transformer> transformers) {
_onDirtyController.add(null);
-
- // If there's a pass-through for this node, mark it dirty while we figure
- // out whether we need to add any transforms for it.
- var controller = _passThroughControllers[node.id];
- if (controller != null) controller.setDirty();
-
- // Once the input is available, hook up transformers for it. If it changes
- // while that's happening, try again.
- _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) {
- var oldTransformers = _transforms[node.id]
- .map((transform) => transform.transformer).toSet();
-
- return _removeStaleTransforms(asset)
- .then((_) => _addFreshTransforms(node, oldTransformers));
- }).then((_) {
- _adjustPassThrough(node);
-
- // Now all the transforms are set up correctly and the asset is available
- // for the time being. Set up handlers for when the asset changes in the
- // future.
- node.onStateChange.first.then((state) {
- if (state.isRemoved) {
- _onDirtyController.add(null);
- _transforms.remove(node.id);
- var passThrough = _passThroughControllers.remove(node.id);
- if (passThrough != null) passThrough.setRemoved();
- } else {
- _adjustTransformers(node);
- }
- }).catchError((e) {
- _adjustTransformersFutures[node.id] = new Future.error(e);
- });
- }).catchError((error) {
- if (error is! AssetNotFoundException || error.id != node.id) throw error;
-
- // If the asset is removed, [tryUntilStable] will throw an
- // [AssetNotFoundException]. In that case, just remove all transforms for
- // the node, and its pass-through.
- _transforms.remove(node.id);
- var passThrough = _passThroughControllers.remove(node.id);
- if (passThrough != null) passThrough.setRemoved();
- }).whenComplete(() {
- _adjustTransformersFutures.remove(node.id);
- });
-
- // Don't top-level errors coming from the input processing. Any errors will
- // eventually be piped through [process]'s returned Future.
- _adjustTransformersFutures[node.id].catchError((_) {});
- }
-
- // Remove any old transforms that used to have [asset] as a primary asset but
- // no longer apply to its new contents.
- Future _removeStaleTransforms(Asset asset) {
- return Future.wait(_transforms[asset.id].map((transform) {
- // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
- // results.
- return transform.transformer.isPrimary(asset).then((isPrimary) {
- if (isPrimary) return;
- _transforms[asset.id].remove(transform);
- _onDirtyPool.remove(transform.onDirty);
- transform.remove();
- });
- }));
- }
-
- // Add new transforms for transformers that consider [node]'s asset to be a
- // primary input.
- //
- // [oldTransformers] is the set of transformers that had [node] as a primary
- // input prior to this. They don't need to be checked, since they were removed
- // or preserved in [_removeStaleTransforms].
- Future _addFreshTransforms(AssetNode node, Set<Transformer> oldTransformers) {
- return Future.wait(_transformers.map((transformer) {
- if (oldTransformers.contains(transformer)) return new Future.value();
-
- // If the asset is unavailable, the results of this [_adjustTransformers]
- // run will be discarded, so we can just short-circuit.
- if (node.asset == null) return new Future.value();
-
- // We can safely access [node.asset] here even though it might have
- // changed since (as above) if it has, [_adjustTransformers] will just be
- // re-run.
- // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
- // results.
- return transformer.isPrimary(node.asset).then((isPrimary) {
- if (!isPrimary) return;
- var transform = new TransformNode(this, transformer, node);
- _transforms[node.id].add(transform);
- _onDirtyPool.add(transform.onDirty);
- });
- }));
- }
-
- /// Adjust whether [node] is passed through the phase unmodified, based on
- /// whether it's consumed by other transforms in this phase.
- ///
- /// If [node] was already passed-through, this will update the passed-through
- /// value.
- void _adjustPassThrough(AssetNode node) {
- assert(node.state.isAvailable);
-
- if (_transforms[node.id].isEmpty) {
- var controller = _passThroughControllers[node.id];
- if (controller != null) {
- controller.setAvailable(node.asset);
- } else {
- _passThroughControllers[node.id] =
- new AssetNodeController.available(node.asset, node.transform);
- }
- } else {
- var controller = _passThroughControllers.remove(node.id);
- if (controller != null) controller.setRemoved();
+ _transformers.clear();
+ _transformers.addAll(transformers);
+ for (var input in _inputs.values) {
+ input.updateTransformers(_transformers);
}
}
+ /// Add a new phase after this one with [transformers].
+ ///
+ /// This may only be called on a phase with no phase following it.
+ Phase addPhase(Iterable<Transformer> transformers) {
+ assert(_next == null);
+ _next = new Phase(cascade, transformers);
+ for (var outputs in _outputs.values) {
+ _next.addInput(outputs.first);
+ }
+ return _next;
+ }
+
/// Processes this phase.
///
/// Returns a future that completes when processing is done. If there is
/// nothing to process, returns `null`.
Future process() {
- if (_adjustTransformersFutures.isEmpty) return _processTransforms();
- return _waitForInputs().then((_) => _processTransforms());
- }
+ if (!_inputs.values.any((input) => input.isDirty)) return null;
- Future _waitForInputs() {
- if (_adjustTransformersFutures.isEmpty) return new Future.value();
- return Future.wait(_adjustTransformersFutures.values)
- .then((_) => _waitForInputs());
- }
-
- /// Applies all currently wired up and dirty transforms.
- Future _processTransforms() {
- if (_next == null) return;
-
- var newPassThroughs = _passThroughControllers.values
- .map((controller) => controller.node)
- .where((output) {
- return !_outputs.containsKey(output.id) ||
- !_outputs[output.id].contains(output);
- }).toSet();
-
- // Convert this to a list so we can safely modify _transforms while
- // iterating over it.
- var dirtyTransforms =
- flatten(_transforms.values.map((transforms) => transforms.toList()))
- .where((transform) => transform.isDirty).toList();
-
- if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null;
-
- var collisions = _passAssetsThrough(newPassThroughs);
- return Future.wait(dirtyTransforms.map((transform) {
- return transform.apply().then((outputs) {
- for (var output in outputs) {
- if (_outputs.containsKey(output.id)) {
- _outputs[output.id].add(output);
- collisions.add(output.id);
- } else {
- _outputs[output.id] = new Queue<AssetNode>.from([output]);
- _next.addInput(output);
- }
-
- _handleOutputRemoval(output);
- }
+ return Future.wait(_inputs.values.map((input) {
+ if (!input.isDirty) return new Future.value(new Set());
+ return input.process().then((outputs) {
+ return outputs.where(_addOutput).map((output) => output.id).toSet();
});
- })).then((_) {
+ })).then((collisionsList) {
// Report collisions in a deterministic order.
- collisions = collisions.toList();
+ var collisions = unionAll(collisionsList).toList();
collisions.sort((a, b) => a.compareTo(b));
for (var collision in collisions) {
// Ensure that there's still a collision. It's possible it was resolved
@@ -371,28 +179,21 @@
});
}
- /// Pass all new assets that aren't consumed by transforms through to the next
- /// phase.
+ /// Add [output] as an output of this phase, forwarding it to the next phase
+ /// if necessary.
///
- /// Returns a set of asset ids that have collisions between new passed-through
- /// assets and pre-existing transform outputs.
- Set<AssetId> _passAssetsThrough(Set<AssetId> newPassThroughs) {
- var collisions = new Set<AssetId>();
- for (var output in newPassThroughs) {
- if (_outputs.containsKey(output.id)) {
- // There shouldn't be another pass-through asset with the same id.
- assert(!_outputs[output.id].any((asset) => asset.transform == null));
+ /// Returns whether or not [output] collides with another pre-existing output.
+ bool _addOutput(AssetNode output) {
+ _handleOutputRemoval(output);
- _outputs[output.id].add(output);
- collisions.add(output.id);
- } else {
- _outputs[output.id] = new Queue<AssetNode>.from([output]);
- _next.addInput(output);
- }
-
- _handleOutputRemoval(output);
+ if (_outputs.containsKey(output.id)) {
+ _outputs[output.id].add(output);
+ return true;
}
- return collisions;
+
+ _outputs[output.id] = new Queue<AssetNode>.from([output]);
+ if (_next != null) _next.addInput(output);
+ return false;
}
/// Properly resolve collisions when [output] is removed.
@@ -414,7 +215,7 @@
// (chronologically) to the next phase. Pump the event queue first to give
// [_next] a chance to handle the removal of its input before getting a
// new input.
- if (wasFirst) {
+ if (wasFirst && _next != null) {
newFuture(() => _next.addInput(assets.first));
}
diff --git a/pkg/barback/lib/src/phase_input.dart b/pkg/barback/lib/src/phase_input.dart
new file mode 100644
index 0000000..7ba6c20
--- /dev/null
+++ b/pkg/barback/lib/src/phase_input.dart
@@ -0,0 +1,296 @@
+// 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.
+
+library barback.phase_input;
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'asset.dart';
+import 'asset_forwarder.dart';
+import 'asset_node.dart';
+import 'errors.dart';
+import 'stream_pool.dart';
+import 'transform_node.dart';
+import 'transformer.dart';
+import 'utils.dart';
+
+/// A class for watching a single [AssetNode] and running any transforms that
+/// take that node as a primary input.
+class PhaseInput {
+ /// The phase for which this is an input.
+ final Phase _phase;
+
+ /// The transformers to (potentially) run against [input].
+ final Set<Transformer> _transformers;
+
+ /// The transforms currently applicable to [input].
+ ///
+ /// These are the transforms that have been "wired up": they represent a
+ /// repeatable transformation of a single concrete set of inputs. "dart2js" is
+ /// a transformer. "dart2js on web/main.dart" is a transform.
+ final _transforms = new Set<TransformNode>();
+
+ /// A forwarder for the input [AssetNode] for this phase.
+ ///
+ /// This is used to mark the node as removed should the input ever be removed.
+ final AssetForwarder _inputForwarder;
+
+ /// The asset node for this input.
+ AssetNode get input => _inputForwarder.node;
+
+ /// The controller that's used for the output node if [input] isn't consumed
+ /// by any transformers.
+ ///
+ /// This needs an intervening controller to ensure that the output can be
+ /// marked dirty when determining whether transforms apply, and removed if
+ /// they do. It's null if the asset is not being passed through.
+ AssetNodeController _passThroughController;
+
+ /// Whether [_passThroughController] has been newly created since [process]
+ /// last completed.
+ bool _newPassThrough = false;
+
+ /// A Future that will complete once the transformers that consume [input] are
+ /// determined.
+ Future _adjustTransformersFuture;
+
+ /// A stream that emits an event whenever this input becomes dirty and needs
+ /// [process] to be called.
+ ///
+ /// This may emit events when the input was already dirty or while processing
+ /// transforms. Events are emitted synchronously to ensure that the dirty
+ /// state is thoroughly propagated as soon as any assets are changed.
+ Stream get onDirty => _onDirtyPool.stream;
+ final _onDirtyPool = new StreamPool.broadcast();
+
+ /// A controller whose stream feeds into [_onDirtyPool].
+ ///
+ /// This is used whenever the input is changed or removed. It's sometimes
+ /// redundant with the events collected from [_transforms], but this stream is
+ /// necessary for removed inputs, and the transform stream is necessary for
+ /// modified secondary inputs.
+ final _onDirtyController = new StreamController.broadcast(sync: true);
+
+ /// Whether this input is dirty and needs [process] to be called.
+ bool get isDirty => _adjustTransformersFuture != null ||
+ _newPassThrough || _transforms.any((transform) => transform.isDirty);
+
+ PhaseInput(this._phase, AssetNode input, Iterable<Transformer> transformers)
+ : _transformers = transformers.toSet(),
+ _inputForwarder = new AssetForwarder(input) {
+ _onDirtyPool.add(_onDirtyController.stream);
+
+ input.onStateChange.listen((state) {
+ if (state.isRemoved) {
+ remove();
+ } else if (_adjustTransformersFuture == null) {
+ _adjustTransformers();
+ }
+ });
+
+ _adjustTransformers();
+ }
+
+ /// Removes this input.
+ ///
+ /// This marks all outputs of the input as removed.
+ void remove() {
+ _onDirtyController.add(null);
+ _onDirtyPool.close();
+ _inputForwarder.close();
+ if (_passThroughController != null) {
+ _passThroughController.setRemoved();
+ _passThroughController = null;
+ }
+ }
+
+ /// Set this input's transformers to [transformers].
+ void updateTransformers(Set<Transformer> newTransformers) {
+ var oldTransformers = _transformers.toSet();
+ for (var removedTransformer in
+ oldTransformers.difference(newTransformers)) {
+ _transformers.remove(removedTransformer);
+
+ // If the transformers are being adjusted for [id], it will
+ // automatically pick up on [removedTransformer] being gone.
+ if (_adjustTransformersFuture != null) continue;
+
+ _transforms.removeWhere((transform) {
+ if (transform.transformer != removedTransformer) return false;
+ transform.remove();
+ return true;
+ });
+ }
+
+ if (_transforms.isEmpty && _adjustTransformersFuture == null &&
+ _passThroughController == null) {
+ _passThroughController =
+ new AssetNodeController.available(input.asset, input.transform);
+ _newPassThrough = true;
+ }
+
+ var brandNewTransformers = newTransformers.difference(oldTransformers);
+ if (brandNewTransformers.isEmpty) return;
+
+ brandNewTransformers.forEach(_transformers.add);
+ _adjustTransformers();
+ }
+
+ /// Asynchronously determines which transformers can consume [input] as a
+ /// primary input and creates transforms for them.
+ ///
+ /// This ensures that if [input] is modified or removed during or after the
+ /// time it takes to adjust its transformers, they're appropriately
+ /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture].
+ void _adjustTransformers() {
+ // Mark the input as dirty. This may not actually end up creating any new
+ // transforms, but we want adding or removing a source asset to consistently
+ // kick off a build, even if that build does nothing.
+ _onDirtyController.add(null);
+
+ // If there's a pass-through for this input, mark it dirty while we figure
+ // out whether we need to add any transforms for it.
+ if (_passThroughController != null) _passThroughController.setDirty();
+
+ // Once the input is available, hook up transformers for it. If it changes
+ // while that's happening, try again.
+ _adjustTransformersFuture = _tryUntilStable((asset, transformers) {
+ var oldTransformers =
+ _transforms.map((transform) => transform.transformer).toSet();
+
+ return _removeStaleTransforms(asset, transformers).then((_) =>
+ _addFreshTransforms(transformers, oldTransformers));
+ }).then((_) => _adjustPassThrough()).catchError((error) {
+ if (error is! AssetNotFoundException || error.id != input.id) {
+ throw error;
+ }
+
+ // If the asset is removed, [_tryUntilStable] will throw an
+ // [AssetNotFoundException]. In that case, just remove it.
+ remove();
+ }).whenComplete(() {
+ _adjustTransformersFuture = null;
+ });
+
+ // Don't top-level errors coming from the input processing. Any errors will
+ // eventually be piped through [process]'s returned Future.
+ _adjustTransformersFuture.catchError((_) {});
+ }
+
+ // Remove any old transforms that used to have [asset] as a primary asset but
+ // no longer apply to its new contents.
+ Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) {
+ return Future.wait(_transforms.map((transform) {
+ return newFuture(() {
+ if (!transformers.contains(transform.transformer)) return false;
+
+ // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
+ // results.
+ return transform.transformer.isPrimary(asset);
+ }).then((isPrimary) {
+ if (isPrimary) return;
+ _transforms.remove(transform);
+ transform.remove();
+ });
+ }));
+ }
+
+ // Add new transforms for transformers that consider [input]'s asset to be a
+ // primary input.
+ //
+ // [oldTransformers] is the set of transformers for which there were
+ // transforms that had [input] as a primary input prior to this. They don't
+ // need to be checked, since their transforms were removed or preserved in
+ // [_removeStaleTransforms].
+ Future _addFreshTransforms(Set<Transformer> transformers,
+ Set<Transformer> oldTransformers) {
+ return Future.wait(transformers.map((transformer) {
+ if (oldTransformers.contains(transformer)) return new Future.value();
+
+ // If the asset is unavailable, the results of this [_adjustTransformers]
+ // run will be discarded, so we can just short-circuit.
+ if (input.asset == null) return new Future.value();
+
+ // We can safely access [input.asset] here even though it might have
+ // changed since (as above) if it has, [_adjustTransformers] will just be
+ // re-run.
+ // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
+ // results.
+ return transformer.isPrimary(input.asset).then((isPrimary) {
+ if (!isPrimary) return;
+ var transform = new TransformNode(_phase, transformer, input);
+ _transforms.add(transform);
+ _onDirtyPool.add(transform.onDirty);
+ });
+ }));
+ }
+
+ /// Adjust whether [input] is passed through the phase unmodified, based on
+ /// whether it's consumed by other transforms in this phase.
+ ///
+ /// If [input] was already passed-through, this will update the passed-through
+ /// value.
+ void _adjustPassThrough() {
+ assert(input.state.isAvailable);
+
+ if (_transforms.isEmpty) {
+ if (_passThroughController != null) {
+ _passThroughController.setAvailable(input.asset);
+ } else {
+ _passThroughController =
+ new AssetNodeController.available(input.asset, input.transform);
+ _newPassThrough = true;
+ }
+ } else if (_passThroughController != null) {
+ _passThroughController.setRemoved();
+ _passThroughController = null;
+ _newPassThrough = false;
+ }
+ }
+
+ /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this
+ /// phase's transformers are modified.
+ Future _tryUntilStable(
+ Future callback(Asset asset, Set<Transformer> transformers)) {
+ var oldTransformers;
+ return input.tryUntilStable((asset) {
+ oldTransformers = _transformers.toSet();
+ return callback(asset, _transformers);
+ }).then((result) {
+ if (setEquals(oldTransformers, _transformers)) return result;
+ return _tryUntilStable(callback);
+ });
+ }
+
+ /// Processes the transforms for this input.
+ Future<Set<AssetNode>> process() {
+ if (_adjustTransformersFuture == null) return _processTransforms();
+ return _waitForInputs().then((_) => _processTransforms());
+ }
+
+ Future _waitForInputs() {
+ // Return a synchronous future so we can be sure [_adjustTransformers] isn't
+ // called between now and when the Future completes.
+ if (_adjustTransformersFuture == null) return new Future.sync(() {});
+ return _adjustTransformersFuture.then((_) => _waitForInputs());
+ }
+
+ /// Applies all currently wired up and dirty transforms.
+ Future<Set<AssetNode>> _processTransforms() {
+ if (input.state.isRemoved) return new Future.value(new Set());
+
+ if (_passThroughController != null) {
+ if (!_newPassThrough) return new Future.value(new Set());
+ _newPassThrough = false;
+ return new Future.value(
+ new Set<AssetNode>.from([_passThroughController.node]));
+ }
+
+ return Future.wait(_transforms.map((transform) {
+ if (!transform.isDirty) return new Future.value(new Set());
+ return transform.apply();
+ })).then((outputs) => unionAll(outputs));
+ }
+}
diff --git a/pkg/barback/lib/src/transform.dart b/pkg/barback/lib/src/transform.dart
index c254778..d5d7176 100644
--- a/pkg/barback/lib/src/transform.dart
+++ b/pkg/barback/lib/src/transform.dart
@@ -13,7 +13,6 @@
import 'errors.dart';
import 'transform_logger.dart';
import 'transform_node.dart';
-import 'utils.dart';
/// Creates a [Transform] by forwarding to the private constructor.
///
diff --git a/pkg/barback/lib/src/transform_node.dart b/pkg/barback/lib/src/transform_node.dart
index 1d3d1dd..578b2fa 100644
--- a/pkg/barback/lib/src/transform_node.dart
+++ b/pkg/barback/lib/src/transform_node.dart
@@ -14,7 +14,6 @@
import 'phase.dart';
import 'transform.dart';
import 'transformer.dart';
-import 'utils.dart';
/// Describes a transform on a set of assets and its relationship to the build
/// dependency graph.
@@ -133,7 +132,7 @@
// Don't allow partial results from a failed transform.
newOutputs.clear();
}).then((_) {
- if (_isDirty) return [];
+ if (_isDirty) return new Set();
return _adjustOutputs(newOutputs);
});
diff --git a/pkg/barback/lib/src/transformer.dart b/pkg/barback/lib/src/transformer.dart
index 422066f..e8479f1 100644
--- a/pkg/barback/lib/src/transformer.dart
+++ b/pkg/barback/lib/src/transformer.dart
@@ -5,7 +5,6 @@
library barback.transformer;
import 'dart:async';
-import 'dart:io';
import 'asset.dart';
import 'transform.dart';
@@ -17,6 +16,15 @@
/// files are all examples of transformers. To define your own transformation
/// step, extend (or implement) this class.
abstract class Transformer {
+ /// Override this to return a space-separated list of file extensions
+ /// (with leading `.`) that are allowed for the primary inputs to this
+ /// transformer.
+ ///
+ /// If you don't override [isPrimary] yourself, it defaults to allowing any
+ /// asset whose extension matches one of the ones returned by this. If you
+ /// don't override [isPrimary] *or* this, it allows all files.
+ String get allowedExtensions => null;
+
/// Returns `true` if [input] can be a primary input for this transformer.
///
/// While a transformer can read from multiple input files, one must be the
@@ -29,7 +37,20 @@
/// of those to generate the final JS. However you still run dart2js "on" a
/// single file: the entrypoint Dart file that has your `main()` method.
/// This entrypoint file would be the primary input.
- Future<bool> isPrimary(Asset input);
+ ///
+ /// If this is not overridden, defaults to allow any asset whose extension
+ /// matches one of the ones returned by [allowedExtensions]. If *that* is
+ /// not overridden, allows all assets.
+ Future<bool> isPrimary(Asset input) {
+ // Allow all files if [primaryExtensions] is not overridden.
+ if (allowedExtensions == null) return new Future.value(true);
+
+ for (var extension in allowedExtensions.split(" ")) {
+ if (input.id.extension == extension) return new Future.value(true);
+ }
+
+ return new Future.value(false);
+ }
/// Run this transformer on on the primary input specified by [transform].
///
diff --git a/pkg/barback/lib/src/utils.dart b/pkg/barback/lib/src/utils.dart
index bee6084..3272e9e 100644
--- a/pkg/barback/lib/src/utils.dart
+++ b/pkg/barback/lib/src/utils.dart
@@ -72,6 +72,10 @@
Map mapMapValues(Map map, fn(key, value)) =>
new Map.fromIterable(map.keys, value: (key) => fn(key, map[key]));
+/// Returns whether [set1] has exactly the same elements as [set2].
+bool setEquals(Set set1, Set set2) =>
+ set1.length == set2.length && set1.containsAll(set2);
+
/// Merges [streams] into a single stream that emits events from all sources.
Stream mergeStreams(Iterable<Stream> streams) {
streams = streams.toList();
diff --git a/pkg/barback/test/asset_id_test.dart b/pkg/barback/test/asset_id_test.dart
index 6e4a7d8..a55bb18 100644
--- a/pkg/barback/test/asset_id_test.dart
+++ b/pkg/barback/test/asset_id_test.dart
@@ -4,8 +4,6 @@
library barback.test.asset_id_test;
-import 'dart:async';
-
import 'package:barback/barback.dart';
import 'package:unittest/unittest.dart';
diff --git a/pkg/barback/test/asset_set_test.dart b/pkg/barback/test/asset_set_test.dart
index 5b6b652..3208329 100644
--- a/pkg/barback/test/asset_set_test.dart
+++ b/pkg/barback/test/asset_set_test.dart
@@ -4,11 +4,7 @@
library barback.test.asset_set_test;
-import 'dart:async';
-import 'dart:io';
-
import 'package:barback/barback.dart';
-import 'package:barback/src/asset_set.dart';
import 'package:unittest/unittest.dart';
import 'utils.dart';
diff --git a/pkg/barback/test/package_graph/add_remove_transform_test.dart b/pkg/barback/test/package_graph/add_remove_transform_test.dart
new file mode 100644
index 0000000..b79c69a
--- /dev/null
+++ b/pkg/barback/test/package_graph/add_remove_transform_test.dart
@@ -0,0 +1,240 @@
+// 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.
+
+library barback.test.package_graph.transform_test;
+
+import 'package:barback/src/utils.dart';
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../utils.dart';
+
+main() {
+ initConfig();
+ test("a new transformer is applied to a matching asset", () {
+ initGraph(["app|foo.blub"]);
+
+ updateSources(["app|foo.blub"]);
+ expectAsset("app|foo.blub", "foo");
+ buildShouldSucceed();
+
+ updateTransformers("app", [[new RewriteTransformer("blub", "blab")]]);
+ expectAsset("app|foo.blab", "foo.blab");
+ expectNoAsset("app|foo.blub");
+ buildShouldSucceed();
+ });
+
+ test("a new transformer is not applied to a non-matching asset", () {
+ initGraph(["app|foo.blub"]);
+
+ updateSources(["app|foo.blub"]);
+ expectAsset("app|foo.blub", "foo");
+ buildShouldSucceed();
+
+ updateTransformers("app", [[new RewriteTransformer("zip", "zap")]]);
+ expectAsset("app|foo.blub", "foo");
+ expectNoAsset("app|foo.zap");
+ buildShouldSucceed();
+ });
+
+ test("updateTransformers doesn't re-run an old transformer", () {
+ var rewrite = new RewriteTransformer("blub", "blab");
+ initGraph(["app|foo.blub"], {"app": [[rewrite]]});
+
+ updateSources(["app|foo.blub"]);
+ expectAsset("app|foo.blab", "foo.blab");
+ expectNoAsset("app|foo.blub");
+ buildShouldSucceed();
+
+ updateTransformers("app", [[rewrite]]);
+ expectAsset("app|foo.blab", "foo.blab");
+ expectNoAsset("app|foo.blub");
+ buildShouldSucceed();
+
+ expect(rewrite.numRuns, completion(equals(1)));
+ });
+
+ test("updateTransformers re-runs old transformers in a new phase", () {
+ var rewrite1 = new RewriteTransformer("txt", "blub");
+ var rewrite2 = new RewriteTransformer("blub", "blab");
+ initGraph(["app|foo.txt"], {"app": [[rewrite1], [rewrite2]]});
+
+ updateSources(["app|foo.txt"]);
+ expectAsset("app|foo.blab", "foo.blub.blab");
+ expectNoAsset("app|foo.blub");
+ buildShouldSucceed();
+
+ updateTransformers("app", [[rewrite2], [rewrite1]]);
+ expectAsset("app|foo.blub", "foo.blub");
+ expectNoAsset("app|foo.blab");
+ buildShouldSucceed();
+ });
+
+ test("updateTransformers re-runs an old transformer when a previous phase "
+ "changes", () {
+ var rewrite = new RewriteTransformer("txt", "out");
+ initGraph(["app|foo.txt"], {"app": [[], [rewrite]]});
+
+ updateSources(["app|foo.txt"]);
+ expectAsset("app|foo.out", "foo.out");
+ buildShouldSucceed();
+
+ updateTransformers("app", [
+ [new RewriteTransformer("txt", "txt")],
+ [rewrite]
+ ]);
+ expectAsset("app|foo.out", "foo.txt.out");
+ buildShouldSucceed();
+ });
+
+ test("a removed transformer is no longer applied", () {
+ initGraph(["app|foo.blub"], {"app": [
+ [new RewriteTransformer("blub", "blab")]
+ ]});
+
+ updateSources(["app|foo.blub"]);
+ expectAsset("app|foo.blab", "foo.blab");
+ expectNoAsset("app|foo.blub");
+ buildShouldSucceed();
+
+ updateTransformers("app", []);
+ expectAsset("app|foo.blub", "foo");
+ expectNoAsset("app|foo.blab");
+ buildShouldSucceed();
+ });
+
+ test("a new transformer is pipelined", () {
+ var rewrite1 = new RewriteTransformer("source", "phase1");
+ var rewrite3 = new RewriteTransformer("phase2", "phase3");
+ initGraph(["app|foo.source"], {"app": [
+ [rewrite1],
+ [rewrite3]
+ ]});
+
+ updateSources(["app|foo.source"]);
+ expectNoAsset("app|foo.phase3");
+ buildShouldSucceed();
+
+ updateTransformers("app", [
+ [rewrite1],
+ [new RewriteTransformer("phase1", "phase2")],
+ [rewrite3]
+ ]);
+ expectAsset("app|foo.phase3", "foo.phase1.phase2.phase3");
+ buildShouldSucceed();
+ });
+
+ test("a removed transformer is un-pipelined", () {
+ var rewrite1 = new RewriteTransformer("source", "phase1");
+ var rewrite3 = new RewriteTransformer("phase2", "phase3");
+ initGraph(["app|foo.source"], {"app": [
+ [rewrite1],
+ [new RewriteTransformer("phase1", "phase2")],
+ [rewrite3]
+ ]});
+
+ updateSources(["app|foo.source"]);
+ expectAsset("app|foo.phase3", "foo.phase1.phase2.phase3");
+ buildShouldSucceed();
+
+ updateTransformers("app", [[rewrite1], [rewrite3]]);
+ expectNoAsset("app|foo.phase3");
+ buildShouldSucceed();
+ });
+
+ test("a transformer is removed during isPrimary", () {
+ var rewrite = new RewriteTransformer("blub", "blab");
+ initGraph(["app|foo.blub"], {"app": [[rewrite]]});
+
+ rewrite.pauseIsPrimary("app|foo.blub");
+ updateSources(["app|foo.blub"]);
+ // Ensure we're waiting on [rewrite.isPrimary].
+ schedule(pumpEventQueue);
+
+ updateTransformers("app", []);
+ rewrite.resumeIsPrimary("app|foo.blub");
+ expectAsset("app|foo.blub", "foo");
+ expectNoAsset("app|foo.blab");
+ buildShouldSucceed();
+ });
+
+ test("a transformer is removed during apply", () {
+ var rewrite = new RewriteTransformer("blub", "blab");
+ initGraph(["app|foo.blub"], {"app": [[rewrite]]});
+
+ rewrite.pauseApply();
+ updateSources(["app|foo.blub"]);
+ // Ensure we're waiting on [rewrite.apply].
+ schedule(pumpEventQueue);
+
+ updateTransformers("app", []);
+ rewrite.resumeApply();
+ expectAsset("app|foo.blub", "foo");
+ expectNoAsset("app|foo.blab");
+ buildShouldSucceed();
+ });
+
+ test("a new transformer can see pass-through assets", () {
+ var rewrite = new RewriteTransformer("zip", "zap");
+ initGraph(["app|foo.blub"], {"app": [[rewrite]]});
+
+ updateSources(["app|foo.blub"]);
+ buildShouldSucceed();
+
+ updateTransformers("app", [
+ [rewrite],
+ [new RewriteTransformer("blub", "blab")]
+ ]);
+ expectAsset("app|foo.blab", "foo.blab");
+ expectNoAsset("app|foo.blub");
+ buildShouldSucceed();
+ });
+
+ test("a cross-package transform sees a new transformer in a new phase", () {
+ // TODO(nweiz): make this work.
+ return;
+
+ var rewrite = new RewriteTransformer("inc", "inc");
+ initGraph({
+ "pkg1|foo.txt": "pkg2|foo.inc",
+ "pkg2|foo.inc": "foo"
+ }, {
+ "pkg1": [[new ManyToOneTransformer("txt")]],
+ "pkg2": [[rewrite]]
+ });
+
+ updateSources(["pkg1|foo.txt", "pkg2|foo.inc"]);
+ expectAsset("pkg1|foo.out", "foo");
+ buildShouldSucceed();
+
+ updateTransformers("pkg2", [
+ [rewrite],
+ [new RewriteTransformer("inc", "inc")]
+ ]);
+ expectAsset("pkg1|foo.out", "foo.inc.inc");
+ buildShouldSucceed();
+ });
+
+ test("a cross-package transform doesn't see a removed transformer in a "
+ "removed phase", () {
+ var rewrite = new RewriteTransformer("inc", "inc");
+ initGraph({
+ "pkg1|foo.txt": "pkg2|foo.inc",
+ "pkg2|foo.inc": "foo"
+ }, {
+ "pkg1": [[new ManyToOneTransformer("txt")]],
+ "pkg2": [
+ [rewrite],
+ [new RewriteTransformer("inc", "inc")]
+ ]
+ });
+
+ updateSources(["pkg1|foo.txt", "pkg2|foo.inc"]);
+ expectAsset("pkg1|foo.out", "foo.inc.inc");
+ buildShouldSucceed();
+
+ updateTransformers("pkg2", [[rewrite]]);
+ expectAsset("pkg1|foo.out", "foo.inc");
+ buildShouldSucceed();
+ });
+}
\ No newline at end of file
diff --git a/pkg/barback/test/package_graph/errors_test.dart b/pkg/barback/test/package_graph/errors_test.dart
index 8e4fd89..8c74991 100644
--- a/pkg/barback/test/package_graph/errors_test.dart
+++ b/pkg/barback/test/package_graph/errors_test.dart
@@ -4,9 +4,6 @@
library barback.test.package_graph.source_test;
-import 'dart:async';
-
-import 'package:barback/barback.dart';
import 'package:barback/src/utils.dart';
import 'package:scheduled_test/scheduled_test.dart';
diff --git a/pkg/barback/test/package_graph/get_all_assets_test.dart b/pkg/barback/test/package_graph/get_all_assets_test.dart
new file mode 100644
index 0000000..0ece84b
--- /dev/null
+++ b/pkg/barback/test/package_graph/get_all_assets_test.dart
@@ -0,0 +1,77 @@
+// 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.
+
+library barback.test.barback_test;
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../utils.dart';
+
+main() {
+ initConfig();
+
+ test("gets all source assets", () {
+ initGraph(["app|a.txt", "app|b.txt", "app|c.txt"]);
+ updateSources(["app|a.txt", "app|b.txt", "app|c.txt"]);
+ expectAllAssets(["app|a.txt", "app|b.txt", "app|c.txt"]);
+ buildShouldSucceed();
+ });
+
+ test("includes transformed outputs, but not consumed ones", () {
+ initGraph(["app|a.txt", "app|foo.blub"], {"app": [
+ [new RewriteTransformer("blub", "blab")]
+ ]});
+ updateSources(["app|a.txt", "app|foo.blub"]);
+ expectAllAssets(["app|a.txt", "app|foo.blab"]);
+ buildShouldSucceed();
+ });
+
+ test("includes non-primary inputs to transformers", () {
+ var transformer = new ManyToOneTransformer("txt");
+ initGraph({
+ "app|a.txt": "a.inc",
+ "app|a.inc": "a"
+ }, {"app": [[transformer]]});
+
+ updateSources(["app|a.txt", "app|a.inc"]);
+ expectAllAssets(["app|a.inc", "app|a.out"]);
+ buildShouldSucceed();
+ });
+
+ test("completes to an error if two transformers output the same file", () {
+ initGraph(["app|foo.a"], {"app": [
+ [
+ new RewriteTransformer("a", "b"),
+ new RewriteTransformer("a", "b")
+ ]
+ ]});
+ updateSources(["app|foo.a"]);
+ expectAllAssetsShouldFail(isAssetCollisionException("app|foo.b"));
+ });
+
+ test("completes to an error if a transformer fails", () {
+ initGraph(["app|foo.txt"], {"app": [
+ [new BadTransformer(["app|foo.out"])]
+ ]});
+
+ updateSources(["app|foo.txt"]);
+ expectAllAssetsShouldFail(isTransformerException(
+ equals(BadTransformer.ERROR)));
+ });
+
+ test("completes to an aggregate error if there are multiple errors", () {
+ initGraph(["app|foo.txt"], {"app": [
+ [
+ new BadTransformer(["app|foo.out"]),
+ new BadTransformer(["app|foo.out2"])
+ ]
+ ]});
+
+ updateSources(["app|foo.txt"]);
+ expectAllAssetsShouldFail(isAggregateException([
+ isTransformerException(equals(BadTransformer.ERROR)),
+ isTransformerException(equals(BadTransformer.ERROR))
+ ]));
+ });
+}
diff --git a/pkg/barback/test/package_graph/source_test.dart b/pkg/barback/test/package_graph/source_test.dart
index 4c47df3..b5763a8 100644
--- a/pkg/barback/test/package_graph/source_test.dart
+++ b/pkg/barback/test/package_graph/source_test.dart
@@ -4,10 +4,6 @@
library barback.test.package_graph.source_test;
-import 'dart:async';
-
-import 'package:barback/barback.dart';
-import 'package:barback/src/utils.dart';
import 'package:scheduled_test/scheduled_test.dart';
import '../utils.dart';
diff --git a/pkg/barback/test/package_graph/transform_test.dart b/pkg/barback/test/package_graph/transform_test.dart
index 4d7b97e..8c108b3 100644
--- a/pkg/barback/test/package_graph/transform_test.dart
+++ b/pkg/barback/test/package_graph/transform_test.dart
@@ -4,9 +4,6 @@
library barback.test.package_graph.transform_test;
-import 'dart:async';
-
-import 'package:barback/barback.dart';
import 'package:barback/src/utils.dart';
import 'package:scheduled_test/scheduled_test.dart';
@@ -1058,5 +1055,45 @@
expectNoAsset("pkg1|c.done");
buildShouldSucceed();
});
+
+ test("sees a transformer that's newly applied to a cross-package "
+ "dependency", () {
+ initGraph({
+ "pkg1|a.txt": "pkg2|a.inc",
+ "pkg2|a.inc": "a"
+ }, {
+ "pkg1": [[new ManyToOneTransformer("txt")]],
+ "pkg2": [[new CheckContentTransformer("b", " transformed")]]
+ });
+
+ updateSources(["pkg1|a.txt", "pkg2|a.inc"]);
+ expectAsset("pkg1|a.out", "a");
+ buildShouldSucceed();
+
+ modifyAsset("pkg2|a.inc", "b");
+ updateSources(["pkg2|a.inc"]);
+ expectAsset("pkg1|a.out", "b transformed");
+ buildShouldSucceed();
+ });
+
+ test("doesn't see a transformer that's newly not applied to a "
+ "cross-package dependency", () {
+ initGraph({
+ "pkg1|a.txt": "pkg2|a.inc",
+ "pkg2|a.inc": "a"
+ }, {
+ "pkg1": [[new ManyToOneTransformer("txt")]],
+ "pkg2": [[new CheckContentTransformer("a", " transformed")]]
+ });
+
+ updateSources(["pkg1|a.txt", "pkg2|a.inc"]);
+ expectAsset("pkg1|a.out", "a transformed");
+ buildShouldSucceed();
+
+ modifyAsset("pkg2|a.inc", "b");
+ updateSources(["pkg2|a.inc"]);
+ expectAsset("pkg1|a.out", "b");
+ buildShouldSucceed();
+ });
});
}
diff --git a/pkg/barback/test/transformer/many_to_one.dart b/pkg/barback/test/transformer/many_to_one.dart
index 45f8f25..59a190c 100644
--- a/pkg/barback/test/transformer/many_to_one.dart
+++ b/pkg/barback/test/transformer/many_to_one.dart
@@ -7,7 +7,6 @@
import 'dart:async';
import 'package:barback/barback.dart';
-import 'package:barback/src/utils.dart';
import 'mock.dart';
diff --git a/pkg/barback/test/transformer_test.dart b/pkg/barback/test/transformer_test.dart
new file mode 100644
index 0000000..9df3f4a
--- /dev/null
+++ b/pkg/barback/test/transformer_test.dart
@@ -0,0 +1,59 @@
+// 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.
+
+library barback.test.transformer_test;
+
+import 'dart:async';
+
+import 'package:barback/barback.dart';
+import 'package:unittest/unittest.dart';
+
+import 'utils.dart';
+
+main() {
+ initConfig();
+
+ group("isPrimary", () {
+ test("defaults to allowedExtensions", () {
+ var transformer = new ExtensionTransformer(".txt .bin");
+ expect(transformer.isPrimary(makeAsset("foo.txt")),
+ completion(isTrue));
+
+ expect(transformer.isPrimary(makeAsset("foo.bin")),
+ completion(isTrue));
+
+ expect(transformer.isPrimary(makeAsset("foo.nottxt")),
+ completion(isFalse));
+ });
+
+ test("allows all files if allowedExtensions is not overridden", () {
+ var transformer = new MockTransformer();
+ expect(transformer.isPrimary(makeAsset("foo.txt")),
+ completion(isTrue));
+
+ expect(transformer.isPrimary(makeAsset("foo.bin")),
+ completion(isTrue));
+
+ expect(transformer.isPrimary(makeAsset("anything")),
+ completion(isTrue));
+ });
+ });
+}
+
+Asset makeAsset(String path) =>
+ new Asset.fromString(new AssetId.parse("app|$path"), "");
+
+class MockTransformer extends Transformer {
+ MockTransformer();
+
+ Future apply(Transform transform) => new Future.value();
+}
+
+class ExtensionTransformer extends Transformer {
+ final String allowedExtensions;
+
+ ExtensionTransformer(this.allowedExtensions);
+
+ Future apply(Transform transform) => new Future.value();
+}
\ No newline at end of file
diff --git a/pkg/barback/test/utils.dart b/pkg/barback/test/utils.dart
index 99a7ec1..73f4f8b 100644
--- a/pkg/barback/test/utils.dart
+++ b/pkg/barback/test/utils.dart
@@ -9,7 +9,6 @@
import 'dart:io';
import 'package:barback/barback.dart';
-import 'package:barback/src/asset_set.dart';
import 'package:barback/src/cancelable_future.dart';
import 'package:barback/src/utils.dart';
import 'package:path/path.dart' as pathos;
@@ -61,9 +60,38 @@
if (assets == null) assets = [];
if (transformers == null) transformers = {};
- _provider = new MockProvider(assets, transformers);
+ var assetList;
+ if (assets is Map) {
+ assetList = assets.keys.map((asset) {
+ var id = new AssetId.parse(asset);
+ return new _MockAsset(id, assets[asset]);
+ });
+ } else if (assets is Iterable) {
+ assetList = assets.map((asset) {
+ var id = new AssetId.parse(asset);
+ var contents = pathos.basenameWithoutExtension(id.path);
+ return new _MockAsset(id, contents);
+ });
+ }
+
+ var assetMap = mapMapValues(groupBy(assetList, (asset) => asset.id.package),
+ (package, assets) => new AssetSet.from(assets));
+
+ // Make sure that packages that have transformers but no assets are considered
+ // by MockProvider to exist.
+ for (var package in transformers.keys) {
+ assetMap.putIfAbsent(package, () => new AssetSet());
+ }
+
+ _provider = new MockProvider(assetMap);
_barback = new Barback(_provider);
_nextBuildResult = 0;
+
+ transformers.forEach(_barback.updateTransformers);
+
+ // There should be one successful build after adding all the transformers but
+ // before adding any sources.
+ if (!transformers.isEmpty) buildShouldSucceed();
}
/// Updates [assets] in the current [PackageProvider].
@@ -102,6 +130,13 @@
void removeSourcesSync(Iterable assets) =>
_barback.removeSources(_parseAssets(assets));
+/// Sets the transformers for [package] to [transformers].
+void updateTransformers(String package,
+ Iterable<Iterable<Transformer>> transformers) {
+ schedule(() => _barback.updateTransformers(package, transformers),
+ "updating transformers for $package");
+}
+
/// Parse a list of strings or [AssetId]s into a list of [AssetId]s.
List<AssetId> _parseAssets(Iterable assets) {
return assets.map((asset) {
@@ -223,6 +258,39 @@
}, "get asset $name");
}
+/// Schedules an expectation that the graph will output all of the given
+/// assets, and no others.
+///
+/// [assets] is a list of strings that can be parsed to [AssetID]s.
+void expectAllAssets(Iterable<String> assets) {
+ var expected = assets.map((asset) => new AssetId.parse(asset));
+
+ schedule(() {
+ return _barback.getAllAssets().then((actualAssets) {
+ var actualIds = actualAssets.map((asset) => asset.id).toSet();
+
+ for (var id in expected) {
+ expect(actualIds, contains(id));
+ actualIds.remove(id);
+ }
+
+ expect(actualIds, isEmpty);
+ });
+ }, "get all assets, expecting ${expected.join(', ')}");
+}
+
+/// Schedules an expectation that [Barback.getAllAssets] will return a [Future]
+/// that completes to a error that matches [matcher].
+///
+/// If [match] is a [List], then it expects the completed error to be an
+/// [AggregateException] whose errors match each matcher in the list. Otherwise,
+/// [match] should be a single matcher that the error should match.
+void expectAllAssetsShouldFail(Matcher matcher) {
+ schedule(() {
+ expect(_barback.getAllAssets(), throwsA(matcher));
+ }, "get all assets should fail");
+}
+
/// Schedules an expectation that a [getAssetById] call for the given asset
/// won't terminate at this point in the schedule.
void expectAssetDoesNotComplete(String name) {
@@ -236,6 +304,25 @@
}, "asset $id should not complete");
}
+/// Returns a matcher for an [AggregateException] containing errors that match
+/// [matchers].
+Matcher isAggregateException(Iterable<Matcher> errors) {
+ // Match the aggregate error itself.
+ var matchers = [
+ new isInstanceOf<AggregateException>(),
+ transform((error) => error.errors, hasLength(errors.length),
+ 'errors.length == ${errors.length}')
+ ];
+
+ // Make sure its contained errors match the matchers.
+ for (var error in errors) {
+ matchers.add(transform((error) => error.errors, contains(error),
+ error.toString()));
+ }
+
+ return allOf(matchers);
+}
+
/// Returns a matcher for an [AssetNotFoundException] with the given [id].
Matcher isAssetNotFoundException(String name) {
var id = new AssetId.parse(name);
@@ -340,9 +427,9 @@
/// An [AssetProvider] that provides the given set of assets.
class MockProvider implements PackageProvider {
- Iterable<String> get packages => _packages.keys;
+ Iterable<String> get packages => _assets.keys;
- Map<String, _MockPackage> _packages;
+ Map<String, AssetSet> _assets;
/// The set of assets for which [MockLoadException]s should be emitted if
/// they're loaded.
@@ -366,42 +453,19 @@
_pauseCompleter = null;
}
- MockProvider(assets,
- Map<String, Iterable<Iterable<Transformer>>> transformers) {
- var assetList;
- if (assets is Map) {
- assetList = assets.keys.map((asset) {
- var id = new AssetId.parse(asset);
- return new _MockAsset(id, assets[asset]);
- });
- } else if (assets is Iterable) {
- assetList = assets.map((asset) {
- var id = new AssetId.parse(asset);
- var contents = pathos.basenameWithoutExtension(id.path);
- return new _MockAsset(id, contents);
- });
- }
-
- _packages = mapMapValues(groupBy(assetList, (asset) => asset.id.package),
- (package, assets) {
- var packageTransformers = transformers[package];
- if (packageTransformers == null) packageTransformers = [];
- return new _MockPackage(
- new AssetSet.from(assets), packageTransformers.toList());
- });
-
+ MockProvider(this._assets) {
// If there are no assets or transformers, add a dummy package. This better
// simulates the real world, where there'll always be at least the
// entrypoint package.
- if (_packages.isEmpty) {
- _packages = {"app": new _MockPackage(new AssetSet(), [])};
+ if (_assets.isEmpty) {
+ _assets = {"app": new AssetSet()};
}
}
void _modifyAsset(String name, String contents) {
var id = new AssetId.parse(name);
_errors.remove(id);
- _packages[id.package].assets[id].contents = contents;
+ _assets[id.package][id].contents = contents;
}
void _setAssetError(String name) => _errors.add(new AssetId.parse(name));
@@ -411,23 +475,15 @@
throw new UnimplementedError("Doesn't handle 'within' yet.");
}
- return _packages[package].assets.map((asset) => asset.id);
- }
-
- Iterable<Iterable<Transformer>> getTransformers(String package) {
- var mockPackage = _packages[package];
- if (mockPackage == null) {
- throw new ArgumentError("No package named $package.");
- }
- return mockPackage.transformers;
+ return _assets[package].map((asset) => asset.id);
}
Future<Asset> getAsset(AssetId id) {
// Eagerly load the asset so we can test an asset's value changing between
// when a load starts and when it finishes.
- var package = _packages[id.package];
+ var assets = _assets[id.package];
var asset;
- if (package != null) asset = package.assets[id];
+ if (assets != null) asset = assets[id];
var hasError = _errors.contains(id);
@@ -455,16 +511,6 @@
String toString() => "Error loading $id.";
}
-/// Used by [MockProvider] to keep track of which assets and transformers exist
-/// for each package.
-class _MockPackage {
- final AssetSet assets;
- final List<List<Transformer>> transformers;
-
- _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers)
- : transformers = transformers.map((phase) => phase.toList()).toList();
-}
-
/// An implementation of [Asset] that never hits the file system.
class _MockAsset implements Asset {
final AssetId id;
diff --git a/pkg/crypto/lib/crypto.dart b/pkg/crypto/lib/crypto.dart
index f5f737b..ee11c25 100644
--- a/pkg/crypto/lib/crypto.dart
+++ b/pkg/crypto/lib/crypto.dart
@@ -2,6 +2,10 @@
// 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.
+/**
+ * Cryptographic algorithms, with support for hash functions such as
+ * SHA-1, SHA-256, HMAC, and MD5.
+ */
library crypto;
import 'dart:math';
diff --git a/pkg/custom_element/lib/custom-elements.debug.js b/pkg/custom_element/lib/custom-elements.debug.js
new file mode 100644
index 0000000..400730b
--- /dev/null
+++ b/pkg/custom_element/lib/custom-elements.debug.js
@@ -0,0 +1,1458 @@
+// Copyright (c) 2012 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+(function() {
+
+var scope = window.PolymerLoader = {};
+var flags = {};
+
+// convert url arguments to flags
+
+if (!flags.noOpts) {
+ location.search.slice(1).split('&').forEach(function(o) {
+ o = o.split('=');
+ o[0] && (flags[o[0]] = o[1] || true);
+ });
+}
+
+// process global logFlags
+
+parseLogFlags(flags);
+
+function load(scopeName) {
+ // imports
+
+ var scope = window[scopeName];
+ var entryPointName = scope.entryPointName;
+ var processFlags = scope.processFlags;
+
+ // acquire attributes and base path from entry point
+
+ var entryPoint = findScript(entryPointName);
+ var base = entryPoint.basePath;
+
+ // acquire common flags
+ var flags = scope.flags;
+
+ // convert attributes to flags
+ var flags = PolymerLoader.flags;
+ for (var i=0, a; (a=entryPoint.attributes[i]); i++) {
+ if (a.name !== 'src') {
+ flags[a.name] = a.value || true;
+ }
+ }
+
+ // parse log flags into global
+ parseLogFlags(flags);
+
+ // exports
+
+ scope.basePath = base;
+ scope.flags = flags;
+
+ // process flags for dynamic dependencies
+
+ if (processFlags) {
+ processFlags.call(scope, flags);
+ }
+
+ // post-process imports
+
+ var modules = scope.modules || [];
+ var sheets = scope.sheets || [];
+
+ // write script tags for dependencies
+
+ modules.forEach(function(src) {
+ document.write('<script src="' + base + src + '"></script>');
+ });
+
+ // write link tags for styles
+
+ sheets.forEach(function(src) {
+ document.write('<link rel="stylesheet" href="' + base + src + '">');
+ });
+}
+
+// utility method
+
+function findScript(fileName) {
+ var script = document.querySelector('script[src*="' + fileName + '"]');
+ var src = script.attributes.src.value;
+ script.basePath = src.slice(0, src.indexOf(fileName));
+ return script;
+}
+
+function parseLogFlags(flags) {
+ var logFlags = window.logFlags = window.logFlags || {};
+ if (flags.log) {
+ flags.log.split(',').forEach(function(f) {
+ logFlags[f] = true;
+ });
+ }
+}
+
+scope.flags = flags;
+scope.load = load;
+
+})();
+
+window.CustomElements = {flags:{}};
+// SideTable is a weak map where possible. If WeakMap is not available the
+// association is stored as an expando property.
+var SideTable;
+// TODO(arv): WeakMap does not allow for Node etc to be keys in Firefox
+if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') < 0) {
+ SideTable = WeakMap;
+} else {
+ (function() {
+ var defineProperty = Object.defineProperty;
+ var hasOwnProperty = Object.hasOwnProperty;
+ var counter = new Date().getTime() % 1e9;
+
+ SideTable = function() {
+ this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
+ };
+
+ SideTable.prototype = {
+ set: function(key, value) {
+ defineProperty(key, this.name, {value: value, writable: true});
+ },
+ get: function(key) {
+ return hasOwnProperty.call(key, this.name) ? key[this.name] : undefined;
+ },
+ delete: function(key) {
+ this.set(key, undefined);
+ }
+ }
+ })();
+}
+
+(function(global) {
+
+ var registrationsTable = new SideTable();
+
+ // We use setImmediate or postMessage for our future callback.
+ var setImmediate = window.msSetImmediate;
+
+ // Use post message to emulate setImmediate.
+ if (!setImmediate) {
+ var setImmediateQueue = [];
+ var sentinel = String(Math.random());
+ window.addEventListener('message', function(e) {
+ if (e.data === sentinel) {
+ var queue = setImmediateQueue;
+ setImmediateQueue = [];
+ queue.forEach(function(func) {
+ func();
+ });
+ }
+ });
+ setImmediate = function(func) {
+ setImmediateQueue.push(func);
+ window.postMessage(sentinel, '*');
+ };
+ }
+
+ // This is used to ensure that we never schedule 2 callas to setImmediate
+ var isScheduled = false;
+
+ // Keep track of observers that needs to be notified next time.
+ var scheduledObservers = [];
+
+ /**
+ * Schedules |dispatchCallback| to be called in the future.
+ * @param {MutationObserver} observer
+ */
+ function scheduleCallback(observer) {
+ scheduledObservers.push(observer);
+ if (!isScheduled) {
+ isScheduled = true;
+ setImmediate(dispatchCallbacks);
+ }
+ }
+
+ function wrapIfNeeded(node) {
+ return window.ShadowDOMPolyfill &&
+ window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
+ node;
+ }
+
+ function dispatchCallbacks() {
+ // http://dom.spec.whatwg.org/#mutation-observers
+
+ isScheduled = false; // Used to allow a new setImmediate call above.
+
+ var observers = scheduledObservers;
+ scheduledObservers = [];
+ // Sort observers based on their creation UID (incremental).
+ observers.sort(function(o1, o2) {
+ return o1.uid_ - o2.uid_;
+ });
+
+ var anyNonEmpty = false;
+ observers.forEach(function(observer) {
+
+ // 2.1, 2.2
+ var queue = observer.takeRecords();
+ // 2.3. Remove all transient registered observers whose observer is mo.
+ removeTransientObserversFor(observer);
+
+ // 2.4
+ if (queue.length) {
+ observer.callback_(queue, observer);
+ anyNonEmpty = true;
+ }
+ });
+
+ // 3.
+ if (anyNonEmpty)
+ dispatchCallbacks();
+ }
+
+ function removeTransientObserversFor(observer) {
+ observer.nodes_.forEach(function(node) {
+ var registrations = registrationsTable.get(node);
+ if (!registrations)
+ return;
+ registrations.forEach(function(registration) {
+ if (registration.observer === observer)
+ registration.removeTransientObservers();
+ });
+ });
+ }
+
+ /**
+ * This function is used for the "For each registered observer observer (with
+ * observer's options as options) in target's list of registered observers,
+ * run these substeps:" and the "For each ancestor ancestor of target, and for
+ * each registered observer observer (with options options) in ancestor's list
+ * of registered observers, run these substeps:" part of the algorithms. The
+ * |options.subtree| is checked to ensure that the callback is called
+ * correctly.
+ *
+ * @param {Node} target
+ * @param {function(MutationObserverInit):MutationRecord} callback
+ */
+ function forEachAncestorAndObserverEnqueueRecord(target, callback) {
+ for (var node = target; node; node = node.parentNode) {
+ var registrations = registrationsTable.get(node);
+
+ if (registrations) {
+ for (var j = 0; j < registrations.length; j++) {
+ var registration = registrations[j];
+ var options = registration.options;
+
+ // Only target ignores subtree.
+ if (node !== target && !options.subtree)
+ continue;
+
+ var record = callback(options);
+ if (record)
+ registration.enqueue(record);
+ }
+ }
+ }
+ }
+
+ var uidCounter = 0;
+
+ /**
+ * The class that maps to the DOM MutationObserver interface.
+ * @param {Function} callback.
+ * @constructor
+ */
+ function JsMutationObserver(callback) {
+ this.callback_ = callback;
+ this.nodes_ = [];
+ this.records_ = [];
+ this.uid_ = ++uidCounter;
+ }
+
+ JsMutationObserver.prototype = {
+ observe: function(target, options) {
+ target = wrapIfNeeded(target);
+
+ // 1.1
+ if (!options.childList && !options.attributes && !options.characterData ||
+
+ // 1.2
+ options.attributeOldValue && !options.attributes ||
+
+ // 1.3
+ options.attributeFilter && options.attributeFilter.length &&
+ !options.attributes ||
+
+ // 1.4
+ options.characterDataOldValue && !options.characterData) {
+
+ throw new SyntaxError();
+ }
+
+ var registrations = registrationsTable.get(target);
+ if (!registrations)
+ registrationsTable.set(target, registrations = []);
+
+ // 2
+ // If target's list of registered observers already includes a registered
+ // observer associated with the context object, replace that registered
+ // observer's options with options.
+ var registration;
+ for (var i = 0; i < registrations.length; i++) {
+ if (registrations[i].observer === this) {
+ registration = registrations[i];
+ registration.removeListeners();
+ registration.options = options;
+ break;
+ }
+ }
+
+ // 3.
+ // Otherwise, add a new registered observer to target's list of registered
+ // observers with the context object as the observer and options as the
+ // options, and add target to context object's list of nodes on which it
+ // is registered.
+ if (!registration) {
+ registration = new Registration(this, target, options);
+ registrations.push(registration);
+ this.nodes_.push(target);
+ }
+
+ registration.addListeners();
+ },
+
+ disconnect: function() {
+ this.nodes_.forEach(function(node) {
+ var registrations = registrationsTable.get(node);
+ for (var i = 0; i < registrations.length; i++) {
+ var registration = registrations[i];
+ if (registration.observer === this) {
+ registration.removeListeners();
+ registrations.splice(i, 1);
+ // Each node can only have one registered observer associated with
+ // this observer.
+ break;
+ }
+ }
+ }, this);
+ this.records_ = [];
+ },
+
+ takeRecords: function() {
+ var copyOfRecords = this.records_;
+ this.records_ = [];
+ return copyOfRecords;
+ }
+ };
+
+ /**
+ * @param {string} type
+ * @param {Node} target
+ * @constructor
+ */
+ function MutationRecord(type, target) {
+ this.type = type;
+ this.target = target;
+ this.addedNodes = [];
+ this.removedNodes = [];
+ this.previousSibling = null;
+ this.nextSibling = null;
+ this.attributeName = null;
+ this.attributeNamespace = null;
+ this.oldValue = null;
+ }
+
+ function copyMutationRecord(original) {
+ var record = new MutationRecord(original.type, original.target);
+ record.addedNodes = original.addedNodes.slice();
+ record.removedNodes = original.removedNodes.slice();
+ record.previousSibling = original.previousSibling;
+ record.nextSibling = original.nextSibling;
+ record.attributeName = original.attributeName;
+ record.attributeNamespace = original.attributeNamespace;
+ record.oldValue = original.oldValue;
+ return record;
+ };
+
+ // We keep track of the two (possibly one) records used in a single mutation.
+ var currentRecord, recordWithOldValue;
+
+ /**
+ * Creates a record without |oldValue| and caches it as |currentRecord| for
+ * later use.
+ * @param {string} oldValue
+ * @return {MutationRecord}
+ */
+ function getRecord(type, target) {
+ return currentRecord = new MutationRecord(type, target);
+ }
+
+ /**
+ * Gets or creates a record with |oldValue| based in the |currentRecord|
+ * @param {string} oldValue
+ * @return {MutationRecord}
+ */
+ function getRecordWithOldValue(oldValue) {
+ if (recordWithOldValue)
+ return recordWithOldValue;
+ recordWithOldValue = copyMutationRecord(currentRecord);
+ recordWithOldValue.oldValue = oldValue;
+ return recordWithOldValue;
+ }
+
+ function clearRecords() {
+ currentRecord = recordWithOldValue = undefined;
+ }
+
+ /**
+ * @param {MutationRecord} record
+ * @return {boolean} Whether the record represents a record from the current
+ * mutation event.
+ */
+ function recordRepresentsCurrentMutation(record) {
+ return record === recordWithOldValue || record === currentRecord;
+ }
+
+ /**
+ * Selects which record, if any, to replace the last record in the queue.
+ * This returns |null| if no record should be replaced.
+ *
+ * @param {MutationRecord} lastRecord
+ * @param {MutationRecord} newRecord
+ * @param {MutationRecord}
+ */
+ function selectRecord(lastRecord, newRecord) {
+ if (lastRecord === newRecord)
+ return lastRecord;
+
+ // Check if the the record we are adding represents the same record. If
+ // so, we keep the one with the oldValue in it.
+ if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
+ return recordWithOldValue;
+
+ return null;
+ }
+
+ /**
+ * Class used to represent a registered observer.
+ * @param {MutationObserver} observer
+ * @param {Node} target
+ * @param {MutationObserverInit} options
+ * @constructor
+ */
+ function Registration(observer, target, options) {
+ this.observer = observer;
+ this.target = target;
+ this.options = options;
+ this.transientObservedNodes = [];
+ }
+
+ Registration.prototype = {
+ enqueue: function(record) {
+ var records = this.observer.records_;
+ var length = records.length;
+
+ // There are cases where we replace the last record with the new record.
+ // For example if the record represents the same mutation we need to use
+ // the one with the oldValue. If we get same record (this can happen as we
+ // walk up the tree) we ignore the new record.
+ if (records.length > 0) {
+ var lastRecord = records[length - 1];
+ var recordToReplaceLast = selectRecord(lastRecord, record);
+ if (recordToReplaceLast) {
+ records[length - 1] = recordToReplaceLast;
+ return;
+ }
+ } else {
+ scheduleCallback(this.observer);
+ }
+
+ records[length] = record;
+ },
+
+ addListeners: function() {
+ this.addListeners_(this.target);
+ },
+
+ addListeners_: function(node) {
+ var options = this.options;
+ if (options.attributes)
+ node.addEventListener('DOMAttrModified', this, true);
+
+ if (options.characterData)
+ node.addEventListener('DOMCharacterDataModified', this, true);
+
+ if (options.childList)
+ node.addEventListener('DOMNodeInserted', this, true);
+
+ if (options.childList || options.subtree)
+ node.addEventListener('DOMNodeRemoved', this, true);
+ },
+
+ removeListeners: function() {
+ this.removeListeners_(this.target);
+ },
+
+ removeListeners_: function(node) {
+ var options = this.options;
+ if (options.attributes)
+ node.removeEventListener('DOMAttrModified', this, true);
+
+ if (options.characterData)
+ node.removeEventListener('DOMCharacterDataModified', this, true);
+
+ if (options.childList)
+ node.removeEventListener('DOMNodeInserted', this, true);
+
+ if (options.childList || options.subtree)
+ node.removeEventListener('DOMNodeRemoved', this, true);
+ },
+
+ /**
+ * Adds a transient observer on node. The transient observer gets removed
+ * next time we deliver the change records.
+ * @param {Node} node
+ */
+ addTransientObserver: function(node) {
+ // Don't add transient observers on the target itself. We already have all
+ // the required listeners set up on the target.
+ if (node === this.target)
+ return;
+
+ this.addListeners_(node);
+ this.transientObservedNodes.push(node);
+ var registrations = registrationsTable.get(node);
+ if (!registrations)
+ registrationsTable.set(node, registrations = []);
+
+ // We know that registrations does not contain this because we already
+ // checked if node === this.target.
+ registrations.push(this);
+ },
+
+ removeTransientObservers: function() {
+ var transientObservedNodes = this.transientObservedNodes;
+ this.transientObservedNodes = [];
+
+ transientObservedNodes.forEach(function(node) {
+ // Transient observers are never added to the target.
+ this.removeListeners_(node);
+
+ var registrations = registrationsTable.get(node);
+ for (var i = 0; i < registrations.length; i++) {
+ if (registrations[i] === this) {
+ registrations.splice(i, 1);
+ // Each node can only have one registered observer associated with
+ // this observer.
+ break;
+ }
+ }
+ }, this);
+ },
+
+ handleEvent: function(e) {
+ // Stop propagation since we are managing the propagation manually.
+ // This means that other mutation events on the page will not work
+ // correctly but that is by design.
+ e.stopImmediatePropagation();
+
+ switch (e.type) {
+ case 'DOMAttrModified':
+ // http://dom.spec.whatwg.org/#concept-mo-queue-attributes
+
+ var name = e.attrName;
+ var namespace = e.relatedNode.namespaceURI;
+ var target = e.target;
+
+ // 1.
+ var record = new getRecord('attributes', target);
+ record.attributeName = name;
+ record.attributeNamespace = namespace;
+
+ // 2.
+ var oldValue =
+ e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
+
+ forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+ // 3.1, 4.2
+ if (!options.attributes)
+ return;
+
+ // 3.2, 4.3
+ if (options.attributeFilter && options.attributeFilter.length &&
+ options.attributeFilter.indexOf(name) === -1 &&
+ options.attributeFilter.indexOf(namespace) === -1) {
+ return;
+ }
+ // 3.3, 4.4
+ if (options.attributeOldValue)
+ return getRecordWithOldValue(oldValue);
+
+ // 3.4, 4.5
+ return record;
+ });
+
+ break;
+
+ case 'DOMCharacterDataModified':
+ // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
+ var target = e.target;
+
+ // 1.
+ var record = getRecord('characterData', target);
+
+ // 2.
+ var oldValue = e.prevValue;
+
+
+ forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+ // 3.1, 4.2
+ if (!options.characterData)
+ return;
+
+ // 3.2, 4.3
+ if (options.characterDataOldValue)
+ return getRecordWithOldValue(oldValue);
+
+ // 3.3, 4.4
+ return record;
+ });
+
+ break;
+
+ case 'DOMNodeRemoved':
+ this.addTransientObserver(e.target);
+ // Fall through.
+ case 'DOMNodeInserted':
+ // http://dom.spec.whatwg.org/#concept-mo-queue-childlist
+ var target = e.relatedNode;
+ var changedNode = e.target;
+ var addedNodes, removedNodes;
+ if (e.type === 'DOMNodeInserted') {
+ addedNodes = [changedNode];
+ removedNodes = [];
+ } else {
+
+ addedNodes = [];
+ removedNodes = [changedNode];
+ }
+ var previousSibling = changedNode.previousSibling;
+ var nextSibling = changedNode.nextSibling;
+
+ // 1.
+ var record = getRecord('childList', target);
+ record.addedNodes = addedNodes;
+ record.removedNodes = removedNodes;
+ record.previousSibling = previousSibling;
+ record.nextSibling = nextSibling;
+
+ forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+ // 2.1, 3.2
+ if (!options.childList)
+ return;
+
+ // 2.2, 3.3
+ return record;
+ });
+
+ }
+
+ clearRecords();
+ }
+ };
+
+ global.JsMutationObserver = JsMutationObserver;
+
+})(this);
+
+if (!window.MutationObserver) {
+ window.MutationObserver =
+ window.WebKitMutationObserver ||
+ window.JsMutationObserver;
+ if (!MutationObserver) {
+ throw new Error("no mutation observer support");
+ }
+}
+
+(function(scope){
+
+/*
+if (HTMLElement.prototype.webkitShadowRoot) {
+ Object.defineProperty(HTMLElement.prototype, 'shadowRoot', {
+ get: function() {
+ return this.webkitShadowRoot;
+ }
+ };
+}
+*/
+
+// walk the subtree rooted at node, applying 'find(element, data)' function
+// to each element
+// if 'find' returns true for 'element', do not search element's subtree
+function findAll(node, find, data) {
+ var e = node.firstElementChild;
+ if (!e) {
+ e = node.firstChild;
+ while (e && e.nodeType !== Node.ELEMENT_NODE) {
+ e = e.nextSibling;
+ }
+ }
+ while (e) {
+ if (find(e, data) !== true) {
+ findAll(e, find, data);
+ }
+ e = e.nextElementSibling;
+ }
+ return null;
+}
+
+// walk all shadowRoots on a given node.
+function forRoots(node, cb) {
+ var root = node.webkitShadowRoot;
+ while(root) {
+ forSubtree(root, cb);
+ root = root.olderShadowRoot;
+ }
+}
+
+// walk the subtree rooted at node, including descent into shadow-roots,
+// applying 'cb' to each element
+function forSubtree(node, cb) {
+ //logFlags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node);
+ findAll(node, function(e) {
+ if (cb(e)) {
+ return true;
+ }
+ forRoots(e, cb);
+ });
+ forRoots(node, cb);
+ //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEnd();
+}
+
+// manage lifecycle on added node
+function added(node) {
+ if (upgrade(node)) {
+ insertedNode(node);
+ return true;
+ }
+ inserted(node);
+}
+
+// manage lifecycle on added node's subtree only
+function addedSubtree(node) {
+ forSubtree(node, function(e) {
+ if (added(e)) {
+ return true;
+ }
+ });
+}
+
+// manage lifecycle on added node and it's subtree
+function addedNode(node) {
+ return added(node) || addedSubtree(node);
+}
+
+// upgrade custom elements at node, if applicable
+function upgrade(node) {
+ if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) {
+ var type = node.getAttribute('is') || node.localName;
+ var definition = scope.registry[type];
+ if (definition) {
+ logFlags.dom && console.group('upgrade:', node.localName);
+ scope.upgrade(node);
+ logFlags.dom && console.groupEnd();
+ return true;
+ }
+ }
+}
+
+function insertedNode(node) {
+ inserted(node);
+ if (inDocument(node)) {
+ forSubtree(node, function(e) {
+ inserted(e);
+ });
+ }
+}
+
+// TODO(sjmiles): if there are descents into trees that can never have inDocument(*) true, fix this
+
+function inserted(element) {
+ // TODO(sjmiles): it's possible we were inserted and removed in the space
+ // of one microtask, in which case we won't be 'inDocument' here
+ // But there are other cases where we are testing for inserted without
+ // specific knowledge of mutations, and must test 'inDocument' to determine
+ // whether to call inserted
+ // If we can factor these cases into separate code paths we can have
+ // better diagnostics.
+ // TODO(sjmiles): when logging, do work on all custom elements so we can
+ // track behavior even when callbacks not defined
+ //console.log('inserted: ', element.localName);
+ if (element.enteredDocumentCallback || (element.__upgraded__ && logFlags.dom)) {
+ logFlags.dom && console.group('inserted:', element.localName);
+ if (inDocument(element)) {
+ element.__inserted = (element.__inserted || 0) + 1;
+ // if we are in a 'removed' state, bluntly adjust to an 'inserted' state
+ if (element.__inserted < 1) {
+ element.__inserted = 1;
+ }
+ // if we are 'over inserted', squelch the callback
+ if (element.__inserted > 1) {
+ logFlags.dom && console.warn('inserted:', element.localName,
+ 'insert/remove count:', element.__inserted)
+ } else if (element.enteredDocumentCallback) {
+ logFlags.dom && console.log('inserted:', element.localName);
+ element.enteredDocumentCallback();
+ }
+ }
+ logFlags.dom && console.groupEnd();
+ }
+}
+
+function removedNode(node) {
+ removed(node);
+ forSubtree(node, function(e) {
+ removed(e);
+ });
+}
+
+function removed(element) {
+ // TODO(sjmiles): temporary: do work on all custom elements so we can track
+ // behavior even when callbacks not defined
+ if (element.leftDocumentCallback || (element.__upgraded__ && logFlags.dom)) {
+ logFlags.dom && console.log('removed:', element.localName);
+ if (!inDocument(element)) {
+ element.__inserted = (element.__inserted || 0) - 1;
+ // if we are in a 'inserted' state, bluntly adjust to an 'removed' state
+ if (element.__inserted > 0) {
+ element.__inserted = 0;
+ }
+ // if we are 'over removed', squelch the callback
+ if (element.__inserted < 0) {
+ logFlags.dom && console.warn('removed:', element.localName,
+ 'insert/remove count:', element.__inserted)
+ } else if (element.leftDocumentCallback) {
+ element.leftDocumentCallback();
+ }
+ }
+ }
+}
+
+function inDocument(element) {
+ var p = element;
+ while (p) {
+ if (p == element.ownerDocument) {
+ return true;
+ }
+ p = p.parentNode || p.host;
+ }
+}
+
+function watchShadow(node) {
+ if (node.webkitShadowRoot && !node.webkitShadowRoot.__watched) {
+ logFlags.dom && console.log('watching shadow-root for: ', node.localName);
+ // watch all unwatched roots...
+ var root = node.webkitShadowRoot;
+ while (root) {
+ watchRoot(root);
+ root = root.olderShadowRoot;
+ }
+ }
+}
+
+function watchRoot(root) {
+ if (!root.__watched) {
+ observe(root);
+ root.__watched = true;
+ }
+}
+
+function watchAllShadows(node) {
+ watchShadow(node);
+ forSubtree(node, function(e) {
+ watchShadow(node);
+ });
+}
+
+function filter(inNode) {
+ switch (inNode.localName) {
+ case 'style':
+ case 'script':
+ case 'template':
+ case undefined:
+ return true;
+ }
+}
+
+function handler(mutations) {
+ //
+ if (logFlags.dom) {
+ var mx = mutations[0];
+ if (mx && mx.type === 'childList' && mx.addedNodes) {
+ if (mx.addedNodes) {
+ var d = mx.addedNodes[0];
+ while (d && d !== document && !d.host) {
+ d = d.parentNode;
+ }
+ var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || '';
+ u = u.split('/?').shift().split('/').pop();
+ }
+ }
+ console.group('mutations (%d) [%s]', mutations.length, u || '');
+ }
+ //
+ mutations.forEach(function(mx) {
+ //logFlags.dom && console.group('mutation');
+ if (mx.type === 'childList') {
+ forEach(mx.addedNodes, function(n) {
+ //logFlags.dom && console.log(n.localName);
+ if (filter(n)) {
+ return;
+ }
+ // watch shadow-roots on nodes that have had them attached manually
+ // TODO(sjmiles): remove if createShadowRoot is overridden
+ // TODO(sjmiles): removed as an optimization, manual shadow roots
+ // must be watched explicitly
+ //watchAllShadows(n);
+ // nodes added may need lifecycle management
+ addedNode(n);
+ });
+ // removed nodes may need lifecycle management
+ forEach(mx.removedNodes, function(n) {
+ //logFlags.dom && console.log(n.localName);
+ if (filter(n)) {
+ return;
+ }
+ removedNode(n);
+ });
+ }
+ //logFlags.dom && console.groupEnd();
+ });
+ logFlags.dom && console.groupEnd();
+};
+
+var observer = new MutationObserver(handler);
+
+function takeRecords() {
+ // TODO(sjmiles): ask Raf why we have to call handler ourselves
+ handler(observer.takeRecords());
+}
+
+var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+
+function observe(inRoot) {
+ observer.observe(inRoot, {childList: true, subtree: true});
+}
+
+function observeDocument(document) {
+ observe(document);
+}
+
+function upgradeDocument(document) {
+ logFlags.dom && console.group('upgradeDocument: ', (document.URL || document._URL || '').split('/').pop());
+ addedNode(document);
+ logFlags.dom && console.groupEnd();
+}
+
+// exports
+
+scope.watchShadow = watchShadow;
+scope.watchAllShadows = watchAllShadows;
+
+scope.upgradeAll = addedNode;
+scope.upgradeSubtree = addedSubtree;
+
+scope.observeDocument = observeDocument;
+scope.upgradeDocument = upgradeDocument;
+
+scope.takeRecords = takeRecords;
+
+})(window.CustomElements);
+
+/**
+ * Implements `document.register`
+ * @module CustomElements
+*/
+
+/**
+ * Polyfilled extensions to the `document` object.
+ * @class Document
+*/
+
+(function(scope) {
+
+// imports
+
+if (!scope) {
+ scope = window.CustomElements = {flags:{}};
+}
+var flags = scope.flags;
+
+// native document.register?
+
+var hasNative = Boolean(document.webkitRegister || document.register);
+var useNative = !flags.register && hasNative;
+
+if (useNative) {
+
+ // normalize
+ document.register = document.register || document.webkitRegister;
+
+ // stub
+ var nop = function() {};
+
+ // exports
+ scope.registry = {};
+ scope.upgradeElement = nop;
+
+ scope.watchShadow = nop;
+ scope.watchAllShadows = nop;
+ scope.upgrade = nop;
+ scope.upgradeAll = nop;
+ scope.upgradeSubtree = nop;
+ scope.observeDocument = nop;
+ scope.upgradeDocument = nop;
+ scope.takeRecords = nop;
+
+} else {
+
+ /**
+ * Registers a custom tag name with the document.
+ *
+ * When a registered element is created, a `readyCallback` method is called
+ * in the scope of the element. The `readyCallback` method can be specified on
+ * either `inOptions.prototype` or `inOptions.lifecycle` with the latter taking
+ * precedence.
+ *
+ * @method register
+ * @param {String} inName The tag name to register. Must include a dash ('-'),
+ * for example 'x-component'.
+ * @param {Object} inOptions
+ * @param {String} [inOptions.extends]
+ * (_off spec_) Tag name of an element to extend (or blank for a new
+ * element). This parameter is not part of the specification, but instead
+ * is a hint for the polyfill because the extendee is difficult to infer.
+ * Remember that the input prototype must chain to the extended element's
+ * prototype (or HTMLElement.prototype) regardless of the value of
+ * `extends`.
+ * @param {Object} inOptions.prototype The prototype to use for the new
+ * element. The prototype must inherit from HTMLElement.
+ * @param {Object} [inOptions.lifecycle]
+ * Callbacks that fire at important phases in the life of the custom
+ * element.
+ *
+ * @example
+ * FancyButton = document.register("fancy-button", {
+ * extends: 'button',
+ * prototype: Object.create(HTMLButtonElement.prototype, {
+ * readyCallback: {
+ * value: function() {
+ * console.log("a fancy-button was created",
+ * }
+ * }
+ * })
+ * });
+ * @return {Function} Constructor for the newly registered type.
+ */
+ function register(inName, inOptions) {
+ //console.warn('document.register("' + inName + '", ', inOptions, ')');
+ // construct a defintion out of options
+ // TODO(sjmiles): probably should clone inOptions instead of mutating it
+ var definition = inOptions || {};
+ if (!inName) {
+ // TODO(sjmiles): replace with more appropriate error (EricB can probably
+ // offer guidance)
+ throw new Error('Name argument must not be empty');
+ }
+ // record name
+ definition.name = inName;
+ // must have a prototype, default to an extension of HTMLElement
+ // TODO(sjmiles): probably should throw if no prototype, check spec
+ if (!definition.prototype) {
+ // TODO(sjmiles): replace with more appropriate error (EricB can probably
+ // offer guidance)
+ throw new Error('Options missing required prototype property');
+ }
+ // ensure a lifecycle object so we don't have to null test it
+ definition.lifecycle = definition.lifecycle || {};
+ // build a list of ancestral custom elements (for native base detection)
+ // TODO(sjmiles): we used to need to store this, but current code only
+ // uses it in 'resolveTagName': it should probably be inlined
+ definition.ancestry = ancestry(definition.extends);
+ // extensions of native specializations of HTMLElement require localName
+ // to remain native, and use secondary 'is' specifier for extension type
+ resolveTagName(definition);
+ // some platforms require modifications to the user-supplied prototype
+ // chain
+ resolvePrototypeChain(definition);
+ // overrides to implement attributeChanged callback
+ overrideAttributeApi(definition.prototype);
+ // 7.1.5: Register the DEFINITION with DOCUMENT
+ registerDefinition(inName, definition);
+ // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
+ // 7.1.8. Return the output of the previous step.
+ definition.ctor = generateConstructor(definition);
+ definition.ctor.prototype = definition.prototype;
+ // force our .constructor to be our actual constructor
+ definition.prototype.constructor = definition.ctor;
+ // if initial parsing is complete
+ if (scope.ready) {
+ // upgrade any pre-existing nodes of this type
+ scope.upgradeAll(document);
+ }
+ return definition.ctor;
+ }
+
+ function ancestry(inExtends) {
+ var extendee = registry[inExtends];
+ if (extendee) {
+ return ancestry(extendee.extends).concat([extendee]);
+ }
+ return [];
+ }
+
+ function resolveTagName(inDefinition) {
+ // if we are explicitly extending something, that thing is our
+ // baseTag, unless it represents a custom component
+ var baseTag = inDefinition.extends;
+ // if our ancestry includes custom components, we only have a
+ // baseTag if one of them does
+ for (var i=0, a; (a=inDefinition.ancestry[i]); i++) {
+ baseTag = a.is && a.tag;
+ }
+ // our tag is our baseTag, if it exists, and otherwise just our name
+ inDefinition.tag = baseTag || inDefinition.name;
+ if (baseTag) {
+ // if there is a base tag, use secondary 'is' specifier
+ inDefinition.is = inDefinition.name;
+ }
+ }
+
+ function resolvePrototypeChain(inDefinition) {
+ // if we don't support __proto__ we need to locate the native level
+ // prototype for precise mixing in
+ if (!Object.__proto__) {
+ // default prototype
+ var native = HTMLElement.prototype;
+ // work out prototype when using type-extension
+ if (inDefinition.is) {
+ var inst = document.createElement(inDefinition.tag);
+ native = Object.getPrototypeOf(inst);
+ }
+ // ensure __proto__ reference is installed at each point on the prototype
+ // chain.
+ // NOTE: On platforms without __proto__, a mixin strategy is used instead
+ // of prototype swizzling. In this case, this generated __proto__ provides
+ // limited support for prototype traversal.
+ var proto = inDefinition.prototype, ancestor;
+ while (proto && (proto !== native)) {
+ var ancestor = Object.getPrototypeOf(proto);
+ proto.__proto__ = ancestor;
+ proto = ancestor;
+ }
+ }
+ // cache this in case of mixin
+ inDefinition.native = native;
+ }
+
+ // SECTION 4
+
+ function instantiate(inDefinition) {
+ // 4.a.1. Create a new object that implements PROTOTYPE
+ // 4.a.2. Let ELEMENT by this new object
+ //
+ // the custom element instantiation algorithm must also ensure that the
+ // output is a valid DOM element with the proper wrapper in place.
+ //
+ return upgrade(domCreateElement(inDefinition.tag), inDefinition);
+ }
+
+ function upgrade(inElement, inDefinition) {
+ // some definitions specify an 'is' attribute
+ if (inDefinition.is) {
+ inElement.setAttribute('is', inDefinition.is);
+ }
+ // make 'element' implement inDefinition.prototype
+ implement(inElement, inDefinition);
+ // flag as upgraded
+ inElement.__upgraded__ = true;
+ // there should never be a shadow root on inElement at this point
+ // we require child nodes be upgraded before `created`
+ scope.upgradeSubtree(inElement);
+ // lifecycle management
+ created(inElement);
+ // OUTPUT
+ return inElement;
+ }
+
+ function implement(inElement, inDefinition) {
+ // prototype swizzling is best
+ if (Object.__proto__) {
+ inElement.__proto__ = inDefinition.prototype;
+ } else {
+ // where above we can re-acquire inPrototype via
+ // getPrototypeOf(Element), we cannot do so when
+ // we use mixin, so we install a magic reference
+ customMixin(inElement, inDefinition.prototype, inDefinition.native);
+ inElement.__proto__ = inDefinition.prototype;
+ }
+ }
+
+ function customMixin(inTarget, inSrc, inNative) {
+ // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
+ // any property. This set should be precalculated. We also need to
+ // consider this for supporting 'super'.
+ var used = {};
+ // start with inSrc
+ var p = inSrc;
+ // sometimes the default is HTMLUnknownElement.prototype instead of
+ // HTMLElement.prototype, so we add a test
+ // the idea is to avoid mixing in native prototypes, so adding
+ // the second test is WLOG
+ while (p !== inNative && p !== HTMLUnknownElement.prototype) {
+ var keys = Object.getOwnPropertyNames(p);
+ for (var i=0, k; k=keys[i]; i++) {
+ if (!used[k]) {
+ Object.defineProperty(inTarget, k,
+ Object.getOwnPropertyDescriptor(p, k));
+ used[k] = 1;
+ }
+ }
+ p = Object.getPrototypeOf(p);
+ }
+ }
+
+ function created(inElement) {
+ // invoke createdCallback
+ if (inElement.createdCallback) {
+ inElement.createdCallback();
+ }
+ }
+
+ // attribute watching
+
+ function overrideAttributeApi(prototype) {
+ // overrides to implement callbacks
+ // TODO(sjmiles): should support access via .attributes NamedNodeMap
+ // TODO(sjmiles): preserves user defined overrides, if any
+ var setAttribute = prototype.setAttribute;
+ prototype.setAttribute = function(name, value) {
+ changeAttribute.call(this, name, value, setAttribute);
+ }
+ var removeAttribute = prototype.removeAttribute;
+ prototype.removeAttribute = function(name, value) {
+ changeAttribute.call(this, name, value, removeAttribute);
+ }
+ }
+
+ function changeAttribute(name, value, operation) {
+ var oldValue = this.getAttribute(name);
+ operation.apply(this, arguments);
+ if (this.attributeChangedCallback
+ && (this.getAttribute(name) !== oldValue)) {
+ this.attributeChangedCallback(name, oldValue);
+ }
+ }
+
+ // element registry (maps tag names to definitions)
+
+ var registry = {};
+
+ function registerDefinition(inName, inDefinition) {
+ if (registry[inName]) {
+ throw new Error('Cannot register a tag more than once');
+ }
+ registry[inName] = inDefinition;
+ }
+
+ function generateConstructor(inDefinition) {
+ return function() {
+ return instantiate(inDefinition);
+ };
+ }
+
+ function createElement(tag, typeExtension) {
+ // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could
+ // error check it, or perhaps there should only ever be one argument
+ var definition = registry[typeExtension || tag];
+ if (definition) {
+ return new definition.ctor();
+ }
+ return domCreateElement(tag);
+ }
+
+ function upgradeElement(inElement) {
+ if (!inElement.__upgraded__ && (inElement.nodeType === Node.ELEMENT_NODE)) {
+ var type = inElement.getAttribute('is') || inElement.localName;
+ var definition = registry[type];
+ return definition && upgrade(inElement, definition);
+ }
+ }
+
+ function cloneNode(deep) {
+ // call original clone
+ var n = domCloneNode.call(this, deep);
+ // upgrade the element and subtree
+ scope.upgradeAll(n);
+ // return the clone
+ return n;
+ }
+ // capture native createElement before we override it
+
+ var domCreateElement = document.createElement.bind(document);
+
+ // capture native cloneNode before we override it
+
+ var domCloneNode = Node.prototype.cloneNode;
+
+ // exports
+
+ document.register = register;
+ document.createElement = createElement; // override
+ Node.prototype.cloneNode = cloneNode; // override
+
+ scope.registry = registry;
+
+ /**
+ * Upgrade an element to a custom element. Upgrading an element
+ * causes the custom prototype to be applied, an `is` attribute
+ * to be attached (as needed), and invocation of the `readyCallback`.
+ * `upgrade` does nothing if the element is already upgraded, or
+ * if it matches no registered custom tag name.
+ *
+ * @method ugprade
+ * @param {Element} inElement The element to upgrade.
+ * @return {Element} The upgraded element.
+ */
+ scope.upgrade = upgradeElement;
+}
+
+scope.hasNative = hasNative;
+scope.useNative = useNative;
+
+})(window.CustomElements);
+
+(function() {
+
+// import
+
+var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none';
+
+// highlander object for parsing a document tree
+
+var parser = {
+ selectors: [
+ 'link[rel=' + IMPORT_LINK_TYPE + ']'
+ ],
+ map: {
+ link: 'parseLink'
+ },
+ parse: function(inDocument) {
+ if (!inDocument.__parsed) {
+ // only parse once
+ inDocument.__parsed = true;
+ // all parsable elements in inDocument (depth-first pre-order traversal)
+ var elts = inDocument.querySelectorAll(parser.selectors);
+ // for each parsable node type, call the mapped parsing method
+ forEach(elts, function(e) {
+ parser[parser.map[e.localName]](e);
+ });
+ // upgrade all upgradeable static elements, anything dynamically
+ // created should be caught by observer
+ CustomElements.upgradeDocument(inDocument);
+ // observe document for dom changes
+ CustomElements.observeDocument(inDocument);
+ }
+ },
+ parseLink: function(linkElt) {
+ // imports
+ if (isDocumentLink(linkElt)) {
+ this.parseImport(linkElt);
+ }
+ },
+ parseImport: function(linkElt) {
+ if (linkElt.content) {
+ parser.parse(linkElt.content);
+ }
+ }
+};
+
+function isDocumentLink(inElt) {
+ return (inElt.localName === 'link'
+ && inElt.getAttribute('rel') === IMPORT_LINK_TYPE);
+}
+
+var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+
+// exports
+
+CustomElements.parser = parser;
+
+})();
+(function(){
+
+// bootstrap parsing
+
+function bootstrap() {
+ // go async so call stack can unwind
+ setTimeout(function() {
+ // parse document
+ CustomElements.parser.parse(document);
+ // one more pass before register is 'live'
+ CustomElements.upgradeDocument(document);
+ // set internal 'ready' flag, now document.register will trigger
+ // synchronous upgrades
+ CustomElements.ready = true;
+ // capture blunt profiling data
+ CustomElements.readyTime = Date.now();
+ if (window.HTMLImports) {
+ CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime;
+ }
+ // notify the system that we are bootstrapped
+ document.body.dispatchEvent(
+ new CustomEvent('WebComponentsReady', {bubbles: true})
+ );
+ }, 0);
+}
+
+// CustomEvent shim for IE
+if (typeof window.CustomEvent !== 'function') {
+ window.CustomEvent = function(inType) {
+ var e = document.createEvent('HTMLEvents');
+ e.initEvent(inType, true, true);
+ return e;
+ };
+}
+
+if (document.readyState === 'complete') {
+ bootstrap();
+} else {
+ var loadEvent = window.HTMLImports ? 'HTMLImportsLoaded' : 'DOMContentLoaded';
+ window.addEventListener(loadEvent, bootstrap);
+}
+
+})();
diff --git a/pkg/custom_element/lib/custom-elements.min.js b/pkg/custom_element/lib/custom-elements.min.js
new file mode 100644
index 0000000..f0c3e81
--- /dev/null
+++ b/pkg/custom_element/lib/custom-elements.min.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+window.CustomElements={flags:{}};var SideTable;if("undefined"!=typeof WeakMap&&navigator.userAgent.indexOf("Firefox/")<0?SideTable=WeakMap:function(){var a=Object.defineProperty,b=Object.hasOwnProperty,c=(new Date).getTime()%1e9;SideTable=function(){this.name="__st"+(1e9*Math.random()>>>0)+(c++ +"__")},SideTable.prototype={set:function(b,c){a(b,this.name,{value:c,writable:!0})},get:function(a){return b.call(a,this.name)?a[this.name]:void 0},"delete":function(a){this.set(a,void 0)}}}(),function(a){function b(a){u.push(a),t||(t=!0,q(d))}function c(a){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(a)||a}function d(){t=!1;var a=u;u=[],a.sort(function(a,b){return a.uid_-b.uid_});var b=!1;a.forEach(function(a){var c=a.takeRecords();e(a),c.length&&(a.callback_(c,a),b=!0)}),b&&d()}function e(a){a.nodes_.forEach(function(b){var c=p.get(b);c&&c.forEach(function(b){b.observer===a&&b.removeTransientObservers()})})}function f(a,b){for(var c=a;c;c=c.parentNode){var d=p.get(c);if(d)for(var e=0;e<d.length;e++){var f=d[e],g=f.options;if(c===a||g.subtree){var h=b(g);h&&f.enqueue(h)}}}}function g(a){this.callback_=a,this.nodes_=[],this.records_=[],this.uid_=++v}function h(a,b){this.type=a,this.target=b,this.addedNodes=[],this.removedNodes=[],this.previousSibling=null,this.nextSibling=null,this.attributeName=null,this.attributeNamespace=null,this.oldValue=null}function i(a){var b=new h(a.type,a.target);return b.addedNodes=a.addedNodes.slice(),b.removedNodes=a.removedNodes.slice(),b.previousSibling=a.previousSibling,b.nextSibling=a.nextSibling,b.attributeName=a.attributeName,b.attributeNamespace=a.attributeNamespace,b.oldValue=a.oldValue,b}function j(a,b){return w=new h(a,b)}function k(a){return x?x:(x=i(w),x.oldValue=a,x)}function l(){w=x=void 0}function m(a){return a===x||a===w}function n(a,b){return a===b?a:x&&m(a)?x:null}function o(a,b,c){this.observer=a,this.target=b,this.options=c,this.transientObservedNodes=[]}var p=new SideTable,q=window.msSetImmediate;if(!q){var r=[],s=String(Math.random());window.addEventListener("message",function(a){if(a.data===s){var b=r;r=[],b.forEach(function(a){a()})}}),q=function(a){r.push(a),window.postMessage(s,"*")}}var t=!1,u=[],v=0;g.prototype={observe:function(a,b){if(a=c(a),!b.childList&&!b.attributes&&!b.characterData||b.attributeOldValue&&!b.attributes||b.attributeFilter&&b.attributeFilter.length&&!b.attributes||b.characterDataOldValue&&!b.characterData)throw new SyntaxError;var d=p.get(a);d||p.set(a,d=[]);for(var e,f=0;f<d.length;f++)if(d[f].observer===this){e=d[f],e.removeListeners(),e.options=b;break}e||(e=new o(this,a,b),d.push(e),this.nodes_.push(a)),e.addListeners()},disconnect:function(){this.nodes_.forEach(function(a){for(var b=p.get(a),c=0;c<b.length;c++){var d=b[c];if(d.observer===this){d.removeListeners(),b.splice(c,1);break}}},this),this.records_=[]},takeRecords:function(){var a=this.records_;return this.records_=[],a}};var w,x;o.prototype={enqueue:function(a){var c=this.observer.records_,d=c.length;if(c.length>0){var e=c[d-1],f=n(e,a);if(f)return c[d-1]=f,void 0}else b(this.observer);c[d]=a},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(a){var b=this.options;b.attributes&&a.addEventListener("DOMAttrModified",this,!0),b.characterData&&a.addEventListener("DOMCharacterDataModified",this,!0),b.childList&&a.addEventListener("DOMNodeInserted",this,!0),(b.childList||b.subtree)&&a.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(a){var b=this.options;b.attributes&&a.removeEventListener("DOMAttrModified",this,!0),b.characterData&&a.removeEventListener("DOMCharacterDataModified",this,!0),b.childList&&a.removeEventListener("DOMNodeInserted",this,!0),(b.childList||b.subtree)&&a.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(a){if(a!==this.target){this.addListeners_(a),this.transientObservedNodes.push(a);var b=p.get(a);b||p.set(a,b=[]),b.push(this)}},removeTransientObservers:function(){var a=this.transientObservedNodes;this.transientObservedNodes=[],a.forEach(function(a){this.removeListeners_(a);for(var b=p.get(a),c=0;c<b.length;c++)if(b[c]===this){b.splice(c,1);break}},this)},handleEvent:function(a){switch(a.stopImmediatePropagation(),a.type){case"DOMAttrModified":var b=a.attrName,c=a.relatedNode.namespaceURI,d=a.target,e=new j("attributes",d);e.attributeName=b,e.attributeNamespace=c;var g=a.attrChange===MutationEvent.ADDITION?null:a.prevValue;f(d,function(a){return!a.attributes||a.attributeFilter&&a.attributeFilter.length&&-1===a.attributeFilter.indexOf(b)&&-1===a.attributeFilter.indexOf(c)?void 0:a.attributeOldValue?k(g):e});break;case"DOMCharacterDataModified":var d=a.target,e=j("characterData",d),g=a.prevValue;f(d,function(a){return a.characterData?a.characterDataOldValue?k(g):e:void 0});break;case"DOMNodeRemoved":this.addTransientObserver(a.target);case"DOMNodeInserted":var h,i,d=a.relatedNode,m=a.target;"DOMNodeInserted"===a.type?(h=[m],i=[]):(h=[],i=[m]);var n=m.previousSibling,o=m.nextSibling,e=j("childList",d);e.addedNodes=h,e.removedNodes=i,e.previousSibling=n,e.nextSibling=o,f(d,function(a){return a.childList?e:void 0})}l()}},a.JsMutationObserver=g}(this),!window.MutationObserver&&(window.MutationObserver=window.WebKitMutationObserver||window.JsMutationObserver,!MutationObserver))throw new Error("no mutation observer support");!function(a){function b(a,c,d){var e=a.firstElementChild;if(!e)for(e=a.firstChild;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.nextSibling;for(;e;)c(e,d)!==!0&&b(e,c,d),e=e.nextElementSibling;return null}function c(a,b){for(var c=a.webkitShadowRoot;c;)d(c,b),c=c.olderShadowRoot}function d(a,d){b(a,function(a){return d(a)?!0:(c(a,d),void 0)}),c(a,d)}function e(a){return h(a)?(i(a),!0):(j(a),void 0)}function f(a){d(a,function(a){return e(a)?!0:void 0})}function g(a){return e(a)||f(a)}function h(b){if(!b.__upgraded__&&b.nodeType===Node.ELEMENT_NODE){var c=b.getAttribute("is")||b.localName,d=a.registry[c];if(d)return logFlags.dom&&console.group("upgrade:",b.localName),a.upgrade(b),logFlags.dom&&console.groupEnd(),!0}}function i(a){j(a),m(a)&&d(a,function(a){j(a)})}function j(a){(a.enteredDocumentCallback||a.__upgraded__&&logFlags.dom)&&(logFlags.dom&&console.group("inserted:",a.localName),m(a)&&(a.__inserted=(a.__inserted||0)+1,a.__inserted<1&&(a.__inserted=1),a.__inserted>1?logFlags.dom&&console.warn("inserted:",a.localName,"insert/remove count:",a.__inserted):a.enteredDocumentCallback&&(logFlags.dom&&console.log("inserted:",a.localName),a.enteredDocumentCallback())),logFlags.dom&&console.groupEnd())}function k(a){l(a),d(a,function(a){l(a)})}function l(a){(a.leftDocumentCallback||a.__upgraded__&&logFlags.dom)&&(logFlags.dom&&console.log("removed:",a.localName),m(a)||(a.__inserted=(a.__inserted||0)-1,a.__inserted>0&&(a.__inserted=0),a.__inserted<0?logFlags.dom&&console.warn("removed:",a.localName,"insert/remove count:",a.__inserted):a.leftDocumentCallback&&a.leftDocumentCallback()))}function m(a){for(var b=a;b;){if(b==a.ownerDocument)return!0;b=b.parentNode||b.host}}function n(a){if(a.webkitShadowRoot&&!a.webkitShadowRoot.__watched){logFlags.dom&&console.log("watching shadow-root for: ",a.localName);for(var b=a.webkitShadowRoot;b;)o(b),b=b.olderShadowRoot}}function o(a){a.__watched||(t(a),a.__watched=!0)}function p(a){n(a),d(a,function(){n(a)})}function q(a){switch(a.localName){case"style":case"script":case"template":case void 0:return!0}}function r(a){if(logFlags.dom){var b=a[0];if(b&&"childList"===b.type&&b.addedNodes&&b.addedNodes){for(var c=b.addedNodes[0];c&&c!==document&&!c.host;)c=c.parentNode;var d=c&&(c.URL||c._URL||c.host&&c.host.localName)||"";d=d.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",a.length,d||"")}a.forEach(function(a){"childList"===a.type&&(x(a.addedNodes,function(a){q(a)||g(a)}),x(a.removedNodes,function(a){q(a)||k(a)}))}),logFlags.dom&&console.groupEnd()}function s(){r(w.takeRecords())}function t(a){w.observe(a,{childList:!0,subtree:!0})}function u(a){t(a)}function v(a){logFlags.dom&&console.group("upgradeDocument: ",(a.URL||a._URL||"").split("/").pop()),g(a),logFlags.dom&&console.groupEnd()}var w=new MutationObserver(r),x=Array.prototype.forEach.call.bind(Array.prototype.forEach);a.watchShadow=n,a.watchAllShadows=p,a.upgradeAll=g,a.upgradeSubtree=f,a.observeDocument=u,a.upgradeDocument=v,a.takeRecords=s}(window.CustomElements),function(a){function b(b,f){var g=f||{};if(!b)throw new Error("Name argument must not be empty");if(g.name=b,!g.prototype)throw new Error("Options missing required prototype property");return g.lifecycle=g.lifecycle||{},g.ancestry=c(g.extends),d(g),e(g),k(g.prototype),m(b,g),g.ctor=n(g),g.ctor.prototype=g.prototype,g.prototype.constructor=g.ctor,a.ready&&a.upgradeAll(document),g.ctor}function c(a){var b=v[a];return b?c(b.extends).concat([b]):[]}function d(a){for(var b,c=a.extends,d=0;b=a.ancestry[d];d++)c=b.is&&b.tag;a.tag=c||a.name,c&&(a.is=a.name)}function e(a){if(!Object.__proto__){var b=HTMLElement.prototype;if(a.is){var c=document.createElement(a.tag);b=Object.getPrototypeOf(c)}for(var d,e=a.prototype;e&&e!==b;){var d=Object.getPrototypeOf(e);e.__proto__=d,e=d}}a.native=b}function f(a){return g(w(a.tag),a)}function g(b,c){return c.is&&b.setAttribute("is",c.is),h(b,c),b.__upgraded__=!0,a.upgradeSubtree(b),j(b),b}function h(a,b){Object.__proto__?a.__proto__=b.prototype:(i(a,b.prototype,b.native),a.__proto__=b.prototype)}function i(a,b,c){for(var d={},e=b;e!==c&&e!==HTMLUnknownElement.prototype;){for(var f,g=Object.getOwnPropertyNames(e),h=0;f=g[h];h++)d[f]||(Object.defineProperty(a,f,Object.getOwnPropertyDescriptor(e,f)),d[f]=1);e=Object.getPrototypeOf(e)}}function j(a){a.createdCallback&&a.createdCallback()}function k(a){var b=a.setAttribute;a.setAttribute=function(a,c){l.call(this,a,c,b)};var c=a.removeAttribute;a.removeAttribute=function(a,b){l.call(this,a,b,c)}}function l(a,b,c){var d=this.getAttribute(a);c.apply(this,arguments),this.attributeChangedCallback&&this.getAttribute(a)!==d&&this.attributeChangedCallback(a,d)}function m(a,b){if(v[a])throw new Error("Cannot register a tag more than once");v[a]=b}function n(a){return function(){return f(a)}}function o(a,b){var c=v[b||a];return c?new c.ctor:w(a)}function p(a){if(!a.__upgraded__&&a.nodeType===Node.ELEMENT_NODE){var b=a.getAttribute("is")||a.localName,c=v[b];return c&&g(a,c)}}function q(b){var c=x.call(this,b);return a.upgradeAll(c),c}a||(a=window.CustomElements={flags:{}});var r=a.flags,s=Boolean(document.webkitRegister||document.register),t=!r.register&&s;if(t){document.register=document.register||document.webkitRegister;var u=function(){};a.registry={},a.upgradeElement=u,a.watchShadow=u,a.watchAllShadows=u,a.upgrade=u,a.upgradeAll=u,a.upgradeSubtree=u,a.observeDocument=u,a.upgradeDocument=u,a.takeRecords=u}else{var v={},w=document.createElement.bind(document),x=Node.prototype.cloneNode;document.register=b,document.createElement=o,Node.prototype.cloneNode=q,a.registry=v,a.upgrade=p}a.hasNative=s,a.useNative=t}(window.CustomElements),function(){function a(a){return"link"===a.localName&&a.getAttribute("rel")===b}var b=window.HTMLImports?HTMLImports.IMPORT_LINK_TYPE:"none",c={selectors:["link[rel="+b+"]"],map:{link:"parseLink"},parse:function(a){if(!a.__parsed){a.__parsed=!0;var b=a.querySelectorAll(c.selectors);d(b,function(a){c[c.map[a.localName]](a)}),CustomElements.upgradeDocument(a),CustomElements.observeDocument(a)}},parseLink:function(b){a(b)&&this.parseImport(b)},parseImport:function(a){a.content&&c.parse(a.content)}},d=Array.prototype.forEach.call.bind(Array.prototype.forEach);CustomElements.parser=c}(),function(){function a(){setTimeout(function(){CustomElements.parser.parse(document),CustomElements.upgradeDocument(document),CustomElements.ready=!0,CustomElements.readyTime=Date.now(),window.HTMLImports&&(CustomElements.elapsed=CustomElements.readyTime-HTMLImports.readyTime),document.body.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))},0)}if("function"!=typeof window.CustomEvent&&(window.CustomEvent=function(a){var b=document.createEvent("HTMLEvents");return b.initEvent(a,!0,!0),b}),"complete"===document.readyState)a();else{var b=window.HTMLImports?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(b,a)}}();
diff --git a/pkg/custom_element/lib/custom_element.dart b/pkg/custom_element/lib/custom_element.dart
index 16978ae..4121dbf 100644
--- a/pkg/custom_element/lib/custom_element.dart
+++ b/pkg/custom_element/lib/custom_element.dart
@@ -313,6 +313,8 @@
host.innerHtml = v;
}
+ InputMethodContext get inputMethodContext => host.inputMethodContext;
+
bool get isContentEditable => host.isContentEditable;
String get lang => host.lang;
@@ -338,7 +340,8 @@
void click() { host.click(); }
- InputMethodContext getInputContext() => host.getInputContext();
+ List<Node> getDestinationInsertionPoints() =>
+ host.getDestinationInsertionPoints();
Element insertAdjacentElement(String where, Element element) =>
host.insertAdjacentElement(where, element);
diff --git a/pkg/docgen/README.md b/pkg/docgen/README.md
index 24ba58a..66d0a46 100644
--- a/pkg/docgen/README.md
+++ b/pkg/docgen/README.md
@@ -32,19 +32,21 @@
- `-h`, `--help` Prints help and usage information.
- `-v`, `--verbose` Output more logging information.
-- `-j`, `--[no-]json` Outputs to JSON. Files are outputted to YAML by default.
+- `-j`, `--[no-]json` Outputs to JSON. Files are outputted to YAML by default.
+If `--append` is used, it takes the file-format of the previous run stated in
+library_list.json ignoring the flag.
- `--include-private` Flag to include private declarations.
- `--include-sdk` Flag to parse SDK Library files imported.
- `--parse-sdk` Parses the SDK libraries only. (Ignores the path passed in.)
- `--package-root` Sets the package root of the library being analyzed.
-- `--append` Appends to the docs folder, library_list.txt, and index.txt.
+- `--append` Appends to the docs folder, library_list.json, and index.txt.
- `--introduction` Adds the provided markdown text file as the introduction
for the outputted documentation.
###### Output Directory
Documented libraries will be located at bin/docs in either YAML or JSON format
-depending on options specified. There will also be a library_list.txt,
+depending on options specified. There will also be a library_list.json,
containing a list of all the libraries inside the docs folder.
To get more information on how to use the outputted documentation with
diff --git a/pkg/docgen/bin/docgen.dart b/pkg/docgen/bin/docgen.dart
index ca4f780..293f3cf 100644
--- a/pkg/docgen/bin/docgen.dart
+++ b/pkg/docgen/bin/docgen.dart
@@ -49,7 +49,9 @@
if (verbose) Logger.root.level = Level.FINEST;
});
parser.addFlag('json', abbr: 'j',
- help: 'Outputs to JSON. Files are outputted to YAML by default.',
+ help: 'Outputs to JSON. Files are outputted to YAML by default. '
+ 'If --append is used, it takes the file-format of the previous '
+ 'run stated in library_list.json ignoring the flag.',
negatable: true);
parser.addFlag('include-private',
help: 'Flag to include private declarations.', negatable: false);
@@ -61,7 +63,7 @@
parser.addOption('package-root',
help: 'Sets the package root of the library being analyzed.');
parser.addFlag('append',
- help: 'Append to the docs folder, library_list.txt and index.txt',
+ help: 'Append to the docs folder, library_list.json and index.txt',
defaultsTo: false, negatable: false);
parser.addOption('introduction',
help: 'Adds the provided markdown text file as the introduction'
diff --git a/pkg/docgen/lib/dart2yaml.dart b/pkg/docgen/lib/dart2yaml.dart
index 57ca7fe..343b61f 100644
--- a/pkg/docgen/lib/dart2yaml.dart
+++ b/pkg/docgen/lib/dart2yaml.dart
@@ -7,6 +7,8 @@
*/
library dart2yaml;
+import 'dart:collection';
+
/**
* Gets a String representing the input Map in YAML format.
*/
@@ -26,10 +28,11 @@
*/
void _addLevel(StringBuffer yaml, Map documentData, int level,
{bool isList: false}) {
- // Since the ordering of the keys could be non-deterministic, the keys
- // are sorted to ensure consistency in the output.
+ // The order of the keys could be nondeterministic, but it is insufficient
+ // to just sort the keys no matter what, as their order could be significant
+ // (i.e. parameters to a method). The order of the keys should be enforced
+ // by the caller of this function.
var keys = documentData.keys.toList();
- keys.sort();
keys.forEach((key) {
_calcSpaces(level, yaml);
// Only the first entry of the map should be preceeded with a '-' since
diff --git a/pkg/docgen/lib/docgen.dart b/pkg/docgen/lib/docgen.dart
index a51ab86..6faca3f5 100644
--- a/pkg/docgen/lib/docgen.dart
+++ b/pkg/docgen/lib/docgen.dart
@@ -244,26 +244,44 @@
if (parseSdk) entityMap['dart.core.Object'].subclasses.clear();
var filteredEntities = entityMap.values.where(_isVisible);
+
+ // Outputs a JSON file with all libraries and their preview comments.
+ // This will help the viewer know what libraries are available to read in.
+ var libraryMap;
+ if (append) {
+ var docsDir = listDir('docs');
+ if (!docsDir.contains('docs/library_list.json')) {
+ throw new StateError('No library_list.json');
+ }
+ libraryMap = parse(new File('docs/library_list.json').readAsStringSync());
+ libraryMap['libraries'].addAll(filteredEntities
+ .where((e) => e is Library)
+ .map((e) => e.previewMap));
+ if (introduction.isNotEmpty) {
+ var intro = libraryMap['introduction'];
+ if (intro.isNotEmpty) intro += '<br/><br/>';
+ intro += markdown.markdownToHtml(
+ new File(introduction).readAsStringSync(),
+ linkResolver: linkResolver, inlineSyntaxes: markdownSyntaxes);
+ libraryMap['introduction'] = intro;
+ }
+ outputToYaml = libraryMap['filetype'] == 'yaml';
+ } else {
+ libraryMap = {
+ 'libraries' : filteredEntities.where((e) =>
+ e is Library).map((e) => e.previewMap).toList(),
+ 'introduction' : introduction == '' ?
+ '' : markdown.markdownToHtml(new File(introduction)
+ .readAsStringSync(), linkResolver: linkResolver,
+ inlineSyntaxes: markdownSyntaxes),
+ 'filetype' : outputToYaml ? 'yaml' : 'json'
+ };
+ }
+ _writeToFile(stringify(libraryMap), 'library_list.json');
// Output libraries and classes to file after all information is generated.
filteredEntities.where((e) => e is Class || e is Library).forEach((output) {
_writeIndexableToFile(output, outputToYaml);
});
- // Outputs a YAML or JSON file with all libraries and their preview comments
- // after creating all libraries. This will help the viewer know what
- // libraries are available to read in.
- var libraryMap = {
- 'libraries' : filteredEntities.where((e) =>
- e is Library).map((e) => e.previewMap).toList(),
- 'introduction' : introduction == '' ?
- '' : markdown.markdownToHtml(new File(introduction).readAsStringSync(),
- linkResolver: linkResolver, inlineSyntaxes: markdownSyntaxes)
- };
- if (outputToYaml) {
- _writeToFile(getYamlString(libraryMap), 'library_list.yaml',
- append: append);
- } else {
- _writeToFile(stringify(libraryMap), 'library_list.json', append: append);
- }
// Outputs all the qualified names documented with their type.
// This will help generate search results.
_writeToFile(filteredEntities.map((e) =>
diff --git a/pkg/intl/lib/generate_localized.dart b/pkg/intl/lib/generate_localized.dart
index 9516372..6477023 100644
--- a/pkg/intl/lib/generate_localized.dart
+++ b/pkg/intl/lib/generate_localized.dart
@@ -215,7 +215,8 @@
Future initializeMessages(String localeName) {
initializeInternalMessageLookup(() => new CompositeMessageLookup());
messageLookup.addLocale(localeName, _findGeneratedMessagesFor);
- return deferredLibraries[localeName].load();
+ var lib = deferredLibraries[localeName];
+ return lib == null ? new Future.value(false) : lib.load();
}
MessageLookupByLibrary _findGeneratedMessagesFor(locale) {
diff --git a/pkg/intl/test/message_extraction/sample_with_messages.dart b/pkg/intl/test/message_extraction/sample_with_messages.dart
index 292273c..753f39c 100644
--- a/pkg/intl/test/message_extraction/sample_with_messages.dart
+++ b/pkg/intl/test/message_extraction/sample_with_messages.dart
@@ -178,6 +178,8 @@
var fr = new Intl("fr");
var english = new Intl("en_US");
var de = new Intl("de_DE");
+ // Throw in an initialize of a null locale to make sure it doesn't throw.
+ initializeMessages(null);
initializeMessages(fr.locale).then((_) => printStuff(fr));
initializeMessages(de.locale).then((_) => printStuff(de));
printStuff(english);
diff --git a/pkg/meta/lib/meta.dart b/pkg/meta/lib/meta.dart
index c86e377..9960d59 100644
--- a/pkg/meta/lib/meta.dart
+++ b/pkg/meta/lib/meta.dart
@@ -46,3 +46,18 @@
class _Override {
const _Override();
}
+
+/**
+ * An annotation used to mark a class that should be considered to implement
+ * every possible getter, setter and method. Tools can use this annotation to
+ * suppress warnings when there is no explicit implementation of a referenced
+ * member. Tools should provide a hint if this annotation is applied to a class
+ * that does not implement or inherit an implementation of the method
+ * [:noSuchMethod:] (other than the implementation in [Object]). Note that
+ * classes are not affected by the use of this annotation on a supertype.
+ */
+const proxy = const _Proxy();
+
+class _Proxy {
+ const _Proxy();
+}
\ No newline at end of file
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 39486e0..f41776a 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -8,9 +8,12 @@
*/packages/*/*: Skip
*/*/packages/*/*: Skip
*/*/*/packages/*/*: Skip
+*/*/*/*/packages/*/*: Skip
# Skip non-test files ending with "_test".
scheduled_test/lib/*: Skip
+polymer/lib/*: Skip
+polymer/example/*: Skip
scheduled_test/test/scheduled_server_test: Pass, Fail, Slow, Crash # Issue 9231, 9582
scheduled_test/test/scheduled_process_test: Pass, Slow # Issue 9231
@@ -20,7 +23,6 @@
[ $compiler == dart2js && $runtime == d8 ]
unmodifiable_collection/test/unmodifiable_collection_test: Pass, Fail # Issue 12429
-csslib/test/declaration_test: Pass, Crash # V8 issue 2846
[ $compiler == dart2js ]
analyzer_experimental/test/generated/ast_test: Fail #Issue 12341
@@ -28,7 +30,7 @@
[ $compiler == dart2js && $checked && $runtime == ie9 ]
crypto/test/base64_test: Timeout # Issue 12486
-[ $compiler == dart2js && ($runtime == d8 || $runtime == drt) ]
+[ $compiler == dart2js && $runtime == drt ]
crypto/test/hmac_sha256_test: Pass, Fail # v8 bug: Issue 12293
crypto/test/sha1_test: Pass, Fail # v8 bug: Issue 12293
crypto/test/sha256_test: Pass, Fail # v8 bug: Issue 12293
@@ -87,6 +89,8 @@
observe/test/observe_test: Fail # Issue 11970
observe/test/path_observer_test: Fail # Issue 11970
mdv/test/binding_syntax_test: Fail # Issue 11970
+polymer_expressions/test/eval_test: Fail # Issue 12578
+polymer_expressions/test/syntax_test: Fail # Issue 12578
serialization/test/serialization_test: Fail # Issue 6490
serialization/test/no_library_test: Fail # Issue 6490
@@ -109,7 +113,6 @@
csslib/test/var_test: Fail # looking for VM-specific stack traces, issue 12469
[ $compiler == dart2js && $runtime == drt ]
-third_party/html5lib/test/parser_feature_test: Fail # issue 12466
csslib: Pass, Fail # issue 12466
[ $browser ]
@@ -138,6 +141,7 @@
oauth2/test/handle_access_token_response_test: Fail, OK # Uses dart:io.
observe/test/transform_test: Fail, OK # Uses dart:io.
path/test/io_test: Fail, OK # Uses dart:io.
+polymer/test/*: Fail, OK # Uses dart:io.
watcher/test/*: Fail, OK # Uses dart:io.
scheduled_test/test/descriptor/async_test: Fail # http://dartbug.com/8440
@@ -179,9 +183,6 @@
# not minified.
unittest/test/*_minified_test: Skip # DO NOT COPY THIS UNLESS YOU WORK ON DART2JS
-[ $compiler == none && $runtime == drt ]
-dartdoc/test/dartdoc_test: Skip # See dartbug.com/4541.
-
[ $arch == arm ]
*: Skip
diff --git a/pkg/polymer/README.md b/pkg/polymer/README.md
new file mode 100644
index 0000000..d4d177f0b
--- /dev/null
+++ b/pkg/polymer/README.md
@@ -0,0 +1,85 @@
+Polymer.dart
+============
+
+Polymer is a new type of library for the web, built on top of Web Components,
+and designed to leverage the evolving web platform on modern browsers.
+
+Polymer.dart is a Dart port of Polymer created and maintained by the Dart team.
+The Dart team is collaborating with the Polymer team to ensure that polymer.dart
+elements and polyfills are fully compatible with Polymer.
+
+For more information about Polymer, see <http://www.polymer-project.org/>.
+For more information about Dart, see <http://www.dartlang.org/>.
+
+Try It Now
+-----------
+Add the polymer.dart package to your pubspec.yaml file:
+
+```yaml
+dependencies:
+ polymer: any
+```
+
+Instead of using `any`, we recommend using version ranges to avoid getting your
+project broken on each release. Using a version range lets you upgrade your
+package at your own pace. You can find the latest version number at
+<https://pub.dartlang.org/packages/polymer>.
+
+
+Learn More
+----------
+
+**Note**: these documents are currently out of date.
+
+* [Read an overview][overview]
+* [Setup your tools][tools]
+* [Browse the features][features]
+* [Dive into the specification][spec]
+
+See our [TodoMVC][] example by opening up the Dart Editor's Welcome Page and
+selecting "TodoMVC".
+
+Running Tests
+-------------
+
+Dependencies are installed using the [Pub Package Manager][pub].
+```bash
+pub install
+
+# Run command line tests and automated end-to-end tests. It needs two
+# executables on your path: `dart` and `content_shell` (see below
+# for links to download `content_shell`)
+test/run.sh
+```
+Note: to run browser tests you will need to have [content_shell][cs],
+which can be downloaded prebuilt for [Ubuntu Lucid][cs_lucid],
+[Windows][cs_win], or [Mac][cs_mac]. You can also build it from the
+[Dartium and content_shell sources][dartium_src].
+
+For Linux users all the necessary fonts must be installed see
+<https://code.google.com/p/chromium/wiki/LayoutTestsLinux>.
+
+Contacting Us
+-------------
+
+Please file issues in our [Issue Tracker][issues] or contact us on the
+[Dart Web UI mailing list][mailinglist].
+
+We also have the [Web UI development list][devlist] for discussions about
+internals of the code, code reviews, etc.
+
+[wc]: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html
+[pub]: http://www.dartlang.org/docs/pub-package-manager/
+[cs]: http://www.chromium.org/developers/testing/webkit-layout-tests
+[cs_lucid]: http://gsdview.appspot.com/dartium-archive/continuous/drt-lucid64.zip
+[cs_mac]: http://gsdview.appspot.com/dartium-archive/continuous/drt-mac.zip
+[cs_win]: http://gsdview.appspot.com/dartium-archive/continuous/drt-win.zip
+[dartium_src]: http://code.google.com/p/dart/wiki/BuildingDartium
+[TodoMVC]: http://addyosmani.github.com/todomvc/
+[issues]: http://dartbug.com/new
+[mailinglist]: https://groups.google.com/a/dartlang.org/forum/?fromgroups#!forum/web-ui
+[devlist]: https://groups.google.com/a/dartlang.org/forum/?fromgroups#!forum/web-ui-dev
+[overview]: http://www.dartlang.org/articles/dart-web-components/
+[tools]: https://www.dartlang.org/articles/dart-web-components/tools.html
+[spec]: https://www.dartlang.org/articles/dart-web-components/spec.html
+[features]: https://www.dartlang.org/articles/dart-web-components/summary.html
diff --git a/pkg/polymer/bin/dwc.dart b/pkg/polymer/bin/dwc.dart
new file mode 100755
index 0000000..3d3a0a2
--- /dev/null
+++ b/pkg/polymer/bin/dwc.dart
@@ -0,0 +1,8 @@
+#!/usr/bin/env dart
+// Copyright (c) 2012, 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.
+
+import 'package:polymer/dwc.dart' as dwc;
+
+void main() => dwc.main();
diff --git a/pkg/polymer/build.dart b/pkg/polymer/build.dart
new file mode 100755
index 0000000..5e863b8
--- /dev/null
+++ b/pkg/polymer/build.dart
@@ -0,0 +1,17 @@
+#!/usr/bin/env dart
+// Copyright (c) 2012, 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.
+
+/** Build logic that lets the Dart editor build examples in the background. */
+library build;
+import 'package:polymer/component_build.dart';
+import 'dart:io';
+
+void main() {
+ var args = new Options().arguments.toList()..addAll(['--', '--deploy']);
+ build(args, [
+ 'example/component/news/web/index.html',
+ 'example/scoped_style/index.html',
+ '../../samples/third_party/todomvc/web/index.html']);
+}
diff --git a/pkg/polymer/example/component/news/test/expected/news_index_test.html.txt b/pkg/polymer/example/component/news/test/expected/news_index_test.html.txt
new file mode 100644
index 0000000..f5907ed
--- /dev/null
+++ b/pkg/polymer/example/component/news/test/expected/news_index_test.html.txt
@@ -0,0 +1,85 @@
+Content-Type: text/plain
+<html><head><style>template { display: none; }</style>
+ <title>Simple Web Components Example</title>
+
+ <script src="../../packages/polymer/testing/testing.js"></script>
+<style>template,
+thead[template],
+tbody[template],
+tfoot[template],
+th[template],
+tr[template],
+td[template],
+caption[template],
+colgroup[template],
+col[template],
+option[template] {
+ display: none;
+}</style></head>
+<body><polymer-element name="x-news" extends="ul">
+ <template>
+ <style scoped="">
+ div.breaking {
+ color: Red;
+ font-size: 20px;
+ border: 1px dashed Purple;
+ }
+ div.other {
+ padding: 2px 0 0 0;
+ border: 1px solid Cyan;
+ }
+ </style>
+ <div class="breaking">
+ <h2>Breaking Stories</h2>
+ <ul>
+ <content select=".breaking"></content>
+ </ul>
+ </div>
+ <div class="other">
+ <h2>Other News</h2>
+ <ul>
+ <content></content>
+ </ul>
+ </div>
+ </template>
+
+</polymer-element>
+
+<h1>Simple Web Components Example</h1>
+<ul is="x-news"><shadow-root>
+ <style scoped="">
+ div.breaking {
+ color: Red;
+ font-size: 20px;
+ border: 1px dashed Purple;
+ }
+ div.other {
+ padding: 2px 0 0 0;
+ border: 1px solid Cyan;
+ }
+ </style>
+ <div class="breaking">
+ <h2>Breaking Stories</h2>
+ <ul>
+ <content select=".breaking"></content>
+ </ul>
+ </div>
+ <div class="other">
+ <h2>Other News</h2>
+ <ul>
+ <content></content>
+ </ul>
+ </div>
+ </shadow-root>
+ <li><a href="//example.com/stories/1">A story</a></li>
+ <li><a href="//example.com/stories/2">Another story</a></li>
+ <li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
+ <li><a href="//example.com/stories/4">Yet another story</a></li>
+ <li><a href="//example.com/stories/4">Awesome story</a></li>
+ <li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
+</ul>
+
+
+
+<script type="text/javascript" src="packages/shadow_dom/shadow_dom.debug.js"></script>
+<script type="application/dart" src="news_index_test.html_bootstrap.dart"></script></body></html>
diff --git a/pkg/polymer/example/component/news/test/news_index_test.html b/pkg/polymer/example/component/news/test/news_index_test.html
new file mode 100644
index 0000000..95bfadd
--- /dev/null
+++ b/pkg/polymer/example/component/news/test/news_index_test.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!--
+This example is from
+https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
+-->
+<html>
+<head>
+ <title>Simple Web Components Example</title>
+ <link rel="import" href="../web/news-component.html">
+ <script src="packages/polymer/testing/testing.js"></script>
+</head>
+<body>
+<h1>Simple Web Components Example</h1>
+<ul is="x-news">
+ <li><a href="//example.com/stories/1">A story</a></li>
+ <li><a href="//example.com/stories/2">Another story</a></li>
+ <li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
+ <li><a href="//example.com/stories/4">Yet another story</a></li>
+ <li><a href="//example.com/stories/4">Awesome story</a></li>
+ <li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
+</ul>
+<script type="application/dart">
+import 'dart:html';
+main() {
+ window.postMessage('done', '*');
+}
+</script>
+</body>
+</html>
diff --git a/pkg/polymer/example/component/news/test/test.dart b/pkg/polymer/example/component/news/test/test.dart
new file mode 100755
index 0000000..a4c98a0
--- /dev/null
+++ b/pkg/polymer/example/component/news/test/test.dart
@@ -0,0 +1,14 @@
+#!/usr/bin/env dart
+// 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.
+
+import 'dart:io';
+import 'package:polymer/testing/content_shell_test.dart';
+import 'package:unittest/compact_vm_config.dart';
+
+void main() {
+ useCompactVMConfiguration();
+ // Base directory, input, expected, output:
+ renderTests('..', '.', 'expected', 'out');
+}
diff --git a/pkg/polymer/example/component/news/web/index.html b/pkg/polymer/example/component/news/web/index.html
new file mode 100644
index 0000000..3354d9b
--- /dev/null
+++ b/pkg/polymer/example/component/news/web/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+This example is from
+https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
+-->
+<html>
+<head>
+ <title>Simple Web Components Example</title>
+ <link rel="import" href="news-component.html">
+ <script src='packages/polymer/boot.js'></script>
+</head>
+<body>
+<h1>Simple Web Components Example</h1>
+<ul is="x-news">
+ <li><a href="//example.com/stories/1">A story</a></li>
+ <li><a href="//example.com/stories/2">Another story</a></li>
+ <li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
+ <li><a href="//example.com/stories/4">Yet another story</a></li>
+ <li><a href="//example.com/stories/4">Awesome story</a></li>
+ <li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
+</ul>
+ <script type="application/dart">main() {}</script>
+</body>
+</html>
diff --git a/pkg/polymer/example/component/news/web/news-component.html b/pkg/polymer/example/component/news/web/news-component.html
new file mode 100644
index 0000000..8e604d2
--- /dev/null
+++ b/pkg/polymer/example/component/news/web/news-component.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+This example is from
+https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
+-->
+<html>
+<head>
+ <title>News Component</title>
+</head>
+<body>
+<polymer-element name="x-news" extends="ul">
+ <template>
+ <style scoped>
+ div.breaking {
+ color: Red;
+ font-size: 20px;
+ border: 1px dashed Purple;
+ }
+ div.other {
+ padding: 2px 0 0 0;
+ border: 1px solid Cyan;
+ }
+ </style>
+ <div class="breaking">
+ <h2>Breaking Stories</h2>
+ <ul>
+ <content select=".breaking"></content>
+ </ul>
+ </div>
+ <div class="other">
+ <h2>Other News</h2>
+ <ul>
+ <content></content>
+ </ul>
+ </div>
+ </template>
+ <script type="application/dart">
+ import 'package:polymer/polymer.dart';
+
+ class XNews extends PolymerElement {}
+
+ @initMethod
+ _init() {
+ registerPolymerElement('x-news', () => new XNews());
+ }
+ </script>
+</polymer-element>
+</body>
+</html>
diff --git a/pkg/polymer/example/scoped_style/index.html b/pkg/polymer/example/scoped_style/index.html
new file mode 100644
index 0000000..bc81e7a
--- /dev/null
+++ b/pkg/polymer/example/scoped_style/index.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Simple CSS Test</title>
+ <link rel="import" href="my_test.html">
+ <script src='packages/polymer/boot.js'></script>
+</head>
+<body>
+<style>
+ p { color: black;}
+</style>
+<p>outside of element, should be black</p>
+
+<my-test></my-test>
+</body>
+</html>
diff --git a/pkg/polymer/example/scoped_style/my_test.html b/pkg/polymer/example/scoped_style/my_test.html
new file mode 100644
index 0000000..2713a3f
--- /dev/null
+++ b/pkg/polymer/example/scoped_style/my_test.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test Compopnent</title>
+</head>
+<body>
+<polymer-element name="my-test">
+ <template>
+ <style>
+ p { color: red;}
+ </style>
+ <p>Inside element, should be red</p>
+ </template>
+ <script type="application/dart">
+ import 'package:polymer/polymer.dart';
+
+ @CustomTag('my-test')
+ class MyTest extends PolymerElement {}
+ </script>
+</polymer-element>
+</body>
+</html>
diff --git a/pkg/polymer/lib/boot.js b/pkg/polymer/lib/boot.js
new file mode 100644
index 0000000..84bb489
--- /dev/null
+++ b/pkg/polymer/lib/boot.js
@@ -0,0 +1,158 @@
+// 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.
+
+// This script dynamically prepares a set of files to run polymer.dart. It uses
+// the html_import polyfill to search for all imported files, then
+// it inlines all <polymer-element> definitions on the top-level page (needed by
+// registerPolymerElement), and it removes script tags that appear inside
+// those tags. It finally rewrites the main entrypoint to call an initialization
+// function on each of the declared <polymer-elements>.
+//
+// This script is needed only when running polymer.dart in Dartium. It should be
+// removed by the polymer deployment commands.
+
+// As an example, given an input of this form:
+// <polymer-element name="c1">
+// <template></template>
+// <script type="application/dart" src="url0.dart"></script>
+// </polymer-element>
+// <element name="c2">
+// <template></template>
+// <script type="application/dart">main() => { print('body2'); }</script>
+// </element>
+// <c1></c1>
+// <c2></c2>
+// <script type="application/dart" src="url2.dart"></script>
+// <script src="packages/polymer/boot.js"></script>
+//
+// This script will simplifies the page as follows:
+// <polymer-element name="c1">
+// <template></template>
+// </polymer-element>
+// <polymer-element name="c2">
+// <template></template>
+// </polymer-element>
+// <c1></c1>
+// <c2></c2>
+// <script type="application/dart">
+// import 'url0.dart' as i0;
+// import "data:application/dart;base64,CiAgICBtYWluKCkgewogICAgICBwcmludCgnYm9keTInKTsKICAgIH0KICAgIA==" as i1;
+// import 'url2.dart' as i2;
+// ...
+// main() {
+// // code that checks which libraries have a 'main' and invokes them.
+// // practically equivalent to: i0._init(); i1._init(); i2.main();
+// }
+// </script>
+
+
+(function() {
+ // Only run in Dartium.
+ if (!navigator.webkitStartDart) {
+ // TODO(sigmund): rephrase when we split build.dart in two: analysis vs
+ // deploy pieces.
+ console.warn('boot.js only works in Dartium. Run the build.dart' +
+ ' tool to compile a depolyable JavaScript version')
+ return;
+ }
+ document.write(
+ '<script src="packages/html_import/html_import.min.js"></script>');
+
+ // Extract a Dart import URL from a script tag, which is the 'src' attribute
+ // of the script tag, or a data-url with the script contents for inlined code.
+ function getScriptUrl(script) {
+ var url = script.src;
+ if (url) {
+ // Normalize package: urls
+ var index = url.indexOf('packages/');
+ if (index == 0 || (index > 0 && url[index - 1] == '/')) {
+ url = "package:" + url.slice(index + 9);
+ }
+ return url;
+ } else {
+ // TODO(sigmund): investigate how to eliminate the warning in Dartium
+ // (changing to text/javascript hides the warning, but seems wrong).
+ return "data:application/dart;base64," + window.btoa(script.textContent);
+ }
+ }
+
+ // Moves <polymer-elements> from imported documents into the top-level page.
+ function inlinePolymerElements(content, ref, seen) {
+ if (!seen) seen = {};
+ var links = content.querySelectorAll('link[rel="import"]');
+ for (var i = 0; i < links.length; i++) {
+ var link = links[i].import;
+ if (seen[link.href]) continue;
+ seen[link.href] = link;
+ inlinePolymerElements(link.content, ref, seen);
+ }
+
+ if (content != document) { // no need to do anything for the top-level page
+ var elements = content.querySelectorAll('polymer-element');
+ for (var i = 0; i < elements.length; i++) {
+ document.body.insertBefore(elements[i], ref);
+ }
+ }
+ }
+
+ // Creates a Dart program that imports [urls] and passes them to initPolymer
+ // (which in turn will invoke their main function, their methods marked with
+ // @initMethod, and register any custom tag labeled with @CustomTag).
+ function createMain(urls, mainUrl) {
+ var imports = Array(urls.length + 1);
+ for (var i = 0; i < urls.length; ++i) {
+ imports[i] = 'import "' + urls[i] + '" as i' + i + ';';
+ }
+ imports[urls.length] = 'import "package:polymer/polymer.dart" as polymer;';
+ var arg = urls.length == 0 ? '[]' :
+ ('[\n "' + urls.join('",\n "') + '"\n ]');
+ return (imports.join('\n') +
+ '\n\nmain() {\n' +
+ ' polymer.initPolymer(' + arg + ');\n' +
+ '}\n');
+ }
+
+ // Finds all top-level <script> tags, and <script> tags in custom elements
+ // and merges them into a single entrypoint.
+ function mergeScripts() {
+ var scripts = document.getElementsByTagName("script");
+ var length = scripts.length;
+
+ var urls = [];
+ var toRemove = [];
+
+ // Collect the information we need to replace the script tags
+ for (var i = 0; i < length; ++i) {
+ var script = scripts[i];
+ if (script.type == "application/dart") {
+ urls.push(getScriptUrl(script));
+ toRemove.push(script);
+ }
+ }
+
+ toRemove.forEach(function (s) { s.parentNode.removeChild(s); });
+
+ // Append a new script tag that initializes everything.
+ var newScript = document.createElement('script');
+ newScript.type = "application/dart";
+ newScript.textContent = createMain(urls);
+ document.body.appendChild(newScript);
+ }
+
+ var alreadyRan = false;
+ window.addEventListener('HTMLImportsLoaded', function (e) {
+ if (alreadyRan) {
+ console.warn('HTMLImportsLoaded fired again.');
+ return;
+ }
+ alreadyRan = true;
+ var ref = document.body.children[0];
+ inlinePolymerElements(document, ref);
+ mergeScripts();
+ if (!navigator.webkitStartDart()) {
+ document.body.innerHTML = 'This build has expired. Please download a ' +
+ 'new Dartium at http://www.dartlang.org/dartium/index.html';
+ }
+ });
+})();
diff --git a/pkg/polymer/lib/component_build.dart b/pkg/polymer/lib/component_build.dart
new file mode 100644
index 0000000..316124e
--- /dev/null
+++ b/pkg/polymer/lib/component_build.dart
@@ -0,0 +1,166 @@
+// Copyright (c) 2012, 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.
+
+/**
+ * Common logic to make it easy to create a `build.dart` for your project.
+ *
+ * The `build.dart` script is invoked automatically by the Editor whenever a
+ * file in the project changes. It must be placed in the root of a project
+ * (where pubspec.yaml lives) and should be named exactly 'build.dart'.
+ *
+ * A common `build.dart` would look as follows:
+ *
+ * import 'dart:io';
+ * import 'package:polymer/component_build.dart';
+ *
+ * main() => build(new Options().arguments, ['web/index.html']);
+ */
+library build_utils;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:json' as json;
+import 'package:args/args.dart';
+
+import 'dwc.dart' as dwc;
+import 'src/utils.dart';
+import 'src/compiler_options.dart';
+
+/**
+ * Set up 'build.dart' to compile with the dart web components compiler every
+ * [entryPoints] listed. On clean commands, the directory where [entryPoints]
+ * live will be scanned for generated files to delete them.
+ */
+// TODO(jmesserly): we need a better way to automatically detect input files
+Future<List<dwc.CompilerResult>> build(List<String> arguments,
+ List<String> entryPoints,
+ {bool printTime: true, bool shouldPrint: true}) {
+ bool useColors = stdioType(stdout) == StdioType.TERMINAL;
+ return asyncTime('Total time', () {
+ var args = _processArgs(arguments);
+ var tasks = new FutureGroup();
+ var lastTask = new Future.value(null);
+ tasks.add(lastTask);
+
+ var changedFiles = args["changed"];
+ var removedFiles = args["removed"];
+ var cleanBuild = args["clean"];
+ var machineFormat = args["machine"];
+ // Also trigger a full build if the script was run from the command line
+ // with no arguments
+ var fullBuild = args["full"] || (!machineFormat && changedFiles.isEmpty &&
+ removedFiles.isEmpty && !cleanBuild);
+
+ var options = CompilerOptions.parse(args.rest, checkUsage: false);
+
+ // [outputOnlyDirs] contains directories known to only have output files.
+ // When outputDir is not specified, we create a new directory which only
+ // contains output files. If options.outputDir is specified, we don't know
+ // if the output directory may also have input files. In which case,
+ // [_handleCleanCommand] and [_isInputFile] are more conservative.
+ //
+ // TODO(sigmund): get rid of this. Instead, use the compiler to understand
+ // which files are input or output files.
+ var outputOnlyDirs = options.outputDir == null ? []
+ : entryPoints.map((e) => _outDir(e)).toList();
+
+ if (cleanBuild) {
+ _handleCleanCommand(outputOnlyDirs);
+ } else if (fullBuild
+ || changedFiles.any((f) => _isInputFile(f, outputOnlyDirs))
+ || removedFiles.any((f) => _isInputFile(f, outputOnlyDirs))) {
+ for (var file in entryPoints) {
+ var dwcArgs = new List.from(args.rest);
+ if (machineFormat) dwcArgs.add('--json_format');
+ if (!useColors) dwcArgs.add('--no-colors');
+ // We'll set 'out/' as the out folder, unless an output directory was
+ // already specified in the command line.
+ if (options.outputDir == null) dwcArgs.addAll(['-o', _outDir(file)]);
+ dwcArgs.add(file);
+ // Chain tasks to that we run one at a time.
+ lastTask = lastTask.then((_) => dwc.run(dwcArgs, printTime: printTime,
+ shouldPrint: shouldPrint));
+ if (machineFormat) {
+ lastTask = lastTask.then((res) {
+ appendMessage(Map jsonMessage) {
+ var message = json.stringify([jsonMessage]);
+ if (shouldPrint) print(message);
+ res.messages.add(message);
+ }
+ // Print for the Editor messages about mappings and generated files
+ res.outputs.forEach((out, input) {
+ if (out.endsWith(".html") && input != null) {
+ appendMessage({
+ "method": "mapping",
+ "params": {"from": input, "to": out},
+ });
+ }
+ appendMessage({"method": "generated", "params": {"file": out}});
+ });
+ return res;
+ });
+ }
+ tasks.add(lastTask);
+ }
+ }
+ return tasks.future.then((r) => r.where((v) => v != null));
+ }, printTime: printTime, useColors: useColors);
+}
+
+String _outDir(String file) => path.join(path.dirname(file), 'out');
+
+/** Tell whether [filePath] is a generated file. */
+bool _isGeneratedFile(String filePath, List<String> outputOnlyDirs) {
+ var dirPrefix = path.dirname(filePath);
+ for (var outDir in outputOnlyDirs) {
+ if (dirPrefix.startsWith(outDir)) return true;
+ }
+ return path.basename(filePath).startsWith('_');
+}
+
+/** Tell whether [filePath] is an input file. */
+bool _isInputFile(String filePath, List<String> outputOnlyDirs) {
+ var ext = path.extension(filePath);
+ return (ext == '.dart' || ext == '.html') &&
+ !_isGeneratedFile(filePath, outputOnlyDirs);
+}
+
+/**
+ * Delete all generated files. Currently we only delete files under directories
+ * that are known to contain only generated code.
+ */
+void _handleCleanCommand(List<String> outputOnlyDirs) {
+ for (var dirPath in outputOnlyDirs) {
+ var dir = new Directory(dirPath);
+ if (!dir.existsSync()) continue;
+ for (var f in dir.listSync(recursive: false)) {
+ if (f is File && _isGeneratedFile(f.path, outputOnlyDirs)) f.deleteSync();
+ }
+ }
+}
+
+/** Process the command-line arguments. */
+ArgResults _processArgs(List<String> arguments) {
+ var parser = new ArgParser()
+ ..addOption("changed", help: "the file has changed since the last build",
+ allowMultiple: true)
+ ..addOption("removed", help: "the file was removed since the last build",
+ allowMultiple: true)
+ ..addFlag("clean", negatable: false, help: "remove any build artifacts")
+ ..addFlag("full", negatable: false, help: "perform a full build")
+ ..addFlag("machine", negatable: false,
+ help: "produce warnings in a machine parseable format")
+ ..addFlag("help", abbr: 'h',
+ negatable: false, help: "displays this help and exit");
+ var args = parser.parse(arguments);
+ if (args["help"]) {
+ print('A build script that invokes the web-ui compiler (dwc).');
+ print('Usage: dart build.dart [options] [-- [dwc-options]]');
+ print('\nThese are valid options expected by build.dart:');
+ print(parser.getUsage());
+ print('\nThese are valid options expected by dwc:');
+ dwc.run(['-h']).then((_) => exit(0));
+ }
+ return args;
+}
diff --git a/pkg/polymer/lib/dwc.dart b/pkg/polymer/lib/dwc.dart
new file mode 100755
index 0000000..b70f29b
--- /dev/null
+++ b/pkg/polymer/lib/dwc.dart
@@ -0,0 +1,198 @@
+// Copyright (c) 2012, 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.
+
+/** The entry point to the compiler. Used to implement `bin/dwc.dart`. */
+library dwc;
+
+import 'dart:async';
+import 'dart:io';
+import 'package:logging/logging.dart' show Level;
+
+import 'src/compiler.dart';
+import 'src/file_system.dart';
+import 'src/file_system/console.dart';
+import 'src/files.dart';
+import 'src/messages.dart';
+import 'src/compiler_options.dart';
+import 'src/utils.dart';
+
+FileSystem _fileSystem;
+
+void main() {
+ run(new Options().arguments).then((result) {
+ exit(result.success ? 0 : 1);
+ });
+}
+
+/** Contains the result of a compiler run. */
+class CompilerResult {
+ final bool success;
+
+ /** Map of output path to source, if there is one */
+ final Map<String, String> outputs;
+
+ /** List of files read during compilation */
+ final List<String> inputs;
+
+ final List<String> messages;
+ String bootstrapFile;
+
+ CompilerResult([this.success = true,
+ this.outputs,
+ this.inputs,
+ this.messages = const [],
+ this.bootstrapFile]);
+
+ factory CompilerResult._(bool success,
+ List<String> messages, List<OutputFile> outputs, List<SourceFile> files) {
+ var file;
+ var outs = new Map<String, String>();
+ for (var out in outputs) {
+ if (path.basename(out.path).endsWith('_bootstrap.dart')) {
+ file = out.path;
+ }
+ outs[out.path] = out.source;
+ }
+ var inputs = files.map((f) => f.path).toList();
+ return new CompilerResult(success, outs, inputs, messages, file);
+ }
+}
+
+/**
+ * Runs the web components compiler with the command-line options in [args].
+ * See [CompilerOptions] for the definition of valid arguments.
+ */
+// TODO(jmesserly): fix this to return a proper exit code
+// TODO(justinfagnani): return messages in the result
+Future<CompilerResult> run(List<String> args, {bool printTime,
+ bool shouldPrint: true}) {
+ var options = CompilerOptions.parse(args);
+ if (options == null) return new Future.value(new CompilerResult());
+ if (printTime == null) printTime = options.verbose;
+
+ _fileSystem = new ConsoleFileSystem();
+ var messages = new Messages(options: options, shouldPrint: shouldPrint);
+
+ return asyncTime('Total time spent on ${options.inputFile}', () {
+ var compiler = new Compiler(_fileSystem, options, messages);
+ var res;
+ return compiler.run()
+ .then((_) {
+ var success = messages.messages.every((m) => m.level != Level.SEVERE);
+ var msgs = options.jsonFormat
+ ? messages.messages.map((m) => m.toJson())
+ : messages.messages.map((m) => m.toString());
+ res = new CompilerResult._(success, msgs.toList(),
+ compiler.output, compiler.files);
+ })
+ .then((_) => _symlinkPubPackages(res, options, messages))
+ .then((_) => _emitFiles(compiler.output, options.clean))
+ .then((_) => res);
+ }, printTime: printTime, useColors: options.useColors);
+}
+
+Future _emitFiles(List<OutputFile> outputs, bool clean) {
+ outputs.forEach((f) => _writeFile(f.path, f.contents, clean));
+ return _fileSystem.flush();
+}
+
+void _writeFile(String filePath, String contents, bool clean) {
+ if (clean) {
+ File fileOut = new File(filePath);
+ if (fileOut.existsSync()) {
+ fileOut.deleteSync();
+ }
+ } else {
+ _createIfNeeded(path.dirname(filePath));
+ _fileSystem.writeString(filePath, contents);
+ }
+}
+
+void _createIfNeeded(String outdir) {
+ if (outdir.isEmpty) return;
+ var outDirectory = new Directory(outdir);
+ if (!outDirectory.existsSync()) {
+ _createIfNeeded(path.dirname(outdir));
+ outDirectory.createSync();
+ }
+}
+
+/**
+ * Creates a symlink to the pub packages directory in the output location. The
+ * returned future completes when the symlink was created (or immediately if it
+ * already exists).
+ */
+Future _symlinkPubPackages(CompilerResult result, CompilerOptions options,
+ Messages messages) {
+ if (options.outputDir == null || result.bootstrapFile == null
+ || options.packageRoot != null) {
+ // We don't need to copy the packages directory if the output was generated
+ // in-place where the input lives, if the compiler was called without an
+ // entry-point file, or if the compiler was called with a package-root
+ // option.
+ return new Future.value(null);
+ }
+
+ var linkDir = path.dirname(result.bootstrapFile);
+ _createIfNeeded(linkDir);
+ var linkPath = path.join(linkDir, 'packages');
+ // A resolved symlink works like a directory
+ // TODO(sigmund): replace this with something smarter once we have good
+ // symlink support in dart:io
+ if (new Directory(linkPath).existsSync()) {
+ // Packages directory already exists.
+ return new Future.value(null);
+ }
+
+ // A broken symlink works like a file
+ var toFile = new File(linkPath);
+ if (toFile.existsSync()) {
+ toFile.deleteSync();
+ }
+
+ var targetPath = path.join(path.dirname(options.inputFile), 'packages');
+ // [fullPathSync] will canonicalize the path, resolving any symlinks.
+ // TODO(sigmund): once it's possible in dart:io, we just want to use a full
+ // path, but not necessarily resolve symlinks.
+ var target = new File(targetPath).fullPathSync().toString();
+ return createSymlink(target, linkPath, messages: messages);
+}
+
+
+// TODO(jmesserly): this code was taken from Pub's io library.
+// Added error handling and don't return the file result, to match the code
+// we had previously. Also "target" and "link" only accept strings. And inlined
+// the relevant parts of runProcess. Note that it uses "cmd" to get the path
+// on Windows.
+/**
+ * Creates a new symlink that creates an alias of [target] at [link], both of
+ * which can be a [String], [File], or [Directory]. Returns a [Future] which
+ * completes to the symlink file (i.e. [link]).
+ */
+Future createSymlink(String target, String link, {Messages messages: null}) {
+ messages = messages == null? new Messages.silent() : messages;
+ var command = 'ln';
+ var args = ['-s', target, link];
+
+ if (Platform.operatingSystem == 'windows') {
+ // Call mklink on Windows to create an NTFS junction point. Only works on
+ // Vista or later. (Junction points are available earlier, but the "mklink"
+ // command is not.) I'm using a junction point (/j) here instead of a soft
+ // link (/d) because the latter requires some privilege shenanigans that
+ // I'm not sure how to specify from the command line.
+ command = 'cmd';
+ args = ['/c', 'mklink', '/j', link, target];
+ }
+
+ return Process.run(command, args).then((result) {
+ if (result.exitCode != 0) {
+ var details = 'subprocess stdout:\n${result.stdout}\n'
+ 'subprocess stderr:\n${result.stderr}';
+ messages.error(
+ 'unable to create symlink\n target: $target\n link:$link\n$details',
+ null);
+ }
+ return null;
+ });
+}
diff --git a/pkg/polymer/lib/observe.dart b/pkg/polymer/lib/observe.dart
new file mode 100644
index 0000000..0a02a90
--- /dev/null
+++ b/pkg/polymer/lib/observe.dart
@@ -0,0 +1,124 @@
+// 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.
+
+/**
+ * Helpers for observable objects.
+ * Intended for use with `package:observe`.
+ */
+library polymer.observe;
+
+import 'dart:async';
+import 'package:observe/observe.dart';
+
+const _VALUE = const Symbol('value');
+
+/**
+ * Forwards an observable property from one object to another. For example:
+ *
+ * class MyModel extends ObservableBase {
+ * StreamSubscription _sub;
+ * MyOtherModel _otherModel;
+ *
+ * MyModel() {
+ * ...
+ * _sub = bindProperty(_otherModel, const Symbol('value'),
+ * () => notifyProperty(this, const Symbol('prop'));
+ * }
+ *
+ * String get prop => _otherModel.value;
+ * set prop(String value) { _otherModel.value = value; }
+ * }
+ *
+ * See also [notifyProperty].
+ */
+StreamSubscription bindProperty(Observable source, Symbol sourceName,
+ void callback()) {
+ return source.changes.listen((records) {
+ for (var record in records) {
+ if (record.changes(sourceName)) {
+ callback();
+ }
+ }
+ });
+}
+
+/**
+ * Notify the property change. Shorthand for:
+ *
+ * target.notifyChange(new PropertyChangeRecord(targetName));
+ */
+void notifyProperty(Observable target, Symbol targetName) {
+ target.notifyChange(new PropertyChangeRecord(targetName));
+}
+
+
+// Inspired by ArrayReduction at:
+// https://raw.github.com/rafaelw/ChangeSummary/master/util/array_reduction.js
+// The main difference is we support anything on the rich Dart Iterable API.
+
+/**
+ * Observes a path starting from each item in the list.
+ */
+class ListPathObserver<E, P> extends ChangeNotifierBase {
+ final ObservableList<E> list;
+ final String _itemPath;
+ final List<PathObserver> _observers = <PathObserver>[];
+ final List<StreamSubscription> _subs = <StreamSubscription>[];
+ StreamSubscription _sub;
+ bool _scheduled = false;
+ Iterable<P> _value;
+
+ ListPathObserver(this.list, String path)
+ : _itemPath = path {
+
+ _sub = list.changes.listen((records) {
+ for (var record in records) {
+ if (record is ListChangeRecord) {
+ _observeItems(record.addedCount - record.removedCount);
+ }
+ }
+ _scheduleReduce(null);
+ });
+
+ _observeItems(list.length);
+ _reduce();
+ }
+
+ Iterable<P> get value => _value;
+
+ void dispose() {
+ if (_sub != null) _sub.cancel();
+ _subs.forEach((s) => s.cancel());
+ _subs.clear();
+ }
+
+ void _reduce() {
+ _scheduled = false;
+ _value = _observers.map((o) => o.value);
+ notifyChange(new PropertyChangeRecord(_VALUE));
+ }
+
+ void _scheduleReduce(_) {
+ if (_scheduled) return;
+ _scheduled = true;
+ runAsync(_reduce);
+ }
+
+ void _observeItems(int lengthAdjust) {
+ if (lengthAdjust > 0) {
+ for (int i = 0; i < lengthAdjust; i++) {
+ int len = _observers.length;
+ var pathObs = new PathObserver(list, '$len.$_itemPath');
+ _subs.add(pathObs.changes.listen(_scheduleReduce));
+ _observers.add(pathObs);
+ }
+ } else if (lengthAdjust < 0) {
+ for (int i = 0; i < -lengthAdjust; i++) {
+ _subs.removeLast().cancel();
+ }
+ int len = _observers.length;
+ _observers.removeRange(len + lengthAdjust, len);
+ }
+ }
+}
diff --git a/pkg/polymer/lib/observe_html.dart b/pkg/polymer/lib/observe_html.dart
new file mode 100644
index 0000000..7280b0e
--- /dev/null
+++ b/pkg/polymer/lib/observe_html.dart
@@ -0,0 +1,63 @@
+// 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.
+
+// TODO(jmesserly): can we handle this more elegantly?
+// In general, it seems like we want a convenient way to take a Stream plus a
+// getter and convert this into an Observable.
+
+/** Helpers for exposing dart:html as observable data. */
+library polymer.observe_html;
+
+import 'dart:html';
+import 'package:observe/observe.dart';
+
+/** An observable version of [window.location.hash]. */
+final ObservableLocationHash windowLocation = new ObservableLocationHash._();
+
+class ObservableLocationHash extends ChangeNotifierBase {
+ ObservableLocationHash._() {
+ // listen on changes to #hash in the URL
+ // Note: listen on both popState and hashChange, because IE9 doesn't support
+ // history API. See http://dartbug.com/5483
+ // TODO(jmesserly): only listen to these if someone is listening to our
+ // changes.
+ window.onHashChange.listen(_notifyHashChange);
+ window.onPopState.listen(_notifyHashChange);
+ }
+
+ String get hash => window.location.hash;
+
+ /**
+ * Pushes a new URL state, similar to the affect of clicking a link.
+ * Has no effect if the [value] already equals [window.location.hash].
+ */
+ void set hash(String value) {
+ if (value == hash) return;
+
+ window.history.pushState(null, '', value);
+ _notifyHashChange(null);
+ }
+
+ void _notifyHashChange(_) {
+ notifyChange(new PropertyChangeRecord(const Symbol('hash')));
+ }
+}
+
+/** Add or remove CSS class [className] based on the [value]. */
+void updateCssClass(Element element, String className, bool value) {
+ if (value == true) {
+ element.classes.add(className);
+ } else {
+ element.classes.remove(className);
+ }
+}
+
+/** Bind a CSS class to the observable [object] and property [path]. */
+PathObserver bindCssClass(Element element, String className,
+ Observable object, String path) {
+
+ return new PathObserver(object, path)..bindSync((value) {
+ updateCssClass(element, className, value);
+ });
+}
diff --git a/pkg/polymer/lib/polymer.dart b/pkg/polymer/lib/polymer.dart
new file mode 100644
index 0000000..575619b
--- /dev/null
+++ b/pkg/polymer/lib/polymer.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2012, 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.
+
+/**
+ * This library exports all of the commonly used functions and types for
+ * building UI's.
+ *
+ * See this article for more information:
+ * <http://www.dartlang.org/articles/dart-web-components/>.
+ */
+library polymer;
+
+import 'dart:async';
+import 'dart:mirrors';
+
+import 'package:mdv/mdv.dart' as mdv;
+import 'package:observe/src/microtask.dart';
+import 'package:path/path.dart' as path;
+import 'polymer_element.dart' show registerPolymerElement;
+
+export 'package:custom_element/custom_element.dart';
+export 'package:observe/observe.dart';
+export 'package:observe/src/microtask.dart';
+
+export 'observe.dart';
+export 'observe_html.dart';
+export 'polymer_element.dart';
+export 'safe_html.dart';
+
+
+/** Annotation used to automatically register polymer elements. */
+class CustomTag {
+ final String tagName;
+ const CustomTag(this.tagName);
+}
+
+/**
+ * Metadata used to label static or top-level methods that are called
+ * automatically when loading the library of a custom element.
+ */
+const initMethod = const _InitMethodAnnotation();
+
+/**
+ * Initializes a polymer application as follows:
+ * * set up up polling for observable changes
+ * * initialize MDV
+ * * for each library in [libraries], register custom elements labeled with
+ * [CustomTag] and invoke the initialization method on it.
+ *
+ * The initialization on each library is either a method named `main` or
+ * a top-level function and annotated with [initMethod].
+ *
+ * The urls in [libraries] can be absolute or relative to [srcUrl].
+ */
+void initPolymer(List<String> libraries, [String srcUrl]) {
+ wrapMicrotask(() {
+ // DOM events don't yet go through microtasks, so we catch those here.
+ new Timer.periodic(new Duration(milliseconds: 125),
+ (_) => performMicrotaskCheckpoint());
+
+ // TODO(jmesserly): mdv should use initMdv instead of mdv.initialize.
+ mdv.initialize();
+ for (var lib in libraries) {
+ _loadLibrary(lib, srcUrl);
+ }
+ })();
+}
+
+/** All libraries in the current isolate. */
+final _libs = currentMirrorSystem().libraries;
+
+/**
+ * Reads the library at [uriString] (which can be an absolute URI or a relative
+ * URI from [srcUrl]), and:
+ *
+ * * If present, invokes `main`.
+ *
+ * * If present, invokes any top-level and static functions marked
+ * with the [initMethod] annotation (in the order they appear).
+ *
+ * * Registers any [PolymerElement] that is marked with the [CustomTag]
+ * annotation.
+ */
+void _loadLibrary(String uriString, [String srcUrl]) {
+ var uri = Uri.parse(uriString);
+ if (uri.scheme == '' && srcUrl != null) {
+ uri = Uri.parse(path.normalize(path.join(path.dirname(srcUrl), uriString)));
+ }
+ var lib = _libs[uri];
+ if (lib == null) {
+ print('warning: $uri library not found');
+ return;
+ }
+
+ // Invoke `main`, if present.
+ if (lib.functions[const Symbol('main')] != null) {
+ lib.invoke(const Symbol('main'), const []);
+ }
+
+ // Search top-level functions marked with @initMethod
+ for (var f in lib.functions.values) {
+ _maybeInvoke(lib, f);
+ }
+
+ for (var c in lib.classes.values) {
+ // Search for @CustomTag on classes
+ for (var m in c.metadata) {
+ var meta = m.reflectee;
+ if (meta is CustomTag) {
+ registerPolymerElement(meta.tagName,
+ () => c.newInstance(const Symbol(''), const []).reflectee);
+ }
+ }
+
+ // TODO(sigmund): check also static methods marked with @initMethod.
+ // This is blocked on two bugs:
+ // - dartbug.com/12133 (static methods are incorrectly listed as top-level
+ // in dart2js, so they end up being called twice)
+ // - dartbug.com/12134 (sometimes "method.metadata" throws an exception,
+ // we could wrap and hide those exceptions, but it's not ideal).
+ }
+}
+
+void _maybeInvoke(ObjectMirror obj, MethodMirror method) {
+ var annotationFound = false;
+ for (var meta in method.metadata) {
+ if (identical(meta.reflectee, initMethod)) {
+ annotationFound = true;
+ break;
+ }
+ }
+ if (!annotationFound) return;
+ if (!method.isStatic) {
+ print("warning: methods marked with @initMethod should be static,"
+ " ${method.simpleName} is not.");
+ return;
+ }
+ if (!method.parameters.where((p) => !p.isOptional).isEmpty) {
+ print("warning: methods marked with @initMethod should take no "
+ "arguments, ${method.simpleName} expects some.");
+ return;
+ }
+ obj.invoke(method.simpleName, const []);
+}
+
+class _InitMethodAnnotation {
+ const _InitMethodAnnotation();
+}
diff --git a/pkg/polymer/lib/polymer_element.dart b/pkg/polymer/lib/polymer_element.dart
new file mode 100644
index 0000000..297625e
--- /dev/null
+++ b/pkg/polymer/lib/polymer_element.dart
@@ -0,0 +1,566 @@
+// 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.
+
+library polymer.polymer_element;
+
+import 'dart:async';
+import 'dart:html';
+import 'dart:mirrors';
+import 'dart:js' as dartJs;
+
+import 'package:custom_element/custom_element.dart';
+import 'package:js/js.dart' as js;
+import 'package:mdv/mdv.dart' show NodeBinding;
+import 'package:observe/observe.dart';
+import 'package:observe/src/microtask.dart';
+import 'package:polymer_expressions/polymer_expressions.dart';
+
+import 'src/utils_observe.dart' show toCamelCase, toHyphenedName;
+
+/**
+ * Registers a [PolymerElement]. This is similar to [registerCustomElement]
+ * but it is designed to work with the `<element>` element and adds additional
+ * features.
+ */
+void registerPolymerElement(String localName, PolymerElement create()) {
+ registerCustomElement(localName, () => create().._initialize(localName));
+}
+
+/**
+ * *Warning*: many features of this class are not fully implemented.
+ *
+ * The base class for Polymer elements. It provides convience features on top
+ * of the custom elements web standard.
+ *
+ * Currently it supports publishing attributes via:
+ *
+ * <element name="..." attributes="foo, bar, baz">
+ *
+ * Any attribute published this way can be used in a data binding expression,
+ * and it should contain a corresponding DOM field.
+ *
+ * *Warning*: due to dart2js mirror limititations, the mapping from HTML
+ * attribute to element property is a conversion from `dash-separated-words`
+ * to camelCase, rather than searching for a property with the same name.
+ */
+// TODO(jmesserly): fix the dash-separated-words issue. Polymer uses lowercase.
+class PolymerElement extends CustomElement with _EventsMixin {
+ // This is a partial port of:
+ // https://github.com/Polymer/polymer/blob/stable/src/attrs.js
+ // https://github.com/Polymer/polymer/blob/stable/src/bindProperties.js
+ // https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
+ // https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
+ // TODO(jmesserly): we still need to port more of the functionality
+
+ /// The one syntax to rule them all.
+ static BindingDelegate _polymerSyntax = new PolymerExpressions();
+ // TODO(sigmund): delete. The next line is only added to avoid warnings from
+ // the analyzer (see http://dartbug.com/11672)
+ Element get host => super.host;
+
+ bool get applyAuthorStyles => false;
+ bool get resetStyleInheritance => false;
+
+ /**
+ * The declaration of this polymer-element, used to extract template contents
+ * and other information.
+ */
+ static Map<String, Element> _declarations = {};
+ static Element getDeclaration(String localName) {
+ if (localName == null) return null;
+ var element = _declarations[localName];
+ if (element == null) {
+ element = document.query('polymer-element[name="$localName"]');
+ _declarations[localName] = element;
+ }
+ return element;
+ }
+
+ Map<String, PathObserver> _publishedAttrs;
+ Map<String, StreamSubscription> _bindings;
+ final List<String> _localNames = [];
+
+ void _initialize(String localName) {
+ if (localName == null) return;
+
+ var declaration = getDeclaration(localName);
+ if (declaration == null) return;
+
+ if (declaration.attributes['extends'] != null) {
+ var base = declaration.attributes['extends'];
+ // Skip normal tags, only initialize parent custom elements.
+ if (base.contains('-')) _initialize(base);
+ }
+
+ _parseHostEvents(declaration);
+ _parseLocalEvents(declaration);
+ _publishAttributes(declaration);
+ _localNames.add(localName);
+ }
+
+ void _publishAttributes(elementElement) {
+ _bindings = {};
+ _publishedAttrs = {};
+
+ var attrs = elementElement.attributes['attributes'];
+ if (attrs != null) {
+ // attributes='a b c' or attributes='a,b,c'
+ for (var name in attrs.split(attrs.contains(',') ? ',' : ' ')) {
+ name = name.trim();
+
+ // TODO(jmesserly): PathObserver is overkill here; it helps avoid
+ // "new Symbol" and other mirrors-related warnings.
+ _publishedAttrs[name] = new PathObserver(this, toCamelCase(name));
+ }
+ }
+ }
+
+ void created() {
+ // TODO(jmesserly): this breaks until we get some kind of type conversion.
+ // _publishedAttrs.forEach((name, propObserver) {
+ // var value = attributes[name];
+ // if (value != null) propObserver.value = value;
+ // });
+ _initShadowRoot();
+ _addHostListeners();
+ }
+
+ /**
+ * Creates the document fragment to use for each instance of the custom
+ * element, given the `<template>` node. By default this is equivalent to:
+ *
+ * template.createInstance(this, polymerSyntax);
+ *
+ * Where polymerSyntax is a singleton `PolymerExpressions` instance from the
+ * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions)
+ * package.
+ *
+ * You can override this method to change the instantiation behavior of the
+ * template, for example to use a different data-binding syntax.
+ */
+ DocumentFragment instanceTemplate(Element template) =>
+ template.createInstance(this, _polymerSyntax);
+
+ void _initShadowRoot() {
+ for (var localName in _localNames) {
+ var declaration = getDeclaration(localName);
+ var root = createShadowRoot(localName);
+ _addInstanceListeners(root, localName);
+
+ root.applyAuthorStyles = applyAuthorStyles;
+ root.resetStyleInheritance = resetStyleInheritance;
+
+ var templateNode = declaration.children.firstWhere(
+ (n) => n.localName == 'template', orElse: () => null);
+ if (templateNode == null) return;
+
+ // Create the contents of the element's ShadowRoot, and add them.
+ root.nodes.add(instanceTemplate(templateNode));
+
+ var extendsName = declaration.attributes['extends'];
+ _shimCss(root, localName, extendsName);
+ }
+ }
+
+ NodeBinding createBinding(String name, model, String path) {
+ var propObserver = _publishedAttrs[name];
+ if (propObserver != null) {
+ return new _PolymerBinding(this, name, model, path, propObserver);
+ }
+ return super.createBinding(name, model, path);
+ }
+
+ /**
+ * Using Polymer's platform/src/ShadowCSS.js passing the style tag's content.
+ */
+ void _shimCss(ShadowRoot root, String localName, String extendsName) {
+ // TODO(terry): Need to detect if ShadowCSS.js has been loaded. Under
+ // Dartium this wouldn't exist. However, dart:js isn't robust
+ // to use to detect in both Dartium and dart2js if Platform is
+ // defined. Instead in Dartium it throws an exception but in
+ // dart2js it works enough to know if Platform is defined (just
+ // can't be used for further derefs). This bug is described
+ // https://code.google.com/p/dart/issues/detail?id=12548
+ // When fixed only use dart:js. This is necessary under
+ // Dartium (no compile) we want to run w/o the JS polyfill.
+ try {
+ if (dartJs.context["Platform"] == null) { return; }
+ } on NoSuchMethodError catch (e) { return; }
+
+ var platform = js.context["Platform"];
+ if (platform == null) return;
+ var shadowCss = platform.ShadowCSS;
+ if (shadowCss == null) return;
+
+ // TODO(terry): Remove calls to shimShadowDOMStyling2 and replace with
+ // shimShadowDOMStyling when we support unwrapping dart:html
+ // Element to a JS DOM node.
+ var shimShadowDOMStyling2 = shadowCss.shimShadowDOMStyling2;
+ if (shimShadowDOMStyling2 == null) return;
+ var style = root.query('style');
+ if (style == null) return;
+ var scopedCSS = shimShadowDOMStyling2(style.text, localName);
+
+ // TODO(terry): Remove when shimShadowDOMStyling is called we don't need to
+ // replace original CSS with scoped CSS shimShadowDOMStyling
+ // does that.
+ style.text = scopedCSS;
+ }
+}
+
+class _PolymerBinding extends NodeBinding {
+ final PathObserver _publishedAttr;
+
+ _PolymerBinding(node, property, model, path, PathObserver this._publishedAttr)
+ : super(node, property, model, path);
+
+ void boundValueChanged(newValue) {
+ _publishedAttr.value = newValue;
+ }
+}
+
+/**
+ * Polymer features to handle the syntactic sugar on-* to declare to
+ * automatically map event handlers to instance methods of the [PolymerElement].
+ * This mixin is a port of:
+ * https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
+ * https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
+ */
+abstract class _EventsMixin {
+ // TODO(sigmund): implement the Dart equivalent of 'inheritDelegates'
+ // Notes about differences in the implementation below:
+ // - _templateDelegates: polymer stores the template delegates directly on
+ // the template node (see in parseLocalEvents: 't.delegates = {}'). Here we
+ // simply use a separate map, where keys are the name of the
+ // custom-element.
+ // - _listenLocal we return true/false and propagate that up, JS
+ // implementation does't forward the return value.
+ // - we don't keep the side-table (weak hash map) of unhandled events (see
+ // handleIfNotHandled)
+ // - we don't use event.type to dispatch events, instead we save the event
+ // name with the event listeners. We do so to avoid translating back and
+ // forth between Dom and Dart event names.
+
+ // ---------------------------------------------------------------------------
+ // The following section was ported from:
+ // https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
+ // ---------------------------------------------------------------------------
+
+ /** Maps event names and their associated method in the element class. */
+ final Map<String, String> _delegates = {};
+
+ /** Expected events per element node. */
+ // TODO(sigmund): investigate whether we need more than 1 set of local events
+ // per element (why does the js implementation stores 1 per template node?)
+ final Map<String, Set<String>> _templateDelegates =
+ new Map<String, Set<String>>();
+
+ /** [host] is needed by this mixin, but not defined here. */
+ Element get host;
+
+ /** Attribute prefix used for declarative event handlers. */
+ static const _eventPrefix = 'on-';
+
+ /** Whether an attribute declares an event. */
+ static bool _isEvent(String attr) => attr.startsWith(_eventPrefix);
+
+ /** Extracts events from the element tag attributes. */
+ void _parseHostEvents(elementElement) {
+ for (var attr in elementElement.attributes.keys.where(_isEvent)) {
+ _delegates[toCamelCase(attr)] = elementElement.attributes[attr];
+ }
+ }
+
+ /** Extracts events under the element's <template>. */
+ void _parseLocalEvents(elementElement) {
+ var name = elementElement.attributes["name"];
+ if (name == null) return;
+ var events = null;
+ for (var template in elementElement.queryAll('template')) {
+ var content = template.content;
+ if (content != null) {
+ for (var child in content.children) {
+ events = _accumulateEvents(child, events);
+ }
+ }
+ }
+ if (events != null) {
+ _templateDelegates[name] = events;
+ }
+ }
+
+ /** Returns all events names listened by [element] and it's children. */
+ static Set<String> _accumulateEvents(Element element, [Set<String> events]) {
+ events = events == null ? new Set<String>() : events;
+
+ // from: accumulateAttributeEvents, accumulateEvent
+ events.addAll(element.attributes.keys.where(_isEvent).map(toCamelCase));
+
+ // from: accumulateChildEvents
+ for (var child in element.children) {
+ _accumulateEvents(child, events);
+ }
+
+ // from: accumulateTemplatedEvents
+ if (element.isTemplate) {
+ var content = element.content;
+ if (content != null) {
+ for (var child in content.children) {
+ _accumulateEvents(child, events);
+ }
+ }
+ }
+ return events;
+ }
+
+ // ---------------------------------------------------------------------------
+ // The following section was ported from:
+ // https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
+ // ---------------------------------------------------------------------------
+
+ /** Attaches event listeners on the [host] element. */
+ void _addHostListeners() {
+ for (var eventName in _delegates.keys) {
+ _addNodeListener(host, eventName,
+ (e) => _hostEventListener(eventName, e));
+ }
+ }
+
+ void _addNodeListener(node, String onEvent, Function listener) {
+ // If [node] is an element (typically when listening for host events) we
+ // use directly the '.onFoo' event stream of the element instance.
+ if (node is Element) {
+ reflect(node).getField(new Symbol(onEvent)).reflectee.listen(listener);
+ return;
+ }
+
+ // When [node] is not an element, most commonly when [node] is the
+ // shadow-root of the polymer-element, we find the appropriate static event
+ // stream providers and attach it to [node].
+ var eventProvider = _eventStreamProviders[onEvent];
+ if (eventProvider != null) {
+ eventProvider.forTarget(node).listen(listener);
+ return;
+ }
+
+ // When no provider is available, mainly because of custom-events, we use
+ // the underlying event listeners from the DOM.
+ var eventName = onEvent.substring(2).toLowerCase(); // onOneTwo => onetwo
+ // Most events names in Dart match those in JS in lowercase except for some
+ // few events listed in this map. We expect these cases to be handled above,
+ // but just in case we include them as a safety net here.
+ var jsNameFixes = const {
+ 'animationend': 'webkitAnimationEnd',
+ 'animationiteration': 'webkitAnimationIteration',
+ 'animationstart': 'webkitAnimationStart',
+ 'doubleclick': 'dblclick',
+ 'fullscreenchange': 'webkitfullscreenchange',
+ 'fullscreenerror': 'webkitfullscreenerror',
+ 'keyadded': 'webkitkeyadded',
+ 'keyerror': 'webkitkeyerror',
+ 'keymessage': 'webkitkeymessage',
+ 'needkey': 'webkitneedkey',
+ 'speechchange': 'webkitSpeechChange',
+ };
+ var fixedName = jsNameFixes[eventName];
+ node.on[fixedName != null ? fixedName : eventName].listen(listener);
+ }
+
+ void _addInstanceListeners(ShadowRoot root, String elementName) {
+ var events = _templateDelegates[elementName];
+ if (events == null) return;
+ for (var eventName in events) {
+ _addNodeListener(root, eventName,
+ (e) => _instanceEventListener(eventName, e));
+ }
+ }
+
+ void _hostEventListener(String eventName, Event event) {
+ var method = _delegates[eventName];
+ if (event.bubbles && method != null) {
+ _dispatchMethod(this, method, event, host);
+ }
+ }
+
+ void _dispatchMethod(Object receiver, String methodName, Event event,
+ Node target) {
+ var detail = event is CustomEvent ? (event as CustomEvent).detail : null;
+ var args = [event, detail, target];
+
+ var method = new Symbol(methodName);
+ // TODO(sigmund): consider making event listeners list all arguments
+ // explicitly. Unless VM mirrors are optimized first, this reflectClass call
+ // will be expensive once custom elements extend directly from Element (see
+ // dartbug.com/11108).
+ var methodDecl = reflectClass(receiver.runtimeType).methods[method];
+ if (methodDecl != null) {
+ // This will either truncate the argument list or extend it with extra
+ // null arguments, so it will match the signature.
+ // TODO(sigmund): consider accepting optional arguments when we can tell
+ // them appart from named arguments (see http://dartbug.com/11334)
+ args.length = methodDecl.parameters.where((p) => !p.isOptional).length;
+ }
+ reflect(receiver).invoke(method, args);
+ performMicrotaskCheckpoint();
+ }
+
+ bool _instanceEventListener(String eventName, Event event) {
+ if (event.bubbles) {
+ if (event.path == null || !ShadowRoot.supported) {
+ return _listenLocalNoEventPath(eventName, event);
+ } else {
+ return _listenLocal(eventName, event);
+ }
+ }
+ return false;
+ }
+
+ bool _listenLocal(String eventName, Event event) {
+ var controller = null;
+ for (var target in event.path) {
+ // if we hit host, stop
+ if (target == host) return true;
+
+ // find a controller for the target, unless we already found `host`
+ // as a controller
+ controller = (controller == host) ? controller : _findController(target);
+
+ // if we have a controller, dispatch the event, and stop if the handler
+ // returns true
+ if (controller != null
+ && handleEvent(controller, eventName, event, target)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // TODO(sorvell): remove when ShadowDOM polyfill supports event path.
+ // Note that _findController will not return the expected controller when the
+ // event target is a distributed node. This is because we cannot traverse
+ // from a composed node to a node in shadowRoot.
+ // This will be addressed via an event path api
+ // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066
+ bool _listenLocalNoEventPath(String eventName, Event event) {
+ var target = event.target;
+ var controller = null;
+ while (target != null && target != host) {
+ controller = (controller == host) ? controller : _findController(target);
+ if (controller != null
+ && handleEvent(controller, eventName, event, target)) {
+ return true;
+ }
+ target = target.parent;
+ }
+ return false;
+ }
+
+ // TODO(sigmund): investigate if this implementation is correct. Polymer looks
+ // up the shadow-root that contains [node] and uses a weak-hashmap to find the
+ // host associated with that root. This implementation assumes that the
+ // [node] is under [host]'s shadow-root.
+ Element _findController(Node node) => host.xtag;
+
+ bool handleEvent(
+ Element controller, String eventName, Event event, Element element) {
+ // Note: local events are listened only in the shadow root. This dynamic
+ // lookup is used to distinguish determine whether the target actually has a
+ // listener, and if so, to determine lazily what's the target method.
+ var methodName = element.attributes[toHyphenedName(eventName)];
+ if (methodName != null) {
+ _dispatchMethod(controller, methodName, event, element);
+ }
+ return event.bubbles;
+ }
+}
+
+
+/** Event stream providers per event name. */
+// TODO(sigmund): after dartbug.com/11108 is fixed, consider eliminating this
+// table and using reflection instead.
+const Map<String, EventStreamProvider> _eventStreamProviders = const {
+ 'onMouseWheel': Element.mouseWheelEvent,
+ 'onTransitionEnd': Element.transitionEndEvent,
+ 'onAbort': Element.abortEvent,
+ 'onBeforeCopy': Element.beforeCopyEvent,
+ 'onBeforeCut': Element.beforeCutEvent,
+ 'onBeforePaste': Element.beforePasteEvent,
+ 'onBlur': Element.blurEvent,
+ 'onChange': Element.changeEvent,
+ 'onClick': Element.clickEvent,
+ 'onContextMenu': Element.contextMenuEvent,
+ 'onCopy': Element.copyEvent,
+ 'onCut': Element.cutEvent,
+ 'onDoubleClick': Element.doubleClickEvent,
+ 'onDrag': Element.dragEvent,
+ 'onDragEnd': Element.dragEndEvent,
+ 'onDragEnter': Element.dragEnterEvent,
+ 'onDragLeave': Element.dragLeaveEvent,
+ 'onDragOver': Element.dragOverEvent,
+ 'onDragStart': Element.dragStartEvent,
+ 'onDrop': Element.dropEvent,
+ 'onError': Element.errorEvent,
+ 'onFocus': Element.focusEvent,
+ 'onInput': Element.inputEvent,
+ 'onInvalid': Element.invalidEvent,
+ 'onKeyDown': Element.keyDownEvent,
+ 'onKeyPress': Element.keyPressEvent,
+ 'onKeyUp': Element.keyUpEvent,
+ 'onLoad': Element.loadEvent,
+ 'onMouseDown': Element.mouseDownEvent,
+ 'onMouseMove': Element.mouseMoveEvent,
+ 'onMouseOut': Element.mouseOutEvent,
+ 'onMouseOver': Element.mouseOverEvent,
+ 'onMouseUp': Element.mouseUpEvent,
+ 'onPaste': Element.pasteEvent,
+ 'onReset': Element.resetEvent,
+ 'onScroll': Element.scrollEvent,
+ 'onSearch': Element.searchEvent,
+ 'onSelect': Element.selectEvent,
+ 'onSelectStart': Element.selectStartEvent,
+ 'onSubmit': Element.submitEvent,
+ 'onTouchCancel': Element.touchCancelEvent,
+ 'onTouchEnd': Element.touchEndEvent,
+ 'onTouchEnter': Element.touchEnterEvent,
+ 'onTouchLeave': Element.touchLeaveEvent,
+ 'onTouchMove': Element.touchMoveEvent,
+ 'onTouchStart': Element.touchStartEvent,
+ 'onFullscreenChange': Element.fullscreenChangeEvent,
+ 'onFullscreenError': Element.fullscreenErrorEvent,
+ 'onAutocomplete': FormElement.autocompleteEvent,
+ 'onAutocompleteError': FormElement.autocompleteErrorEvent,
+ 'onSpeechChange': InputElement.speechChangeEvent,
+ 'onCanPlay': MediaElement.canPlayEvent,
+ 'onCanPlayThrough': MediaElement.canPlayThroughEvent,
+ 'onDurationChange': MediaElement.durationChangeEvent,
+ 'onEmptied': MediaElement.emptiedEvent,
+ 'onEnded': MediaElement.endedEvent,
+ 'onLoadStart': MediaElement.loadStartEvent,
+ 'onLoadedData': MediaElement.loadedDataEvent,
+ 'onLoadedMetadata': MediaElement.loadedMetadataEvent,
+ 'onPause': MediaElement.pauseEvent,
+ 'onPlay': MediaElement.playEvent,
+ 'onPlaying': MediaElement.playingEvent,
+ 'onProgress': MediaElement.progressEvent,
+ 'onRateChange': MediaElement.rateChangeEvent,
+ 'onSeeked': MediaElement.seekedEvent,
+ 'onSeeking': MediaElement.seekingEvent,
+ 'onShow': MediaElement.showEvent,
+ 'onStalled': MediaElement.stalledEvent,
+ 'onSuspend': MediaElement.suspendEvent,
+ 'onTimeUpdate': MediaElement.timeUpdateEvent,
+ 'onVolumeChange': MediaElement.volumeChangeEvent,
+ 'onWaiting': MediaElement.waitingEvent,
+ 'onKeyAdded': MediaElement.keyAddedEvent,
+ 'onKeyError': MediaElement.keyErrorEvent,
+ 'onKeyMessage': MediaElement.keyMessageEvent,
+ 'onNeedKey': MediaElement.needKeyEvent,
+ 'onWebGlContextLost': CanvasElement.webGlContextLostEvent,
+ 'onWebGlContextRestored': CanvasElement.webGlContextRestoredEvent,
+ 'onPointerLockChange': Document.pointerLockChangeEvent,
+ 'onPointerLockError': Document.pointerLockErrorEvent,
+ 'onReadyStateChange': Document.readyStateChangeEvent,
+ 'onSelectionChange': Document.selectionChangeEvent,
+ 'onSecurityPolicyViolation': Document.securityPolicyViolationEvent,
+};
diff --git a/pkg/polymer/lib/safe_html.dart b/pkg/polymer/lib/safe_html.dart
new file mode 100644
index 0000000..c15dd5c
--- /dev/null
+++ b/pkg/polymer/lib/safe_html.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2011, 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.
+
+// TODO(sigmund): move this library to a shared package? or make part of
+// dart:html?
+library polymer.safe_html;
+
+/** Declares a string that is a well-formed HTML fragment. */
+class SafeHtml {
+
+ /** Underlying html string. */
+ final String _html;
+
+ // TODO(sigmund): provide a constructor that does html validation
+ SafeHtml.unsafe(this._html);
+
+ String toString() => _html;
+
+ operator ==(other) => other is SafeHtml && _html == other._html;
+ int get hashCode => _html.hashCode;
+}
+
+/**
+ * Declares a string that is safe to use in a Uri attribute, such as `<a href=`,
+ * to avoid cross-site scripting (XSS) attacks.
+ */
+class SafeUri {
+ final String _uri;
+
+ // TODO(sigmund): provide a constructor that takes or creates a Uri and
+ // validates that it is safe (not a javascript: scheme, for example)
+ SafeUri.unsafe(this._uri);
+
+ String toString() => _uri;
+
+ operator ==(other) => other is SafeUri && _uri == other._uri;
+ int get hashCode => _uri.hashCode;
+}
diff --git a/pkg/polymer/lib/src/analyzer.dart b/pkg/polymer/lib/src/analyzer.dart
new file mode 100644
index 0000000..193e139
--- /dev/null
+++ b/pkg/polymer/lib/src/analyzer.dart
@@ -0,0 +1,566 @@
+// 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.
+
+/**
+ * Part of the template compilation that concerns with extracting information
+ * from the HTML parse tree.
+ */
+library analyzer;
+
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/dom_parsing.dart';
+import 'package:source_maps/span.dart' hide SourceFile;
+
+import 'custom_tag_name.dart';
+import 'dart_parser.dart' show parseDartCode;
+import 'files.dart';
+import 'info.dart';
+import 'messages.dart';
+import 'summary.dart';
+
+/**
+ * Finds custom elements in this file and the list of referenced files with
+ * component declarations. This is the first pass of analysis on a file.
+ *
+ * Adds emitted error/warning messages to [messages], if [messages] is
+ * supplied.
+ */
+FileInfo analyzeDefinitions(GlobalInfo global, UrlInfo inputUrl,
+ Document document, String packageRoot,
+ Messages messages, {bool isEntryPoint: false}) {
+ var result = new FileInfo(inputUrl, isEntryPoint);
+ var loader = new _ElementLoader(global, result, packageRoot, messages);
+ loader.visit(document);
+ return result;
+}
+
+/**
+ * Extract relevant information from all files found from the root document.
+ *
+ * Adds emitted error/warning messages to [messages], if [messages] is
+ * supplied.
+ */
+void analyzeFile(SourceFile file, Map<String, FileInfo> info,
+ Iterator<int> uniqueIds, GlobalInfo global,
+ Messages messages, emulateScopedCss) {
+ var fileInfo = info[file.path];
+ var analyzer = new _Analyzer(fileInfo, uniqueIds, global, messages,
+ emulateScopedCss);
+ analyzer._normalize(fileInfo, info);
+ analyzer.visit(file.document);
+}
+
+
+/** A visitor that walks the HTML to extract all the relevant information. */
+class _Analyzer extends TreeVisitor {
+ final FileInfo _fileInfo;
+ LibraryInfo _currentInfo;
+ Iterator<int> _uniqueIds;
+ GlobalInfo _global;
+ Messages _messages;
+
+ int _generatedClassNumber = 0;
+
+ /**
+ * Whether to keep indentation spaces. Break lines and indentation spaces
+ * within templates are preserved in HTML. When users specify the attribute
+ * 'indentation="remove"' on a template tag, we'll trim those indentation
+ * spaces that occur within that tag and its decendants. If any decendant
+ * specifies 'indentation="preserve"', then we'll switch back to the normal
+ * behavior.
+ */
+ bool _keepIndentationSpaces = true;
+
+ final bool _emulateScopedCss;
+
+ _Analyzer(this._fileInfo, this._uniqueIds, this._global, this._messages,
+ this._emulateScopedCss) {
+ _currentInfo = _fileInfo;
+ }
+
+ void visitElement(Element node) {
+ if (node.tagName == 'script') {
+ // We already extracted script tags in previous phase.
+ return;
+ }
+
+ if (node.tagName == 'style') {
+ // We've already parsed the CSS.
+ // If this is a component remove the style node.
+ if (_currentInfo is ComponentInfo && _emulateScopedCss) node.remove();
+ return;
+ }
+
+ _bindCustomElement(node);
+
+ var lastInfo = _currentInfo;
+ if (node.tagName == 'polymer-element') {
+ // If element is invalid _ElementLoader already reported an error, but
+ // we skip the body of the element here.
+ var name = node.attributes['name'];
+ if (name == null) return;
+
+ ComponentInfo component = _fileInfo.components[name];
+ if (component == null) return;
+
+ _analyzeComponent(component);
+
+ _currentInfo = component;
+
+ // Remove the <element> tag from the tree
+ node.remove();
+ }
+
+ node.attributes.forEach((name, value) {
+ if (name.startsWith('on')) {
+ _validateEventHandler(node, name, value);
+ } else if (name == 'pseudo' && _currentInfo is ComponentInfo) {
+ // Any component's custom pseudo-element(s) defined?
+ _processPseudoAttribute(node, value.split(' '));
+ }
+ });
+
+ var keepSpaces = _keepIndentationSpaces;
+ if (node.tagName == 'template' &&
+ node.attributes.containsKey('indentation')) {
+ var value = node.attributes['indentation'];
+ if (value != 'remove' && value != 'preserve') {
+ _messages.warning(
+ "Invalid value for 'indentation' ($value). By default we preserve "
+ "the indentation. Valid values are either 'remove' or 'preserve'.",
+ node.sourceSpan);
+ }
+ _keepIndentationSpaces = value != 'remove';
+ }
+
+ // Invoke super to visit children.
+ super.visitElement(node);
+
+ _keepIndentationSpaces = keepSpaces;
+ _currentInfo = lastInfo;
+
+ if (node.tagName == 'body' || node.parent == null) {
+ _fileInfo.body = node;
+ }
+ }
+
+ void _analyzeComponent(ComponentInfo component) {
+ var baseTag = component.extendsTag;
+ component.extendsComponent = baseTag == null ? null
+ : _fileInfo.components[baseTag];
+ if (component.extendsComponent == null && isCustomTag(baseTag)) {
+ _messages.warning(
+ 'custom element with tag name ${component.extendsTag} not found.',
+ component.element.sourceSpan);
+ }
+
+ // Now that the component's code has been loaded, we can validate that the
+ // class exists.
+ component.findClassDeclaration(_messages);
+ }
+
+ void _bindCustomElement(Element node) {
+ // <fancy-button>
+ var component = _fileInfo.components[node.tagName];
+ if (component == null) {
+ // TODO(jmesserly): warn for unknown element tags?
+
+ // <button is="fancy-button">
+ var componentName = node.attributes['is'];
+ if (componentName != null) {
+ component = _fileInfo.components[componentName];
+ } else if (isCustomTag(node.tagName)) {
+ componentName = node.tagName;
+ }
+ if (component == null && componentName != null &&
+ componentName != 'polymer-element') {
+ _messages.warning(
+ 'custom element with tag name $componentName not found.',
+ node.sourceSpan);
+ }
+ }
+
+ if (component != null) {
+ if (!component.hasConflict) {
+ _currentInfo.usedComponents[component] = true;
+ }
+
+ var baseTag = component.baseExtendsTag;
+ var nodeTag = node.tagName;
+ var hasIsAttribute = node.attributes.containsKey('is');
+
+ if (baseTag != null && !hasIsAttribute) {
+ _messages.warning(
+ 'custom element "${component.tagName}" extends from "$baseTag", but'
+ ' this tag will not include the default properties of "$baseTag". '
+ 'To fix this, either write this tag as <$baseTag '
+ 'is="${component.tagName}"> or remove the "extends" attribute from '
+ 'the custom element declaration.', node.sourceSpan);
+ } else if (hasIsAttribute) {
+ if (baseTag == null) {
+ _messages.warning(
+ 'custom element "${component.tagName}" doesn\'t declare any type '
+ 'extensions. To fix this, either rewrite this tag as '
+ '<${component.tagName}> or add \'extends="$nodeTag"\' to '
+ 'the custom element declaration.', node.sourceSpan);
+ } else if (baseTag != nodeTag) {
+ _messages.warning(
+ 'custom element "${component.tagName}" extends from "$baseTag". '
+ 'Did you mean to write <$baseTag is="${component.tagName}">?',
+ node.sourceSpan);
+ }
+ }
+ }
+ }
+
+ void _processPseudoAttribute(Node node, List<String> values) {
+ List mangledValues = [];
+ for (var pseudoElement in values) {
+ if (_global.pseudoElements.containsKey(pseudoElement)) continue;
+
+ _uniqueIds.moveNext();
+ var newValue = "${pseudoElement}_${_uniqueIds.current}";
+ _global.pseudoElements[pseudoElement] = newValue;
+ // Mangled name of pseudo-element.
+ mangledValues.add(newValue);
+
+ if (!pseudoElement.startsWith('x-')) {
+ // TODO(terry): The name must start with x- otherwise it's not a custom
+ // pseudo-element. May want to relax since components no
+ // longer need to start with x-. See isse #509 on
+ // pseudo-element prefix.
+ _messages.warning("Custom pseudo-element must be prefixed with 'x-'.",
+ node.sourceSpan);
+ }
+ }
+
+ // Update the pseudo attribute with the new mangled names.
+ node.attributes['pseudo'] = mangledValues.join(' ');
+ }
+
+ /**
+ * Support for inline event handlers that take expressions.
+ * For example: `on-double-click=myHandler($event, todo)`.
+ */
+ void _validateEventHandler(Element node, String name, String value) {
+ if (!name.startsWith('on-')) {
+ // TODO(jmesserly): do we need an option to suppress this warning?
+ _messages.warning('Event handler $name will be interpreted as an inline '
+ 'JavaScript event handler. Use the form '
+ 'on-event-name="handlerName" if you want a Dart handler '
+ 'that will automatically update the UI based on model changes.',
+ node.sourceSpan);
+ }
+
+ if (value.contains('.') || value.contains('(')) {
+ // TODO(sigmund): should we allow more if we use fancy-syntax?
+ _messages.warning('Invalid event handler body "$value". Declare a method '
+ 'in your custom element "void handlerName(event, detail, target)" '
+ 'and use the form on-event-name="handlerName".',
+ node.sourceSpan);
+ }
+ }
+
+ /**
+ * Normalizes references in [info]. On the [analyzeDefinitions] phase, the
+ * analyzer extracted names of files and components. Here we link those names
+ * to actual info classes. In particular:
+ * * we initialize the [FileInfo.components] map in [info] by importing all
+ * [declaredComponents],
+ * * we scan all [info.componentLinks] and import their
+ * [info.declaredComponents], using [files] to map the href to the file
+ * info. Names in [info] will shadow names from imported files.
+ * * we fill [LibraryInfo.externalCode] on each component declared in
+ * [info].
+ */
+ void _normalize(FileInfo info, Map<String, FileInfo> files) {
+ _attachExtenalScript(info, files);
+
+ for (var component in info.declaredComponents) {
+ _addComponent(info, component);
+ _attachExtenalScript(component, files);
+ }
+
+ for (var link in info.componentLinks) {
+ var file = files[link.resolvedPath];
+ // We already issued an error for missing files.
+ if (file == null) continue;
+ file.declaredComponents.forEach((c) => _addComponent(info, c));
+ }
+ }
+
+ /**
+ * Stores a direct reference in [info] to a dart source file that was loaded
+ * in a script tag with the 'src' attribute.
+ */
+ void _attachExtenalScript(LibraryInfo info, Map<String, FileInfo> files) {
+ var externalFile = info.externalFile;
+ if (externalFile != null) {
+ info.externalCode = files[externalFile.resolvedPath];
+ if (info.externalCode != null) info.externalCode.htmlFile = info;
+ }
+ }
+
+ /** Adds a component's tag name to the names in scope for [fileInfo]. */
+ void _addComponent(FileInfo fileInfo, ComponentSummary component) {
+ var existing = fileInfo.components[component.tagName];
+ if (existing != null) {
+ if (existing == component) {
+ // This is the same exact component as the existing one.
+ return;
+ }
+
+ if (existing is ComponentInfo && component is! ComponentInfo) {
+ // Components declared in [fileInfo] shadow component names declared in
+ // imported files.
+ return;
+ }
+
+ if (existing.hasConflict) {
+ // No need to report a second error for the same name.
+ return;
+ }
+
+ existing.hasConflict = true;
+
+ if (component is ComponentInfo) {
+ _messages.error('duplicate custom element definition for '
+ '"${component.tagName}".', existing.sourceSpan);
+ _messages.error('duplicate custom element definition for '
+ '"${component.tagName}" (second location).', component.sourceSpan);
+ } else {
+ _messages.error('imported duplicate custom element definitions '
+ 'for "${component.tagName}".', existing.sourceSpan);
+ _messages.error('imported duplicate custom element definitions '
+ 'for "${component.tagName}" (second location).',
+ component.sourceSpan);
+ }
+ } else {
+ fileInfo.components[component.tagName] = component;
+ }
+ }
+}
+
+/** A visitor that finds `<link rel="import">` and `<element>` tags. */
+class _ElementLoader extends TreeVisitor {
+ final GlobalInfo _global;
+ final FileInfo _fileInfo;
+ LibraryInfo _currentInfo;
+ String _packageRoot;
+ bool _inHead = false;
+ Messages _messages;
+
+ /**
+ * Adds emitted warning/error messages to [_messages]. [_messages]
+ * must not be null.
+ */
+ _ElementLoader(this._global, this._fileInfo, this._packageRoot,
+ this._messages) {
+ _currentInfo = _fileInfo;
+ }
+
+ void visitElement(Element node) {
+ switch (node.tagName) {
+ case 'link': visitLinkElement(node); break;
+ case 'element':
+ _messages.warning('<element> elements are not supported, use'
+ ' <polymer-element> instead', node.sourceSpan);
+ break;
+ case 'polymer-element':
+ visitElementElement(node);
+ break;
+ case 'script': visitScriptElement(node); break;
+ case 'head':
+ var savedInHead = _inHead;
+ _inHead = true;
+ super.visitElement(node);
+ _inHead = savedInHead;
+ break;
+ default: super.visitElement(node); break;
+ }
+ }
+
+ /**
+ * Process `link rel="import"` as specified in:
+ * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/components/index.html#link-type-component>
+ */
+ void visitLinkElement(Element node) {
+ var rel = node.attributes['rel'];
+ if (rel != 'component' && rel != 'components' &&
+ rel != 'import' && rel != 'stylesheet') return;
+
+ if (!_inHead) {
+ _messages.warning('link rel="$rel" only valid in '
+ 'head.', node.sourceSpan);
+ return;
+ }
+
+ if (rel == 'component' || rel == 'components') {
+ _messages.warning('import syntax is changing, use '
+ 'rel="import" instead of rel="$rel".', node.sourceSpan);
+ }
+
+ var href = node.attributes['href'];
+ if (href == null || href == '') {
+ _messages.warning('link rel="$rel" missing href.',
+ node.sourceSpan);
+ return;
+ }
+
+ bool isStyleSheet = rel == 'stylesheet';
+ var urlInfo = UrlInfo.resolve(href, _fileInfo.inputUrl, node.sourceSpan,
+ _packageRoot, _messages, ignoreAbsolute: isStyleSheet);
+ if (urlInfo == null) return;
+ if (isStyleSheet) {
+ _fileInfo.styleSheetHrefs.add(urlInfo);
+ } else {
+ _fileInfo.componentLinks.add(urlInfo);
+ }
+ }
+
+ void visitElementElement(Element node) {
+ // TODO(jmesserly): what do we do in this case? It seems like an <element>
+ // inside a Shadow DOM should be scoped to that <template> tag, and not
+ // visible from the outside.
+ if (_currentInfo is ComponentInfo) {
+ _messages.error('Nested component definitions are not yet supported.',
+ node.sourceSpan);
+ return;
+ }
+
+ var tagName = node.attributes['name'];
+ var extendsTag = node.attributes['extends'];
+
+ if (tagName == null) {
+ _messages.error('Missing tag name of the component. Please include an '
+ 'attribute like \'name="your-tag-name"\'.',
+ node.sourceSpan);
+ return;
+ }
+
+ var component = new ComponentInfo(node, _fileInfo, tagName, extendsTag);
+ _fileInfo.declaredComponents.add(component);
+ _addComponent(component);
+
+ var lastInfo = _currentInfo;
+ _currentInfo = component;
+ super.visitElement(node);
+ _currentInfo = lastInfo;
+ }
+
+ /** Adds a component's tag name to the global list. */
+ void _addComponent(ComponentInfo component) {
+ var existing = _global.components[component.tagName];
+ if (existing != null) {
+ if (existing.hasConflict) {
+ // No need to report a second error for the same name.
+ return;
+ }
+
+ existing.hasConflict = true;
+
+ _messages.error('duplicate custom element definition for '
+ '"${component.tagName}".', existing.sourceSpan);
+ _messages.error('duplicate custom element definition for '
+ '"${component.tagName}" (second location).', component.sourceSpan);
+ } else {
+ _global.components[component.tagName] = component;
+ }
+ }
+
+ void visitScriptElement(Element node) {
+ var scriptType = node.attributes['type'];
+ var src = node.attributes["src"];
+
+ if (scriptType == null) {
+ // Note: in html5 leaving off type= is fine, but it defaults to
+ // text/javascript. Because this might be a common error, we warn about it
+ // in two cases:
+ // * an inline script tag in a web component
+ // * a script src= if the src file ends in .dart (component or not)
+ //
+ // The hope is that neither of these cases should break existing valid
+ // code, but that they'll help component authors avoid having their Dart
+ // code accidentally interpreted as JavaScript by the browser.
+ if (src == null && _currentInfo is ComponentInfo) {
+ _messages.warning('script tag in component with no type will '
+ 'be treated as JavaScript. Did you forget type="application/dart"?',
+ node.sourceSpan);
+ }
+ if (src != null && src.endsWith('.dart')) {
+ _messages.warning('script tag with .dart source file but no type will '
+ 'be treated as JavaScript. Did you forget type="application/dart"?',
+ node.sourceSpan);
+ }
+ return;
+ }
+
+ if (scriptType != 'application/dart') {
+ if (_currentInfo is ComponentInfo) {
+ // TODO(jmesserly): this warning should not be here, but our compiler
+ // does the wrong thing and it could cause surprising behavior, so let
+ // the user know! See issue #340 for more info.
+ // What we should be doing: leave JS component untouched by compiler.
+ _messages.warning('our custom element implementation does not support '
+ 'JavaScript components yet. If this is affecting you please let us '
+ 'know at https://github.com/dart-lang/web-ui/issues/340.',
+ node.sourceSpan);
+ }
+
+ return;
+ }
+
+ if (src != null) {
+ if (!src.endsWith('.dart')) {
+ _messages.warning('"application/dart" scripts should '
+ 'use the .dart file extension.',
+ node.sourceSpan);
+ }
+
+ if (node.innerHtml.trim() != '') {
+ _messages.error('script tag has "src" attribute and also has script '
+ 'text.', node.sourceSpan);
+ }
+
+ if (_currentInfo.codeAttached) {
+ _tooManyScriptsError(node);
+ } else {
+ _currentInfo.externalFile = UrlInfo.resolve(src, _fileInfo.inputUrl,
+ node.sourceSpan, _packageRoot, _messages);
+ }
+ return;
+ }
+
+ if (node.nodes.length == 0) return;
+
+ // I don't think the html5 parser will emit a tree with more than
+ // one child of <script>
+ assert(node.nodes.length == 1);
+ Text text = node.nodes[0];
+
+ if (_currentInfo.codeAttached) {
+ _tooManyScriptsError(node);
+ } else if (_currentInfo == _fileInfo && !_fileInfo.isEntryPoint) {
+ _messages.warning('top-level dart code is ignored on '
+ ' HTML pages that define components, but are not the entry HTML '
+ 'file.', node.sourceSpan);
+ } else {
+ _currentInfo.inlinedCode = parseDartCode(
+ _currentInfo.dartCodeUrl.resolvedPath, text.value,
+ text.sourceSpan.start);
+ if (_currentInfo.userCode.partOf != null) {
+ _messages.error('expected a library, not a part.',
+ node.sourceSpan);
+ }
+ }
+ }
+
+ void _tooManyScriptsError(Node node) {
+ var location = _currentInfo is ComponentInfo ?
+ 'a custom element declaration' : 'the top-level HTML page';
+
+ _messages.error('there should be only one dart script tag in $location.',
+ node.sourceSpan);
+ }
+}
diff --git a/pkg/polymer/lib/src/compiler.dart b/pkg/polymer/lib/src/compiler.dart
new file mode 100644
index 0000000..5e2c98f
--- /dev/null
+++ b/pkg/polymer/lib/src/compiler.dart
@@ -0,0 +1,770 @@
+// Copyright (c) 2012, 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.
+
+library compiler;
+
+import 'dart:async';
+import 'dart:collection' show SplayTreeMap;
+import 'dart:json' as json;
+
+import 'package:analyzer_experimental/src/generated/ast.dart' show Directive, UriBasedDirective;
+import 'package:csslib/visitor.dart' show StyleSheet, treeToDebugString;
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/parser.dart';
+import 'package:observe/transform.dart' show transformObservables;
+import 'package:source_maps/span.dart' show Span;
+import 'package:source_maps/refactor.dart' show TextEditTransaction;
+import 'package:source_maps/printer.dart';
+
+import 'analyzer.dart';
+import 'css_analyzer.dart' show analyzeCss, findUrlsImported,
+ findImportsInStyleSheet, parseCss;
+import 'css_emitters.dart' show rewriteCssUris,
+ emitComponentStyleSheet, emitOriginalCss, emitStyleSheet;
+import 'dart_parser.dart';
+import 'emitters.dart';
+import 'file_system.dart';
+import 'files.dart';
+import 'info.dart';
+import 'messages.dart';
+import 'compiler_options.dart';
+import 'paths.dart';
+import 'utils.dart';
+
+/**
+ * Parses an HTML file [contents] and returns a DOM-like tree.
+ * Note that [contents] will be a [String] if coming from a browser-based
+ * [FileSystem], or it will be a [List<int>] if running on the command line.
+ *
+ * Adds emitted error/warning to [messages], if [messages] is supplied.
+ */
+Document parseHtml(contents, String sourcePath, Messages messages) {
+ var parser = new HtmlParser(contents, generateSpans: true,
+ sourceUrl: sourcePath);
+ var document = parser.parse();
+
+ // Note: errors aren't fatal in HTML (unless strict mode is on).
+ // So just print them as warnings.
+ for (var e in parser.errors) {
+ messages.warning(e.message, e.span);
+ }
+ return document;
+}
+
+/** Compiles an application written with Dart web components. */
+class Compiler {
+ final FileSystem fileSystem;
+ final CompilerOptions options;
+ final List<SourceFile> files = <SourceFile>[];
+ final List<OutputFile> output = <OutputFile>[];
+
+ String _mainPath;
+ String _resetCssFile;
+ StyleSheet _cssResetStyleSheet;
+ PathMapper _pathMapper;
+ Messages _messages;
+
+ FutureGroup _tasks;
+ Set _processed;
+
+ /** Information about source [files] given their href. */
+ final Map<String, FileInfo> info = new SplayTreeMap<String, FileInfo>();
+ final _edits = new Map<DartCodeInfo, TextEditTransaction>();
+
+ final GlobalInfo global = new GlobalInfo();
+
+ /** Creates a compiler with [options] using [fileSystem]. */
+ Compiler(this.fileSystem, this.options, this._messages) {
+ _mainPath = options.inputFile;
+ var mainDir = path.dirname(_mainPath);
+ var baseDir = options.baseDir != null ? options.baseDir : mainDir;
+ var outputDir = options.outputDir != null ? options.outputDir : mainDir;
+ var packageRoot = options.packageRoot != null ? options.packageRoot
+ : path.join(path.dirname(_mainPath), 'packages');
+
+ if (options.resetCssFile != null) {
+ _resetCssFile = options.resetCssFile;
+ if (path.isRelative(_resetCssFile)) {
+ // If CSS reset file path is relative from our current path.
+ _resetCssFile = path.resolve(_resetCssFile);
+ }
+ }
+
+ // Normalize paths - all should be relative or absolute paths.
+ if (path.isAbsolute(_mainPath) || path.isAbsolute(baseDir) ||
+ path.isAbsolute(outputDir) || path.isAbsolute(packageRoot)) {
+ if (path.isRelative(_mainPath)) _mainPath = path.resolve(_mainPath);
+ if (path.isRelative(baseDir)) baseDir = path.resolve(baseDir);
+ if (path.isRelative(outputDir)) outputDir = path.resolve(outputDir);
+ if (path.isRelative(packageRoot)) {
+ packageRoot = path.resolve(packageRoot);
+ }
+ }
+ _pathMapper = new PathMapper(
+ baseDir, outputDir, packageRoot, options.forceMangle,
+ options.rewriteUrls);
+ }
+
+ /** Compile the application starting from the given input file. */
+ Future run() {
+ if (path.basename(_mainPath).endsWith('.dart')) {
+ _messages.error("Please provide an HTML file as your entry point.",
+ null);
+ return new Future.value(null);
+ }
+ return _parseAndDiscover(_mainPath).then((_) {
+ _analyze();
+
+ // Analyze all CSS files.
+ _time('Analyzed Style Sheets', '', () =>
+ analyzeCss(_pathMapper.packageRoot, files, info,
+ global.pseudoElements, _messages,
+ warningsAsErrors: options.warningsAsErrors));
+
+ // TODO(jmesserly): need to go through our errors, and figure out if some
+ // of them should be warnings instead.
+ if (_messages.hasErrors || options.analysisOnly) return;
+ _transformDart();
+ _emit();
+ });
+ }
+
+ /**
+ * Asynchronously parse [inputFile] and transitively discover web components
+ * to load and parse. Returns a future that completes when all files are
+ * processed.
+ */
+ Future _parseAndDiscover(String inputFile) {
+ _tasks = new FutureGroup();
+ _processed = new Set();
+ _processed.add(inputFile);
+ _tasks.add(_parseHtmlFile(new UrlInfo(inputFile, inputFile, null)));
+ return _tasks.future;
+ }
+
+ void _processHtmlFile(UrlInfo inputUrl, SourceFile file) {
+ if (file == null) return;
+
+ bool isEntryPoint = _processed.length == 1;
+
+ files.add(file);
+
+ var fileInfo = _time('Analyzed definitions', inputUrl.url, () {
+ return analyzeDefinitions(global, inputUrl, file.document,
+ _pathMapper.packageRoot, _messages, isEntryPoint: isEntryPoint);
+ });
+ info[inputUrl.resolvedPath] = fileInfo;
+
+ if (isEntryPoint && _resetCssFile != null) {
+ _processed.add(_resetCssFile);
+ _tasks.add(_parseCssFile(new UrlInfo(_resetCssFile, _resetCssFile,
+ null)));
+ }
+
+ _setOutputFilenames(fileInfo);
+ _processImports(fileInfo);
+
+ // Load component files referenced by [file].
+ for (var link in fileInfo.componentLinks) {
+ _loadFile(link, _parseHtmlFile);
+ }
+
+ // Load stylesheet files referenced by [file].
+ for (var link in fileInfo.styleSheetHrefs) {
+ _loadFile(link, _parseCssFile);
+ }
+
+ // Load .dart files being referenced in the page.
+ _loadFile(fileInfo.externalFile, _parseDartFile);
+
+ // Process any @imports inside of a <style> tag.
+ var urlInfos = findUrlsImported(fileInfo, fileInfo.inputUrl,
+ _pathMapper.packageRoot, file.document, _messages, options);
+ for (var urlInfo in urlInfos) {
+ _loadFile(urlInfo, _parseCssFile);
+ }
+
+ // Load .dart files being referenced in components.
+ for (var component in fileInfo.declaredComponents) {
+ if (component.externalFile != null) {
+ _loadFile(component.externalFile, _parseDartFile);
+ } else if (component.userCode != null) {
+ _processImports(component);
+ }
+
+ // Process any @imports inside of the <style> tag in a component.
+ var urlInfos = findUrlsImported(component,
+ component.declaringFile.inputUrl, _pathMapper.packageRoot,
+ component.element, _messages, options);
+ for (var urlInfo in urlInfos) {
+ _loadFile(urlInfo, _parseCssFile);
+ }
+ }
+ }
+
+ /**
+ * Helper function to load [urlInfo] and parse it using [loadAndParse] if it
+ * hasn't been loaded before.
+ */
+ void _loadFile(UrlInfo urlInfo, Future loadAndParse(UrlInfo inputUrl)) {
+ if (urlInfo == null) return;
+ var resolvedPath = urlInfo.resolvedPath;
+ if (!_processed.contains(resolvedPath)) {
+ _processed.add(resolvedPath);
+ _tasks.add(loadAndParse(urlInfo));
+ }
+ }
+
+ void _setOutputFilenames(FileInfo fileInfo) {
+ var filePath = fileInfo.dartCodeUrl.resolvedPath;
+ fileInfo.outputFilename = _pathMapper.mangle(path.basename(filePath),
+ '.dart', path.extension(filePath) == '.html');
+ for (var component in fileInfo.declaredComponents) {
+ var externalFile = component.externalFile;
+ var name = null;
+ if (externalFile != null) {
+ name = _pathMapper.mangle(
+ path.basename(externalFile.resolvedPath), '.dart');
+ } else {
+ var declaringFile = component.declaringFile;
+ var prefix = path.basename(declaringFile.inputUrl.resolvedPath);
+ if (declaringFile.declaredComponents.length == 1
+ && !declaringFile.codeAttached && !declaringFile.isEntryPoint) {
+ name = _pathMapper.mangle(prefix, '.dart', true);
+ } else {
+ var componentName = component.tagName.replaceAll('-', '_');
+ name = _pathMapper.mangle('${prefix}_$componentName', '.dart', true);
+ }
+ }
+ component.outputFilename = name;
+ }
+ }
+
+ /** Parse an HTML file. */
+ Future _parseHtmlFile(UrlInfo inputUrl) {
+ if (!_pathMapper.checkInputPath(inputUrl, _messages)) {
+ return new Future<SourceFile>.value(null);
+ }
+ var filePath = inputUrl.resolvedPath;
+ return fileSystem.readTextOrBytes(filePath)
+ .catchError((e) => _readError(e, inputUrl))
+ .then((source) {
+ if (source == null) return;
+ var file = new SourceFile(filePath);
+ file.document = _time('Parsed', filePath,
+ () => parseHtml(source, filePath, _messages));
+ _processHtmlFile(inputUrl, file);
+ });
+ }
+
+ /** Parse a Dart file. */
+ Future _parseDartFile(UrlInfo inputUrl) {
+ if (!_pathMapper.checkInputPath(inputUrl, _messages)) {
+ return new Future<SourceFile>.value(null);
+ }
+ var filePath = inputUrl.resolvedPath;
+ return fileSystem.readText(filePath)
+ .catchError((e) => _readError(e, inputUrl))
+ .then((code) {
+ if (code == null) return;
+ var file = new SourceFile(filePath, type: SourceFile.DART);
+ file.code = code;
+ _processDartFile(inputUrl, file);
+ });
+ }
+
+ /** Parse a stylesheet file. */
+ Future _parseCssFile(UrlInfo inputUrl) {
+ if (!options.emulateScopedCss ||
+ !_pathMapper.checkInputPath(inputUrl, _messages)) {
+ return new Future<SourceFile>.value(null);
+ }
+ var filePath = inputUrl.resolvedPath;
+ return fileSystem.readText(filePath)
+ .catchError((e) => _readError(e, inputUrl, isWarning: true))
+ .then((code) {
+ if (code == null) return;
+ var file = new SourceFile(filePath, type: SourceFile.STYLESHEET);
+ file.code = code;
+ _processCssFile(inputUrl, file);
+ });
+ }
+
+
+ SourceFile _readError(error, UrlInfo inputUrl, {isWarning: false}) {
+ var message = 'unable to open file "${inputUrl.resolvedPath}"';
+ if (options.verbose) {
+ message = '$message. original message:\n $error';
+ }
+ if (isWarning) {
+ _messages.warning(message, inputUrl.sourceSpan);
+ } else {
+ _messages.error(message, inputUrl.sourceSpan);
+ }
+ return null;
+ }
+
+ void _processDartFile(UrlInfo inputUrl, SourceFile dartFile) {
+ if (dartFile == null) return;
+
+ files.add(dartFile);
+
+ var resolvedPath = inputUrl.resolvedPath;
+ var fileInfo = new FileInfo(inputUrl);
+ info[resolvedPath] = fileInfo;
+ fileInfo.inlinedCode = parseDartCode(resolvedPath, dartFile.code);
+ fileInfo.outputFilename =
+ _pathMapper.mangle(path.basename(resolvedPath), '.dart', false);
+
+ _processImports(fileInfo);
+ }
+
+ void _processImports(LibraryInfo library) {
+ if (library.userCode == null) return;
+
+ for (var directive in library.userCode.directives) {
+ _loadFile(_getDirectiveUrlInfo(library, directive), _parseDartFile);
+ }
+ }
+
+ void _processCssFile(UrlInfo inputUrl, SourceFile cssFile) {
+ if (cssFile == null) return;
+
+ files.add(cssFile);
+
+ var fileInfo = new FileInfo(inputUrl);
+ info[inputUrl.resolvedPath] = fileInfo;
+
+ var styleSheet = parseCss(cssFile.code, _messages, options);
+ if (inputUrl.url == _resetCssFile) {
+ _cssResetStyleSheet = styleSheet;
+ } else if (styleSheet != null) {
+ _resolveStyleSheetImports(inputUrl, cssFile.path, styleSheet);
+ fileInfo.styleSheets.add(styleSheet);
+ }
+ }
+
+ /** Load and parse all style sheets referenced with an @imports. */
+ void _resolveStyleSheetImports(UrlInfo inputUrl, String processingFile,
+ StyleSheet styleSheet) {
+ var urlInfos = _time('CSS imports', processingFile, () =>
+ findImportsInStyleSheet(styleSheet, _pathMapper.packageRoot, inputUrl,
+ _messages));
+
+ for (var urlInfo in urlInfos) {
+ if (urlInfo == null) break;
+ // Load any @imported stylesheet files referenced in this style sheet.
+ _loadFile(urlInfo, _parseCssFile);
+ }
+ }
+
+ String _directiveUri(Directive directive) {
+ var uriDirective = (directive as UriBasedDirective).uri;
+ return (uriDirective as dynamic).value;
+ }
+
+ UrlInfo _getDirectiveUrlInfo(LibraryInfo library, Directive directive) {
+ var uri = _directiveUri(directive);
+ if (uri.startsWith('dart:')) return null;
+ if (uri.startsWith('package:') && uri.startsWith('package:polymer/')) {
+ // Don't process our own package -- we'll implement @observable manually.
+ return null;
+ }
+
+ var span = library.userCode.sourceFile.span(
+ directive.offset, directive.end);
+ return UrlInfo.resolve(uri, library.dartCodeUrl, span,
+ _pathMapper.packageRoot, _messages);
+ }
+
+ /**
+ * Transform Dart source code.
+ * Currently, the only transformation is [transformObservables].
+ * Calls _emitModifiedDartFiles to write the transformed files.
+ */
+ void _transformDart() {
+ var libraries = _findAllDartLibraries();
+
+ var transformed = [];
+ for (var lib in libraries) {
+ var userCode = lib.userCode;
+ var transaction = transformObservables(userCode.compilationUnit,
+ userCode.sourceFile, userCode.code, _messages);
+ if (transaction != null) {
+ _edits[lib.userCode] = transaction;
+ if (transaction.hasEdits) {
+ transformed.add(lib);
+ } else if (lib.htmlFile != null) {
+ // All web components will be transformed too. Track that.
+ transformed.add(lib);
+ }
+ }
+ }
+
+ _findModifiedDartFiles(libraries, transformed);
+
+ libraries.forEach(_fixImports);
+
+ _emitModifiedDartFiles(libraries);
+ }
+
+ /**
+ * Finds all Dart code libraries.
+ * Each library will have [LibraryInfo.inlinedCode] that is non-null.
+ * Also each inlinedCode will be unique.
+ */
+ List<LibraryInfo> _findAllDartLibraries() {
+ var libs = <LibraryInfo>[];
+ void _addLibrary(LibraryInfo lib) {
+ if (lib.inlinedCode != null) libs.add(lib);
+ }
+
+ for (var sourceFile in files) {
+ var file = info[sourceFile.path];
+ _addLibrary(file);
+ file.declaredComponents.forEach(_addLibrary);
+ }
+
+ // Assert that each file path is unique.
+ assert(_uniquePaths(libs));
+ return libs;
+ }
+
+ bool _uniquePaths(List<LibraryInfo> libs) {
+ var seen = new Set();
+ for (var lib in libs) {
+ if (seen.contains(lib.inlinedCode)) {
+ throw new StateError('internal error: '
+ 'duplicate user code for ${lib.dartCodeUrl.resolvedPath}.'
+ ' Files were: $files');
+ }
+ seen.add(lib.inlinedCode);
+ }
+ return true;
+ }
+
+ /**
+ * Queue modified Dart files to be written.
+ * This will not write files that are handled by [WebComponentEmitter] and
+ * [EntryPointEmitter].
+ */
+ void _emitModifiedDartFiles(List<LibraryInfo> libraries) {
+ for (var lib in libraries) {
+ // Components will get emitted by WebComponentEmitter, and the
+ // entry point will get emitted by MainPageEmitter.
+ // So we only need to worry about other .dart files.
+ if (lib.modified && lib is FileInfo &&
+ lib.htmlFile == null && !lib.isEntryPoint) {
+ var transaction = _edits[lib.userCode];
+
+ // Save imports that were modified by _fixImports.
+ for (var d in lib.userCode.directives) {
+ transaction.edit(d.offset, d.end, d.toString());
+ }
+
+ if (!lib.userCode.isPart) {
+ var pos = lib.userCode.firstPartOffset;
+ // Note: we use a different prefix than "autogenerated" to make
+ // ChangeRecord unambiguous. Otherwise it would be imported by this
+ // and polymer, resulting in a collision.
+ // TODO(jmesserly): only generate this for libraries that need it.
+ transaction.edit(pos, pos, "\nimport "
+ "'package:observe/observe.dart' as __observe;\n");
+ }
+ _emitFileAndSourceMaps(lib, transaction.commit(), lib.dartCodeUrl);
+ }
+ }
+ }
+
+ /**
+ * This method computes which Dart files have been modified, starting
+ * from [transformed] and marking recursively through all files that import
+ * the modified files.
+ */
+ void _findModifiedDartFiles(List<LibraryInfo> libraries,
+ List<FileInfo> transformed) {
+
+ if (transformed.length == 0) return;
+
+ // Compute files that reference each file, then use this information to
+ // flip the modified bit transitively. This is a lot simpler than trying
+ // to compute it the other way because of circular references.
+ for (var lib in libraries) {
+ for (var directive in lib.userCode.directives) {
+ var importPath = _getDirectiveUrlInfo(lib, directive);
+ if (importPath == null) continue;
+
+ var importInfo = info[importPath.resolvedPath];
+ if (importInfo != null) {
+ importInfo.referencedBy.add(lib);
+ }
+ }
+ }
+
+ // Propegate the modified bit to anything that references a modified file.
+ void setModified(LibraryInfo library) {
+ if (library.modified) return;
+ library.modified = true;
+ library.referencedBy.forEach(setModified);
+ }
+ transformed.forEach(setModified);
+
+ for (var lib in libraries) {
+ // We don't need this anymore, so free it.
+ lib.referencedBy = null;
+ }
+ }
+
+ void _fixImports(LibraryInfo library) {
+ // Fix imports. Modified files must use the generated path, otherwise
+ // we need to make the path relative to the input.
+ for (var directive in library.userCode.directives) {
+ var importPath = _getDirectiveUrlInfo(library, directive);
+ if (importPath == null) continue;
+ var importInfo = info[importPath.resolvedPath];
+ if (importInfo == null) continue;
+
+ String newUri = null;
+ if (importInfo.modified) {
+ // Use the generated URI for this file.
+ newUri = _pathMapper.importUrlFor(library, importInfo);
+ } else if (options.rewriteUrls) {
+ // Get the relative path to the input file.
+ newUri = _pathMapper.transformUrl(
+ library.dartCodeUrl.resolvedPath, directive.uri.value);
+ }
+ if (newUri != null) {
+ directive.uri = createStringLiteral(newUri);
+ }
+ }
+ }
+
+ /** Run the analyzer on every input html file. */
+ void _analyze() {
+ var uniqueIds = new IntIterator();
+ for (var file in files) {
+ if (file.isHtml) {
+ _time('Analyzed contents', file.path, () =>
+ analyzeFile(file, info, uniqueIds, global, _messages,
+ options.emulateScopedCss));
+ }
+ }
+ }
+
+ /** Emit the generated code corresponding to each input file. */
+ void _emit() {
+ for (var file in files) {
+ if (file.isDart || file.isStyleSheet) continue;
+ _time('Codegen', file.path, () {
+ var fileInfo = info[file.path];
+ _emitComponents(fileInfo);
+ });
+ }
+
+ var entryPoint = files[0];
+ assert(info[entryPoint.path].isEntryPoint);
+ _emitMainDart(entryPoint);
+ _emitMainHtml(entryPoint);
+
+ assert(_unqiueOutputs());
+ }
+
+ bool _unqiueOutputs() {
+ var seen = new Set();
+ for (var file in output) {
+ if (seen.contains(file.path)) {
+ throw new StateError('internal error: '
+ 'duplicate output file ${file.path}. Files were: $output');
+ }
+ seen.add(file.path);
+ }
+ return true;
+ }
+
+ /** Emit the main .dart file. */
+ void _emitMainDart(SourceFile file) {
+ var fileInfo = info[file.path];
+
+ var codeInfo = fileInfo.userCode;
+ if (codeInfo != null) {
+ var printer = new NestedPrinter(0);
+ if (codeInfo.libraryName == null) {
+ printer.addLine('library ${fileInfo.libraryName};');
+ }
+ printer.add(codeInfo.code);
+ _emitFileAndSourceMaps(fileInfo, printer, fileInfo.dartCodeUrl);
+ }
+ }
+
+ // TODO(jmesserly): refactor this out of Compiler.
+ /** Generate an html file with the (trimmed down) main html page. */
+ void _emitMainHtml(SourceFile file) {
+ var fileInfo = info[file.path];
+
+ var bootstrapName = '${path.basename(file.path)}_bootstrap.dart';
+ var bootstrapPath = path.join(path.dirname(file.path), bootstrapName);
+ var bootstrapOutPath = _pathMapper.outputPath(bootstrapPath, '');
+ var bootstrapOutName = path.basename(bootstrapOutPath);
+ var bootstrapInfo = new FileInfo(new UrlInfo('', bootstrapPath, null));
+ var printer = generateBootstrapCode(bootstrapInfo, fileInfo, global,
+ _pathMapper, options);
+ printer.build(bootstrapOutPath);
+ output.add(new OutputFile(
+ bootstrapOutPath, printer.text, source: file.path));
+
+ var document = file.document;
+ var hasCss = _emitAllCss();
+ transformMainHtml(document, fileInfo, _pathMapper, hasCss,
+ options.rewriteUrls, _messages, global, bootstrapOutName);
+ output.add(new OutputFile(_pathMapper.outputPath(file.path, '.html'),
+ document.outerHtml, source: file.path));
+ }
+
+ // TODO(jmesserly): refactor this and other CSS related transforms out of
+ // Compiler.
+ /**
+ * Generate an CSS file for all style sheets (main and components).
+ * Returns true if a file was generated, otherwise false.
+ */
+ bool _emitAllCss() {
+ if (!options.emulateScopedCss) return false;
+
+ var buff = new StringBuffer();
+
+ // Emit all linked style sheet files first.
+ for (var file in files) {
+ var css = new StringBuffer();
+ var fileInfo = info[file.path];
+ if (file.isStyleSheet) {
+ for (var styleSheet in fileInfo.styleSheets) {
+ // Translate any URIs in CSS.
+ rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath,
+ options.rewriteUrls, styleSheet);
+ css.write(
+ '/* Auto-generated from style sheet href = ${file.path} */\n'
+ '/* DO NOT EDIT. */\n\n');
+ css.write(emitStyleSheet(styleSheet, fileInfo));
+ css.write('\n\n');
+ }
+
+ // Emit the linked style sheet in the output directory.
+ if (fileInfo.inputUrl.url != _resetCssFile) {
+ var outCss = _pathMapper.outputPath(fileInfo.inputUrl.resolvedPath,
+ '');
+ output.add(new OutputFile(outCss, css.toString()));
+ }
+ }
+ }
+
+ // Emit all CSS for each component (style scoped).
+ for (var file in files) {
+ if (file.isHtml) {
+ var fileInfo = info[file.path];
+ for (var component in fileInfo.declaredComponents) {
+ for (var styleSheet in component.styleSheets) {
+ // Translate any URIs in CSS.
+ rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath,
+ options.rewriteUrls, styleSheet);
+
+ if (buff.isEmpty) {
+ buff.write(
+ '/* Auto-generated from components style tags. */\n'
+ '/* DO NOT EDIT. */\n\n');
+ }
+ buff.write(
+ '/* ==================================================== \n'
+ ' Component ${component.tagName} stylesheet \n'
+ ' ==================================================== */\n');
+
+ var tagName = component.tagName;
+ if (!component.hasAuthorStyles) {
+ if (_cssResetStyleSheet != null) {
+ // If component doesn't have apply-author-styles then we need to
+ // reset the CSS the styles for the component (if css-reset file
+ // option was passed).
+ buff.write('\n/* Start CSS Reset */\n');
+ var style;
+ if (options.emulateScopedCss) {
+ style = emitComponentStyleSheet(_cssResetStyleSheet, tagName);
+ } else {
+ style = emitOriginalCss(_cssResetStyleSheet);
+ }
+ buff.write(style);
+ buff.write('/* End CSS Reset */\n\n');
+ }
+ }
+ if (options.emulateScopedCss) {
+ buff.write(emitComponentStyleSheet(styleSheet, tagName));
+ } else {
+ buff.write(emitOriginalCss(styleSheet));
+ }
+ buff.write('\n\n');
+ }
+ }
+ }
+ }
+
+ if (buff.isEmpty) return false;
+
+ var cssPath = _pathMapper.outputPath(_mainPath, '.css', true);
+ output.add(new OutputFile(cssPath, buff.toString()));
+ return true;
+ }
+
+ /** Emits the Dart code for all components in [fileInfo]. */
+ void _emitComponents(FileInfo fileInfo) {
+ for (var component in fileInfo.declaredComponents) {
+ // TODO(terry): Handle more than one stylesheet per component
+ if (component.styleSheets.length > 1 && options.emulateScopedCss) {
+ var span = component.externalFile != null
+ ? component.externalFile.sourceSpan : null;
+ _messages.warning(
+ 'Component has more than one stylesheet - first stylesheet used.',
+ span);
+ }
+ var printer = emitPolymerElement(
+ component, _pathMapper, _edits[component.userCode], options);
+ _emitFileAndSourceMaps(component, printer, component.externalFile);
+ }
+ }
+
+ /**
+ * Emits a file that was created using [NestedPrinter] and it's corresponding
+ * source map file.
+ */
+ void _emitFileAndSourceMaps(
+ LibraryInfo lib, NestedPrinter printer, UrlInfo dartCodeUrl) {
+ // Bail if we had an error generating the code for the file.
+ if (printer == null) return;
+
+ var libPath = _pathMapper.outputLibraryPath(lib);
+ var dir = path.dirname(libPath);
+ var filename = path.basename(libPath);
+ printer.add('\n//# sourceMappingURL=$filename.map');
+ printer.build(libPath);
+ var sourcePath = dartCodeUrl != null ? dartCodeUrl.resolvedPath : null;
+ output.add(new OutputFile(libPath, printer.text, source: sourcePath));
+ // Fix-up the paths in the source map file
+ var sourceMap = json.parse(printer.map);
+ var urls = sourceMap['sources'];
+ for (int i = 0; i < urls.length; i++) {
+ urls[i] = path.relative(urls[i], from: dir);
+ }
+ output.add(new OutputFile(path.join(dir, '$filename.map'),
+ json.stringify(sourceMap)));
+ }
+
+ _time(String logMessage, String filePath, callback(),
+ {bool printTime: false}) {
+ var message = new StringBuffer();
+ message.write(logMessage);
+ var filename = path.basename(filePath);
+ for (int i = (60 - logMessage.length - filename.length); i > 0 ; i--) {
+ message.write(' ');
+ }
+ message.write(filename);
+ return time(message.toString(), callback,
+ printTime: options.verbose || printTime);
+ }
+}
diff --git a/pkg/polymer/lib/src/compiler_options.dart b/pkg/polymer/lib/src/compiler_options.dart
new file mode 100644
index 0000000..4c1d317
--- /dev/null
+++ b/pkg/polymer/lib/src/compiler_options.dart
@@ -0,0 +1,148 @@
+// Copyright (c) 2012, 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.
+
+library polymer.src.compiler_options;
+
+import 'package:args/args.dart';
+
+class CompilerOptions {
+ /** Report warnings as errors. */
+ final bool warningsAsErrors;
+
+ /** True to show informational messages. The `--verbose` flag. */
+ final bool verbose;
+
+ /** Remove any generated files. */
+ final bool clean;
+
+ /** Whether to use colors to print messages on the terminal. */
+ final bool useColors;
+
+ /** Force mangling any generated name (even when --out is provided). */
+ final bool forceMangle;
+
+ /** Generate component's dart code, but not the main entry point file. */
+ final bool componentsOnly;
+
+ /** File to process by the compiler. */
+ String inputFile;
+
+ /** Directory where all sources are found. */
+ final String baseDir;
+
+ /** Directory where all output will be generated. */
+ final String outputDir;
+
+ /** Directory where to look for 'package:' imports. */
+ final String packageRoot;
+
+ /**
+ * Adjust resource URLs in the output HTML to point back to the original
+ * location in the file system. Commonly this is enabled during development,
+ * but disabled for deployment.
+ */
+ final bool rewriteUrls;
+
+ /**
+ * Whether to print error messages using the json format understood by the
+ * Dart editor.
+ */
+ final bool jsonFormat;
+
+ /** Emulate scoped styles using a CSS polyfill. */
+ final bool emulateScopedCss;
+
+ /** Use CSS file for CSS Reset. */
+ final String resetCssFile;
+
+ /** Whether to analyze the input for warnings without generating any code. */
+ final bool analysisOnly;
+
+ // We could make this faster, if it ever matters.
+ factory CompilerOptions() => parse(['']);
+
+ CompilerOptions.fromArgs(ArgResults args)
+ : warningsAsErrors = args['warnings_as_errors'],
+ verbose = args['verbose'],
+ clean = args['clean'],
+ useColors = args['colors'],
+ baseDir = args['basedir'],
+ outputDir = args['out'],
+ packageRoot = args['package-root'],
+ rewriteUrls = args['rewrite-urls'],
+ forceMangle = args['unique_output_filenames'],
+ jsonFormat = args['json_format'],
+ componentsOnly = args['components_only'],
+ emulateScopedCss = args['scoped-css'],
+ resetCssFile = args['css-reset'],
+ analysisOnly = !args['deploy'],
+ inputFile = args.rest.length > 0 ? args.rest[0] : null;
+
+ /**
+ * Returns the compiler options parsed from [arguments]. Set [checkUsage] to
+ * false to suppress checking of correct usage or printing help messages.
+ */
+ // TODO(sigmund): convert all flags to use dashes instead of underscores
+ static CompilerOptions parse(List<String> arguments,
+ {bool checkUsage: true}) {
+ var parser = new ArgParser()
+ ..addFlag('verbose', abbr: 'v')
+ ..addFlag('clean', help: 'Remove all generated files',
+ defaultsTo: false, negatable: false)
+ ..addFlag('warnings_as_errors', abbr: 'e',
+ help: 'Warnings handled as errors',
+ defaultsTo: false, negatable: false)
+ ..addFlag('colors', help: 'Display errors/warnings in colored text',
+ defaultsTo: true)
+ ..addFlag('rewrite-urls',
+ help: 'Adjust every resource url to point to the original location in'
+ ' the filesystem.\nThis on by default during development and can be'
+ ' disabled to make the generated code easier to deploy.',
+ defaultsTo: true)
+ ..addFlag('unique_output_filenames', abbr: 'u',
+ help: 'Use unique names for all generated files, so they will not '
+ 'have the\nsame name as your input files, even if they are in a'
+ ' different directory',
+ defaultsTo: false, negatable: false)
+ ..addFlag('json_format',
+ help: 'Print error messsages in a json format easy to parse by tools,'
+ ' such as the Dart editor',
+ defaultsTo: false, negatable: false)
+ ..addFlag('components_only',
+ help: 'Generate only the code for component classes, do not generate '
+ 'HTML files or the main bootstrap code.',
+ defaultsTo: false, negatable: false)
+ ..addFlag('scoped-css', help: 'Emulate scoped styles with CSS polyfill',
+ defaultsTo: false)
+ ..addOption('css-reset', abbr: 'r', help: 'CSS file used to reset CSS')
+ ..addFlag('deploy', help: 'Emit code used for deploying a polymer app,'
+ ' if false just show warnings and errors (default)',
+ defaultsTo: false, negatable: false)
+ ..addOption('out', abbr: 'o', help: 'Directory where to generate files'
+ ' (defaults to the same directory as the source file)')
+ ..addOption('basedir', help: 'Base directory where to find all source '
+ 'files (defaults to the source file\'s directory)')
+ ..addOption('package-root', help: 'Where to find "package:" imports'
+ '(defaults to the "packages/" subdirectory next to the source file)')
+ ..addFlag('help', abbr: 'h', help: 'Displays this help message',
+ defaultsTo: false, negatable: false);
+ try {
+ var results = parser.parse(arguments);
+ if (checkUsage && (results['help'] || results.rest.length == 0)) {
+ showUsage(parser);
+ return null;
+ }
+ return new CompilerOptions.fromArgs(results);
+ } on FormatException catch (e) {
+ print(e.message);
+ showUsage(parser);
+ return null;
+ }
+ }
+
+ static showUsage(parser) {
+ print('Usage: dwc [options...] input.html');
+ print(parser.getUsage());
+ }
+}
diff --git a/pkg/polymer/lib/src/css_analyzer.dart b/pkg/polymer/lib/src/css_analyzer.dart
new file mode 100644
index 0000000..270af5d
--- /dev/null
+++ b/pkg/polymer/lib/src/css_analyzer.dart
@@ -0,0 +1,507 @@
+// 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.
+
+/** Portion of the analyzer dealing with CSS sources. */
+library polymer.src.css_analyzer;
+
+import 'package:csslib/parser.dart' as css;
+import 'package:csslib/visitor.dart';
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/dom_parsing.dart';
+
+import 'info.dart';
+import 'files.dart' show SourceFile;
+import 'messages.dart';
+import 'compiler_options.dart';
+
+void analyzeCss(String packageRoot, List<SourceFile> files,
+ Map<String, FileInfo> info, Map<String, String> pseudoElements,
+ Messages messages, {warningsAsErrors: false}) {
+ var analyzer = new _AnalyzerCss(packageRoot, info, pseudoElements, messages,
+ warningsAsErrors);
+ for (var file in files) analyzer.process(file);
+ analyzer.normalize();
+}
+
+class _AnalyzerCss {
+ final String packageRoot;
+ final Map<String, FileInfo> info;
+ final Map<String, String> _pseudoElements;
+ final Messages _messages;
+ final bool _warningsAsErrors;
+
+ Set<StyleSheet> allStyleSheets = new Set<StyleSheet>();
+
+ /**
+ * [_pseudoElements] list of known pseudo attributes found in HTML, any
+ * CSS pseudo-elements 'name::custom-element' is mapped to the manged name
+ * associated with the pseudo-element key.
+ */
+ _AnalyzerCss(this.packageRoot, this.info, this._pseudoElements,
+ this._messages, this._warningsAsErrors);
+
+ /**
+ * Run the analyzer on every file that is a style sheet or any component that
+ * has a style tag.
+ */
+ void process(SourceFile file) {
+ var fileInfo = info[file.path];
+ if (file.isStyleSheet || fileInfo.styleSheets.length > 0) {
+ var styleSheets = processVars(fileInfo);
+
+ // Add to list of all style sheets analyzed.
+ allStyleSheets.addAll(styleSheets);
+ }
+
+ // Process any components.
+ for (var component in fileInfo.declaredComponents) {
+ var all = processVars(component);
+
+ // Add to list of all style sheets analyzed.
+ allStyleSheets.addAll(all);
+ }
+
+ processCustomPseudoElements();
+ }
+
+ void normalize() {
+ // Remove all var definitions for all style sheets analyzed.
+ for (var tree in allStyleSheets) new _RemoveVarDefinitions().visitTree(tree);
+ }
+
+ List<StyleSheet> processVars(var libraryInfo) {
+ // Get list of all stylesheet(s) dependencies referenced from this file.
+ var styleSheets = _dependencies(libraryInfo).toList();
+
+ var errors = [];
+ css.analyze(styleSheets, errors: errors, options:
+ [_warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
+
+ // Print errors as warnings.
+ for (var e in errors) {
+ _messages.warning(e.message, e.span);
+ }
+
+ // Build list of all var definitions.
+ Map varDefs = new Map();
+ for (var tree in styleSheets) {
+ var allDefs = (new _VarDefinitions()..visitTree(tree)).found;
+ allDefs.forEach((key, value) {
+ varDefs[key] = value;
+ });
+ }
+
+ // Resolve all definitions to a non-VarUsage (terminal expression).
+ varDefs.forEach((key, value) {
+ for (var expr in (value.expression as Expressions).expressions) {
+ var def = _findTerminalVarDefinition(varDefs, value);
+ varDefs[key] = def;
+ }
+ });
+
+ // Resolve all var usages.
+ for (var tree in styleSheets) new _ResolveVarUsages(varDefs).visitTree(tree);
+
+ return styleSheets;
+ }
+
+ processCustomPseudoElements() {
+ var polyFiller = new _PseudoElementExpander(_pseudoElements);
+ for (var tree in allStyleSheets) {
+ polyFiller.visitTree(tree);
+ }
+ }
+
+ /**
+ * Given a component or file check if any stylesheets referenced. If so then
+ * return a list of all referenced stylesheet dependencies (@imports or <link
+ * rel="stylesheet" ..>).
+ */
+ Set<StyleSheet> _dependencies(var libraryInfo, {Set<StyleSheet> seen}) {
+ if (seen == null) seen = new Set();
+
+ // Used to resolve all pathing information.
+ var inputUrl = libraryInfo is FileInfo
+ ? libraryInfo.inputUrl
+ : (libraryInfo as ComponentInfo).declaringFile.inputUrl;
+
+ for (var styleSheet in libraryInfo.styleSheets) {
+ if (!seen.contains(styleSheet)) {
+ // TODO(terry): VM uses expandos to implement hashes. Currently, it's a
+ // linear (not constant) time cost (see dartbug.com/5746).
+ // If this bug isn't fixed and performance show's this a
+ // a problem we'll need to implement our own hashCode or
+ // use a different key for better perf.
+ // Add the stylesheet.
+ seen.add(styleSheet);
+
+ // Any other imports in this stylesheet?
+ var urlInfos = findImportsInStyleSheet(styleSheet, packageRoot,
+ inputUrl, _messages);
+
+ // Process other imports in this stylesheets.
+ for (var importSS in urlInfos) {
+ var importInfo = info[importSS.resolvedPath];
+ if (importInfo != null) {
+ // Add all known stylesheets processed.
+ seen.addAll(importInfo.styleSheets);
+ // Find dependencies for stylesheet referenced with a
+ // @import
+ for (var ss in importInfo.styleSheets) {
+ var urls = findImportsInStyleSheet(ss, packageRoot, inputUrl,
+ _messages);
+ for (var url in urls) {
+ _dependencies(info[url.resolvedPath], seen: seen);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return seen;
+ }
+}
+
+/**
+ * Find var- definitions in a style sheet.
+ * [found] list of known definitions.
+ */
+class _VarDefinitions extends Visitor {
+ final Map<String, VarDefinition> found = new Map();
+
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ visitVarDefinition(VarDefinition node) {
+ // Replace with latest variable definition.
+ found[node.definedName] = node;
+ super.visitVarDefinition(node);
+ }
+
+ void visitVarDefinitionDirective(VarDefinitionDirective node) {
+ visitVarDefinition(node.def);
+ }
+}
+
+/**
+ * Resolve any CSS expression which contains a var() usage to the ultimate real
+ * CSS expression value e.g.,
+ *
+ * var-one: var(two);
+ * var-two: #ff00ff;
+ *
+ * .test {
+ * color: var(one);
+ * }
+ *
+ * then .test's color would be #ff00ff
+ */
+class _ResolveVarUsages extends Visitor {
+ final Map<String, VarDefinition> varDefs;
+ bool inVarDefinition = false;
+ bool inUsage = false;
+ Expressions currentExpressions;
+
+ _ResolveVarUsages(this.varDefs);
+
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ void visitVarDefinition(VarDefinition varDef) {
+ inVarDefinition = true;
+ super.visitVarDefinition(varDef);
+ inVarDefinition = false;
+ }
+
+ void visitExpressions(Expressions node) {
+ currentExpressions = node;
+ super.visitExpressions(node);
+ currentExpressions = null;
+ }
+
+ void visitVarUsage(VarUsage node) {
+ // Don't process other var() inside of a varUsage. That implies that the
+ // default is a var() too. Also, don't process any var() inside of a
+ // varDefinition (they're just place holders until we've resolved all real
+ // usages.
+ if (!inUsage && !inVarDefinition && currentExpressions != null) {
+ var expressions = currentExpressions.expressions;
+ var index = expressions.indexOf(node);
+ assert(index >= 0);
+ var def = varDefs[node.name];
+ if (def != null) {
+ // Found a VarDefinition use it.
+ _resolveVarUsage(currentExpressions.expressions, index, def);
+ } else if (node.defaultValues.any((e) => e is VarUsage)) {
+ // Don't have a VarDefinition need to use default values resolve all
+ // default values.
+ var terminalDefaults = [];
+ for (var defaultValue in node.defaultValues) {
+ terminalDefaults.addAll(resolveUsageTerminal(defaultValue));
+ }
+ expressions.replaceRange(index, index + 1, terminalDefaults);
+ } else {
+ // No VarDefinition but default value is a terminal expression; use it.
+ expressions.replaceRange(index, index + 1, node.defaultValues);
+ }
+ }
+
+ inUsage = true;
+ super.visitVarUsage(node);
+ inUsage = false;
+ }
+
+ List<Expression> resolveUsageTerminal(VarUsage usage) {
+ var result = [];
+
+ var varDef = varDefs[usage.name];
+ var expressions;
+ if (varDef == null) {
+ // VarDefinition not found try the defaultValues.
+ expressions = usage.defaultValues;
+ } else {
+ // Use the VarDefinition found.
+ expressions = (varDef.expression as Expressions).expressions;
+ }
+
+ for (var expr in expressions) {
+ if (expr is VarUsage) {
+ // Get terminal value.
+ result.addAll(resolveUsageTerminal(expr));
+ }
+ }
+
+ // We're at a terminal just return the VarDefinition expression.
+ if (result.isEmpty && varDef != null) {
+ result = (varDef.expression as Expressions).expressions;
+ }
+
+ return result;
+ }
+
+ _resolveVarUsage(List<Expressions> expressions, int index,
+ VarDefinition def) {
+ var defExpressions = (def.expression as Expressions).expressions;
+ expressions.replaceRange(index, index + 1, defExpressions);
+ }
+}
+
+/** Remove all var definitions. */
+class _RemoveVarDefinitions extends Visitor {
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ void visitStyleSheet(StyleSheet ss) {
+ ss.topLevels.removeWhere((e) => e is VarDefinitionDirective);
+ super.visitStyleSheet(ss);
+ }
+
+ void visitDeclarationGroup(DeclarationGroup node) {
+ node.declarations.removeWhere((e) => e is VarDefinition);
+ super.visitDeclarationGroup(node);
+ }
+}
+
+/**
+ * Process all selectors looking for a pseudo-element in a selector. If the
+ * name is found in our list of known pseudo-elements. Known pseudo-elements
+ * are built when parsing a component looking for an attribute named "pseudo".
+ * The value of the pseudo attribute is the name of the custom pseudo-element.
+ * The name is mangled so Dart/JS can't directly access the pseudo-element only
+ * CSS can access a custom pseudo-element (and see issue #510, querying needs
+ * access to custom pseudo-elements).
+ *
+ * Change the custom pseudo-element to be a child of the pseudo attribute's
+ * mangled custom pseudo element name. e.g,
+ *
+ * .test::x-box
+ *
+ * would become:
+ *
+ * .test > *[pseudo="x-box_2"]
+ */
+class _PseudoElementExpander extends Visitor {
+ final Map<String, String> _pseudoElements;
+
+ _PseudoElementExpander(this._pseudoElements);
+
+ void visitTree(StyleSheet tree) => visitStyleSheet(tree);
+
+ visitSelector(Selector node) {
+ var selectors = node.simpleSelectorSequences;
+ for (var index = 0; index < selectors.length; index++) {
+ var selector = selectors[index].simpleSelector;
+ if (selector is PseudoElementSelector) {
+ if (_pseudoElements.containsKey(selector.name)) {
+ // Pseudo Element is a custom element.
+ var mangledName = _pseudoElements[selector.name];
+
+ var span = selectors[index].span;
+
+ var attrSelector = new AttributeSelector(
+ new Identifier('pseudo', span), css.TokenKind.EQUALS,
+ mangledName, span);
+ // The wildcard * namespace selector.
+ var wildCard = new ElementSelector(new Wildcard(span), span);
+ selectors[index] = new SimpleSelectorSequence(wildCard, span,
+ css.TokenKind.COMBINATOR_GREATER);
+ selectors.insert(++index,
+ new SimpleSelectorSequence(attrSelector, span));
+ }
+ }
+ }
+ }
+}
+
+List<UrlInfo> findImportsInStyleSheet(StyleSheet styleSheet,
+ String packageRoot, UrlInfo inputUrl, Messages messages) {
+ var visitor = new _CssImports(packageRoot, inputUrl, messages);
+ visitor.visitTree(styleSheet);
+ return visitor.urlInfos;
+}
+
+/**
+ * Find any imports in the style sheet; normalize the style sheet href and
+ * return a list of all fully qualified CSS files.
+ */
+class _CssImports extends Visitor {
+ final String packageRoot;
+
+ /** Input url of the css file, used to normalize relative import urls. */
+ final UrlInfo inputUrl;
+
+ /** List of all imported style sheets. */
+ final List<UrlInfo> urlInfos = [];
+
+ final Messages _messages;
+
+ _CssImports(this.packageRoot, this.inputUrl, this._messages);
+
+ void visitTree(StyleSheet tree) {
+ visitStyleSheet(tree);
+ }
+
+ void visitImportDirective(ImportDirective node) {
+ var urlInfo = UrlInfo.resolve(node.import, inputUrl,
+ node.span, packageRoot, _messages, ignoreAbsolute: true);
+ if (urlInfo == null) return;
+ urlInfos.add(urlInfo);
+ }
+}
+
+StyleSheet parseCss(String content, Messages messages,
+ CompilerOptions options) {
+ if (content.trim().isEmpty) return null;
+
+ var errors = [];
+
+ // TODO(terry): Add --checked when fully implemented and error handling.
+ var stylesheet = css.parse(content, errors: errors, options:
+ [options.warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
+
+ // Note: errors aren't fatal in HTML (unless strict mode is on).
+ // So just print them as warnings.
+ for (var e in errors) {
+ messages.warning(e.message, e.span);
+ }
+
+ return stylesheet;
+}
+
+/** Find terminal definition (non VarUsage implies real CSS value). */
+VarDefinition _findTerminalVarDefinition(Map<String, VarDefinition> varDefs,
+ VarDefinition varDef) {
+ var expressions = varDef.expression as Expressions;
+ for (var expr in expressions.expressions) {
+ if (expr is VarUsage) {
+ var usageName = (expr as VarUsage).name;
+ var foundDef = varDefs[usageName];
+
+ // If foundDef is unknown check if defaultValues; if it exist then resolve
+ // to terminal value.
+ if (foundDef == null) {
+ // We're either a VarUsage or terminal definition if in varDefs;
+ // either way replace VarUsage with it's default value because the
+ // VarDefinition isn't found.
+ var defaultValues = (expr as VarUsage).defaultValues;
+ var replaceExprs = expressions.expressions;
+ assert(replaceExprs.length == 1);
+ replaceExprs.replaceRange(0, 1, defaultValues);
+ return varDef;
+ }
+ if (foundDef is VarDefinition) {
+ return _findTerminalVarDefinition(varDefs, foundDef);
+ }
+ } else {
+ // Return real CSS property.
+ return varDef;
+ }
+ }
+
+ // Didn't point to a var definition that existed.
+ return varDef;
+}
+
+/**
+ * Find urls imported inside style tags under [info]. If [info] is a FileInfo
+ * then process only style tags in the body (don't process any style tags in a
+ * component). If [info] is a ComponentInfo only process style tags inside of
+ * the element are processed. For an [info] of type FileInfo [node] is the
+ * file's document and for an [info] of type ComponentInfo then [node] is the
+ * component's element tag.
+ */
+List<UrlInfo> findUrlsImported(LibraryInfo info, UrlInfo inputUrl,
+ String packageRoot, Node node, Messages messages, CompilerOptions options) {
+ // Process any @imports inside of the <style> tag.
+ var styleProcessor =
+ new _CssStyleTag(packageRoot, info, inputUrl, messages, options);
+ styleProcessor.visit(node);
+ return styleProcessor.imports;
+}
+
+/* Process CSS inside of a style tag. */
+class _CssStyleTag extends TreeVisitor {
+ final String _packageRoot;
+
+ /** Either a FileInfo or ComponentInfo. */
+ final LibraryInfo _info;
+ final Messages _messages;
+ final CompilerOptions _options;
+
+ /**
+ * Path of the declaring file, for a [_info] of type FileInfo it's the file's
+ * path for a type ComponentInfo it's the declaring file path.
+ */
+ final UrlInfo _inputUrl;
+
+ /** List of @imports found. */
+ List<UrlInfo> imports = [];
+
+ _CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages,
+ this._options);
+
+ void visitElement(Element node) {
+ // Don't process any style tags inside of element if we're processing a
+ // FileInfo. The style tags inside of a component defintion will be
+ // processed when _info is a ComponentInfo.
+ if (node.tagName == 'polymer-element' && _info is FileInfo) return;
+ if (node.tagName == 'style') {
+ // Parse the contents of the scoped style tag.
+ var styleSheet = parseCss(node.nodes.single.value, _messages, _options);
+ if (styleSheet != null) {
+ _info.styleSheets.add(styleSheet);
+
+ // Find all imports return list of @imports in this style tag.
+ var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot,
+ _inputUrl, _messages);
+ imports.addAll(urlInfos);
+ }
+ }
+ super.visitElement(node);
+ }
+}
diff --git a/pkg/polymer/lib/src/css_emitters.dart b/pkg/polymer/lib/src/css_emitters.dart
new file mode 100644
index 0000000..d1863f5
--- /dev/null
+++ b/pkg/polymer/lib/src/css_emitters.dart
@@ -0,0 +1,155 @@
+// 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.
+
+library polymer.src.css_emitters;
+
+import 'package:csslib/visitor.dart' show Visitor, CssPrinter, ElementSelector,
+ UriTerm, Selector, HostDirective, SimpleSelectorSequence, StyleSheet;
+
+import 'info.dart';
+import 'paths.dart' show PathMapper;
+import 'utils.dart';
+
+void rewriteCssUris(PathMapper pathMapper, String cssPath, bool rewriteUrls,
+ StyleSheet styleSheet) {
+ new _UriVisitor(pathMapper, cssPath, rewriteUrls).visitTree(styleSheet);
+}
+
+/** Compute each CSS URI resource relative from the generated CSS file. */
+class _UriVisitor extends Visitor {
+ /**
+ * Relative path from the output css file to the location of the original
+ * css file that contained the URI to each resource.
+ */
+ final String _pathToOriginalCss;
+
+ factory _UriVisitor(PathMapper pathMapper, String cssPath, bool rewriteUrl) {
+ var cssDir = path.dirname(cssPath);
+ var outCssDir = rewriteUrl ? pathMapper.outputDirPath(cssPath)
+ : path.dirname(cssPath);
+ return new _UriVisitor._internal(path.relative(cssDir, from: outCssDir));
+ }
+
+ _UriVisitor._internal(this._pathToOriginalCss);
+
+ void visitUriTerm(UriTerm node) {
+ // Don't touch URIs that have any scheme (http, etc.).
+ var uri = Uri.parse(node.text);
+ if (uri.host != '') return;
+ if (uri.scheme != '' && uri.scheme != 'package') return;
+
+ node.text = pathToUrl(
+ path.normalize(path.join(_pathToOriginalCss, node.text)));
+ }
+}
+
+
+/** Emit the contents of the style tag outside of a component. */
+String emitStyleSheet(StyleSheet ss, FileInfo file) =>
+ (new _CssEmitter(file.components.keys.toSet())
+ ..visitTree(ss, pretty: true)).toString();
+
+/** Emit a component's style tag content emulating scoped css. */
+String emitComponentStyleSheet(StyleSheet ss, String tagName) =>
+ (new _ComponentCssEmitter(tagName)..visitTree(ss, pretty: true)).toString();
+
+String emitOriginalCss(StyleSheet css) =>
+ (new CssPrinter()..visitTree(css)).toString();
+
+/** Only x-tag name element selectors are emitted as [is="x-"]. */
+class _CssEmitter extends CssPrinter {
+ final Set _componentsTag;
+ _CssEmitter(this._componentsTag);
+
+ void visitElementSelector(ElementSelector node) {
+ // If element selector is a component's tag name, then change selector to
+ // find element who's is attribute's the component's name.
+ if (_componentsTag.contains(node.name)) {
+ emit('[is="${node.name}"]');
+ return;
+ }
+ super.visitElementSelector(node);
+ }
+}
+
+/**
+ * Emits a css stylesheet applying rules to emulate scoped css. The rules adjust
+ * element selectors to include the component's tag name.
+ */
+class _ComponentCssEmitter extends CssPrinter {
+ final String _componentTagName;
+ bool _inHostDirective = false;
+ bool _selectorStartInHostDirective = false;
+
+ _ComponentCssEmitter(this._componentTagName);
+
+ /** Is the element selector an x-tag name. */
+ bool _isSelectorElementXTag(Selector node) {
+ if (node.simpleSelectorSequences.length > 0) {
+ var selector = node.simpleSelectorSequences[0].simpleSelector;
+ return selector is ElementSelector && selector.name == _componentTagName;
+ }
+ return false;
+ }
+
+ void visitSelector(Selector node) {
+ // If the selector starts with an x-tag name don't emit it twice.
+ if (!_isSelectorElementXTag(node)) {
+ if (_inHostDirective) {
+ // Style the element that's hosting the component, therefore don't emit
+ // the descendent combinator (first space after the [is="x-..."]).
+ emit('[is="$_componentTagName"]');
+ // Signal that first simpleSelector must be checked.
+ _selectorStartInHostDirective = true;
+ } else {
+ // Emit its scoped as a descendent (space at end).
+ emit('[is="$_componentTagName"] ');
+ }
+ }
+ super.visitSelector(node);
+ }
+
+ /**
+ * If first simple selector of a ruleset in a @host directive is a wildcard
+ * then don't emit the wildcard.
+ */
+ void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
+ if (_selectorStartInHostDirective) {
+ _selectorStartInHostDirective = false;
+ if (node.simpleSelector.isWildcard) {
+ // Skip the wildcard if first item in the sequence.
+ return;
+ }
+ assert(node.isCombinatorNone);
+ }
+
+ super.visitSimpleSelectorSequence(node);
+ }
+
+ void visitElementSelector(ElementSelector node) {
+ // If element selector is the component's tag name, then change selector to
+ // find element who's is attribute is the component's name.
+ if (_componentTagName == node.name) {
+ emit('[is="$_componentTagName"]');
+ return;
+ }
+ super.visitElementSelector(node);
+ }
+
+ /**
+ * If we're polyfilling scoped styles the @host directive is stripped. Any
+ * ruleset(s) processed in an @host will fixup the first selector. See
+ * visitSelector and visitSimpleSelectorSequence in this class, they adjust
+ * the selectors so it styles the element hosting the compopnent.
+ */
+ void visitHostDirective(HostDirective node) {
+ _inHostDirective = true;
+ emit('/* @host */');
+ for (var ruleset in node.rulesets) {
+ ruleset.visit(this);
+ }
+ _inHostDirective = false;
+ emit('/* end of @host */\n');
+ }
+}
diff --git a/pkg/polymer/lib/src/custom_tag_name.dart b/pkg/polymer/lib/src/custom_tag_name.dart
new file mode 100644
index 0000000..637b937
--- /dev/null
+++ b/pkg/polymer/lib/src/custom_tag_name.dart
@@ -0,0 +1,27 @@
+// 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.
+
+library polymer.src.custom_tag_name;
+
+/**
+ * Returns true if this is a valid custom element name. See:
+ * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn-custom-element-name>
+ */
+bool isCustomTag(String name) {
+ if (name == null || !name.contains('-')) return false;
+
+ // These names have meaning in SVG or MathML, so they aren't allowed as custom
+ // tags.
+ var invalidNames = const {
+ 'annotation-xml': '',
+ 'color-profile': '',
+ 'font-face': '',
+ 'font-face-src': '',
+ 'font-face-uri': '',
+ 'font-face-format': '',
+ 'font-face-name': '',
+ 'missing-glyph': '',
+ };
+ return !invalidNames.containsKey(name);
+}
diff --git a/pkg/polymer/lib/src/dart_parser.dart b/pkg/polymer/lib/src/dart_parser.dart
new file mode 100644
index 0000000..a2f44c3
--- /dev/null
+++ b/pkg/polymer/lib/src/dart_parser.dart
@@ -0,0 +1,132 @@
+// 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.
+
+/**
+ * Parser for Dart code based on the experimental analyzer.
+ */
+library dart_parser;
+
+import 'package:analyzer_experimental/src/generated/ast.dart';
+import 'package:analyzer_experimental/src/generated/error.dart';
+import 'package:analyzer_experimental/src/generated/parser.dart';
+import 'package:analyzer_experimental/src/generated/scanner.dart';
+import 'package:source_maps/span.dart' show SourceFile, SourceFileSegment, Location;
+import 'utils.dart' show escapeDartString;
+
+/** Information extracted from a source Dart file. */
+class DartCodeInfo {
+
+ /** Library qualified identifier, if any. */
+ final String libraryName;
+
+ /** Library which the code is part-of, if any. */
+ final String partOf;
+
+ /** Declared imports, exports, and parts. */
+ final List<Directive> directives;
+
+ /** Source file representation used to compute source map information. */
+ final SourceFile sourceFile;
+
+ /** The parsed code. */
+ final CompilationUnit compilationUnit;
+
+ /** The full source code. */
+ final String code;
+
+ DartCodeInfo(this.libraryName, this.partOf, this.directives, code,
+ this.sourceFile, [compilationUnit])
+ : this.code = code,
+ this.compilationUnit = compilationUnit == null
+ ? _parseCompilationUnit(code) : compilationUnit;
+
+ bool get isPart =>
+ compilationUnit.directives.any((d) => d is PartOfDirective);
+
+ int get directivesEnd {
+ if (compilationUnit.directives.length == 0) return 0;
+ return compilationUnit.directives.last.end;
+ }
+
+ /**
+ * The position of the first "part" directive. If none is found,
+ * this behaves like [directivesEnd].
+ */
+ int get firstPartOffset {
+ for (var directive in compilationUnit.directives) {
+ if (directive is PartDirective) return directive.offset;
+ }
+ // No part directives, just return directives end.
+ return directivesEnd;
+ }
+
+ /** Gets the code after the [directives]. */
+ String codeAfterDirectives() => code.substring(directivesEnd);
+
+ ClassDeclaration findClass(String name) {
+ for (var decl in compilationUnit.declarations) {
+ if (decl is ClassDeclaration) {
+ if (decl.name.name == name) return decl;
+ }
+ }
+ return null;
+ }
+}
+
+SimpleStringLiteral createStringLiteral(String contents) {
+ var lexeme = "'${escapeDartString(contents)}'";
+ var token = new StringToken(TokenType.STRING, lexeme, null);
+ return new SimpleStringLiteral.full(token, contents);
+}
+
+
+/**
+ * Parse and extract top-level directives from [code].
+ *
+ */
+// TODO(sigmund): log emitted error/warning messages
+DartCodeInfo parseDartCode(String path, String code, [Location offset]) {
+ var unit = _parseCompilationUnit(code);
+
+ // Extract some information from the compilation unit.
+ String libraryName, partName;
+ var directives = [];
+ int directiveEnd = 0;
+ for (var directive in unit.directives) {
+ if (directive is LibraryDirective) {
+ libraryName = directive.name.name;
+ } else if (directive is PartOfDirective) {
+ partName = directive.libraryName.name;
+ } else {
+ assert(directive is UriBasedDirective);
+ // Normalize the library URI.
+ var uriNode = directive.uri;
+ if (uriNode is! SimpleStringLiteral) {
+ String uri = uriNode.accept(new ConstantEvaluator());
+ directive.uri = createStringLiteral(uri);
+ }
+ directives.add(directive);
+ }
+ }
+
+ var sourceFile = offset == null
+ ? new SourceFile.text(path, code)
+ : new SourceFileSegment(path, code, offset);
+
+ return new DartCodeInfo(libraryName, partName, directives, code,
+ sourceFile, unit);
+}
+
+CompilationUnit _parseCompilationUnit(String code) {
+ var errorListener = new _ErrorCollector();
+ var scanner = new StringScanner(null, code, errorListener);
+ var token = scanner.tokenize();
+ var parser = new Parser(null, errorListener);
+ return parser.parseCompilationUnit(token);
+}
+
+class _ErrorCollector extends AnalysisErrorListener {
+ final errors = new List<AnalysisError>();
+ onError(error) => errors.add(error);
+}
diff --git a/pkg/polymer/lib/src/emitters.dart b/pkg/polymer/lib/src/emitters.dart
new file mode 100644
index 0000000..dbd6816
--- /dev/null
+++ b/pkg/polymer/lib/src/emitters.dart
@@ -0,0 +1,249 @@
+// Copyright (c) 2012, 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.
+
+/** Collects several code emitters for the template tool. */
+library emitters;
+
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/dom_parsing.dart' show TreeVisitor;
+import 'package:html5lib/parser.dart' show parseFragment;
+import 'package:source_maps/printer.dart';
+import 'package:source_maps/refactor.dart';
+
+import 'compiler_options.dart';
+import 'css_emitters.dart' show emitStyleSheet, emitOriginalCss;
+import 'html5_utils.dart';
+import 'info.dart' show ComponentInfo, FileInfo, GlobalInfo;
+import 'messages.dart';
+import 'paths.dart' show PathMapper;
+import 'utils.dart' show escapeDartString, path;
+
+/** Generates the class corresponding to a single web component. */
+NestedPrinter emitPolymerElement(ComponentInfo info, PathMapper pathMapper,
+ TextEditTransaction transaction, CompilerOptions options) {
+ if (info.classDeclaration == null) return null;
+
+ var codeInfo = info.userCode;
+ if (transaction == null) {
+ // TODO(sigmund): avoid emitting this file if we don't need to do any
+ // modifications (e.g. no @observable and not adding the libraryName).
+ transaction = new TextEditTransaction(codeInfo.code, codeInfo.sourceFile);
+ }
+ if (codeInfo.libraryName == null) {
+ // For deploy, we need to import the library associated with the component,
+ // so we need to ensure there is a library directive.
+ var libraryName = info.tagName.replaceAll(new RegExp('[-./]'), '_');
+ transaction.edit(0, 0, 'library $libraryName;');
+ }
+ return transaction.commit();
+}
+
+/** The code that will be used to bootstrap the application. */
+NestedPrinter generateBootstrapCode(
+ FileInfo info, FileInfo userMainInfo, GlobalInfo global,
+ PathMapper pathMapper, CompilerOptions options) {
+
+ var printer = new NestedPrinter(0)
+ ..addLine('library app_bootstrap;')
+ ..addLine('')
+ ..addLine("import 'package:polymer/polymer.dart';")
+ ..addLine("import 'dart:mirrors' show currentMirrorSystem;");
+
+ int i = 0;
+ for (var c in global.components.values) {
+ if (c.hasConflict) continue;
+ printer.addLine("import '${pathMapper.importUrlFor(info, c)}' as i$i;");
+ i++;
+ }
+ if (userMainInfo.userCode != null) {
+ printer..addLine("import '${pathMapper.importUrlFor(info, userMainInfo)}' "
+ "as i$i;\n");
+ }
+
+ printer..addLine('')
+ ..addLine('void main() {')
+ ..indent += 1
+ ..addLine("initPolymer([")
+ ..indent += 2;
+
+ for (var c in global.components.values) {
+ if (c.hasConflict) continue;
+ printer.addLine("'${pathMapper.importUrlFor(info, c)}',");
+ }
+
+ if (userMainInfo.userCode != null) {
+ printer.addLine("'${pathMapper.importUrlFor(info, userMainInfo)}',");
+ }
+
+ return printer
+ ..indent -= 1
+ ..addLine('],')
+ ..addLine(
+ "currentMirrorSystem().findLibrary(const Symbol('app_bootstrap'))")
+ ..indent += 2
+ ..addLine(".first.uri.toString());")
+ ..indent -= 4
+ ..addLine('}');
+}
+
+
+/**
+ * Rewrites attributes that contain relative URL (excluding src urls in script
+ * and link tags which are already rewritten by other parts of the compiler).
+*/
+class _AttributeUrlTransform extends TreeVisitor {
+ final String filePath;
+ final PathMapper pathMapper;
+
+ _AttributeUrlTransform(this.filePath, this.pathMapper);
+
+ visitElement(Element node) {
+ if (node.tagName == 'script') return;
+ if (node.tagName == 'link') return;
+
+ for (var key in node.attributes.keys) {
+ if (urlAttributes.contains(key)) {
+ node.attributes[key] =
+ pathMapper.transformUrl(filePath, node.attributes[key]);
+ }
+ }
+ super.visitElement(node);
+ }
+}
+
+final _shadowDomJS = new RegExp(r'shadowdom\..*\.js', caseSensitive: false);
+final _bootJS = new RegExp(r'.*/polymer/boot.js', caseSensitive: false);
+
+/** Trim down the html for the main html page. */
+void transformMainHtml(Document document, FileInfo fileInfo,
+ PathMapper pathMapper, bool hasCss, bool rewriteUrls,
+ Messages messages, GlobalInfo global, String bootstrapOutName) {
+ var filePath = fileInfo.inputUrl.resolvedPath;
+
+ var dartLoaderTag = null;
+ bool shadowDomFound = false;
+ for (var tag in document.queryAll('script')) {
+ var src = tag.attributes['src'];
+ if (src != null) {
+ var last = src.split('/').last;
+ if (last == 'dart.js' || last == 'testing.js') {
+ dartLoaderTag = tag;
+ } else if (_shadowDomJS.hasMatch(last)) {
+ shadowDomFound = true;
+ }
+ }
+ if (tag.attributes['type'] == 'application/dart') {
+ tag.remove();
+ } else if (src != null) {
+ if (_bootJS.hasMatch(src)) {
+ tag.remove();
+ } else if (rewriteUrls) {
+ tag.attributes["src"] = pathMapper.transformUrl(filePath, src);
+ }
+ }
+ }
+
+ for (var tag in document.queryAll('link')) {
+ var href = tag.attributes['href'];
+ var rel = tag.attributes['rel'];
+ if (rel == 'component' || rel == 'components' || rel == 'import') {
+ tag.remove();
+ } else if (href != null && rewriteUrls && !hasCss) {
+ // Only rewrite URL if rewrite on and we're not CSS polyfilling.
+ tag.attributes['href'] = pathMapper.transformUrl(filePath, href);
+ }
+ }
+
+ if (rewriteUrls) {
+ // Transform any element's attribute which is a relative URL.
+ new _AttributeUrlTransform(filePath, pathMapper).visit(document);
+ }
+
+ if (hasCss) {
+ var newCss = pathMapper.mangle(path.basename(filePath), '.css', true);
+ var linkElem = new Element.html(
+ '<link rel="stylesheet" type="text/css" href="$newCss">');
+ document.head.insertBefore(linkElem, null);
+ }
+
+ var styles = document.queryAll('style');
+ if (styles.length > 0) {
+ var allCss = new StringBuffer();
+ fileInfo.styleSheets.forEach((styleSheet) =>
+ allCss.write(emitStyleSheet(styleSheet, fileInfo)));
+ styles[0].nodes.clear();
+ styles[0].nodes.add(new Text(allCss.toString()));
+ for (var i = styles.length - 1; i > 0 ; i--) {
+ styles[i].remove();
+ }
+ }
+
+ // TODO(jmesserly): put this in the global CSS file?
+ // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#css-additions
+ document.head.nodes.insert(0, parseFragment(
+ '<style>template { display: none; }</style>'));
+
+ // Move all <element> declarations to the main HTML file
+ // TODO(sigmund): remove this once we have HTMLImports implemented.
+ for (var c in global.components.values) {
+ document.body.nodes.insert(0, new Text('\n'));
+ var fragment = c.element;
+ for (var tag in fragment.queryAll('script')) {
+ // TODO(sigmund): leave script tags around when we start using "boot.js"
+ if (tag.attributes['type'] == 'application/dart') {
+ tag.remove();
+ }
+ }
+ document.body.nodes.insert(0, fragment);
+ }
+
+ if (!shadowDomFound) {
+ // TODO(jmesserly): we probably shouldn't add this automatically.
+ document.body.nodes.add(parseFragment('<script type="text/javascript" '
+ 'src="packages/shadow_dom/shadow_dom.debug.js"></script>\n'));
+
+ // JS interop code required for Polymer CSS shimming.
+ document.body.nodes.add(parseFragment('<script type="text/javascript" '
+ 'src="packages/browser/interop.js"></script>\n'));
+ }
+
+ var bootstrapScript = parseFragment(
+ '<script type="application/dart" src="$bootstrapOutName"></script>');
+ if (dartLoaderTag == null) {
+ document.body.nodes.add(bootstrapScript);
+ // TODO(jmesserly): turn this warning on.
+ //messages.warning('Missing script to load Dart. '
+ // 'Please add this line to your HTML file: $dartLoader',
+ // document.body.sourceSpan);
+ // TODO(sigmund): switch to 'boot.js'
+ document.body.nodes.add(parseFragment('<script type="text/javascript" '
+ 'src="packages/browser/dart.js"></script>\n'));
+ } else if (dartLoaderTag.parent != document.body) {
+ document.body.nodes.add(bootstrapScript);
+ } else {
+ document.body.insertBefore(bootstrapScript, dartLoaderTag);
+ }
+
+ // Insert the "auto-generated" comment after the doctype, otherwise IE will
+ // go into quirks mode.
+ int commentIndex = 0;
+ DocumentType doctype =
+ document.nodes.firstWhere((n) => n is DocumentType, orElse: () => null);
+ if (doctype != null) {
+ commentIndex = document.nodes.indexOf(doctype) + 1;
+ // TODO(jmesserly): the html5lib parser emits a warning for missing
+ // doctype, but it allows you to put it after comments. Presumably they do
+ // this because some comments won't force IE into quirks mode (sigh). See
+ // this link for more info:
+ // http://bugzilla.validator.nu/show_bug.cgi?id=836
+ // For simplicity we emit the warning always, like validator.nu does.
+ if (doctype.tagName != 'html' || commentIndex != 1) {
+ messages.warning('file should start with <!DOCTYPE html> '
+ 'to avoid the possibility of it being parsed in quirks mode in IE. '
+ 'See http://www.w3.org/TR/html5-diff/#doctype', doctype.sourceSpan);
+ }
+ }
+ document.nodes.insert(commentIndex, parseFragment(
+ '\n<!-- This file was auto-generated from $filePath. -->\n'));
+}
diff --git a/pkg/polymer/lib/src/file_system.dart b/pkg/polymer/lib/src/file_system.dart
new file mode 100644
index 0000000..c6af55c
--- /dev/null
+++ b/pkg/polymer/lib/src/file_system.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2012, 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.
+
+/** Abstraction for file systems and utility functions to manipulate paths. */
+library file_system;
+
+import 'dart:async';
+
+/**
+ * Abstraction around file system access to work in a variety of different
+ * environments.
+ */
+abstract class FileSystem {
+ /**
+ * Apply all pending writes. Until this method is called, writeString is not
+ * guaranteed to have any observable impact.
+ */
+ Future flush();
+
+ /**
+ * Reads bytes if possible, but falls back to text if running in a browser.
+ * Return type is either [Future<List<int>>] or [Future<String>].
+ */
+ Future readTextOrBytes(String path);
+
+ /* Like [readTextOrBytes], but decodes bytes as UTF-8. Used for Dart code. */
+ Future<String> readText(String path);
+
+ /**
+ * Writes [text] to file at [path]. Call flush to insure that changes are
+ * visible.
+ */
+ void writeString(String path, String text);
+}
diff --git a/pkg/polymer/lib/src/file_system/console.dart b/pkg/polymer/lib/src/file_system/console.dart
new file mode 100644
index 0000000..6776745
--- /dev/null
+++ b/pkg/polymer/lib/src/file_system/console.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2012, 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.
+
+library console;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:utf';
+import 'package:polymer/src/file_system.dart';
+
+/** File system implementation for console VM (i.e. no browser). */
+class ConsoleFileSystem implements FileSystem {
+
+ /** Pending futures for file write requests. */
+ final _pending = <String, Future>{};
+
+ Future flush() => Future.wait(_pending.values.toList());
+
+ void writeString(String path, String text) {
+ if(!_pending.containsKey(path)) {
+ _pending[path] = new File(path).open(mode: FileMode.WRITE)
+ .then((file) => file.writeString(text))
+ .then((file) => file.close())
+ .whenComplete(() { _pending.remove(path); });
+ }
+ }
+
+ // TODO(jmesserly): even better would be to pass the RandomAccessFile directly
+ // to html5lib. This will require a further restructuring of FileSystem.
+ // Probably it just needs "readHtml" and "readText" methods.
+ Future<List<int>> readTextOrBytes(String path) {
+ return new File(path).open().then(
+ (file) => file.length().then((length) {
+ // TODO(jmesserly): is this guaranteed to read all of the bytes?
+ var buffer = new List<int>(length);
+ return file.readInto(buffer, 0, length)
+ .then((_) => file.close())
+ .then((_) => buffer);
+ }));
+ }
+
+ // TODO(jmesserly): do we support any encoding other than UTF-8 for Dart?
+ Future<String> readText(String path) {
+ return readTextOrBytes(path).then(decodeUtf8);
+ }
+}
diff --git a/pkg/polymer/lib/src/files.dart b/pkg/polymer/lib/src/files.dart
new file mode 100644
index 0000000..9fea763
--- /dev/null
+++ b/pkg/polymer/lib/src/files.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2012, 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.
+
+library files;
+
+import 'package:html5lib/dom.dart';
+
+/** An input file to process by the template compiler. */
+class SourceFile {
+ static const int HTML = 1;
+ static const int DART = 2;
+ static const int STYLESHEET = 3;
+
+ final String path;
+ final int type;
+
+ Document document;
+
+ /** Dart code or contents of a linked style sheet. */
+ String code;
+
+ SourceFile(this.path, {this.type: HTML});
+
+ bool get isDart => type == DART;
+ bool get isHtml => type == HTML;
+ bool get isStyleSheet => type == STYLESHEET;
+
+ String toString() => "#<SourceFile $path>";
+}
+
+/** An output file to generated by the template compiler. */
+class OutputFile {
+ final String path;
+ final String contents;
+
+ /**
+ * Path to the source file that was transformed into this OutputFile, `null`
+ * for files that are generated and do not correspond to an input
+ * [SourceFile].
+ */
+ final String source;
+
+ OutputFile(this.path, this.contents, {this.source});
+
+ String toString() => "#<OutputFile $path>";
+}
diff --git a/pkg/polymer/lib/src/html5_utils.dart b/pkg/polymer/lib/src/html5_utils.dart
new file mode 100644
index 0000000..391c12b
--- /dev/null
+++ b/pkg/polymer/lib/src/html5_utils.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2012, 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.
+
+// TODO(jmesserly): html5lib might be a better home for this.
+// But at the moment we only need it here.
+
+library html5_utils;
+
+/**
+ * HTML attributes that expect a URL value.
+ * <http://dev.w3.org/html5/spec/section-index.html#attributes-1>
+ *
+ * Every one of these attributes is a URL in every context where it is used in
+ * the DOM. The comments show every DOM element where an attribute can be used.
+ */
+const urlAttributes = const [
+ 'action', // in form
+ 'background', // in body
+ 'cite', // in blockquote, del, ins, q
+ 'data', // in object
+ 'formaction', // in button, input
+ 'href', // in a, area, link, base, command
+ 'icon', // in command
+ 'manifest', // in html
+ 'poster', // in video
+ 'src', // in audio, embed, iframe, img, input, script, source, track,
+ // video
+];
diff --git a/pkg/polymer/lib/src/info.dart b/pkg/polymer/lib/src/info.dart
new file mode 100644
index 0000000..9a7b530
--- /dev/null
+++ b/pkg/polymer/lib/src/info.dart
@@ -0,0 +1,330 @@
+// 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.
+
+/**
+ * Datatypes holding information extracted by the analyzer and used by later
+ * phases of the compiler.
+ */
+library polymer.src.info;
+
+import 'dart:collection' show SplayTreeMap, LinkedHashMap;
+
+import 'package:analyzer_experimental/src/generated/ast.dart';
+import 'package:csslib/visitor.dart';
+import 'package:html5lib/dom.dart';
+import 'package:source_maps/span.dart' show Span;
+
+import 'dart_parser.dart' show DartCodeInfo;
+import 'messages.dart';
+import 'summary.dart';
+import 'utils.dart';
+
+/**
+ * Information that is global. Roughly corresponds to `window` and `document`.
+ */
+class GlobalInfo {
+ /**
+ * Pseudo-element names exposed in a component via a pseudo attribute.
+ * The name is only available from CSS (not Dart code) so they're mangled.
+ * The same pseudo-element in different components maps to the same
+ * mangled name (as the pseudo-element is scoped inside of the component).
+ */
+ final Map<String, String> pseudoElements = <String, String>{};
+
+ /** All components declared in the application. */
+ final Map<String, ComponentInfo> components = new SplayTreeMap();
+}
+
+/**
+ * Information for any library-like input. We consider each HTML file a library,
+ * and each component declaration a library as well. Hence we use this as a base
+ * class for both [FileInfo] and [ComponentInfo]. Both HTML files and components
+ * can have .dart code provided by the user for top-level user scripts and
+ * component-level behavior code. This code can either be inlined in the HTML
+ * file or included in a script tag with the "src" attribute.
+ */
+abstract class LibraryInfo implements LibrarySummary {
+
+ /** Whether there is any code associated with the page/component. */
+ bool get codeAttached => inlinedCode != null || externalFile != null;
+
+ /**
+ * The actual inlined code. Use [userCode] if you want the code from this file
+ * or from an external file.
+ */
+ DartCodeInfo inlinedCode;
+
+ /**
+ * If this library's code was loaded using a script tag (e.g. in a component),
+ * [externalFile] has the path to such Dart file relative from the compiler's
+ * base directory.
+ */
+ UrlInfo externalFile;
+
+ /** Info asscociated with [externalFile], if any. */
+ FileInfo externalCode;
+
+ /**
+ * The inverse of [externalCode]. If this .dart file was imported via a script
+ * tag, this refers to the HTML file that imported it.
+ */
+ LibraryInfo htmlFile;
+
+ /** File where the top-level code was defined. */
+ UrlInfo get dartCodeUrl;
+
+ /**
+ * Name of the file that will hold any generated Dart code for this library
+ * unit. Note this is initialized after parsing.
+ */
+ String outputFilename;
+
+ /** Parsed cssSource. */
+ List<StyleSheet> styleSheets = [];
+
+ /** This is used in transforming Dart code to track modified files. */
+ bool modified = false;
+
+ /**
+ * This is used in transforming Dart code to compute files that reference
+ * [modified] files.
+ */
+ List<FileInfo> referencedBy = [];
+
+ /**
+ * Components used within this library unit. For [FileInfo] these are
+ * components used directly in the page. For [ComponentInfo] these are
+ * components used within their shadowed template.
+ */
+ final Map<ComponentSummary, bool> usedComponents =
+ new LinkedHashMap<ComponentSummary, bool>();
+
+ /**
+ * The actual code, either inlined or from an external file, or `null` if none
+ * was defined.
+ */
+ DartCodeInfo get userCode =>
+ externalCode != null ? externalCode.inlinedCode : inlinedCode;
+}
+
+/** Information extracted at the file-level. */
+class FileInfo extends LibraryInfo implements HtmlFileSummary {
+ /** Relative path to this file from the compiler's base directory. */
+ final UrlInfo inputUrl;
+
+ /**
+ * Whether this file should be treated as the entry point of the web app, i.e.
+ * the file users navigate to in their browser. This will be true if this file
+ * was passed in the command line to the dwc compiler, and the
+ * `--components_only` flag was omitted.
+ */
+ final bool isEntryPoint;
+
+ // TODO(terry): Ensure that that the libraryName is a valid identifier:
+ // a..z || A..Z || _ [a..z || A..Z || 0..9 || _]*
+ String get libraryName =>
+ path.basename(inputUrl.resolvedPath).replaceAll('.', '_');
+
+ /** File where the top-level code was defined. */
+ UrlInfo get dartCodeUrl => externalFile != null ? externalFile : inputUrl;
+
+ /**
+ * All custom element definitions in this file. This may contain duplicates.
+ * Normally you should use [components] for lookup.
+ */
+ final List<ComponentInfo> declaredComponents = new List<ComponentInfo>();
+
+ /**
+ * All custom element definitions defined in this file or imported via
+ *`<link rel='components'>` tag. Maps from the tag name to the component
+ * information. This map is sorted by the tag name.
+ */
+ final Map<String, ComponentSummary> components =
+ new SplayTreeMap<String, ComponentSummary>();
+
+ /** Files imported with `<link rel="import">` */
+ final List<UrlInfo> componentLinks = <UrlInfo>[];
+
+ /** Files imported with `<link rel="stylesheet">` */
+ final List<UrlInfo> styleSheetHrefs = <UrlInfo>[];
+
+ /** Root is associated with the body node. */
+ Element body;
+
+ FileInfo(this.inputUrl, [this.isEntryPoint = false]);
+
+ /**
+ * Query for an [Element] matching the provided [tag], starting from the
+ * [body].
+ */
+ Element query(String tag) => body.query(tag);
+}
+
+
+/** Information about a web component definition declared locally. */
+// TODO(sigmund): use a mixin to pull in ComponentSummary.
+class ComponentInfo extends LibraryInfo implements ComponentSummary {
+ /** The file that declares this component. */
+ final FileInfo declaringFile;
+
+ /** The component tag name, defined with the `name` attribute on `element`. */
+ final String tagName;
+
+ /**
+ * The tag name that this component extends, defined with the `extends`
+ * attribute on `element`.
+ */
+ final String extendsTag;
+
+ /**
+ * The component info associated with the [extendsTag] name, if any.
+ * This will be `null` if the component extends a built-in HTML tag, or
+ * if the analyzer has not run yet.
+ */
+ ComponentSummary extendsComponent;
+
+ /** The Dart class containing the component's behavior. */
+ String className;
+
+ /** The Dart class declaration. */
+ ClassDeclaration get classDeclaration => _classDeclaration;
+ ClassDeclaration _classDeclaration;
+
+ /** The declaring `<element>` tag. */
+ final Node element;
+
+ /** File where this component was defined. */
+ UrlInfo get dartCodeUrl => externalFile != null
+ ? externalFile : declaringFile.inputUrl;
+
+ /**
+ * True if [tagName] was defined by more than one component. If this happened
+ * we will skip over the component.
+ */
+ bool hasConflict = false;
+
+ ComponentInfo(this.element, this.declaringFile, this.tagName,
+ this.extendsTag);
+
+ /**
+ * Gets the HTML tag extended by the base of the component hierarchy.
+ * Equivalent to [extendsTag] if this inherits directly from an HTML element,
+ * in other words, if [extendsComponent] is null.
+ */
+ String get baseExtendsTag =>
+ extendsComponent == null ? extendsTag : extendsComponent.baseExtendsTag;
+
+ Span get sourceSpan => element.sourceSpan;
+
+ /** Is apply-author-styles enabled. */
+ bool get hasAuthorStyles =>
+ element.attributes.containsKey('apply-author-styles');
+
+ /**
+ * Finds the declaring class, and initializes [className] and
+ * [classDeclaration]. Also [userCode] is generated if there was no script.
+ */
+ void findClassDeclaration(Messages messages) {
+ var constructor = element.attributes['constructor'];
+ className = constructor != null ? constructor :
+ toCamelCase(tagName, startUppercase: true);
+
+ // If we don't have any code, generate a small class definition, and
+ // pretend the user wrote it as inlined code.
+ if (userCode == null) {
+ var superclass = extendsComponent != null ? extendsComponent.className
+ : 'autogenerated.PolymerElement';
+ inlinedCode = new DartCodeInfo(null, null, [],
+ 'class $className extends $superclass {\n}', null);
+ }
+
+ var code = userCode.code;
+ _classDeclaration = userCode.findClass(className);
+ if (_classDeclaration == null) {
+ // Check for deprecated x-tags implied constructor.
+ if (tagName.startsWith('x-') && constructor == null) {
+ var oldCtor = toCamelCase(tagName.substring(2), startUppercase: true);
+ _classDeclaration = userCode.findClass(oldCtor);
+ if (_classDeclaration != null) {
+ messages.warning('Implied constructor name for x-tags has changed to '
+ '"$className". You should rename your class or add a '
+ 'constructor="$oldCtor" attribute to the element declaration. '
+ 'Also custom tags are not required to start with "x-" if their '
+ 'name has at least one dash.',
+ element.sourceSpan);
+ className = oldCtor;
+ }
+ }
+
+ if (_classDeclaration == null) {
+ messages.error('please provide a class definition '
+ 'for $className:\n $code', element.sourceSpan);
+ return;
+ }
+ }
+ }
+
+ String toString() => '#<ComponentInfo $tagName '
+ '${inlinedCode != null ? "inline" : "from ${dartCodeUrl.resolvedPath}"}>';
+}
+
+
+/**
+ * Information extracted about a URL that refers to another file. This is
+ * mainly introduced to be able to trace back where URLs come from when
+ * reporting errors.
+ */
+class UrlInfo {
+ /** Original url. */
+ final String url;
+
+ /** Path that the URL points to. */
+ final String resolvedPath;
+
+ /** Original source location where the URL was extracted from. */
+ final Span sourceSpan;
+
+ UrlInfo(this.url, this.resolvedPath, this.sourceSpan);
+
+ /**
+ * Resolve a path from an [url] found in a file located at [inputUrl].
+ * Returns null for absolute [url]. Unless [ignoreAbsolute] is true, reports
+ * an error message if the url is an absolute url.
+ */
+ static UrlInfo resolve(String url, UrlInfo inputUrl, Span span,
+ String packageRoot, Messages messages, {bool ignoreAbsolute: false}) {
+
+ var uri = Uri.parse(url);
+ if (uri.host != '' || (uri.scheme != '' && uri.scheme != 'package')) {
+ if (!ignoreAbsolute) {
+ messages.error('absolute paths not allowed here: "$url"', span);
+ }
+ return null;
+ }
+
+ var target;
+ if (url.startsWith('package:')) {
+ target = path.join(packageRoot, url.substring(8));
+ } else if (path.isAbsolute(url)) {
+ if (!ignoreAbsolute) {
+ messages.error('absolute paths not allowed here: "$url"', span);
+ }
+ return null;
+ } else {
+ target = path.join(path.dirname(inputUrl.resolvedPath), url);
+ url = pathToUrl(path.normalize(path.join(
+ path.dirname(inputUrl.url), url)));
+ }
+ target = path.normalize(target);
+
+ return new UrlInfo(url, target, span);
+ }
+
+ bool operator ==(UrlInfo other) =>
+ url == other.url && resolvedPath == other.resolvedPath;
+
+ int get hashCode => resolvedPath.hashCode;
+
+ String toString() => "#<UrlInfo url: $url, resolvedPath: $resolvedPath>";
+}
diff --git a/pkg/polymer/lib/src/messages.dart b/pkg/polymer/lib/src/messages.dart
new file mode 100644
index 0000000..5dc6247
--- /dev/null
+++ b/pkg/polymer/lib/src/messages.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2012, 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.
+
+library messages;
+
+import 'dart:json' as json;
+
+import 'package:barback/barback.dart' show TransformLogger;
+import 'package:source_maps/span.dart' show Span;
+import 'package:logging/logging.dart' show Level;
+
+import 'compiler_options.dart';
+import 'utils.dart';
+
+/** Map between error levels and their display color. */
+final Map<Level, String> _ERROR_COLORS = (() {
+ var colorsMap = new Map<Level, String>();
+ colorsMap[Level.SEVERE] = RED_COLOR;
+ colorsMap[Level.WARNING] = MAGENTA_COLOR;
+ colorsMap[Level.INFO] = GREEN_COLOR;
+ return colorsMap;
+})();
+
+/** A single message from the compiler. */
+class Message {
+ final Level level;
+ final String message;
+ final Span span;
+ final bool useColors;
+
+ Message(this.level, this.message, {this.span, this.useColors: false});
+
+ String get kind => level == Level.SEVERE ? 'error' :
+ (level == Level.WARNING ? 'warning' : 'info');
+
+ String toString() {
+ var output = new StringBuffer();
+ bool colors = useColors && _ERROR_COLORS.containsKey(level);
+ var levelColor = _ERROR_COLORS[level];
+ if (colors) output.write(levelColor);
+ output..write(kind)..write(' ');
+ if (colors) output.write(NO_COLOR);
+
+ if (span == null) {
+ output.write(message);
+ } else {
+ output.write(span.getLocationMessage(message, useColors: colors,
+ color: levelColor));
+ }
+
+ return output.toString();
+ }
+
+ String toJson() {
+ if (span == null) return toString();
+ return json.stringify([{
+ 'method': kind,
+ 'params': {
+ 'file': span.sourceUrl,
+ 'message': message,
+ 'line': span.start.line + 1,
+ 'charStart': span.start.offset,
+ 'charEnd': span.end.offset,
+ }
+ }]);
+ }
+}
+
+/**
+ * This class tracks and prints information, warnings, and errors emitted by the
+ * compiler.
+ */
+class Messages implements TransformLogger {
+ final CompilerOptions options;
+ final bool shouldPrint;
+
+ final List<Message> messages = <Message>[];
+
+ Messages({CompilerOptions options, this.shouldPrint: true})
+ : options = options != null ? options : new CompilerOptions();
+
+ /**
+ * Creates a new instance of [Messages] which doesn't write messages to
+ * the console.
+ */
+ Messages.silent(): this(shouldPrint: false);
+
+ /**
+ * True if we have an error that prevents correct codegen.
+ * For example, if we failed to read an input file.
+ */
+ bool get hasErrors => messages.any((m) => m.level == Level.SEVERE);
+
+ // Convenience methods for testing
+ int get length => messages.length;
+
+ Message operator[](int index) => messages[index];
+
+ void clear() {
+ messages.clear();
+ }
+
+ /** [message] is considered a static compile-time error by the Dart lang. */
+ void error(String message, [Span span]) {
+ var msg = new Message(Level.SEVERE, message, span: span,
+ useColors: options.useColors);
+
+ messages.add(msg);
+ printMessage(msg);
+ }
+
+ /** [message] is considered a type warning by the Dart lang. */
+ void warning(String message, [Span span]) {
+ if (options.warningsAsErrors) {
+ error(message, span);
+ } else {
+ var msg = new Message(Level.WARNING, message,
+ span: span, useColors: options.useColors);
+
+ messages.add(msg);
+ printMessage(msg);
+ }
+ }
+
+ /// the list of error messages. Empty list, if there are no error messages.
+ List<Message> get errors =>
+ messages.where((m) => m.level == Level.SEVERE).toList();
+
+ /// the list of warning messages. Empty list if there are no warning messages.
+ List<Message> get warnings =>
+ messages.where((m) => m.level == Level.WARNING).toList();
+
+ /**
+ * [message] at [span] will tell the user about what the compiler
+ * is doing.
+ */
+ void info(String message, [Span span]) {
+ var msg = new Message(Level.INFO, message, span: span,
+ useColors: options.useColors);
+
+ messages.add(msg);
+ if (options.verbose) printMessage(msg);
+ }
+
+ void printMessage(msg) {
+ if (shouldPrint) print(options.jsonFormat ? msg.toJson() : msg);
+ }
+}
diff --git a/pkg/polymer/lib/src/paths.dart b/pkg/polymer/lib/src/paths.dart
new file mode 100644
index 0000000..34037d9
--- /dev/null
+++ b/pkg/polymer/lib/src/paths.dart
@@ -0,0 +1,170 @@
+// 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.
+
+/**
+ * Holds path information that is used by the WebUI compiler to find files,
+ * compute their output location and relative paths between them.
+ */
+library polymer.src.paths;
+
+import 'info.dart' show UrlInfo;
+import 'messages.dart';
+import 'summary.dart';
+import 'utils.dart' show path, pathToUrl;
+
+/**
+ * Stores information about paths and computes mappings between input and output
+ * path locations.
+ */
+class PathMapper {
+ /**
+ * Common prefix to all input paths that are read from the file system. The
+ * output generated by the compiler will reflect the directory structure
+ * starting from [_baseDir]. For instance, if [_baseDir] is `a/b/c` and
+ * [_outputDir] is `g/h/`, then the corresponding output file for
+ * `a/b/c/e/f.html` will be under `g/h/e/f.html.dart`.
+ */
+ final String _baseDir;
+
+ /** Base path where all output is generated. */
+ final String _outputDir;
+
+ /** The package root directory. */
+ final String packageRoot;
+
+ /** Whether to add prefixes and to output file names. */
+ final bool _mangleFilenames;
+
+ final bool _rewriteUrls;
+
+ bool get _rewritePackageImports => _rewriteUrls || !_mangleFilenames;
+
+ /** Default prefix added to all filenames. */
+ static const String _DEFAULT_PREFIX = '_';
+
+ PathMapper(String baseDir, String outputDir, this.packageRoot,
+ bool forceMangle, this._rewriteUrls)
+ : _baseDir = baseDir,
+ _outputDir = outputDir,
+ _mangleFilenames = forceMangle || (baseDir == outputDir);
+
+ /** Add a prefix and [suffix] if [_mangleFilenames] is true */
+ String mangle(String name, String suffix, [bool forceSuffix = false]) =>
+ _mangleFilenames ? "$_DEFAULT_PREFIX$name$suffix"
+ : (forceSuffix ? "$name$suffix" : name);
+
+ /**
+ * Checks that `input.resolvedPath` is a valid input path. It must be in
+ * [_baseDir] and must not be in the [_outputDir]. If not, an error message
+ * is added to [messages].
+ */
+ bool checkInputPath(UrlInfo input, Messages messages) {
+ if (_mangleFilenames) return true;
+ var canonicalized = path.normalize(input.resolvedPath);
+ var parentDir = '..${path.separator}';
+ if (!path.relative(canonicalized, from: _outputDir).startsWith(parentDir)) {
+ messages.error(
+ 'The file ${input.resolvedPath} cannot be processed. '
+ 'Files cannot be under the output folder (${_outputDir}).',
+ input.sourceSpan);
+ return false;
+ }
+ if (path.relative(canonicalized, from: _baseDir).startsWith(parentDir)) {
+ messages.error(
+ 'The file ${input.resolvedPath} cannot be processed. '
+ 'All processed files must be under the base folder (${_baseDir}), you'
+ ' can specify the base folder using the --basedir flag.',
+ input.sourceSpan);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * The path to the output file corresponding to [input], by adding
+ * [_DEFAULT_PREFIX] and a [suffix] to its file name.
+ */
+ String outputPath(String input, String suffix, [bool forceSuffix = false]) =>
+ path.join(outputDirPath(input),
+ mangle(path.basename(input), suffix, forceSuffix));
+
+ /** The path to the output file corresponding to [info]. */
+ String outputLibraryPath(LibrarySummary lib) =>
+ path.join(outputDirPath(lib.dartCodeUrl.resolvedPath),
+ lib.outputFilename);
+
+ /** The corresponding output directory for [input]'s directory. */
+ String outputDirPath(String input) {
+ return _rewritePackages(path.normalize(
+ path.join(_outputDir, path.relative(
+ path.dirname(input), from: _baseDir))));
+ }
+
+ /**
+ * We deal with `packages/` directories in a very special way. We assume it
+ * points to resources loaded from other pub packages. If an output directory
+ * is specified, the compiler will create a packages symlink so that
+ * `package:` imports work.
+ *
+ * To make it possible to share components through pub, we allow using tags of
+ * the form `<link rel="import" href="packages/...">`, so that you can
+ * refer to components within the packages symlink. Regardless of whether an
+ * --out option was given to the compiler, we don't want to generate files
+ * inside `packages/` for those components. Instead we will generate such
+ * code in a special directory called `_from_packages/`.
+ */
+ String _rewritePackages(String outputPath) {
+ // TODO(jmesserly): this should match against packageRoot instead.
+ if (!outputPath.contains('packages')) return outputPath;
+ if (!_rewritePackageImports) return outputPath;
+ var segments = path.split(outputPath);
+ return path.joinAll(
+ segments.map((s) => s == 'packages' ? '_from_packages' : s));
+ }
+
+ /**
+ * Returns a url to import/export the output library represented by [target]
+ * from the output library of [src]. In other words, a url to import or export
+ * `target.outputFilename` from `src.outputFilename`.
+ */
+ String importUrlFor(LibrarySummary src, LibrarySummary target) {
+ if (!_rewritePackageImports &&
+ target.dartCodeUrl.url.startsWith('package:')) {
+ return pathToUrl(path.join(path.dirname(target.dartCodeUrl.url),
+ target.outputFilename));
+ }
+ var srcDir = path.dirname(src.dartCodeUrl.resolvedPath);
+ var relDir = path.relative(
+ path.dirname(target.dartCodeUrl.resolvedPath), from: srcDir);
+ return pathToUrl(_rewritePackages(path.normalize(
+ path.join(relDir, target.outputFilename))));
+ }
+
+ /**
+ * Transforms a [target] url seen in [src] (e.g. a Dart import, a .css href in
+ * an HTML file, etc) into a corresponding url from the output file associated
+ * with [src]. This will keep 'package:', 'dart:', path-absolute, and absolute
+ * urls intact, but it will fix relative paths to walk from the output
+ * directory back to the input directory. An exception will be thrown if
+ * [target] is not under [_baseDir].
+ */
+ String transformUrl(String src, String target) {
+ var uri = Uri.parse(target);
+ if (uri.isAbsolute) return target;
+ if (!uri.scheme.isEmpty) return target;
+ if (!uri.host.isEmpty) return target;
+ if (uri.path.isEmpty) return target; // Implies standalone ? or # in URI.
+ if (path.isAbsolute(target)) return target;
+
+ return pathToUrl(path.normalize(path.relative(
+ path.join(path.dirname(src), target), from: outputDirPath(src))));
+ }
+}
+
+/**
+ * Returns a "mangled" name, with a prefix and [suffix] depending on the
+ * compiler's settings. [forceSuffix] causes [suffix] to be appended even if
+ * the compiler is not mangling names.
+ */
+typedef String NameMangler(String name, String suffix, [bool forceSuffix]);
diff --git a/pkg/polymer/lib/src/summary.dart b/pkg/polymer/lib/src/summary.dart
new file mode 100644
index 0000000..841870a
--- /dev/null
+++ b/pkg/polymer/lib/src/summary.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2012, 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.
+
+/**
+ * Summary information for components and libraries.
+ *
+ * These classes are used for modular compilation. Summaries are a subset of the
+ * information collected by Info objects (see `info.dart`). When we are
+ * compiling a single file, the information extracted from that file is stored
+ * as info objects, but any information that is needed from other files (like
+ * imported components) is stored as a summary.
+ */
+library polymer.src.summary;
+
+import 'package:source_maps/span.dart' show Span;
+
+// TODO(sigmund): consider moving UrlInfo out of info.dart
+import 'info.dart' show UrlInfo;
+
+/**
+ * Summary information from other library-like objects, which includes HTML
+ * components and dart libraries).
+ */
+class LibrarySummary {
+ /** Path to the sources represented by this summary. */
+ final UrlInfo dartCodeUrl;
+
+ /** Name given to this source after it was compiled. */
+ final String outputFilename;
+
+ LibrarySummary(this.dartCodeUrl, this.outputFilename);
+}
+
+/** Summary information for an HTML file that defines custom elements. */
+class HtmlFileSummary extends LibrarySummary {
+ /**
+ * Summary of each component defined either explicitly the HTML file or
+ * included transitively from `<link rel="import">` tags.
+ */
+ final Map<String, ComponentSummary> components;
+
+ HtmlFileSummary(UrlInfo dartCodeUrl, String outputFilename, this.components)
+ : super(dartCodeUrl, outputFilename);
+}
+
+/** Information about a web component definition. */
+class ComponentSummary extends LibrarySummary {
+ /** The component tag name, defined with the `name` attribute on `element`. */
+ final String tagName;
+
+ /**
+ * The tag name that this component extends, defined with the `extends`
+ * attribute on `element`.
+ */
+ final String extendsTag;
+
+ /**
+ * The Dart class containing the component's behavior, derived from tagName or
+ * defined in the `constructor` attribute on `element`.
+ */
+ final String className;
+
+ /** Summary of the base component, if any. */
+ final ComponentSummary extendsComponent;
+
+ /**
+ * True if [tagName] was defined by more than one component. Used internally
+ * by the analyzer. Conflicting component will be skipped by the compiler.
+ */
+ bool hasConflict;
+
+ /** Original span where this component is declared. */
+ final Span sourceSpan;
+
+ ComponentSummary(UrlInfo dartCodeUrl, String outputFilename,
+ this.tagName, this.extendsTag, this.className, this.extendsComponent,
+ this.sourceSpan, [this.hasConflict = false])
+ : super(dartCodeUrl, outputFilename);
+
+ /**
+ * Gets the HTML tag extended by the base of the component hierarchy.
+ * Equivalent to [extendsTag] if this inherits directly from an HTML element,
+ * in other words, if [extendsComponent] is null.
+ */
+ String get baseExtendsTag =>
+ extendsComponent == null ? extendsTag : extendsComponent.baseExtendsTag;
+}
diff --git a/pkg/polymer/lib/src/utils.dart b/pkg/polymer/lib/src/utils.dart
new file mode 100644
index 0000000..da79dc2
--- /dev/null
+++ b/pkg/polymer/lib/src/utils.dart
@@ -0,0 +1,177 @@
+// Copyright (c) 2012, 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.
+
+library polymer.src.utils;
+
+import 'dart:async';
+import 'package:path/path.dart' show Builder;
+export 'utils_observe.dart' show toCamelCase, toHyphenedName;
+
+/**
+ * An instance of the pathos library builder. We could just use the default
+ * builder in pathos, but we add this indirection to make it possible to run
+ * unittest for windows paths.
+ */
+Builder path = new Builder();
+
+/** Convert a OS specific path into a url. */
+String pathToUrl(String relPath) =>
+ (path.separator == '/') ? relPath : path.split(relPath).join('/');
+
+/**
+ * Invokes [callback], logs how long it took to execute in ms, and returns
+ * whatever [callback] returns. The log message will be printed if [printTime]
+ * is true.
+ */
+time(String logMessage, callback(),
+ {bool printTime: false, bool useColors: false}) {
+ final watch = new Stopwatch();
+ watch.start();
+ var result = callback();
+ watch.stop();
+ final duration = watch.elapsedMilliseconds;
+ if (printTime) {
+ _printMessage(logMessage, duration, useColors);
+ }
+ return result;
+}
+
+/**
+ * Invokes [callback], logs how long it takes from the moment [callback] is
+ * executed until the future it returns is completed. Returns the future
+ * returned by [callback]. The log message will be printed if [printTime]
+ * is true.
+ */
+Future asyncTime(String logMessage, Future callback(),
+ {bool printTime: false, bool useColors: false}) {
+ final watch = new Stopwatch();
+ watch.start();
+ return callback()..then((_) {
+ watch.stop();
+ final duration = watch.elapsedMilliseconds;
+ if (printTime) {
+ _printMessage(logMessage, duration, useColors);
+ }
+ });
+}
+
+void _printMessage(String logMessage, int duration, bool useColors) {
+ var buf = new StringBuffer();
+ buf.write(logMessage);
+ for (int i = logMessage.length; i < 60; i++) buf.write(' ');
+ buf.write(' -- ');
+ if (useColors) {
+ buf.write(GREEN_COLOR);
+ }
+ if (duration < 10) buf.write(' ');
+ if (duration < 100) buf.write(' ');
+ buf..write(duration)..write(' ms');
+ if (useColors) {
+ buf.write(NO_COLOR);
+ }
+ print(buf.toString());
+}
+
+// Color constants used for generating messages.
+final String GREEN_COLOR = '\u001b[32m';
+final String RED_COLOR = '\u001b[31m';
+final String MAGENTA_COLOR = '\u001b[35m';
+final String NO_COLOR = '\u001b[0m';
+
+/** A future that waits until all added [Future]s complete. */
+// TODO(sigmund): this should be part of the futures/core libraries.
+class FutureGroup {
+ static const _FINISHED = -1;
+
+ int _pending = 0;
+ Future _failedTask;
+ final Completer<List> _completer = new Completer<List>();
+ final List results = [];
+
+ /** Gets the task that failed, if any. */
+ Future get failedTask => _failedTask;
+
+ /**
+ * Wait for [task] to complete.
+ *
+ * If this group has already been marked as completed, you'll get a
+ * [StateError].
+ *
+ * If this group has a [failedTask], new tasks will be ignored, because the
+ * error has already been signaled.
+ */
+ void add(Future task) {
+ if (_failedTask != null) return;
+ if (_pending == _FINISHED) throw new StateError("Future already completed");
+
+ _pending++;
+ var i = results.length;
+ results.add(null);
+ task.then((res) {
+ results[i] = res;
+ if (_failedTask != null) return;
+ _pending--;
+ if (_pending == 0) {
+ _pending = _FINISHED;
+ _completer.complete(results);
+ }
+ }, onError: (e) {
+ if (_failedTask != null) return;
+ _failedTask = task;
+ _completer.completeError(e, getAttachedStackTrace(e));
+ });
+ }
+
+ Future<List> get future => _completer.future;
+}
+
+
+/**
+ * Escapes [text] for use in a Dart string.
+ * [single] specifies single quote `'` vs double quote `"`.
+ * [triple] indicates that a triple-quoted string, such as `'''` or `"""`.
+ */
+String escapeDartString(String text, {bool single: true, bool triple: false}) {
+ // Note: don't allocate anything until we know we need it.
+ StringBuffer result = null;
+
+ for (int i = 0; i < text.length; i++) {
+ int code = text.codeUnitAt(i);
+ var replace = null;
+ switch (code) {
+ case 92/*'\\'*/: replace = r'\\'; break;
+ case 36/*r'$'*/: replace = r'\$'; break;
+ case 34/*'"'*/: if (!single) replace = r'\"'; break;
+ case 39/*"'"*/: if (single) replace = r"\'"; break;
+ case 10/*'\n'*/: if (!triple) replace = r'\n'; break;
+ case 13/*'\r'*/: if (!triple) replace = r'\r'; break;
+
+ // Note: we don't escape unicode characters, under the assumption that
+ // writing the file in UTF-8 will take care of this.
+
+ // TODO(jmesserly): do we want to replace any other non-printable
+ // characters (such as \f) for readability?
+ }
+
+ if (replace != null && result == null) {
+ result = new StringBuffer(text.substring(0, i));
+ }
+
+ if (result != null) result.write(replace != null ? replace : text[i]);
+ }
+
+ return result == null ? text : result.toString();
+}
+
+/** Iterates through an infinite sequence, starting from zero. */
+class IntIterator implements Iterator<int> {
+ int _next = -1;
+
+ int get current => _next < 0 ? null : _next;
+
+ bool moveNext() {
+ _next++;
+ return true;
+ }
+}
diff --git a/pkg/polymer/lib/src/utils_observe.dart b/pkg/polymer/lib/src/utils_observe.dart
new file mode 100644
index 0000000..b568d0c
--- /dev/null
+++ b/pkg/polymer/lib/src/utils_observe.dart
@@ -0,0 +1,33 @@
+// 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.
+
+library polymer.src.utils_observe;
+
+/**
+ * Converts a string name with hyphens into an identifier, by removing hyphens
+ * and capitalizing the following letter. Optionally [startUppercase] to
+ * captialize the first letter.
+ */
+String toCamelCase(String hyphenedName, {bool startUppercase: false}) {
+ var segments = hyphenedName.split('-');
+ int start = startUppercase ? 0 : 1;
+ for (int i = start; i < segments.length; i++) {
+ var segment = segments[i];
+ if (segment.length > 0) {
+ // Character between 'a'..'z' mapped to 'A'..'Z'
+ segments[i] = '${segment[0].toUpperCase()}${segment.substring(1)}';
+ }
+ }
+ return segments.join('');
+}
+
+String toHyphenedName(String word) {
+ var sb = new StringBuffer();
+ for (int i = 0; i < word.length; i++) {
+ var lower = word[i].toLowerCase();
+ if (word[i] != lower && i > 0) sb.write('-');
+ sb.write(lower);
+ }
+ return sb.toString();
+}
diff --git a/pkg/polymer/lib/testing/content_shell_test.dart b/pkg/polymer/lib/testing/content_shell_test.dart
new file mode 100644
index 0000000..97398be
--- /dev/null
+++ b/pkg/polymer/lib/testing/content_shell_test.dart
@@ -0,0 +1,229 @@
+// 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.
+
+/**
+ * Helper library to run tests in content_shell
+ */
+library polymer.testing.end2end;
+
+import 'dart:io';
+import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
+import 'package:unittest/unittest.dart';
+import 'package:polymer/dwc.dart' as dwc;
+
+
+/**
+ * Compiles [testFile] with the web-ui compiler, and then runs the output as a
+ * unit test in content_shell.
+ */
+void endToEndTests(String inputDir, String outDir, {List<String> arguments}) {
+ _testHelper(new _TestOptions(inputDir, inputDir, null, outDir,
+ arguments: arguments));
+}
+
+/**
+ * Compiles [testFile] with the web-ui compiler, and then runs the output as a
+ * render test in content_shell.
+ */
+void renderTests(String baseDir, String inputDir, String expectedDir,
+ String outDir, {List<String> arguments, String script, String pattern,
+ bool deleteDir: true}) {
+ _testHelper(new _TestOptions(baseDir, inputDir, expectedDir, outDir,
+ arguments: arguments, script: script, pattern: pattern,
+ deleteDir: deleteDir));
+}
+
+void _testHelper(_TestOptions options) {
+ expect(options, isNotNull);
+
+ var paths = new Directory(options.inputDir).listSync()
+ .where((f) => f is File).map((f) => f.path)
+ .where((p) => p.endsWith('_test.html') && options.pattern.hasMatch(p));
+
+ if (paths.isEmpty) return;
+
+ // First clear the output folder. Otherwise we can miss bugs when we fail to
+ // generate a file.
+ var dir = new Directory(options.outDir);
+ if (dir.existsSync() && options.deleteDir) {
+ print('Cleaning old output for ${path.normalize(options.outDir)}');
+ dir.deleteSync(recursive: true);
+ }
+ dir.createSync();
+
+ for (var filePath in paths) {
+ var filename = path.basename(filePath);
+ test('compile $filename', () {
+ var testArgs = ['-o', options.outDir, '--basedir', options.baseDir,
+ '--deploy']
+ ..addAll(options.compilerArgs)
+ ..add(filePath);
+ expect(dwc.run(testArgs, printTime: false).then((res) {
+ expect(res.messages.length, 0, reason: res.messages.join('\n'));
+ }), completes);
+ });
+ }
+
+ var filenames = paths.map(path.basename).toList();
+ // Sort files to match the order in which run.sh runs diff.
+ filenames.sort();
+
+ // Get the path from "input" relative to "baseDir"
+ var relativeToBase = path.relative(options.inputDir, from: options.baseDir);
+ var finalOutDir = path.join(options.outDir, relativeToBase);
+
+ runTests(String search) {
+ var output;
+
+ for (var filename in filenames) {
+ test('content_shell run $filename$search', () {
+ var args = ['--dump-render-tree',
+ 'file://$finalOutDir/$filename$search'];
+ var env = {'DART_FLAGS': '--checked'};
+ expect(Process.run('content_shell', args, environment: env).then((res) {
+ expect(res.exitCode, 0, reason: 'content_shell exit code: '
+ '${res.exitCode}. Contents of stderr: \n${res.stderr}');
+ var outs = res.stdout.split('#EOF\n')
+ .where((s) => !s.trim().isEmpty).toList();
+ expect(outs.length, 1);
+ output = outs.first;
+ }), completes);
+ });
+
+ test('verify $filename $search', () {
+ expect(output, isNotNull, reason:
+ 'Output not available, maybe content_shell failed to run.');
+ var outPath = path.join(options.outDir, '$filename.txt');
+ new File(outPath).writeAsStringSync(output);
+ if (options.isRenderTest) {
+ var expectedPath = path.join(options.expectedDir, '$filename.txt');
+ var expected = new File(expectedPath).readAsStringSync();
+ expect(output, expected, reason: 'unexpected output for <$filename>');
+ } else {
+ bool passes = matches(
+ new RegExp('All .* tests passed')).matches(output, {});
+ expect(passes, true, reason: 'unit test failed:\n$output');
+ }
+ });
+ }
+ }
+
+ bool compiled = false;
+ ensureCompileToJs() {
+ if (compiled) return;
+ compiled = true;
+
+ for (var filename in filenames) {
+ test('dart2js $filename', () {
+ // TODO(jmesserly): this depends on DWC's output scheme.
+ // Alternatively we could use html5lib to find the script tag.
+ var inPath = '${filename}_bootstrap.dart';
+ var outPath = '${inPath}.js';
+
+ inPath = path.join(finalOutDir, inPath);
+ outPath = path.join(finalOutDir, outPath);
+
+ expect(Process.run('dart2js', ['-o$outPath', inPath]).then((res) {
+ expect(res.exitCode, 0, reason: 'dart2js exit code: '
+ '${res.exitCode}. Contents of stderr: \n${res.stderr}. '
+ 'Contents of stdout: \n${res.stdout}.');
+ expect(new File(outPath).existsSync(), true, reason: 'input file '
+ '$inPath should have been compiled to $outPath.');
+ }), completes);
+ });
+ }
+ }
+
+ if (options.runAsDart) {
+ runTests('');
+ }
+ if (options.runAsJs) {
+ ensureCompileToJs();
+ runTests('?js=1');
+ }
+ if (options.forcePolyfillShadowDom) {
+ ensureCompileToJs();
+ runTests('?js=1&shadowdomjs=1');
+ }
+}
+
+class _TestOptions {
+ final String baseDir;
+ final String inputDir;
+
+ final String expectedDir;
+ bool get isRenderTest => expectedDir != null;
+
+ final String outDir;
+ final bool deleteDir;
+
+ final bool runAsDart;
+ final bool runAsJs;
+ final bool forcePolyfillShadowDom;
+
+ final List<String> compilerArgs;
+ final RegExp pattern;
+
+ factory _TestOptions(String baseDir, String inputDir, String expectedDir,
+ String outDir, {List<String> arguments, String script, String pattern,
+ bool deleteDir: true}) {
+ if (arguments == null) arguments = new Options().arguments;
+ if (script == null) script = new Options().script;
+
+ var args = _parseArgs(arguments, script);
+ if (args == null) return null;
+ var compilerArgs = args.rest;
+ var filePattern;
+ if (pattern != null) {
+ filePattern = new RegExp(pattern);
+ } else if (compilerArgs.length > 0) {
+ filePattern = new RegExp(compilerArgs[0]);
+ compilerArgs = compilerArgs.sublist(1);
+ } else {
+ filePattern = new RegExp('.');
+ }
+
+ var scriptDir = path.absolute(path.dirname(script));
+ baseDir = path.join(scriptDir, baseDir);
+ inputDir = path.join(scriptDir, inputDir);
+ outDir = path.join(scriptDir, outDir);
+ if (expectedDir != null) {
+ expectedDir = path.join(scriptDir, expectedDir);
+ }
+
+ return new _TestOptions._(baseDir, inputDir, expectedDir, outDir, deleteDir,
+ args['dart'] == true, args['js'] == true, args['shadowdom'] == true,
+ compilerArgs, filePattern);
+ }
+
+ _TestOptions._(this.baseDir, this.inputDir, this.expectedDir, this.outDir,
+ this.deleteDir, this.runAsDart, this.runAsJs,
+ this.forcePolyfillShadowDom, this.compilerArgs, this.pattern);
+}
+
+ArgResults _parseArgs(List<String> arguments, String script) {
+ var parser = new ArgParser()
+ ..addFlag('dart', abbr: 'd', help: 'run on Dart VM', defaultsTo: true)
+ ..addFlag('js', abbr: 'j', help: 'run compiled dart2js', defaultsTo: true)
+ ..addFlag('shadowdom', abbr: 's',
+ help: 'run dart2js and polyfilled ShadowDOM', defaultsTo: true)
+ ..addFlag('help', abbr: 'h', help: 'Displays this help message',
+ defaultsTo: false, negatable: false);
+
+ showUsage() {
+ print('Usage: $script [options...] [test_name_regexp]');
+ print(parser.getUsage());
+ return null;
+ }
+
+ try {
+ var results = parser.parse(arguments);
+ if (results['help']) return showUsage();
+ return results;
+ } on FormatException catch (e) {
+ print(e.message);
+ return showUsage();
+ }
+}
diff --git a/pkg/polymer/lib/testing/testing.js b/pkg/polymer/lib/testing/testing.js
new file mode 100644
index 0000000..de27047
--- /dev/null
+++ b/pkg/polymer/lib/testing/testing.js
@@ -0,0 +1,147 @@
+// Copyright (c) 2012, 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.
+
+(function() {
+ var undoReplaceScripts = [];
+
+ var flags = {};
+ // populate flags from location
+ location.search.slice(1).split('&').forEach(function(o) {
+ o = o.split('=');
+ o[0] && (flags[o[0]] = o[1] || true);
+ });
+
+ // Webkit is migrating from layoutTestController to testRunner, we use
+ // layoutTestController as a fallback until that settles in.
+ var runner = window.testRunner || window.layoutTestController;
+
+ if (runner) {
+ runner.dumpAsText();
+ runner.waitUntilDone();
+ }
+
+ function dumpDOM() {
+ // Undo any scripts that were modified.
+ undoReplaceScripts.forEach(function(undo) { undo(); });
+
+ function expandShadowRoot(node) {
+ for (var n = node.firstChild; n; n = n.nextSibling) {
+ expandShadowRoot(n);
+ }
+ var shadow = node.shadowRoot || node.webkitShadowRoot ||
+ node.olderShadowRoot;
+
+ if (shadow) {
+ expandShadowRoot(shadow);
+
+ var name = 'shadow-root';
+ if (shadow == node.olderShadowRoot) name = 'older-' + name;
+
+ var fakeShadow = document.createElement(name);
+ while (shadow.firstChild) fakeShadow.appendChild(shadow.firstChild);
+ node.insertBefore(fakeShadow, node.firstChild);
+ }
+ }
+
+ // TODO(jmesserly): use querySelector to workaround unwrapped "document".
+ expandShadowRoot(document.querySelector('body'));
+
+ // Clean up all of the junk added to the DOM by js-interop and shadow CSS
+ // TODO(jmesserly): it seems like we're leaking lots of dart-port attributes
+ // for the document elemenet
+ function cleanTree(node) {
+ for (var n = node.firstChild; n; n = n.nextSibling) {
+ cleanTree(n);
+ }
+
+ // Remove dart-port attributes
+ if (node.attributes) {
+ for (var i = 0; i < node.attributes.length; i++) {
+ if (node.attributes[i].value.indexOf('dart-port') == 0) {
+ node.removeAttribute(i);
+ }
+ }
+ }
+
+ if (node.tagName == 'script' &&
+ node.textContent.indexOf('_DART_TEMPORARY_ATTACHED') >= 0) {
+ node.parentNode.removeChild(node);
+ }
+ }
+
+ // TODO(jmesserly): use querySelector to workaround unwrapped "document".
+ cleanTree(document.querySelector('html'));
+
+ var out = document.createElement('pre');
+ out.textContent = document.documentElement.outerHTML;
+ document.body.innerHTML = '';
+ document.body.appendChild(out);
+ }
+
+ function messageHandler(e) {
+ if (e.data == 'done' && runner) {
+ // On success, dump the DOM. Convert shadowRoot contents into
+ // <shadow-root>
+ dumpDOM();
+ runner.notifyDone();
+ }
+ }
+
+ window.addEventListener('message', messageHandler, false);
+
+ function errorHandler(e) {
+ if (runner) {
+ window.setTimeout(function() { runner.notifyDone(); }, 0);
+ }
+ window.console.log('FAIL');
+ }
+
+ window.addEventListener('error', errorHandler, false);
+
+ if (navigator.webkitStartDart && !flags.js) {
+ // TODO(jmesserly): fix this so we don't need to copy from browser/dart.js
+ if (!navigator.webkitStartDart()) {
+ document.body.innerHTML = 'This build has expired. Please download a new Dartium at http://www.dartlang.org/dartium/index.html';
+ }
+ } else {
+ if (flags.shadowdomjs) {
+ // Allow flags to force polyfill of ShadowDOM so we can test it.
+ window.__forceShadowDomPolyfill = true;
+ }
+
+ // TODO:
+ // - Support in-browser compilation.
+ // - Handle inline Dart scripts.
+ window.addEventListener("DOMContentLoaded", function (e) {
+ // Fall back to compiled JS. Run through all the scripts and
+ // replace them if they have a type that indicate that they source
+ // in Dart code.
+ //
+ // <script type="application/dart" src="..."></script>
+ //
+ var scripts = document.getElementsByTagName("script");
+ var length = scripts.length;
+ for (var i = 0; i < length; ++i) {
+ var script = scripts[i];
+ if (script.type == "application/dart") {
+ // Remap foo.dart to foo.dart.js.
+ if (script.src && script.src != '') {
+ var jsScript = document.createElement('script');
+ jsScript.src = script.src.replace(/\.dart(?=\?|$)/, '.dart.js');
+ var parent = script.parentNode;
+ // TODO(vsm): Find a solution for issue 8455 that works with more
+ // than one script.
+ document.currentScript = jsScript;
+
+ undoReplaceScripts.push(function() {
+ parent.replaceChild(script, jsScript);
+ });
+ parent.replaceChild(jsScript, script);
+ }
+ }
+ }
+ }, false);
+ }
+
+})();
diff --git a/pkg/polymer/pubspec.yaml b/pkg/polymer/pubspec.yaml
new file mode 100644
index 0000000..78b2b66
--- /dev/null
+++ b/pkg/polymer/pubspec.yaml
@@ -0,0 +1,26 @@
+name: polymer
+author: Web UI Authors <web-ui-dev@dartlang.org>
+description: >
+ Polymer.dart is a new type of library for the web, built on top of Web
+ Components, and designed to leverage the evolving web platform on modern
+ browsers.
+homepage: https://www.dartlang.org
+dependencies:
+ analyzer_experimental: any
+ args: any
+ barback: any
+ browser: any
+ csslib: any
+ custom_element: any
+ html_import: any
+ html5lib: any
+ js: any
+ logging: any
+ mdv: any
+ observe: any
+ path: any
+ polymer_expressions: any
+ shadow_dom: any
+ source_maps: any
+ # TODO(jmesserly): make this a dev_dependency
+ unittest: any
diff --git a/pkg/polymer/test/compiler_test.dart b/pkg/polymer/test/compiler_test.dart
new file mode 100644
index 0000000..d5d47ba
--- /dev/null
+++ b/pkg/polymer/test/compiler_test.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2012, 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.
+
+/** End-to-end tests for the [Compiler] API. */
+library compiler_test;
+
+import 'package:logging/logging.dart' show Level;
+import 'package:path/path.dart' as path;
+import 'package:polymer/src/messages.dart';
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+import 'testing.dart';
+
+main() {
+ useCompactVMConfiguration();
+
+ test('recursive dependencies', () {
+ var messages = new Messages.silent();
+ var compiler = createCompiler({
+ 'index.html': '<head>'
+ '<link rel="import" href="foo.html">'
+ '<link rel="import" href="bar.html">'
+ '<body><x-foo></x-foo><x-bar></x-bar>'
+ '<script type="application/dart">main() {}</script>',
+ 'foo.html': '<head><link rel="import" href="bar.html">'
+ '<body><polymer-element name="x-foo" constructor="Foo">'
+ '<template><x-bar>',
+ 'bar.html': '<head><link rel="import" href="foo.html">'
+ '<body><polymer-element name="x-bar" constructor="Boo">'
+ '<template><x-foo>',
+ }, messages);
+
+ compiler.run().then(expectAsync1((e) {
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, equals({
+ 'index.html': 1,
+ 'foo.html': 1,
+ 'bar.html': 1
+ }), reason: 'Actual:\n ${fs.readCount}');
+
+ var outputs = compiler.output.map((o) => o.path);
+ expect(outputs, equals([
+ 'foo.html.dart',
+ 'foo.html.dart.map',
+ 'bar.html.dart',
+ 'bar.html.dart.map',
+ 'index.html.dart',
+ 'index.html.dart.map',
+ 'index.html_bootstrap.dart',
+ 'index.html',
+ ].map((p) => path.join('out', p))));
+ }));
+ });
+
+ group('missing files', () {
+ test('main script', () {
+ var messages = new Messages.silent();
+ var compiler = createCompiler({
+ 'index.html': '<head></head><body>'
+ '<script type="application/dart" src="notfound.dart"></script>'
+ '</body>',
+ }, messages);
+
+ compiler.run().then(expectAsync1((e) {
+ var msgs = messages.messages.where((m) =>
+ m.message.contains('unable')).toList();
+
+ expect(msgs.length, 1);
+ expect(msgs[0].level, Level.SEVERE);
+ expect(msgs[0].message, contains('unable to open file'));
+ expect(msgs[0].span, isNotNull);
+ expect(msgs[0].span.sourceUrl, 'index.html');
+
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, { 'index.html': 1, 'notfound.dart': 1 });
+
+ var outputs = compiler.output.map((o) => o.path.toString());
+ expect(outputs, []);
+ }));
+ });
+
+ test('component html', () {
+ var messages = new Messages.silent();
+ var compiler = createCompiler({
+ 'index.html': '<head>'
+ '<link rel="import" href="notfound.html">'
+ '<body><x-foo>'
+ '<script type="application/dart">main() {}</script>',
+ }, messages);
+
+ compiler.run().then(expectAsync1((e) {
+ var msgs = messages.messages.where((m) =>
+ m.message.contains('unable')).toList();
+
+ expect(msgs.length, 1);
+ expect(msgs[0].level, Level.SEVERE);
+ expect(msgs[0].message, contains('unable to open file'));
+ expect(msgs[0].span, isNotNull);
+ expect(msgs[0].span.sourceUrl, 'index.html');
+
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, { 'index.html': 1, 'notfound.html': 1 });
+
+ var outputs = compiler.output.map((o) => o.path.toString());
+ expect(outputs, []);
+ }));
+ });
+
+ test('component script', () {
+ var messages = new Messages.silent();
+ var compiler = createCompiler({
+ 'index.html': '<head>'
+ '<link rel="import" href="foo.html">'
+ '<body><x-foo></x-foo>'
+ '<script type="application/dart">main() {}</script>'
+ '</body>',
+ 'foo.html': '<body><polymer-element name="x-foo" constructor="Foo">'
+ '<template></template>'
+ '<script type="application/dart" src="notfound.dart"></script>',
+ }, messages);
+
+ compiler.run().then(expectAsync1((e) {
+ var msgs = messages.messages.where((m) =>
+ m.message.contains('unable')).toList();
+
+ expect(msgs.length, 1);
+ expect(msgs[0].level, Level.SEVERE);
+ expect(msgs[0].message, contains('unable to open file'));
+
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount,
+ { 'index.html': 1, 'foo.html': 1, 'notfound.dart': 1 });
+
+ var outputs = compiler.output.map((o) => o.path.toString());
+ expect(outputs, []);
+ }));
+ });
+ });
+}
diff --git a/pkg/polymer/test/css_test.dart b/pkg/polymer/test/css_test.dart
new file mode 100644
index 0000000..1702312
--- /dev/null
+++ b/pkg/polymer/test/css_test.dart
@@ -0,0 +1,559 @@
+// 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.
+
+library css_test;
+
+import 'package:path/path.dart' as path;
+import 'package:polymer/src/messages.dart';
+import 'package:polymer/src/utils.dart' as utils;
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+import 'testing.dart';
+
+test_simple_var() {
+ Map createFiles() {
+ return {
+ 'index.html':
+ '<!DOCTYPE html>'
+ '<html lang="en">'
+ '<head>'
+ '<meta charset="utf-8">'
+ '</head>'
+ '<body>'
+ '<style>'
+ '@main_color: var(b);'
+ '@b: var(c);'
+ '@c: red;'
+ '</style>'
+ '<style>'
+ '.test { color: var(main_color); }'
+ '</style>'
+ '<script type="application/dart">main() {}</script>'
+ '</body>'
+ '</html>',
+ };
+ }
+
+ var messages = new Messages.silent();
+ var compiler = createCompiler(createFiles(), messages, errors: true,
+ scopedCss: true);
+
+ compiler.run().then(expectAsync1((e) {
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, equals({
+ 'index.html': 1,
+ }), reason: 'Actual:\n ${fs.readCount}');
+
+ var htmlInfo = compiler.info['index.html'];
+ expect(htmlInfo.styleSheets.length, 2);
+ expect(prettyPrintCss(htmlInfo.styleSheets[0]), '');
+ expect(prettyPrintCss(htmlInfo.styleSheets[1]), '.test { color: red; }');
+
+ var outputs = compiler.output.map((o) => o.path);
+ expect(outputs, equals([
+ 'out/index.html.dart',
+ 'out/index.html.dart.map',
+ 'out/index.html_bootstrap.dart',
+ 'out/index.html',
+ ]));
+ }));
+}
+
+test_var() {
+ Map createFiles() {
+ return {
+ 'index.html':
+ '<!DOCTYPE html>'
+ '<html lang="en">'
+ '<head>'
+ '<meta charset="utf-8">'
+ '</head>'
+ '<body>'
+ '<style>'
+ '@main-color: var(b);'
+ '@b: var(c);'
+ '@c: red;'
+ '@d: var(main-color-1, green);'
+ '@border-pen: solid;'
+ '@inset: 5px;'
+ '@frame-color: solid orange;'
+ '@big-border: 2px 2px 2px;'
+ '@border-stuff: 3px dashed var(main-color);'
+ '@border-2: 3px var(big-border) dashed var(main-color-1, green);'
+ '@blue-border: bold var(not-found, 1px 10px blue)'
+ '</style>'
+ '<style>'
+ '.test-1 { color: var(main-color-1, blue); }'
+ '.test-2 { color: var(main-color-1, var(main-color)); }'
+ '.test-3 { color: var(d, yellow); }'
+ '.test-4 { color: var(d-1, yellow); }'
+ '.test-5 { color: var(d-1, var(d)); }'
+ '.test-6 { border: var(inset) var(border-pen) var(d); }'
+ '.test-7 { border: 10px var(border-pen) var(d); }'
+ '.test-8 { border: 20px var(border-pen) yellow; }'
+ '.test-9 { border: 30px dashed var(d); }'
+ '.test-10 { border: 40px var(frame-color);}'
+ '.test-11 { border: 40px var(frame-color-1, blue);}'
+ '.test-12 { border: 40px var(frame-color-1, solid blue);}'
+ '.test-13 {'
+ 'border: 40px var(x1, var(x2, var(x3, var(frame-color)));'
+ '}'
+ '.test-14 { border: 40px var(x1, var(frame-color); }'
+ '.test-15 { border: 40px var(x1, solid blue);}'
+ '.test-16 { border: 1px 1px 2px 3px var(frame-color);}'
+ '.test-17 { border: 1px 1px 2px 3px var(x1, solid blue);}'
+ '.test-18 { border: 1px 1px 2px var(border-stuff);}'
+ '.test-19 { border: var(big-border) var(border-stuff);}'
+ '.test-20 { border: var(border-2);}'
+ '.test-21 { border: var(blue-border);}'
+ '</style>'
+ '<script type="application/dart">main() {}</script>'
+ '</body>'
+ '</html>',
+ };
+ }
+
+ var messages = new Messages.silent();
+ var compiler = createCompiler(createFiles(), messages, errors: true,
+ scopedCss: true);
+
+ compiler.run().then(expectAsync1((e) {
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, equals({
+ 'index.html': 1,
+ }), reason: 'Actual:\n ${fs.readCount}');
+
+ var htmlInfo = compiler.info['index.html'];
+ expect(htmlInfo.styleSheets.length, 2);
+ expect(prettyPrintCss(htmlInfo.styleSheets[0]), '');
+ expect(prettyPrintCss(htmlInfo.styleSheets[1]),
+ '.test-1 { color: blue; } '
+ '.test-2 { color: red; } '
+ '.test-3 { color: green; } '
+ '.test-4 { color: yellow; } '
+ '.test-5 { color: green; } '
+ '.test-6 { border: 5px solid green; } '
+ '.test-7 { border: 10px solid green; } '
+ '.test-8 { border: 20px solid yellow; } '
+ '.test-9 { border: 30px dashed green; } '
+ '.test-10 { border: 40px solid orange; } '
+ '.test-11 { border: 40px blue; } '
+ '.test-12 { border: 40px solid blue; } '
+ '.test-13 { border: 40px solid orange; } '
+ '.test-14 { border: 40px solid orange; } '
+ '.test-15 { border: 40px solid blue; } '
+ '.test-16 { border: 1px 1px 2px 3px solid orange; } '
+ '.test-17 { border: 1px 1px 2px 3px solid blue; } '
+ '.test-18 { border: 1px 1px 2px 3px dashed red; } '
+ '.test-19 { border: 2px 2px 2px 3px dashed red; } '
+ '.test-20 { border: 3px 2px 2px 2px dashed green; } '
+ '.test-21 { border: bold 1px 10px blue; }');
+ var outputs = compiler.output.map((o) => o.path);
+ expect(outputs, equals([
+ 'out/index.html.dart',
+ 'out/index.html.dart.map',
+ 'out/index.html_bootstrap.dart',
+ 'out/index.html',
+ ]));
+ }));
+}
+
+test_simple_import() {
+ Map createFiles() {
+ return {
+ 'foo.css': r'''@main_color: var(b);
+ @b: var(c);
+ @c: red;''',
+ 'index.html':
+ '<!DOCTYPE html>'
+ '<html lang="en">'
+ '<head>'
+ '<meta charset="utf-8">'
+ '</head>'
+ '<body>'
+ '<style>'
+ '@import "foo.css";'
+ '.test { color: var(main_color); }'
+ '</style>'
+ '<script type="application/dart">main() {}</script>'
+ '</body>'
+ '</html>',
+ };
+ }
+
+ var messages = new Messages.silent();
+ var compiler = createCompiler(createFiles(), messages, errors: true,
+ scopedCss: true);
+
+ compiler.run().then(expectAsync1((e) {
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, equals({
+ 'foo.css': 1,
+ 'index.html': 1,
+ }), reason: 'Actual:\n ${fs.readCount}');
+
+ var cssInfo = compiler.info['foo.css'];
+ expect(cssInfo.styleSheets.length, 1);
+ expect(prettyPrintCss(cssInfo.styleSheets[0]), '');
+
+ var htmlInfo = compiler.info['index.html'];
+ expect(htmlInfo.styleSheets.length, 1);
+ expect(prettyPrintCss(htmlInfo.styleSheets[0]),
+ '@import url(foo.css); .test { color: red; }');
+
+ var outputs = compiler.output.map((o) => o.path);
+ expect(outputs, equals([
+ 'out/index.html.dart',
+ 'out/index.html.dart.map',
+ 'out/index.html_bootstrap.dart',
+ 'out/foo.css',
+ 'out/index.html',
+ ]));
+ }));
+}
+
+test_imports() {
+ Map createFiles() {
+ return {
+ 'first.css':
+ '@import "third.css";'
+ '@main-width: var(main-width-b);'
+ '@main-width-b: var(main-width-c);'
+ '@main-width-c: var(wide-width);',
+ 'second.css':
+ '@import "fourth.css";'
+ '@main-color: var(main-color-b);'
+ '@main-color-b: var(main-color-c);'
+ '@main-color-c: var(color-value);',
+ 'third.css':
+ '@wide-width: var(wide-width-b);'
+ '@wide-width-b: var(wide-width-c);'
+ '@wide-width-c: 100px;',
+ 'fourth.css':
+ '@color-value: var(color-value-b);'
+ '@color-value-b: var(color-value-c);'
+ '@color-value-c: red;',
+ 'index.html':
+ '<!DOCTYPE html>'
+ '<html lang="en">'
+ '<head>'
+ '<meta charset="utf-8">'
+ '<link rel="stylesheet" href="first.css">'
+ '</head>'
+ '<body>'
+ '<style>'
+ '@import "first.css";'
+ '@import "second.css";'
+ '.test-1 { color: var(main-color); }'
+ '.test-2 { width: var(main-width); }'
+ '</style>'
+ '<script type="application/dart">main() {}</script>'
+ '</body>'
+ '</html>',
+ };
+ }
+
+ var messages = new Messages.silent();
+ var compiler = createCompiler(createFiles(), messages, errors: true,
+ scopedCss: true);
+
+ compiler.run().then(expectAsync1((e) {
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, equals({
+ 'first.css': 1,
+ 'second.css': 1,
+ 'third.css': 1,
+ 'fourth.css': 1,
+ 'index.html': 1,
+ }), reason: 'Actual:\n ${fs.readCount}');
+
+ var firstInfo = compiler.info['first.css'];
+ expect(firstInfo.styleSheets.length, 1);
+ expect(prettyPrintCss(firstInfo.styleSheets[0]), '@import url(third.css);');
+
+ var secondInfo = compiler.info['second.css'];
+ expect(secondInfo.styleSheets.length, 1);
+ expect(prettyPrintCss(secondInfo.styleSheets[0]),
+ '@import url(fourth.css);');
+
+ var thirdInfo = compiler.info['third.css'];
+ expect(thirdInfo.styleSheets.length, 1);
+ expect(prettyPrintCss(thirdInfo.styleSheets[0]), '');
+
+ var fourthInfo = compiler.info['fourth.css'];
+ expect(fourthInfo.styleSheets.length, 1);
+ expect(prettyPrintCss(fourthInfo.styleSheets[0]), '');
+
+ var htmlInfo = compiler.info['index.html'];
+ expect(htmlInfo.styleSheets.length, 1);
+ expect(prettyPrintCss(htmlInfo.styleSheets[0]),
+ '@import url(first.css); '
+ '@import url(second.css); '
+ '.test-1 { color: red; } '
+ '.test-2 { width: 100px; }');
+
+ var outputs = compiler.output.map((o) => o.path);
+ expect(outputs, equals([
+ 'out/index.html.dart',
+ 'out/index.html.dart.map',
+ 'out/index.html_bootstrap.dart',
+ 'out/first.css',
+ 'out/second.css',
+ 'out/third.css',
+ 'out/fourth.css',
+ 'out/index.html',
+ ]));
+ }));
+}
+
+test_component_var() {
+ Map createFiles() {
+ return {
+ 'index.html': '<!DOCTYPE html>'
+ '<html lang="en">'
+ '<head>'
+ '<meta charset="utf-8">'
+ '<link rel="import" href="foo.html">'
+ '</head>'
+ '<body>'
+ '<x-foo></x-foo>'
+ '<script type="application/dart">main() {}</script>'
+ '</body>'
+ '</html>',
+ 'foo.html': '<!DOCTYPE html>'
+ '<html lang="en">'
+ '<head>'
+ '<meta charset="utf-8">'
+ '</head>'
+ '<body>'
+ '<polymer-element name="x-foo" constructor="Foo">'
+ '<template>'
+ '<style scoped>'
+ '@import "foo.css";'
+ '.main { color: var(main_color); }'
+ '.test-background { '
+ 'background: url(http://www.foo.com/bar.png);'
+ '}'
+ '</style>'
+ '</template>'
+ '</polymer-element>'
+ '</body>'
+ '</html>',
+ 'foo.css': r'''@main_color: var(b);
+ @b: var(c);
+ @c: red;
+
+ @one: var(two);
+ @two: var(one);
+
+ @four: var(five);
+ @five: var(six);
+ @six: var(four);
+
+ @def-1: var(def-2);
+ @def-2: var(def-3);
+ @def-3: var(def-2);''',
+ };
+ }
+
+ test('var- and Less @define', () {
+ var messages = new Messages.silent();
+ var compiler = createCompiler(createFiles(), messages, errors: true,
+ scopedCss: true);
+
+ compiler.run().then(expectAsync1((e) {
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, equals({
+ 'index.html': 1,
+ 'foo.html': 1,
+ 'foo.css': 1
+ }), reason: 'Actual:\n ${fs.readCount}');
+
+ var cssInfo = compiler.info['foo.css'];
+ expect(cssInfo.styleSheets.length, 1);
+ var htmlInfo = compiler.info['foo.html'];
+ expect(htmlInfo.styleSheets.length, 0);
+ expect(htmlInfo.declaredComponents.length, 1);
+ expect(htmlInfo.declaredComponents[0].styleSheets.length, 1);
+
+ var outputs = compiler.output.map((o) => o.path);
+ expect(outputs, equals([
+ 'out/foo.html.dart',
+ 'out/foo.html.dart.map',
+ 'out/index.html.dart',
+ 'out/index.html.dart.map',
+ 'out/index.html_bootstrap.dart',
+ 'out/foo.css',
+ 'out/index.html.css',
+ 'out/index.html',
+ ]));
+
+ for (var file in compiler.output) {
+ if (file.path == 'out/index.html.css') {
+ expect(file.contents,
+ '/* Auto-generated from components style tags. */\n'
+ '/* DO NOT EDIT. */\n\n'
+ '/* ==================================================== \n'
+ ' Component x-foo stylesheet \n'
+ ' ==================================================== */\n'
+ '@import "foo.css";\n'
+ '[is="x-foo"] .main {\n'
+ ' color: #f00;\n'
+ '}\n'
+ '[is="x-foo"] .test-background {\n'
+ ' background: url("http://www.foo.com/bar.png");\n'
+ '}\n\n');
+ } else if (file.path == 'out/foo.css') {
+ expect(file.contents,
+ '/* Auto-generated from style sheet href = foo.css */\n'
+ '/* DO NOT EDIT. */\n\n\n\n');
+ }
+ }
+
+ // Check for warning messages about var- cycles.
+ expect(messages.messages.length, 8);
+
+ var errorMessage = messages.messages[0];
+ expect(errorMessage.message, contains('var cycle detected var-def-1'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span.start.line, 11);
+ expect(errorMessage.span.start.column, 22);
+ expect(errorMessage.span.text, '@def-1: var(def-2)');
+
+ errorMessage = messages.messages[1];
+ expect(errorMessage.message, contains('var cycle detected var-five'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span.start.line, 8);
+ expect(errorMessage.span.start.column, 22);
+ expect(errorMessage.span.text, '@five: var(six)');
+
+ errorMessage = messages.messages[2];
+ expect(errorMessage.message, contains('var cycle detected var-six'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span.start.line, 9);
+ expect(errorMessage.span.start.column, 22);
+ expect(errorMessage.span.text, '@six: var(four)');
+
+ errorMessage = messages.messages[3];
+ expect(errorMessage.message, contains('var cycle detected var-def-3'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span.start.line, 13);
+ expect(errorMessage.span.start.column, 22);
+ expect(errorMessage.span.text, '@def-3: var(def-2)');
+
+ errorMessage = messages.messages[4];
+ expect(errorMessage.message, contains('var cycle detected var-two'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span.start.line, 5);
+ expect(errorMessage.span.start.column, 22);
+ expect(errorMessage.span.text, '@two: var(one)');
+
+ errorMessage = messages.messages[5];
+ expect(errorMessage.message, contains('var cycle detected var-def-2'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span.start.line, 12);
+ expect(errorMessage.span.start.column, 22);
+ expect(errorMessage.span.text, '@def-2: var(def-3)');
+
+ errorMessage = messages.messages[6];
+ expect(errorMessage.message, contains('var cycle detected var-one'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span.start.line, 4);
+ expect(errorMessage.span.start.column, 22);
+ expect(errorMessage.span.text, '@one: var(two)');
+
+ errorMessage = messages.messages[7];
+ expect(errorMessage.message, contains('var cycle detected var-four'));
+ expect(errorMessage.span, isNotNull);
+ expect(errorMessage.span.start.line, 7);
+ expect(errorMessage.span.start.column, 22);
+ expect(errorMessage.span.text, '@four: var(five)');
+ }));
+ });
+}
+
+test_pseudo_element() {
+ var messages = new Messages.silent();
+ var compiler = createCompiler({
+ 'index.html': '<head>'
+ '<link rel="import" href="foo.html">'
+ '<style>'
+ '.test::x-foo { background-color: red; }'
+ '.test::x-foo1 { color: blue; }'
+ '.test::x-foo2 { color: green; }'
+ '</style>'
+ '<body>'
+ '<x-foo class=test></x-foo>'
+ '<x-foo></x-foo>'
+ '<script type="application/dart">main() {}</script>',
+ 'foo.html': '<head>'
+ '<body><polymer-element name="x-foo" constructor="Foo">'
+ '<template>'
+ '<div pseudo="x-foo">'
+ '<div>Test</div>'
+ '</div>'
+ '<div pseudo="x-foo1 x-foo2">'
+ '<div>Test</div>'
+ '</div>'
+ '</template>',
+ }, messages, scopedCss: true);
+
+ compiler.run().then(expectAsync1((e) {
+ MockFileSystem fs = compiler.fileSystem;
+ expect(fs.readCount, equals({
+ 'index.html': 1,
+ 'foo.html': 1,
+ }), reason: 'Actual:\n ${fs.readCount}');
+
+ var outputs = compiler.output.map((o) => o.path);
+ expect(outputs, equals([
+ 'out/foo.html.dart',
+ 'out/foo.html.dart.map',
+ 'out/index.html.dart',
+ 'out/index.html.dart.map',
+ 'out/index.html_bootstrap.dart',
+ 'out/index.html',
+ ]));
+ expect(compiler.output.last.contents, contains(
+ '<div pseudo="x-foo_0">'
+ '<div>Test</div>'
+ '</div>'
+ '<div pseudo="x-foo1_1 x-foo2_2">'
+ '<div>Test</div>'
+ '</div>'));
+ expect(compiler.output.last.contents, contains(
+ '<style>.test > *[pseudo="x-foo_0"] {\n'
+ ' background-color: #f00;\n'
+ '}\n'
+ '.test > *[pseudo="x-foo1_1"] {\n'
+ ' color: #00f;\n'
+ '}\n'
+ '.test > *[pseudo="x-foo2_2"] {\n'
+ ' color: #008000;\n'
+ '}'
+ '</style>'));
+ }));
+}
+
+main() {
+ useCompactVMConfiguration();
+
+ group('css', () {
+ setUp(() {
+ utils.path = new path.Builder(style: path.Style.posix);
+ });
+
+ tearDown(() {
+ utils.path = new path.Builder();
+ });
+
+ test('test_simple_var', test_simple_var);
+ test('test_var', test_var);
+ test('test_simple_import', test_simple_import);
+ test('test_imports', test_imports);
+ group('test_component_var', test_component_var);
+ test('test_pseudo_element', test_pseudo_element);
+ });
+}
diff --git a/pkg/polymer/test/data/unit/event_path_test.html b/pkg/polymer/test/data/unit/event_path_test.html
new file mode 100644
index 0000000..811479d
--- /dev/null
+++ b/pkg/polymer/test/data/unit/event_path_test.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<!--
+ 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.
+-->
+<html>
+ <head>
+ <title>event path</title>
+ <script src="packages/polymer/testing/testing.js"></script>
+ <script src="packages/unittest/test_controller.js"></script>
+ <!--
+ Test ported from:
+ https://github.com/Polymer/polymer/blob/7936ff8/test/html/event-path.html
+
+ This test actually doesn't test the polymer's event layer. It just ensures
+ that tests are propagated in the right order when using Shadow DOM.
+ -->
+ </head>
+ <body>
+
+ <polymer-element name="x-selector">
+ <template>
+ <div id="selectorDiv">
+ <content id="selectorContent"></content>
+ </div>
+ </template>
+ <script type="application/dart">
+ import 'package:polymer/polymer.dart';
+ @CustomTag("x-selector")
+ class XSelector extends PolymerElement {}
+ </script>
+ </polymer-element>
+
+ <polymer-element name="x-overlay">
+ <template>
+ <content id="overlayContent"></content>
+ </template>
+ <script type="application/dart">
+ import 'package:polymer/polymer.dart';
+ @CustomTag("x-overlay")
+ class XOverlay extends PolymerElement {}
+ </script>
+ </polymer-element>
+
+ <polymer-element name="x-menu" extends="x-selector">
+ <template>
+ <div id="menuDiv">
+ <shadow id="menuShadow"></shadow>
+ </div>
+ </template>
+ <script type="application/dart">
+ import 'package:polymer/polymer.dart';
+ @CustomTag("x-menu")
+ class XMenu extends PolymerElement {}
+ </script>
+ </polymer-element>
+
+ <polymer-element name="x-menu-button">
+ <template>
+ <div>
+ <x-overlay id="overlay">
+ <div id="menuButtonDiv">
+ <x-menu id="menu">
+ <content id="menuButtonContent"></content>
+ </x-menu>
+ </div>
+ </x-overlay>
+ </div>
+ </template>
+ <script type="application/dart">
+ import 'package:polymer/polymer.dart';
+ @CustomTag("x-menu-button")
+ class XMenuButton extends PolymerElement {}
+ </script>
+ </polymer-element>
+
+ <x-menu-button id="menuButton">
+ <div id="item1"><div id="source"></div>Item1</div>
+ <div id="item2">Item2</div>
+ </x-menu-button>
+
+
+ <script type="application/dart">
+ import 'dart:html';
+ import 'dart:async';
+ import 'package:unittest/unittest.dart';
+ import 'package:unittest/html_config.dart';
+
+ main() {
+ useHtmlConfiguration();
+ test('bubbling in the right order', () {
+ // TODO(sigmund): this should change once we port over the
+ // 'WebComponentsReady' event.
+ runAsync(expectAsync0(() {
+ var item1 = query('#item1');
+ var menuButton = query('#menuButton');
+ // Note: polymer uses automatic node finding (menuButton.$.menu)
+ // also note that their node finding code also reachs into the ids
+ // from the parent shadow (menu.$.selectorContent instead of
+ // menu.$.menuShadow.$.selectorContent)
+ var menu = menuButton.shadowRoot.query('#menu');
+ var selector = menu.shadowRoot.query("#menuShadow");
+ var overlay = menuButton.shadowRoot.query('#overlay');
+ var expectedPath = <Node>[
+ item1,
+ menuButton.shadowRoot.query('#menuButtonContent'),
+ selector.olderShadowRoot.query('#selectorContent'),
+ selector.olderShadowRoot.query('#selectorDiv'),
+ menu.shadowRoot.query('#menuShadow').olderShadowRoot,
+ menu.shadowRoot.query('#menuShadow'),
+ menu.shadowRoot.query('#menuDiv'),
+ menu.shadowRoot,
+ menu,
+ menuButton.shadowRoot.query('#menuButtonDiv'),
+ // TODO(sigmund): this test is currently broken because currently
+ // registerElement is sensitive to the order in which each custom
+ // element is registered. When fixed, we should be able to add the
+ // following three targets:
+ // overlay.shadowRoot.query('#overlayContent'),
+ // overlay.shadowRoot,
+ // overlay,
+ menuButton.shadowRoot,
+ menuButton
+ ];
+ var x = 0;
+ for (int i = 0; i < expectedPath.length; i++) {
+ var node = expectedPath[i];
+ expect(node, isNotNull, reason: "Should not be null at $i");
+ node.on['x'].listen(expectAsync1((e) {
+ expect(e.currentTarget, node);
+ expect(x++, i);
+ }));
+ }
+
+ item1.dispatchEvent(new Event('x', canBubble: true));
+ }));
+ });
+ }
+ </script>
+ </body>
+</html>
diff --git a/pkg/polymer/test/data/unit/events_test.html b/pkg/polymer/test/data/unit/events_test.html
new file mode 100644
index 0000000..cbf33d1
--- /dev/null
+++ b/pkg/polymer/test/data/unit/events_test.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<!--
+ 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.
+-->
+<html>
+ <head>
+ <title>event path</title>
+ <script src="packages/polymer/testing/testing.js"></script>
+ <script src="packages/unittest/test_controller.js"></script>
+ <!--
+ Test ported from:
+ https://github.com/Polymer/polymer/blob/7936ff8/test/js/events.js
+
+ TODO(sigmund): when we have support for mutation observers, render all of
+ the test in Dart (like events.js does in JS)
+ -->
+ </head>
+ <body>
+
+ <polymer-element name="test-a" on-click="clickHandler">
+ <template></template>
+ <script type="application/dart">
+ import 'package:polymer/polymer.dart';
+
+ @CustomTag("test-a")
+ class TestA extends PolymerElement {
+ List clicks = [];
+ void clickHandler() {
+ clicks.add('host click on: $localName (id $id)');
+ }
+ }
+ </script>
+ </polymer-element>
+
+ <polymer-element name="test-b">
+ <template>
+ <div>
+ <span id="b-1">1</span>
+ <span id="b-2" on-click="clickHandler">2</span>
+ </div>
+ </template>
+ <script type="application/dart">
+ import 'package:polymer/polymer.dart';
+
+ @CustomTag("test-b")
+ class TestB extends PolymerElement {
+ List clicks = [];
+ void clickHandler(event, detail, target) {
+ clicks.add('local click under $localName (id $id) on ${target.id}');
+ }
+ }
+ </script>
+ </polymer-element>
+
+ <test-a id="a"></test-a>
+ <test-b id="b"></test-b>
+
+ <script type="application/dart">
+ import 'dart:html';
+ import 'dart:async';
+ import 'package:unittest/unittest.dart';
+ import 'package:unittest/html_config.dart';
+
+ main() {
+ useHtmlConfiguration();
+
+ test('host event', () {
+ // Note: this test is currently the only event in
+ // polymer/test/js/events.js at commit #7936ff8
+ Timer.run(expectAsync0(() {
+ var testA = query('#a');
+ expect(testA.xtag.clicks, isEmpty);
+ testA.click();
+ expect(testA.xtag.clicks, ['host click on: test-a (id a)']);
+ }));
+ });
+
+ test('local event', () {
+ Timer.run(expectAsync0(() {
+ var testB = query('#b');
+ expect(testB.xtag.clicks, isEmpty);
+ testB.click();
+ expect(testB.xtag.clicks, []);
+ var b1 = testB.shadowRoot.query('#b-1');
+ b1.click();
+ expect(testB.xtag.clicks, []);
+ var b2 = testB.shadowRoot.query('#b-2');
+ b2.click();
+ expect(testB.xtag.clicks, ['local click under test-b (id b) on b-2']);
+ }));
+ });
+ }
+ </script>
+ </body>
+</html>
diff --git a/pkg/polymer/test/paths_test.dart b/pkg/polymer/test/paths_test.dart
new file mode 100644
index 0000000..4a54a9d
--- /dev/null
+++ b/pkg/polymer/test/paths_test.dart
@@ -0,0 +1,339 @@
+// Copyright (c) 2012, 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.
+
+/** Tests for [PathMapper]. */
+library path_info_test;
+
+import 'package:path/path.dart' as path;
+import 'package:polymer/src/info.dart';
+import 'package:polymer/src/paths.dart';
+import 'package:polymer/src/utils.dart' as utils;
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+main() {
+ useCompactVMConfiguration();
+ group('paths', () {
+ setUp(() {
+ utils.path = new path.Builder(style: path.Style.posix);
+ });
+
+ tearDown(() {
+ utils.path = new path.Builder();
+ });
+
+ testPaths();
+ });
+}
+
+testPaths() {
+ group('outdir == basedir:', () {
+ group('outputPath', () {
+ test('mangle automatic', () {
+ var pathMapper = _newPathMapper('a', 'a', false);
+ var file = _mockFile('a/b.dart', pathMapper);
+ expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
+ expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+ 'a/_b.dart.dart');
+ });
+
+ test('within packages/', () {
+ var pathMapper = _newPathMapper('a', 'a', false);
+ var file = _mockFile('a/packages/b.dart', pathMapper);
+ expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
+ expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+ 'a/_from_packages/_b.dart.dart');
+ });
+ });
+
+ group('importUrlFor', () {
+ test('simple pathMapper', () {
+ var pathMapper = _newPathMapper('a', 'a', false);
+ var file1 = _mockFile('a/b.dart', pathMapper);
+ var file2 = _mockFile('a/c/d.dart', pathMapper);
+ var file3 = _mockFile('a/e/f.dart', pathMapper);
+ expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file1, file3), 'e/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file3), '../e/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+ });
+
+ test('include packages/', () {
+ var pathMapper = _newPathMapper('a', 'a', false);
+ var file1 = _mockFile('a/b.dart', pathMapper);
+ var file2 = _mockFile('a/c/d.dart', pathMapper);
+ var file3 = _mockFile('a/packages/f.dart', pathMapper);
+ expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file1, file3),
+ '_from_packages/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file3),
+ '../_from_packages/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+ });
+
+ test('packages, but no rewrite', () {
+ var pathMapper = _newPathMapper('a', 'a', false, rewriteUrls: false);
+ var file1 = _mockFile('a/b.dart', pathMapper);
+ var file2 = _mockFile('a/c/d.dart', pathMapper);
+ var file3 = _mockFile('a/packages/c/f.dart', pathMapper,
+ url: 'package:e/f.dart');
+ expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file1, file3),
+ 'package:e/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file3),
+ 'package:e/_f.dart.dart');
+ });
+
+ test('windows paths', () {
+ try {
+ utils.path = new path.Builder(style: path.Style.windows);
+ var pathMapper = _newPathMapper('a', 'a', false);
+ var file1 = _mockFile('a\\b.dart', pathMapper);
+ var file2 = _mockFile('a\\c\\d.dart', pathMapper);
+ var file3 = _mockFile('a\\packages\\f.dart', pathMapper);
+ expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file1, file3),
+ '_from_packages/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file3),
+ '../_from_packages/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+ } finally {
+ utils.path = new path.Builder();
+ }
+ });
+ });
+
+ test('transformUrl simple paths', () {
+ var pathMapper = _newPathMapper('a', 'a', false);
+ var file1 = 'a/b.dart';
+ var file2 = 'a/c/d.html';
+ // when the output == input directory, no paths should be rewritten
+ expect(pathMapper.transformUrl(file1, '/a.dart'), '/a.dart');
+ expect(pathMapper.transformUrl(file1, 'c.dart'), 'c.dart');
+ expect(pathMapper.transformUrl(file1, '../c/d.dart'), '../c/d.dart');
+ expect(pathMapper.transformUrl(file1, 'packages/c.dart'),
+ 'packages/c.dart');
+ expect(pathMapper.transformUrl(file2, 'e.css'), 'e.css');
+ expect(pathMapper.transformUrl(file2, '../c/e.css'), 'e.css');
+ expect(pathMapper.transformUrl(file2, '../q/e.css'), '../q/e.css');
+ expect(pathMapper.transformUrl(file2, 'packages/c.css'),
+ 'packages/c.css');
+ expect(pathMapper.transformUrl(file2, '../packages/c.css'),
+ '../packages/c.css');
+ });
+
+ test('transformUrl with source in packages/', () {
+ var pathMapper = _newPathMapper('a', 'a', false);
+ var file = 'a/packages/e.html';
+ // Even when output == base, files under packages/ are moved to
+ // _from_packages, so all imports are affected:
+ expect(pathMapper.transformUrl(file, 'e.css'), '../packages/e.css');
+ expect(pathMapper.transformUrl(file, '../packages/e.css'),
+ '../packages/e.css');
+ expect(pathMapper.transformUrl(file, '../q/e.css'), '../q/e.css');
+ expect(pathMapper.transformUrl(file, 'packages/c.css'),
+ '../packages/packages/c.css');
+ });
+ });
+
+ group('outdir != basedir:', () {
+ group('outputPath', (){
+ test('no force mangle', () {
+ var pathMapper = _newPathMapper('a', 'out', false);
+ var file = _mockFile('a/b.dart', pathMapper);
+ expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
+ expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+ 'out/b.dart');
+ });
+
+ test('force mangling', () {
+ var pathMapper = _newPathMapper('a', 'out', true);
+ var file = _mockFile('a/b.dart', pathMapper);
+ expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
+ expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+ 'out/_b.dart.dart');
+ });
+
+ test('within packages/, no mangle', () {
+ var pathMapper = _newPathMapper('a', 'out', false);
+ var file = _mockFile('a/packages/b.dart', pathMapper);
+ expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
+ expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+ 'out/_from_packages/b.dart');
+ });
+
+ test('within packages/, mangle', () {
+ var pathMapper = _newPathMapper('a', 'out', true);
+ var file = _mockFile('a/packages/b.dart', pathMapper);
+ expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
+ expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+ 'out/_from_packages/_b.dart.dart');
+ });
+ });
+
+ group('importUrlFor', (){
+ test('simple paths, no mangle', () {
+ var pathMapper = _newPathMapper('a', 'out', false);
+ var file1 = _mockFile('a/b.dart', pathMapper);
+ var file2 = _mockFile('a/c/d.dart', pathMapper);
+ var file3 = _mockFile('a/e/f.dart', pathMapper);
+ expect(pathMapper.importUrlFor(file1, file2), 'c/d.dart');
+ expect(pathMapper.importUrlFor(file1, file3), 'e/f.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../b.dart');
+ expect(pathMapper.importUrlFor(file2, file3), '../e/f.dart');
+ expect(pathMapper.importUrlFor(file3, file2), '../c/d.dart');
+ expect(pathMapper.importUrlFor(file3, file1), '../b.dart');
+ });
+
+ test('simple paths, mangle', () {
+ var pathMapper = _newPathMapper('a', 'out', true);
+ var file1 = _mockFile('a/b.dart', pathMapper);
+ var file2 = _mockFile('a/c/d.dart', pathMapper);
+ var file3 = _mockFile('a/e/f.dart', pathMapper);
+ expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file1, file3), 'e/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file3), '../e/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+ });
+
+ test('include packages/, no mangle', () {
+ var pathMapper = _newPathMapper('a', 'out', false);
+ var file1 = _mockFile('a/b.dart', pathMapper);
+ var file2 = _mockFile('a/c/d.dart', pathMapper);
+ var file3 = _mockFile('a/packages/e/f.dart', pathMapper,
+ url: 'package:e/f.dart');
+ expect(pathMapper.importUrlFor(file1, file2), 'c/d.dart');
+ expect(pathMapper.importUrlFor(file1, file3),
+ '_from_packages/e/f.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../b.dart');
+ expect(pathMapper.importUrlFor(file2, file3),
+ '../_from_packages/e/f.dart');
+ expect(pathMapper.importUrlFor(file3, file2), '../../c/d.dart');
+ expect(pathMapper.importUrlFor(file3, file1), '../../b.dart');
+ });
+
+ test('include packages/, mangle', () {
+ var pathMapper = _newPathMapper('a', 'out', true);
+ var file1 = _mockFile('a/b.dart', pathMapper);
+ var file2 = _mockFile('a/c/d.dart', pathMapper);
+ var file3 = _mockFile('a/packages/e/f.dart', pathMapper,
+ url: 'package:e/f.dart');
+ expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file1, file3),
+ '_from_packages/e/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file3),
+ '../_from_packages/e/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file2), '../../c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file1), '../../_b.dart.dart');
+ });
+
+ test('windows paths', () {
+ try {
+ utils.path = new path.Builder(style: path.Style.windows);
+ var pathMapper = _newPathMapper('a', 'out', true);
+ var file1 = _mockFile('a\\b.dart', pathMapper);
+ var file2 = _mockFile('a\\c\\d.dart', pathMapper);
+ var file3 = _mockFile('a\\packages\\f.dart', pathMapper);
+ expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file1, file3),
+ '_from_packages/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+ expect(pathMapper.importUrlFor(file2, file3),
+ '../_from_packages/_f.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+ expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+ } finally {
+ utils.path = new path.Builder();
+ }
+ });
+ });
+
+ group('transformUrl', () {
+ test('simple source, not in packages/', () {
+ var pathMapper = _newPathMapper('a', 'out', false);
+ var file1 = 'a/b.dart';
+ var file2 = 'a/c/d.html';
+ // when the output == input directory, no paths should be rewritten
+ expect(pathMapper.transformUrl(file1, '/a.dart'), '/a.dart');
+ expect(pathMapper.transformUrl(file1, 'c.dart'), '../a/c.dart');
+
+ // reach out from basedir:
+ expect(pathMapper.transformUrl(file1, '../c/d.dart'), '../c/d.dart');
+
+ // reach into packages dir:
+ expect(pathMapper.transformUrl(file1, 'packages/c.dart'),
+ '../a/packages/c.dart');
+
+ expect(pathMapper.transformUrl(file2, 'e.css'), '../../a/c/e.css');
+
+ _checkPath('../../a/c/../c/e.css', '../../a/c/e.css');
+ expect(pathMapper.transformUrl(file2, '../c/e.css'), '../../a/c/e.css');
+
+ _checkPath('../../a/c/../q/e.css', '../../a/q/e.css');
+ expect(pathMapper.transformUrl(file2, '../q/e.css'), '../../a/q/e.css');
+
+ expect(pathMapper.transformUrl(file2, 'packages/c.css'),
+ '../../a/c/packages/c.css');
+ _checkPath('../../a/c/../packages/c.css', '../../a/packages/c.css');
+ expect(pathMapper.transformUrl(file2, '../packages/c.css'),
+ '../../a/packages/c.css');
+ });
+
+ test('input in packages/', () {
+ var pathMapper = _newPathMapper('a', 'out', true);
+ var file = 'a/packages/e.html';
+ expect(pathMapper.transformUrl(file, 'e.css'),
+ '../../a/packages/e.css');
+ expect(pathMapper.transformUrl(file, '../packages/e.css'),
+ '../../a/packages/e.css');
+ expect(pathMapper.transformUrl(file, '../q/e.css'), '../../a/q/e.css');
+ expect(pathMapper.transformUrl(file, 'packages/c.css'),
+ '../../a/packages/packages/c.css');
+ });
+
+ test('src fragments', () {
+ var pathMapper = _newPathMapper('a', 'out', false);
+ var file1 = 'a/b.dart';
+ var file2 = 'a/c/html.html';
+ // when the output == input directory, no paths should be rewritten
+ expect(pathMapper.transformUrl(file1, '#tips'), '#tips');
+ expect(pathMapper.transformUrl(file1,
+ 'http://www.w3schools.com/html_links.htm#tips'),
+ 'http://www.w3schools.com/html_links.htm#tips');
+ expect(pathMapper.transformUrl(file2,
+ 'html_links.html'),
+ '../../a/c/html_links.html');
+ expect(pathMapper.transformUrl(file2,
+ 'html_links.html#tips'),
+ '../../a/c/html_links.html#tips');
+ });
+ });
+ });
+}
+
+_newPathMapper(String baseDir, String outDir, bool forceMangle,
+ {bool rewriteUrls: true}) =>
+ new PathMapper(baseDir, outDir, 'packages', forceMangle, rewriteUrls);
+
+_mockFile(String filePath, PathMapper pathMapper, {String url}) {
+ var file = new FileInfo(new UrlInfo(
+ url == null ? filePath : url, filePath, null));
+ file.outputFilename = pathMapper.mangle(
+ utils.path.basename(filePath), '.dart', false);
+ return file;
+}
+
+_checkPath(String filePath, String expected) {
+ expect(utils.path.normalize(filePath), expected);
+}
diff --git a/pkg/polymer/test/run.sh b/pkg/polymer/test/run.sh
new file mode 100755
index 0000000..0d2b569
--- /dev/null
+++ b/pkg/polymer/test/run.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+# Copyright (c) 2012, 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.
+
+# Usage: call directly in the commandline as test/run.sh ensuring that you have
+# both 'dart' and 'content_shell' in your path. Filter tests by passing a
+# pattern as an argument to this script.
+
+# TODO(sigmund): replace with a real test runner
+
+# bail on error
+set -e
+
+# print commands executed by this script
+# set -x
+
+DIR=$( cd $( dirname "${BASH_SOURCE[0]}" ) && pwd )
+export DART_FLAGS="--checked"
+TEST_PATTERN=$1
+
+function fail {
+ return 1
+}
+
+function show_diff {
+ diff -u -N $1 $2 | \
+ sed -e "s/^\(+.*\)/[32m\1[0m/" |\
+ sed -e "s/^\(-.*\)/[31m\1[0m/"
+ return 1
+}
+
+function update {
+ read -p "Would you like to update the expectations? [y/N]: " answer
+ if [[ $answer == 'y' || $answer == 'Y' ]]; then
+ cp $2 $1
+ return 0
+ fi
+ return 1
+}
+
+function pass {
+ echo -e "[32mOK[0m"
+}
+
+function compare {
+ # use a standard diff, if they are not identical, format the diff nicely to
+ # see what's the error and prompt to see if they wish to update it. If they
+ # do, continue running more tests.
+ diff -q $1 $2 && pass || show_diff $1 $2 || update $1 $2
+}
+
+if [[ ($TEST_PATTERN == "") ]]; then
+ # Note: dartanalyzer needs to be run from the root directory for proper path
+ # canonicalization.
+ pushd $DIR/.. > /dev/null
+
+ echo Analyzing compiler for warnings or type errors
+ dartanalyzer --hints --fatal-warnings --fatal-type-errors bin/dwc.dart
+
+ echo -e "\nAnalyzing runtime for warnings or type errors"
+ dartanalyzer --hints --fatal-warnings --fatal-type-errors lib/polymer.dart
+
+ popd > /dev/null
+fi
+
+function compare_all {
+# TODO(jmesserly): bash and dart regexp might not be 100% the same. Ideally we
+# could do all the heavy lifting in Dart code, and keep this script as a thin
+# wrapper that sets `--enable-type-checks --enable-asserts`
+ for input in $DIR/../example/component/news/test/*_test.html \
+ $DIR/../../../samples/third_party/todomvc/test/*_test.html; do
+ if [[ ($TEST_PATTERN == "") || ($input =~ $TEST_PATTERN) ]]; then
+ FILENAME=`basename $input`
+ DIRNAME=`dirname $input`
+ if [[ `basename $DIRNAME` == 'input' ]]; then
+ DIRNAME=`dirname $DIRNAME`
+ fi
+ echo -e -n "Checking diff for $FILENAME "
+ DUMP="$DIRNAME/out/$FILENAME.txt"
+ EXPECTATION="$DIRNAME/expected/$FILENAME.txt"
+
+ compare $EXPECTATION $DUMP
+ fi
+ done
+ echo -e "[31mSome tests failed[0m"
+ fail
+}
+
+if [[ -e $DIR/data/input/example ]]; then
+ echo "WARNING: detected old data/input/example symlink."
+ echo "Removing it and rerunning pub install to fix broken example symlinks."
+ echo "See http://dartbug.com/9418 for more information."
+ echo "You should only see this message once."
+ if [[ -e $DIR/packages ]]; then
+ find . -name packages -type l | xargs rm
+ fi
+ rm $DIR/data/input/example
+ pushd $DIR/..
+ pub install
+ popd
+fi
+
+# TODO(jmesserly): dart:io fails if we run the Dart scripts with an absolute
+# path. So use pushd/popd to change the working directory.
+if [[ ($TEST_PATTERN == "") ]]; then
+ pushd $DIR/.. > /dev/null
+ echo -e "\nTesting build.dart... "
+ dart $DART_FLAGS build.dart
+ # Run it the way the editor does. Hide stdout because it is in noisy machine
+ # format. Show stderr in case something breaks.
+ # NOTE: not using --checked because the editor doesn't use it, and to workaround
+ # http://dartbug.com/9637
+ dart build.dart --machine --clean > /dev/null
+ dart build.dart --machine --full > /dev/null
+ dart build.dart --machine --changed ../../samples/third_party/todomvc/web/index.html > /dev/null
+ popd > /dev/null
+fi
+
+pushd $DIR > /dev/null
+echo -e "\nRunning unit tests... "
+dart $DART_FLAGS run_all.dart $@ || compare_all
+popd > /dev/null
+
+# Run Dart analyzer to check that we're generating warning clean code.
+# It's a bit slow, so only do this for TodoMVC and html5_utils tests.
+OUT_PATTERN="$DIR/../../../third_party/samples/todomvc/test/out/test/*$TEST_PATTERN*_bootstrap.dart"
+if [[ `ls $OUT_PATTERN 2>/dev/null` != "" ]]; then
+ echo -e "\nAnalyzing generated code for warnings or type errors."
+ ls $OUT_PATTERN 2>/dev/null | dartanalyzer --package-root=packages \
+ --fatal-warnings --fatal-type-errors -batch
+fi
+
+echo -e "[32mAll tests pass[0m"
diff --git a/pkg/polymer/test/run_all.dart b/pkg/polymer/test/run_all.dart
new file mode 100644
index 0000000..b149502
--- /dev/null
+++ b/pkg/polymer/test/run_all.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2012, 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.
+
+/**
+ * This is a helper for run.sh. We try to run all of the Dart code in one
+ * instance of the Dart VM to reduce warm-up time.
+ */
+library run_impl;
+
+import 'dart:io';
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:polymer/testing/content_shell_test.dart';
+
+import 'css_test.dart' as css_test;
+import 'compiler_test.dart' as compiler_test;
+import 'paths_test.dart' as paths_test;
+import 'utils_test.dart' as utils_test;
+
+main() {
+ var args = new Options().arguments;
+ var pattern = new RegExp(args.length > 0 ? args[0] : '.');
+
+ useCompactVMConfiguration();
+
+ void addGroup(testFile, testMain) {
+ if (pattern.hasMatch(testFile)) {
+ group(testFile.replaceAll('_test.dart', ':'), testMain);
+ }
+ }
+
+ addGroup('compiler_test.dart', compiler_test.main);
+ addGroup('css_test.dart', css_test.main);
+ addGroup('paths_test.dart', paths_test.main);
+ addGroup('utils_test.dart', utils_test.main);
+
+ endToEndTests('data/unit/', 'data/out');
+
+ // Note: if you're adding more render test suites, make sure to update run.sh
+ // as well for convenient baseline diff/updating.
+
+ // TODO(jmesserly): figure out why this fails in content_shell but works in
+ // Dartium and Firefox when using the ShadowDOM polyfill.
+ exampleTest('../example/component/news', ['--no-shadowdom']..addAll(args));
+
+ exampleTest('../../../samples/third_party/todomvc');
+}
+
+void exampleTest(String path, [List<String> args]) {
+ renderTests(path, '$path/test', '$path/test/expected', '$path/test/out',
+ arguments: args);
+}
+
+void cssCompileMangleTest(String path, String pattern,
+ [bool deleteDirectory = true]) {
+ renderTests(path, path, '$path/expected', '$path/out',
+ arguments: ['--css-mangle'], pattern: pattern,
+ deleteDir: deleteDirectory);
+}
+
+void cssCompilePolyFillTest(String path, String pattern, String cssReset,
+ [bool deleteDirectory = true]) {
+ var args = ['--no-css-mangle'];
+ if (cssReset != null) {
+ args.addAll(['--css-reset', '${path}/${cssReset}']);
+ }
+ renderTests(path, path, '$path/expected', '$path/out',
+ arguments: args, pattern: pattern, deleteDir: deleteDirectory);
+}
+
+void cssCompileShadowDOMTest(String path, String pattern,
+ [bool deleteDirectory = true]) {
+ var args = ['--no-css'];
+ renderTests(path, path, '$path/expected', '$path/out',
+ arguments: args, pattern: pattern,
+ deleteDir: deleteDirectory);
+}
diff --git a/pkg/polymer/test/testing.dart b/pkg/polymer/test/testing.dart
new file mode 100644
index 0000000..c89b416
--- /dev/null
+++ b/pkg/polymer/test/testing.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2012, 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.
+
+/** Common definitions used for setting up the test environment. */
+library testing;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:csslib/visitor.dart';
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/parser.dart';
+import 'package:polymer/src/analyzer.dart';
+import 'package:polymer/src/compiler.dart';
+import 'package:polymer/src/file_system.dart';
+import 'package:polymer/src/info.dart';
+import 'package:polymer/src/messages.dart';
+import 'package:polymer/src/compiler_options.dart';
+import 'package:polymer/src/files.dart';
+import 'package:polymer/src/utils.dart';
+
+
+Document parseDocument(String html) => parse(html);
+
+Element parseSubtree(String html) => parseFragment(html).nodes[0];
+
+FileInfo analyzeDefinitionsInTree(Document doc, Messages messages,
+ {String packageRoot: 'packages'}) {
+
+ return analyzeDefinitions(new GlobalInfo(), new UrlInfo('', '', null),
+ doc, packageRoot, messages);
+}
+
+/** Parses files in [fileContents], with [mainHtmlFile] being the main file. */
+List<SourceFile> parseFiles(Map<String, String> fileContents,
+ [String mainHtmlFile = 'index.html']) {
+
+ var result = <SourceFile>[];
+ fileContents.forEach((filename, contents) {
+ var src = new SourceFile(filename);
+ src.document = parse(contents);
+ result.add(src);
+ });
+
+ return result;
+}
+
+/** Analyze all files. */
+Map<String, FileInfo> analyzeFiles(List<SourceFile> files,
+ {Messages messages, String packageRoot: 'packages'}) {
+ messages = messages == null ? new Messages.silent() : messages;
+ var result = new Map<String, FileInfo>();
+
+ // analyze definitions
+ var global = new GlobalInfo();
+ for (var file in files) {
+ var path = file.path;
+ result[path] = analyzeDefinitions(global, new UrlInfo(path, path, null),
+ file.document, packageRoot, messages);
+ }
+
+ // analyze file contents
+ var uniqueIds = new IntIterator();
+ var pseudoElements = new Map();
+ for (var file in files) {
+ analyzeFile(file, result, uniqueIds, pseudoElements, messages, true);
+ }
+ return result;
+}
+
+Compiler createCompiler(Map files, Messages messages, {bool errors: false,
+ bool scopedCss: false}) {
+ List baseOptions = ['--no-colors', '-o', 'out', '--deploy', 'index.html'];
+ if (errors) baseOptions.insert(0, '--warnings_as_errors');
+ if (scopedCss) baseOptions.insert(0, '--scoped-css');
+ var options = CompilerOptions.parse(baseOptions);
+ var fs = new MockFileSystem(files);
+ return new Compiler(fs, options, messages);
+}
+
+String prettyPrintCss(StyleSheet styleSheet) =>
+ ((new CssPrinter())..visitTree(styleSheet)).toString();
+
+/**
+ * Abstraction around file system access to work in a variety of different
+ * environments.
+ */
+class MockFileSystem extends FileSystem {
+ final Map _files;
+ final Map readCount = {};
+
+ MockFileSystem(this._files);
+
+ Future readTextOrBytes(String filename) => readText(filename);
+
+ Future<String> readText(String path) {
+ readCount[path] = readCount.putIfAbsent(path, () => 0) + 1;
+ var file = _files[path];
+ if (file != null) {
+ return new Future.value(file);
+ } else {
+ return new Future.error(
+ new FileException('MockFileSystem: $path not found'));
+ }
+ }
+
+ // Compiler doesn't call these
+ void writeString(String outfile, String text) {}
+ Future flush() {}
+}
diff --git a/pkg/polymer/test/utils_test.dart b/pkg/polymer/test/utils_test.dart
new file mode 100644
index 0000000..393d215
--- /dev/null
+++ b/pkg/polymer/test/utils_test.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2012, 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.
+
+/** Tests for some of the utility helper functions used by the compiler. */
+library utils_test;
+
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:polymer/src/utils.dart';
+
+main() {
+ useCompactVMConfiguration();
+
+ for (bool startUppercase in [false, true]) {
+ Matcher caseEquals(String str) {
+ if (startUppercase) str = str[0].toUpperCase() + str.substring(1);
+ return equals(str);
+ }
+
+ camelCase(str) => toCamelCase(str, startUppercase: startUppercase);
+
+ group('toCamelCase startUppercase=$startUppercase', () {
+ test('empty', () {
+ expect(camelCase(''), equals(''));
+ });
+
+ test('single token', () {
+ expect(camelCase('a'), caseEquals('a'));
+ expect(camelCase('ab'), caseEquals('ab'));
+ expect(camelCase('Ab'), caseEquals('Ab'));
+ expect(camelCase('AB'), caseEquals('AB'));
+ expect(camelCase('long_word'), caseEquals('long_word'));
+ });
+
+ test('dashes in the middle', () {
+ expect(camelCase('a-b'), caseEquals('aB'));
+ expect(camelCase('a-B'), caseEquals('aB'));
+ expect(camelCase('A-b'), caseEquals('AB'));
+ expect(camelCase('long-word'), caseEquals('longWord'));
+ });
+
+ test('leading/trailing dashes', () {
+ expect(camelCase('-hi'), caseEquals('Hi'));
+ expect(camelCase('hi-'), caseEquals('hi'));
+ expect(camelCase('hi-friend-'), caseEquals('hiFriend'));
+ });
+
+ test('consecutive dashes', () {
+ expect(camelCase('--hi-friend'), caseEquals('HiFriend'));
+ expect(camelCase('hi--friend'), caseEquals('hiFriend'));
+ expect(camelCase('hi-friend--'), caseEquals('hiFriend'));
+ });
+ });
+ }
+
+ group('toHyphenedName', () {
+ test('empty', () {
+ expect(toHyphenedName(''), '');
+ });
+
+ test('all lower case', () {
+ expect(toHyphenedName('a'), 'a');
+ expect(toHyphenedName('a-b'), 'a-b');
+ expect(toHyphenedName('aBc'), 'a-bc');
+ expect(toHyphenedName('abC'), 'ab-c');
+ expect(toHyphenedName('abc-d'), 'abc-d');
+ expect(toHyphenedName('long_word'), 'long_word');
+ });
+
+ test('capitalized letters in the middle/end', () {
+ expect(toHyphenedName('aB'), 'a-b');
+ expect(toHyphenedName('longWord'), 'long-word');
+ });
+
+ test('leading capital letters', () {
+ expect(toHyphenedName('Hi'), 'hi');
+ expect(toHyphenedName('Hi-'), 'hi-');
+ expect(toHyphenedName('HiFriend'), 'hi-friend');
+ });
+
+ test('consecutive capital letters', () {
+ expect(toHyphenedName('aBC'), 'a-b-c');
+ });
+ });
+}
diff --git a/pkg/scheduled_test/lib/scheduled_process.dart b/pkg/scheduled_test/lib/scheduled_process.dart
index 3ff903c..27ebc17 100644
--- a/pkg/scheduled_test/lib/scheduled_process.dart
+++ b/pkg/scheduled_test/lib/scheduled_process.dart
@@ -5,6 +5,7 @@
library scheduled_test.scheduled_process;
import 'dart:async';
+import 'dart:convert';
import 'dart:io';
import 'scheduled_test.dart';
@@ -203,7 +204,7 @@
return chunk;
})
.transform(new StringDecoder(_encoding))
- .transform(new LineTransformer()));
+ .transform(new LineSplitter()));
}
/// Schedule an exception handler that will clean up the process and provide
diff --git a/pkg/scheduled_test/test/metatest.dart b/pkg/scheduled_test/test/metatest.dart
index 4834b6e..9e94d07 100644
--- a/pkg/scheduled_test/test/metatest.dart
+++ b/pkg/scheduled_test/test/metatest.dart
@@ -200,10 +200,9 @@
/// Special test configuration for use within the child isolates. This hides all
/// output and reports data back to the parent isolate.
-class _MetaConfiguration extends SimpleConfiguration {
- final name = "MetaConfiguration";
+class _MetaConfiguration extends Configuration {
- void logTestCaseMesssage(TestCase testCase, String message) {}
+ _MetaConfiguration() : super.blank();
void onSummary(int passed, int failed, int errors, List<TestCase> results,
String uncaughtError) {
@@ -220,7 +219,4 @@
}).toList()
});
}
-
- void onInit() {}
- void onDone(bool success) {}
}
diff --git a/pkg/scheduled_test/test/scheduled_process_test.dart b/pkg/scheduled_test/test/scheduled_process_test.dart
index b855e02..33cfa7c 100644
--- a/pkg/scheduled_test/test/scheduled_process_test.dart
+++ b/pkg/scheduled_test/test/scheduled_process_test.dart
@@ -5,6 +5,7 @@
library scheduled_process_test;
import 'dart:async';
+import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
@@ -367,11 +368,12 @@
var utilsPath = path.absolute(path.join(Platform.script, 'utils.dart'));
return new File(path.join(dir, 'test.dart')).writeAsString('''
import 'dart:async';
+ import 'dart:convert';
import 'dart:io';
var stdinLines = stdin
.transform(new StringDecoder())
- .transform(new LineTransformer());
+ .transform(new LineSplitter());
void main() {
$script
diff --git a/pkg/stack_trace/lib/src/frame.dart b/pkg/stack_trace/lib/src/frame.dart
index 3db4ab5..b222737 100644
--- a/pkg/stack_trace/lib/src/frame.dart
+++ b/pkg/stack_trace/lib/src/frame.dart
@@ -134,6 +134,12 @@
}
}
+ /// Parses a string representation of an IE stack frame.
+ ///
+ /// IE10+ frames look just like V8 frames. Prior to IE10, stack traces can't
+ /// be retrieved.
+ factory Frame.parseIE(String frame) => new Frame.parseV8(frame);
+
/// Parses a string representation of a Firefox stack frame.
factory Frame.parseFirefox(String frame) {
var match = _firefoxFrame.firstMatch(frame);
@@ -155,6 +161,12 @@
return new Frame(uri, int.parse(match[4]), null, member);
}
+ /// Parses a string representation of a Safari stack frame.
+ ///
+ /// Safari 6+ frames look just like Firefox frames. Prior to Safari 6, stack
+ /// traces can't be retrieved.
+ factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame);
+
/// Parses this package's string representation of a stack frame.
factory Frame.parseFriendly(String frame) {
var match = _friendlyFrame.firstMatch(frame);
diff --git a/pkg/stack_trace/lib/src/trace.dart b/pkg/stack_trace/lib/src/trace.dart
index 863d64e..19f0a12 100644
--- a/pkg/stack_trace/lib/src/trace.dart
+++ b/pkg/stack_trace/lib/src/trace.dart
@@ -14,6 +14,14 @@
final _terseRegExp = new RegExp(r"(-patch)?(/.*)?$");
+/// A RegExp to match V8's stack traces.
+///
+/// V8's traces start with a line that's either just "Error" or else is a
+/// description of the exception that occurred. That description can be multiple
+/// lines, so we just look for any line other than the first that begins with
+/// four spaces and "at".
+final _v8Trace = new RegExp(r"\n at ");
+
/// A RegExp to match Firefox's stack traces.
///
/// Firefox's trace frames start with the name of the function in which the
@@ -74,8 +82,9 @@
factory Trace.parse(String trace) {
try {
if (trace.isEmpty) return new Trace(<Frame>[]);
- if (trace.startsWith("Error\n")) return new Trace.parseV8(trace);
- if (trace.contains(_firefoxTrace)) return new Trace.parseFirefox(trace);
+ if (trace.contains(_v8Trace)) return new Trace.parseV8(trace);
+ // Valid Safari traces are a superset of valid Firefox traces.
+ if (trace.contains(_firefoxTrace)) return new Trace.parseSafari(trace);
if (trace.contains(_friendlyTrace)) return new Trace.parseFriendly(trace);
// Default to parsing the stack trace as a VM trace. This is also hit on
@@ -93,13 +102,36 @@
/// Parses a string representation of a Chrome/V8 stack trace.
Trace.parseV8(String trace)
- : this(trace.split("\n").skip(1).map((line) => new Frame.parseV8(line)));
+ : this(trace.split("\n").skip(1)
+ // It's possible that an Exception's description contains a line that
+ // looks like a V8 trace line, which will screw this up.
+ // Unfortunately, that's impossible to detect.
+ .skipWhile((line) => !line.startsWith(" at "))
+ .map((line) => new Frame.parseV8(line)));
+
+ /// Parses a string representation of an Internet Explorer stack trace.
+ ///
+ /// IE10+ traces look just like V8 traces. Prior to IE10, stack traces can't
+ /// be retrieved.
+ Trace.parseIE(String trace)
+ : this.parseV8(trace);
/// Parses a string representation of a Firefox stack trace.
Trace.parseFirefox(String trace)
: this(trace.trim().split("\n")
.map((line) => new Frame.parseFirefox(line)));
+ /// Parses a string representation of a Safari stack trace.
+ ///
+ /// Safari 6+ stack traces look just like Firefox traces, except that they
+ /// sometimes (e.g. in isolates) have a "[native code]" frame. We just ignore
+ /// this frame to make the stack format more consistent between browsers.
+ /// Prior to Safari 6, stack traces can't be retrieved.
+ Trace.parseSafari(String trace)
+ : this(trace.trim().split("\n")
+ .where((line) => line != '[native code]')
+ .map((line) => new Frame.parseFirefox(line)));
+
/// Parses this package's a string representation of a stack trace.
Trace.parseFriendly(String trace)
: this(trace.trim().split("\n")
diff --git a/pkg/stack_trace/test/trace_test.dart b/pkg/stack_trace/test/trace_test.dart
index 92b2b20..c3ce039 100644
--- a/pkg/stack_trace/test/trace_test.dart
+++ b/pkg/stack_trace/test/trace_test.dart
@@ -61,6 +61,35 @@
equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
expect(trace.frames[2].uri,
equals(Uri.parse("http://pub.dartlang.org/thing.js")));
+
+ trace = new Trace.parse(
+ "Exception: foo\n"
+ ' at Foo._bar (http://pub.dartlang.org/stuff.js:42:21)\n'
+ ' at http://pub.dartlang.org/stuff.js:0:2\n'
+ ' at zip.<anonymous>.zap '
+ '(http://pub.dartlang.org/thing.js:1:100)');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+ expect(trace.frames[2].uri,
+ equals(Uri.parse("http://pub.dartlang.org/thing.js")));
+
+ trace = new Trace.parse(
+ 'Exception: foo\n'
+ ' bar\n'
+ ' at Foo._bar (http://pub.dartlang.org/stuff.js:42:21)\n'
+ ' at http://pub.dartlang.org/stuff.js:0:2\n'
+ ' at zip.<anonymous>.zap '
+ '(http://pub.dartlang.org/thing.js:1:100)');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+ expect(trace.frames[2].uri,
+ equals(Uri.parse("http://pub.dartlang.org/thing.js")));
});
test('parses a Firefox stack trace correctly', () {
@@ -101,6 +130,22 @@
equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
});
+ test('.parseSafari', () {
+ var trace = new Trace.parse(
+ 'Foo._bar@http://pub.dartlang.org/stuff.js:42\n'
+ 'zip/<@http://pub.dartlang.org/stuff.js:0\n'
+ 'zip.zap(12, "@)()/<")@http://pub.dartlang.org/thing.js:1\n'
+ '[native code]');
+
+ expect(trace.frames[0].uri,
+ equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+ expect(trace.frames[1].uri,
+ equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+ expect(trace.frames[2].uri,
+ equals(Uri.parse("http://pub.dartlang.org/thing.js")));
+ expect(trace.frames.length, equals(3));
+ });
+
test('parses a package:stack_trace stack trace correctly', () {
var trace = new Trace.parse(
'http://dartlang.org/foo/bar.dart 10:11 Foo.<fn>.bar\n'
diff --git a/pkg/unittest/lib/src/configuration.dart b/pkg/unittest/lib/src/configuration.dart
index 4571b90..c9ec1c0 100644
--- a/pkg/unittest/lib/src/configuration.dart
+++ b/pkg/unittest/lib/src/configuration.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
+// 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.
@@ -16,10 +16,17 @@
factory Configuration() => new SimpleConfiguration();
/**
+ * Creates an [Configuration] instances that does nothing.
+ *
+ * For use by subclasses which wish to implement only a subset of features.
+ */
+ Configuration.blank();
+
+ /**
* If [true], tests are started automatically. Otherwise [runTests]
* must be called explicitly after tests are set up.
*/
- bool get autoStart;
+ bool get autoStart => true;
/**
* Called as soon as the unittest framework becomes initialized. This is done
@@ -28,7 +35,7 @@
* It is also used to tell the vm or browser that tests are going to be run
* asynchronously and that the process should wait until they are done.
*/
- void onInit();
+ void onInit() {}
/** Called as soon as the unittest framework starts running. */
void onStart() {}
@@ -37,31 +44,31 @@
* Called when each test starts. Useful to show intermediate progress on
* a test suite.
*/
- void onTestStart(TestCase testCase);
+ void onTestStart(TestCase testCase) {}
/**
* Called when each test is first completed. Useful to show intermediate
* progress on a test suite.
*/
- void onTestResult(TestCase testCase);
+ void onTestResult(TestCase testCase) {}
/**
* Called when an already completed test changes state. For example: a test
* that was marked as passing may later be marked as being in error because
* it still had callbacks being invoked.
*/
- void onTestResultChanged(TestCase testCase);
+ void onTestResultChanged(TestCase testCase) {}
/**
* Handles the logging of messages by a test case.
*/
- void onLogMessage(TestCase testCase, String message);
+ void onLogMessage(TestCase testCase, String message) {}
/**
* Called when the unittest framework is done running. [success] indicates
* whether all tests passed successfully.
*/
- void onDone(bool success);
+ void onDone(bool success) {}
/**
* Called with the result of all test cases. Browser tests commonly override
@@ -71,6 +78,6 @@
* of tests (e.g. setting up the test).
*/
void onSummary(int passed, int failed, int errors, List<TestCase> results,
- String uncaughtError);
+ String uncaughtError) {}
}
diff --git a/pkg/unittest/lib/src/simple_configuration.dart b/pkg/unittest/lib/src/simple_configuration.dart
index b9d3a02..d4a0f6e 100644
--- a/pkg/unittest/lib/src/simple_configuration.dart
+++ b/pkg/unittest/lib/src/simple_configuration.dart
@@ -22,7 +22,7 @@
* advantage of the platform can create a subclass and override methods from
* this class.
*/
-class SimpleConfiguration implements Configuration {
+class SimpleConfiguration extends Configuration {
// The VM won't shut down if a receive port is open. Use this to make sure
// we correctly wait for asynchronous tests.
ReceivePort _receivePort;
@@ -32,7 +32,7 @@
* Particularly useful in cases where we have parent/child configurations
* such as layout tests.
*/
- final String name = 'Configuration';
+ String get name => 'Configuration';
bool get autoStart => true;
@@ -56,7 +56,7 @@
* The constructor sets up a failure handler for [expect] that redirects
* [expect] failures to [onExpectFailure].
*/
- SimpleConfiguration() {
+ SimpleConfiguration() : super.blank() {
configureExpectFailureHandler(new _ExpectFailureHandler(this));
}
@@ -65,8 +65,6 @@
_postMessage('unittest-suite-wait-for-done');
}
- void onStart() {}
-
/**
* Called when each test starts. Useful to show intermediate progress on
* a test suite. Derived classes should call this first before their own
diff --git a/pkg/unittest/test/unittest_test_utils.dart b/pkg/unittest/test/unittest_test_utils.dart
index 03fc8d6..7d9a922 100644
--- a/pkg/unittest/test/unittest_test_utils.dart
+++ b/pkg/unittest/test/unittest_test_utils.dart
@@ -33,7 +33,7 @@
'$setup:$teardown:$uncaughtError$testDetails';
}
-class TestConfiguration extends SimpleConfiguration {
+class TestConfiguration extends Configuration {
// Some test state that is captured.
int count = 0; // A count of callbacks.
@@ -44,9 +44,7 @@
final SendPort _port;
String _result;
- TestConfiguration(this._port);
-
- void onInit() {}
+ TestConfiguration(this._port) : super.blank();
void onSummary(int passed, int failed, int errors, List<TestCase> results,
String uncaughtError) {
diff --git a/runtime/bin/dbg_connection.cc b/runtime/bin/dbg_connection.cc
index e5d4f6b..e5abc5e 100644
--- a/runtime/bin/dbg_connection.cc
+++ b/runtime/bin/dbg_connection.cc
@@ -260,7 +260,9 @@
int msg_id,
const char* err_msg) {
dart::TextBuffer msg(64);
- msg.Printf("{\"id\": %d, \"error\": \"Error: %s\"}", msg_id, err_msg);
+ msg.Printf("{\"id\": %d, \"error\": \"Error: ", msg_id);
+ msg.AddEscapedString(err_msg);
+ msg.Printf("\"}");
SendMsg(debug_fd, &msg);
}
diff --git a/runtime/bin/dbg_message.cc b/runtime/bin/dbg_message.cc
index caec295..36cfeba 100644
--- a/runtime/bin/dbg_message.cc
+++ b/runtime/bin/dbg_message.cc
@@ -50,6 +50,14 @@
}
+bool MessageParser::HasParam(const char* name) const {
+ const char* params = Params();
+ ASSERT(params != NULL);
+ dart::JSONReader r(params);
+ return r.Seek(name);
+}
+
+
intptr_t MessageParser::GetIntParam(const char* name) const {
const char* params = Params();
ASSERT(params != NULL);
@@ -224,13 +232,13 @@
intptr_t len = 0;
Dart_Handle res = Dart_ListLength(object, &len);
ASSERT_NOT_ERROR(res);
- buf->Printf("\"kind\":\"list\",\"length\":%"Pd",", len);
+ buf->Printf("\"kind\":\"list\",\"length\":%" Pd ",", len);
} else {
buf->Printf("\"kind\":\"object\",");
intptr_t class_id = 0;
Dart_Handle res = Dart_GetObjClassId(object, &class_id);
if (!Dart_IsError(res)) {
- buf->Printf("\"classId\":%"Pd",", class_id);
+ buf->Printf("\"classId\":%" Pd ",", class_id);
}
}
buf->Printf("\"text\":\"");
@@ -250,7 +258,7 @@
static void FormatRemoteObj(dart::TextBuffer* buf, Dart_Handle object) {
intptr_t obj_id = Dart_CacheObject(object);
ASSERT(obj_id >= 0);
- buf->Printf("{\"objectId\":%"Pd",", obj_id);
+ buf->Printf("{\"objectId\":%" Pd ",", obj_id);
FormatValue(buf, object);
buf->Printf("}");
}
@@ -300,9 +308,9 @@
RETURN_IF_ERROR(name);
buf->Printf("{\"name\":\"%s\",", GetStringChars(name));
if (super_id > 0) {
- buf->Printf("\"superclassId\":%"Pd",", super_id);
+ buf->Printf("\"superclassId\":%" Pd ",", super_id);
}
- buf->Printf("\"libraryId\":%"Pd",", library_id);
+ buf->Printf("\"libraryId\":%" Pd ",", library_id);
RETURN_IF_ERROR(static_fields);
buf->Printf("\"fields\":");
FormatNamedValueList(buf, static_fields);
@@ -366,7 +374,7 @@
}
Dart_Handle res = Dart_GetObjClassId(object, &class_id);
RETURN_IF_ERROR(res);
- buf->Printf("{\"classId\": %"Pd",", class_id);
+ buf->Printf("{\"classId\": %" Pd ",", class_id);
buf->Printf("\"kind\":\"object\",\"fields\":");
Dart_Handle fields = Dart_GetInstanceFields(object);
RETURN_IF_ERROR(fields);
@@ -383,8 +391,8 @@
intptr_t slice_length) {
intptr_t end_index = index + slice_length;
ASSERT(end_index <= list_length);
- buf->Printf("{\"index\":%"Pd",", index);
- buf->Printf("\"length\":%"Pd",", slice_length);
+ buf->Printf("{\"index\":%" Pd ",", index);
+ buf->Printf("\"length\":%" Pd ",", slice_length);
buf->Printf("\"elements\":[");
for (intptr_t i = index; i < end_index; i++) {
Dart_Handle value = Dart_ListGetAt(list, i);
@@ -470,6 +478,7 @@
{ "getClassProperties", DbgMessage::HandleGetClassPropsCmd },
{ "getLibraryProperties", DbgMessage::HandleGetLibPropsCmd },
{ "setLibraryProperties", DbgMessage::HandleSetLibPropsCmd },
+ { "evaluateExpr", DbgMessage::HandleEvaluateExprCmd },
{ "getObjectProperties", DbgMessage::HandleGetObjPropsCmd },
{ "getListElements", DbgMessage::HandleGetListCmd },
{ "getGlobalVariables", DbgMessage::HandleGetGlobalsCmd },
@@ -633,6 +642,54 @@
}
+bool DbgMessage::HandleEvaluateExprCmd(DbgMessage* in_msg) {
+ ASSERT(in_msg != NULL);
+ MessageParser msg_parser(in_msg->buffer(), in_msg->buffer_len());
+ int msg_id = msg_parser.MessageId();
+ Dart_Handle target;
+
+ if (msg_parser.HasParam("libraryId")) {
+ in_msg->SendErrorReply(msg_id,
+ "libararyId evaluation target not supported");
+ return false;
+ } else if (msg_parser.HasParam("classId")) {
+ in_msg->SendErrorReply(msg_id,
+ "classId evaluation target not supported");
+ return false;
+ } else if (msg_parser.HasParam("objectId")) {
+ intptr_t obj_id = msg_parser.GetIntParam("objectId");
+ target = Dart_GetCachedObject(obj_id);
+ } else {
+ in_msg->SendErrorReply(msg_id, "illegal evaluation target");
+ return false;
+ }
+
+ if (Dart_IsError(target)) {
+ in_msg->SendErrorReply(msg_id, Dart_GetError(target));
+ return false;
+ }
+ char* expr_chars = msg_parser.GetStringParam("expression");
+ Dart_Handle expr = Dart_NewStringFromCString(expr_chars);
+ if (Dart_IsError(expr)) {
+ in_msg->SendErrorReply(msg_id, Dart_GetError(expr));
+ return false;
+ }
+
+ Dart_Handle value = Dart_EvaluateExpr(target, expr);
+ if (Dart_IsError(value)) {
+ in_msg->SendErrorReply(msg_id, Dart_GetError(value));
+ return false;
+ }
+
+ dart::TextBuffer msg(64);
+ msg.Printf("{\"id\":%d, \"result\":", msg_id);
+ FormatRemoteObj(&msg, value);
+ msg.Printf("}");
+ in_msg->SendReply(&msg);
+ return false;
+}
+
+
bool DbgMessage::HandleGetObjPropsCmd(DbgMessage* in_msg) {
ASSERT(in_msg != NULL);
MessageParser msg_parser(in_msg->buffer(), in_msg->buffer_len());
@@ -869,7 +926,7 @@
Dart_Handle res = Dart_IntegerToUint64(bp_id, &bp_id_value);
ASSERT_NOT_ERROR(res);
dart::TextBuffer msg(64);
- msg.Printf("{ \"id\": %d, \"result\": { \"breakpointId\": %"Pu64" }}",
+ msg.Printf("{ \"id\": %d, \"result\": { \"breakpointId\": %" Pu64 " }}",
msg_id, bp_id_value);
in_msg->SendReply(&msg);
return false;
@@ -992,7 +1049,7 @@
dart::TextBuffer msg(128);
msg.Printf("{ \"event\": \"paused\", \"params\": { ");
msg.Printf("\"reason\": \"breakpoint\", ");
- msg.Printf("\"isolateId\": %"Pd64"", isolate_id_);
+ msg.Printf("\"isolateId\": %" Pd64 "", isolate_id_);
if (!Dart_IsNull(location.script_url)) {
ASSERT(Dart_IsString(location.script_url));
msg.Printf(",\"location\": { \"url\":");
@@ -1014,7 +1071,7 @@
dart::TextBuffer msg(128);
msg.Printf("{ \"event\": \"paused\", \"params\": {");
msg.Printf("\"reason\": \"exception\", ");
- msg.Printf("\"isolateId\": %"Pd64", ", isolate_id_);
+ msg.Printf("\"isolateId\": %" Pd64 ", ", isolate_id_);
msg.Printf("\"exception\":");
FormatRemoteObj(&msg, exception);
FormatLocationFromTrace(&msg, stack_trace, ", ");
@@ -1034,7 +1091,7 @@
ASSERT_NOT_ERROR(res);
msg.Printf("{ \"event\": \"paused\", \"params\": { ");
msg.Printf("\"reason\": \"interrupted\", ");
- msg.Printf("\"isolateId\": %"Pd64"", isolate_id);
+ msg.Printf("\"isolateId\": %" Pd64 "", isolate_id);
FormatLocationFromTrace(&msg, trace, ", ");
msg.Printf("}}");
} else {
@@ -1045,7 +1102,7 @@
ASSERT(kind == kShutdown);
msg.Printf("\"reason\": \"shutdown\", ");
}
- msg.Printf("\"id\": %"Pd64" ", isolate_id);
+ msg.Printf("\"id\": %" Pd64 " ", isolate_id);
msg.Printf("}}");
}
DebuggerConnectionHandler::BroadcastMsg(&msg);
@@ -1170,10 +1227,10 @@
return; // No items in the list.
}
DbgMsgQueue* queue = list_;
- msg->Printf("%"Pd64"", queue->isolate_id());
+ msg->Printf("%" Pd64 "", queue->isolate_id());
queue = queue->next();
while (queue != NULL) {
- msg->Printf(",%"Pd64"", queue->isolate_id());
+ msg->Printf(",%" Pd64 "", queue->isolate_id());
queue = queue->next();
}
}
@@ -1185,9 +1242,9 @@
Dart_EnterScope();
dart::TextBuffer msg(128);
msg.Printf("{ \"event\": \"breakpointResolved\", \"params\": {");
- msg.Printf("\"breakpointId\": %"Pd"", bp_id);
+ msg.Printf("\"breakpointId\": %" Pd "", bp_id);
- msg.Printf(", \"isolateId\":%"Pd64"", isolate_id);
+ msg.Printf(", \"isolateId\":%" Pd64 "", isolate_id);
ASSERT(!Dart_IsNull(location.script_url));
ASSERT(Dart_IsString(location.script_url));
msg.Printf(", \"location\":{\"url\":");
diff --git a/runtime/bin/dbg_message.h b/runtime/bin/dbg_message.h
index ca05255..5386318 100644
--- a/runtime/bin/dbg_message.h
+++ b/runtime/bin/dbg_message.h
@@ -43,6 +43,7 @@
int MessageId() const;
const char* Params() const;
+ bool HasParam(const char* name) const;
intptr_t GetIntParam(const char* name) const;
intptr_t GetOptIntParam(const char* name, intptr_t default_val) const;
@@ -103,6 +104,7 @@
static bool HandleGetLibPropsCmd(DbgMessage* msg);
static bool HandleSetLibPropsCmd(DbgMessage* msg);
static bool HandleGetGlobalsCmd(DbgMessage* msg);
+ static bool HandleEvaluateExprCmd(DbgMessage* msg);
static bool HandleGetObjPropsCmd(DbgMessage* msg);
static bool HandleGetListCmd(DbgMessage* msg);
static bool HandleGetScriptURLsCmd(DbgMessage* msg);
diff --git a/runtime/bin/eventhandler_linux.cc b/runtime/bin/eventhandler_linux.cc
index 111e7e6..6d15c9e 100644
--- a/runtime/bin/eventhandler_linux.cc
+++ b/runtime/bin/eventhandler_linux.cc
@@ -171,7 +171,7 @@
if (result == -1) {
perror("Interrupt message failure:");
}
- FATAL1("Interrupt message failure. Wrote %"Pd" bytes.", result);
+ FATAL1("Interrupt message failure. Wrote %" Pd " bytes.", result);
}
}
diff --git a/runtime/bin/eventhandler_macos.cc b/runtime/bin/eventhandler_macos.cc
index 971ef1b..419c93e 100644
--- a/runtime/bin/eventhandler_macos.cc
+++ b/runtime/bin/eventhandler_macos.cc
@@ -190,7 +190,7 @@
if (result == -1) {
perror("Interrupt message failure:");
}
- FATAL1("Interrupt message failure. Wrote %"Pd" bytes.", result);
+ FATAL1("Interrupt message failure. Wrote %" Pd " bytes.", result);
}
}
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index ff790d6..2948ce8 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -35,6 +35,8 @@
V(Platform_LocalHostname, 0) \
V(Platform_ExecutableName, 0) \
V(Platform_Environment, 0) \
+ V(Platform_ExecutableArguments, 0) \
+ V(Platform_PackageRoot, 0) \
V(Platform_GetVersion, 0) \
V(Process_Start, 10) \
V(Process_Wait, 5) \
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 57c5168..7e5138e 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -339,6 +339,8 @@
}
}
+ // The arguments to the VM are at positions 1 through i-1 in argv.
+ Platform::SetExecutableArguments(i, argv);
// Get the script name.
if (i < argc) {
@@ -461,6 +463,7 @@
return NULL;
}
+ Platform::SetPackageRoot(package_root);
Dart_Handle io_lib_url = DartUtils::NewString("dart:io");
CHECK_RESULT(io_lib_url);
Dart_Handle io_lib = Dart_LookupLibrary(io_lib_url);
diff --git a/runtime/bin/platform.cc b/runtime/bin/platform.cc
index 4d1bbed..f7a2920 100644
--- a/runtime/bin/platform.cc
+++ b/runtime/bin/platform.cc
@@ -13,6 +13,9 @@
namespace bin {
const char* Platform::executable_name_ = NULL;
+const char* Platform::package_root_ = NULL;
+int Platform::script_index_ = 1;
+char** Platform::argv_ = NULL;
void FUNCTION_NAME(Platform_NumberOfProcessors)(Dart_NativeArguments args) {
Dart_SetReturnValue(args, Dart_NewInteger(Platform::NumberOfProcessors()));
@@ -46,6 +49,31 @@
args, Dart_NewStringFromCString(Platform::GetExecutableName()));
}
+
+void FUNCTION_NAME(Platform_ExecutableArguments)(Dart_NativeArguments args) {
+ int end = Platform::GetScriptIndex();
+ char** argv = Platform::GetArgv();
+ Dart_Handle result = Dart_NewList(end - 1);
+ for (intptr_t i = 1; i < end; i++) {
+ Dart_Handle str = DartUtils::NewString(argv[i]);
+ Dart_Handle error = Dart_ListSetAt(result, i - 1, str);
+ if (Dart_IsError(error)) {
+ Dart_PropagateError(error);
+ }
+ }
+ Dart_SetReturnValue(args, result);
+}
+
+
+void FUNCTION_NAME(Platform_PackageRoot)(Dart_NativeArguments args) {
+ const char* package_root = Platform::GetPackageRoot();
+ if (package_root == NULL) {
+ package_root = "";
+ }
+ Dart_SetReturnValue(args, Dart_NewStringFromCString(package_root));
+}
+
+
void FUNCTION_NAME(Platform_Environment)(Dart_NativeArguments args) {
intptr_t count = 0;
char** env = Platform::Environment(&count);
diff --git a/runtime/bin/platform.h b/runtime/bin/platform.h
index dd195c3..ca723ae3 100644
--- a/runtime/bin/platform.h
+++ b/runtime/bin/platform.h
@@ -42,8 +42,31 @@
return executable_name_;
}
+ // Stores and gets the package root.
+ static void SetPackageRoot(const char* package_root) {
+ package_root_ = package_root;
+ }
+ static const char* GetPackageRoot() {
+ return package_root_;
+ }
+
+ // Stores and gets the flags passed to the executable.
+ static void SetExecutableArguments(int script_index, char** argv) {
+ script_index_ = script_index;
+ argv_ = argv;
+ }
+ static int GetScriptIndex() {
+ return script_index_;
+ }
+ static char** GetArgv() {
+ return argv_;
+ }
+
private:
static const char* executable_name_;
+ static const char* package_root_;
+ static int script_index_;
+ static char** argv_; // VM flags are argv_[1 ... script_index_ - 1]
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(Platform);
diff --git a/runtime/bin/platform_patch.dart b/runtime/bin/platform_patch.dart
index cfe7a80..fe1e90c 100644
--- a/runtime/bin/platform_patch.dart
+++ b/runtime/bin/platform_patch.dart
@@ -11,5 +11,8 @@
/* patch */ static _localHostname() native "Platform_LocalHostname";
/* patch */ static _executable() native "Platform_ExecutableName";
/* patch */ static _environment() native "Platform_Environment";
+ /* patch */ static List<String> _executableArguments()
+ native "Platform_ExecutableArguments";
+ /* patch */ static String _packageRoot() native "Platform_PackageRoot";
/* patch */ static String _version() native "Platform_GetVersion";
}
diff --git a/runtime/bin/run_vm_tests.cc b/runtime/bin/run_vm_tests.cc
index 9daac7d..7ff4198 100644
--- a/runtime/bin/run_vm_tests.cc
+++ b/runtime/bin/run_vm_tests.cc
@@ -52,7 +52,7 @@
(run_filter == kAllBenchmarks) ||
(strcmp(run_filter, this->name()) == 0)) {
this->Run();
- OS::Print("%s(RunTime): %"Pd"\n", this->name(), this->score());
+ OS::Print("%s(RunTime): %" Pd "\n", this->name(), this->score());
run_matches++;
} else if (run_filter == kList) {
fprintf(stdout, "%s\n", this->name());
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index aa3963a..4f35fab 100755
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -2010,6 +2010,39 @@
void** peer);
/**
+ * Gets an integer native argument at some index.
+ * \param args Native arguments structure.
+ * \param arg_index Index of the desired argument in the structure above.
+ * \param value Returns the integer value if the argument is an Integer.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeIntegerArgument(Dart_NativeArguments args,
+ int index,
+ int64_t* value);
+
+/**
+ * Gets a boolean native argument at some index.
+ * \param args Native arguments structure.
+ * \param arg_index Index of the desired argument in the structure above.
+ * \param value Returns the boolean value if the argument is a Boolean.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeBooleanArgument(Dart_NativeArguments args,
+ int index,
+ bool* value);
+
+/**
+ * Gets a double native argument at some index.
+ * \param args Native arguments structure.
+ * \param arg_index Index of the desired argument in the structure above.
+ * \param value Returns the double value if the argument is a double.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeDoubleArgument(Dart_NativeArguments args,
+ int index,
+ double* value);
+
+/**
* Sets the return value for a native function.
*/
DART_EXPORT void Dart_SetReturnValue(Dart_NativeArguments args,
@@ -2022,7 +2055,7 @@
bool retval);
DART_EXPORT void Dart_SetIntegerReturnValue(Dart_NativeArguments args,
- intptr_t retval);
+ int64_t retval);
DART_EXPORT void Dart_SetDoubleReturnValue(Dart_NativeArguments args,
double retval);
diff --git a/runtime/include/dart_debugger_api.h b/runtime/include/dart_debugger_api.h
index 36249ff..b847a91 100755
--- a/runtime/include/dart_debugger_api.h
+++ b/runtime/include/dart_debugger_api.h
@@ -472,6 +472,25 @@
/**
+ * Execute the expression given in string \expr in the context
+ * of \target.
+ *
+ * Requires there to be a current isolate.
+ *
+ * The expression is evaluated in the context of \target.
+ * If \target is a Dart object, the expression is evaluated as if
+ * it were an instance method of the class of the object.
+ * TODO(hausner): add other execution contexts, e.g. library and class.
+ *
+ * \return A handle to the computed value, or an error object if
+ * the compilation of the expression fails, or if the evaluation throws
+ * an error.
+ */
+DART_EXPORT Dart_Handle Dart_EvaluateExpr(Dart_Handle target,
+ Dart_Handle expr);
+
+
+/**
* Returns the class of the given \object.
*
* Requires there to be a current isolate.
diff --git a/runtime/lib/array.cc b/runtime/lib/array.cc
index da55de9..7116f5b 100644
--- a/runtime/lib/array.cc
+++ b/runtime/lib/array.cc
@@ -19,7 +19,7 @@
isolate, arguments->NativeArgAt(1));
if (!length.IsSmi()) {
const String& error = String::Handle(String::NewFormatted(
- "Length must be an integer in the range [0..%"Pd"].",
+ "Length must be an integer in the range [0..%" Pd "].",
Array::kMaxElements));
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, error);
@@ -28,7 +28,7 @@
intptr_t len = Smi::Cast(length).Value();
if (len < 0 || len > Array::kMaxElements) {
const String& error = String::Handle(String::NewFormatted(
- "Length (%"Pd") must be an integer in the range [0..%"Pd"].",
+ "Length (%" Pd ") must be an integer in the range [0..%" Pd "].",
len, Array::kMaxElements));
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, error);
diff --git a/runtime/lib/double.cc b/runtime/lib/double.cc
index 04006e5..f69e963 100644
--- a/runtime/lib/double.cc
+++ b/runtime/lib/double.cc
@@ -172,16 +172,6 @@
}
-DEFINE_NATIVE_ENTRY(Double_pow, 2) {
- const double operand =
- Double::CheckedHandle(arguments->NativeArgAt(0)).value();
- GET_NON_NULL_NATIVE_ARGUMENT(
- Double, exponent_object, arguments->NativeArgAt(1));
- const double exponent = exponent_object.value();
- return Double::New(pow(operand, exponent));
-}
-
-
#if defined(TARGET_OS_MACOS)
// MAC OSX math library produces old style cast warning.
#pragma GCC diagnostic ignored "-Wold-style-cast"
diff --git a/runtime/lib/double.dart b/runtime/lib/double.dart
index 29247ad..166d731 100644
--- a/runtime/lib/double.dart
+++ b/runtime/lib/double.dart
@@ -126,21 +126,6 @@
int toInt() native "Double_toInt";
double toDouble() { return this; }
- double pow(num exponent) {
- if (exponent == 0) {
- return 1.0; // ECMA-262 15.8.2.13
- }
- if (exponent is! num) {
- throw new ArgumentError(null);
- }
- double doubleExponent = exponent.toDouble();
- if (isNaN || exponent.isNaN) {
- return double.NAN;
- }
- return _pow(doubleExponent);
- }
- double _pow(double exponent) native "Double_pow";
-
String toStringAsFixed(int fractionDigits) {
// See ECMAScript-262, 15.7.4.5 for details.
diff --git a/runtime/lib/integers.dart b/runtime/lib/integers.dart
index aaf5712..dca7ef8 100644
--- a/runtime/lib/integers.dart
+++ b/runtime/lib/integers.dart
@@ -163,23 +163,6 @@
int toInt() { return this; }
double toDouble() { return new _Double.fromInteger(this); }
- int pow(int exponent) {
- // Exponentiation by squaring.
- int base = this;
- int result = 1;
- while (exponent != 0) {
- if ((exponent & 1) == 1) {
- result *= base;
- }
- exponent >>= 1;
- // Skip unnecessary operation (can overflow to Mint or Bigint).
- if (exponent != 0) {
- base *= base;
- }
- }
- return result;
- }
-
String toStringAsFixed(int fractionDigits) {
return this.toDouble().toStringAsFixed(fractionDigits);
}
diff --git a/runtime/lib/math.cc b/runtime/lib/math.cc
index 317045d..ca7c7e6 100644
--- a/runtime/lib/math.cc
+++ b/runtime/lib/math.cc
@@ -66,6 +66,15 @@
return Double::New(log(operand.value()));
}
+DEFINE_NATIVE_ENTRY(Math_doublePow, 2) {
+ const double operand =
+ Double::CheckedHandle(arguments->NativeArgAt(0)).value();
+ GET_NON_NULL_NATIVE_ARGUMENT(
+ Double, exponent_object, arguments->NativeArgAt(1));
+ const double exponent = exponent_object.value();
+ return Double::New(pow(operand, exponent));
+}
+
// Returns the typed-data array store in '_Random._state' field.
static RawTypedData* GetRandomStateArray(const Instance& receiver) {
diff --git a/runtime/lib/math_patch.dart b/runtime/lib/math_patch.dart
index 1bf701d..00cacdf 100644
--- a/runtime/lib/math_patch.dart
+++ b/runtime/lib/math_patch.dart
@@ -10,10 +10,38 @@
// an [int], otherwise the result is a [double].
patch num pow(num x, num exponent) {
if ((x is int) && (exponent is int) && (exponent >= 0)) {
- return x.pow(exponent);
+ return _intPow(x, exponent);
}
- // Double.pow will call exponent.toDouble().
- return x.toDouble().pow(exponent);
+ return _doublePow(x.toDouble(), exponent.toDouble());
+}
+
+double _doublePow(double base, double exponent) {
+ if (exponent == 0.0) {
+ return 1.0; // ECMA-262 15.8.2.13
+ }
+ if (base == 1.0) return 1.0;
+ if (base.isNaN || exponent.isNaN) {
+ return double.NAN;
+ }
+ return _pow(base, exponent);
+}
+
+double _pow(double base, double exponent) native "Math_doublePow";
+
+int _intPow(int base, int exponent) {
+ // Exponentiation by squaring.
+ int result = 1;
+ while (exponent != 0) {
+ if ((exponent & 1) == 1) {
+ result *= base;
+ }
+ exponent >>= 1;
+ // Skip unnecessary operation (can overflow to Mint or Bigint).
+ if (exponent != 0) {
+ base *= base;
+ }
+ }
+ return result;
}
patch double atan2(num a, num b) => _atan2(a.toDouble(), b.toDouble());
diff --git a/runtime/lib/mirrors.cc b/runtime/lib/mirrors.cc
index 7ab68b6..e0c8b23 100644
--- a/runtime/lib/mirrors.cc
+++ b/runtime/lib/mirrors.cc
@@ -305,9 +305,16 @@
GET_NON_NULL_NATIVE_ARGUMENT(Type, type, arguments->NativeArgAt(0));
const Class& cls = Class::Handle(type.type_class());
ASSERT(!cls.IsNull());
- return CreateClassMirror(cls,
- AbstractType::Handle(),
- Instance::null_instance());
+ // Strip the type for generics only.
+ if (cls.NumTypeParameters() == 0) {
+ return CreateClassMirror(cls,
+ type,
+ Object::null_instance());
+ } else {
+ return CreateClassMirror(cls,
+ AbstractType::Handle(),
+ Object::null_instance());
+ }
}
diff --git a/runtime/lib/mirrors_impl.dart b/runtime/lib/mirrors_impl.dart
index 88a89cc..ea63f90 100644
--- a/runtime/lib/mirrors_impl.dart
+++ b/runtime/lib/mirrors_impl.dart
@@ -369,6 +369,15 @@
final Type _reflectedType;
final bool _isGeneric;
+ bool get hasReflectedType => _reflectedType != null;
+ Type get reflectedType {
+ if (!hasReflectedType) {
+ throw new UnsupportedError(
+ "Declarations of generics have no reflected type");
+ }
+ return _reflectedType;
+ }
+
Symbol _simpleName;
Symbol get simpleName {
// dynamic, void and the function types have their names set eagerly in the
@@ -417,7 +426,7 @@
// Object has no superclass.
return null;
}
- _superclass = reflectClass(supertype);
+ _superclass = _Mirrors._reflectType(supertype);
}
return _superclass;
}
@@ -924,7 +933,22 @@
this.isGenerativeConstructor,
this.isRedirectingConstructor,
this.isFactoryConstructor)
- : super(reflectee, _s(simpleName));
+ : this.isOperator = _operators.contains(simpleName),
+ super(reflectee, _s(simpleName));
+
+ static const _operators = const ["%", "&", "*", "+", "-", "/", "<", "<<",
+ "<=", "==", ">", ">=", ">>", "[]", "[]=", "^", "|", "~", "unary-", "~/"];
+
+ final bool isStatic;
+ final bool isAbstract;
+ final bool isGetter;
+ final bool isSetter;
+ final bool isConstructor;
+ final bool isConstConstructor;
+ final bool isGenerativeConstructor;
+ final bool isRedirectingConstructor;
+ final bool isFactoryConstructor;
+ final bool isOperator;
DeclarationMirror _owner;
DeclarationMirror get owner {
@@ -969,20 +993,8 @@
return _parameters;
}
- final bool isStatic;
- final bool isAbstract;
-
bool get isRegularMethod => !isGetter && !isSetter && !isConstructor;
- TypeMirror get isOperator {
- throw new UnimplementedError(
- 'MethodMirror.isOperator is not implemented');
- }
-
- final bool isGetter;
- final bool isSetter;
- final bool isConstructor;
-
Symbol _constructorName = null;
Symbol get constructorName {
if (_constructorName == null) {
@@ -1004,11 +1016,6 @@
return _constructorName;
}
- final bool isConstConstructor;
- final bool isGenerativeConstructor;
- final bool isRedirectingConstructor;
- final bool isFactoryConstructor;
-
String toString() => "MethodMirror on '${_n(simpleName)}'";
static dynamic _MethodMirror_owner(reflectee)
diff --git a/runtime/lib/simd128.cc b/runtime/lib/simd128.cc
index 9b79412..1d4d551 100644
--- a/runtime/lib/simd128.cc
+++ b/runtime/lib/simd128.cc
@@ -229,13 +229,36 @@
}
+DEFINE_NATIVE_ENTRY(Float32x4_getSignMask, 1) {
+ GET_NON_NULL_NATIVE_ARGUMENT(Float32x4, self, arguments->NativeArgAt(0));
+ uint32_t mx = (bit_cast<uint32_t>(self.x()) & 0x80000000) >> 31;
+ uint32_t my = (bit_cast<uint32_t>(self.y()) & 0x80000000) >> 31;
+ uint32_t mz = (bit_cast<uint32_t>(self.z()) & 0x80000000) >> 31;
+ uint32_t mw = (bit_cast<uint32_t>(self.w()) & 0x80000000) >> 31;
+ uint32_t value = mx | (my << 1) | (mz << 2) | (mw << 3);
+ return Integer::New(value);
+}
+
+
+DEFINE_NATIVE_ENTRY(Uint32x4_getSignMask, 1) {
+ GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, self, arguments->NativeArgAt(0));
+ uint32_t mx = (self.x() & 0x80000000) >> 31;
+ uint32_t my = (self.y() & 0x80000000) >> 31;
+ uint32_t mz = (self.z() & 0x80000000) >> 31;
+ uint32_t mw = (self.w() & 0x80000000) >> 31;
+ uint32_t value = mx | (my << 1) | (mz << 2) | (mw << 3);
+ return Integer::New(value);
+}
+
+
DEFINE_NATIVE_ENTRY(Float32x4_shuffle, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Float32x4, self, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Integer, mask, arguments->NativeArgAt(1));
int64_t m = mask.AsInt64Value();
if (m < 0 || m > 255) {
const String& error = String::Handle(
- String::NewFormatted("mask (%"Pd64") must be in the range [0..256)", m));
+ String::NewFormatted("mask (%" Pd64 ") must be in the range [0..256)",
+ m));
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, error);
Exceptions::ThrowByType(Exceptions::kRange, args);
@@ -471,6 +494,28 @@
}
+DEFINE_NATIVE_ENTRY(Uint32x4_add, 2) {
+ GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, self, arguments->NativeArgAt(0));
+ GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, other, arguments->NativeArgAt(1));
+ uint32_t _x = self.x() + other.x();
+ uint32_t _y = self.y() + other.y();
+ uint32_t _z = self.z() + other.z();
+ uint32_t _w = self.w() + other.w();
+ return Uint32x4::New(_x, _y, _z, _w);
+}
+
+
+DEFINE_NATIVE_ENTRY(Uint32x4_sub, 2) {
+ GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, self, arguments->NativeArgAt(0));
+ GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, other, arguments->NativeArgAt(1));
+ uint32_t _x = self.x() - other.x();
+ uint32_t _y = self.y() - other.y();
+ uint32_t _z = self.z() - other.z();
+ uint32_t _w = self.w() - other.w();
+ return Uint32x4::New(_x, _y, _z, _w);
+}
+
+
DEFINE_NATIVE_ENTRY(Uint32x4_getX, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, self, arguments->NativeArgAt(0));
uint32_t value = self.x();
diff --git a/runtime/lib/typed_data.cc b/runtime/lib/typed_data.cc
index 23f0b83..bd7a266 100644
--- a/runtime/lib/typed_data.cc
+++ b/runtime/lib/typed_data.cc
@@ -30,7 +30,7 @@
intptr_t element_size_in_bytes) {
if (!Utils::RangeCheck(offset_in_bytes, num_bytes, length_in_bytes)) {
const String& error = String::Handle(String::NewFormatted(
- "index (%"Pd") must be in the range [0..%"Pd")",
+ "index (%" Pd ") must be in the range [0..%" Pd ")",
(offset_in_bytes / element_size_in_bytes),
(length_in_bytes / element_size_in_bytes)));
const Array& args = Array::Handle(Array::New(1));
@@ -44,7 +44,7 @@
static void LengthCheck(intptr_t len, intptr_t max) {
if (len < 0 || len > max) {
const String& error = String::Handle(String::NewFormatted(
- "Length (%"Pd") of object must be in range [0..%"Pd"]",
+ "Length (%" Pd ") of object must be in range [0..%" Pd "]",
len, max));
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, error);
@@ -114,7 +114,7 @@
if (length.Value() < 0) {
const String& error = String::Handle(String::NewFormatted(
- "length (%"Pd") must be non-negative", length.Value()));
+ "length (%" Pd ") must be non-negative", length.Value()));
const Array& args = Array::Handle(Array::New(1));
args.SetAt(0, error);
Exceptions::ThrowByType(Exceptions::kArgument, args);
diff --git a/runtime/lib/typed_data.dart b/runtime/lib/typed_data.dart
index 4e76c27..e60c145 100644
--- a/runtime/lib/typed_data.dart
+++ b/runtime/lib/typed_data.dart
@@ -2022,6 +2022,7 @@
double get y native "Float32x4_getY";
double get z native "Float32x4_getZ";
double get w native "Float32x4_getW";
+ int get signMask native "Float32x4_getSignMask";
Float32x4 shuffle(int mask) native "Float32x4_shuffle";
@@ -2081,10 +2082,19 @@
return _xor(other);
}
Uint32x4 _xor(Uint32x4 other) native "Uint32x4_xor";
+ Uint32x4 operator +(Uint32x4 other) {
+ return _add(other);
+ }
+ Uint32x4 _add(Uint32x4 other) native "Uint32x4_add";
+ Uint32x4 operator -(Uint32x4 other) {
+ return _sub(other);
+ }
+ Uint32x4 _sub(Uint32x4 other) native "Uint32x4_sub";
int get x native "Uint32x4_getX";
int get y native "Uint32x4_getY";
int get z native "Uint32x4_getZ";
int get w native "Uint32x4_getW";
+ int get signMask native "Uint32x4_getSignMask";
Uint32x4 withX(int x) native "Uint32x4_setX";
Uint32x4 withY(int y) native "Uint32x4_setY";
Uint32x4 withZ(int z) native "Uint32x4_setZ";
diff --git a/runtime/platform/thread_win.cc b/runtime/platform/thread_win.cc
index 7b3ca56..e8be678 100644
--- a/runtime/platform/thread_win.cc
+++ b/runtime/platform/thread_win.cc
@@ -140,7 +140,7 @@
void Mutex::Unlock() {
BOOL result = ReleaseSemaphore(data_.semaphore_, 1, NULL);
if (result == 0) {
- FATAL1("Mutex unlock failed", GetLastError());
+ FATAL1("Mutex unlock failed %d", GetLastError());
}
}
@@ -244,7 +244,7 @@
// Signal event.
BOOL result = SetEvent(first->event_);
if (result == 0) {
- FATAL1("Monitor::Notify failed to signal event", GetLastError());
+ FATAL1("Monitor::Notify failed to signal event %d", GetLastError());
}
}
LeaveCriticalSection(&waiters_cs_);
@@ -261,7 +261,7 @@
while (current != NULL) {
BOOL result = SetEvent(current->event_);
if (result == 0) {
- FATAL1("Failed to set event for NotifyAll", GetLastError());
+ FATAL1("Failed to set event for NotifyAll %d", GetLastError());
}
current = current->next_;
}
@@ -315,14 +315,14 @@
// Wait forever for a Notify or a NotifyAll event.
result = WaitForSingleObject(wait_data->event_, INFINITE);
if (result == WAIT_FAILED) {
- FATAL1("Monitor::Wait failed", GetLastError());
+ FATAL1("Monitor::Wait failed %d", GetLastError());
}
} else {
// Wait for the given period of time for a Notify or a NotifyAll
// event.
result = WaitForSingleObject(wait_data->event_, millis);
if (result == WAIT_FAILED) {
- FATAL1("Monitor::Wait with timeout failed", GetLastError());
+ FATAL1("Monitor::Wait with timeout failed %d", GetLastError());
}
if (result == WAIT_TIMEOUT) {
// No longer waiting. Remove from the list of waiters.
diff --git a/runtime/vm/allocation.cc b/runtime/vm/allocation.cc
index 0e97ed0..1c88436 100644
--- a/runtime/vm/allocation.cc
+++ b/runtime/vm/allocation.cc
@@ -19,7 +19,7 @@
ASSERT(isolate != NULL);
ASSERT(isolate->current_zone() != NULL);
if (size > static_cast<uword>(kIntptrMax)) {
- FATAL1("ZoneAllocated object has unexpectedly large size %"Pu"", size);
+ FATAL1("ZoneAllocated object has unexpectedly large size %" Pu "", size);
}
return reinterpret_cast<void*>(isolate->current_zone()->AllocUnsafe(size));
}
diff --git a/runtime/vm/assembler_ia32.cc b/runtime/vm/assembler_ia32.cc
index c04347c..a1dfc13 100644
--- a/runtime/vm/assembler_ia32.cc
+++ b/runtime/vm/assembler_ia32.cc
@@ -571,6 +571,24 @@
}
+void Assembler::addpl(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xFE);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void Assembler::subpl(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xFA);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
void Assembler::addps(XmmRegister dst, XmmRegister src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0x0F);
diff --git a/runtime/vm/assembler_ia32.h b/runtime/vm/assembler_ia32.h
index 83b1c49..d0972ea 100644
--- a/runtime/vm/assembler_ia32.h
+++ b/runtime/vm/assembler_ia32.h
@@ -411,7 +411,8 @@
void divsd(XmmRegister dst, XmmRegister src);
void divsd(XmmRegister dst, const Address& src);
-
+ void addpl(XmmRegister dst, XmmRegister src);
+ void subpl(XmmRegister dst, XmmRegister src);
void addps(XmmRegister dst, XmmRegister src);
void subps(XmmRegister dst, XmmRegister src);
void divps(XmmRegister dst, XmmRegister src);
diff --git a/runtime/vm/assembler_ia32_test.cc b/runtime/vm/assembler_ia32_test.cc
index 3a7d4a5..2f3087f 100644
--- a/runtime/vm/assembler_ia32_test.cc
+++ b/runtime/vm/assembler_ia32_test.cc
@@ -874,6 +874,31 @@
}
+ASSEMBLER_TEST_GENERATE(PackedIntOperations, assembler) {
+ __ movl(EAX, Immediate(0x2));
+ __ movd(XMM0, EAX);
+ __ shufps(XMM0, XMM0, Immediate(0x0));
+ __ movl(EAX, Immediate(0x1));
+ __ movd(XMM1, EAX);
+ __ shufps(XMM1, XMM1, Immediate(0x0));
+ __ addpl(XMM0, XMM1); // 0x3
+ __ addpl(XMM0, XMM0); // 0x6
+ __ subpl(XMM0, XMM1); // 0x5
+ // Copy the low lane at ESP.
+ __ pushl(EAX);
+ __ movss(Address(ESP, 0), XMM0);
+ __ popl(EAX);
+ __ ret();
+}
+
+
+ASSEMBLER_TEST_RUN(PackedIntOperations, test) {
+ typedef uint32_t (*PackedIntOperationsCode)();
+ uint32_t res = reinterpret_cast<PackedIntOperationsCode>(test->entry())();
+ EXPECT_EQ(static_cast<uword>(0x5), res);
+}
+
+
ASSEMBLER_TEST_GENERATE(PackedFPOperations2, assembler) {
__ movl(EAX, Immediate(bit_cast<int32_t, float>(4.0f)));
__ movd(XMM0, EAX);
diff --git a/runtime/vm/assembler_x64.cc b/runtime/vm/assembler_x64.cc
index 488cd55..38fad31 100644
--- a/runtime/vm/assembler_x64.cc
+++ b/runtime/vm/assembler_x64.cc
@@ -641,6 +641,27 @@
EmitXmmRegisterOperand(dst & 7, src);
}
+
+void Assembler::addpl(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitREX_RB(dst, src);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xFE);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
+void Assembler::subpl(XmmRegister dst, XmmRegister src) {
+ AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+ EmitREX_RB(dst, src);
+ EmitUint8(0x66);
+ EmitUint8(0x0F);
+ EmitUint8(0xFA);
+ EmitXmmRegisterOperand(dst, src);
+}
+
+
void Assembler::addps(XmmRegister dst, XmmRegister src) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitREX_RB(dst, src);
diff --git a/runtime/vm/assembler_x64.h b/runtime/vm/assembler_x64.h
index 17324c0..fd0d70b 100644
--- a/runtime/vm/assembler_x64.h
+++ b/runtime/vm/assembler_x64.h
@@ -415,6 +415,8 @@
void mulsd(XmmRegister dst, XmmRegister src);
void divsd(XmmRegister dst, XmmRegister src);
+ void addpl(XmmRegister dst, XmmRegister src);
+ void subpl(XmmRegister dst, XmmRegiste