4.2: an autolink holding a backtick gives way inside a directive's content

This commit is contained in:
2026-09-14 22:39:20 +02:00
parent e166e969cb
commit b6930b45a4
4 changed files with 61 additions and 7 deletions
@@ -0,0 +1,50 @@
{
"content": [
{
"content": [
{
"marks": [
{
"type": "underline"
},
{
"attrs": {
"href": "http://a`b"
},
"type": "link"
}
],
"text": "http://a`b",
"type": "text"
},
{
"text": "`c",
"type": "text"
}
],
"type": "paragraph"
},
{
"content": [
{
"marks": [
{
"type": "underline"
},
{
"attrs": {
"href": "http://d`e"
},
"type": "link"
}
],
"text": "http://d`e",
"type": "text"
}
],
"type": "paragraph"
}
],
"type": "doc",
"version": 1
}
@@ -0,0 +1,3 @@
:underline[[http://a\`b](http://a\`b)]`c
:underline[[http://d\`e](http://d`e)]
+6 -5
View File
@@ -48,7 +48,8 @@ normalizes to it through the round-trip.
title; title in double quotes. A backslash escapes a parenthesis the destination leaves title; title in double quotes. A backslash escapes a parenthesis the destination leaves
unbalanced, and a quote inside the title; a balanced pair stays bare. `<url>` autolink form only unbalanced, and a quote inside the title; a balanced pair stays bare. `<url>` autolink form only
when the text equals the destination and the destination is a valid CommonMark autolink when the text equals the destination and the destination is a valid CommonMark autolink
(absolute URI). (absolute URI) — inside an inline directive's `[content]`, one holding no backtick and no
unbalanced bracket.
- Paragraphs on one line — no soft wrapping; a soft line break in input becomes a single space. - Paragraphs on one line — no soft wrapping; a soft line break in input becomes a single space.
- Entity references in input decode to their characters; output backslash-escapes only where text - Entity references in input decode to their characters; output backslash-escapes only where text
would otherwise parse as syntax, scanning the assembled line rather than each text node: escape would otherwise parse as syntax, scanning the assembled line rather than each text node: escape
@@ -131,10 +132,10 @@ each a named error naming the spelling to write instead.
**Escaping**: the emitter backslash-escapes whatever literal text would otherwise parse as **Escaping**: the emitter backslash-escapes whatever literal text would otherwise parse as
directive syntax — the leading `:` of a would-be directive, `]` inside content, a bracket a link's directive syntax — the leading `:` of a would-be directive, `]` inside content, a bracket a link's
destination and title inside content leave unbalanced (an autolink holding one takes the destination and title inside content leave unbalanced and a backtick there that would open a code
`[text](url)` form) and a backtick there that would open a code span, a `{` right after a span, a `{` right after a directive's closing `]`, which would otherwise be read as the attributes
directive's closing `]`, which would otherwise be read as the attributes it has none of; outside it has none of; outside code spans and code blocks, a backslash before `:` in input yields a
code spans and code blocks, a backslash before `:` in input yields a literal colon. literal colon.
**Malformed directives are error results**, named: an unclosed container at end of input, a body **Malformed directives are error results**, named: an unclosed container at end of input, a body
fence line of the container's length or longer, a bare colon-run line outside any container or fence line of the container's length or longer, a bare colon-run line outside any container or
+2 -2
View File
@@ -284,8 +284,8 @@ function emitLink(nodes: readonly AdfNode[], mark: AdfMark, depth: number, range
if (typeof href !== 'string') return success({ carry: range }) if (typeof href !== 'string') return success({ carry: range })
const node = nodes[0] const node = nodes[0]
const bare = nodes.length === 1 && node !== undefined && node.type === 'text' && node.text === href && nodeMarks(node).length === depth + 1 const bare = nodes.length === 1 && node !== undefined && node.type === 'text' && node.text === href && nodeMarks(node).length === depth + 1
const balanced = !context.bracketed || escapeUnbalanced(href, '[', ']') === href const autolinkHolds = !context.bracketed || (!href.includes('`') && escapeUnbalanced(href, '[', ']') === href)
if (bare && balanced && title === undefined && isAutolink(href) && !holdsEntityReference(href)) return success({ segments: [syntax(`<${href}>`)] }) if (bare && autolinkHolds && title === undefined && isAutolink(href) && !holdsEntityReference(href)) return success({ segments: [syntax(`<${href}>`)] })
const target = spellLinkTarget(href, typeof title === 'string' ? title : undefined, path) const target = spellLinkTarget(href, typeof title === 'string' ? title : undefined, path)
if (!target.ok) return target if (!target.ok) return target
const inner = emitRun(nodes, depth + 1, range.first, { ...context, bracketed: true }) const inner = emitRun(nodes, depth + 1, range.first, { ...context, bracketed: true })