lint-rule-development
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePurpose
用途
Use this skill when creating new lint rules or assist actions for Biome. It provides scaffolding commands, implementation patterns, testing workflows, and documentation guidelines.
当你为Biome创建新的lint规则或辅助操作时,可以使用本技能。它提供了脚手架命令、实现模式、测试工作流以及文档指南。
Prerequisites
前置条件
- Install required tools:
just install-tools - Ensure ,
cargo, andjustare availablepnpm - Read for in-depth concepts
crates/biome_analyze/CONTRIBUTING.md
- 安装所需工具:
just install-tools - 确保、
cargo和just已可用pnpm - 阅读以深入了解相关概念
crates/biome_analyze/CONTRIBUTING.md
Common Workflows
常见工作流
Create a New Lint Rule
创建新的Lint规则
Generate scaffolding for a JavaScript lint rule:
shell
just new-js-lintrule useMyRuleNameFor other languages:
shell
just new-css-lintrule myRuleName
just new-json-lintrule myRuleName
just new-graphql-lintrule myRuleNameThis creates a file in
crates/biome_js_analyze/src/lint/nursery/use_my_rule_name.rs为JavaScript lint规则生成脚手架:
shell
just new-js-lintrule useMyRuleName针对其他语言:
shell
just new-css-lintrule myRuleName
just new-json-lintrule myRuleName
just new-graphql-lintrule myRuleName此命令会在路径下创建文件
crates/biome_js_analyze/src/lint/nursery/use_my_rule_name.rsImplement the Rule
实现规则
Basic rule structure (generated by scaffolding):
rust
use 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..."
}),
)
}
}基础规则结构(由脚手架生成):
rust
use 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..."
}),
)
}
}Using Semantic Model
使用语义模型
For rules that need binding analysis:
rust
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(())
}
}对于需要绑定分析的规则:
rust
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(())
}
}Add Code Actions (Fixes)
添加代码操作(修复功能)
To provide automatic fixes:
rust
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,
))
}
}若要提供自动修复:
rust
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,
))
}
}Quick Testing
快速测试
Use the quick test for rapid iteration:
rust
// 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");Run the test:
shell
cd crates/biome_js_analyze
cargo test quick_test -- --show-output使用快速测试进行快速迭代:
rust
// 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");运行测试:
shell
cd crates/biome_js_analyze
cargo test quick_test -- --show-outputCreate Snapshot Tests
创建快照测试
Create test files in :
tests/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 configurationExample :
invalid.jsjavascript
const prohibited_name = 1;
const another_prohibited = 2;Run snapshot tests:
shell
just test-lintrule useMyRuleNameReview snapshots:
shell
cargo insta review在路径下创建测试文件:
tests/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 configuration示例:
invalid.jsjavascript
const prohibited_name = 1;
const another_prohibited = 2;运行快照测试:
shell
just test-lintrule useMyRuleName查看快照:
shell
cargo insta reviewGenerate Analyzer Code
生成分析器代码
After modifying rules, generate updated boilerplate:
shell
just gen-analyzerThis updates:
- Rule registrations
- Configuration schemas
- Documentation exports
- Type bindings
修改规则后,生成更新后的样板代码:
shell
just gen-analyzer此命令会更新:
- 规则注册信息
- 配置模式
- 文档导出内容
- 类型绑定
Format and Lint
格式化与检查
Before committing:
shell
just f # Format code
just l # Lint code提交前执行:
shell
just f # Format code
just l # Lint codeTips
提示
- Rule naming: Use prefix for rules that forbid something (e.g.,
no*),noVarfor rules that mandate something (e.g.,use*)useConst - Nursery group: All new rules start in the group
nursery - Semantic queries: Use query when you need binding/scope analysis
Semantic<Node> - Multiple signals: Return or
Vec<Self::State>to emit multiple diagnosticsBox<[Self::State]> - Safe vs Unsafe fixes: Mark fixes as if they could change program behavior
Unsafe - Check for globals: Always verify if a variable is global before reporting it (use semantic model)
- Error recovery: When navigating CST, use pattern to handle missing nodes gracefully
.ok()? - Testing arrays: Use files with arrays of code snippets for multiple test cases
.jsonc
- 规则命名:使用前缀命名禁止性规则(例如
no*),使用noVar前缀命名强制性规则(例如use*)useConst - Nursery分组:所有新规则初始都属于分组
nursery - 语义查询:当需要绑定/作用域分析时,使用查询
Semantic<Node> - 多信号返回:返回或
Vec<Self::State>以输出多个诊断信息Box<[Self::State]> - 安全与不安全修复:如果修复可能改变程序行为,标记为类型
Unsafe - 全局变量检查:报告变量前务必验证其是否为全局变量(使用语义模型)
- 错误恢复:遍历CST时,使用模式优雅处理缺失的节点
.ok()? - 测试数组:使用文件存储代码片段数组,实现多测试用例
.jsonc
Common Query Types
常见查询类型
rust
// 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>;rust
// 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>;References
参考资料
- Full guide:
crates/biome_analyze/CONTRIBUTING.md - Rule examples:
crates/biome_js_analyze/src/lint/ - Semantic model: Search for in existing rules
Semantic< - Testing guide: Main testing section
CONTRIBUTING.md
- 完整指南:
crates/biome_analyze/CONTRIBUTING.md - 规则示例:
crates/biome_js_analyze/src/lint/ - 语义模型:在现有规则中搜索
Semantic< - 测试指南:主中的测试章节
CONTRIBUTING.md