Skip to content

test(markdown): add minimal service plumbing for formatter integration#9070

Open
jfmcdowell wants to merge 3 commits intobiomejs:nextfrom
jfmcdowell:feat/markdown-formatter-test-harness
Open

test(markdown): add minimal service plumbing for formatter integration#9070
jfmcdowell wants to merge 3 commits intobiomejs:nextfrom
jfmcdowell:feat/markdown-formatter-test-harness

Conversation

@jfmcdowell
Copy link
Contributor

Note

AI Assistance Disclosure: This PR was developed with assistance from Claude Code.

Summary

This PR adds the minimum Markdown formatter plumbing needed so Markdown is recognized as a formattable language in Biome.

PR #8962 introduced formatter boilerplate. This PR focuses on the required syntax/service/formatter-option integration to make Markdown format-capable from the service layer.

Test corpus and comparison/snapshot-heavy formatter tests are handled separately in #9067

Continues work on #3718

Why

Markdown must be recognized by biome_service as a format-capable language before formatter infrastructure can be exercised end-to-end.

Scope

1) Markdown file source + service routing

  • Added MarkdownFileSource with:
    • extension detection: md, markdown
    • language ID detection: markdown
  • Added DocumentFileSource::Markdown support in service routing/capability dispatch.
  • Added markdown handler in biome_service with:
    • parse + format capabilities
    • debug syntax tree + formatter IR
    • formatter/linter/assist enabled checks

2) Markdown formatter options plumbing

  • Added Display for MarkdownFormatOptions (required by service language trait
    bounds).
  • Added option builders:
    • with_indent_style
    • with_indent_width
    • with_line_ending
    • with_trailing_newline

3) Service/settings integration

  • Added markdown language slot to LanguageListSettings.
  • Added markdown deps/schema wiring in biome_service Cargo config.

4) Exhaustive match maintenance

  • Added DocumentFileSource::Markdown arm in xtask/rules_check for exhaustive handling.

5) Follow-up consistency cleanup in this PR

  • Added DocumentFileSource::to_markdown_file_source() accessor for parity with other file source helpers.
  • Added TODOs in markdown service handler to explicitly mark override-pattern support as pending.

Not Included

  • No substantive Markdown formatting behavior changes yet.
  • No markdown fixture/snapshot corpus in this PR.
  • No Prettier markdown snapshot import in this PR.
  • Markdown override-pattern formatting settings are not implemented yet.

Those tests/corpus are being developed in #9067

Validation

  • just f
  • just l

Add the minimum markdown service integration so markdown is recognized as a formattable language and formatter tests can run against the standard service path.

- Add MarkdownFileSource detection for md/markdown extensions and markdown language id.
- Add DocumentFileSource::Markdown routing and markdown handler capabilities in biome_service.
- Add markdown ServiceLanguage settings/format option resolution and formatter entry points.
- Add MarkdownFormatOptions Display + missing option builder methods required by service plumbing.
- Add focused service tests for markdown file-source detection and formatter capability routing.
- Add markdown exhaustive match handling in xtask rules_check.

Scope intentionally excludes Prettier markdown comparison tests (tracked separately).
@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

⚠️ No Changeset found

Latest commit: 1e8665e

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 added A-Project Area: project A-Parser Area: parser A-Formatter Area: formatter A-Tooling Area: internal tools labels Feb 14, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 14, 2026

Merging this PR will not alter performance

✅ 58 untouched benchmarks
⏩ 95 skipped benchmarks1


Comparing jfmcdowell:feat/markdown-formatter-test-harness (1e8665e) with next (66ef5ba)

Open in CodSpeed

Footnotes

  1. 95 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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 2026

Walkthrough

Adds Markdown support across the workspace: new MarkdownFileSource type and module, re-exports in biome_markdown_syntax, workspace dependency additions, new Markdown file handler and ServiceLanguage implementation in biome_service (formatter/linter/assist settings and capability wiring), builder-style setters and a Display impl for MarkdownFormatOptions in biome_markdown_formatter, Cargo workspace dependency additions (notify) and a small rules_check update to recognise Markdown. Several tests and feature wiring for detection, parsing and formatting were added. No existing public struct signatures were removed.

Possibly related PRs

Suggested labels

A-Core

Suggested reviewers

  • ematipico
  • dyc3
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (76 files):

