tokensAndErrors.errors)
.concat(treeAndErrors.errors));
@@ -68,6 +77,8 @@ class TreeBuilder {
this.peek.type === HtmlTokenType.ESCAPABLE_RAW_TEXT) {
this._closeVoidElement();
this._consumeText(this._advance());
+ } else if (this.peek.type === HtmlTokenType.EXPANSION_FORM_START) {
+ this._consumeExpansion(this._advance());
} else {
// Skip all other tokens...
this._advance();
@@ -105,6 +116,63 @@ class TreeBuilder {
this._addToParent(new HtmlCommentAst(value, token.sourceSpan));
}
+ private _consumeExpansion(token: HtmlToken) {
+ let switchValue = this._advance();
+
+ let type = this._advance();
+ let cases = [];
+
+ // read =
+ while (this.peek.type === HtmlTokenType.EXPANSION_CASE_VALUE) {
+ let value = this._advance();
+
+ // read {
+ let exp = [];
+ if (this.peek.type !== HtmlTokenType.EXPANSION_CASE_EXP_START) {
+ this.errors.push(HtmlTreeError.create(null, this.peek.sourceSpan,
+ `Invalid expansion form. Missing '{'.,`));
+ return;
+ }
+
+ // read until }
+ let start = this._advance();
+ while (this.peek.type !== HtmlTokenType.EXPANSION_CASE_EXP_END) {
+ exp.push(this._advance());
+ if (this.peek.type === HtmlTokenType.EOF) {
+ this.errors.push(
+ HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
+ return;
+ }
+ }
+ let end = this._advance();
+ exp.push(new HtmlToken(HtmlTokenType.EOF, [], end.sourceSpan));
+
+ // parse everything in between { and }
+ let parsedExp = new TreeBuilder(exp).build();
+ if (parsedExp.errors.length > 0) {
+ this.errors = this.errors.concat(parsedExp.errors);
+ return;
+ }
+
+ let sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end);
+ let expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end);
+ cases.push(new HtmlExpansionCaseAst(value.parts[0], parsedExp.rootNodes, sourceSpan,
+ value.sourceSpan, expSourceSpan));
+ }
+
+ // read the final }
+ if (this.peek.type !== HtmlTokenType.EXPANSION_FORM_END) {
+ this.errors.push(
+ HtmlTreeError.create(null, this.peek.sourceSpan, `Invalid expansion form. Missing '}'.`));
+ return;
+ }
+ this._advance();
+
+ let mainSourceSpan = new ParseSourceSpan(token.sourceSpan.start, this.peek.sourceSpan.end);
+ this._addToParent(new HtmlExpansionAst(switchValue.parts[0], type.parts[0], cases,
+ mainSourceSpan, switchValue.sourceSpan));
+ }
+
private _consumeText(token: HtmlToken) {
let text = token.parts[0];
if (text.length > 0 && text[0] == '\n') {
diff --git a/modules/angular2/test/compiler/html_parser_spec.ts b/modules/angular2/test/compiler/html_parser_spec.ts
index 3af628f315..403deb3d4f 100644
--- a/modules/angular2/test/compiler/html_parser_spec.ts
+++ b/modules/angular2/test/compiler/html_parser_spec.ts
@@ -18,7 +18,9 @@ import {
HtmlAttrAst,
HtmlTextAst,
HtmlCommentAst,
- htmlVisitAll
+ htmlVisitAll,
+ HtmlExpansionAst,
+ HtmlExpansionCaseAst
} from 'angular2/src/compiler/html_ast';
import {ParseError, ParseLocation} from 'angular2/src/compiler/parse_util';
import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_ast_spec_utils';
@@ -227,7 +229,7 @@ export function main() {
.toEqual([[HtmlElementAst, 'template', 0], [HtmlAttrAst, 'k', 'v']]);
});
- it('should support mamespace', () => {
+ it('should support namespace', () => {
expect(humanizeDom(parser.parse('', 'TestComp')))
.toEqual([[HtmlElementAst, '@svg:use', 0], [HtmlAttrAst, '@xlink:href', 'Port']]);
});
@@ -240,6 +242,54 @@ export function main() {
});
});
+ describe("expansion forms", () => {
+ it("should parse out expansion forms", () => {
+ let parsed = parser.parse(`before{messages.length, plural, =0 {You have no messages} =1 {One {{message}}}}after
`,
+ 'TestComp', true);
+
+ expect(humanizeDom(parsed))
+ .toEqual([
+ [HtmlElementAst, 'div', 0],
+ [HtmlTextAst, 'before', 1],
+ [HtmlExpansionAst, 'messages.length', 'plural'],
+ [HtmlExpansionCaseAst, '0'],
+ [HtmlExpansionCaseAst, '1'],
+ [HtmlTextAst, 'after', 1]
+ ]);
+
+ let cases = (parsed.rootNodes[0]).children[1].cases;
+
+ expect(humanizeDom(new HtmlParseTreeResult(cases[0].expression, [])))
+ .toEqual([
+ [HtmlTextAst, 'You have ', 0],
+ [HtmlElementAst, 'b', 0],
+ [HtmlTextAst, 'no', 1],
+ [HtmlTextAst, ' messages', 0],
+ ]);
+
+ expect(humanizeDom(new HtmlParseTreeResult(cases[1].expression, [])))
+ .toEqual([[HtmlTextAst, 'One {{message}}', 0]]);
+ });
+
+ it("should error when expansion form is not closed", () => {
+ let p = parser.parse(`{messages.length, plural, =0 {one}`, 'TestComp', true);
+ expect(humanizeErrors(p.errors))
+ .toEqual([[null, "Invalid expansion form. Missing '}'.", '0:34']]);
+ });
+
+ it("should error when expansion case is not closed", () => {
+ let p = parser.parse(`{messages.length, plural, =0 {one`, 'TestComp', true);
+ expect(humanizeErrors(p.errors))
+ .toEqual([[null, "Invalid expansion form. Missing '}'.", '0:29']]);
+ });
+
+ it("should error when invalid html in the case", () => {
+ let p = parser.parse(`{messages.length, plural, =0 {}`, 'TestComp', true);
+ expect(humanizeErrors(p.errors))
+ .toEqual([['b', 'Only void and foreign elements can be self closed "b"', '0:30']]);
+ });
+ });
+
describe('source spans', () => {
it('should store the location', () => {
expect(humanizeDomSourceSpans(parser.parse(