From b6930b45a4132535c0a1db56082256a8524d2bfd Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 14 Sep 2026 22:39:20 +0200 Subject: [PATCH] 4.2: an autolink holding a backtick gives way inside a directive's content --- .../directive-autolink-backtick.json | 50 +++++++++++++++++++ .../directive-autolink-backtick.md | 3 ++ spec/flavour.md | 11 ++-- src/markdown/emit/inline-line.ts | 4 +- 4 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 corpus/round-trip/combinations/directive-autolink-backtick.json create mode 100644 corpus/round-trip/combinations/directive-autolink-backtick.md diff --git a/corpus/round-trip/combinations/directive-autolink-backtick.json b/corpus/round-trip/combinations/directive-autolink-backtick.json new file mode 100644 index 0000000..2ddbd7f --- /dev/null +++ b/corpus/round-trip/combinations/directive-autolink-backtick.json @@ -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 +} diff --git a/corpus/round-trip/combinations/directive-autolink-backtick.md b/corpus/round-trip/combinations/directive-autolink-backtick.md new file mode 100644 index 0000000..163f313 --- /dev/null +++ b/corpus/round-trip/combinations/directive-autolink-backtick.md @@ -0,0 +1,3 @@ +:underline[[http://a\`b](http://a\`b)]`c + +:underline[[http://d\`e](http://d`e)] diff --git a/spec/flavour.md b/spec/flavour.md index d503374..d3506f5 100644 --- a/spec/flavour.md +++ b/spec/flavour.md @@ -48,7 +48,8 @@ normalizes to it through the round-trip. 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. `` autolink form only 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. - 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 @@ -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 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 -`[text](url)` form) and a backtick there that would open a code span, a `{` right after a -directive's closing `]`, which would otherwise be read as the attributes it has none of; outside -code spans and code blocks, a backslash before `:` in input yields a literal colon. +destination and title inside content leave unbalanced and a backtick there that would open a code +span, a `{` right after a directive's closing `]`, which would otherwise be read as the attributes +it has none of; outside code spans and code blocks, a backslash before `:` in input yields a +literal colon. **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 diff --git a/src/markdown/emit/inline-line.ts b/src/markdown/emit/inline-line.ts index deed5ff..ec3bf1f 100644 --- a/src/markdown/emit/inline-line.ts +++ b/src/markdown/emit/inline-line.ts @@ -284,8 +284,8 @@ function emitLink(nodes: readonly AdfNode[], mark: AdfMark, depth: number, range if (typeof href !== 'string') return success({ carry: range }) const node = nodes[0] 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 - if (bare && balanced && title === undefined && isAutolink(href) && !holdsEntityReference(href)) return success({ segments: [syntax(`<${href}>`)] }) + const autolinkHolds = !context.bracketed || (!href.includes('`') && escapeUnbalanced(href, '[', ']') === 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) if (!target.ok) return target const inner = emitRun(nodes, depth + 1, range.first, { ...context, bracketed: true })