⚔️ Cargo.lock (content)
⚔️ Cargo.toml (content)
⚔️ crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs (content)
⚔️ crates/biome_cli/tests/cases/handle_vue_files.rs (content)
⚔️ crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/full_support.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/full_support.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/full_support_ts.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/full_support.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/full_support_jsx.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/full_support_ts.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/full_support_tsx.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_cases_html/should_apply_fixes_to_embedded_languages.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_commands_lint/lint_only_group_skip_rule.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_commands_lint/linter_shows_the_default_severity_of_rule_on.snap (content)
⚔️ crates/biome_cli/tests/snapshots/main_configuration/incorrect_rule_name.snap (content)
⚔️ crates/biome_configuration/src/analyzer/linter/rules.rs (content)
⚔️ crates/biome_configuration/src/generated/domain_selector.rs (content)
⚔️ crates/biome_diagnostics_categories/src/categories.rs (content)
⚔️ crates/biome_html_analyze/src/lint/a11y/no_redundant_alt.rs (content)
⚔️ crates/biome_html_analyze/src/lint/a11y/no_svg_without_title.rs (content)
⚔️ crates/biome_js_analyze/src/lint/a11y/no_svg_without_title.rs (content)
⚔️ crates/biome_js_analyze/src/lint/nursery/no_shadow.rs (content)
⚔️ crates/biome_js_analyze/src/lint/nursery/use_explicit_type.rs (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-01.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-02.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-03.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-04.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-05.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-06.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-07.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-08.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-10.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-11.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-12.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-13.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-14.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-15.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-16.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-17.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-18.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-19.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-20.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-21.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-22.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-23.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-24.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-25.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-26.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-27.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-28.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-29.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-30.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-31.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/eslint/invalid-32.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/invalid.js.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/typescript-eslint/invalid-01.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/typescript-eslint/invalid-02.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/typescript-eslint/invalid-03.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/typescript-eslint/invalid-04.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/typescript-eslint/invalid-05.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/typescript-eslint/invalid-06.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/typescript-eslint/invalid-07.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/noShadow/typescript-eslint/invalid-08.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/useExplicitType/invalid.ts.snap (content)
⚔️ crates/biome_js_analyze/tests/specs/nursery/useExplicitType/invalidArguments.ts.snap (content)
⚔️ crates/biome_markdown_formatter/src/context.rs (content)
⚔️ crates/biome_markdown_syntax/Cargo.toml (content)
⚔️ crates/biome_markdown_syntax/src/lib.rs (content)
⚔️ crates/biome_service/Cargo.toml (content)
⚔️ crates/biome_service/src/file_handlers/javascript.rs (content)
⚔️ crates/biome_service/src/file_handlers/mod.rs (content)
⚔️ crates/biome_service/src/settings.rs (content)
⚔️ crates/biome_service/src/settings.tests.rs (content)
⚔️ packages/@biomejs/backend-jsonrpc/src/workspace.ts (content)
⚔️ packages/@biomejs/biome/configuration_schema.json (content)
⚔️ xtask/rules_check/src/lib.rs (content)

These conflicts must be resolved before merging into next.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main purpose of the PR: adding minimal service plumbing for Markdown formatter integration. It's concise, specific, and directly relates to the changeset.
Description check ✅ Passed The description comprehensively explains the PR's scope, rationale, and what's included/excluded. It clearly relates to the changeset and provides meaningful context about Markdown formatter integration work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch feat/markdown-formatter-test-harness
  • Post resolved changes as copyable diffs in a comment

No actionable comments were generated in the recent review. 🎉

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/biome_service/Cargo.toml`:
- Line 74: The manifest uses an explicit version for the notify dependency
instead of the workspace reference; update the crate's Cargo.toml so the notify
dependency references the workspace-level declaration (use workspace = true) or
move notify into the root workspace [dependencies] and keep workspace = true
here; locate the notify entry (notify = { version = "8.2.0", features =
["crossbeam-channel"] }) and change it to a workspace reference consistent with
other dependencies or add notify to the workspace dependencies table and remove
the explicit version here.
🧹 Nitpick comments (2)
crates/biome_service/src/file_handlers/markdown.rs (1)

210-225: Parser metadata is discarded during AnyParse conversion.

parse_markdown_with_cache returns list_tightness, list_item_indents, and quote_indents alongside the syntax tree (see crates/biome_markdown_parser/src/lib.rs). These are lost when converting to AnyParse via NodeParse. This is fine for the current minimal plumbing, but will need addressing when substantive formatting logic lands — list tightness in particular affects Markdown rendering semantics.

crates/biome_service/src/file_handlers/mod.rs (1)

434-441: can_contain_embeds returns false for Markdown — worth a TODO?

Markdown files commonly contain fenced code blocks (e.g., ```js ... ```), which are semantically embedded snippets. Returning false is fine for this minimal plumbing PR, but it might be worth leaving a short comment or TODO so this isn't forgotten when embedded-language support lands.

Optional: leave a breadcrumb
             Self::Html(_) | Self::Js(_) => true,
             Self::Css(_)
             | Self::Graphql(_)
             | Self::Json(_)
             | Self::Grit(_)
+            // TODO: Markdown fenced code blocks may contain embeds — revisit when embedded support lands.
             | Self::Markdown(_)
             | Self::Ignore
             | Self::Unknown => false,

@@ -0,0 +1,267 @@
use super::{
Copy link
Contributor

@tidefield tidefield Feb 15, 2026

Choose a reason for hiding this comment

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

Hi @jfmcdowell . Sorry I'm afraid this will conflict with #9067 soon. :(

I was having this implementation in my local so I can verify the snapshots are correctly captured in the tests. I'm fixing some errors before pushing it to my PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

FYI the reason I needed to implement what you had in this PR is because the tests need to have a TestFormatLanguage implementation which then requires ServiceLanguage implementation. So it overlaps with what you're doing in the PR.

Copy link
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

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

It looks good, however we need to gate the implementation under a cargo feature or Biome will start parsing the files


pub fn try_from_extension(extension: &str) -> Result<Self, FileSourceError> {
match extension {
"md" | "markdown" => Ok(Self::markdown()),
Copy link
Member

Choose a reason for hiding this comment

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

This will enable handling markdown to our users, and we're not ready for this yet. To prevent this, do the same thing I did for SCSS in this PR: #9091


pub fn try_from_language_id(language_id: &str) -> Result<Self, FileSourceError> {
match language_id {
"markdown" => Ok(Self::markdown()),
Copy link
Member

Choose a reason for hiding this comment

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

Same thing here, let's gate it under a cargo feature

#[derive(
Debug, Clone, Default, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct MarkdownFileSource {
Copy link
Member

Choose a reason for hiding this comment

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

nit: I would call it MdFileSource, just because it's shorter

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Formatter Area: formatter A-Parser Area: parser A-Project Area: project A-Tooling Area: internal tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants