4.2: a list whose item holds a whitespace-only line takes the directive form

This commit is contained in:
2026-09-14 21:10:10 +02:00
parent c219aeb07b
commit fb1cef719a
6 changed files with 94 additions and 10 deletions
@@ -0,0 +1,57 @@
{
"content": [
{
"content": [
{
"content": [
{
"content": [
{
"text": " \na",
"type": "text"
}
],
"type": "codeBlock"
}
],
"type": "listItem"
}
],
"type": "bulletList"
},
{
"attrs": {
"order": 1
},
"content": [
{
"content": [
{
"content": [
{
"content": [
{
"content": [
{
"text": "\t",
"type": "text"
}
],
"type": "codeBlock"
}
],
"type": "listItem"
}
],
"type": "bulletList"
}
],
"type": "listItem"
}
],
"type": "orderedList"
}
],
"type": "doc",
"version": 1
}
@@ -0,0 +1,20 @@
::::bulletList
:::listItem
```
a
```
:::
::::
::::::orderedList {order=1}
:::::listItem
::::bulletList
:::listItem
```
```
:::
::::
:::::
::::::
+2 -1
View File
@@ -28,7 +28,8 @@ normalizes to it through the round-trip.
adjacent lists of a kind back as one. The leaf `::listBreak` parts them, taking the separation
any directive block takes where it sits. It builds no node, and it reads only between two
adjacent lists of one type: elsewhere, or carrying an argument, `{attrs}` or a body, it is a
named error.
named error. A list whose item holds a line of spaces or tabs alone, which a list item reads
back empty, takes the directive form.
- Blockquotes prefix lines with `> `; a blank line inside a blockquote is a bare `>`.
- ATX headings (`#` … `######`); setext input normalizes to ATX.
- Code fences ``` with the node's language as info string, the fence lengthened past any backtick
+4
View File
@@ -167,6 +167,10 @@ export function isAutolink(text: string): boolean {
return autolink.test(text)
}
export function isBlankLine(line: string): boolean {
return /^[ \t]*$/.test(line)
}
export function isThematicBreak(line: string): boolean {
return thematicBreak.test(line)
}
+4 -2
View File
@@ -6,7 +6,7 @@ import { carriedBlock } from '../opaque-carry.ts'
import { emitInlineLine } from './inline-line.ts'
import { failure, faulted, success, type ConvertErrorPath, type Result } from '../../result.ts'
import { fencedCodeBlock } from '../backtick-runs.ts'
import { holdsNullCharacter, isThematicBreak, markerInterruptsParagraph } from '../commonmark-grammar.ts'
import { holdsNullCharacter, isBlankLine, isThematicBreak, markerInterruptsParagraph } from '../commonmark-grammar.ts'
import { languageSlot } from '../code-language.ts'
import { largestNesting } from '../../nesting.ts'
import { listBreakSpelling } from '../list-break.ts'
@@ -225,8 +225,10 @@ function emitListItem(item: AdfNode, marker: string, path: ConvertErrorPath, dep
const inner = emitBlocks(nodeContent(item), 'list-item', path, depth + 1)
if (!inner.ok) return inner
if (inner.value.text === '') return success({ fenceColons: 0, text: marker.trimEnd() })
const body = inner.value.text.split('\n')
if (body.some((line) => line !== '' && isBlankLine(line))) return undefined
const indent = ' '.repeat(marker.length)
const lines = inner.value.text.split('\n').map((line, index) => (index === 0 ? `${marker}${line}` : line === '' ? '' : `${indent}${line}`))
const lines = body.map((line, index) => (index === 0 ? `${marker}${line}` : line === '' ? '' : `${indent}${line}`))
if (isThematicBreak(lines[0] ?? '')) return undefined
return success({ fenceColons: inner.value.fenceColons, text: lines.join('\n') })
}
+7 -7
View File
@@ -7,6 +7,7 @@ import {
claimsPipeLine,
closingCodeFence,
decodeTextEscapes,
isBlankLine,
isThematicBreak,
listMarker,
markerInterruptsParagraph,
@@ -61,7 +62,6 @@ type Line = { column: number; text: string }
type Walk = ParsedBlocks & { leaf: OpenLeaf | undefined; position: SourcePosition; stack: OpenContainer[] }
const blankLine = /^[ \t]*$/
const indentedCodeColumns = 4
const largestOpenerIndentation = 3
const leafColons = 2
@@ -117,7 +117,7 @@ function continuesContainer(walk: Walk, container: OpenContainer, line: Line): L
// A directive container has no continuation marker: only its own fence closes it.
if (container.kind === 'directive') return line
// A list item begins with at most one blank line: an empty one gives the second up.
if (blankLine.test(line.text)) {
if (isBlankLine(line.text)) {
return container.blocks.length === 0 && walk.leaf === undefined ? undefined : { column: line.column, text: '' }
}
return leadingColumns(line) < container.indentation ? undefined : removeColumns(line, container.indentation)
@@ -155,7 +155,7 @@ function itemStart(line: Line, opener: Line, paragraphOpen: boolean, enclosing:
const marker = listMarker(opener.text)
if (marker === undefined) return undefined
const after: Line = { column: opener.column + marker.width, text: opener.text.slice(marker.width) }
const blank = blankLine.test(after.text)
const blank = isBlankLine(after.text)
if (paragraphOpen && !markerInterruptsParagraph(marker.start, blank)) return undefined
const spaces = leadingColumns(after)
const padding = blank || spaces > indentedCodeColumns ? 1 : spaces
@@ -268,7 +268,7 @@ function pushFault(walk: Walk, fault: ConvertFault): void {
// A claimed line ends the lazy continuation CommonMark would fold it into (spec/flavour.md).
function continuesLazily(walk: Walk, line: Line): boolean {
if (walk.leaf?.kind !== 'paragraph' || blankLine.test(line.text)) return false
if (walk.leaf?.kind !== 'paragraph' || isBlankLine(line.text)) return false
if (leadingColumns(line) >= indentedCodeColumns) return true
const opener = removeColumns(line, largestOpenerIndentation).text
if (claimsDirectiveLine(opener) || claimsPipeLine(opener) || isThematicBreak(opener)) return false
@@ -283,7 +283,7 @@ function readBlockLine(walk: Walk, line: Line): void {
return
}
if (leaf?.kind === 'html') {
if (leaf.closer === undefined ? blankLine.test(line.text) : leaf.closer.test(line.text)) closeLeaf(walk)
if (leaf.closer === undefined ? isBlankLine(line.text) : leaf.closer.test(line.text)) closeLeaf(walk)
return
}
if (leaf?.kind === 'pipe-table') {
@@ -298,7 +298,7 @@ function readBlockLine(walk: Walk, line: Line): void {
if (readIndentedCodeLine(leaf, line)) return
closeLeaf(walk)
}
if (blankLine.test(line.text)) {
if (isBlankLine(line.text)) {
closeLeaf(walk)
return
}
@@ -310,7 +310,7 @@ function readBlockLine(walk: Walk, line: Line): void {
}
function readIndentedCodeLine(leaf: Extract<OpenLeaf, { kind: 'indented-code' }>, line: Line): boolean {
if (blankLine.test(line.text)) {
if (isBlankLine(line.text)) {
leaf.held.push(removeColumns(line, indentedCodeColumns).text)
return true
}