diff --git a/modules/angular2/src/compiler/html_lexer.ts b/modules/angular2/src/compiler/html_lexer.ts
index 76f48683d9..bedab75ca8 100644
--- a/modules/angular2/src/compiler/html_lexer.ts
+++ b/modules/angular2/src/compiler/html_lexer.ts
@@ -125,8 +125,7 @@ class _HtmlTokenizer {
private currentTokenStart: ParseLocation;
private currentTokenType: HtmlTokenType;
- private inExpansionCase: boolean = false;
- private inExpansionForm: boolean = false;
+ private expansionCaseStack = [];
tokens: HtmlToken[] = [];
errors: HtmlTokenError[] = [];
@@ -169,10 +168,12 @@ class _HtmlTokenizer {
} else if (this.peek === $EQ && this.tokenizeExpansionForms) {
this._consumeExpansionCaseStart();
- } else if (this.peek === $RBRACE && this.inExpansionCase && this.tokenizeExpansionForms) {
+ } else if (this.peek === $RBRACE && this.isInExpansionCase() &&
+ this.tokenizeExpansionForms) {
this._consumeExpansionCaseEnd();
- } else if (this.peek === $RBRACE && !this.inExpansionCase && this.tokenizeExpansionForms) {
+ } else if (this.peek === $RBRACE && this.isInExpansionForm() &&
+ this.tokenizeExpansionForms) {
this._consumeExpansionFormEnd();
} else {
@@ -551,7 +552,7 @@ class _HtmlTokenizer {
this._requireCharCode($COMMA);
this._attemptCharCodeUntilFn(isNotWhitespace);
- this.inExpansionForm = true;
+ this.expansionCaseStack.push(HtmlTokenType.EXPANSION_FORM_START);
}
private _consumeExpansionCaseStart() {
@@ -567,7 +568,7 @@ class _HtmlTokenizer {
this._endToken([], this._getLocation());
this._attemptCharCodeUntilFn(isNotWhitespace);
- this.inExpansionCase = true;
+ this.expansionCaseStack.push(HtmlTokenType.EXPANSION_CASE_EXP_START);
}
private _consumeExpansionCaseEnd() {
@@ -576,7 +577,7 @@ class _HtmlTokenizer {
this._endToken([], this._getLocation());
this._attemptCharCodeUntilFn(isNotWhitespace);
- this.inExpansionCase = false;
+ this.expansionCaseStack.pop();
}
private _consumeExpansionFormEnd() {
@@ -584,7 +585,7 @@ class _HtmlTokenizer {
this._requireCharCode($RBRACE);
this._endToken([]);
- this.inExpansionForm = false;
+ this.expansionCaseStack.pop();
}
private _consumeText() {
@@ -622,7 +623,9 @@ class _HtmlTokenizer {
if (this.peek === $LT || this.peek === $EOF) return true;
if (this.tokenizeExpansionForms) {
if (isSpecialFormStart(this.peek, this.nextPeek)) return true;
- if (this.peek === $RBRACE && !interpolation && this.inExpansionForm) return true;
+ if (this.peek === $RBRACE && !interpolation &&
+ (this.isInExpansionCase() || this.isInExpansionForm()))
+ return true;
}
return false;
}
@@ -648,6 +651,18 @@ class _HtmlTokenizer {
this.tokens = ListWrapper.slice(this.tokens, 0, nbTokens);
}
}
+
+ private isInExpansionCase(): boolean {
+ return this.expansionCaseStack.length > 0 &&
+ this.expansionCaseStack[this.expansionCaseStack.length - 1] ===
+ HtmlTokenType.EXPANSION_CASE_EXP_START;
+ }
+
+ private isInExpansionForm(): boolean {
+ return this.expansionCaseStack.length > 0 &&
+ this.expansionCaseStack[this.expansionCaseStack.length - 1] ===
+ HtmlTokenType.EXPANSION_FORM_START;
+ }
}
function isNotWhitespace(code: number): boolean {
diff --git a/modules/angular2/src/compiler/html_parser.ts b/modules/angular2/src/compiler/html_parser.ts
index 0bd124cbe4..14437d0d9a 100644
--- a/modules/angular2/src/compiler/html_parser.ts
+++ b/modules/angular2/src/compiler/html_parser.ts
@@ -124,40 +124,9 @@ class TreeBuilder {
// 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));
+ let expCase = this._parseExpansionCase();
+ if (isBlank(expCase)) return; // error
+ cases.push(expCase);
}
// read the final }
@@ -173,6 +142,80 @@ class TreeBuilder {
mainSourceSpan, switchValue.sourceSpan));
}
+ private _parseExpansionCase(): HtmlExpansionCaseAst {
+ let value = this._advance();
+
+ // read {
+ if (this.peek.type !== HtmlTokenType.EXPANSION_CASE_EXP_START) {
+ this.errors.push(HtmlTreeError.create(null, this.peek.sourceSpan,
+ `Invalid expansion form. Missing '{'.,`));
+ return null;
+ }
+
+ // read until }
+ let start = this._advance();
+
+ let exp = this._collectExpansionExpTokens(start);
+ if (isBlank(exp)) return null;
+
+ 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 null;
+ }
+
+ let sourceSpan = new ParseSourceSpan(value.sourceSpan.start, end.sourceSpan.end);
+ let expSourceSpan = new ParseSourceSpan(start.sourceSpan.start, end.sourceSpan.end);
+ return new HtmlExpansionCaseAst(value.parts[0], parsedExp.rootNodes, sourceSpan,
+ value.sourceSpan, expSourceSpan);
+ }
+
+ private _collectExpansionExpTokens(start: HtmlToken): HtmlToken[] {
+ let exp = [];
+ let expansionFormStack = [HtmlTokenType.EXPANSION_CASE_EXP_START];
+
+ while (true) {
+ if (this.peek.type === HtmlTokenType.EXPANSION_FORM_START ||
+ this.peek.type === HtmlTokenType.EXPANSION_CASE_EXP_START) {
+ expansionFormStack.push(this.peek.type);
+ }
+
+ if (this.peek.type === HtmlTokenType.EXPANSION_CASE_EXP_END) {
+ if (lastOnStack(expansionFormStack, HtmlTokenType.EXPANSION_CASE_EXP_START)) {
+ expansionFormStack.pop();
+ if (expansionFormStack.length == 0) return exp;
+
+ } else {
+ this.errors.push(
+ HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
+ return null;
+ }
+ }
+
+ if (this.peek.type === HtmlTokenType.EXPANSION_FORM_END) {
+ if (lastOnStack(expansionFormStack, HtmlTokenType.EXPANSION_FORM_START)) {
+ expansionFormStack.pop();
+ } else {
+ this.errors.push(
+ HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
+ return null;
+ }
+ }
+
+ if (this.peek.type === HtmlTokenType.EOF) {
+ this.errors.push(
+ HtmlTreeError.create(null, start.sourceSpan, `Invalid expansion form. Missing '}'.`));
+ return null;
+ }
+
+ exp.push(this._advance());
+ }
+ }
+
private _consumeText(token: HtmlToken) {
let text = token.parts[0];
if (text.length > 0 && text[0] == '\n') {
@@ -321,3 +364,7 @@ function getElementFullName(prefix: string, localName: string,
return mergeNsAndName(prefix, localName);
}
+
+function lastOnStack(stack: any[], element: any): boolean {
+ return stack.length > 0 && stack[stack.length - 1] === element;
+}
\ No newline at end of file
diff --git a/modules/angular2/src/compiler/template_parser.ts b/modules/angular2/src/compiler/template_parser.ts
index 1c332773f3..0595beedd0 100644
--- a/modules/angular2/src/compiler/template_parser.ts
+++ b/modules/angular2/src/compiler/template_parser.ts
@@ -254,6 +254,10 @@ class TemplateParseVisitor implements HtmlAstVisitor {
}
}
+ visitExpansion(ast: HtmlExpansionAst, context: any): any { return null; }
+
+ visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return null; }
+
visitText(ast: HtmlTextAst, parent: ElementContext): any {
var ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
var expr = this._parseInterpolation(ast.value, ast.sourceSpan);
@@ -270,7 +274,7 @@ class TemplateParseVisitor implements HtmlAstVisitor {
visitComment(ast: HtmlCommentAst, context: any): any { return null; }
- visitElement(element: HtmlElementAst, component: ElementContext): any {
+ visitElement(element: HtmlElementAst, parent: ElementContext): any {
var nodeName = element.name;
var preparsedElement = preparseElement(element);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
@@ -773,7 +777,6 @@ class NonBindableVisitor implements HtmlAstVisitor {
return new TextAst(ast.value, ngContentIndex, ast.sourceSpan);
}
visitExpansion(ast: HtmlExpansionAst, context: any): any { return ast; }
-
visitExpansionCase(ast: HtmlExpansionCaseAst, context: any): any { return ast; }
}
diff --git a/modules/angular2/src/i18n/expander.ts b/modules/angular2/src/i18n/expander.ts
index 6a27dd6373..6fc8127a9f 100644
--- a/modules/angular2/src/i18n/expander.ts
+++ b/modules/angular2/src/i18n/expander.ts
@@ -12,6 +12,7 @@ import {
import {BaseException} from 'angular2/src/facade/exceptions';
+
/**
* Expands special forms into elements.
*
@@ -35,7 +36,18 @@ import {BaseException} from 'angular2/src/facade/exceptions';
*
* ```
*/
-export class Expander implements HtmlAstVisitor {
+export function expandNodes(nodes: HtmlAst[]): ExpansionResult {
+ let e = new _Expander();
+ let n = htmlVisitAll(e, nodes);
+ return new ExpansionResult(n, e.expanded);
+}
+
+export class ExpansionResult {
+ constructor(public nodes: HtmlAst[], public expanded: boolean) {}
+}
+
+class _Expander implements HtmlAstVisitor {
+ expanded: boolean = false;
constructor() {}
visitElement(ast: HtmlElementAst, context: any): any {
@@ -50,6 +62,7 @@ export class Expander implements HtmlAstVisitor {
visitComment(ast: HtmlCommentAst, context: any): any { return ast; }
visitExpansion(ast: HtmlExpansionAst, context: any): any {
+ this.expanded = true;
return ast.type == "plural" ? _expandPluralForm(ast) : _expandDefaultForm(ast);
}
@@ -59,36 +72,44 @@ export class Expander implements HtmlAstVisitor {
}
function _expandPluralForm(ast: HtmlExpansionAst): HtmlElementAst {
- let children = ast.cases.map(
- c => new HtmlElementAst(
- `template`,
- [
- new HtmlAttrAst("[ngPluralCase]", c.value, c.valueSourceSpan),
- ],
- [
- new HtmlElementAst(
- `li`, [new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)],
- c.expression, c.sourceSpan, c.sourceSpan, c.sourceSpan)
- ],
- c.sourceSpan, c.sourceSpan, c.sourceSpan));
+ let children = ast.cases.map(c => {
+ let expansionResult = expandNodes(c.expression);
+ let i18nAttrs = expansionResult.expanded ?
+ [] :
+ [new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)];
+
+ return new HtmlElementAst(`template`,
+ [
+ new HtmlAttrAst("ngPluralCase", c.value, c.valueSourceSpan),
+ ],
+ [
+ new HtmlElementAst(`li`, i18nAttrs, expansionResult.nodes,
+ c.sourceSpan, c.sourceSpan, c.sourceSpan)
+ ],
+ c.sourceSpan, c.sourceSpan, c.sourceSpan);
+ });
let switchAttr = new HtmlAttrAst("[ngPlural]", ast.switchValue, ast.switchValueSourceSpan);
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
ast.sourceSpan);
}
function _expandDefaultForm(ast: HtmlExpansionAst): HtmlElementAst {
- let children = ast.cases.map(
- c => new HtmlElementAst(
- `template`,
- [
- new HtmlAttrAst("[ngSwitchWhen]", c.value, c.valueSourceSpan),
- ],
- [
- new HtmlElementAst(
- `li`, [new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)],
- c.expression, c.sourceSpan, c.sourceSpan, c.sourceSpan)
- ],
- c.sourceSpan, c.sourceSpan, c.sourceSpan));
+ let children = ast.cases.map(c => {
+ let expansionResult = expandNodes(c.expression);
+ let i18nAttrs = expansionResult.expanded ?
+ [] :
+ [new HtmlAttrAst("i18n", `${ast.type}_${c.value}`, c.valueSourceSpan)];
+
+ return new HtmlElementAst(`template`,
+ [
+ new HtmlAttrAst("ngSwitchWhen", c.value, c.valueSourceSpan),
+ ],
+ [
+ new HtmlElementAst(`li`, i18nAttrs, expansionResult.nodes,
+ c.sourceSpan, c.sourceSpan, c.sourceSpan)
+ ],
+ c.sourceSpan, c.sourceSpan, c.sourceSpan);
+ });
let switchAttr = new HtmlAttrAst("[ngSwitch]", ast.switchValue, ast.switchValueSourceSpan);
return new HtmlElementAst("ul", [switchAttr], children, ast.sourceSpan, ast.sourceSpan,
ast.sourceSpan);
diff --git a/modules/angular2/src/i18n/i18n_html_parser.ts b/modules/angular2/src/i18n/i18n_html_parser.ts
index 23b09735a0..bcf22061a4 100644
--- a/modules/angular2/src/i18n/i18n_html_parser.ts
+++ b/modules/angular2/src/i18n/i18n_html_parser.ts
@@ -16,7 +16,7 @@ import {RegExpWrapper, NumberWrapper, isPresent} from 'angular2/src/facade/lang'
import {BaseException} from 'angular2/src/facade/exceptions';
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
import {Message, id} from './message';
-import {Expander} from './expander';
+import {expandNodes} from './expander';
import {
messageFromAttribute,
I18nError,
@@ -126,21 +126,16 @@ export class I18nHtmlParser implements HtmlParser {
parseExpansionForms: boolean = false): HtmlParseTreeResult {
this.errors = [];
- let res = this._htmlParser.parse(sourceContent, sourceUrl, parseExpansionForms);
+ let res = this._htmlParser.parse(sourceContent, sourceUrl, true);
if (res.errors.length > 0) {
return res;
} else {
- let nodes = this._recurse(this._expandNodes(res.rootNodes));
+ let nodes = this._recurse(expandNodes(res.rootNodes).nodes);
return this.errors.length > 0 ? new HtmlParseTreeResult([], this.errors) :
new HtmlParseTreeResult(nodes, []);
}
}
- private _expandNodes(nodes: HtmlAst[]): HtmlAst[] {
- let e = new Expander();
- return htmlVisitAll(e, nodes);
- }
-
private _processI18nPart(p: Part): HtmlAst[] {
try {
return p.hasI18n ? this._mergeI18Part(p) : this._recurseIntoI18nPart(p);
@@ -155,9 +150,11 @@ export class I18nHtmlParser implements HtmlParser {
}
private _mergeI18Part(p: Part): HtmlAst[] {
- let messageId = id(p.createMessage(this._parser));
+ let message = p.createMessage(this._parser);
+ let messageId = id(message);
if (!StringMapWrapper.contains(this._messages, messageId)) {
- throw new I18nError(p.sourceSpan, `Cannot find message for id '${messageId}'`);
+ throw new I18nError(
+ p.sourceSpan, `Cannot find message for id '${messageId}', content '${message.content}'.`);
}
let parsedMessage = this._messages[messageId];
@@ -294,14 +291,17 @@ export class I18nHtmlParser implements HtmlParser {
}
let i18n = i18ns[0];
- let messageId = id(messageFromAttribute(this._parser, el, i18n));
+ let message = messageFromAttribute(this._parser, el, i18n);
+ let messageId = id(message);
if (StringMapWrapper.contains(this._messages, messageId)) {
let updatedMessage = this._replaceInterpolationInAttr(attr, this._messages[messageId]);
res.push(new HtmlAttrAst(attr.name, updatedMessage, attr.sourceSpan));
} else {
- throw new I18nError(attr.sourceSpan, `Cannot find message for id '${messageId}'`);
+ throw new I18nError(
+ attr.sourceSpan,
+ `Cannot find message for id '${messageId}', content '${message.content}'.`);
}
});
return res;
diff --git a/modules/angular2/src/i18n/message_extractor.ts b/modules/angular2/src/i18n/message_extractor.ts
index 7da4ee0f79..5630d44af7 100644
--- a/modules/angular2/src/i18n/message_extractor.ts
+++ b/modules/angular2/src/i18n/message_extractor.ts
@@ -13,7 +13,7 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {Parser} from 'angular2/src/compiler/expression_parser/parser';
import {Message, id} from './message';
-import {Expander} from './expander';
+import {expandNodes} from './expander';
import {
I18nError,
Part,
@@ -126,16 +126,11 @@ export class MessageExtractor {
if (res.errors.length > 0) {
return new ExtractionResult([], res.errors);
} else {
- this._recurse(this._expandNodes(res.rootNodes));
+ this._recurse(expandNodes(res.rootNodes).nodes);
return new ExtractionResult(this.messages, this.errors);
}
}
- private _expandNodes(nodes: HtmlAst[]): HtmlAst[] {
- let e = new Expander();
- return htmlVisitAll(e, nodes);
- }
-
private _extractMessagesFromPart(p: Part): void {
if (p.hasI18n) {
this.messages.push(p.createMessage(this._parser));
diff --git a/modules/angular2/test/compiler/html_lexer_spec.ts b/modules/angular2/test/compiler/html_lexer_spec.ts
index aef9b09658..63f57d231d 100644
--- a/modules/angular2/test/compiler/html_lexer_spec.ts
+++ b/modules/angular2/test/compiler/html_lexer_spec.ts
@@ -646,6 +646,31 @@ export function main() {
[HtmlTokenType.EOF]
]);
});
+
+ it("should parse nested expansion forms", () => {
+ expect(tokenizeAndHumanizeParts(`{one.two, three, =4 { {xx, yy, =x {one}} }}`, true))
+ .toEqual([
+ [HtmlTokenType.EXPANSION_FORM_START],
+ [HtmlTokenType.RAW_TEXT, 'one.two'],
+ [HtmlTokenType.RAW_TEXT, 'three'],
+ [HtmlTokenType.EXPANSION_CASE_VALUE, '4'],
+ [HtmlTokenType.EXPANSION_CASE_EXP_START],
+
+ [HtmlTokenType.EXPANSION_FORM_START],
+ [HtmlTokenType.RAW_TEXT, 'xx'],
+ [HtmlTokenType.RAW_TEXT, 'yy'],
+ [HtmlTokenType.EXPANSION_CASE_VALUE, 'x'],
+ [HtmlTokenType.EXPANSION_CASE_EXP_START],
+ [HtmlTokenType.TEXT, 'one'],
+ [HtmlTokenType.EXPANSION_CASE_EXP_END],
+ [HtmlTokenType.EXPANSION_FORM_END],
+ [HtmlTokenType.TEXT, ' '],
+
+ [HtmlTokenType.EXPANSION_CASE_EXP_END],
+ [HtmlTokenType.EXPANSION_FORM_END],
+ [HtmlTokenType.EOF]
+ ]);
+ });
});
describe('errors', () => {
diff --git a/modules/angular2/test/compiler/html_parser_spec.ts b/modules/angular2/test/compiler/html_parser_spec.ts
index 403deb3d4f..945ab983ae 100644
--- a/modules/angular2/test/compiler/html_parser_spec.ts
+++ b/modules/angular2/test/compiler/html_parser_spec.ts
@@ -271,6 +271,27 @@ export function main() {
.toEqual([[HtmlTextAst, 'One {{message}}', 0]]);
});
+ it("should parse out nested expansion forms", () => {
+ let parsed = parser.parse(`{messages.length, plural, =0 { {p.gender, gender, =m {m}} }}`,
+ 'TestComp', true);
+
+
+ expect(humanizeDom(parsed))
+ .toEqual([
+ [HtmlExpansionAst, 'messages.length', 'plural'],
+ [HtmlExpansionCaseAst, '0'],
+ ]);
+
+ let firstCase = (parsed.rootNodes[0]).cases[0];
+
+ expect(humanizeDom(new HtmlParseTreeResult(firstCase.expression, [])))
+ .toEqual([
+ [HtmlExpansionAst, 'p.gender', 'gender'],
+ [HtmlExpansionCaseAst, 'm'],
+ [HtmlTextAst, ' ', 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))
diff --git a/modules/angular2/test/i18n/i18n_html_parser_spec.ts b/modules/angular2/test/i18n/i18n_html_parser_spec.ts
index 65ebe42450..0b4f2e93c7 100644
--- a/modules/angular2/test/i18n/i18n_html_parser_spec.ts
+++ b/modules/angular2/test/i18n/i18n_html_parser_spec.ts
@@ -188,7 +188,7 @@ export function main() {
expect(res[1].sourceSpan.start.offset).toEqual(10);
});
- it("should handle the plural special form", () => {
+ it("should handle the plural expansion form", () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('zerobold', "plural_0", null))] =
'ZEROBOLD';
@@ -200,7 +200,7 @@ export function main() {
[HtmlElementAst, 'ul', 0],
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
[HtmlElementAst, 'template', 1],
- [HtmlAttrAst, '[ngPluralCase]', '0'],
+ [HtmlAttrAst, 'ngPluralCase', '0'],
[HtmlElementAst, 'li', 2],
[HtmlTextAst, 'ZERO', 3],
[HtmlElementAst, 'b', 3],
@@ -208,6 +208,31 @@ export function main() {
]);
});
+ it("should handle nested expansion forms", () => {
+ let translations: {[key: string]: string} = {};
+ translations[id(new Message('m', "gender_m", null))] = 'M';
+
+ let res = parse(`{messages.length, plural, =0 { {p.gender, gender, =m {m}} }}`, translations);
+
+ expect(humanizeDom(res))
+ .toEqual([
+ [HtmlElementAst, 'ul', 0],
+ [HtmlAttrAst, '[ngPlural]', 'messages.length'],
+ [HtmlElementAst, 'template', 1],
+ [HtmlAttrAst, 'ngPluralCase', '0'],
+ [HtmlElementAst, 'li', 2],
+
+ [HtmlElementAst, 'ul', 3],
+ [HtmlAttrAst, '[ngSwitch]', 'p.gender'],
+ [HtmlElementAst, 'template', 4],
+ [HtmlAttrAst, 'ngSwitchWhen', 'm'],
+ [HtmlElementAst, 'li', 5],
+ [HtmlTextAst, 'M', 6],
+
+ [HtmlTextAst, ' ', 3]
+ ]);
+ });
+
it("should correctly set source code positions", () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('bold', "plural_0", null))] =
@@ -258,7 +283,7 @@ export function main() {
[HtmlElementAst, 'ul', 0],
[HtmlAttrAst, '[ngSwitch]', 'person.gender'],
[HtmlElementAst, 'template', 1],
- [HtmlAttrAst, '[ngSwitchWhen]', 'male'],
+ [HtmlAttrAst, 'ngSwitchWhen', 'male'],
[HtmlElementAst, 'li', 2],
[HtmlTextAst, 'M', 3],
]);
@@ -273,13 +298,13 @@ export function main() {
it("should error when no matching message (attr)", () => {
let mid = id(new Message("some message", null, null));
expect(humanizeErrors(parse("", {}).errors))
- .toEqual([`Cannot find message for id '${mid}'`]);
+ .toEqual([`Cannot find message for id '${mid}', content 'some message'.`]);
});
it("should error when no matching message (text)", () => {
let mid = id(new Message("some message", null, null));
expect(humanizeErrors(parse("some message
", {}).errors))
- .toEqual([`Cannot find message for id '${mid}'`]);
+ .toEqual([`Cannot find message for id '${mid}', content 'some message'.`]);
});
it("should error when a non-placeholder element appears in translation", () => {