diff --git a/src/Tokenizer.ts b/src/Tokenizer.ts index 925074e71a..113df71c74 100644 --- a/src/Tokenizer.ts +++ b/src/Tokenizer.ts @@ -267,7 +267,11 @@ export class _Tokenizer { raw = cap[0]; src = src.substring(raw.length); - let line = cap[2].split('\n', 1)[0].replace(this.rules.other.listReplaceTabs, (t: string) => ' '.repeat(3 * t.length)); + // Normalize the first line by replacing tabs with 4 spaces so we can + // compute indent and slice the correct number of characters. This keeps + // behavior consistent with how subsequent lines are normalized. + const rawLineFirst = cap[2].split('\n', 1)[0]; + let line = rawLineFirst.replace(this.rules.other.tabCharGlobal, ' '); let nextLine = src.split('\n', 1)[0]; let blankLine = !line.trim(); @@ -278,7 +282,9 @@ export class _Tokenizer { } else if (blankLine) { indent = cap[1].length + 1; } else { - indent = cap[2].search(this.rules.other.nonSpaceChar); // Find first non-space char + // Treat tabs as 4 spaces when finding first non-space char so a leading tab + // isn't considered a non-space and we compute indent correctly. + indent = line.search(this.rules.other.nonSpaceChar); // Find first non-space char indent = indent > 4 ? 1 : indent; // Treat indented code blocks (> 4 spaces) as having only 1 indent itemContents = line.slice(indent); indent += cap[1].length; diff --git a/test/specs/new/list_with_tabs.html b/test/specs/new/list_with_tabs.html new file mode 100644 index 0000000000..45230fac3f Binary files /dev/null and b/test/specs/new/list_with_tabs.html differ diff --git a/test/specs/new/list_with_tabs.md b/test/specs/new/list_with_tabs.md new file mode 100644 index 0000000000..61151ea6b4 --- /dev/null +++ b/test/specs/new/list_with_tabs.md @@ -0,0 +1,13 @@ +1. Some Text +2. Some Text +3. Some Text + +Paragraph before list + +1. Some Text +2. Some Text +3. Some Text + +- Some Text +- Some Text +- Some Text diff --git a/test/unit/marked.test.js b/test/unit/marked.test.js index 7f0f130ce7..ca7cead0ed 100644 --- a/test/unit/marked.test.js +++ b/test/unit/marked.test.js @@ -2,6 +2,9 @@ import { Marked, Renderer, lexer, parseInline, getDefaults, walkTokens, defaults import { timeout } from './utils.js'; import assert from 'node:assert'; import { describe, it, beforeEach, mock } from 'node:test'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'node:url'; describe('marked unit', () => { let marked; @@ -56,6 +59,15 @@ describe('marked unit', () => { }); }); + describe('lists with tabs', () => { + it('should correctly render markdown from the list_with_tabs fixture', () => { + const markdown = fs.readFileSync(mdPath, 'utf8'); + const html = marked.parse(markdown); + assert.ok(html.includes('
    ') && html.includes('
      '), 'Expected list HTML not found'); + }); +}); + + describe('parseInline', () => { it('should parse inline tokens', () => { const md = '**strong** _em_';