Answer the architecture pass: the list a blank line split, and the slot the seam names
CI / gate (push) Successful in 8s
CI / gate (push) Successful in 8s
This commit is contained in:
@@ -3,10 +3,12 @@ import { carryName } from './opaque-carry.ts'
|
||||
import { holdsControlCharacter } from './commonmark-grammar.ts'
|
||||
import { holdsEntityReference } from './entity-references.ts'
|
||||
|
||||
// spec/flavour.md, The CommonMark blocks: the info string the language rides, `undefined` where the attribute carries it.
|
||||
export function fenceInfo(language: JsonValue | undefined): string | undefined {
|
||||
if (language === undefined) return ''
|
||||
if (typeof language !== 'string' || language === '' || language === carryName) return undefined
|
||||
if (/[`\\]/.test(language) || holdsControlCharacter(language) || language !== language.trim() || holdsEntityReference(language)) return undefined
|
||||
return language
|
||||
export type LanguageSlot = { info: string; kind: 'fence' } | { kind: 'attribute' } | { kind: 'none' }
|
||||
|
||||
// spec/flavour.md, The CommonMark blocks: the one slot a codeBlock's language rides, both directions.
|
||||
export function languageSlot(language: JsonValue | undefined): LanguageSlot {
|
||||
if (language === undefined) return { kind: 'none' }
|
||||
if (typeof language !== 'string' || language === '' || language === carryName) return { kind: 'attribute' }
|
||||
if (/[`\\]/.test(language) || holdsControlCharacter(language) || language !== language.trim() || holdsEntityReference(language)) return { kind: 'attribute' }
|
||||
return { info: language, kind: 'fence' }
|
||||
}
|
||||
|
||||
@@ -171,13 +171,13 @@ export function isUnicodeWhitespace(character: string): boolean {
|
||||
}
|
||||
|
||||
// `start` is the list's first number, `undefined` for a bullet.
|
||||
export function listMarker(line: string): { delimiter: string; start: number | undefined; width: number } | undefined {
|
||||
export function listMarker(line: string): { start: number | undefined; width: number } | undefined {
|
||||
const ordered = orderedListOpener.exec(line)
|
||||
if (ordered !== null) {
|
||||
const digits = ordered[1] ?? ''
|
||||
return { delimiter: ordered[2] ?? '', start: Number(digits), width: digits.length + 1 }
|
||||
return { start: Number(digits), width: digits.length + 1 }
|
||||
}
|
||||
return bulletListOpener.test(line) ? { delimiter: line.charAt(0), start: undefined, width: 1 } : undefined
|
||||
return bulletListOpener.test(line) ? { start: undefined, width: 1 } : undefined
|
||||
}
|
||||
|
||||
export function markerInterruptsParagraph(start: number | undefined, empty: boolean): boolean {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { carriesOnly, isAdfDocument } from '../../adf/document.ts'
|
||||
import { emitInlineLine } from './inline-line.ts'
|
||||
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { fencedCodeBlock } from '../backtick-runs.ts'
|
||||
import { fenceInfo } from '../code-language.ts'
|
||||
import { languageSlot } from '../code-language.ts'
|
||||
import { holdsNullCharacter, isThematicBreak, markerInterruptsParagraph } from '../commonmark-grammar.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { spellDirectiveHeader } from './block-directive-spelling.ts'
|
||||
@@ -147,20 +147,21 @@ function emitBlockquote(node: AdfNode, path: ConvertErrorPath, depth: number): R
|
||||
|
||||
function emitCodeBlock(node: AdfNode, path: ConvertErrorPath): Result<EmittedBlock> | undefined {
|
||||
if (!carriesOnly(node, ['language'])) return undefined
|
||||
const info = fenceInfo(node.attrs?.['language'])
|
||||
if (info === undefined) return undefined
|
||||
const slot = languageSlot(node.attrs?.['language'])
|
||||
if (slot.kind === 'attribute') return undefined
|
||||
const text = codeBlockText(node, path)
|
||||
if (!text.ok) return text
|
||||
return success(commonMarkText(fencedCodeBlock(info, text.value)))
|
||||
return success(commonMarkText(fencedCodeBlock(slot.kind === 'fence' ? slot.info : '', text.value)))
|
||||
}
|
||||
|
||||
function emitCodeDirective(node: AdfNode, directive: BlockDirective, path: ConvertErrorPath): Result<EmittedBlock> {
|
||||
const info = fenceInfo(node.attrs?.['language'])
|
||||
const header = spellDirectiveHeader(node, directive, info === undefined ? [] : ['language'])
|
||||
const slot = languageSlot(node.attrs?.['language'])
|
||||
const header = spellDirectiveHeader(node, directive, slot.kind === 'attribute' ? [] : ['language'])
|
||||
if (header === undefined) return commonMarkLine(carriedBlock(node, path))
|
||||
const text = codeBlockText(node, path)
|
||||
if (!text.ok) return text
|
||||
return success({ fenceColons: 3, spelling: 'directive', text: `:::${header}\n${fencedCodeBlock(info ?? '', text.value)}\n:::` })
|
||||
const info = slot.kind === 'fence' ? slot.info : ''
|
||||
return success({ fenceColons: 3, spelling: 'directive', text: `:::${header}\n${fencedCodeBlock(info, text.value)}\n:::` })
|
||||
}
|
||||
|
||||
function codeBlockText(node: AdfNode, path: ConvertErrorPath): Result<string> {
|
||||
|
||||
@@ -176,9 +176,19 @@ function openContainer(walk: Walk, start: ContainerStart): void {
|
||||
walk.stack.push(blockquote)
|
||||
return
|
||||
}
|
||||
if (start.fresh) currentBlocks(walk).push(start.list)
|
||||
start.list.items.push(blocks)
|
||||
walk.stack.push({ blocks, indentation: start.indentation, kind: 'item', list: start.list })
|
||||
const list = openedList(walk, start)
|
||||
list.items.push(blocks)
|
||||
walk.stack.push({ blocks, indentation: start.indentation, kind: 'item', list })
|
||||
}
|
||||
|
||||
// Two lists of a kind never sit adjacent: one `- ` spelling reads them back as one (spec/flavour.md).
|
||||
function openedList(walk: Walk, start: Extract<ContainerStart, { kind: 'item' }>): ListBlock {
|
||||
if (!start.fresh) return start.list
|
||||
const blocks = currentBlocks(walk)
|
||||
const previous = blocks.at(-1)
|
||||
if ((previous?.kind === 'bulletList' || previous?.kind === 'orderedList') && previous.kind === start.list.kind) return previous
|
||||
blocks.push(start.list)
|
||||
return start.list
|
||||
}
|
||||
|
||||
function closeContainers(walk: Walk, depth: number): void {
|
||||
|
||||
@@ -390,6 +390,8 @@ test('reads a bullet list, the marker width setting the continuation', () => {
|
||||
assert.deepEqual(content(markdownToAdf('- Code.\n')), [bulletList(item({ content: [text('Code.')], type: 'codeBlock' }))])
|
||||
assert.deepEqual(content(markdownToAdf('- a\n* b\n')), [bulletList(item(paragraph('a')), item(paragraph('b')))])
|
||||
assert.deepEqual(content(markdownToAdf('- a\n\n+ b\n')), [bulletList(item(paragraph('a')), item(paragraph('b')))])
|
||||
assert.deepEqual(content(markdownToAdf('- a\n-\n\n- c\n')), [bulletList(item(paragraph('a')), item(), item(paragraph('c')))])
|
||||
assert.deepEqual(content(markdownToAdf('- a\n1. b\n')), [bulletList(item(paragraph('a'))), orderedList(1, item(paragraph('b')))])
|
||||
assert.deepEqual(content(markdownToAdf('-\n\n Part.\n')), [bulletList(item()), paragraph('Part.')])
|
||||
})
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { LinkDefinitions } from './inline-content.ts'
|
||||
import { carryName } from '../opaque-carry.ts'
|
||||
import { commonMarkSpelling } from '../emit/adf-to-markdown.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { fenceInfo } from '../code-language.ts'
|
||||
import { languageSlot } from '../code-language.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { parseBlocks } from './blocks.ts'
|
||||
import { parseInlineContent } from './inline-content.ts'
|
||||
@@ -85,8 +85,8 @@ function codeDirectiveNode(node: AdfNode, blocks: readonly Block[], path: Conver
|
||||
if (only?.kind !== 'code') return failure('unsupported-node-shape', `${node.type} takes one fenced code block as its body`, path)
|
||||
const attribute = node.attrs?.['language']
|
||||
const fromFence = only.language !== ''
|
||||
const info = fenceInfo(fromFence ? only.language : attribute)
|
||||
if ((info !== undefined && info !== '') !== fromFence || (fromFence && attribute !== undefined)) {
|
||||
const slot = languageSlot(fromFence ? only.language : attribute)
|
||||
if ((slot.kind === 'fence') !== fromFence || (fromFence && attribute !== undefined)) {
|
||||
return failure('unsupported-node-shape', `${node.type} spells its language in the fence info string, or in the attribute where no info string carries it back`, path)
|
||||
}
|
||||
const spelled = fromFence ? { ...node, attrs: { ...node.attrs, language: only.language } } : node
|
||||
|
||||
Reference in New Issue
Block a user