Skip to content

fix: strip a tab that follows spaces in an indented code block - #4080

Merged
UziTech merged 2 commits into
markedjs:masterfrom
Kjubikstronk:tab-expansion-block-structure
Sep 8, 2026
Merged

fix: strip a tab that follows spaces in an indented code block#4080
UziTech merged 2 commits into
markedjs:masterfrom
Kjubikstronk:tab-expansion-block-structure

Conversation

@Kjubikstronk

Copy link
Copy Markdown
Contributor

Marked version: 18.0.11 (53cb13f)

Markdown flavor: CommonMark

Description

CommonMark example 2. A line indented with spaces and then a tab keeps the tab as code content:

in     "··\tfoo\tbaz\t\tbim"

want   <pre><code>foo\tbaz\t\tbim
got    <pre><code>\tfoo\tbaz\t\tbim

A tab advances to the next four column stop, so ··\t is four columns of indentation and all of it belongs to the block structure, not to the content.

Cause

codeRemoveIndent is /^(?: {1,4}| {0,3}\t)/gm. Regex alternation takes the first branch that matches, so on ··\tfoo the {1,4} branch matches the two spaces and stops, and the tab is left in the content. The tab branch only ever gets a chance when the line has no leading space at all, which is why example 1 (\tfoo…) has always passed and example 2 has not.

Swapping the order fixes it, because the tab branch already carries its own {0,3} prefix:

codeRemoveIndent: /^(?: {0,3}\t| {1,4})/gm,

Every space-only input is unaffected, since the tab branch cannot match without a tab:

line before after
··\tfoo \tfoo foo
···\tfoo \tfoo foo
\tfoo foo foo
····foo foo foo
·····foo ·foo ·foo
······foo ··foo ··foo

The regex is used in one place, Tokenizer.ts:108, inside the indented code tokenizer, so it cannot reach fenced content.

Measurement

Whole spec through the CommonMark repository's own test/normalize.py, 0.31.2, gfm: false:

fails
18.0.11 28
with this change 27

The one that moves is example 2, and nothing else moves in either direction. That measurement matters here because marked's own suite cannot see this bug: htmlIsEqual leaves ignoreWhitespaces at true, and the difference is indentation inside pre. Example 2 reports as passing today with no shouldFail flag against it, the same blind spot as #4073.

Contributor

  • Test(s) exist to ensure functionality and minimize regression (if no tests added, list tests covering this PR); or,
  • no tests required for this PR.
  • If submitting new feature, it has been documented in the appropriate places.

Six tests in test/unit/marked.test.js, asserting exact output for the reason above. Three fail without the change; the other three are controls that must pass either way, and do. Full spec suite (1789) and unit suite (200) pass.

Committer

In most cases, this should be a different person than the contributor.


Context: this is the first half of the tab work discussed in #4050. Example 6 is the other half and I am not sending it, for reasons I have written up in that issue.

codeRemoveIndent tried its space alternative first, so a line indented
with spaces then a tab lost only the spaces and kept the tab as content.
Trying the tab alternative first matches CommonMark example 2, where a
tab advances to the next four column stop and the whole run is indent.
@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown

@Kjubikstronk is attempting to deploy a commit to the MarkedJS Team on Vercel.

A member of the Team first needs to authorize it.

Uses renderExact so the fixture compares exactly, which htmlIsEqual
does not, and keeps the assertions out of marked.test.js.
@Kjubikstronk

Copy link
Copy Markdown
Contributor Author

Moved the tests to test/specs/new/tabs_indented_code.md with renderExact: true, in e6490be, before you have to ask. @UziTech pointed out renderExact on #4075 and it is the right home for these: an exact comparison, so the fixture can hold a whitespace-inside-pre fix that htmlIsEqual cannot see.

Same six cases the unit tests covered, controls included:

··\tfoo\tbaz\t\tbim   ->  <pre><code>foo\tbaz\t\tbim
···\tthree spaces…        ->  <pre><code>three spaces…
\tlone tab                ->  <pre><code>lone tab
····four spaces           ->  <pre><code>four spaces
·····five spaces          ->  <pre><code>·five spaces

Reverting the alternation order fails the fixture, so it has teeth. marked.test.js is back to untouched, and the spec and unit suites are both green.

@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
marked-website Ready Ready Preview Sep 2, 2026 2:32pm UTC

Request Review

@UziTech UziTech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 💯

@UziTech
UziTech merged commit dbb393d into markedjs:master Sep 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants