Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/wild-dogs-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'markdown-to-jsx': patch
---

Fix exponential backtracking issue for unpaired inline delimiter sequences.
41 changes: 41 additions & 0 deletions index.compiler.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,47 @@ it('#234 perf regression', () => {
`)
})

it('#700 perf regression with unclosed inline syntax', () => {
render(
compiler(
'«Cleanliness is the finest of uniforms and a great defender against disease»*. Silver fabric was flowing. A wasp, buzzing, touches the bronze lips of the dragon with delicate <Tooltip><TooltipTrigger>hymenous wings</TooltipTrigger><TooltipContent>wings thin like a membrane (hymenous = thin, like a hymen, meaning very thin skin).</TooltipContent></Tooltip>. On the <Tooltip><TooltipTrigger>carved</TooltipTrigger><TooltipContent>engraved.</TooltipContent></Tooltip> tree trunk like a <Tooltip><TooltipTrigger>cradle</TooltipTrigger><TooltipContent>a swing.</TooltipContent></Tooltip> trough, where the animals quench their thirst, the beehive rests after gathering from the flowers.'
)
)

expect(root.innerHTML).toMatchInlineSnapshot(`
<span>
«Cleanliness is the finest of uniforms and a great defender against disease»*. Silver fabric was flowing. A wasp, buzzing, touches the bronze lips of the dragon with delicate
<tooltip>
<tooltiptrigger>
hymenous wings
</tooltiptrigger>
<tooltipcontent>
wings thin like a membrane (hymenous = thin, like a hymen, meaning very thin skin).
</tooltipcontent>
</tooltip>
. On the
<tooltip>
<tooltiptrigger>
carved
</tooltiptrigger>
<tooltipcontent>
engraved.
</tooltipcontent>
</tooltip>
tree trunk like a
<tooltip>
<tooltiptrigger>
cradle
</tooltiptrigger>
<tooltipcontent>
a swing.
</tooltipcontent>
</tooltip>
trough, where the animals quench their thirst, the beehive rests after gathering from the flowers.
</span>
`)
})

describe('inline textual elements', () => {
it('should handle emphasized text', () => {
render(compiler('*Hello.*'))
Expand Down
25 changes: 19 additions & 6 deletions index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,12 @@ const TABLE_CENTER_ALIGN = /^ *:-+: *$/
const TABLE_LEFT_ALIGN = /^ *:-+ *$/
const TABLE_RIGHT_ALIGN = /^ *-+: *$/

/**
* Ensure there's at least one more instance of the delimiter later
* in the current sequence.
*/
const LOOKAHEAD = (double: number) => `(?=[\\s\\S]+?\\1${double ? '\\1' : ''})`

/**
* For inline formatting, this partial attempts to ignore characters that
* may appear in nested formatting that could prematurely trigger detection
Expand All @@ -310,22 +316,28 @@ const INLINE_SKIP_R =
* Detect a sequence like **foo** or __foo__. Note that bold has a higher priority
* than emphasized to support nesting of both since they share a delimiter.
*/
const TEXT_BOLD_R = new RegExp(`^([*_])\\1${INLINE_SKIP_R}\\1\\1(?!\\1)`)
const TEXT_BOLD_R = new RegExp(
`^([*_])\\1${LOOKAHEAD(1)}${INLINE_SKIP_R}\\1\\1(?!\\1)`
)

/**
* Detect a sequence like *foo* or _foo_.
*/
const TEXT_EMPHASIZED_R = new RegExp(`^([*_])${INLINE_SKIP_R}\\1(?!\\1)`)
const TEXT_EMPHASIZED_R = new RegExp(
`^([*_])${LOOKAHEAD(0)}${INLINE_SKIP_R}\\1(?!\\1)`
)

/**
* Detect a sequence like ==foo==.
*/
const TEXT_MARKED_R = new RegExp(`^(==)${INLINE_SKIP_R}\\1`)
const TEXT_MARKED_R = new RegExp(`^(==)${LOOKAHEAD(0)}${INLINE_SKIP_R}\\1`)

/**
* Detect a sequence like ~~foo~~.
*/
const TEXT_STRIKETHROUGHED_R = new RegExp(`^(~~)${INLINE_SKIP_R}\\1`)
const TEXT_STRIKETHROUGHED_R = new RegExp(
`^(~~)${LOOKAHEAD(0)}${INLINE_SKIP_R}\\1`
)

/**
* Special case for shortcodes like :big-smile: or :emoji:
Expand Down Expand Up @@ -558,7 +570,8 @@ function generateListRule(
}
}

const LINK_INSIDE = '(?:\\[[^\\[\\]]*(?:\\[[^\\[\\]]*\\][^\\[\\]]*)*\\]|[^\\[\\]])*'
const LINK_INSIDE =
'(?:\\[[^\\[\\]]*(?:\\[[^\\[\\]]*\\][^\\[\\]]*)*\\]|[^\\[\\]])*'
const LINK_HREF_AND_TITLE =
'\\s*<?((?:\\([^)]*\\)|[^\\s\\\\]|\\\\.)*?)>?(?:\\s+[\'"]([\\s\\S]*?)[\'"])?\\s*'
const LINK_R = new RegExp(
Expand Down Expand Up @@ -1903,7 +1916,7 @@ export function compiler(
} as MarkdownToJSX.Rule<{ alt?: string; ref: string }>,

[RuleType.refLink]: {
_qualify: (source) => source[0] === '[' && source.indexOf('](') === -1,
_qualify: source => source[0] === '[' && source.indexOf('](') === -1,
_match: inlineRegex(REFERENCE_LINK_R),
_order: Priority.MAX,
_parse(capture, parse, state) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"jest-serializer-html": "^7.1.0",
"jest-watch-typeahead": "^2.2.2",
"markdown-it": "^14.0.0",
"markdown-to-jsx-latest": "npm:[email protected].10",
"markdown-to-jsx-latest": "npm:[email protected].11",
"microbundle": "^0.15.1",
"microtime": "^3.1.1",
"mkdirp": "^3.0.1",
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7114,12 +7114,12 @@ __metadata:
languageName: node
linkType: hard

"markdown-to-jsx-latest@npm:[email protected].10":
version: 7.7.10
resolution: "markdown-to-jsx@npm:7.7.10"
"markdown-to-jsx-latest@npm:[email protected].11":
version: 7.7.11
resolution: "markdown-to-jsx@npm:7.7.11"
peerDependencies:
react: ">= 0.14.0"
checksum: 10/f0c55a235e91adf28bf1cc654bd33e44a97c2601f6154800206172be621dd4236520c9934860f905ba478e195e408f224863187b09f22497d932093974fab040
checksum: 10/4697abffc1729a4d397e529043834848d294f26186c03187359fd7ad23efd4b1b3f757e3bcf3e27bdd75fe08bc2b51afc60ace3486f50bf13b76441f95426f2e
languageName: node
linkType: hard

Expand All @@ -7145,7 +7145,7 @@ __metadata:
jest-serializer-html: "npm:^7.1.0"
jest-watch-typeahead: "npm:^2.2.2"
markdown-it: "npm:^14.0.0"
markdown-to-jsx-latest: "npm:[email protected].10"
markdown-to-jsx-latest: "npm:[email protected].11"
microbundle: "npm:^0.15.1"
microtime: "npm:^3.1.1"
mkdirp: "npm:^3.0.1"
Expand Down