fix(compiler): always match close tag to the nearest open element (#42554)
This commit updates the parser logic to continue to try to match an end tag to an unclosed open tag on the stack. Previously, it would only push an error to the list and stop looking at unclosed elements. For example, the invalid HTML of `<li><div></li>`, has an unclosed element stack of [`li`, `div`] when it encounters the close `li` tag. We compare against the previously unclosed tag `div` and see that this is unexpected. Instead of simply giving up here, we continue to move up the unclosed tags until we find a match (if there is one). PR Close #42554
This commit is contained in:
parent
044e0229bd
commit
8c1e0e6ad0
|
@ -313,6 +313,7 @@ class _TreeBuilder {
|
||||||
* opening tag is recovered).
|
* opening tag is recovered).
|
||||||
*/
|
*/
|
||||||
private _popElement(fullName: string, endSourceSpan: ParseSourceSpan|null): boolean {
|
private _popElement(fullName: string, endSourceSpan: ParseSourceSpan|null): boolean {
|
||||||
|
let unexpectedCloseTagDetected = false;
|
||||||
for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) {
|
for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) {
|
||||||
const el = this._elementStack[stackIndex];
|
const el = this._elementStack[stackIndex];
|
||||||
if (el.name == fullName) {
|
if (el.name == fullName) {
|
||||||
|
@ -323,11 +324,14 @@ class _TreeBuilder {
|
||||||
el.sourceSpan.end = endSourceSpan !== null ? endSourceSpan.end : el.sourceSpan.end;
|
el.sourceSpan.end = endSourceSpan !== null ? endSourceSpan.end : el.sourceSpan.end;
|
||||||
|
|
||||||
this._elementStack.splice(stackIndex, this._elementStack.length - stackIndex);
|
this._elementStack.splice(stackIndex, this._elementStack.length - stackIndex);
|
||||||
return true;
|
return !unexpectedCloseTagDetected;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.getTagDefinition(el.name).closedByParent) {
|
if (!this.getTagDefinition(el.name).closedByParent) {
|
||||||
return false;
|
// Note that we encountered an unexpected close tag but continue processing the element
|
||||||
|
// stack so we can assign an `endSourceSpan` if there is a corresponding start tag for this
|
||||||
|
// end tag in the stack.
|
||||||
|
unexpectedCloseTagDetected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -857,6 +857,20 @@ import {humanizeDom, humanizeDomSourceSpans, humanizeLineColumn, humanizeNodes}
|
||||||
]]);
|
]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('gets correct close tag for parent when a child is not closed', () => {
|
||||||
|
const {errors, rootNodes} = parser.parse('<div><span></div>', 'TestComp');
|
||||||
|
expect(errors.length).toEqual(1);
|
||||||
|
expect(humanizeErrors(errors)).toEqual([[
|
||||||
|
'div',
|
||||||
|
'Unexpected closing tag "div". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags',
|
||||||
|
'0:11'
|
||||||
|
]]);
|
||||||
|
expect(humanizeNodes(rootNodes, true)).toEqual([
|
||||||
|
[html.Element, 'div', 0, '<div><span></div>', '<div>', '</div>'],
|
||||||
|
[html.Element, 'span', 1, '<span>', '<span>', null],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
describe('incomplete element tag', () => {
|
describe('incomplete element tag', () => {
|
||||||
it('should parse and report incomplete tags after the tag name', () => {
|
it('should parse and report incomplete tags after the tag name', () => {
|
||||||
const {errors, rootNodes} = parser.parse('<div<span><div </span>', 'TestComp');
|
const {errors, rootNodes} = parser.parse('<div<span><div </span>', 'TestComp');
|
||||||
|
|
Loading…
Reference in New Issue