with one click
testing-codegen
// Guide for testing workflows and code generation commands in Biome. Use when running snapshot tests for lint rules, managing insta snapshots, or regenerating analyzer/parser/formatter code after changes.
// Guide for testing workflows and code generation commands in Biome. Use when running snapshot tests for lint rules, managing insta snapshots, or regenerating analyzer/parser/formatter code after changes.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | testing-codegen |
| description | Guide for testing workflows and code generation commands in Biome. Use when running snapshot tests for lint rules, managing insta snapshots, or regenerating analyzer/parser/formatter code after changes. |
| compatibility | Designed for coding agents working on the Biome codebase (github.com/biomejs/biome). |
Use this skill for testing and code generation. Covers snapshot testing with
insta and code generation commands.
just install-tools (installs cargo-insta)corepack enable and pnpm install in repo root# Run all tests
cargo test
# Run tests for specific crate
cd crates/biome_js_analyze
cargo test
# Run specific test
cargo test quick_test
# Show test output (for dbg! macros)
cargo test quick_test -- --show-output
# Run tests with just (uses CI test runner)
just test
# Test specific crate with just
just test-crate biome_cli
Fast iteration during development:
// In crates/biome_js_analyze/tests/quick_test.rs
// Modify the quick_test function:
const SOURCE: &str = r#"
const x = 1;
var y = 2;
"#;
let rule_filter = RuleFilter::Rule("nursery", "noVar");
Run:
just qt biome_js_analyze
IMPORTANT: Use this instead of building full Biome binary for syntax inspection - it's much faster!
For inspecting AST structure when implementing parsers or working with embedded languages:
// In crates/biome_html_parser/tests/quick_test.rs
// Modify the quick_test function:
#[test]
pub fn quick_test() {
let code = r#"<button on:click={handleClick}>Click</button>"#;
let source_type = HtmlFileSource::svelte();
let options = HtmlParserOptions::from(&source_type);
let root = parse_html(code, options);
let syntax = root.syntax();
dbg!(&syntax, root.diagnostics(), root.has_errors());
}
Run:
just qt biome_html_parser
The dbg! output shows the full AST tree structure, helping you understand:
HtmlAttribute vs SvelteBindDirective)HtmlString (quotes) or HtmlTextExpression (curly braces)Run tests and generate snapshots:
cargo test
Review generated/changed snapshots:
# Interactive review (recommended)
cargo insta review
# Accept all changes
cargo insta accept
# Reject all changes
cargo insta reject
# Review for specific test
cargo insta review --test-runner nextest
Snapshot commands:
a - accept snapshotr - reject snapshots - skip snapshotq - quitWhen tests are removed or renamed, their old snapshot files become orphaned. Never delete snapshot files manually with rm โ always use insta's built-in pruning:
# Delete unreferenced snapshots after a successful test run
cargo insta test --unreferenced delete -p <crate_name>
# Or scoped to specific tests
cargo insta test --unreferenced delete -p biome_cli --test main -- "handle_vue"
This runs the tests first, then deletes any .snap files that no test references. It is the only safe way to clean up snapshots โ manual rm risks deleting snapshots that are still needed or creating git conflicts.
# Test specific rule by name
just test-lintrule noVar
# Run from analyzer crate
cd crates/biome_js_analyze
cargo test
Single file tests - Place in tests/specs/{group}/{rule}/ under the appropriate *_analyze crate for the language:
tests/specs/nursery/noVar/
โโโ invalid.js # Code that should generate diagnostics
โโโ valid.js # Code that should not generate diagnostics
โโโ options.json # Optional: rule configuration
File and folder naming conventions (IMPORTANT):
valid or invalid in file names or parent folder names to indicate expected behaviour.valid in the name (but not invalid) are expected to produce no diagnostics.invalid in the name are expected to produce diagnostics.valid/invalid e.g. validResolutionReact/invalidResolutionReacttests/specs/nursery/noShadow/
โโโ invalid.js # should generate diagnostics
โโโ valid.js # should not generate diagnostics
โโโ validResolutionReact/
โโโโโโ file.js # should generate diagnostics
โโโ file2.js # should not generate diagnostics
Multiple test cases - Use .jsonc files with arrays:
// tests/specs/nursery/noVar/invalid.jsonc
[
"var x = 1;",
"var y = 2; var z = 3;",
"for (var i = 0; i < 10; i++) {}"
]
Test-specific options - Create options.json:
{
"linter": {
"rules": {
"nursery": {
"noVar": {
"level": "error",
"options": {
"someOption": "value"
}
}
}
}
}
}
Every test spec file must begin with a top-level comment declaring whether it expects diagnostics. The test runner
(assert_diagnostics_expectation_comment in biome_test_utils) enforces this and panics if the rules are violated.
Write the marker text using whatever comment syntax the language under test supports.
For languages that do not support comments at all, rely on the file/folder naming convention (valid/invalid) instead.
For files whose name contains "valid" (but not "invalid"):
The comment is mandatory โ the test panics if it is absent.
For files whose name contains "invalid" (or other names):
The comment is strongly recommended and is also enforced when present: if the comment says
should generate diagnostics but no diagnostics appear, the test panics.
Rules enforced by the test runner:
| File name contains | Comment present? | Behaviour |
|---|---|---|
| "valid" (not "invalid") | should not generate diagnostics | Passes if no diagnostics |
| "valid" (not "invalid") | should generate diagnostics | Passes if diagnostics present |
| "valid" (not "invalid") | absent | PANIC โ comment is mandatory |
| "invalid" or neutral name | should not generate diagnostics | Passes if no diagnostics |
| "invalid" or neutral name | should generate diagnostics | Passes if diagnostics present |
| "invalid" or neutral name | absent | No enforcement (but add it anyway) |
Important details:
foo.js, bar.ts) that don't contain "valid" or "invalid" in their name do not require a comment, since they are not considered "valid test files" by the runner..snap, .json, .jsonc.HTML-ish files (.vue, .svelte, .astro, .html):
These files are analyzed via the workspace-based test path (analyze_with_workspace in biome_test_utils), which
checks the expectation comment by scanning the raw file content (not the parsed AST trivia). Use an HTML comment
at the very top of the file:
<!-- should not generate diagnostics -->
<script setup lang="ts">
const x = 1;
</script>
<template>{{ x }}</template>
<!-- should generate diagnostics -->
<script>
debugger;
</script>
The same rules apply: valid files must have the comment, invalid files should have it.
Do not place the comment inside <script> โ put it at the top level of the file as an HTML comment.
After modifying analyzers/lint rules (during development):
just gen-rules # Updates rule registrations in *_analyze crates
just gen-configuration # Updates configuration schemas
These lightweight commands generate enough code to compile and test without errors.
Full analyzer codegen (optional โ CI autofix handles this):
just gen-analyzer
This is a composite command that runs gen-rules, gen-configuration, gen-migrate, gen-bindings, lint-rules, and format. You typically don't need to run this locally โ the CI autofix job does it automatically when you open a PR.
After modifying grammar (.ungram files):
# Specific language
just gen-grammar html
# Multiple languages
just gen-grammar html css
# All languages
just gen-grammar
After modifying formatters:
just gen-formatter html
After modifying configuration:
just gen-bindings
Generates TypeScript types and JSON schema.
Full codegen (rarely needed):
just gen-all
Before committing:
just ready
Runs full codegen + format + lint (takes time).
Or run individually:
just f # Format Rust and TOML
just l # Lint code
Test code examples in documentation comments:
just test-doc
Use dbg!() macro in Rust code:
fn some_function() -> &'static str {
let some_variable = "debug_value";
dbg!(&some_variable); // Prints during test
some_variable
}
Run with output:
cargo test test_name -- --show-output
valid.js and invalid.js filesoptions.json applies to all tests in that folder.jsonc arrays: Use for multiple quick test cases in script context (no imports/exports)just commands when possible (matches CI)#[ignore] for slow tests, run with cargo test -- --ignoredjust qt <package> to run quick_test and inspect AST, NOT full Biome builds (much faster)For general Biome development tips (string extraction, borrow checker patterns, legacy syntax), see the biome-developer skill.
// Snapshot test in rule file
#[test]
fn test_rule() {
assert_lint_rule! {
noVar,
invalid => [
"var x = 1;",
"var y = 2;",
],
valid => [
"const x = 1;",
"let y = 2;",
]
}
}
// Quick test pattern
#[test]
#[ignore] // Uncomment when using
fn quick_test() {
const SOURCE: &str = r#"
var x = 1;
"#;
let rule_filter = RuleFilter::Rule("nursery", "noVar");
// Test runs with this configuration
}
| When you modify... | Run during dev... | Full (optional, CI does this) |
|---|---|---|
.ungram grammar files | just gen-grammar <lang> | โ |
Lint rules in *_analyze | just gen-rules && just gen-configuration | just gen-analyzer |
Formatter in *_formatter | just gen-formatter <lang> | โ |
| Configuration types | just gen-bindings | โ |
| Before committing | just f && just l | โ |
| Full rebuild | โ | just gen-all (slow) |
CONTRIBUTING.md ยง Testingcrates/biome_analyze/CONTRIBUTING.md ยง Testing../changeset/SKILL.md