| #
2bb62bca |
| 20-Apr-2026 |
Andreas Gohr <gohr@cosmocode.de> |
add GFM em-wrapping-strong modes for `***foo***` / `___foo___`
Two new inline formatting modes that render triple-delimiter runs as em wrapping strong:
GfmEmphasisStrong `***text***`
add GFM em-wrapping-strong modes for `***foo***` / `___foo___`
Two new inline formatting modes that render triple-delimiter runs as em wrapping strong:
GfmEmphasisStrong `***text***` → <em><strong>text</strong></em> GfmEmphasisStrongUnderscore `___text___` → same (MD-preferred only)
Only the exact 3+3 symmetric case is handled. The other long-run and asymmetric variants (4+4, 5+5, `***foo**`, etc.) require CommonMark's stack-based delimiter-pairing algorithm with its flanking and multiple-of-3 rules, which is explicitly out of scope; those examples stay skipped in gfm-spec/skip.php.
Implementation notes:
* Patterns enforce exact 3+3 via `(?<!\*)` / `(?<!_)` lookbehinds (preventing entry at the second `*` of a `****...` run) and `(?!\*)` / `(?!_)` lookaheads after the closing triple (rejecting `***foo****` etc.). Combined with the existing non-whitespace adjacency lookaheads, all asymmetric cases cleanly fall through to other modes or stay literal.
* GfmEmphasisStrong overrides handle() to emit two instructions on entry (emphasis_open + strong_open) and two on exit (strong_close + emphasis_close). GfmEmphasisStrongUnderscore inherits that handler — only delimiters and word-boundary rules differ.
* Sort 65 — below Strong (70) and GfmEmphasis (80) so the em+strong modes win the lexer race for `***`/`___` runs. Underscore variant is MD-preferred-only, matching the existing gating of GfmEmphasisUnderscore and GfmStrongUnderscore.
Per-mode unit tests cover basic matching, single-char bodies, whitespace flanking rejection, paragraph-boundary rejection, longer-run rejection, asymmetric rejection, multibyte intraword protection, and sort values. ModeRegistryTest's gating data provider picks up the two new rules.
Net effect on GfmSpecTest: example #476 (`***foo***`) now passes; 473/474/475/477 remain skipped as documented in skip.php.
show more ...
|