feat(HtmlParser): better error message when a void tag has content
This commit is contained in:
parent
9c6b929c7b
commit
62c2ed7c43
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue