From 20e02e70e9644b3b12edbc578333db26f4b66786 Mon Sep 17 00:00:00 2001 From: Josh Doman Date: Sun, 17 Aug 2025 19:16:02 -0400 Subject: [PATCH] fix(parser): Protect inline code from formatting while allowing spans --- src/parser.js | 13 ++++ test/overtype.test.js | 160 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) diff --git a/src/parser.js b/src/parser.js index f53c694..f4a43ae 100644 --- a/src/parser.js +++ b/src/parser.js @@ -155,9 +155,22 @@ export class MarkdownParser { let html = text; // Order matters: parse code first to avoid conflicts html = this.parseInlineCode(html); + // Use placeholders to protect inline code while preserving formatting spans + const codeBlocks = new Map(); + html = html.replace(/(.*?<\/code>)/g, (match) => { + // Prevent conflicts with private use area Unicode + const placeholder = `\uE000${codeBlocks.size}\uE001`; + codeBlocks.set(placeholder, match); + return placeholder; + }); + // Process other inline elements on text with placeholders html = this.parseLinks(html); html = this.parseBold(html); html = this.parseItalic(html); + // Restore code blocks + codeBlocks.forEach((codeBlock, placeholder) => { + html = html.replace(placeholder, codeBlock); + }); return html; } diff --git a/test/overtype.test.js b/test/overtype.test.js index 581fcc2..9c56dd3 100644 --- a/test/overtype.test.js +++ b/test/overtype.test.js @@ -198,6 +198,166 @@ This is **bold** and *italic*. assert(actual.includes('class="raw-line"'), 'Raw line display', 'Should show raw line for active line'); })(); +// Test: Inline code with underscores and stars +(() => { + const tests = [ + { + input: '`OP_CAT_DOG`', + expected: '
`OP_CAT_DOG`
', + description: 'Should not italicize underscores inside code' + }, + { + input: '`OP_CAT` and *dog*', + expected: '
`OP_CAT` and *dog*
', + description: 'Should italicize outside code but not inside' + }, + { + input: '`function_name_here` _should work_', + expected: '
`function_name_here` _should work_
', + description: 'Should handle mixed code and italic with underscores' + }, + { + input: '`__init__` method', + expected: '
`__init__` method
', + description: 'Should not bold double underscores inside code' + }, + { + input: 'Text `with_code` and **bold**', + expected: '
Text `with_code` and **bold**
', + description: 'Should handle code with underscores and separate bold' + }, + { + input: '`*asterisk*` and _underscore_', + expected: '
`*asterisk*` and _underscore_
', + description: 'Should not italicize asterisks inside code' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Inline code protection: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + +// Test: Formatting that spans across code blocks +(() => { + const tests = [ + { + input: '*cat `test` dog*', + expected: '
*cat `test` dog*
', + description: 'Should italicize text that spans across code blocks' + }, + { + input: '**bold `code_here` more bold**', + expected: '
**bold `code_here` more bold**
', + description: 'Should bold text that spans across code blocks' + }, + { + input: '_italic `with_underscores` still italic_', + expected: '
_italic `with_underscores` still italic_
', + description: 'Should handle italic with underscores spanning code' + }, + { + input: '__bold `code` and `more_code` bold__', + expected: '
__bold `code` and `more_code` bold__
', + description: 'Should bold text spanning multiple code blocks' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Spanning code: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + +// Test: Multiple inline code blocks with external formatting +(() => { + const tests = [ + { + input: '`first_code` and `second_code` with *italic*', + expected: '
`first_code` and `second_code` with *italic*
', + description: 'Should handle multiple code blocks with external formatting' + }, + { + input: '*Before `__code__` between `_more_code_` after*', + expected: '
*Before `__code__` between `_more_code_` after*
', + description: 'Should handle italic spanning multiple protected code blocks' + }, + { + input: '**Text `code1` middle `code2` end**', + expected: '
**Text `code1` middle `code2` end**
', + description: 'Should bold across multiple code blocks' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Multiple code + format: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + +// Test: Complex nested scenarios +(() => { + const tests = [ + { + input: 'Normal `code_block` and **bold `with_code` bold** text', + expected: '
Normal `code_block` and **bold `with_code` bold** text
', + description: 'Should handle mixed standalone and spanning formatting' + }, + { + input: '*italic* `code_here` **bold `spanning_code` bold**', + expected: '
*italic* `code_here` **bold `spanning_code` bold**
', + description: 'Should handle multiple different formatting types with code' + }, + { + input: '[Link `with_code` text](url) and `regular_code`', + expected: '
[Link `with_code` text](url) and `regular_code`
', + description: 'Should handle links spanning code blocks' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Complex nested code: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + +// Test: Edge cases that should NOT be formatted +(() => { + const tests = [ + { + input: '`**not_bold**`', + expected: '
`**not_bold**`
', + description: 'Should not process bold markers inside code' + }, + { + input: '`__also_not_bold__`', + expected: '
`__also_not_bold__`
', + description: 'Should not process underscore bold markers inside code' + }, + { + input: '`*not_italic*`', + expected: '
`*not_italic*`
', + description: 'Should not process asterisk italic markers inside code' + }, + { + input: '`_not_italic_`', + expected: '
`_not_italic_`
', + description: 'Should not process underscore italic markers inside code' + }, + { + input: '`[not_a_link](url)`', + expected: '
`[not_a_link](url)`
', + description: 'Should not process link markers inside code' + } + ]; + + tests.forEach(test => { + const actual = MarkdownParser.parseLine(test.input); + assert(htmlEqual(actual, test.expected), `Code protection edge cases: ${test.input}`, `${test.description}. Expected: ${test.expected}, Got: ${actual}`); + }); +})(); + // ===== Integration Tests ===== console.log('\n🔧 Integration Tests\n');