Skip to content
Development
Skill

/formatter-development

Use this skill whenever implementing or debugging Biome formatter behavior, IR composition, node rules, layout selection, source-comment handling, verbatim formatting, idempotency, internal specs, or Prettier comparison. Do not use it for generic snapshot commands or parser

From plugin
biome
26k11 skills
Install
$ npx -y skills add biomejs/biome --skill formatter-development --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/formatter-development

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use this skill whenever implementing or debugging Biome formatter behavior, IR composition, node rules, layout selection, source-comment handling, verbatim formatting, idempotency, internal specs, or Prettier comparison. Do not use it for generic snapshot commands or parser

SKILL.md

formatter-development.SKILL.md
name: formatter-development
description: Use this skill whenever implementing or debugging Biome formatter behavior, IR composition, node rules, layout selection, source-comment handling, verbatim formatting, idempotency, internal specs, or Prettier comparison. Do not use it for generic snapshot commands or parser changes.
compatibility: Designed for coding agents working on the Biome codebase (github.com/biomejs/biome).

Formatter Development

Use `crates/biome_formatter/CONTRIBUTING.md` and the language formatter's guide as the canonical architecture references. Inspect neighboring node implementations before selecting IR primitives.

Workflow

1. Reproduce the behavior with a focused internal formatter spec or `quick_test`. 2. Inspect the node fields, comments, and nearby formatting rules. 3. Implement the smallest layout change using formatter IR. 4. Run focused formatter tests and inspect snapshots. 5. Compare with Prettier when compatibility is relevant. 6. Format and lint before committing.

Printing Discipline

Formatter output is a structured rewrite of the source tree, not a fresh pretty-printer. Treat every missed node, missed token, unchecked suppression, and untracked replacement as a formatter bug, not as style feedback.

  • Every source token in the formatted range MUST be consumed exactly once. A token is consumed only by formatting it, removing it with `format_removed`, replacing it with `format_replaced`, or by a language-specific helper that does one of those operations.
  • Every source node in the formatted range MUST be handled every time. Prefer `node.format()` or `node.format().with_options(...)` so the node's own rule formats the node, checks suppressions, and routes comments through the formatter infrastructure.
  • A parent formatter MUST NOT inline a child node's fields just to get a convenient layout. Move the layout decision into the child rule or pass options into the child formatter.
  • If an architecture-specific formatter bypasses a node's rule, it MUST NOT skip the node silently. It MUST check `f.context().comments().is_suppressed(node.syntax())`; if the node is suppressed, it MUST write the language's `format_suppressed_node(...)` helper instead of formatting the node body. Without this check, debug builds fail suppression coverage and user suppressions can be ignored.
  • Source tokens MUST be printed through their typed accessors. Use `token("...")` only for syntax inserted by the formatter when no source token exists.
  • Removing a source token from output MUST use `format_removed(&token)`. Do not drop the field, bind it to `_`, or omit it from `write!` without consuming it through `format_removed`; skipped trivia still belongs to that token.
  • Replacing a source token's text MUST use `format_replaced(&token, &replacement)`. Do not print the replacement directly and do not use `token("...")` for replacement text, because the original token still has trivia and must be marked consumed.
  • Custom formatting MUST be carried by a small struct implementing `Format<Context>`. Do not use free functions or stored closure values to carry formatter state or layout invariants. Use `format_with` only for one-off local glue that is immediately written.

Node Rules

Generated node rules implement `FormatNodeRule`. In `fmt_fields`:

  • destructure the generated `*Fields` type explicitly;
  • format source tokens through their typed accessors;
  • use `_` rather than `..` only when the field is consumed elsewhere in the same formatting path or deliberately handled by `format_removed` / `format_replaced`; otherwise `_` on a node or token field is a dropped-tree bug;
  • preserve every token and comment unless the formatter contract intentionally removes or replaces it;
  • keep layout decisions near the type that owns them.

`format_verbatim_*` methods preserve a node's source text. Replace verbatim formatting with structured formatting only when tests cover valid, malformed, and commented forms of the node.

Token Rules

Format, replace, or remove every token. Formatter tests panic when a token is not handled, preventing accidental source loss.

Use `format_replaced` when substituting a token and `format_removed` when removing one.

Ad-Hoc Formatting

Format a node through `node.format()` when possible. Its regular rule checks formatter-suppression comments as part of normal formatting.

When a helper formats a node or its tokens outside `FormatNodeRule`, run the formatter tests. If the suppression-check assertion reports a node, call `f.context().comments().mark_suppression_checked(node.syntax())` for that reported node. The assertion shows that the helper bypasses the node's normal suppression check.

IR Composition

Use semantic IR rather than writing whitespace as arbitrary text:

  • `space()` for required spaces;
  • soft line breaks for optional wrapping;
  • hard line breaks for mandatory breaks;
  • groups to choose flat versus expanded layout;
  • indentation primitives matching the enclosing construct;
  • conditional content tied to the group whose fit decision controls it.

For a distinct formatting concern, use a named type implementing `Format`. A cluster of free functions that pass `&mut Formatter` obscures what has already been written and which layout invariants apply.

Represent multi-way layout with an enum selected once. Recomputing layout at several write sites can produce inconsistent output and idempotency failures.

Comments

Leading and trailing comments are generally handled by formatter infrastructure. Explicitly format dangling comments when the node owns a position to which no child can attach them.

Test comments at each structural boundary affected by the change: before the first child, between children, after the last child, and around empty nodes. Dropping or moving a source comment is data loss.

Tests and Idempotency

Load `testing-codegen` for snapshot commands and review.

Internal specs should contain the focused source sha

Read more
Ships withbiome

A toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP.

Get the whole plugin
Stats
25,807
Stars
1,232
Forks
Active
Maintenance
Rust
Language
Apache-2.0
License
14h ago
Last commit
3y ago
Created

Repo: biomejs/biome

Other skills on biome.