feat(HtmlParser): enforce no end tag for void elements
BREAKING CHANGE End tags used to be tolerated for void elements with no content. They are no more allowed so that we more closely follow the HTML5 spec.
This commit is contained in:
parent
b925ff5b8d
commit
56604468e0
|
@ -171,17 +171,14 @@ class TreeBuilder {
|
||||||
private _consumeEndTag(endTagToken: HtmlToken) {
|
private _consumeEndTag(endTagToken: HtmlToken) {
|
||||||
var fullName =
|
var fullName =
|
||||||
getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement());
|
getElementFullName(endTagToken.parts[0], endTagToken.parts[1], this._getParentElement());
|
||||||
if (!this._popElement(fullName)) {
|
|
||||||
let msg;
|
|
||||||
|
|
||||||
if (getHtmlTagDefinition(fullName).isVoid) {
|
if (getHtmlTagDefinition(fullName).isVoid) {
|
||||||
msg =
|
this.errors.push(
|
||||||
`Void elements do not have end tags (they can not have content) "${endTagToken.parts[1]}"`;
|
HtmlTreeError.create(fullName, endTagToken.sourceSpan.start,
|
||||||
} else {
|
`Void elements do not have end tags "${endTagToken.parts[1]}"`));
|
||||||
msg = `Unexpected closing tag "${endTagToken.parts[1]}"`;
|
} else if (!this._popElement(fullName)) {
|
||||||
}
|
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan.start,
|
||||||
|
`Unexpected closing tag "${endTagToken.parts[1]}"`));
|
||||||
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan.start, msg));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,11 +85,6 @@ export function main() {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should tolerate end tags for void elements when they have no content', () => {
|
|
||||||
expect(humanizeDom(parser.parse('<input></input>', 'TestComp')))
|
|
||||||
.toEqual([[HtmlElementAst, 'input', 0]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support optional end tags', () => {
|
it('should support optional end tags', () => {
|
||||||
expect(humanizeDom(parser.parse('<div><p>1<p>2</div>', 'TestComp')))
|
expect(humanizeDom(parser.parse('<div><p>1<p>2</div>', 'TestComp')))
|
||||||
.toEqual([
|
.toEqual([
|
||||||
|
@ -214,30 +209,11 @@ export function main() {
|
||||||
expect(humanizeErrors(errors)).toEqual([['p', 'Unexpected closing tag "p"', '0:5']]);
|
expect(humanizeErrors(errors)).toEqual([['p', 'Unexpected closing tag "p"', '0:5']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should report text content in void elements', () => {
|
it('should report closing tag for void elements', () => {
|
||||||
let errors = parser.parse('<input>content</input>', 'TestComp').errors;
|
let errors = parser.parse('<input></input>', 'TestComp').errors;
|
||||||
expect(errors.length).toEqual(1);
|
expect(errors.length).toEqual(1);
|
||||||
expect(humanizeErrors(errors))
|
expect(humanizeErrors(errors))
|
||||||
.toEqual([
|
.toEqual([['input', 'Void elements do not have end tags "input"', '0:7']]);
|
||||||
[
|
|
||||||
'input',
|
|
||||||
'Void elements do not have end tags (they can not have content) "input"',
|
|
||||||
'0:14'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should report html content in void elements', () => {
|
|
||||||
let errors = parser.parse('<input><p></p></input>', 'TestComp').errors;
|
|
||||||
expect(errors.length).toEqual(1);
|
|
||||||
expect(humanizeErrors(errors))
|
|
||||||
.toEqual([
|
|
||||||
[
|
|
||||||
'input',
|
|
||||||
'Void elements do not have end tags (they can not have content) "input"',
|
|
||||||
'0:14'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should also report lexer errors', () => {
|
it('should also report lexer errors', () => {
|
||||||
|
|
|
@ -241,7 +241,7 @@ export function main() {
|
||||||
var template = normalizer.normalizeLoadedTemplate(
|
var template = normalizer.normalizeLoadedTemplate(
|
||||||
dirType,
|
dirType,
|
||||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||||
'<link href="b" rel="a"></link>', 'package:some/module/');
|
'<link href="b" rel="a">', 'package:some/module/');
|
||||||
expect(template.styleUrls).toEqual([]);
|
expect(template.styleUrls).toEqual([]);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -250,8 +250,7 @@ export function main() {
|
||||||
var template = normalizer.normalizeLoadedTemplate(
|
var template = normalizer.normalizeLoadedTemplate(
|
||||||
dirType,
|
dirType,
|
||||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||||
'<link href="http://some/external.css" rel="stylesheet"></link>',
|
'<link href="http://some/external.css" rel="stylesheet">', 'package:some/module/');
|
||||||
'package:some/module/');
|
|
||||||
expect(template.styleUrls).toEqual([]);
|
expect(template.styleUrls).toEqual([]);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -774,8 +774,7 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||||
|
|
||||||
it('should keep <link rel="stylesheet"> elements if they have an absolute non package: url',
|
it('should keep <link rel="stylesheet"> elements if they have an absolute non package: url',
|
||||||
() => {
|
() => {
|
||||||
expect(
|
expect(humanizeTplAst(parse('<link rel="stylesheet" href="http://someurl">a', [])))
|
||||||
humanizeTplAst(parse('<link rel="stylesheet" href="http://someurl"></link>a', [])))
|
|
||||||
.toEqual([
|
.toEqual([
|
||||||
[ElementAst, 'link'],
|
[ElementAst, 'link'],
|
||||||
[AttrAst, 'rel', 'stylesheet'],
|
[AttrAst, 'rel', 'stylesheet'],
|
||||||
|
@ -785,22 +784,21 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should keep <link rel="stylesheet"> elements if they have no uri', () => {
|
it('should keep <link rel="stylesheet"> elements if they have no uri', () => {
|
||||||
expect(humanizeTplAst(parse('<link rel="stylesheet"></link>a', [])))
|
expect(humanizeTplAst(parse('<link rel="stylesheet">a', [])))
|
||||||
.toEqual([[ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'], [TextAst, 'a']]);
|
.toEqual([[ElementAst, 'link'], [AttrAst, 'rel', 'stylesheet'], [TextAst, 'a']]);
|
||||||
expect(humanizeTplAst(parse('<link REL="stylesheet"></link>a', [])))
|
expect(humanizeTplAst(parse('<link REL="stylesheet">a', [])))
|
||||||
.toEqual([[ElementAst, 'link'], [AttrAst, 'REL', 'stylesheet'], [TextAst, 'a']]);
|
.toEqual([[ElementAst, 'link'], [AttrAst, 'REL', 'stylesheet'], [TextAst, 'a']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore <link rel="stylesheet"> elements if they have a relative uri', () => {
|
it('should ignore <link rel="stylesheet"> elements if they have a relative uri', () => {
|
||||||
expect(humanizeTplAst(parse('<link rel="stylesheet" href="./other.css"></link>a', [])))
|
expect(humanizeTplAst(parse('<link rel="stylesheet" href="./other.css">a', [])))
|
||||||
.toEqual([[TextAst, 'a']]);
|
.toEqual([[TextAst, 'a']]);
|
||||||
expect(humanizeTplAst(parse('<link rel="stylesheet" HREF="./other.css"></link>a', [])))
|
expect(humanizeTplAst(parse('<link rel="stylesheet" HREF="./other.css">a', [])))
|
||||||
.toEqual([[TextAst, 'a']]);
|
.toEqual([[TextAst, 'a']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore <link rel="stylesheet"> elements if they have a package: uri', () => {
|
it('should ignore <link rel="stylesheet"> elements if they have a package: uri', () => {
|
||||||
expect(humanizeTplAst(
|
expect(humanizeTplAst(parse('<link rel="stylesheet" href="package:somePackage">a', [])))
|
||||||
parse('<link rel="stylesheet" href="package:somePackage"></link>a', [])))
|
|
||||||
.toEqual([[TextAst, 'a']]);
|
.toEqual([[TextAst, 'a']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -835,8 +833,7 @@ Property binding a not used by any directive on an embedded template ("[ERROR ->
|
||||||
|
|
||||||
it('should ignore <link rel="stylesheet"> elements inside of elements with ng-non-bindable',
|
it('should ignore <link rel="stylesheet"> elements inside of elements with ng-non-bindable',
|
||||||
() => {
|
() => {
|
||||||
expect(humanizeTplAst(
|
expect(humanizeTplAst(parse('<div ng-non-bindable><link rel="stylesheet">a</div>', [])))
|
||||||
parse('<div ng-non-bindable><link rel="stylesheet"></link>a</div>', [])))
|
|
||||||
.toEqual([[ElementAst, 'div'], [AttrAst, 'ng-non-bindable', ''], [TextAst, 'a']]);
|
.toEqual([[ElementAst, 'div'], [AttrAst, 'ng-non-bindable', ''], [TextAst, 'a']]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1007,7 +1007,7 @@ export function main() {
|
||||||
tcb.overrideView(
|
tcb.overrideView(
|
||||||
MyComp, new ViewMetadata({
|
MyComp, new ViewMetadata({
|
||||||
template:
|
template:
|
||||||
'<input type="checkbox" listenerprevent></input><input type="checkbox" listenernoprevent></input>',
|
'<input type="checkbox" listenerprevent><input type="checkbox" listenernoprevent>',
|
||||||
directives: [
|
directives: [
|
||||||
DirectiveListeningDomEventPrevent,
|
DirectiveListeningDomEventPrevent,
|
||||||
DirectiveListeningDomEventNoPrevent
|
DirectiveListeningDomEventNoPrevent
|
||||||
|
|
|
@ -211,8 +211,7 @@ export function main() {
|
||||||
it('should call actions on the element independent of the compilation',
|
it('should call actions on the element independent of the compilation',
|
||||||
inject([TestComponentBuilder, Renderer, AsyncTestCompleter],
|
inject([TestComponentBuilder, Renderer, AsyncTestCompleter],
|
||||||
(tcb: TestComponentBuilder, renderer: Renderer, async) => {
|
(tcb: TestComponentBuilder, renderer: Renderer, async) => {
|
||||||
tcb.overrideView(MyComp,
|
tcb.overrideView(MyComp, new ViewMetadata({template: '<input [title]="y">'}))
|
||||||
new ViewMetadata({template: '<input [title]="y"></input>'}))
|
|
||||||
.createAsync(MyComp)
|
.createAsync(MyComp)
|
||||||
.then((fixture) => {
|
.then((fixture) => {
|
||||||
var elRef = fixture.debugElement.componentViewChildren[0].elementRef;
|
var elRef = fixture.debugElement.componentViewChildren[0].elementRef;
|
||||||
|
|
Loading…
Reference in New Issue