Refuse a fence info string that decodes to the reserved carry name
CI / gate (push) Successful in 5s
CI / gate (push) Successful in 5s
This commit is contained in:
@@ -151,7 +151,8 @@ One-line commit messages and PR titles; short PR summaries. No AI-attribution ma
|
|||||||
|
|
||||||
No wiki markup (§1), no network or filesystem I/O, no name→id resolution (§3), no ADF schema
|
No wiki markup (§1), no network or filesystem I/O, no name→id resolution (§3), no ADF schema
|
||||||
validation or exported validator — a refusal that keeps the round-trip is not schema validation,
|
validation or exported validator — a refusal that keeps the round-trip is not schema validation,
|
||||||
so the one a node carrying the same mark type twice earns stays, no shipped CSS (§4), no streaming APIs, no performance budget —
|
so the one a spelled node carrying the same mark type twice earns stays, no shipped CSS (§4), no
|
||||||
|
streaming APIs, no performance budget —
|
||||||
conversions are O(n), real documents are kilobytes. A CLI is a later goal (`todo.md`), not a
|
conversions are O(n), real documents are kilobytes. A CLI is a later goal (`todo.md`), not a
|
||||||
non-goal.
|
non-goal.
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ test('refuses the code block info strings the fence cannot hold', () => {
|
|||||||
assert.equal(code(adfToMarkdown(document({ attrs: { language: '' }, type: 'codeBlock' }))), 'ambiguous-attribute-spelling')
|
assert.equal(code(adfToMarkdown(document({ attrs: { language: '' }, type: 'codeBlock' }))), 'ambiguous-attribute-spelling')
|
||||||
assert.equal(code(adfToMarkdown(document({ attrs: { language: 'a`b' }, type: 'codeBlock' }))), 'unspellable-code-block-language')
|
assert.equal(code(adfToMarkdown(document({ attrs: { language: 'a`b' }, type: 'codeBlock' }))), 'unspellable-code-block-language')
|
||||||
assert.equal(code(adfToMarkdown(document({ attrs: { language: ' sql' }, type: 'codeBlock' }))), 'unspellable-code-block-language')
|
assert.equal(code(adfToMarkdown(document({ attrs: { language: ' sql' }, type: 'codeBlock' }))), 'unspellable-code-block-language')
|
||||||
|
assert.equal(code(adfToMarkdown(document({ attrs: { language: 'adf' }, type: 'codeBlock' }))), 'unspellable-code-block-language')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('refuses a link destination CommonMark cannot spell', () => {
|
test('refuses a link destination CommonMark cannot spell', () => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { emitInlineLine } from './markdown-inline.ts'
|
|||||||
import { tryPipeTable } from './markdown-pipe-table.ts'
|
import { tryPipeTable } from './markdown-pipe-table.ts'
|
||||||
import { carriedBlock, carryName } from './opaque-carry.ts'
|
import { carriedBlock, carryName } from './opaque-carry.ts'
|
||||||
import { failure, success, type ConvertErrorPath, type Result } from './result.ts'
|
import { failure, success, type ConvertErrorPath, type Result } from './result.ts'
|
||||||
import { holdsNullCharacter, isThematicBreak } from './commonmark-grammar.ts'
|
import { holdsEntityReference, holdsNullCharacter, isThematicBreak } from './commonmark-grammar.ts'
|
||||||
import { isAdfDocument } from './adf-document.ts'
|
import { isAdfDocument } from './adf-document.ts'
|
||||||
import { largestNesting } from './nesting.ts'
|
import { largestNesting } from './nesting.ts'
|
||||||
import { fencedCodeBlock } from './backtick-runs.ts'
|
import { fencedCodeBlock } from './backtick-runs.ts'
|
||||||
@@ -187,6 +187,9 @@ function spellCodeFenceInfo(language: JsonValue | undefined, path: ConvertErrorP
|
|||||||
if (/[`\n\r]/.test(language) || language !== language.trim()) {
|
if (/[`\n\r]/.test(language) || language !== language.trim()) {
|
||||||
return failure('unspellable-code-block-language', 'a fence info string holds no backtick and no edge whitespace', path)
|
return failure('unspellable-code-block-language', 'a fence info string holds no backtick and no edge whitespace', path)
|
||||||
}
|
}
|
||||||
|
if (holdsEntityReference(language)) {
|
||||||
|
return failure('unspellable-code-block-language', 'a fence info string shaped like an entity reference decodes on the way back', path)
|
||||||
|
}
|
||||||
return success(language)
|
return success(language)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ function inlineRuns(nodes: readonly AdfNode[], depth: number, firstIndex: number
|
|||||||
const runs: InlineRun[] = []
|
const runs: InlineRun[] = []
|
||||||
for (const [offset, node] of nodes.entries()) {
|
for (const [offset, node] of nodes.entries()) {
|
||||||
const index = firstIndex + offset
|
const index = firstIndex + offset
|
||||||
|
// spec/flavour.md, Marks.
|
||||||
const mark = carries(node) ? undefined : (node.marks ?? [])[depth]
|
const mark = carries(node) ? undefined : (node.marks ?? [])[depth]
|
||||||
if (mark === undefined) {
|
if (mark === undefined) {
|
||||||
runs.push({ index, kind: 'plain', node })
|
runs.push({ index, kind: 'plain', node })
|
||||||
@@ -148,7 +149,6 @@ function nodePath(context: InlineContext, index: number): ConvertErrorPath {
|
|||||||
return [...context.path, 'content', index]
|
return [...context.path, 'content', index]
|
||||||
}
|
}
|
||||||
|
|
||||||
// spec/flavour.md, Marks.
|
|
||||||
function carries(node: AdfNode): boolean {
|
function carries(node: AdfNode): boolean {
|
||||||
return node.type !== 'hardBreak' && node.type !== 'text' && inlineDirective(node.type) === undefined
|
return node.type !== 'hardBreak' && node.type !== 'text' && inlineDirective(node.type) === undefined
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user