From bed322fbef02e1c3625428efe880878a1dbd5443 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 31 Oct 2026 04:13:17 +0100 Subject: [PATCH] Keep a node's empty marks array where a run would span it --- spec/flavour.md | 2 +- src/markdown/emit/adf-to-markdown.test.ts | 2 ++ src/markdown/emit/skipped-nodes.ts | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/flavour.md b/spec/flavour.md index a829464..3cb886c 100644 --- a/spec/flavour.md +++ b/spec/flavour.md @@ -566,7 +566,7 @@ carrying an identical mark, attributes included, at that depth: `attrs: {}` diff and a directive spells it `{attrs=empty}`. A run breaks at every node the emitter carries, so no emitted carry sits inside a mark spelling. An `em`, `strike`, `strong` or `link` run also spans a node between two of its nodes when that node cannot hold the run's mark. It does so only where the -spanned line reads back to the same marks; otherwise the run breaks there. So `_One\` then `Two_` +spanned line reads back to the same nodes; otherwise the run breaks there. So `_One\` then `Two_` is one `em` run over two texts, the hard break between them unmarked, and ``**Run `npm ci` first**`` is one `strong` run around unmarked code. `adfToPortableMarkdown` spans hard breaks and code spans the same way. diff --git a/src/markdown/emit/adf-to-markdown.test.ts b/src/markdown/emit/adf-to-markdown.test.ts index 0707fc4..40785b3 100644 --- a/src/markdown/emit/adf-to-markdown.test.ts +++ b/src/markdown/emit/adf-to-markdown.test.ts @@ -914,6 +914,8 @@ test('spells an emphasis or link run once across the hard breaks between two nod const link: AdfMark = { attrs: { href: '/u' }, type: 'link' } const marked = (text: string, ...marks: AdfMark[]): AdfNode => ({ marks, text, type: 'text' }) const hardBreak: AdfNode = { type: 'hardBreak' } + assert.equal(readsBack(document(paragraph(marked('a', em), { marks: [], type: 'hardBreak' }, marked('b', em)))), '_a_!adf:hardBreak{marks=empty}_b_\n') + assert.equal(readsBack(document(paragraph(marked('a', em), { attrs: { id: 'x', text: '@A' }, marks: [], type: 'mention' }, marked('b', em)))), '_a_!adf:mention[@A]{id=x marks=empty}_b_\n') assert.equal(readsBack(document(paragraph(marked('One', em), hardBreak, marked('Two', em)))), '_One\\\nTwo_\n') assert.equal(readsBack(document(paragraph(marked('a', link), hardBreak, marked('b', link)))), '[a\\\nb](/u)\n') assert.equal(readsBack(document(paragraph(marked('a', em, link), hardBreak, marked('b', em)))), '_[a](/u)\\\nb_\n') diff --git a/src/markdown/emit/skipped-nodes.ts b/src/markdown/emit/skipped-nodes.ts index b6397f3..5663abc 100644 --- a/src/markdown/emit/skipped-nodes.ts +++ b/src/markdown/emit/skipped-nodes.ts @@ -47,7 +47,12 @@ function heldMark(spanned: readonly AdfNode[], index: number, depth: number, car function skipsMark(spanned: readonly AdfNode[], index: number, mark: AdfMark, carries: Carries): boolean { const node = spanned[index] const kind = markSpelling(mark.type)?.kind - return node !== undefined && (kind === 'emphasis' || kind === 'link') && !carries(node, index) && !takesMark(node, mark.type) + return node !== undefined && (kind === 'emphasis' || kind === 'link') && !carries(node, index) && !takesMark(node, mark.type) && !holdsEmptyMarks(node) +} + +// An empty marks array is spelled `marks=empty`, which a lent mark would erase. +function holdsEmptyMarks(node: AdfNode): boolean { + return Array.isArray(node.marks) && node.marks.length === 0 } function withMark(node: AdfNode, mark: AdfMark, depth: number): AdfNode {