From db9c9c9f66acfb46e33ca25f500fb992a09710ab Mon Sep 17 00:00:00 2001 From: adarshx01 Date: Mon, 17 Aug 2026 17:26:50 +0530 Subject: [PATCH] Honor implied end tags for dt and dd Unclosed dt/dd left the parser stack mismatched, so closing a parent dl/body dropped the surrounding tree. Close them the way HTML does: another dt/dd starts, or the parent dl/body/html ends. --- src/nodes/html.ts | 8 ++++++++ test/tests/issues/315.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 test/tests/issues/315.js diff --git a/src/nodes/html.ts b/src/nodes/html.ts index f6f4ea7..05b99ea 100644 --- a/src/nodes/html.ts +++ b/src/nodes/html.ts @@ -1011,6 +1011,10 @@ const kElementsClosedByOpening = { H5: { h5: true, H5: true }, h6: { h6: true, H6: true }, H6: { h6: true, H6: true }, + dt: { dt: true, dd: true, DT: true, DD: true }, + DT: { dt: true, dd: true, DT: true, DD: true }, + dd: { dt: true, dd: true, DT: true, DD: true }, + DD: { dt: true, dd: true, DT: true, DD: true }, } as Record>; const kElementsClosedByClosing = { li: { ul: true, ol: true, UL: true, OL: true }, @@ -1027,6 +1031,10 @@ const kElementsClosedByClosing = { TD: { tr: true, table: true, TR: true, TABLE: true }, th: { tr: true, table: true, TR: true, TABLE: true }, TH: { tr: true, table: true, TR: true, TABLE: true }, + dt: { dl: true, body: true, html: true, DL: true, BODY: true, HTML: true }, + DT: { dl: true, body: true, html: true, DL: true, BODY: true, HTML: true }, + dd: { dl: true, body: true, html: true, DL: true, BODY: true, HTML: true }, + DD: { dl: true, body: true, html: true, DL: true, BODY: true, HTML: true }, } as Record>; const kElementsClosedByClosingExcept = { p: { a: true, audio: true, del: true, ins: true, map: true, noscript: true, video: true }, diff --git a/test/tests/issues/315.js b/test/tests/issues/315.js new file mode 100644 index 0000000..fff113d --- /dev/null +++ b/test/tests/issues/315.js @@ -0,0 +1,37 @@ +const { parse } = require('@test/test-target'); + +describe('issue 315 unclosed dt/dd tags', function () { + it('keeps the document tree when dt and dd omit their end tags', function () { + const html = ` + + +
+
+
+
+ + +`; + const root = parse(html); + root.querySelector('body').should.be.ok(); + root.querySelector('dl').should.be.ok(); + root.querySelectorAll('dt').length.should.eql(1); + root.querySelectorAll('dd').length.should.eql(1); + root.toString().should.containEql(''); + root.toString().should.containEql('
'); + }); + + it('does not drop body when a lone dt is unclosed', function () { + const root = parse('
'); + root.querySelector('body').should.be.ok(); + root.querySelectorAll('dt').length.should.eql(1); + root.toString().should.containEql(''); + }); + + it('closes an open dt when a dd starts', function () { + const root = parse('
term
def
'); + root.querySelector('dt').text.should.eql('term'); + root.querySelector('dd').text.should.eql('def'); + root.querySelector('dl').childNodes.filter((n) => n.tagName).map((n) => n.tagName).should.eql(['DT', 'DD']); + }); +});