From d388c0ae62cced3550f775ccaba11d58958ac49c Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Thu, 3 Dec 2015 16:10:20 -0800 Subject: [PATCH] feat(HtmlParser): enforce only void & foreign elts can be self closed BREAKING CHANGE: `` used to be expanded to ``. The parser now follows the HTML5 spec more closely. Only void and foreign elements can be self closed. Closes #5591 --- modules/angular2/src/compiler/html_parser.ts | 5 ++++ .../test/compiler/html_parser_spec.ts | 30 +++++++++++++++++-- .../test/compiler/template_parser_spec.ts | 4 +-- .../test/core/linker/integration_spec.ts | 10 ++++--- .../template_compiler/all_tests.dart | 6 ++-- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/modules/angular2/src/compiler/html_parser.ts b/modules/angular2/src/compiler/html_parser.ts index cf3474ba02..9d595b684e 100644 --- a/modules/angular2/src/compiler/html_parser.ts +++ b/modules/angular2/src/compiler/html_parser.ts @@ -134,6 +134,11 @@ class TreeBuilder { if (this.peek.type === HtmlTokenType.TAG_OPEN_END_VOID) { this._advance(); selfClosing = true; + if (namespacePrefix(fullName) == null && !getHtmlTagDefinition(fullName).isVoid) { + this.errors.push(HtmlTreeError.create( + fullName, startTagToken.sourceSpan.start, + `Only void and foreign elements can be self closed "${startTagToken.parts[1]}"`)); + } } else if (this.peek.type === HtmlTokenType.TAG_OPEN_END) { this._advance(); selfClosing = false; diff --git a/modules/angular2/test/compiler/html_parser_spec.ts b/modules/angular2/test/compiler/html_parser_spec.ts index d779f0dd4b..fd55214414 100644 --- a/modules/angular2/test/compiler/html_parser_spec.ts +++ b/modules/angular2/test/compiler/html_parser_spec.ts @@ -147,6 +147,16 @@ export function main() { expect(humanizeDom(parser.parse('

', 'TestComp'))) .toEqual([[HtmlElementAst, 'DiV', 0], [HtmlElementAst, 'P', 1]]); }); + + it('should support self closing void elements', () => { + expect(humanizeDom(parser.parse('', 'TestComp'))) + .toEqual([[HtmlElementAst, 'input', 0]]); + }); + + it('should support self closing foreign elements', () => { + expect(humanizeDom(parser.parse('', 'TestComp'))) + .toEqual([[HtmlElementAst, '@math:math', 0]]); + }); }); describe('attributes', () => { @@ -175,8 +185,8 @@ export function main() { }); it('should support mamespace', () => { - expect(humanizeDom(parser.parse('', 'TestComp'))) - .toEqual([[HtmlElementAst, 'use', 0], [HtmlAttrAst, '@xlink:href', 'Port']]); + expect(humanizeDom(parser.parse('', 'TestComp'))) + .toEqual([[HtmlElementAst, '@svg:use', 0], [HtmlAttrAst, '@xlink:href', 'Port']]); }); }); @@ -216,6 +226,22 @@ export function main() { .toEqual([['input', 'Void elements do not have end tags "input"', '0:7']]); }); + it('should report self closing html element', () => { + let errors = parser.parse('

', 'TestComp').errors; + expect(errors.length).toEqual(1); + expect(humanizeErrors(errors)) + .toEqual([['p', 'Only void and foreign elements can be self closed "p"', '0:0']]); + }); + + it('should report self closing custom element', () => { + let errors = parser.parse('', 'TestComp').errors; + expect(errors.length).toEqual(1); + expect(humanizeErrors(errors)) + .toEqual([ + ['my-cmp', 'Only void and foreign elements can be self closed "my-cmp"', '0:0'] + ]); + }); + it('should also report lexer errors', () => { let errors = parser.parse('

', 'TestComp').errors; expect(errors.length).toEqual(2); diff --git a/modules/angular2/test/compiler/template_parser_spec.ts b/modules/angular2/test/compiler/template_parser_spec.ts index b9901638e9..061ea32eed 100644 --- a/modules/angular2/test/compiler/template_parser_spec.ts +++ b/modules/angular2/test/compiler/template_parser_spec.ts @@ -728,8 +728,8 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("
parse('
', [dirB, dirA])).toThrowError(`Template parse errors: -More than one component: DirB,DirA ("[ERROR ->]
"): TestComp@0:0`); + expect(() => parse('
', [dirB, dirA])).toThrowError(`Template parse errors: +More than one component: DirB,DirA ("[ERROR ->]
"): TestComp@0:0`); }); it('should not allow components or element bindings nor dom events on explicit embedded templates', diff --git a/modules/angular2/test/core/linker/integration_spec.ts b/modules/angular2/test/core/linker/integration_spec.ts index f647a1cf4e..86f1cc7622 100644 --- a/modules/angular2/test/core/linker/integration_spec.ts +++ b/modules/angular2/test/core/linker/integration_spec.ts @@ -578,10 +578,12 @@ export function main() { inject( [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { - tcb.overrideView(MyComp, new ViewMetadata({ - template: '

', - directives: [ChildComp] - })) + tcb.overrideView( + MyComp, new ViewMetadata({ + template: + '

', + directives: [ChildComp] + })) .createAsync(MyComp) .then((fixture) => { diff --git a/modules_dart/transform/test/transform/template_compiler/all_tests.dart b/modules_dart/transform/test/transform/template_compiler/all_tests.dart index 57a0fcbab9..0b1533a76f 100644 --- a/modules_dart/transform/test/transform/template_compiler/all_tests.dart +++ b/modules_dart/transform/test/transform/template_compiler/all_tests.dart @@ -352,7 +352,8 @@ void allTests() { }); it('should include platform directives.', () async { - fooComponentMeta.template = new CompileTemplateMetadata(template: ''); + fooComponentMeta.template = new CompileTemplateMetadata( + template: ''); final viewAnnotation = new AnnotationModel() ..name = 'View' ..isView = true; @@ -370,7 +371,8 @@ void allTests() { }); it('should include platform directives when it it a list.', () async { - fooComponentMeta.template = new CompileTemplateMetadata(template: ''); + fooComponentMeta.template = new CompileTemplateMetadata( + template: ''); final viewAnnotation = new AnnotationModel() ..name = 'View' ..isView = true;