From b16f668795daa16eff915c416feefc74e5b3e4c4 Mon Sep 17 00:00:00 2001
From: Evan Jacobs <570070+quantizor@users.noreply.github.com>
Date: Tue, 12 Nov 2024 00:13:21 -0500
Subject: [PATCH] fix: false detection of nested list due to bad lookback cache
(#614)
* fix: false detection of nested list due to bad lookback cache
* chore: add changeset
---
.changeset/eighty-bees-think.md | 5 +++++
index.compiler.spec.tsx | 31 +++++++++++++++++++++++++++++--
index.tsx | 22 ++++++++++++++++------
3 files changed, 50 insertions(+), 8 deletions(-)
create mode 100644 .changeset/eighty-bees-think.md
diff --git a/.changeset/eighty-bees-think.md b/.changeset/eighty-bees-think.md
new file mode 100644
index 00000000..73e27786
--- /dev/null
+++ b/.changeset/eighty-bees-think.md
@@ -0,0 +1,5 @@
+---
+'markdown-to-jsx': patch
+---
+
+Fix issue with lookback cache resulting in false detection of lists inside lists in some scenarios
diff --git a/index.compiler.spec.tsx b/index.compiler.spec.tsx
index 08d3d39f..26388e6a 100644
--- a/index.compiler.spec.tsx
+++ b/index.compiler.spec.tsx
@@ -1760,6 +1760,33 @@ describe('lists', () => {
`)
})
+
+ it('regression #613 - list false detection inside inline syntax', () => {
+ render(
+ compiler(`
+- foo
+- bar **+ baz** qux **quux**
+ `)
+ )
+
+ expect(root.innerHTML).toMatchInlineSnapshot(`
+
+ -
+ foo
+
+ -
+ bar
+
+ + baz
+
+ qux
+
+ quux
+
+
+
+ `)
+ })
})
describe('GFM task lists', () => {
@@ -4314,12 +4341,12 @@ describe('options.renderRule', () => {
it('should allow arbitrary modification of content', () => {
render(
compiler('Hello.\n\n```latex\n$$f(X,n) = X_n + X_{n-1}$$\n```\n', {
- renderRule(defaultRenderer, node, renderChildren, state) {
+ renderRule(next, node, renderChildren, state) {
if (node.type === RuleType.codeBlock && node.lang === 'latex') {
return I'm latex.
}
- return defaultRenderer()
+ return next()
},
})
)
diff --git a/index.tsx b/index.tsx
index ad34347e..e94e8762 100644
--- a/index.tsx
+++ b/index.tsx
@@ -424,7 +424,7 @@ function generateListRule(
: UNORDERED_LIST_ITEM_PREFIX_R
return {
- match(source, state, prevCapture) {
+ match(source, state) {
// We only want to break into a list if we are at the start of a
// line. This is to avoid parsing "hi * there" with "* there"
// becoming a part of a list.
@@ -433,7 +433,7 @@ function generateListRule(
// lists can be inline, because they might be inside another list,
// in which case we can parse with inline scope, but need to allow
// nested lists inside this inline scope.
- const isStartOfLine = LIST_LOOKBEHIND_R.exec(prevCapture)
+ const isStartOfLine = LIST_LOOKBEHIND_R.exec(state.prevCapture)
const isListBlock = state.list || (!state.inline && !state.simple)
if (isStartOfLine && isListBlock) {
@@ -837,21 +837,28 @@ function parserFor(
): MarkdownToJSX.ParserResult[] {
let result = []
+ state.prevCapture = state.prevCapture || ''
+
// We store the previous capture so that match functions can
// use some limited amount of lookbehind. Lists use this to
// ensure they don't match arbitrary '- ' or '* ' in inline
// text (see the list rule for more information).
- let prevCapture = ''
while (source) {
let i = 0
while (i < ruleList.length) {
const ruleType = ruleList[i]
const rule = rules[ruleType]
- const capture = rule.match(source, state, prevCapture)
+
+ const capture = rule.match(source, state)
if (capture) {
const currCaptureString = capture[0]
+
+ // retain what's been processed so far for lookbacks
+ state.prevCapture += currCaptureString
+
source = source.substring(currCaptureString.length)
+
const parsed = rule.parse(capture, nestedParse, state)
// We also let rules override the default type of
@@ -863,8 +870,6 @@ function parserFor(
}
result.push(parsed)
-
- prevCapture = currCaptureString
break
}
@@ -872,6 +877,9 @@ function parserFor(
}
}
+ // reset on exit
+ state.prevCapture = ''
+
return result
}
@@ -2018,6 +2026,8 @@ export namespace MarkdownToJSX {
key?: React.Key
/** true if in a list */
list?: boolean
+ /** used for lookbacks */
+ prevCapture?: string
/** true if parsing in inline context w/o links */
simple?: boolean
}