lint-rule-development

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Purpose

用途

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

前置条件

  1. Install required tools:
    just install-tools
  2. Ensure
    cargo
    ,
    just
    , and
    pnpm
    are available
  3. Read
    crates/biome_analyze/CONTRIBUTING.md
    for in-depth concepts
  1. 安装所需工具:
    just install-tools
  2. 确保
    cargo
    just
    pnpm
    已可用
  3. 阅读
    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 useMyRuleName
For other languages:
shell
just new-css-lintrule myRuleName
just new-json-lintrule myRuleName
just new-graphql-lintrule myRuleName
This 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.rs
路径下创建文件

Implement 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-output

Create 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 configuration
Example
invalid.js
:
javascript
const prohibited_name = 1;
const another_prohibited = 2;
Run snapshot tests:
shell
just test-lintrule useMyRuleName
Review 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.js
javascript
const prohibited_name = 1;
const another_prohibited = 2;
运行快照测试:
shell
just test-lintrule useMyRuleName
查看快照:
shell
cargo insta review

Generate Analyzer Code

生成分析器代码

After modifying rules, generate updated boilerplate:
shell
just gen-analyzer
This 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 code

Tips

提示

  • Rule naming: Use
    no*
    prefix for rules that forbid something (e.g.,
    noVar
    ),
    use*
    for rules that mandate something (e.g.,
    useConst
    )
  • Nursery group: All new rules start in the
    nursery
    group
  • Semantic queries: Use
    Semantic<Node>
    query when you need binding/scope analysis
  • Multiple signals: Return
    Vec<Self::State>
    or
    Box<[Self::State]>
    to emit multiple diagnostics
  • Safe vs Unsafe fixes: Mark fixes as
    Unsafe
    if they could change program behavior
  • Check for globals: Always verify if a variable is global before reporting it (use semantic model)
  • Error recovery: When navigating CST, use
    .ok()?
    pattern to handle missing nodes gracefully
  • Testing arrays: Use
    .jsonc
    files with arrays of code snippets for multiple test cases
  • 规则命名:使用
    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
    Semantic<
    in existing rules
  • Testing guide: Main
    CONTRIBUTING.md
    testing section
  • 完整指南:
    crates/biome_analyze/CONTRIBUTING.md
  • 规则示例:
    crates/biome_js_analyze/src/lint/
  • 语义模型:在现有规则中搜索
    Semantic<
  • 测试指南:主
    CONTRIBUTING.md
    中的测试章节