Skip to content

The serializer escapes a backtick that cannot form a code span where it sits #374

Description

@Azganoth

Summary

Saving escapes a backtick that cannot open a code span. A code span needs a closing run of the same length inside the same block, and a backtick with no such run is literal text as written. The serializer escapes it anyway, so the file gains a backslash the author did not write and the document is unchanged.

corpus/commonmark/blocks.md carries the case under the heading Block structure takes precedence over inline parsing, where two list items each hold one backtick and the span cannot cross the boundary between them. That is one instance of the general rule rather than a case of its own.

Steps to reproduce

  1. Open a document containing a followed by a single backtick and b.
  2. Save it without editing.
  3. Read the file.

Expected behavior

The line is unchanged. Nothing closes the backtick, so nothing needs holding literal.

Actual behavior

Measured against 8bc36754 by driving the editor mount used by the plugin tests.

Document Saved
a ` b a \` b
`unclosed \`unclosed
- `a code span does not / - cross a list-item boundary` both backticks escaped
a `\n\nb ` (one in each of two paragraphs) both backticks escaped
> a ` b > a \` b
# a ` b # a \` b

A backtick that does close is unaffected, because the pair parses as a code span and never reaches this path: a ` b ` c is read as an inlineCode node and written a `b` c.

Related context

Done when

  • A backtick with no closing run of its own length in the same block is written without an escape.
  • A backtick that could still be closed keeps its escape, and a real code span is unaffected.
  • Regression coverage asserts the saved bytes and the document the saved file reopens as, since closure already holds and cannot catch this.

Notes, logs, screenshots

Diagnosis

No relaxation pass reconsiders a backtick. serializeMarkdownText runs relaxAttentionEscapes, relaxBlockMarkerEscapes, relaxBracketEscapes, relaxAngleEscapes, relaxAutolinkLiteralEscapes, and relaxCharacterReferenceEscapes. A backtick falls into none of them: ATTENTION_CHARACTERS is *_~, and relaxBlockMarkerEscapes branches only on #, the ordered-list markers, and |. The escape state.safe emits is therefore never revisited.

The machinery to decide it already exists. skipCodeSpan walks a block for exactly this question and returns the opening offset unchanged when no closing run of equal length follows, which is the condition under which the escape is unnecessary. It is currently used only by findBracketLinks.

Out of scope

Activity

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

Metadata

Metadata

Assignees

Labels

BugSomething isn't working

Projects

  • Status
    In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions