Skip to content

Commit da003e4

Browse files
authored
700/forward seek perf (#701)
* update benchmark comparison * avoid exponential backtracking for unpaired inline syntax fixes #700
1 parent be306c2 commit da003e4

File tree

5 files changed

+71
-12
lines changed

5 files changed

+71
-12
lines changed

.changeset/wild-dogs-search.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'markdown-to-jsx': patch
3+
---
4+
5+
Fix exponential backtracking issue for unpaired inline delimiter sequences.

index.compiler.spec.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,47 @@ it('#234 perf regression', () => {
210210
`)
211211
})
212212

213+
it('#700 perf regression with unclosed inline syntax', () => {
214+
render(
215+
compiler(
216+
'«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.'
217+
)
218+
)
219+
220+
expect(root.innerHTML).toMatchInlineSnapshot(`
221+
<span>
222+
«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
223+
<tooltip>
224+
<tooltiptrigger>
225+
hymenous wings
226+
</tooltiptrigger>
227+
<tooltipcontent>
228+
wings thin like a membrane (hymenous = thin, like a hymen, meaning very thin skin).
229+
</tooltipcontent>
230+
</tooltip>
231+
. On the
232+
<tooltip>
233+
<tooltiptrigger>
234+
carved
235+
</tooltiptrigger>
236+
<tooltipcontent>
237+
engraved.
238+
</tooltipcontent>
239+
</tooltip>
240+
tree trunk like a
241+
<tooltip>
242+
<tooltiptrigger>
243+
cradle
244+
</tooltiptrigger>
245+
<tooltipcontent>
246+
a swing.
247+
</tooltipcontent>
248+
</tooltip>
249+
trough, where the animals quench their thirst, the beehive rests after gathering from the flowers.
250+
</span>
251+
`)
252+
})
253+
213254
describe('inline textual elements', () => {
214255
it('should handle emphasized text', () => {
215256
render(compiler('*Hello.*'))

index.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,12 @@ const TABLE_CENTER_ALIGN = /^ *:-+: *$/
298298
const TABLE_LEFT_ALIGN = /^ *:-+ *$/
299299
const TABLE_RIGHT_ALIGN = /^ *-+: *$/
300300

301+
/**
302+
* Ensure there's at least one more instance of the delimiter later
303+
* in the current sequence.
304+
*/
305+
const LOOKAHEAD = (double: number) => `(?=[\\s\\S]+?\\1${double ? '\\1' : ''})`
306+
301307
/**
302308
* For inline formatting, this partial attempts to ignore characters that
303309
* may appear in nested formatting that could prematurely trigger detection
@@ -310,22 +316,28 @@ const INLINE_SKIP_R =
310316
* Detect a sequence like **foo** or __foo__. Note that bold has a higher priority
311317
* than emphasized to support nesting of both since they share a delimiter.
312318
*/
313-
const TEXT_BOLD_R = new RegExp(`^([*_])\\1${INLINE_SKIP_R}\\1\\1(?!\\1)`)
319+
const TEXT_BOLD_R = new RegExp(
320+
`^([*_])\\1${LOOKAHEAD(1)}${INLINE_SKIP_R}\\1\\1(?!\\1)`
321+
)
314322

315323
/**
316324
* Detect a sequence like *foo* or _foo_.
317325
*/
318-
const TEXT_EMPHASIZED_R = new RegExp(`^([*_])${INLINE_SKIP_R}\\1(?!\\1)`)
326+
const TEXT_EMPHASIZED_R = new RegExp(
327+
`^([*_])${LOOKAHEAD(0)}${INLINE_SKIP_R}\\1(?!\\1)`
328+
)
319329

320330
/**
321331
* Detect a sequence like ==foo==.
322332
*/
323-
const TEXT_MARKED_R = new RegExp(`^(==)${INLINE_SKIP_R}\\1`)
333+
const TEXT_MARKED_R = new RegExp(`^(==)${LOOKAHEAD(0)}${INLINE_SKIP_R}\\1`)
324334

325335
/**
326336
* Detect a sequence like ~~foo~~.
327337
*/
328-
const TEXT_STRIKETHROUGHED_R = new RegExp(`^(~~)${INLINE_SKIP_R}\\1`)
338+
const TEXT_STRIKETHROUGHED_R = new RegExp(
339+
`^(~~)${LOOKAHEAD(0)}${INLINE_SKIP_R}\\1`
340+
)
329341

330342
/**
331343
* Special case for shortcodes like :big-smile: or :emoji:
@@ -558,7 +570,8 @@ function generateListRule(
558570
}
559571
}
560572

561-
const LINK_INSIDE = '(?:\\[[^\\[\\]]*(?:\\[[^\\[\\]]*\\][^\\[\\]]*)*\\]|[^\\[\\]])*'
573+
const LINK_INSIDE =
574+
'(?:\\[[^\\[\\]]*(?:\\[[^\\[\\]]*\\][^\\[\\]]*)*\\]|[^\\[\\]])*'
562575
const LINK_HREF_AND_TITLE =
563576
'\\s*<?((?:\\([^)]*\\)|[^\\s\\\\]|\\\\.)*?)>?(?:\\s+[\'"]([\\s\\S]*?)[\'"])?\\s*'
564577
const LINK_R = new RegExp(
@@ -1903,7 +1916,7 @@ export function compiler(
19031916
} as MarkdownToJSX.Rule<{ alt?: string; ref: string }>,
19041917

19051918
[RuleType.refLink]: {
1906-
_qualify: (source) => source[0] === '[' && source.indexOf('](') === -1,
1919+
_qualify: source => source[0] === '[' && source.indexOf('](') === -1,
19071920
_match: inlineRegex(REFERENCE_LINK_R),
19081921
_order: Priority.MAX,
19091922
_parse(capture, parse, state) {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"jest-serializer-html": "^7.1.0",
7878
"jest-watch-typeahead": "^2.2.2",
7979
"markdown-it": "^14.0.0",
80-
"markdown-to-jsx-latest": "npm:[email protected].10",
80+
"markdown-to-jsx-latest": "npm:[email protected].11",
8181
"microbundle": "^0.15.1",
8282
"microtime": "^3.1.1",
8383
"mkdirp": "^3.0.1",

yarn.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7114,12 +7114,12 @@ __metadata:
71147114
languageName: node
71157115
linkType: hard
71167116

7117-
"markdown-to-jsx-latest@npm:[email protected].10":
7118-
version: 7.7.10
7119-
resolution: "markdown-to-jsx@npm:7.7.10"
7117+
"markdown-to-jsx-latest@npm:[email protected].11":
7118+
version: 7.7.11
7119+
resolution: "markdown-to-jsx@npm:7.7.11"
71207120
peerDependencies:
71217121
react: ">= 0.14.0"
7122-
checksum: 10/f0c55a235e91adf28bf1cc654bd33e44a97c2601f6154800206172be621dd4236520c9934860f905ba478e195e408f224863187b09f22497d932093974fab040
7122+
checksum: 10/4697abffc1729a4d397e529043834848d294f26186c03187359fd7ad23efd4b1b3f757e3bcf3e27bdd75fe08bc2b51afc60ace3486f50bf13b76441f95426f2e
71237123
languageName: node
71247124
linkType: hard
71257125

@@ -7145,7 +7145,7 @@ __metadata:
71457145
jest-serializer-html: "npm:^7.1.0"
71467146
jest-watch-typeahead: "npm:^2.2.2"
71477147
markdown-it: "npm:^14.0.0"
7148-
markdown-to-jsx-latest: "npm:[email protected].10"
7148+
markdown-to-jsx-latest: "npm:[email protected].11"
71497149
microbundle: "npm:^0.15.1"
71507150
microtime: "npm:^3.1.1"
71517151
mkdirp: "npm:^3.0.1"

0 commit comments

Comments
 (0)