Skip to content

Commit

Permalink
fix(compiler): always match close tag to the nearest open element (#4…
Browse files Browse the repository at this point in the history
…2554)

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
  • Loading branch information
atscott authored and alxhub committed Jun 14, 2021
1 parent 18d8322 commit 89fc131
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 2 deletions.
8 changes: 6 additions & 2 deletions packages/compiler/src/ml_parser/parser.ts
Expand Up @@ -313,6 +313,7 @@ class _TreeBuilder {
* opening tag is recovered).
*/
private _popElement(fullName: string, endSourceSpan: ParseSourceSpan|null): boolean {
let unexpectedCloseTagDetected = false;
for (let stackIndex = this._elementStack.length - 1; stackIndex >= 0; stackIndex--) {
const el = this._elementStack[stackIndex];
if (el.name == fullName) {
Expand All @@ -323,11 +324,14 @@ class _TreeBuilder {
el.sourceSpan.end = endSourceSpan !== null ? endSourceSpan.end : el.sourceSpan.end;

this._elementStack.splice(stackIndex, this._elementStack.length - stackIndex);
return true;
return !unexpectedCloseTagDetected;
}

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;
Expand Down
14 changes: 14 additions & 0 deletions packages/compiler/test/ml_parser/html_parser_spec.ts
Expand Up @@ -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', () => {
it('should parse and report incomplete tags after the tag name', () => {
const {errors, rootNodes} = parser.parse('<div<span><div </span>', 'TestComp');
Expand Down

0 comments on commit 89fc131

Please sign in to comment.