Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion src/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,13 @@ const _notPunctuationOrSpace = /[^\s\p{P}\p{S}]/u;
const punctuation = edit(/^((?![*_])punctSpace)/, 'u')
.replace(/punctSpace/g, _punctuationOrSpace).getRegex();

/** Quote chars for pedantic rule 6 lookbehind; includes both straight and curly forms. */
const closeQuoteClass = '["\u201c\u201d\u2018\u2019\']';
/** Quote chars excluded from pedantic left-delim captured punct so `**"` / `**'` yields an empty nextChar. */
const openQuoteClass = '["\'\u2018\u2019\u201c]';
/** When lookbehind is supported, pedantic rule 6 does not match immediately after a quote (e.g. `"**` in `**"A"**`). */
const closeQuoteLookbehind = supportsLookbehind ? `(?<!${closeQuoteClass})` : '';

// GFM allows ~ inside strong and em for strikethrough
const _punctuationGfmStrongEm = /(?!~)[\p{P}\p{S}]/u;
const _punctuationOrSpaceGfmStrongEm = /(?!~)[\s\p{P}\p{S}]/u;
Expand All @@ -308,6 +315,14 @@ const emStrongLDelimGfm = edit(emStrongLDelimCore, 'u')
.replace(/punct/g, _punctuationGfmStrongEm)
.getRegex();

// Pedantic: do not treat a quote as left-delim punct (pairs with pedantic emStrongRDelim rule 6).
const emStrongLDelimPedanticCore = /^(?:\*+(?:((?!\*)(?!openQuote)punct)|([^\s*]))?)|^_+(?:((?!_)(?!openQuote)punct)|([^\s_]))?/;

const emStrongLDelimPedantic = edit(emStrongLDelimPedanticCore, 'u')
.replace(/openQuote/g, openQuoteClass)
.replace(/punct/g, _punctuation)
.getRegex();

const emStrongRDelimAstCore =
'^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)' // Skip orphan inside strong
+ '|[^*]+(?=[^*])' // Consume to delim
Expand All @@ -330,7 +345,25 @@ const emStrongRDelimAstGfm = edit(emStrongRDelimAstCore, 'gu')
.replace(/punct/g, _punctuationGfmStrongEm)
.getRegex();

// (6) Not allowed for _
// Pedantic: rule 3 requires whitespace before left delim; rule 6 allows punct before close (e.g. `:**bar`).
const emStrongRDelimAstPedanticCore =
'^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)' // Skip orphan inside strong
+ '|[^*]+(?=[^*])' // Consume to delim
+ '|(?!\\*)punct(\\*+)(?=[\\s]|$)' // (1) #*** can only be a Right Delimiter
+ '|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)' // (2) a***#, a*** can only be a Right Delimiter
+ '|(?!\\*)[\\s](\\*+)(?=notPunctSpace)' // (3) ***a can only be Left Delimiter (whitespace required; not #***a)
+ '|[\\s](\\*+)(?!\\*)(?=punct)' // (4) ***# can only be Left Delimiter
+ '|(?!\\*)punct(\\*+)(?!\\*)(?=punct)' // (5) #***# can be either Left or Right Delimiter
+ '|closeQuoteLookbehind(?:(?!\\*)punct|notPunctSpace)(\\*+)(?!\\*)(?=notPunctSpace)'; // (6) a***a, #***a, and :** (e.g. **foo:**bar)

const emStrongRDelimAstPedantic = edit(emStrongRDelimAstPedanticCore, 'gu')
.replace(/closeQuoteLookbehind/g, closeQuoteLookbehind)
.replace(/notPunctSpace/g, _notPunctuationOrSpace)
.replace(/punctSpace/g, _punctuationOrSpace)
.replace(/punct/g, _punctuation)
.getRegex();

// Normal: no rule 6 for _ (a___a cannot close as emphasis).
const emStrongRDelimUnd = edit(
'^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)' // Skip orphan inside strong
+ '|[^_]+(?=[^_])' // Consume to delim
Expand All @@ -344,6 +377,24 @@ const emStrongRDelimUnd = edit(
.replace(/punct/g, _punctuation)
.getRegex();

// Pedantic: same rule 3/6 adjustments as emStrongRDelimAstPedantic (e.g. `_foo:_bar`).
const emStrongRDelimUndPedanticCore =
'^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)' // Skip orphan inside strong
+ '|[^_]+(?=[^_])' // Consume to delim
+ '|(?!_)punct(_+)(?=[\\s]|$)' // (1) #___ can only be a Right Delimiter
+ '|notPunctSpace(_+)(?!_)(?=punctSpace|$)' // (2) a___#, a___ can only be a Right Delimiter
+ '|(?!_)[\\s](_+)(?=notPunctSpace)' // (3) ___a can only be Left Delimiter (whitespace required; not #___a)
+ '|[\\s](_+)(?!_)(?=punct)' // (4) ___# can only be Left Delimiter
+ '|(?!_)punct(_+)(?!_)(?=punct)' // (5) #___# can be either Left or Right Delimiter
+ '|closeQuoteLookbehind(?:(?!_)punct|notPunctSpace)(_+)(?!_)(?=notPunctSpace)'; // (6) a___a, #___a, and :__ (e.g. _foo:_bar)

const emStrongRDelimUndPedantic = edit(emStrongRDelimUndPedanticCore, 'gu')
.replace(/closeQuoteLookbehind/g, closeQuoteLookbehind)
.replace(/notPunctSpace/g, _notPunctuationOrSpace)
.replace(/punctSpace/g, _punctuationOrSpace)
.replace(/punct/g, _punctuation)
.getRegex();

// Tilde left delimiter for strikethrough (similar to emStrongLDelim for asterisk)
const delLDelim = edit(/^~~?(?:((?!~)punct)|[^\s~])/, 'u')
.replace(/punct/g, _punctuation)
Expand Down Expand Up @@ -446,6 +497,9 @@ type InlineKeys = keyof typeof inlineNormal;

const inlinePedantic: Record<InlineKeys, RegExp> = {
...inlineNormal,
emStrongLDelim: emStrongLDelimPedantic,
emStrongRDelimAst: emStrongRDelimAstPedantic,
emStrongRDelimUnd: emStrongRDelimUndPedantic,
link: edit(/^!?\[(label)\]\((.*?)\)/)
.replace('label', _inlineLabel)
.getRegex(),
Expand Down
15 changes: 15 additions & 0 deletions test/specs/original/strong_punctuation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<p><strong>foo:</strong>bar</p>

<p><strong>foo</strong>bar</p>

<p><em>foo:</em>bar</p>

<p><em>foo:</em>bar</p>

<p><strong>foo:</strong>bar</p>

<p>aa<strong>&quot;A&quot;</strong>and<strong>&quot;B&quot;</strong>aa</p>

<p>aa<strong>&#39;A&#39;</strong>and<strong>&#39;B&#39;</strong>aa</p>

<p>We can compare <strong>&quot;direction&quot;</strong> and <strong>&quot;distance&quot;</strong> in two ways</p>
19 changes: 19 additions & 0 deletions test/specs/original/strong_punctuation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
pedantic: true
---

**foo:**bar

**foo**bar

*foo:*bar

_foo:_bar

__foo:__bar

aa**"A"**and**"B"**aa

aa**'A'**and**'B'**aa

We can compare **"direction"** and **"distance"** in two ways