--- id: biomejs/biome/diagnostics-development version: "c3b2c0af" license: Apache-2.0 install: manual updated: 2026-07-27 --- # diagnostics-development — Build effective error messages, warnings, and hints for Biome lint rules using the Diagnostic trait and advice system. Learn to craft messages that explain what went wrong, why it matters, and how to fix it—with rich markup, code frames, and actionable guidance. Publisher: biomejs · Stars: 25418 · Updated: 2026-07-27 Install (manual): `git clone https://github.com/biomejs/biome` ## SKILL.md ## 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: ```rust 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: ```rust 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 ```rust 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 } // 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 ```rust use biome_analyze::{Rule, RuleDiagnostic}; impl Rule for NoVar { fn diagnostic(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); Some( RuleDiagnostic::new( rule_category!(), node.range(), markup! { "Using ""var"" is not recommended." }, ) .note(markup! { "Variables declared with ""var"" are function-scoped, not block-scoped, which means they can leak outside of loops and conditionals and cause unexpected behavior." }) .note(markup! { "Consider using ""let"" or ""const"" instead." }) ) } } ``` ### Use Markup for Rich Text Biome supports rich markup in diagnostic messages: ```rust use biome_console::markup; markup! { // Emphasis (bold/colored) "Use ""const"" instead." // Code/identifiers "The variable "{variable_name}" is never used." // Hyperlinks "See the ""documentation""." // Interpolation "Found "{count}" issues." } ``` ### Register Diagnostic Category Add new categories to `crates/biome_diagnostics_categories/src/categories.rs`: ```rust 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 ```rust #[derive(Debug, Diagnostic)] #[diagnostic(severity = Warning)] struct ComplexDiagnostic { #[location(span)] span: TextRange, #[message] message: &'static str, // Multiple advices #[advice] first_advice: LogAdvice, #[advice] code_frame: CodeFrameAdvice, #[verbose_advice] verbose_help: LogAdvice, } ``` ### Add Tags to Diagnostics ```rust #[derive(Debug, Diagnostic)] #[diagnostic( severity = Warning, tags(FIXABLE, DEPRECATED_CODE) // Add diagnostic tags )] struct MyDiagnostic { // ... } ``` Available tags: - `FIXABLE` - Diagnostic has fix information - `INTERNAL` - Internal error in Biome - `UNNECESSARY_CODE` - Code is unused - `DEPRECATED_CODE` - Code uses deprecated features ## Best Practices ### Message Guidelines **Good messages:** ```rust // Good - specific and actionable "Use 'let' or 'const' instead of 'var'" // Good - explains why "This variable is never reassigned, consider using 'const'" // Good - shows what to do "Remove the unused import statement" ``` **Bad messages:** ```rust // Bad - too vague "Invalid syntax" // Bad - just states the obvious "Variable declared with 'var'" // Bad - no guidance "This code has a problem" ``` ### Advice Guidelines **Show, don't tell:** ```rust // Good - shows code frame CodeFrameAdvice { path: "file.js", span: node.text_range(), source_code: source, } // Less helpful - just text LogAdvice { category: LogCategory::Info, text: markup! { "The expression at line 5 is always truthy" }, } ``` **Provide actionable fixes:** ```rust // Good - shows exact change DiffAdvice { diff: text_edit, // AsRef } // Less helpful - describes change LogAdvice { category: LogCategory::Info, text: markup! { "Change 'var' to 'const'" }, } ``` ### Severity Levels Choose appropriate severity: ```rust // Fatal - Biome can't continue severity = Fatal // Error - Must be fixed (correctness, security, a11y) severity = Error // Warning - Should be fixed (suspicious code) severity = Warning // Information - Style suggestions severity = Information // Hint - Minor improvements severity = Hint ``` ## Common Patterns ```rust // Pattern 1: Simple diagnostic with note RuleDiagnostic::new( rule_category!(), node.range(), markup! { "Main message" }, ) .note(markup! { "Additional context" }) // Pattern 2: Diagnostic with code frame RuleDiagnostic::new( rule_category!(), node.range(), markup! { "Main message" }, ) .detail( node.syntax().text_range(), markup! { "This part is problematic" } ) // Pattern 3: Diagnostic with link RuleDiagnostic::new( rule_category!(), node.range(), markup! { "Main message" }, ) .note(markup! { "See ""documentation""." }) // Pattern 4: Conditional advice impl Advices for MyAdvice { fn record(&self, visitor: &mut dyn Visit) -> std::io::Result<()> { if self.show_hint { visitor.record_log( LogCategory::Info, &markup! { "Hint: ..." } )?; } Ok(()) } } ``` ## Tips - **Category format**: Use `area/group/ruleName` format (e.g., `lint/correctness/noVar`) - **Markup formatting**: Use `markup!` macro for all user-facing text - **Hyperlinks**: Always link to documentation for more details - **Code frames**: Include for spatial context when helpful - **Multiple advices**: Chain multiple pieces of information - **Verbose advices**: Use for extra details users can opt into - **Description vs Message**: Description for plain text contexts (IDE popover), message for rich display - **Register categories**: Don't forget to add to `categories.rs` ## References - Full guide: `crates/biome_diagnostics/CONTRIBUTING.md` - Technical principles: https://biomejs.dev/internals/philosophy/#technical - Diagnostic trait: `crates/biome_diagnostics/src/diagnostic.rs` - Advice types: `crates/biome_diagnostics/src/advice.rs` - Examples: Search for `#[derive(Diagnostic)]` in codebase [View on SkillFed](https://skillfed.io/biomejs/biome/diagnostics-development) · [View on GitHub](https://github.com/biomejs/biome)