feat(I18N): generate error on unknown cases

fixes #9094
This commit is contained in:
Victor Berchet
2016-06-09 14:53:03 -07:00
parent 43148d8233
commit 5267115481
8 changed files with 192 additions and 223 deletions

View File

@ -485,98 +485,65 @@ export function main() {
});
describe("expansion forms", () => {
it("should parse an expansion form", () => {
describe('expansion forms', () => {
it('should parse an expansion form', () => {
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four} =5 {five} foo {bar} }', 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.TEXT, 'four'],
[HtmlTokenType.EXPANSION_CASE_EXP_END],
[HtmlTokenType.EXPANSION_CASE_VALUE, '=5'],
[HtmlTokenType.EXPANSION_CASE_EXP_START],
[HtmlTokenType.TEXT, 'five'],
[HtmlTokenType.EXPANSION_CASE_EXP_END],
[HtmlTokenType.EXPANSION_CASE_VALUE, 'foo'],
[HtmlTokenType.EXPANSION_CASE_EXP_START],
[HtmlTokenType.TEXT, 'bar'],
[HtmlTokenType.EXPANSION_CASE_EXP_END],
[HtmlTokenType.EXPANSION_FORM_END],
[HtmlTokenType.EXPANSION_FORM_START], [HtmlTokenType.RAW_TEXT, 'one.two'],
[HtmlTokenType.RAW_TEXT, 'three'], [HtmlTokenType.EXPANSION_CASE_VALUE, '=4'],
[HtmlTokenType.EXPANSION_CASE_EXP_START], [HtmlTokenType.TEXT, 'four'],
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_CASE_VALUE, '=5'],
[HtmlTokenType.EXPANSION_CASE_EXP_START], [HtmlTokenType.TEXT, 'five'],
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_CASE_VALUE, 'foo'],
[HtmlTokenType.EXPANSION_CASE_EXP_START], [HtmlTokenType.TEXT, 'bar'],
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
[HtmlTokenType.EOF]
]);
});
it("should parse an expansion form with text elements surrounding it", () => {
expect(tokenizeAndHumanizeParts('before{one.two, three, =4 {four}}after', true))
.toEqual([
[HtmlTokenType.TEXT, "before"],
[HtmlTokenType.EXPANSION_FORM_START],
[HtmlTokenType.RAW_TEXT, 'one.two'],
[HtmlTokenType.RAW_TEXT, 'three'],
[HtmlTokenType.EXPANSION_CASE_VALUE, '=4'],
[HtmlTokenType.EXPANSION_CASE_EXP_START],
[HtmlTokenType.TEXT, 'four'],
[HtmlTokenType.EXPANSION_CASE_EXP_END],
[HtmlTokenType.EXPANSION_FORM_END],
[HtmlTokenType.TEXT, "after"],
[HtmlTokenType.EOF]
]);
it('should parse an expansion form with text elements surrounding it', () => {
expect(tokenizeAndHumanizeParts('before{one.two, three, =4 {four}}after', true)).toEqual([
[HtmlTokenType.TEXT, 'before'], [HtmlTokenType.EXPANSION_FORM_START],
[HtmlTokenType.RAW_TEXT, 'one.two'], [HtmlTokenType.RAW_TEXT, 'three'],
[HtmlTokenType.EXPANSION_CASE_VALUE, '=4'], [HtmlTokenType.EXPANSION_CASE_EXP_START],
[HtmlTokenType.TEXT, 'four'], [HtmlTokenType.EXPANSION_CASE_EXP_END],
[HtmlTokenType.EXPANSION_FORM_END], [HtmlTokenType.TEXT, 'after'], [HtmlTokenType.EOF]
]);
});
it("should parse an expansion forms with elements in it", () => {
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four <b>a</b>}}', 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.TEXT, 'four '],
[HtmlTokenType.TAG_OPEN_START, null, 'b'],
[HtmlTokenType.TAG_OPEN_END],
[HtmlTokenType.TEXT, 'a'],
[HtmlTokenType.TAG_CLOSE, null, 'b'],
[HtmlTokenType.EXPANSION_CASE_EXP_END],
[HtmlTokenType.EXPANSION_FORM_END],
[HtmlTokenType.EOF]
]);
it('should parse an expansion forms with elements in it', () => {
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four <b>a</b>}}', 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.TEXT, 'four '],
[HtmlTokenType.TAG_OPEN_START, null, 'b'], [HtmlTokenType.TAG_OPEN_END],
[HtmlTokenType.TEXT, 'a'], [HtmlTokenType.TAG_CLOSE, null, 'b'],
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
[HtmlTokenType.EOF]
]);
});
it("should parse an expansion forms with interpolation in it", () => {
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four {{a}}}}', 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.TEXT, 'four {{a}}'],
[HtmlTokenType.EXPANSION_CASE_EXP_END],
[HtmlTokenType.EXPANSION_FORM_END],
[HtmlTokenType.EOF]
]);
it('should parse an expansion forms with interpolation in it', () => {
expect(tokenizeAndHumanizeParts('{one.two, three, =4 {four {{a}}}}', 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.TEXT, 'four {{a}}'],
[HtmlTokenType.EXPANSION_CASE_EXP_END], [HtmlTokenType.EXPANSION_FORM_END],
[HtmlTokenType.EOF]
]);
});
it("should parse nested expansion forms", () => {
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_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.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],

View File

@ -1,15 +1,8 @@
import {HtmlAst, HtmlAstVisitor, HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst, htmlVisitAll} from '@angular/compiler/src/html_ast';
import {HtmlAttrAst, HtmlCommentAst, HtmlElementAst, HtmlExpansionAst, HtmlExpansionCaseAst, HtmlTextAst} from '@angular/compiler/src/html_ast';
import {HtmlTokenType} from '@angular/compiler/src/html_lexer';
import {HtmlParser, HtmlParseTreeResult, HtmlTreeError} from '@angular/compiler/src/html_parser';
import {
HtmlElementAst,
HtmlAttrAst,
HtmlTextAst,
HtmlCommentAst,
HtmlExpansionAst,
HtmlExpansionCaseAst
} from '@angular/compiler/src/html_ast';
import {HtmlParseTreeResult, HtmlParser, HtmlTreeError} from '@angular/compiler/src/html_parser';
import {ParseError} from '@angular/compiler/src/parse_util';
import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn} from './html_ast_spec_utils';
export function main() {
@ -238,15 +231,14 @@ export function main() {
`<div>before{messages.length, plural, =0 {You have <b>no</b> messages} =1 {One {{message}}}}after</div>`,
'TestComp', true);
expect(humanizeDom(parsed))
.toEqual([
[HtmlElementAst, 'div', 0],
[HtmlTextAst, 'before', 1],
[HtmlExpansionAst, 'messages.length', 'plural'],
[HtmlExpansionCaseAst, '=0'],
[HtmlExpansionCaseAst, '=1'],
[HtmlTextAst, 'after', 1],
]);
expect(humanizeDom(parsed)).toEqual([
[HtmlElementAst, 'div', 0],
[HtmlTextAst, 'before', 1],
[HtmlExpansionAst, 'messages.length', 'plural'],
[HtmlExpansionCaseAst, '=0'],
[HtmlExpansionCaseAst, '=1'],
[HtmlTextAst, 'after', 1],
]);
let cases = (<any>parsed.rootNodes[0]).children[1].cases;
expect(humanizeDom(new HtmlParseTreeResult(cases[0].expression, []))).toEqual([
@ -263,20 +255,18 @@ export function main() {
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'],
]);
expect(humanizeDom(parsed)).toEqual([
[HtmlExpansionAst, 'messages.length', 'plural'],
[HtmlExpansionCaseAst, '=0'],
]);
let firstCase = (<any>parsed.rootNodes[0]).cases[0];
expect(humanizeDom(new HtmlParseTreeResult(firstCase.expression, [])))
.toEqual([
[HtmlExpansionAst, 'p.gender', 'gender'],
[HtmlExpansionCaseAst, '=m'],
[HtmlTextAst, ' ', 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', () => {

View File

@ -21,8 +21,7 @@ export function main() {
let msgs = '';
StringMapWrapper.forEach(
messages, (v: any /** TODO #9100 */, k: any /** TODO #9100 */) => msgs +=
`<msg id="${k}">${v}</msg>`);
messages, (v: string, k: string) => msgs += `<msg id="${k}">${v}</msg>`);
let res = deserializeXmb(`<message-bundle>${msgs}</message-bundle>`, 'someUrl');
return new I18nHtmlParser(
@ -175,52 +174,45 @@ export function main() {
it('should handle the plural expansion form', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('zero<ph name="e1">bold</ph>', "plural_=0", null))] =
translations[id(new Message('zero<ph name="e1">bold</ph>', 'plural_=0', null))] =
'ZERO<ph name="e1">BOLD</ph>';
let res = parse(`{messages.length, plural,=0 {zero<b>bold</b>}}`, translations);
expect(humanizeDom(res))
.toEqual([
[HtmlElementAst, 'ul', 0],
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
[HtmlElementAst, 'template', 1],
[HtmlAttrAst, 'ngPluralCase', '=0'],
[HtmlElementAst, 'li', 2],
[HtmlTextAst, 'ZERO', 3],
[HtmlElementAst, 'b', 3],
[HtmlTextAst, 'BOLD', 4],
]);
expect(humanizeDom(res)).toEqual([
[HtmlElementAst, 'ul', 0],
[HtmlAttrAst, '[ngPlural]', 'messages.length'],
[HtmlElementAst, 'template', 1],
[HtmlAttrAst, 'ngPluralCase', '=0'],
[HtmlElementAst, 'li', 2],
[HtmlTextAst, 'ZERO', 3],
[HtmlElementAst, 'b', 3],
[HtmlTextAst, 'BOLD', 4],
]);
});
it('should handle nested expansion forms', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('m', "gender_=m", null))] = 'M';
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],
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],
[HtmlElementAst, 'ul', 3], [HtmlAttrAst, '[ngSwitch]', 'p.gender'],
[HtmlElementAst, 'template', 4], [HtmlAttrAst, 'ngSwitchWhen', '=m'],
[HtmlElementAst, 'li', 5], [HtmlTextAst, 'M', 6],
[HtmlTextAst, ' ', 3]
]);
[HtmlTextAst, ' ', 3]
]);
});
it('should correctly set source code positions', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('<ph name="e0">bold</ph>', "plural_=0", null))] =
translations[id(new Message('<ph name="e0">bold</ph>', 'plural_=0', null))] =
'<ph name="e0">BOLD</ph>';
let nodes = parse(`{messages.length, plural,=0 {<b>bold</b>}}`, translations).rootNodes;
@ -259,19 +251,18 @@ export function main() {
it('should handle other special forms', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('m', "gender_=male", null))] = 'M';
translations[id(new Message('m', 'gender_=male', null))] = 'M';
let res = parse(`{person.gender, gender,=male {m}}`, translations);
expect(humanizeDom(res))
.toEqual([
[HtmlElementAst, 'ul', 0],
[HtmlAttrAst, '[ngSwitch]', 'person.gender'],
[HtmlElementAst, 'template', 1],
[HtmlAttrAst, 'ngSwitchWhen', '=male'],
[HtmlElementAst, 'li', 2],
[HtmlTextAst, 'M', 3],
]);
expect(humanizeDom(res)).toEqual([
[HtmlElementAst, 'ul', 0],
[HtmlAttrAst, '[ngSwitch]', 'person.gender'],
[HtmlElementAst, 'template', 1],
[HtmlAttrAst, 'ngSwitchWhen', '=male'],
[HtmlElementAst, 'li', 2],
[HtmlTextAst, 'M', 3],
]);
});
describe('errors', () => {
@ -321,45 +312,51 @@ export function main() {
.toEqual(['Invalid interpolation name \'99\'']);
});
describe('implicit translation', () => {
it('should support attributes', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('some message', null, null))] = 'another message';
it('should error on unknown plural cases', () => {
let mid = id(new Message('-', 'plural_unknown', null));
expect(humanizeErrors(parse('{n, plural, unknown {-}}', {mid: ''}).errors)).toEqual([
`Cannot find message for id '${mid}', content '-'.`,
`Plural cases should be "=<number>" or one of zero, one, two, few, many, other`,
]);
});
});
expect(humanizeDom(parse('<i18n-el value=\'some message\'></i18n-el>', translations, [], {
'i18n-el': ['value']
}))).toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlAttrAst, 'value', 'another message']]);
});
describe('implicit translation', () => {
it('should support attributes', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('some message', null, null))] = 'another message';
it('should support attributes with meaning and description', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('some message', 'meaning', 'description'))] =
'another message';
expect(humanizeDom(parse('<i18n-el value=\'some message\'></i18n-el>', translations, [], {
'i18n-el': ['value']
}))).toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlAttrAst, 'value', 'another message']]);
});
expect(
humanizeDom(parse(
'<i18n-el value=\'some message\' i18n-value=\'meaning|description\'></i18n-el>',
translations, [], {'i18n-el': ['value']})))
.toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlAttrAst, 'value', 'another message']]);
});
it('should support attributes with meaning and description', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('some message', 'meaning', 'description'))] = 'another message';
it('should support elements', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('message', null, null))] = 'another message';
expect(humanizeDom(parse(
'<i18n-el value=\'some message\' i18n-value=\'meaning|description\'></i18n-el>',
translations, [], {'i18n-el': ['value']})))
.toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlAttrAst, 'value', 'another message']]);
});
expect(humanizeDom(parse('<i18n-el>message</i18n-el>', translations, ['i18n-el'])))
.toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlTextAst, 'another message', 1]]);
});
it('should support elements', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('message', null, null))] = 'another message';
it('should support elements with meaning and description', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('message', 'meaning', 'description'))] = 'another message';
expect(humanizeDom(parse('<i18n-el>message</i18n-el>', translations, ['i18n-el'])))
.toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlTextAst, 'another message', 1]]);
});
expect(humanizeDom(parse(
'<i18n-el i18n=\'meaning|description\'>message</i18n-el>', translations,
['i18n-el'])))
.toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlTextAst, 'another message', 1]]);
});
it('should support elements with meaning and description', () => {
let translations: {[key: string]: string} = {};
translations[id(new Message('message', 'meaning', 'description'))] = 'another message';
expect(humanizeDom(parse(
'<i18n-el i18n=\'meaning|description\'>message</i18n-el>', translations,
['i18n-el'])))
.toEqual([[HtmlElementAst, 'i18n-el', 0], [HtmlTextAst, 'another message', 1]]);
});
});
});

View File

@ -151,8 +151,9 @@ export function main() {
]);
});
it("should extract messages from expansion forms", () => {
let res = extractor.extract(`
it('should extract messages from expansion forms', () => {
let res = extractor.extract(
`
<div>
{messages.length, plural,
=0 {You have <b>no</b> messages}
@ -160,14 +161,13 @@ export function main() {
other {You have messages}
}
</div>`,
"someurl");
'someurl');
expect(res.messages)
.toEqual([
new Message('You have <ph name="e1">no</ph> messages', "plural_=0", null),
new Message('You have one message', "plural_=1", null),
new Message('You have messages', "plural_other", null),
]);
expect(res.messages).toEqual([
new Message('You have <ph name="e1">no</ph> messages', 'plural_=0', null),
new Message('You have one message', 'plural_=1', null),
new Message('You have messages', 'plural_other', null),
]);
});
it('should remove duplicate messages', () => {
@ -235,6 +235,14 @@ export function main() {
expect(res.errors.length).toEqual(1);
expect(res.errors[0].msg).toEqual('Unexpected character "s"');
});
it('should return parse errors on unknown plural cases', () => {
let res = extractor.extract('{n, plural, unknown {-}}', 'someUrl');
expect(res.errors.length).toEqual(1);
expect(res.errors[0].msg)
.toEqual(
'Plural cases should be "=<number>" or one of zero, one, two, few, many, other');
});
});
});
}