Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/Tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,11 @@ export class _Tokenizer<ParserOutput = string, RendererOutput = string> {
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();

Expand All @@ -278,7 +282,9 @@ export class _Tokenizer<ParserOutput = string, RendererOutput = string> {
} 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;
Expand Down
25 changes: 25 additions & 0 deletions test/unit/marked.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,31 @@ describe('marked unit', () => {
});
});

describe('lists with tabs', () => {
it('should treat tabs after list markers the same as spaces', () => {
// Simple ordered lists
const mdSpaces = '1. Some Text\n2. Some Text\n3. Some Text\n';
const mdTabs = '1.\tSome Text\n2.\tSome Text\n3.\tSome Text\n';
const htmlSpaces = marked.parse(mdSpaces);
const htmlTabs = marked.parse(mdTabs);
assert.strictEqual(htmlSpaces, htmlTabs);

// Ordered lists after a paragraph (text before the list) — reported scenario
const mdSpacesPre = 'Paragraph before list\n\n1. Some Text\n2. Some Text\n3. Some Text\n';
const mdTabsPre = 'Paragraph before list\n\n1.\tSome Text\n2.\tSome Text\n3.\tSome Text\n';
const htmlSpacesPre = marked.parse(mdSpacesPre);
const htmlTabsPre = marked.parse(mdTabsPre);
assert.strictEqual(htmlSpacesPre, htmlTabsPre);

// Unordered lists (bullets)
const ulSpaces = '- Some Text\n- Some Text\n- Some Text\n';
const ulTabs = '-\tSome Text\n-\tSome Text\n-\tSome Text\n';
const htmlUlSpaces = marked.parse(ulSpaces);
const htmlUlTabs = marked.parse(ulTabs);
assert.strictEqual(htmlUlSpaces, htmlUlTabs);
});
});

describe('parseInline', () => {
it('should parse inline tokens', () => {
const md = '**strong** _em_';
Expand Down