Skip to content

:: at the start of plain text is escaped too weakly, so it becomes a textDirective after round-trip. #14

Closed

Description

@ChristianMurphy

Initial checklist

Affected package

mdast-util-directive 3.1.0, mdast-util-to-markdown 2.1.2, mdast-util-from-markdown 2.0.3, micromark-extension-directive 3.0.2

Steps to reproduce

import {fromMarkdown} from 'mdast-util-from-markdown'
import {toMarkdown} from 'mdast-util-to-markdown'
import {directiveFromMarkdown, directiveToMarkdown} from 'mdast-util-directive'
import {directive} from 'micromark-extension-directive'

const input = '::a}'
const tree1 = fromMarkdown(input, {extensions: [directive()], mdastExtensions: [directiveFromMarkdown()]})
// tree1: root > paragraph > text (value: '::a}')  -- no directive

const text2 = toMarkdown(tree1, {extensions: [directiveToMarkdown()]})
// text2: '\\::a}\n'

const tree2 = fromMarkdown(text2, {extensions: [directive()], mdastExtensions: [directiveFromMarkdown()]})
// tree2: root > paragraph > [text, textDirective(name='a'), text]

The escape rule writes \: before a following :. After decoding the
backslash-escape, the text still contains ::a}. On re-parse, the
directive scanner sees ::a as a text directive start, so a textDirective
node appears in tree₂ that wasn't in tree₁.

Actual behavior

A textDirective node is introduced on re-parse. The escape rule escapes one
colon, but the surviving decoded text still contains ::.

Possible fixes:

  1. escape both colons of a leading :: run, or
  2. use a numeric character reference (:) for one of them, or
  3. widen the escape context to match any :: prefix, not just : + :.

Expected behavior

Text that does not originally parse as a directive should not become one
after round-trip.

Runtime

node v24.13.1

Package manager

No response

Operating system

Ubuntu (WSL2)

Build and bundle tools

No response

Activity

  1. added
    👋 phase/newPost is being triaged automatically
    🤞 phase/openPost is being triaged manually
    and removed
    👋 phase/newPost is being triaged automatically
    on Apr 23, 2026
  2. github-actions commented on Oct 3, 2026

    @github-actions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions