Answer the architecture pass: one fault vocabulary, and the guards pinned where they compose
CI / gate (push) Successful in 5s
CI / gate (push) Successful in 5s
This commit is contained in:
@@ -9,13 +9,13 @@ function attributes(...pairs: [string, string][]): ReadonlyMap<string, string> {
|
||||
return new Map(pairs)
|
||||
}
|
||||
|
||||
function header(colons: number, name: string, argument?: string, ...pairs: [string, string][]): DirectiveLine {
|
||||
return { argument, attributes: attributes(...pairs), colons, kind: 'header', name }
|
||||
function header(colons: number, name: string, argument?: string, ...pairs: [string, string][]): { value: DirectiveLine } {
|
||||
return { value: { argument, attributes: attributes(...pairs), colons, kind: 'header', name } }
|
||||
}
|
||||
|
||||
function fault(line: string): string {
|
||||
const read = readDirectiveLine(line)
|
||||
return read?.kind === 'fault' ? read.fault.message : `read ${JSON.stringify(read)}`
|
||||
return read?.fault === undefined ? `read ${JSON.stringify(read)}` : read.fault.message
|
||||
}
|
||||
|
||||
function inline(text: string): unknown {
|
||||
@@ -37,9 +37,9 @@ test('claims a colon-run line only where a name or nothing follows the colons',
|
||||
})
|
||||
|
||||
test('reads a bare colon run as the fence that closes a container', () => {
|
||||
assert.deepEqual(readDirectiveLine(':::'), { colons: 3, kind: 'closing' })
|
||||
assert.deepEqual(readDirectiveLine('::'), { colons: 2, kind: 'closing' })
|
||||
assert.deepEqual(readDirectiveLine(':::::: \t'), { colons: 6, kind: 'closing' })
|
||||
assert.deepEqual(readDirectiveLine(':::'), { value: { colons: 3, kind: 'closing' } })
|
||||
assert.deepEqual(readDirectiveLine('::'), { value: { colons: 2, kind: 'closing' } })
|
||||
assert.deepEqual(readDirectiveLine(':::::: \t'), { value: { colons: 6, kind: 'closing' } })
|
||||
})
|
||||
|
||||
test('reads the leaf and container forms, their argument and their attributes', () => {
|
||||
|
||||
@@ -12,11 +12,10 @@ export type DirectiveAttributes = ReadonlyMap<string, string>
|
||||
export type DirectiveLine =
|
||||
| { argument: string | undefined; attributes: DirectiveAttributes; colons: number; kind: 'header'; name: string }
|
||||
| { colons: number; kind: 'closing' }
|
||||
| { fault: ConvertFault; kind: 'fault' }
|
||||
|
||||
export type InlineDirective = { attributes: DirectiveAttributes; content: string | undefined; length: number; name: string }
|
||||
export type DirectiveSpan = { attributes: DirectiveAttributes; content: string | undefined; length: number; name: string }
|
||||
|
||||
type Read<T> = { fault: ConvertFault; value?: undefined } | { fault?: undefined; value: T }
|
||||
export type Read<T> = { fault: ConvertFault; value?: undefined } | { fault?: undefined; value: T }
|
||||
|
||||
type Attributes = { attributes: DirectiveAttributes; length: number }
|
||||
|
||||
@@ -50,17 +49,17 @@ export function opensInlineDirective(text: string, index: number): boolean {
|
||||
return inlineDirectiveName(text, index) !== undefined
|
||||
}
|
||||
|
||||
export function readDirectiveLine(line: string): DirectiveLine | undefined {
|
||||
export function readDirectiveLine(line: string): Read<DirectiveLine> | undefined {
|
||||
if (!claimsDirectiveLine(line)) return undefined
|
||||
const colons = runLength(line, 0)
|
||||
const rest = line.slice(colons)
|
||||
if (lineEnd.test(rest)) return { colons, kind: 'closing' }
|
||||
if (lineEnd.test(rest)) return { value: { colons, kind: 'closing' } }
|
||||
const header = readDirectiveHeader(rest)
|
||||
if (header.fault !== undefined) return { fault: header.fault, kind: 'fault' }
|
||||
return { argument: header.value.argument, attributes: header.value.attributes, colons, kind: 'header', name: header.value.name }
|
||||
if (header.fault !== undefined) return { fault: header.fault }
|
||||
return { value: { argument: header.value.argument, attributes: header.value.attributes, colons, kind: 'header', name: header.value.name } }
|
||||
}
|
||||
|
||||
export function readInlineDirective(text: string, index: number): Read<InlineDirective> | undefined {
|
||||
export function readInlineDirective(text: string, index: number): Read<DirectiveSpan> | undefined {
|
||||
return readNestedDirective(text, index, 1)
|
||||
}
|
||||
|
||||
@@ -130,7 +129,7 @@ function readDirectiveHeader(rest: string): Read<{ argument: string | undefined;
|
||||
return { value: { argument, attributes, name } }
|
||||
}
|
||||
|
||||
function readNestedDirective(text: string, index: number, depth: number): Read<InlineDirective> | undefined {
|
||||
function readNestedDirective(text: string, index: number, depth: number): Read<DirectiveSpan> | undefined {
|
||||
const name = inlineDirectiveName(text, index)
|
||||
if (name === undefined) return undefined
|
||||
if (depth > largestNesting) {
|
||||
|
||||
@@ -87,6 +87,16 @@ test('holds a directive container open until the fence that closes it', () => {
|
||||
assert.deepEqual(faults(':::panel info\n> Part.\n> :::\n'), [])
|
||||
assert.deepEqual(faults('::::panel info\n- :::expand\n Part.\n :::\n::::\n'), [])
|
||||
assert.deepEqual(faults(':::panel info\n```\n:::\n```\n:::\n'), [])
|
||||
assert.deepEqual(parseBlocks(':::panel info {panelColor="#ff0000"}\nPart.\n:::\n').blocks, [
|
||||
{
|
||||
argument: 'info',
|
||||
attributes: new Map([['panelColor', '#ff0000']]),
|
||||
blocks: [{ kind: 'paragraph', text: 'Part.' }],
|
||||
kind: 'directive',
|
||||
name: 'panel',
|
||||
},
|
||||
])
|
||||
assert.deepEqual(parseBlocks('::rule\n').blocks, [{ argument: undefined, attributes: new Map(), blocks: undefined, kind: 'directive', name: 'rule' }])
|
||||
})
|
||||
|
||||
test('names the directive fence a container does not sit longer than', () => {
|
||||
|
||||
@@ -181,10 +181,15 @@ function openContainer(walk: Walk, start: ContainerStart): void {
|
||||
|
||||
function closeContainers(walk: Walk, depth: number): void {
|
||||
closeLeaf(walk)
|
||||
for (const container of walk.stack.splice(depth)) {
|
||||
for (const container of walk.stack.slice(depth)) {
|
||||
if (container.kind !== 'directive') continue
|
||||
container.parent[container.index] = { fault: malformedDirective(`a container fenced with ${container.colons} colons is unclosed`), kind: 'fault' }
|
||||
}
|
||||
dropContainers(walk, depth)
|
||||
}
|
||||
|
||||
function dropContainers(walk: Walk, depth: number): void {
|
||||
walk.stack.length = depth
|
||||
}
|
||||
|
||||
function openDirective(walk: Walk, directive: Extract<DirectiveLine, { kind: 'header' }>): void {
|
||||
@@ -200,12 +205,7 @@ function openDirective(walk: Walk, directive: Extract<DirectiveLine, { kind: 'he
|
||||
if (block.blocks !== undefined) walk.stack.push({ blocks: block.blocks, colons: directive.colons, index: parent.length - 1, kind: 'directive', parent })
|
||||
}
|
||||
|
||||
function readDirective(walk: Walk, directive: DirectiveLine): void {
|
||||
closeLeaf(walk)
|
||||
if (directive.kind === 'fault') {
|
||||
pushFault(walk, directive.fault)
|
||||
return
|
||||
}
|
||||
function applyDirectiveLine(walk: Walk, directive: DirectiveLine): void {
|
||||
const enclosing = innermostDirective(walk)
|
||||
if (directive.kind === 'closing') {
|
||||
closeDirective(walk, directive.colons, enclosing)
|
||||
@@ -227,7 +227,7 @@ function closeDirective(walk: Walk, colons: number, enclosing: { container: Open
|
||||
pushFault(walk, malformedDirective(`a closing fence is shorter than the ${enclosing.container.colons} colons it would close`))
|
||||
return
|
||||
}
|
||||
walk.stack.length = enclosing.depth
|
||||
dropContainers(walk, enclosing.depth)
|
||||
}
|
||||
|
||||
function innermostDirective(walk: Walk): { container: OpenDirective; depth: number } | undefined {
|
||||
@@ -292,7 +292,9 @@ function openLeaf(walk: Walk, line: Line): void {
|
||||
const opener = removeColumns(line, largestOpenerIndentation).text
|
||||
const directive = readDirectiveLine(opener)
|
||||
if (directive !== undefined) {
|
||||
readDirective(walk, directive)
|
||||
closeLeaf(walk)
|
||||
if (directive.fault === undefined) applyDirectiveLine(walk, directive.value)
|
||||
else pushFault(walk, directive.fault)
|
||||
return
|
||||
}
|
||||
if (claimsPipeLine(opener)) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { LinkDefinition } from '../link-syntax.ts'
|
||||
import { backslashEscape, decodeTextEscapes, inlineHtmlConstruct, readBracketedAutolink, readEmailAutolink } from '../commonmark-grammar.ts'
|
||||
import { backtickRun, closingBacktickRun } from '../backtick-runs.ts'
|
||||
import { delimiterFlags, matchEmphasis, runLength } from '../emphasis-matching.ts'
|
||||
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { mergeAdjacentText } from '../../adf/editor-normal.ts'
|
||||
import { normalizeLabel, readInlineTarget, readLabel } from '../link-syntax.ts'
|
||||
import { readInlineDirective, unknownDirectiveFault } from '../directive-syntax.ts'
|
||||
@@ -144,8 +144,7 @@ function readDirective(scan: Scan, index: number): Result<number> {
|
||||
scan.pending += ':'
|
||||
return success(index + 1)
|
||||
}
|
||||
const fault = directive.fault ?? unknownDirectiveFault(directive.value.name)
|
||||
return failure(fault.code, fault.message, scan.path)
|
||||
return faulted(directive.fault ?? unknownDirectiveFault(directive.value.name), scan.path)
|
||||
}
|
||||
|
||||
function flush(scan: Scan, strip: boolean): void {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AdfDocument, AdfNode } from '../../adf/document.ts'
|
||||
import type { Block } from './blocks.ts'
|
||||
import type { LinkDefinitions } from './inline-content.ts'
|
||||
import { failure, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
|
||||
import { largestNesting } from '../../nesting.ts'
|
||||
import { parseBlocks } from './blocks.ts'
|
||||
import { parseInlineContent } from './inline-content.ts'
|
||||
@@ -33,12 +33,10 @@ function blockNode(block: Block, definitions: LinkDefinitions, path: ConvertErro
|
||||
return listNode({ type: 'bulletList' }, block.items, definitions, path, depth)
|
||||
case 'code':
|
||||
return success(codeBlockNode(block.language, block.text))
|
||||
case 'directive': {
|
||||
const fault = unknownDirectiveFault(block.name)
|
||||
return failure(fault.code, fault.message, path)
|
||||
}
|
||||
case 'directive':
|
||||
return faulted(unknownDirectiveFault(block.name), path)
|
||||
case 'fault':
|
||||
return failure(block.fault.code, block.fault.message, path)
|
||||
return faulted(block.fault, path)
|
||||
case 'heading':
|
||||
return contentNode({ attrs: { level: block.level }, type: 'heading' }, block.text, definitions, path)
|
||||
case 'html':
|
||||
|
||||
+7
-2
@@ -18,12 +18,13 @@ export type ConvertErrorCode =
|
||||
|
||||
export type ConvertErrorPath = readonly (number | string)[]
|
||||
|
||||
export type ConvertFault = {
|
||||
export type ConvertError = {
|
||||
code: ConvertErrorCode
|
||||
message: string
|
||||
path: ConvertErrorPath
|
||||
}
|
||||
|
||||
export type ConvertError = ConvertFault & { path: ConvertErrorPath }
|
||||
export type ConvertFault = Omit<ConvertError, 'path'>
|
||||
|
||||
export type Result<T> = { error: ConvertError; ok: false } | { ok: true; value: T }
|
||||
|
||||
@@ -31,6 +32,10 @@ export function failure<T>(code: ConvertErrorCode, message: string, path: Conver
|
||||
return { error: { code, message, path }, ok: false }
|
||||
}
|
||||
|
||||
export function faulted<T>(fault: ConvertFault, path: ConvertErrorPath): Result<T> {
|
||||
return failure(fault.code, fault.message, path)
|
||||
}
|
||||
|
||||
export function success<T>(value: T): Result<T> {
|
||||
return { ok: true, value }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user