feat(HtmlParser): enforce only void & foreign elts can be self closed

BREAKING CHANGE:

`<whatever />` used to be expanded to `<whatever></whatever>`.
The parser now follows the HTML5 spec more closely.
Only void and foreign elements can be self closed.

Closes #5591
This commit is contained in:
Victor Berchet 2015-12-03 16:10:20 -08:00
parent 56604468e0
commit d388c0ae62
5 changed files with 45 additions and 10 deletions

View File

@ -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;

View File

@ -147,6 +147,16 @@ export function main() {
expect(humanizeDom(parser.parse('<DiV><P></p></dIv>', 'TestComp')))
.toEqual([[HtmlElementAst, 'DiV', 0], [HtmlElementAst, 'P', 1]]);
});
it('should support self closing void elements', () => {
expect(humanizeDom(parser.parse('<input />', 'TestComp')))
.toEqual([[HtmlElementAst, 'input', 0]]);
});
it('should support self closing foreign elements', () => {
expect(humanizeDom(parser.parse('<math />', 'TestComp')))
.toEqual([[HtmlElementAst, '@math:math', 0]]);
});
});
describe('attributes', () => {
@ -175,8 +185,8 @@ export function main() {
});
it('should support mamespace', () => {
expect(humanizeDom(parser.parse('<use xlink:href="Port" />', 'TestComp')))
.toEqual([[HtmlElementAst, 'use', 0], [HtmlAttrAst, '@xlink:href', 'Port']]);
expect(humanizeDom(parser.parse('<svg:use xlink:href="Port" />', '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('<p />', '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('<my-cmp />', '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('<!-err--><div></p></div>', 'TestComp').errors;
expect(errors.length).toEqual(2);

View File

@ -728,8 +728,8 @@ Parser Error: Unexpected token 'b' at column 3 in [a b] in TestComp@0:5 ("<div [
type: new CompileTypeMetadata({name: 'DirB'}),
template: new CompileTemplateMetadata({ngContentSelectors: []})
});
expect(() => parse('<div/>', [dirB, dirA])).toThrowError(`Template parse errors:
More than one component: DirB,DirA ("[ERROR ->]<div/>"): TestComp@0:0`);
expect(() => parse('<div>', [dirB, dirA])).toThrowError(`Template parse errors:
More than one component: DirB,DirA ("[ERROR ->]<div>"): TestComp@0:0`);
});
it('should not allow components or element bindings nor dom events on explicit embedded templates',

View File

@ -578,10 +578,12 @@ export function main() {
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.overrideView(MyComp, new ViewMetadata({
template: '<p><child-cmp var-alice/><child-cmp var-bob/></p>',
directives: [ChildComp]
}))
tcb.overrideView(
MyComp, new ViewMetadata({
template:
'<p><child-cmp var-alice></child-cmp><child-cmp var-bob></child-cmp></p>',
directives: [ChildComp]
}))
.createAsync(MyComp)
.then((fixture) => {

View File

@ -352,7 +352,8 @@ void allTests() {
});
it('should include platform directives.', () async {
fooComponentMeta.template = new CompileTemplateMetadata(template: '<bar/>');
fooComponentMeta.template = new CompileTemplateMetadata(
template: '<bar></bar>');
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: '<bar/>');
fooComponentMeta.template = new CompileTemplateMetadata(
template: '<bar></bar>');
final viewAnnotation = new AnnotationModel()
..name = 'View'
..isView = true;