Skip to content

Add MDS064 rule for ATX heading whitespace and indentation - #347

Merged
jeduden merged 12 commits into
mainfrom
claude/fix-atx-heading-whitespace-2lGCo
May 19, 2026
Merged

Add MDS064 rule for ATX heading whitespace and indentation#347
jeduden merged 12 commits into
mainfrom
claude/fix-atx-heading-whitespace-2lGCo

Conversation

@jeduden

@jeduden jeduden commented May 18, 2026

Copy link
Copy Markdown
Owner

Implements the MDS064 rule to check and fix ATX heading whitespace and indentation issues, completing plan 176.

Summary

This PR adds a new linting rule that validates ATX heading formatting, including:

  • Missing or extra spaces after opening # markers (MD018, MD019)
  • Closing # markers preceded by whitespace (MD020/MD021 — see note below)
  • Headings indented away from column 1 (MD023)

The rule provides automatic fixes that normalize headings to # Content (open ATX style).

Key Changes

  • New rule MDS064 (internal/rules/atxheadingwhitespace/rule.go):

    • Detects missing space after opening hashes (#Heading) → fixes to # Heading
    • Detects multiple spaces after opening hashes (# Heading) → fixes to # Heading
    • Detects a tab after opening hash (#\tHeading) → fixes to # Heading
    • Detects closing ATX marker preceded by whitespace (# Heading #, # Heading #) → strips the closing marker
    • Detects indented headings → dedents to column 1
    • Skips code blocks and PI directive bodies
    • Ignores lines with 7+ hashes (not valid ATX)
    • Skips #+digit on indented lines (avoids false positives on issue/PR references like #22 in list continuations)
  • MD020 note (#Heading#): Per CommonMark, a trailing # run is only a closing ATX marker when preceded by whitespace. #Heading# has no space before the trailing #, so it is treated as content — MD018 fires (missing opening space), fix produces # Heading#. MD020 is therefore marked partial in the coverage matrix. This is intentional and matches CommonMark's spec.

  • Comprehensive test suite (internal/rules/atxheadingwhitespace/rule_test.go)

  • Fixture tests under internal/rules/MDS064-atx-heading-whitespace/

  • Rule README with description, examples, and metadata

  • Updated markdownlint coverage matrix and rule index

  • Plan 176 marked complete

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

Copilot AI review requested due to automatic review settings May 18, 2026 22:05
@codecov

codecov Bot commented May 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.05%. Comparing base (cb59be2) to head (f8ffc69).

Additional details and impacted files
Components Coverage Δ
Go 96.02% <100.00%> (+0.01%) ⬆️
TypeScript 99.35% <ø> (ø)

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds the new MDS059 (atx-heading-whitespace) rule to mdsmith, implementing plan 176 by detecting and auto-fixing ATX heading spacing, optional closing-marker formatting, and heading indentation.

Changes:

  • Added MDS059 rule implementation plus unit tests.
  • Added rule documentation and integration fixtures (good/bad/fixed) and registered the rule for integration testing.
  • Updated plan tracking and the markdownlint coverage matrix to reflect the new coverage.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
plan/176_atx-heading-whitespace.md Marks plan 176 as complete and checks off acceptance criteria.
PLAN.md Updates the plan index row for 176 to ✅.
internal/rules/MDS059-atx-heading-whitespace/README.md Adds rule documentation and configuration guidance.
internal/rules/MDS059-atx-heading-whitespace/good/default.md Adds “good” integration fixture coverage.
internal/rules/MDS059-atx-heading-whitespace/bad/*.md Adds “bad” fixtures with expected diagnostics for each violation type.
internal/rules/MDS059-atx-heading-whitespace/fixed/*.md Adds “fixed” fixtures asserting autofix output.
internal/rules/index.md Registers MDS059 in the rules index catalog.
internal/rules/headingwhitespace/rule.go Implements the MDS059 check + fix logic.
internal/rules/headingwhitespace/rule_test.go Adds unit tests covering diagnostics and fix behavior.
internal/integration/rules_test.go Registers the new rule package in the integration test harness.
docs/research/markdownlint-coverage/README.md Updates the markdownlint coverage table to map MD018–MD021 and MD023 to MDS059.

Comment thread internal/rules/headingwhitespace/rule.go Outdated
Comment thread internal/rules/headingwhitespace/rule.go Outdated
Comment thread internal/rules/headingwhitespace/rule.go Outdated
Comment thread internal/rules/atxheadingwhitespace/rule.go
Comment thread internal/rules/MDS059-atx-heading-whitespace/README.md Outdated
@jeduden
jeduden requested a review from Copilot May 19, 2026 09:49
@jeduden
jeduden force-pushed the claude/fix-atx-heading-whitespace-2lGCo branch from 38e07b3 to 60bff34 Compare May 19, 2026 09:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comment thread docs/research/markdownlint-coverage/README.md
Comment thread internal/rules/atxheadingwhitespace/rule.go
Comment thread internal/rules/atxheadingwhitespace/rule.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

claude added 4 commits May 19, 2026 15:35
Implements rule MDS059 covering the markdownlint ATX-heading
whitespace family (MD018–MD021) and heading indentation (MD023).

Detects missing space, multiple spaces, closed ATX markers, and
leading indentation on raw ATX heading lines. Autofix normalizes
every defect to open ATX with a single space and no leading indent.
Skips fenced/indented code blocks and directive bodies.

Closes plan 176.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
Three statement-coverage gaps in rule.go — the all-hash guard in
checkClosingATX, the level-out-of-range guard in normalizeLine, and
the empty-string early return in extractContent — are only reachable
by calling the unexported functions directly.  Add
rule_coverage_test.go to hit all three paths and reach 100% statement
coverage.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
- Add \r to TrimRight calls so CRLF line endings never produce
  spurious "missing space after # in heading" diagnostics on
  empty ATX headings (##\r\n).

- Flag a tab immediately after opening hashes as "missing space"
  and normalise it to a single space (#\tHeading → # Heading).

- Only treat a trailing # run as a closing ATX marker when
  preceded by whitespace (CommonMark rule). Previously, content
  like "# C#" was wrongly diagnosed and mangled by Fix. The
  case with no preceding space now returns nil cleanly.
  Consequence: #Heading# is fixed only for MD018 (missing
  opening space) → # Heading#; MD020 coverage downgraded to
  partial in the coverage matrix.

- Update rule README to reflect accurate behaviour.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
- Coverage matrix: correct preamble to 41 of 52 (2 partial),
  11 remaining; remove completed plan 176 from plan list.

- normalizeLine: preserve trailing \r when the original line has
  one so CRLF files don't get mixed LF/CRLF after a partial fix.
  Test: TestFix_PreservesCRLFOnRewrittenLines.

- Add TestCheck_SkipsPIBlock and TestFix_PreservesPIBlock to pin
  the directive-body (PI block) skip behaviour for Check and Fix.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
@jeduden
jeduden force-pushed the claude/fix-atx-heading-whitespace-2lGCo branch from 6cdfb2f to 8f3c3cd Compare May 19, 2026 15:39
@jeduden
jeduden requested a review from Copilot May 19, 2026 15:39
shutdown() used request(), which reads exactly one frame. After a
rename the server emits publishDiagnostics notifications that can
arrive before the shutdown response, causing the id==99 assertion
to see nil. Switch to requestPickResult(), which already loops past
interleaved server frames, matching the pattern used by every other
multi-step LSP test helper.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

internal/rules/headingwhitespace/rule.go:196

  • extractContent treats trailing hashes without preceding whitespace as content and therefore normalizes #Heading# to # Heading# (retaining the trailing #). If the intent is to normalize closed-ATX headings to open style (plan 176), this needs to strip the trailing hash run for the MD020 case while still preserving legitimate content like # C#.
	// Trailing hashes not preceded by whitespace are content, not a closing marker.
	if s[hashStart-1] != ' ' && s[hashStart-1] != '\t' {
		return s
	}
	return strings.TrimRight(s[:hashStart], " \t")

Comment thread internal/rules/atxheadingwhitespace/rule.go
Comment thread internal/rules/atxheadingwhitespace/rule.go
Comment thread plan/176_atx-heading-whitespace.md Outdated
Comment thread docs/research/markdownlint-coverage/README.md
Comment thread internal/rules/atxheadingwhitespace/rule_test.go
@jeduden jeduden changed the title Add MDS059 rule for ATX heading whitespace and indentation Add MDS064 rule for ATX heading whitespace and indentation May 19, 2026
Split the acceptance criterion that covered both `# Heading #` and
`#Heading#` into separate bullets. The `#Heading#` bullet now explicitly
notes that per CommonMark a trailing `#` without preceding whitespace is
content, not a closing marker, so MD020 is partial for that case. This
matches the implementation and resolves the Copilot review comment about
the checked-off criterion contradicting the fix output.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Comment thread internal/rules/headingwhitespace/rule.go Outdated
…ading"

leadingSpaces() counts tabs as well as spaces, so the check at line 71
fires for mixed whitespace like "# \tHeading" (space then tab). The old
message "multiple spaces after # in heading" was inaccurate in that case.
Rename to "multiple spaces or tabs after # in heading" and add tests for
the space+tab pattern.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

plan/176_atx-heading-whitespace.md:8

  • The plan front matter still describes this as a provisional “MDS060” rule (and likely has other MDS060 references elsewhere). Since the PR implements MDS064, update the plan text so the recorded design/summary matches what was actually shipped.
status: "✅"
model: sonnet
depends-on: []
summary: >-
  New rule MDS060 (provisional) covering the markdownlint

Comment thread internal/integration/rules_test.go
Comment thread internal/rules/headingwhitespace/rule.go Outdated
… register in all.go

The package name headingwhitespace did not follow the repo convention
of deriving the directory name from the rule name with hyphens removed
(blockquote-whitespace → blockquotewhitespace, list-marker-space →
listmarkerspace). Rename to atxheadingwhitespace for consistency.

Also fixes a production bug: the package was never blank-imported in
internal/rules/all/all.go, so MDS064 was not registered and would
never run in cmd/mdsmith. Add the import in alphabetical order.

Also updates plan/176 front matter and design text to replace the
provisional MDS060 ID with the shipped MDS064.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
claude added 2 commits May 19, 2026 16:08
…ssue refs

Lines like "  #22 \"Mandatory headings\"" and "  #288.**" are GitHub
issue/PR references soft-wrapped inside list items, not malformed ATX
headings. Computing `after` before the MD023 check and returning nil
when after[0] is a digit avoids flagging these lines.

Add TestCheck_IssueReference, TestCheck_IssueReferenceAtColumn1, and
TestFix_IssueReferenceUnchanged to pin the behaviour.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz
The import was left at the position where headingwhitespace used to sit
(after headingstyle) instead of its correct alphabetical slot after
ambiguousemphasis. Moves it so gofmt/goimports is satisfied.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread internal/rules/MDS064-atx-heading-whitespace/README.md Outdated
… content

The README said the opening hashes must be followed by "exactly one space"
without qualification, implying empty headings like "##" are invalid. Reword
to make clear the space check only applies when the heading has content;
empty headings are valid and produce no diagnostic.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread internal/rules/atxheadingwhitespace/rule.go Outdated
The previous guard returned nil for any '#'+digit line regardless of
indentation. That silenced MD018 for genuine malformed headings like
#1Heading or ##22Title at column 1.

Scope the guard to leading>0: a '#'+digit run on an indented line is
almost certainly a soft-wrapped issue/PR reference (#22, #288) — at
column 1 it is a missing-space defect and is flagged normally.

Add TestCheck_DigitAtColumn1IsFlagged and TestFix_DigitAtColumn1 to
pin the column-1 behaviour and prevent regression.

https://claude.ai/code/session_01KCANC7X1jkYcLdAUqmBeaz

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 1 comment.

Comment thread internal/rules/atxheadingwhitespace/rule.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

@jeduden
jeduden merged commit 3d9d619 into main May 19, 2026
30 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