/diagnostics-development
Guide for creating high-quality, user-friendly diagnostics in Biome. Use when creating diagnostics for lint rules, adding helpful advice to error messages, implementing code frame displays, or improving diagnostic quality.
$ npx -y skills add biomejs/biome --skill diagnostics-development --agent claude-codeHow 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
/diagnostics-development
Context preview
The summary Claude sees to decide when to auto-load this skill.
Guide for creating high-quality, user-friendly diagnostics in Biome. Use when creating diagnostics for lint rules, adding helpful advice to error messages, implementing code frame displays, or improving diagnostic quality.
SKILL.md
diagnostics-development.SKILL.mdname: diagnostics-development
description: Guide for creating high-quality, user-friendly diagnostics in Biome. Use when creating diagnostics for lint rules, adding helpful advice to error messages, implementing code frame displays, or improving diagnostic quality.
compatibility: Designed for coding agents working on the Biome codebase (github.com/biomejs/biome).
Purpose
Use this skill when creating diagnostics - the error messages, warnings, and hints shown to users. Covers the `Diagnostic` trait, advice types, and best practices for clear, actionable messages.
Prerequisites
1. Read `crates/biome_diagnostics/CONTRIBUTING.md` for concepts 2. Understand Biome's [Technical Principles](https://biomejs.dev/internals/philosophy/#technical) 3. Follow the "show don't tell" philosophy
Diagnostic Principles
1. **Explain what** - State what the error is (diagnostic message) 2. **Explain why** - Explain why it's an error (advice notes) 3. **Tell how to fix** - Provide actionable fixes (code actions, diff advice, command advice)
**Follow Technical Principles:**
- Informative: Explain, don't just state
- Concise: Short messages, rich context via advices
- Actionable: Always suggest how to fix
- Show don't tell: Prefer code frames over textual explanations
Common Workflows
Create a Diagnostic Type
Use the `#[derive(Diagnostic)]` macro:
use biome_diagnostics::{Diagnostic, category};
#[derive(Debug, Diagnostic)]
#[diagnostic(
severity = Error,
category = "lint/correctness/noVar"
)]
struct NoVarDiagnostic {
#[location(span)]
span: TextRange,
#[message]
#[description]
message: MessageAndDescription,
#[advice]
advice: NoVarAdvice,
}
#[derive(Debug)]
struct MessageAndDescription;
impl fmt::Display for MessageAndDescription {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Use 'let' or 'const' instead of 'var'")
}
}Implement Advices
Create advice types that implement `Advices` trait:
use biome_diagnostics::{Advices, Visit};
use biome_console::markup;
struct NoVarAdvice {
is_const_candidate: bool,
}
impl Advices for NoVarAdvice {
fn record(&self, visitor: &mut dyn Visit) -> std::io::Result<()> {
if self.is_const_candidate {
visitor.record_log(
LogCategory::Info,
&markup! {
"This variable is never reassigned, use 'const' instead."
}
)?;
} else {
visitor.record_log(
LogCategory::Info,
&markup! {
"Variables declared with 'var' are function-scoped, use 'let' for block-scoping."
}
)?;
}
Ok(())
}
}Use Built-in Advice Types
use biome_diagnostics::{LogAdvice, CodeFrameAdvice, DiffAdvice, CommandAdvice, LogCategory};
// Log advice - simple text message
LogAdvice {
category: LogCategory::Info,
text: markup! { "Consider using arrow functions." },
}
// Code frame advice - highlight code location
// Fields: path (AsResource), span (AsSpan), source_code (AsSourceCode)
CodeFrameAdvice {
path: "file.js",
span: node.text_range(),
source_code: ctx.source_code(),
}
// Diff advice - show a TextEdit diff
DiffAdvice {
diff: text_edit, // must implement AsRef<TextEdit>
}
// Command advice - suggest CLI command
CommandAdvice {
command: "biome check --write",
}In practice, most lint rules use the `RuleDiagnostic` builder pattern instead of constructing advice types directly. See the [Add Diagnostic to Rule](#add-diagnostic-to-rule) section below.
Add Diagnostic to Rule
use biome_analyze::{Rule, RuleDiagnostic};
impl Rule for NoVar {
fn diagnostic(ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
let node = ctx.query();
Some(
RuleDiagnostic::new(
rule_category!(),
node.range(),
markup! {
"Using "<Emphasis>"var"</Emphasis>" is not recommended."
},
)
.note(markup! {
"Variables declared with "<Emphasis>"var"</Emphasis>" are function-scoped, not block-scoped, which means they can leak outside of loops and conditionals and cause unexpected behavior."
})
.note(markup! {
"Consider using "<Emphasis>"let"</Emphasis>" or "<Emphasis>"const"</Emphasis>" instead."
})
)
}
}Use Markup for Rich Text
Biome supports rich markup in diagnostic messages:
use biome_console::markup;
markup! {
// Emphasis (bold/colored)
"Use "<Emphasis>"const"</Emphasis>" instead."
// Code/identifiers
"The variable "<Emphasis>{variable_name}</Emphasis>" is never used."
// Hyperlinks
"See the "<Hyperlink href="https://example.com">"documentation"</Hyperlink>"."
// Interpolation
"Found "{count}" issues."
}Register Diagnostic Category
Add new categories to `crates/biome_diagnostics_categories/src/categories.rs`:
define_categories! {
// Existing categories...
"lint/correctness/noVar": "https://biomejs.dev/linter/rules/no-var",
"lint/style/useConst": "https://biomejs.dev/linter/rules/use-const",
}Create Multi-Advice Diagnostics
#[derive(Debug, Diagnostic)]
#[diagnostic(severity = Warning)]
struct ComplexDiagnostic {
#[location(span)]
span: TextRange,
#[message]
message: &'static str,
// Multiple advices
#[advice]
first_advice: LogAdvice<MarkupBuf>,
#[advice]
code_frame: CodeFrameAdvice<String, TextRange, String>,
#[verbose_advice]
verbose_help: LogAdvice<MarkupBuf>,
}Add Tags to Diagnostics
#[derive(Debug, Diagnostic)]
#[diagnostic(
severity = Warning,
tags(FIXABLE, DEPRECATED_CODE) // Add diagnostic tags
)]
struct MyDiagnRead more
name: diagnostics-development description: Guide for creating high-quality, user-friendly diagnostics in Biome. Use when creating diagnostics for lint rules, adding helpful advice to error messages, implementing code frame displays, or improving diagnostic quality. compatibility: Designed for coding agents working on the Biome codebase (github.com/biomejs/biome).
Purpose
Use this skill when creating diagnostics - the error messages, warnings, and hints shown to users. Covers the `Diagnostic` trait, advice types, and best practices for clear, actionable messages.
Prerequisites
1. Read `crates/biome_diagnostics/CONTRIBUTING.md` for concepts 2. Understand Biome's [Technical Principles](https://biomejs.dev/internals/philosophy/#technical) 3. Follow the "show don't tell" philosophy
Diagnostic Principles
1. **Explain what** - State what the error is (diagnostic message) 2. **Explain why** - Explain why it's an error (advice notes) 3. **Tell how to fix** - Provide actionable fixes (code actions, diff advice, command advice)
**Follow Technical Principles:**
- Informative: Explain, don't just state
- Concise: Short messages, rich context via advices
- Actionable: Always suggest how to fix
- Show don't tell: Prefer code frames over textual explanations
Common Workflows
Create a Diagnostic Type
Use the `#[derive(Diagnostic)]` macro:
use biome_diagnostics::{Diagnostic, category};
#[derive(Debug, Diagnostic)]
#[diagnostic(
severity = Error,
category = "lint/correctness/noVar"
)]
struct NoVarDiagnostic {
#[location(span)]
span: TextRange,
#[message]
#[description]
message: MessageAndDescription,
#[advice]
advice: NoVarAdvice,
}
#[derive(Debug)]
struct MessageAndDescription;
impl fmt::Display for MessageAndDescription {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Use 'let' or 'const' instead of 'var'")
}
}Implement Advices
Create advice types that implement `Advices` trait:
use biome_diagnostics::{Advices, Visit};
use biome_console::markup;
struct NoVarAdvice {
is_const_candidate: bool,
}
impl Advices for NoVarAdvice {
fn record(&self, visitor: &mut dyn Visit) -> std::io::Result<()> {
if self.is_const_candidate {
visitor.record_log(
LogCategory::Info,
&markup! {
"This variable is never reassigned, use 'const' instead."
}
)?;
} else {
visitor.record_log(
LogCategory::Info,
&markup! {
"Variables declared with 'var' are function-scoped, use 'let' for block-scoping."
}
)?;
}
Ok(())
}
}Use Built-in Advice Types
use biome_diagnostics::{LogAdvice, CodeFrameAdvice, DiffAdvice, CommandAdvice, LogCategory};
// Log advice - simple text message
LogAdvice {
category: LogCategory::Info,
text: markup! { "Consider using arrow functions." },
}
// Code frame advice - highlight code location
// Fields: path (AsResource), span (AsSpan), source_code (AsSourceCode)
CodeFrameAdvice {
path: "file.js",
span: node.text_range(),
source_code: ctx.source_code(),
}
// Diff advice - show a TextEdit diff
DiffAdvice {
diff: text_edit, // must implement AsRef<TextEdit>
}
// Command advice - suggest CLI command
CommandAdvice {
command: "biome check --write",
}In practice, most lint rules use the `RuleDiagnostic` builder pattern instead of constructing advice types directly. See the [Add Diagnostic to Rule](#add-diagnostic-to-rule) section below.
Add Diagnostic to Rule
use biome_analyze::{Rule, RuleDiagnostic};
impl Rule for NoVar {
fn diagnostic(ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> {
let node = ctx.query();
Some(
RuleDiagnostic::new(
rule_category!(),
node.range(),
markup! {
"Using "<Emphasis>"var"</Emphasis>" is not recommended."
},
)
.note(markup! {
"Variables declared with "<Emphasis>"var"</Emphasis>" are function-scoped, not block-scoped, which means they can leak outside of loops and conditionals and cause unexpected behavior."
})
.note(markup! {
"Consider using "<Emphasis>"let"</Emphasis>" or "<Emphasis>"const"</Emphasis>" instead."
})
)
}
}Use Markup for Rich Text
Biome supports rich markup in diagnostic messages:
use biome_console::markup;
markup! {
// Emphasis (bold/colored)
"Use "<Emphasis>"const"</Emphasis>" instead."
// Code/identifiers
"The variable "<Emphasis>{variable_name}</Emphasis>" is never used."
// Hyperlinks
"See the "<Hyperlink href="https://example.com">"documentation"</Hyperlink>"."
// Interpolation
"Found "{count}" issues."
}Register Diagnostic Category
Add new categories to `crates/biome_diagnostics_categories/src/categories.rs`:
define_categories! {
// Existing categories...
"lint/correctness/noVar": "https://biomejs.dev/linter/rules/no-var",
"lint/style/useConst": "https://biomejs.dev/linter/rules/use-const",
}Create Multi-Advice Diagnostics
#[derive(Debug, Diagnostic)]
#[diagnostic(severity = Warning)]
struct ComplexDiagnostic {
#[location(span)]
span: TextRange,
#[message]
message: &'static str,
// Multiple advices
#[advice]
first_advice: LogAdvice<MarkupBuf>,
#[advice]
code_frame: CodeFrameAdvice<String, TextRange, String>,
#[verbose_advice]
verbose_help: LogAdvice<MarkupBuf>,
}Add Tags to Diagnostics
#[derive(Debug, Diagnostic)]
#[diagnostic(
severity = Warning,
tags(FIXABLE, DEPRECATED_CODE) // Add diagnostic tags
)]
struct MyDiagnA toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP.
Repo: biomejs/biome
Other skills on biome.
- /biome-code-review
Static, read-only code review of a Biome (github.com/biomejs/biome) pull request, local branch, commit range, diff, or working tree being prepared as a PR. Applies Biome's own conventions - nursery placement and naming for lint rules, diagnostics quality, allocation and borrow
Open skill - /biome-developer
General development best practices and common gotchas when working on Biome. Use for avoiding common mistakes, understanding Biome-specific patterns (AST, syntax nodes, string extraction, embedded languages), and learning technical tips.
Open skill - /changeset
Guide for creating and writing proper changesets for Biome PRs. Use when a PR introduces user-visible changes (bug fixes, new features, rule changes, formatter changes, parser changes) that need a changeset entry for the CHANGELOG. Trigger when creating changesets, writing
Open skill - /doc-comments
How to write inline comments, rustdoc, and module documentation in the Biome codebase. The audience is Biome developers reading the source, not end users. Use whenever writing or editing `//` comments, `///` item docs, or `//!` module docs — including comments added incidentally
Open skill - /eslint-migrate-options
Guide for implementing ESLint-to-Biome rule option migrators inside `biome migrate eslint`. Use whenever you add or update a Biome lint rule that has an ESLint source rule with configurable options, need to deserialize plugin-specific ESLint options, or need custom migration
Open skill - /formatter-development
Guide for implementing formatting rules using Biome's IR-based formatter infrastructure. Use when implementing formatting for new syntax nodes, handling comments in formatted output, writing or debugging formatter snapshot tests, diagnosing idempotency failures, or comparing
Open skill

