From 82f8ada9725259023a48a2e45d80d05fd7776856 Mon Sep 17 00:00:00 2001 From: Anand Hegde Date: Sat, 19 Sep 2026 16:56:54 +0530 Subject: [PATCH] fix(latex): do not re-escape a dollar that is already escaped (#15602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Split out of #15596 on review feedback — this is an independent, pre-existing bug in the currency pass, so it does not belong in that PR. The currency pass escapes any `$` followed by a digit without looking at what precedes it: ```ts preprocessedContent = preprocessedContent.replace(/\$(?=\d)/g, '\\$'); ``` When the dollar is **already escaped**, that turns an odd backslash run even: ``` input: \$4 (an escaped dollar — the author wants a literal "$4") output: \\$4 (a literal backslash, then an unescaped `$`) ``` `MarkdownPreprocessorManager.process` runs this before `_markdownToAst`, so remark-math sees that bare `$` and can take it as an active delimiter — the opposite of what escaping it was for. ### The fix Escape only a dollar whose preceding backslash run is **even**, keeping the run itself: ```ts preprocessedContent = preprocessedContent.replace( /(? ## Summary by CodeRabbit - **Bug Fixes** - Improved LaTeX handling for currency values by escaping dollar signs only when required. - Preserved already-escaped dollar signs and backslash sequences correctly. - **Tests** - Added coverage for plain currency amounts, already-escaped dollar signs, and even backslash runs. --- .../latex-currency-escape.unit.spec.ts | 30 +++++++++++++++++++ .../src/adapters/markdown/preprocessor.ts | 15 ++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 blocksuite/affine/all/src/__tests__/adapters/latex-currency-escape.unit.spec.ts diff --git a/blocksuite/affine/all/src/__tests__/adapters/latex-currency-escape.unit.spec.ts b/blocksuite/affine/all/src/__tests__/adapters/latex-currency-escape.unit.spec.ts new file mode 100644 index 0000000000..9feae80c1a --- /dev/null +++ b/blocksuite/affine/all/src/__tests__/adapters/latex-currency-escape.unit.spec.ts @@ -0,0 +1,30 @@ +import { preprocessLatex } from '@blocksuite/affine-block-latex'; +import { describe, expect, test } from 'vitest'; + +describe('latex preprocessor currency escaping', () => { + test.each([ + ['a plain price', 'costs $5 today', 'costs \\$5 today'], + ['a bare amount', '$4', '\\$4'], + ['two prices', 'costs $5 and $10', 'costs \\$5 and \\$10'], + ])('escapes %s', (_, markdown, expected) => { + expect(preprocessLatex(markdown)).toBe(expected); + }); + + describe('a dollar that is already escaped', () => { + // Escaping it again turns the odd backslash run even, which leaves a + // literal backslash followed by an unescaped `$` for remark-math. + test.each([ + ['one backslash', 'costs \\$4 today'], + ['three backslashes', '\\\\\\$4'], + ['several amounts', '\\$4 and \\$10'], + ])('is left alone with %s', (_, markdown) => { + expect(preprocessLatex(markdown)).toBe(markdown); + }); + }); + + test('an even backslash run still has its dollar escaped', () => { + // `\\` is a literal backslash, so the `$` after it is unescaped and is a + // genuine currency candidate. The run itself must survive untouched. + expect(preprocessLatex('costs \\\\$4 today')).toBe('costs \\\\\\$4 today'); + }); +}); diff --git a/blocksuite/affine/blocks/latex/src/adapters/markdown/preprocessor.ts b/blocksuite/affine/blocks/latex/src/adapters/markdown/preprocessor.ts index 7f1128a3bb..39f0d957c2 100644 --- a/blocksuite/affine/blocks/latex/src/adapters/markdown/preprocessor.ts +++ b/blocksuite/affine/blocks/latex/src/adapters/markdown/preprocessor.ts @@ -32,7 +32,7 @@ function escapeMhchem(text: string) { * @param content - The content to preprocess * @returns The preprocessed content */ -function preprocessLatex(content: string) { +export function preprocessLatex(content: string) { // Protect code blocks const codeBlocks: string[] = []; let preprocessedContent = content; @@ -54,8 +54,17 @@ function preprocessLatex(content: string) { } ); - // Escape dollar signs that are likely currency indicators - preprocessedContent = preprocessedContent.replace(/\$(?=\d)/g, '\\$'); + // Escape dollar signs that are likely currency indicators. + // + // A dollar preceded by an odd number of backslashes is already escaped, so + // adding another backslash makes the run even: `\$4` becomes `\\$4`, which is + // a literal backslash followed by an unescaped `$` that remark-math can then + // treat as a delimiter. Only escape a dollar whose preceding backslash run is + // even, and keep that run as it was. + preprocessedContent = preprocessedContent.replace( + /(?