fix: write a delimiter run the pairing sum leaves literal without an escape - #383
Merged
Conversation
The safety check escaped a left-flanking run without asking whether any closer on the line could match it. CommonMark declines a pair whose two runs sum to a multiple of three unless both lengths are, so a two-marker opener and the single marker closing it reach nothing and the run is already literal without a backslash. That rule holds only where one of the two runs can also play the other part, which this shape reaches only once the roles are read as micromark settles them rather than as the flanking rules alone state them: a run facing another attention marker can play both, and the GFM extension puts the tilde in that set. Widening the roles keeps an escape wherever it reaches anything else, so the sum is what the relaxation rests on. That sum also reaches a case covered as one that keeps its escape. `x\*\*\*\*strong**` holds both of its runs in one text node, where four markers and the two closing them sum to six, so it now writes bare and moves to the coverage asserting the bytes and the reopened document together.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The serializer escaped a
**run that the parser already reads as literal text, writing_\*\*underscore and one asterisk stay literal*forcorpus/commonmark/emphasis.mdline 121. The safety check saw a left-flanking run and escaped it as an opener without asking whether any closer on the line could match it. CommonMark declines a pair whose two runs sum to a multiple of three unless both lengths are, so a two-marker opener and the single marker closing it reach nothing and the run is literal without a backslash. This is the class #252, #256, #257, #258, #271, #289, #292, #293, #336, and #353 removed, reaching the one pairing constraint none of them covered.That rule holds only where one of the two runs can also play the other part, and by flanking alone neither run in this shape can, so the constraint alone does not reach it. Measured with
mdast-util-from-markdown2.0.3 and the GFM extension from the project's dependency tree,**b*and.**b*both pair while_**b*and~**b*stay literal. micromark's attention tokenizer carries a clause the spec prose does not: a run is read as able to play a part wherever the character on that side is another attention marker, andmicromark-extension-gfmputs the tilde into that set. The roles are therefore read as micromark settles them rather than as the flanking rules alone state them, which is what puts this shape within reach of the sum at all. That widening only ever keeps an escape on its own, so the sum is what the relaxation rests on. A tilde run is left on plain flanking, becausemicromark-extension-gfm-strikethroughtokenizes it without the widening and closes only with a run of its own length.The sum is applied in both directions of the pairing search, which is what lets the closing run relax too: with the opener no longer escaped, the closer would otherwise have found an opener it cannot actually pair with and kept a backslash of its own.
docs/decisions.mdrecords this beside the merged-run rule underAccept Milkdown GFM preset behavior, since it is that same sum reaching the seam where both runs are in hand rather than one of them belonging to a mark.Related Issue
Closes #372
Verification
markdownCompatibility.test.tscovers both directions underEscape precision. Relaxation is asserted for_**a*,~**a*, andx****strong**; retention fora\*\*\*b***, where both lengths are multiples of three and the pair forms on the exception the rule carves out, and fora\*\*b**, where a sum of four is not a multiple of three at all. Both retention cases also show that a backslash on the opening run is what holds the pair apart, and that the closing run needs none once that opener has stopped being a delimiter.Because closure and convergence both already hold for this class, the corpus round trip and the document-preservation guard cannot see it.
_**underscore and one asterisk stay literal*andx****strong**are therefore asserted in the block that writes a source as authored and reopens it as the same document.Measured by mounting the corpus files and saving them,
commonmark/emphasis.mdgoes from three differing lines to two: line 121 is now written as authored, leaving the two nesting-collapse lines #213 settled, which no serializer change reaches.gfm/strikethrough.mdstays at its three, so the widened roles moved nothing there.pnpm check:frontendpasses.Not verified: no manual pass in the assembled application, and
pnpm check:backendwas not run. The change is serializer behavior with no rendered surface of its own, and it touches no Rust or Tauri code.Notes
x\*\*\*\*strong**was covered as a run that keeps its escape, under the comment about a mark's far delimiters being decided by text no handler has seen. Its document is a single text node holding both runs and no mark at all, so it was always the same-node path; four markers and the two closing them sum to six and pair with nothing. Both spellings parse toparagraph("x****strong**"), so the row moved to the coverage asserting the bytes and the reopened document together rather than being dropped.