mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48:29 +01:00
Answer stability round 1: 0 🔴, 4 🟡 (0 misread); cap the every mode alone, name the cap's steps, pin links and highlights at the cap
This commit is contained in:
@@ -452,12 +452,13 @@ test('carries the mark spelling that cannot close where it sits, and keeps the l
|
||||
assert.equal(emitted(marked('^', { type: 'strike' }), b, marked('c', { type: 'strike' })), '!adf:carry{json="{\\"marks\\":[{\\"type\\":\\"strike\\"}],\\"text\\":\\"^\\",\\"type\\":\\"text\\"}"}b~~c~~\n')
|
||||
})
|
||||
|
||||
test('carries every node holding em, strong or strike in a line still misread after 32 passes', () => {
|
||||
test('carries every node holding em, strike or strong, and no other, in a line still misread after 32 passes', () => {
|
||||
const marked = (text: string, ...types: string[]): AdfNode => ({ marks: types.map((type) => ({ type })), text, type: 'text' })
|
||||
const carry = (node: AdfNode): string => `!adf:carry{json="${JSON.stringify(node).replaceAll('"', '\\"')}"}`
|
||||
const repeat = [marked(' ~~ ', 'em', 'strong'), marked(')', 'em', 'strike', 'strong'), { text: 'a', type: 'text' }, marked('.unun', 'strike', 'strong')]
|
||||
const written = repeat.map((node) => (node.marks === undefined ? 'a' : carry(node))).join('')
|
||||
assert.equal(markdown(adfToLosslessMarkdown(document(paragraph(...Array.from({ length: 40 }, () => repeat).flat())))), `${written.repeat(40)}\n`)
|
||||
const line = [...Array.from({ length: 40 }, () => repeat).flat(), { text: ' ', type: 'text' }, { marks: [{ attrs: { href: 'u' }, type: 'link' }], text: 'b', type: 'text' }]
|
||||
assert.equal(markdown(adfToLosslessMarkdown(document(paragraph(...line)))), `${written.repeat(40)} [b](u)\n`)
|
||||
})
|
||||
|
||||
test('refuses a node carrying one mark type twice', () => {
|
||||
|
||||
@@ -27,7 +27,7 @@ type Emission = { carries: [NodeRange, ...NodeRange[]]; segments?: undefined } |
|
||||
|
||||
type Fallback = [NodeRange, ...NodeRange[]] | 'opening-link'
|
||||
|
||||
// How many fallbacks one pass applies: `every`, or the `first` alone; a property checks that both write the same line.
|
||||
// How many fallbacks one pass applies: `every`, or the `first` alone and with no cap; a property checks that both write the same line.
|
||||
export type FallbacksPerPass = 'every' | 'first'
|
||||
|
||||
type InlineContext = {
|
||||
@@ -47,7 +47,7 @@ type LineAttempt = { fallback: Fallback; line?: undefined } | { fallback?: undef
|
||||
|
||||
type LineFallbacks = { carried: Set<number>; flavour: WrittenFlavour; openingLinkAsDirective: boolean }
|
||||
|
||||
// docs/decisions.md §A line reads back at most 32 times, then takes back every emphasis run.
|
||||
// docs/decisions.md §After 32 passes, a line carries or drops every em, strike and strong mark.
|
||||
export const readBackPasses = 32
|
||||
|
||||
export type PortableLineFallback = { kind: 'opening-link' } | { kind: 'unspellable-run'; runs: [MarkRun, ...MarkRun[]] }
|
||||
@@ -103,10 +103,14 @@ function emitLine(nodes: readonly AdfNode[], container: LineContainer, path: Con
|
||||
if (attempt.fallback === undefined) return success({ line: attempt.line, openingLinkAsDirective: fallbacks.openingLinkAsDirective, segments: attempt.segments })
|
||||
const taken = takeFallback(fallbacks, takenThisPass(attempt.fallback, perPass), path)
|
||||
if (!taken.ok) return taken
|
||||
if (pass === readBackPasses) for (const [index, node] of nodes.entries()) if (nodeMarks(node).some(isEmphasis)) fallbacks.carried.add(index)
|
||||
if (perPass === 'every' && pass === readBackPasses) carryEveryEmphasis(fallbacks, nodes)
|
||||
}
|
||||
}
|
||||
|
||||
function carryEveryEmphasis(fallbacks: LineFallbacks, nodes: readonly AdfNode[]): void {
|
||||
for (const [index, node] of nodes.entries()) if (nodeMarks(node).some(isEmphasis)) fallbacks.carried.add(index)
|
||||
}
|
||||
|
||||
function takenThisPass(fallback: Fallback, perPass: FallbacksPerPass): Fallback {
|
||||
return fallback === 'opening-link' || perPass === 'every' ? fallback : [fallback[0]]
|
||||
}
|
||||
|
||||
@@ -401,11 +401,11 @@ test('drops the mark of the run that cannot close where it sits, and keeps the l
|
||||
assert.equal(portable(paragraph(text('~', highlight), text('b'), text('日本', highlight))), '~b==日本==\n')
|
||||
})
|
||||
|
||||
test('drops every em, strong and strike mark in a line still misread after 32 passes', () => {
|
||||
test('drops every em, strike and strong mark, and keeps every other, in a line still misread after 32 passes', () => {
|
||||
const strike: AdfMark = { type: 'strike' }
|
||||
const repeat = [text(' ~~ ', em, strong), text(')', em, strike, strong), text('a'), text('.unun', strike, strong)]
|
||||
const line = Array.from({ length: 40 }, () => repeat).flat()
|
||||
assert.equal(portable(paragraph(...line)), `${Array.from({ length: 40 }, () => '~~ )a.unun').join(' ')}\n`)
|
||||
const line = [...Array.from({ length: 40 }, () => repeat).flat(), text(' '), text('b', { attrs: { href: 'u' }, type: 'link' }), text(' '), text('c', { type: 'backgroundColor' })]
|
||||
assert.equal(portable(paragraph(...line)), `${Array.from({ length: 40 }, () => '~~ )a.unun').join(' ')} [b](u) ==c==\n`)
|
||||
})
|
||||
|
||||
test('gives up every highlight run whose own delimiter cannot flank in one fallback, so a line of them re-emits once however many it holds', () => {
|
||||
|
||||
@@ -23,6 +23,7 @@ test('reduces a line alike dropping every run a pass may or the first alone', ()
|
||||
[[...[1, 2, 3].flatMap(() => [{ marks: [{ type: 'strike' }], text: 'b(', type: 'text' }, { marks: [{ type: 'em' }], text: 'b(', type: 'text' }]), { text: 'z', type: 'text' }], 'paragraph'],
|
||||
[[...[1, 2, 3].flatMap(() => [{ marks: [{ type: 'em' }, { type: 'strong' }], text: '(', type: 'text' }, { marks: [{ type: 'em' }], text: '(', type: 'text' }]), { text: 'a', type: 'text' }], 'paragraph'],
|
||||
[[...[1, 2, 3, 4, 5].flatMap(() => [{ marks: [{ type: 'em' }, { type: 'strong' }], text: '~** ', type: 'text' }, { marks: [{ type: 'em' }], text: '- _', type: 'text' }]), { text: 'r', type: 'text' }], 'table-cell'],
|
||||
[[{ marks: [{ type: 'em' }, { type: 'strike' }], text: '-', type: 'text' }, { text: 'a(.', type: 'text' }, ...[1, 2, 3, 4, 5].flatMap(() => [{ marks: [{ type: 'strong' }, { type: 'strike' }, { type: 'em' }], text: '0.-', type: 'text' }, { marks: [{ type: 'link' }], text: 'un', type: 'text' }, { marks: [{ type: 'strike' }, { type: 'em' }, { type: 'strong' }], text: '=', type: 'text' }]), { marks: [{ type: 'strong' }, { type: 'em' }], text: '[', type: 'text' }, { text: '`=', type: 'text' }], 'table-cell'],
|
||||
[[...[1, 2, 3].flatMap(() => [{ marks: [{ type: 'em' }, { type: 'strong' }], text: ' ~~ ', type: 'text' }, { marks: [{ type: 'em' }, { type: 'strike' }, { type: 'strong' }], text: ')', type: 'text' }]), { text: 'a', type: 'text' }, { marks: [{ type: 'strike' }, { type: 'strong' }], text: '.unun', type: 'text' }], 'heading'],
|
||||
],
|
||||
},
|
||||
|
||||
@@ -226,7 +226,7 @@ function spellableLine(leaves: AdfNode[], container: LineContainer, path: Conver
|
||||
if (fallback.value === undefined) return success(current)
|
||||
const fixed = withoutFallback(current, fallback.value, perPass)
|
||||
if (fixed === undefined) return failure('unsupported-node-shape', 'a portable line keeps a spelling that dropping a mark does not change', path)
|
||||
current = trimmedEdges(pass === readBackPasses ? fixed.map((leaf) => withMarks(leaf, nodeMarks(leaf).filter((mark) => !isEmphasis(mark)))) : fixed)
|
||||
current = trimmedEdges(perPass === 'every' && pass === readBackPasses ? withoutEmphasis(fixed) : fixed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +241,10 @@ function withoutFallback(leaves: readonly AdfNode[], fallback: PortableLineFallb
|
||||
return spans ? leaves.map((leaf, index) => (index > last ? leaf : withMarks(leaf, nodeMarks(leaf).filter((held) => held.type !== 'code')))) : undefined
|
||||
}
|
||||
|
||||
function withoutEmphasis(leaves: readonly AdfNode[]): AdfNode[] {
|
||||
return leaves.map((leaf) => withMarks(leaf, nodeMarks(leaf).filter((mark) => !isEmphasis(mark))))
|
||||
}
|
||||
|
||||
// The runs may nest, so a leaf drops the mark at every depth a run holds it.
|
||||
function withoutMarks(leaves: readonly AdfNode[], runs: readonly MarkRun[]): AdfNode[] {
|
||||
const depths = new Map<number, number[]>()
|
||||
|
||||
Reference in New Issue
Block a user