mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 11:48: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:
@@ -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', () => {
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
`Bar = 0.1`
|
||||
|
||||
`Next ID = 162`
|
||||
`Next ID = 163`
|
||||
|
||||
## Items
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
| 75 | 0.4.0 | | | **Make the generated-markdown property write block carries as `adf:` fences, canonical and hostile.** | 1 | 1 | 1 | 1 | 2 | code health by 2 | 17.4 |
|
||||
| 154 | 0.4.0 | | | **Pair highlights over the inline reader's pieces by their offsets before building nodes, so no placeholder text node stands in for a delimiter.** | 2 | 1 | 2 | 3 | 4 | code health by 4 | 17.4 |
|
||||
| 155 | 0.4.0 | | | **Have `closeBracket` return the link, image or literal bracket it builds, and apply it to the scan in one caller.** | 2 | 1 | 2 | 3 | 4 | code health by 4 | 17.4 |
|
||||
| 162 | 0.4.0 | | | **Pin each rule of the emphasis sweep with a hand-worked line, so every mutant that lets a pair fall where the one-run-per-pass reference keeps it fails a test.** | 2 | 1 | 1 | 2 | 4 | code health by 4 | 17.4 |
|
||||
| 45 | 0.4.0 | | defect | **Replace `isAdfDocument` with a reader returning `Result<AdfDocument>`.** | 2 | 1 | 3 | 4 | 3 | 1 by 2 | 13.1 |
|
||||
| 68 | 0.4.0 | | defect | **Carry a non-text inline node holding `content` or `text`, a block holding `text`, and a leaf block holding `content`.** | 2 | 1 | 2 | 3 | 3 | 1 by 2 | 13.1 |
|
||||
| 127 | 0.4.0 | | principle | **Hold what `entity-references.test.ts`, `json-text.test.ts` and `result.test.ts` check by asserting outcomes, so none reads source text.** | 2 | 2 | 1 | 3 | 3 | code health by 2 | 13.1 |
|
||||
@@ -180,6 +181,20 @@ A comprehension seat found `src/markdown/inline-content.ts` `closeBracket` hard:
|
||||
`popBracket`, which write `scan.pieces`, `scan.pending` and the link openers. Make that place
|
||||
easier to read.
|
||||
|
||||
### 162. Pin each rule of the emphasis sweep with a hand-worked line, so every mutant that lets a pair fall where the one-run-per-pass reference keeps it fails a test.
|
||||
|
||||
About 20 mutants of `src/markdown/emit/unspellable-runs.ts` survive the gate. Each lets a pair fall
|
||||
with the first where the one-run-per-pass reference keeps it, so the line comes out other than the
|
||||
reference writes it, against `docs/decisions.md` §With the first misread emphasis run,
|
||||
`unspellableRuns` returns only runs that, taken back early, leave the line as the one-run-per-pass
|
||||
reference writes it. They sit in `endsAlone`'s mark check, `holdsAlike`'s `held >= 0`,
|
||||
`mayBeWrittenWith`, the `'moves'` give-ups in `runEdge`, `wordBeside` and `pairStanding`,
|
||||
`pairSpellings`' carry-split rule and `holdsPairBefore`'s `<=`. The properties beside
|
||||
`inline-line.ts` and `inline-reduction.ts` miss them; the `runEdge` one survived 20000 runs on a
|
||||
random seed. Write one line per rule of that entry, its expected markdown worked from the entry,
|
||||
never from the code, then run Stryker on the file. A mutant that only takes fewer runs back costs a
|
||||
pass and may survive.
|
||||
|
||||
### 45. Replace `isAdfDocument` with a reader returning `Result<AdfDocument>`.
|
||||
|
||||
Lands with item 77: the reader returns item 77's copy. Goal 1 has every call return a result; the
|
||||
|
||||
Reference in New Issue
Block a user