Skip to content

feat: useTopLevelHeading - #11286

Merged
Netail merged 4 commits into
biomejs:mainfrom
Netail:feat/use-top-level-heading
Aug 11, 2026
Merged

feat: useTopLevelHeading#11286
Netail merged 4 commits into
biomejs:mainfrom
Netail:feat/use-top-level-heading

Conversation

@Netail

@Netail Netail commented Aug 9, 2026

Copy link
Copy Markdown
Member

Summary

Added Markdownlint's MD041 which requires markdown documents to start with a top-level heading (heading 1)

One thing that's different tho, is that markdownlint checks in the HTML block if the first tag is <h1>, which would require some embedding (for now, I've marked a html block as fine)

Closes #11263

Test Plan

Unit tests

Docs

@changeset-bot

changeset-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: bad98a9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

✅ Organic activity

No automation signals detected in the analyzed events.

View full analysis →

This is an automated analysis by AgentScan

@github-actions github-actions Bot added A-Project Area: project A-Linter Area: linter A-Parser Area: parser A-Diagnostic Area: diagnostocis L-Markdown Language: Markdown labels Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 80399467-f04f-4559-8c03-9dd74fd4f5fd

📥 Commits

Reviewing files that changed from the base of the PR and between 78a0546 and bad98a9.

📒 Files selected for processing (1)
  • crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs

Walkthrough

Adds the nursery useTopLevelHeading Markdown lint rule. The rule checks the first meaningful block and accepts level-1 ATX or setext headings. It ignores leading comments, newline blocks, and continuation indentation. HTML blocks and thematic breaks are exempt. The change adds syntax helpers, rule options, diagnostics, fixtures, test wiring, workflow configuration, and updated Markdown rule templates.

Possibly related PRs

