diff --git a/src/adf/document.ts b/src/adf/document.ts index 375a36f..42da38e 100644 --- a/src/adf/document.ts +++ b/src/adf/document.ts @@ -51,7 +51,7 @@ export function attributeNestingMessage(key: string, type: string): string { return `the ${key} attribute of ${type} nests deeper than the ${largestNesting} levels an attribute carries` } -export function carriesOnly(node: AdfNode, attributes: readonly string[]): boolean { +export function holdsOnlyAttributes(node: AdfNode, attributes: readonly string[]): boolean { if (nodeMarks(node).length > 0 || node.text !== undefined || emptyKeys(node).length > 0) return false return holdsOnly(nodeAttrs(node), attributes) } diff --git a/src/markdown/emit/adf-to-markdown.ts b/src/markdown/emit/adf-to-markdown.ts index 99331ec..5ab6977 100644 --- a/src/markdown/emit/adf-to-markdown.ts +++ b/src/markdown/emit/adf-to-markdown.ts @@ -1,7 +1,7 @@ import type { AdfDocument, AdfNode } from '../../adf/document.ts' import type { BlockNodeModel } from '../../adf/block-nodes.ts' import type { Flavour } from '../plain/conventions.ts' -import { adfDocumentFault, carriesOnly, isPlainText, nodeAttrs, nodeContent } from '../../adf/document.ts' +import { adfDocumentFault, holdsOnlyAttributes, isPlainText, nodeAttrs, nodeContent } from '../../adf/document.ts' import { alertMarker, foldedAlertMarker, leadingMarker, readAlertMarker, readTaskMarker, taskMarker } from '../plain/conventions.ts' import { blockDirectiveForm, documentSpelling, listBreakSpelling } from '../block-directive.ts' import { blockNodeModel, blockNodes } from '../../adf/block-nodes.ts' @@ -76,15 +76,15 @@ function joinBlocks(blocks: readonly PlacedBlock[], container: BlockContainer): } function separationBetween(previous: PlacedBlock, next: PlacedBlock, container: BlockContainer): string { - const plainPair = previous.spelling !== 'directive' && next.spelling !== 'directive' - if (plainPair && next.spelling === 'list') { + const bothCommonMark = previous.spelling !== 'directive' && next.spelling !== 'directive' + if (bothCommonMark && next.spelling === 'list') { if (previous.spelling === 'list' && previous.node.type === next.node.type) { const gap = container === 'directive' ? '\n' : '\n\n' return `${gap}${listBreakSpelling}${gap}` } if (container === 'list-item') return interruptsParagraph(next.node) ? '\n' : '\n\n' } - return container === 'directive' && !plainPair ? '\n' : '\n\n' + return container === 'directive' && !bothCommonMark ? '\n' : '\n\n' } function interruptsParagraph(node: AdfNode): boolean { @@ -243,7 +243,7 @@ function emitDirectiveBody(node: AdfNode, model: BlockNodeModel, opener: string, } function tryBlockquote(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing): Result | undefined { - if (!carriesOnly(node, [])) return undefined + if (!holdsOnlyAttributes(node, [])) return undefined const inner = walkBlocks(nodeContent(node), path, depth + 1, writing) if (!inner.ok) return inner const text = joinBlocks(inner.value.blocks, 'document') @@ -252,7 +252,7 @@ function tryBlockquote(node: AdfNode, path: ConvertErrorPath, depth: number, wri } function tryCodeBlock(node: AdfNode, path: ConvertErrorPath): Result | undefined { - if (!carriesOnly(node, ['language'])) return undefined + if (!holdsOnlyAttributes(node, ['language'])) return undefined const slot = languageSlot(nodeAttrs(node)['language']) if (slot.kind === 'attribute') return undefined const texts = fencedTexts(node, path) @@ -291,7 +291,7 @@ function fencedTexts(node: AdfNode, path: ConvertErrorPath): Result | undefined { - if (!carriesOnly(node, ['level'])) return undefined + if (!holdsOnlyAttributes(node, ['level'])) return undefined const level = nodeAttrs(node)['level'] if (typeof level !== 'number' || !Number.isInteger(level) || level < 1 || level > 6) return undefined const hashes = '#'.repeat(level) @@ -304,11 +304,11 @@ function tryHeading(node: AdfNode, path: ConvertErrorPath, flavour: Flavour): Re function tryList(node: AdfNode, path: ConvertErrorPath, depth: number, writing: Writing): Result | undefined { const ordered = node.type === 'orderedList' - if (!carriesOnly(node, ordered ? ['order'] : [])) return undefined + if (!holdsOnlyAttributes(node, ordered ? ['order'] : [])) return undefined const items = nodeContent(node) const start = listStart(node, items.length) if (start === undefined || items.length === 0) return undefined - if (items.some((item) => item.type !== 'listItem' || !carriesOnly(item, []))) return undefined + if (items.some((item) => item.type !== 'listItem' || !holdsOnlyAttributes(item, []))) return undefined const walked: WalkedItem[] = [] let headroom = Number.POSITIVE_INFINITY for (const [offset, item] of items.entries()) { @@ -356,13 +356,13 @@ function tryListItemLines(inner: string, marker: string): string | undefined { function tryParagraph(node: AdfNode, path: ConvertErrorPath, flavour: Flavour): Result | undefined { const content = nodeContent(node) - if (content.length === 0 || !carriesOnly(node, [])) return undefined + if (content.length === 0 || !holdsOnlyAttributes(node, [])) return undefined const line = emitInlineLine(content, 'paragraph', path, flavour) if (!line.ok) return line return success(commonMarkText(line.value)) } function tryRule(node: AdfNode): string | undefined { - if (!carriesOnly(node, []) || nodeContent(node).length > 0) return undefined + if (!holdsOnlyAttributes(node, []) || nodeContent(node).length > 0) return undefined return '---' } diff --git a/src/markdown/emit/image.ts b/src/markdown/emit/image.ts index be6828e..e9ca734 100644 --- a/src/markdown/emit/image.ts +++ b/src/markdown/emit/image.ts @@ -1,6 +1,6 @@ import type { AdfNode } from '../../adf/document.ts' import type { ConvertErrorPath } from '../../result.ts' -import { carriesOnly, nodeAttrs, nodeContent } from '../../adf/document.ts' +import { holdsOnlyAttributes, nodeAttrs, nodeContent } from '../../adf/document.ts' import { serializeCanonicalJson } from '../../canonical-json.ts' import { tryImageLine } from './inline-line.ts' @@ -16,8 +16,8 @@ export function tryImage(node: AdfNode, path: ConvertErrorPath): string | undefi function imageShape(node: AdfNode): { alt: string | undefined; url: string } | undefined { const content = nodeContent(node) const media = content[0] - if (!carriesOnly(node, ['layout']) || serializeCanonicalJson(nodeAttrs(node), 'compact') !== centeredMediaSingle) return undefined - if (media === undefined || content.length !== 1 || media.type !== 'media' || !carriesOnly(media, imageAttributes) || nodeContent(media).length > 0) return undefined + if (!holdsOnlyAttributes(node, ['layout']) || serializeCanonicalJson(nodeAttrs(node), 'compact') !== centeredMediaSingle) return undefined + if (media === undefined || content.length !== 1 || media.type !== 'media' || !holdsOnlyAttributes(media, imageAttributes) || nodeContent(media).length > 0) return undefined const attrs = nodeAttrs(media) const alt = attrs['alt'] const url = attrs['url'] diff --git a/src/markdown/emit/inline-line.ts b/src/markdown/emit/inline-line.ts index c4ce116..bcd3be4 100644 --- a/src/markdown/emit/inline-line.ts +++ b/src/markdown/emit/inline-line.ts @@ -114,7 +114,7 @@ function lineSegments(nodes: readonly AdfNode[], container: LineContainer, path: const emission = emitRun(nodes, 0, 0, context) if (!emission.ok) return emission if (emission.value.carry !== undefined) return emission - return success({ segments: carryStrippedWhitespace(emission.value.segments) }) + return success({ segments: spellEdgeWhitespace(emission.value.segments) }) } function attemptLine(segments: readonly InlineSegment[], container: LineContainer, path: ConvertErrorPath, flavour: Flavour): Result { @@ -137,32 +137,32 @@ function lineVerdict(segments: readonly InlineSegment[], container: LineContaine } // spec/flavour.md, Inline nodes. -function carryStrippedWhitespace(segments: readonly InlineSegment[]): InlineSegment[] { - const carried: InlineSegment[] = [] +function spellEdgeWhitespace(segments: readonly InlineSegment[]): InlineSegment[] { + const spelled: InlineSegment[] = [] for (const [index, segment] of segments.entries()) { const previous = segments[index - 1] const next = segments[index + 1] const leading = previous === undefined || previous.text.includes('\n') const trailing = next === undefined || next.text.includes('\n') - carried.push(...carryEdges(segment, leading, trailing)) + spelled.push(...edgeWhitespaceSegments(segment, leading, trailing)) } - return carried + return spelled } -function carryEdges(segment: InlineSegment, leading: boolean, trailing: boolean): InlineSegment[] { +function edgeWhitespaceSegments(segment: InlineSegment, leading: boolean, trailing: boolean): InlineSegment[] { if (segment.escaping !== 'backslash' && segment.escaping !== 'bracketed') return [segment] const head = leading ? (/^[ \t]+/.exec(segment.text)?.[0] ?? '') : '' const body = segment.text.slice(head.length) const middle = trailing ? trimTrailingSpace(body) : body const tail = body.slice(middle.length) const edges: InlineSegment[] = [] - if (head !== '') edges.push(carriedText(head)) + if (head !== '') edges.push(textDirectiveSegment(head)) if (middle !== '') edges.push({ escaping: segment.escaping, text: middle }) - if (tail !== '') edges.push(carriedText(tail)) + if (tail !== '') edges.push(textDirectiveSegment(tail)) return edges } -function carriedText(text: string): InlineSegment { +function textDirectiveSegment(text: string): InlineSegment { return syntax(spellTextDirective(text)) } @@ -275,7 +275,7 @@ function emitText(node: AdfNode, context: InlineContext, index: number, path: Co if (holdsNullCharacter(node.text)) return failure('unspellable-character', 'a text node holds a null character CommonMark replaces', path) const escaping: InlineEscaping = context.bracketed ? 'bracketed' : 'backslash' const parts = node.text.split(/(\n+)/).filter((part) => part !== '') - return success({ segments: parts.map((part) => (part.startsWith('\n') ? carriedText(part) : { escaping, text: part })) }) + return success({ segments: parts.map((part) => (part.startsWith('\n') ? textDirectiveSegment(part) : { escaping, text: part })) }) } function emitMarkedRun(nodes: readonly AdfNode[], mark: AdfMark, depth: number, index: number, context: InlineContext): Result { @@ -300,11 +300,11 @@ function emitEmphasis(nodes: readonly AdfNode[], spelling: string, depth: number const inner = emitRun(nodes, depth + 1, range.first, context) if (!inner.ok) return inner if (inner.value.carry !== undefined) return inner - const carried = carryStrippedWhitespace(inner.value.segments) + const spelled = spellEdgeWhitespace(inner.value.segments) return success({ segments: [ { emphasis: 'open', escaping: 'none', nodes: { ...range, depth }, text: spelling }, - ...carried, + ...spelled, { emphasis: 'close', escaping: 'none', nodes: { ...range, depth }, text: spelling }, ], }) diff --git a/src/markdown/emit/pipe-table.ts b/src/markdown/emit/pipe-table.ts index 89ab454..0998cf2 100644 --- a/src/markdown/emit/pipe-table.ts +++ b/src/markdown/emit/pipe-table.ts @@ -1,7 +1,7 @@ import type { AdfNode } from '../../adf/document.ts' import type { ConvertErrorPath } from '../../result.ts' import type { Flavour } from '../plain/conventions.ts' -import { carriesOnly, nodeContent } from '../../adf/document.ts' +import { holdsOnlyAttributes, nodeContent } from '../../adf/document.ts' import { spellPipeDelimiter, spellPipeRow } from '../pipe-table-syntax.ts' import { tryPipeCell } from './inline-line.ts' @@ -26,16 +26,16 @@ export function tryPipeTable(node: AdfNode, path: ConvertErrorPath, flavour: Fla function pipeRows(node: AdfNode): AdfNode[][] | undefined { const rows = nodeContent(node) const columns = rows[0] === undefined ? 0 : nodeContent(rows[0]).length - if (!carriesOnly(node, []) || columns === 0) return undefined + if (!holdsOnlyAttributes(node, []) || columns === 0) return undefined const grid: AdfNode[][] = [] for (const [index, row] of rows.entries()) { const cells = nodeContent(row) - if (row.type !== 'tableRow' || !carriesOnly(row, []) || cells.length !== columns) return undefined + if (row.type !== 'tableRow' || !holdsOnlyAttributes(row, []) || cells.length !== columns) return undefined const wanted = index === 0 ? 'tableHeader' : 'tableCell' const paragraphs: AdfNode[] = [] for (const cell of cells) { const paragraph = plainParagraph(cell) - if (paragraph === undefined || cell.type !== wanted || !carriesOnly(cell, [])) return undefined + if (paragraph === undefined || cell.type !== wanted || !holdsOnlyAttributes(cell, [])) return undefined paragraphs.push(paragraph) } grid.push(paragraphs) @@ -46,6 +46,6 @@ function pipeRows(node: AdfNode): AdfNode[][] | undefined { function plainParagraph(cell: AdfNode): AdfNode | undefined { const content = nodeContent(cell) const paragraph = content[0] - if (paragraph === undefined || content.length !== 1 || paragraph.type !== 'paragraph' || !carriesOnly(paragraph, [])) return undefined + if (paragraph === undefined || content.length !== 1 || paragraph.type !== 'paragraph' || !holdsOnlyAttributes(paragraph, [])) return undefined return paragraph }