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(` + + `) + }) }) 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 }