Suggested reviewers: ematipico

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding the useTopLevelHeading Markdown lint rule.
Description check ✅ Passed The description explains the MD041 implementation, its HTML-block behaviour, tests, and linked issue.
Linked Issues check ✅ Passed The changes implement the useTopLevelHeading rule, add supporting syntax APIs, options, fixtures, and tests requested by issue [#11263].
Out of Scope Changes check ✅ Passed The changes support the new Markdown lint rule and its testing and code-generation paths; no unrelated code changes are evident.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
crates/biome_markdown_syntax/src/header_ext.rs (1)

11-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add rustdoc for the new public rule API.

Document the public methods and options type.

  • crates/biome_markdown_syntax/src/header_ext.rs#L11-L21: Add contracts for is_level_1 and is_level_2.
  • crates/biome_rule_options/src/use_top_level_heading.rs#L3-L6: Document UseTopLevelHeadingOptions as the configuration type for useTopLevelHeading.

As per coding guidelines, use rustdoc for new features and rule options.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/biome_markdown_syntax/src/header_ext.rs` around lines 11 - 21, Add
rustdoc contracts above MdSetextHeader::is_level_1 and
MdSetextHeader::is_level_2 in crates/biome_markdown_syntax/src/header_ext.rs
(lines 11-21), and document UseTopLevelHeadingOptions as the configuration type
for useTopLevelHeading in crates/biome_rule_options/src/use_top_level_heading.rs
(lines 3-6); no other sites require changes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/breezy-lines-peel.md:
- Line 5: Correct the Markdown link for the useTopLevelHeading rule in the
changeset so it points to that rule’s documentation page rather than
no-invalid-property-init-value, while preserving the existing rule text and
changeset format.

In
`@crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/heading-1.md`:
- Line 2: Update the headings in heading-1.md (line 2) and setext-heading-1.md
(line 2) to use “Top-level heading” consistently, replacing “Top level heading”
in both fixtures.

In
`@crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/yaml.md`:
- Line 8: Update the heading in the useTopLevelHeading YAML fixture from ATX
syntax to the repository’s setext heading style required by MD003, while
preserving its level and heading text.

---

Nitpick comments:
In `@crates/biome_markdown_syntax/src/header_ext.rs`:
- Around line 11-21: Add rustdoc contracts above MdSetextHeader::is_level_1 and
MdSetextHeader::is_level_2 in crates/biome_markdown_syntax/src/header_ext.rs
(lines 11-21), and document UseTopLevelHeadingOptions as the configuration type
for useTopLevelHeading in crates/biome_rule_options/src/use_top_level_heading.rs
(lines 3-6); no other sites require changes.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1d551b31-45da-4d13-a297-79d5c9de940b

📥 Commits

Reviewing files that changed from the base of the PR and between 23c0369 and 61d494a.

⛔ Files ignored due to path filters (11)
  • crates/biome_configuration/src/analyzer/linter/rules.rs is excluded by !**/rules.rs and included by **
  • crates/biome_configuration/src/generated/linter_options_check.rs is excluded by !**/generated/**, !**/generated/** and included by **
  • crates/biome_diagnostics_categories/src/categories.rs is excluded by !**/categories.rs and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/heading-2.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/paragraph.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/setext-heading-2.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/heading-1.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/html.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/setext-heading-1.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/yaml.md.snap is excluded by !**/*.snap and included by **
  • packages/@biomejs/backend-jsonrpc/src/workspace.ts is excluded by !**/backend-jsonrpc/src/workspace.ts and included by **
📒 Files selected for processing (13)
  • .changeset/breezy-lines-peel.md
  • crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/heading-2.md
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/paragraph.md
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/setext-heading-2.md
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/heading-1.md
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/html.md
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/setext-heading-1.md
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/valid/yaml.md
  • crates/biome_markdown_syntax/src/header_ext.rs
  • crates/biome_rule_options/src/lib.rs
  • crates/biome_rule_options/src/use_top_level_heading.rs
  • justfile

Comment thread .changeset/breezy-lines-peel.md Outdated
@codspeed-hq

codspeed-hq Bot commented Aug 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 33 untouched benchmarks
⏩ 253 skipped benchmarks1


Comparing Netail:feat/use-top-level-heading (bad98a9) with main (23c0369)

Open in CodSpeed

Footnotes

  1. 253 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Netail
Netail force-pushed the feat/use-top-level-heading branch from 61d494a to fdefa0e Compare August 9, 2026 22:27
Comment thread .changeset/breezy-lines-peel.md Outdated
Comment thread .github/workflows/pull_request_markdown.yml
Comment on lines +15 to +16
/// Leading HTML comments used as file preamble are ignored when determining
/// the first meaningful block.

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.

What's this about?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If the markdown files starts with a comment, like we do in test files, we skip the comment to check what the next block is

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.

This seems like an implementation detail, doesn't it? Or, if not, reword it? "Meaningful block" is very technical. What's a meaningful block? That's not something I would write to our end users

Comment thread crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs Outdated
Comment thread crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs Outdated
Comment thread crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs Outdated
Comment thread crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs Outdated
Comment thread crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs Outdated
Comment thread crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs Outdated
},
)
.note(markup! {
"The first block should be a top-level heading (h1). Add a "<Emphasis>"# Heading"</Emphasis>" (or a level-1 setext heading) to the start of the document."

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.

The second rule pillar is missing. Or if there is, it should be reworded

@Netail Netail Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Tweaked it a bit

@github-actions github-actions Bot added the A-Tooling Area: internal tools label Aug 10, 2026

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
crates/biome_markdown_syntax/src/block_ext.rs (1)

116-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new public helper.

MdHtmlBlock::is_html_comment is a new public API used by the Markdown rule. Add /// rustdoc that defines the accepted delimiters and explains why missing content() or value_token() returns false.

As per coding guidelines, use rustdoc for new rule-related Rust features.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/biome_markdown_syntax/src/block_ext.rs` around lines 116 - 126, Add
rustdoc to the public MdHtmlBlock::is_html_comment helper documenting that it
recognizes content beginning with the HTML comment delimiter "<!--" and returns
false when content() or value_token() is unavailable. Keep the implementation
behavior unchanged.

Source: Coding guidelines

xtask/codegen/src/generate_new_analyzer_rule.rs (1)

588-590: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the generated Markdown template.

Add or verify a focused test for generate_rule_template that checks the Markdown example emits md, not markdown. If the output uses snapshots, update them with the repository's cargo insta workflow.

As per coding guidelines, all Rust code changes must include appropriate tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xtask/codegen/src/generate_new_analyzer_rule.rs` around lines 588 - 590, Add
or update a focused test for generate_rule_template that verifies the generated
Markdown example uses the md language tag rather than markdown. If this output
is snapshot-tested, refresh the relevant snapshot using the repository’s cargo
insta workflow.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/biome_markdown_syntax/src/block_ext.rs`:
- Around line 116-126: Add rustdoc to the public MdHtmlBlock::is_html_comment
helper documenting that it recognizes content beginning with the HTML comment
delimiter "<!--" and returns false when content() or value_token() is
unavailable. Keep the implementation behavior unchanged.

In `@xtask/codegen/src/generate_new_analyzer_rule.rs`:
- Around line 588-590: Add or update a focused test for generate_rule_template
that verifies the generated Markdown example uses the md language tag rather
than markdown. If this output is snapshot-tested, refresh the relevant snapshot
using the repository’s cargo insta workflow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1004c3b1-b92b-4db9-8b9c-94f90a60c0fe

📥 Commits

Reviewing files that changed from the base of the PR and between fdefa0e and 2b8cb49.

⛔ Files ignored due to path filters (3)
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/heading-2.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/paragraph.md.snap is excluded by !**/*.snap and included by **
  • crates/biome_markdown_analyze/tests/specs/nursery/useTopLevelHeading/invalid/setext-heading-2.md.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (3)
  • crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs
  • crates/biome_markdown_syntax/src/block_ext.rs
  • xtask/codegen/src/generate_new_analyzer_rule.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/biome_markdown_analyze/src/lint/nursery/use_top_level_heading.rs

@Netail
Netail requested a review from ematipico August 10, 2026 20:11
html_block.is_html_comment()
}
AnyMdBlock::AnyMdLeafBlock(AnyMdLeafBlock::MdParagraph(paragraph)) => {
let text = paragraph.syntax().text_trimmed().to_string();

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.

This allocates a string

@Netail
Netail merged commit d5d75de into biomejs:main Aug 11, 2026
35 checks passed
@Netail
Netail deleted the feat/use-top-level-heading branch August 11, 2026 06:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Parser Area: parser A-Project Area: project A-Tooling Area: internal tools L-Markdown Language: Markdown

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📎 Port MD041 - first-line-h1 from markdownlint

2 participants