Loading...
Loading...
Step-by-step guide for creating and implementing lint rules in Biome's analyzer. Use when implementing rules like noVar, useConst, or any custom lint/assist rule. Examples:<example>User wants to create a rule that detects unused variables</example><example>User needs to add code actions to fix diagnostic issues</example><example>User is implementing semantic analysis for binding references</example>
npx skill4agent add biomejs/biome lint-rule-developmentjust install-toolscargojustpnpmcrates/biome_analyze/CONTRIBUTING.mdjust new-js-lintrule useMyRuleNamejust new-css-lintrule myRuleName
just new-json-lintrule myRuleName
just new-graphql-lintrule myRuleNamecrates/biome_js_analyze/src/lint/nursery/use_my_rule_name.rsuse biome_analyze::{context::RuleContext, declare_lint_rule, Rule, RuleDiagnostic};
use biome_js_syntax::JsIdentifierBinding;
use biome_rowan::AstNode;
declare_lint_rule! {
/// Disallows the use of prohibited identifiers.
pub UseMyRuleName {
version: "next",
name: "useMyRuleName",
language: "js",
recommended: false,
}
}
impl Rule for UseMyRuleName {
type Query = Ast<JsIdentifierBinding>;
type State = ();
type Signals = Option<Self::State>;
type Options = ();
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let binding = ctx.query();
// Check if identifier matches your rule logic
if binding.name_token().ok()?.text() == "prohibited_name" {
return Some(());
}
None
}
fn diagnostic(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<RuleDiagnostic> {
let node = ctx.query();
Some(
RuleDiagnostic::new(
rule_category!(),
node.range(),
markup! {
"Avoid using this identifier."
},
)
.note(markup! {
"This identifier is prohibited because..."
}),
)
}
}use biome_analyze::Semantic;
impl Rule for MySemanticRule {
type Query = Semantic<JsReferenceIdentifier>;
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();
let model = ctx.model();
// Check if binding is declared
let binding = node.binding(model)?;
// Get all references to this binding
let all_refs = binding.all_references(model);
// Get only read references
let read_refs = binding.all_reads(model);
// Get only write references
let write_refs = binding.all_writes(model);
Some(())
}
}use biome_analyze::FixKind;
declare_lint_rule! {
pub UseMyRuleName {
version: "next",
name: "useMyRuleName",
language: "js",
recommended: false,
fix_kind: FixKind::Safe, // or FixKind::Unsafe
}
}
impl Rule for UseMyRuleName {
fn action(ctx: &RuleContext<Self>, _state: &Self::State) -> Option<JsRuleAction> {
let node = ctx.query();
let mut mutation = ctx.root().begin();
// Example: Replace the node
mutation.replace_node(
node.clone(),
make::js_identifier_binding(make::ident("replacement"))
);
Some(JsRuleAction::new(
ctx.action_category(ctx.category(), ctx.group()),
ctx.metadata().applicability(),
markup! { "Use 'replacement' instead" }.to_owned(),
mutation,
))
}
}// In crates/biome_js_analyze/tests/quick_test.rs
// Uncomment #[ignore] and modify:
const SOURCE: &str = r#"
const prohibited_name = 1;
"#;
let rule_filter = RuleFilter::Rule("nursery", "useMyRuleName");cd crates/biome_js_analyze
cargo test quick_test -- --show-outputtests/specs/nursery/useMyRuleName/tests/specs/nursery/useMyRuleName/
├── invalid.js # Code that triggers the rule
├── valid.js # Code that doesn't trigger the rule
└── options.json # Optional rule configurationinvalid.jsconst prohibited_name = 1;
const another_prohibited = 2;just test-lintrule useMyRuleNamecargo insta reviewjust gen-analyzerjust f # Format code
just l # Lint codeno*noVaruse*useConstnurserySemantic<Node>Vec<Self::State>Box<[Self::State]>Unsafe.ok()?.jsonc// Simple AST query
type Query = Ast<JsVariableDeclaration>;
// Semantic query (needs binding info)
type Query = Semantic<JsReferenceIdentifier>;
// Multiple node types (requires declare_node_union!)
declare_node_union! {
pub AnyFunctionLike = AnyJsFunction | JsMethodObjectMember | JsMethodClassMember
}
type Query = Semantic<AnyFunctionLike>;crates/biome_analyze/CONTRIBUTING.mdcrates/biome_js_analyze/src/lint/Semantic<CONTRIBUTING.md