Keep delimiters from pairing across runs in one paragraph - #46
Conversation
A delimiter with nothing to pair with is inert, so it was left unescaped. That check ran over one run, but Markdown pairs across the whole paragraph: two runs each ending in a backtick put two of them on the line, and the text between became a code span. Emphasis and links went the same way, so a paragraph reading `see [` / `note](http://example.com)` came out as a working hyperlink the document never had. Runs that still have content after them now escape their pairable delimiters, which leaves at most one raw delimiter per paragraph, at the end, where nothing can reach it. Link labels and image alt text count as having content after them, since `](url)` always follows. Across 684 generated paragraphs pairing delimiters over a hard break, 44 converted to markup that was not in the document; none do now. The fixture corpus is unchanged.
|
@abimaelmartell I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
1 issue found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/render/markdown/escape.rs">
<violation number="1" location="src/render/markdown/escape.rs:73">
P2: The new `pairs_ahead` in `paired` does not escape a `*`, `_`, or `~` that sits at the very end of a run and is followed by more of the paragraph (a line break or anchor then more plain text). For those characters the escape is gated by `next_nonspace`, which at run end falls back to `trailing_active` (false when the next run is plain), so the trailing delimiter stays raw and can pair with a later run's raw delimiter — e.g. ["a *", LineBreak, "b *"] renders two unescaped `*` in one paragraph, defeating the fix. Consider making the run-end `next_nonspace` fall back to `trailing_active || pairs_ahead` (or short-circuit on `pairs_ahead`) so a delimiter before more paragraph content is always escaped.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| let next_nonspace = next.map_or(trailing_active, |n| !n.is_whitespace()); | ||
| let paired = |slot: usize| trailing_active || last[slot].is_some_and(|j| j > i); | ||
| let paired = | ||
| |slot: usize| trailing_active || pairs_ahead || last[slot].is_some_and(|j| j > i); |
There was a problem hiding this comment.
P2: The new pairs_ahead in paired does not escape a *, _, or ~ that sits at the very end of a run and is followed by more of the paragraph (a line break or anchor then more plain text). For those characters the escape is gated by next_nonspace, which at run end falls back to trailing_active (false when the next run is plain), so the trailing delimiter stays raw and can pair with a later run's raw delimiter — e.g. ["a *", LineBreak, "b *"] renders two unescaped * in one paragraph, defeating the fix. Consider making the run-end next_nonspace fall back to trailing_active || pairs_ahead (or short-circuit on pairs_ahead) so a delimiter before more paragraph content is always escaped.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/render/markdown/escape.rs, line 73:
<comment>The new `pairs_ahead` in `paired` does not escape a `*`, `_`, or `~` that sits at the very end of a run and is followed by more of the paragraph (a line break or anchor then more plain text). For those characters the escape is gated by `next_nonspace`, which at run end falls back to `trailing_active` (false when the next run is plain), so the trailing delimiter stays raw and can pair with a later run's raw delimiter — e.g. ["a *", LineBreak, "b *"] renders two unescaped `*` in one paragraph, defeating the fix. Consider making the run-end `next_nonspace` fall back to `trailing_active || pairs_ahead` (or short-circuit on `pairs_ahead`) so a delimiter before more paragraph content is always escaped.</comment>
<file context>
@@ -64,7 +69,8 @@ pub(crate) fn escape_text(text: &str, ctx: InlineContext, opts: EscapeOpts) -> S
let next_nonspace = next.map_or(trailing_active, |n| !n.is_whitespace());
- let paired = |slot: usize| trailing_active || last[slot].is_some_and(|j| j > i);
+ let paired =
+ |slot: usize| trailing_active || pairs_ahead || last[slot].is_some_and(|j| j > i);
let escape = match c {
'\\' => true,
</file context>
Runs that still have content after them now escape their pairable delimiters, so at most one raw delimiter survives per paragraph and nothing can pair with it.
For issue #45.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.