feat(HtmlParser): better error message when a void tag has content

This commit is contained in:
Victor Berchet 2015-12-02 10:11:01 -08:00 committed by Jeremy Elbourn
parent 9c6b929c7b
commit 62c2ed7c43
2 changed files with 47 additions and 7 deletions

View File

@ -128,21 +128,21 @@ class TreeBuilder {
attrs.push(this._consumeAttr(this._advance())); attrs.push(this._consumeAttr(this._advance()));
} }
var fullName = getElementFullName(prefix, name, this._getParentElement()); var fullName = getElementFullName(prefix, name, this._getParentElement());
var voidElement = false; var selfClosing = false;
// Note: There could have been a tokenizer error // Note: There could have been a tokenizer error
// so that we don't get a token for the end tag... // so that we don't get a token for the end tag...
if (this.peek.type === HtmlTokenType.TAG_OPEN_END_VOID) { if (this.peek.type === HtmlTokenType.TAG_OPEN_END_VOID) {
this._advance(); this._advance();
voidElement = true; selfClosing = true;
} else if (this.peek.type === HtmlTokenType.TAG_OPEN_END) { } else if (this.peek.type === HtmlTokenType.TAG_OPEN_END) {
this._advance(); this._advance();
voidElement = false; selfClosing = false;
} }
var end = this.peek.sourceSpan.start; var end = this.peek.sourceSpan.start;
var el = new HtmlElementAst(fullName, attrs, [], var el = new HtmlElementAst(fullName, attrs, [],
new ParseSourceSpan(startTagToken.sourceSpan.start, end)); new ParseSourceSpan(startTagToken.sourceSpan.start, end));
this._pushElement(el); this._pushElement(el);
if (voidElement) { if (selfClosing) {
this._popElement(fullName); this._popElement(fullName);
} }
} }
@ -172,18 +172,27 @@ class TreeBuilder {
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)) { if (!this._popElement(fullName)) {
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan.start, let msg;
`Unexpected closing tag "${endTagToken.parts[1]}"`));
if (getHtmlTagDefinition(fullName).isVoid) {
msg =
`Void elements do not have end tags (they can not have content) "${endTagToken.parts[1]}"`;
} else {
msg = `Unexpected closing tag "${endTagToken.parts[1]}"`;
}
this.errors.push(HtmlTreeError.create(fullName, endTagToken.sourceSpan.start, msg));
} }
} }
private _popElement(fullName: string): boolean { private _popElement(fullName: string): boolean {
for (let stackIndex = this.elementStack.length - 1; stackIndex >= 0; stackIndex--) { for (let stackIndex = this.elementStack.length - 1; stackIndex >= 0; stackIndex--) {
var el = this.elementStack[stackIndex]; let el = this.elementStack[stackIndex];
if (el.name.toLowerCase() == fullName.toLowerCase()) { if (el.name.toLowerCase() == fullName.toLowerCase()) {
ListWrapper.splice(this.elementStack, stackIndex, this.elementStack.length - stackIndex); ListWrapper.splice(this.elementStack, stackIndex, this.elementStack.length - stackIndex);
return true; return true;
} }
if (!getHtmlTagDefinition(el.name).closedByParent) { if (!getHtmlTagDefinition(el.name).closedByParent) {
return false; return false;
} }

View File

@ -85,6 +85,11 @@ export function main() {
]); ]);
}); });
it('should tolerate end tags for void elements when they have no content', () => {
expect(humanizeDom(parser.parse('<ng-content></ng-content>', 'TestComp')))
.toEqual([[HtmlElementAst, 'ng-content', 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([
@ -209,6 +214,32 @@ 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', () => {
let errors = parser.parse('<ng-content>content</ng-content>', 'TestComp').errors;
expect(errors.length).toEqual(1);
expect(humanizeErrors(errors))
.toEqual([
[
'ng-content',
'Void elements do not have end tags (they can not have content) "ng-content"',
'0:19'
]
]);
});
it('should report html content in void elements', () => {
let errors = parser.parse('<ng-content><p></p></ng-content>', 'TestComp').errors;
expect(errors.length).toEqual(1);
expect(humanizeErrors(errors))
.toEqual([
[
'ng-content',
'Void elements do not have end tags (they can not have content) "ng-content"',
'0:19'
]
]);
});
it('should also report lexer errors', () => { it('should also report lexer errors', () => {
let errors = parser.parse('<!-err--><div></p></div>', 'TestComp').errors; let errors = parser.parse('<!-err--><div></p></div>', 'TestComp').errors;
expect(errors.length).toEqual(2); expect(errors.length).toEqual(2);