Skip to content

Commit

Permalink
fix: false detection of nested list due to bad lookback cache (#614)
Browse files Browse the repository at this point in the history
* fix: false detection of nested list due to bad lookback cache

* chore: add changeset
  • Loading branch information
quantizor authored Nov 12, 2024
1 parent 3a04b9e commit b16f668
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-bees-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'markdown-to-jsx': patch
---

Fix issue with lookback cache resulting in false detection of lists inside lists in some scenarios
31 changes: 29 additions & 2 deletions index.compiler.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,33 @@ describe('lists', () => {
</div>
`)
})

it('regression #613 - list false detection inside inline syntax', () => {
render(
compiler(`
- foo
- bar **+ baz** qux **quux**
`)
)

expect(root.innerHTML).toMatchInlineSnapshot(`
<ul>
<li>
foo
</li>
<li>
bar
<strong>
+ baz
</strong>
qux
<strong>
quux
</strong>
</li>
</ul>
`)
})
})

describe('GFM task lists', () => {
Expand Down Expand Up @@ -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 <div key={state.key}>I'm latex.</div>
}

return defaultRenderer()
return next()
},
})
)
Expand Down
22 changes: 16 additions & 6 deletions index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -863,15 +870,16 @@ function parserFor(
}

result.push(parsed)

prevCapture = currCaptureString
break
}

i++
}
}

// reset on exit
state.prevCapture = ''

return result
}

Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit b16f668

Please sign in to comment.