fix(components): end autolinks at non-ASCII punctuation - #336
Conversation
A GFM literal autolink only ends at ASCII whitespace, so CJK prose written without one is swallowed into the destination: `**https://.../pull/317**,分支` rendered as a single link over `...317**,分支`, with the `**` never closing the strong it opened. Treat a non-ASCII punctuation mark or separator as the end of a bare URL in both autolinkers — those never appear unencoded in a URL, while non-ASCII letters still can (`/wiki/中文`). For GFM autolinks this runs as a pass before the existing bold repair; every boundary character is Markdown punctuation or whitespace, so a `**` left text-final by the cut was already a valid strong closer in the source. That also makes the repair's "suffix must contain inline markup" guard wrong — the swallowed suffix here is plain text — and the guard's original case (`**https://example.com/path**segment`) is already covered by the strong-closer validity check. Model: claude-opus-5[1m]
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fc5d52352
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // (,。、)「」… ) never appear unencoded in a URL, so end the URL there. | ||
| // Non-ASCII letters still may (`/wiki/中文`), and symbols are left alone | ||
| // because they are not Markdown punctuation for strong-closer purposes. | ||
| const NON_ASCII_URL_BOUNDARY = /(?!\p{ASCII})[\p{P}\p{Z}]/u; |
There was a problem hiding this comment.
Preserve valid punctuation inside international URLs
When a bare URL legitimately contains Unicode punctuation, this boundary truncates the link at the first such character. For example, https://ja.wikipedia.org/wiki/ハリー・ポッター is split at the common Japanese middle dot (・, category \p{P}), producing a link only to .../ハリー; paths containing en dashes and similar valid characters break likewise in both autolinking passes. Restrict the boundary to known sentence delimiters or otherwise distinguish punctuation inside a URL from following prose.
Useful? React with 👍 / 👎.
A GFM literal autolink only ends at ASCII whitespace, so CJK prose written without one gets swallowed into the destination.
**https://github.com/LodyAI/Lody/pull/317**,分支rendered as one link spanninghttps://…/pull/317**,分支— the**disappeared into the URL, so the strong it opened never closed and showed up as literal asterisks.Two layers had the same gap:
AUTOLINK_PATTERN/splitAutolinkTrailingonly trimmed trailing punctuation, sohttps://example.com/a。然后kept。然后— the loop stops at the first non-punctuation character, and后is a letter.Fix
NON_ASCII_URL_BOUNDARY = /(?!\p{ASCII})[\p{P}\p{Z}]/u, applied insplitAutolinkTrailingand in a new mdast pass (trimNonAsciiAutolinkTail) that runs before the existing bold repair. The trimmed tail is re-parsed as inline markdown, and the destination is truncated only when it actually ends with that tail (raw or percent-encoded) — otherwise the node is left untouched rather than guessing at a cut point.**https://example.com/path**segment) is already rejected byisValidStrongCloser.Ordering is safe by construction: every boundary character is Markdown punctuation or whitespace, so a
**left text-final by the cut was already a valid strong closer in the source — the trim cannot manufacture one.Deliberate scope
Non-ASCII letters stay in the URL, so
https://zh.example.com/wiki/中文still links whole; non-ASCII symbols like€are also untouched, preserving the existing "€ is not Markdown punctuation" behaviour. A URL followed immediately by 汉字 with no punctuation (访问https://example.com吧) therefore still swallows the character. That is GFM-conformant, and cutting there would break CJK IRI paths, so I left it.Verification
@lody/componentssuite: 421 files / 3042 tests passingtsc --noEmitclean,oxlintclean。, a CJK path that must stay intact, and twosplitAutolinkTrailingcases🤖 Generated with Claude Code