3
0
mirror of https://github.com/larvit/adf-codec.git synced 2026-11-13 04:08:29 +01:00

Answer stability round 1: 1 🔴, 3 🟡 (0 misread); start an empty line's walk at its innermost quote, pin two guards, file item 162

This commit is contained in:
2026-10-31 01:18:45 +01:00
committed by Lillem4n
parent 085ad7f508
commit 088494511d
5 changed files with 52 additions and 11 deletions
@@ -322,6 +322,10 @@ test('carries every inline node of a line that only the carry spells', () => {
markdown(adfToLosslessMarkdown(document(paragraph(odd, { text: ' ', type: 'text' }, { marks: [{ type: 'link' }], text: 'x', type: 'text' }, { text: ' ', type: 'text' }, odd)))),
`${carriedOdd} ${unlinked} ${carriedOdd}\n`,
)
assert.equal(
markdown(adfToLosslessMarkdown(document(paragraph({ text: 'a', type: 'text' }, { attrs: { foo: 1 }, type: 'hardBreak' }, { text: 'b', type: 'text' })))),
'a!adf:carry{json="{\\"attrs\\":{\\"foo\\":1},\\"type\\":\\"hardBreak\\"}"}b\n',
)
})
test('carries an inline node holding a mark Atlassian\'s schema withholds from it', () => {
+20
View File
@@ -31,3 +31,23 @@ test('measures a block text as the text it writes', () => {
propertyRuns(2000),
)
})
test('writes an empty line without reading the started prefixes inside its innermost quote', () => {
const reads = (emptyLines: number): number => {
let count = 0
let body = blockText(`a${'\n'.repeat(emptyLines)}`)
for (let depth = 0; depth < 3; depth += 1) {
body = new Proxy(prefixedText('- ', ' ', body), {
get(target, key, receiver) {
count += 1
return Reflect.get(target, key, receiver)
},
})
}
const text = quotedText(body)
count = 0
writtenText(text)
return count
}
assert.equal(reads(1000), reads(1))
})
+11 -9
View File
@@ -8,9 +8,10 @@ type Lines =
| { kind: 'joined'; parts: readonly BlockText[] }
| { kind: 'text'; lines: readonly string[] }
type Prefix = Lines & { kind: 'prefixed' | 'quoted' }
// The written first line. The flags describe `text` so no caller reads it: reading a concatenated string copies it whole, once per nesting level.
type OpenPrefix = { innermostQuote: number; prefix: Prefix }
// The written first line. A caller tests `character` or a flag before it reads the characters of `text`: reading a concatenated string copies it whole, once per nesting level.
type FirstLine = Readonly<{ blank: boolean; character: string; ruleCharacters: boolean; text: string }>
// How many written lines after the first are blank, empty, or hold text, blank ones included.
// Counts of the lines after the first: `blank` ones hold only spaces or tabs, `empty` ones nothing, and `written` ones at least one character, `blank` ones included.
type LaterLines = Readonly<{ blank: number; empty: number; written: number }>
export const emptyLine = blockText('')
@@ -70,7 +71,7 @@ export function opening(text: BlockText): string {
export function writtenText(text: BlockText): string {
const lines: string[] = []
// The open prefixes, outermost first. A line starts every open prefix, so the unstarted ones are the innermost.
const open: Prefix[] = []
const open: OpenPrefix[] = []
let started = 0
// Each pending text with how many prefixes stay open around it.
const pending: { text: BlockText; within: number }[] = [{ text, within: 0 }]
@@ -85,18 +86,19 @@ export function writtenText(text: BlockText): string {
}
} else if (current.kind === 'joined') for (const part of current.parts.slice().reverse()) pending.push({ text: part, within: open.length })
else {
open.push(current)
open.push({ innermostQuote: current.kind === 'quoted' ? open.length : (open.at(-1)?.innermostQuote ?? -1), prefix: current })
pending.push({ text: current.body, within: open.length })
}
}
return lines.join('\n')
}
// Innermost first, since an empty line takes no indent and a quote turns it into `>`.
function prefixedLine(line: string, open: readonly Prefix[], started: number): string {
return open.reduceRight((written, prefix, depth) => {
// Innermost first: whether a line is empty when an outer prefix meets it depends on the prefixes inside.
function prefixedLine(line: string, open: readonly OpenPrefix[], started: number): string {
// An empty line takes no indent, so where every prefix has started its walk begins at the innermost quote.
const walked = line === '' && started === open.length ? open.slice(0, (open.at(-1)?.innermostQuote ?? -1) + 1) : open
return walked.reduceRight((written, { prefix }, depth) => {
if (prefix.kind === 'quoted') return written === '' ? '>' : `> ${written}`
if (depth >= started) return `${prefix.head}${written}`
return written === '' ? '' : `${prefix.indent}${written}`
return depth >= started ? `${prefix.head}${written}` : `${prefix.indent}${written}`
}, line)
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { propertyRuns } from '../../conformance/property-harness.ts'
test('reads a task or alert marker only where the text opens with the marker opening', () => {
fc.assert(
fc.property(fc.string({ maxLength: 8, unit: fc.constantFrom(' ', '!', '+', '-', 'N', 'x', '[', '\\', ']') }), (text) => {
fc.property(fc.array(fc.constantFrom(' ', '!', '+', '-', 'N', 'x', '[', '\\', ']', '[ ] ', '[x]', '[!NOTE]'), { maxLength: 4 }).map((pieces) => pieces.join('')), (text) => {
if (readTaskMarker(text) !== undefined || readAlertMarker(text) !== undefined) assert.equal(text.charAt(0), markerOpening)
}),
propertyRuns(2000),