rule-options
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePurpose
用途
Use this skill when implementing configurable options for lint rules. Covers defining option types, JSON deserialization, configuration merging, and testing with options.
当你为lint规则实现可配置选项时使用本指南。内容涵盖选项类型定义、JSON反序列化、配置合并以及带选项的测试。
Prerequisites
前置条件
- Understand that options should be minimal - only add when needed
- Options must follow Technical Philosophy
- Rule must be implemented before adding options
- 理解选项应尽可能精简——仅在必要时添加
- 选项必须遵循技术理念
- 必须先实现规则,再添加选项
Common Workflows
常见工作流
Define Rule Options Type
定义规则选项类型
Options live in crate. After running , a file is created for your rule.
biome_rule_optionsjust gen-analyzerExample for rule in :
useThisConventionbiome_rule_options/src/use_this_convention.rsrust
use biome_deserialize_macros::{Deserializable, Merge};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Serialize, Deserialize, Deserializable)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, default)]
pub struct UseThisConventionOptions {
/// What behavior to enforce
#[serde(skip_serializing_if = "Option::is_none")]
behavior: Option<Behavior>,
/// Threshold value between 0-255
#[serde(skip_serializing_if = "Option::is_none")]
threshold: Option<u8>,
/// Exceptions to the behavior
#[serde(skip_serializing_if = "Option::is_none")]
behavior_exceptions: Option<Box<[Box<str>]>>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, Deserializable, Merge)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub enum Behavior {
#[default]
A,
B,
C,
}Key points:
- All fields wrapped in for proper merging
Option<_> - Use instead of
Box<[Box<str>]>(saves memory)Vec<String> - for JavaScript naming
#[serde(rename_all = "camelCase")] - to catch typos
#[serde(deny_unknown_fields)] - makes all fields optional
#[serde(default)]
选项存放在 crate中。运行后,会为你的规则创建一个文件。
biome_rule_optionsjust gen-analyzer以中的规则为例:
biome_rule_options/src/use_this_convention.rsuseThisConventionrust
use biome_deserialize_macros::{Deserializable, Merge};
use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Serialize, Deserialize, Deserializable)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, default)]
pub struct UseThisConventionOptions {
/// What behavior to enforce
#[serde(skip_serializing_if = "Option::is_none")]
behavior: Option<Behavior>,
/// Threshold value between 0-255
#[serde(skip_serializing_if = "Option::is_none")]
threshold: Option<u8>,
/// Exceptions to the behavior
#[serde(skip_serializing_if = "Option::is_none")]
behavior_exceptions: Option<Box<[Box<str>]>>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize, Deserializable, Merge)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub enum Behavior {
#[default]
A,
B,
C,
}关键点:
- 所有字段都用包裹,以支持正确的合并
Option<_> - 使用而非
Box<[Box<str>]>(节省内存)Vec<String> - 使用以符合JavaScript命名规范
#[serde(rename_all = "camelCase")] - 使用捕获拼写错误
#[serde(deny_unknown_fields)] - 让所有字段变为可选
#[serde(default)]
Implement Merge Trait
实现Merge trait
Options from shared config + user config need merging:
rust
impl biome_deserialize::Merge for UseThisConventionOptions {
fn merge_with(&mut self, other: Self) {
// `self` = shared config
// `other` = user config
// For simple values, use helper
self.behavior.merge_with(other.behavior);
self.threshold.merge_with(other.threshold);
// For collections, typically reset instead of combine
if let Some(exceptions) = other.behavior_exceptions {
self.behavior_exceptions = Some(exceptions);
}
}
}Merge strategies:
- Simple values (enums, numbers): Use (takes user value if present)
merge_with() - Collections: Usually reset to user value, not combine
- Derive macro: Can use for simple cases
#[derive(Merge)]
来自共享配置与用户配置的选项需要合并:
rust
impl biome_deserialize::Merge for UseThisConventionOptions {
fn merge_with(&mut self, other: Self) {
// `self` = shared config
// `other` = user config
// For simple values, use helper
self.behavior.merge_with(other.behavior);
self.threshold.merge_with(other.threshold);
// For collections, typically reset instead of combine
if let Some(exceptions) = other.behavior_exceptions {
self.behavior_exceptions = Some(exceptions);
}
}
}合并策略:
- 简单值(枚举、数字):使用(如果存在用户值则优先使用)
merge_with() - 集合:通常重置为用户值,而非合并
- 派生宏:简单场景可使用
#[derive(Merge)]
Use Options in Rule
在规则中使用选项
rust
use biome_rule_options::use_this_convention::UseThisConventionOptions;
impl Rule for UseThisConvention {
type Query = Semantic<JsCallExpression>;
type State = Fix;
type Signals = Vec<Self::State>;
type Options = UseThisConventionOptions;
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
// Get options for current location
let options = ctx.options();
// Access option values (all are Option<T>)
let behavior = options.behavior.as_ref();
let threshold = options.threshold.unwrap_or(50); // default to 50
if let Some(exceptions) = &options.behavior_exceptions {
if exceptions.iter().any(|ex| ex.as_ref() == name) {
// Name is in exceptions, skip rule
return vec![];
}
}
// Rule logic using options...
vec![]
}
}Context automatically handles:
- Configuration file location
- inheritance
extends - for specific files
overrides
rust
use biome_rule_options::use_this_convention::UseThisConventionOptions;
impl Rule for UseThisConvention {
type Query = Semantic<JsCallExpression>;
type State = Fix;
type Signals = Vec<Self::State>;
type Options = UseThisConventionOptions;
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
// Get options for current location
let options = ctx.options();
// Access option values (all are Option<T>)
let behavior = options.behavior.as_ref();
let threshold = options.threshold.unwrap_or(50); // default to 50
if let Some(exceptions) = &options.behavior_exceptions {
if exceptions.iter().any(|ex| ex.as_ref() == name) {
// Name is in exceptions, skip rule
return vec![];
}
}
// Rule logic using options...
vec![]
}
}上下文会自动处理:
- 配置文件位置
- 继承
extends - 针对特定文件的
overrides
Configure in biome.json
在biome.json中配置
Users configure options like this:
json
{
"linter": {
"rules": {
"nursery": {
"useThisConvention": {
"level": "error",
"options": {
"behavior": "A",
"threshold": 30,
"behaviorExceptions": ["foo", "bar"]
}
}
}
}
}
}用户可按如下方式配置选项:
json
{
"linter": {
"rules": {
"nursery": {
"useThisConvention": {
"level": "error",
"options": {
"behavior": "A",
"threshold": 30,
"behaviorExceptions": ["foo", "bar"]
}
}
}
}
}
}Test with Options
带选项测试
Create in test directory:
options.jsontests/specs/nursery/useThisConvention/
├── invalid.js
├── valid.js
├── with_behavior_a/
│ ├── options.json
│ ├── invalid.js
│ └── valid.js
└── with_exceptions/
├── options.json
└── valid.jsExample :
with_behavior_a/options.jsonjson
{
"linter": {
"rules": {
"nursery": {
"useThisConvention": {
"level": "error",
"options": {
"behavior": "A",
"threshold": 10
}
}
}
}
}
}Options apply to all test files in that directory.
在测试目录中创建:
options.jsontests/specs/nursery/useThisConvention/
├── invalid.js
├── valid.js
├── with_behavior_a/
│ ├── options.json
│ ├── invalid.js
│ └── valid.js
└── with_exceptions/
├── options.json
└── valid.jswith_behavior_a/options.jsonjson
{
"linter": {
"rules": {
"nursery": {
"useThisConvention": {
"level": "error",
"options": {
"behavior": "A",
"threshold": 10
}
}
}
}
}
}选项会应用到该目录下的所有测试文件。
Document Options in Rule
在规则中记录选项
Add options documentation to rule's rustdoc:
rust
declare_lint_rule! {
/// Enforces a specific convention for code organization.
///
/// ## Options
///
/// ### `behavior`
///
/// Specifies which behavior to enforce. Accepted values are:
/// - `"A"` (default): Enforces behavior A
/// - `"B"`: Enforces behavior B
/// - `"C"`: Enforces behavior C
///
/// ### `threshold`
///
/// A number between 0-255 (default: 50). Controls sensitivity of detection.
///
/// ### `behaviorExceptions`
///
/// An array of strings. Names listed here are excluded from the rule.
///
/// ## Examples
///
/// ### With default options
///
/// [examples with default behavior]
///
/// ### With `behavior` set to "B"
///
/// ```json
/// {
/// "useThisConvention": {
/// "level": "error",
/// "options": {
/// "behavior": "B"
/// }
/// }
/// }
/// ```
///
/// [examples with behavior B]
pub UseThisConvention {
version: "next",
name: "useThisConvention",
language: "js",
recommended: false,
}
}将选项文档添加到规则的rustdoc中:
rust
declare_lint_rule! {
/// Enforces a specific convention for code organization.
///
/// ## Options
///
/// ### `behavior`
///
/// Specifies which behavior to enforce. Accepted values are:
/// - `"A"` (default): Enforces behavior A
/// - `"B"`: Enforces behavior B
/// - `"C"`: Enforces behavior C
///
/// ### `threshold`
///
/// A number between 0-255 (default: 50). Controls sensitivity of detection.
///
/// ### `behaviorExceptions`
///
/// An array of strings. Names listed here are excluded from the rule.
///
/// ## Examples
///
/// ### With default options
///
/// [examples with default behavior]
///
/// ### With `behavior` set to "B"
///
/// ```json
/// {
/// "useThisConvention": {
/// "level": "error",
/// "options": {
/// "behavior": "B"
/// }
/// }
/// }
/// ```
///
/// [examples with behavior B]
pub UseThisConvention {
version: "next",
name: "useThisConvention",
language: "js",
recommended: false,
}
}Generate Schema and Bindings
生成Schema与绑定
After implementing options:
shell
just gen-analyzerThis updates:
- JSON schema in configuration
- TypeScript bindings
- Documentation exports
实现选项后运行:
shell
just gen-analyzer这会更新:
- 配置中的JSON schema
- TypeScript绑定
- 文档导出
Option Design Guidelines
选项设计指南
When to Add Options
何时添加选项
Good reasons:
- Conflicting style preferences in community
- Rule has multiple valid interpretations
- Different behavior needed for different environments
Bad reasons:
- Making rule "more flexible" without clear use case
- Avoiding making opinionated decision
- Working around incomplete implementation
合理理由:
- 社区存在冲突的风格偏好
- 规则有多种合理解释
- 不同环境需要不同行为
不合理理由:
- 无明确使用场景却想让规则“更灵活”
- 避免做出有主见的决策
- 规避不完整的实现
Option Naming
选项命名
rust
// ✅ Good - clear, semantic names
allow_single_line: bool
max_depth: u8
ignore_patterns: Box<[Box<str>]>
// ❌ Bad - unclear, technical names
flag: bool
n: u8
list: Vec<String>rust
// ✅ Good - clear, semantic names
allow_single_line: bool
max_depth: u8
ignore_patterns: Box<[Box<str>]>
// ❌ Bad - unclear, technical names
flag: bool
n: u8
list: Vec<String>Option Types
选项类型
rust
// Simple values
enabled: bool
max_count: u8 // or u16, u32
min_length: usize
// Enums for fixed choices
#[derive(Deserializable, Merge)]
enum QuoteStyle {
Single,
Double,
Preserve,
}
// Collections (use boxed slices)
patterns: Box<[Box<str>]>
ignore_names: Box<[Box<str>]>
// Complex nested options
#[derive(Deserializable)]
struct AdvancedOptions {
mode: Mode,
exclusions: Box<[Box<str>]>,
}rust
// Simple values
enabled: bool
max_count: u8 // or u16, u32
min_length: usize
// Enums for fixed choices
#[derive(Deserializable, Merge)]
enum QuoteStyle {
Single,
Double,
Preserve,
}
// Collections (use boxed slices)
patterns: Box<[Box<str>]>
ignore_names: Box<[Box<str>]>
// Complex nested options
#[derive(Deserializable)]
struct AdvancedOptions {
mode: Mode,
exclusions: Box<[Box<str>]>,
}Common Patterns
常见模式
rust
// Pattern 1: Boolean option with default false
#[derive(Default)]
struct MyOptions {
allow_something: Option<bool>,
}
impl Rule for MyRule {
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let allow = ctx.options().allow_something.unwrap_or(false);
if allow { return None; }
// ...
}
}
// Pattern 2: Enum option with default
#[derive(Default)]
enum Mode {
#[default]
Strict,
Loose,
}
// Pattern 3: Collection option (exclusions)
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let options = ctx.options();
if let Some(exclusions) = &options.exclusions {
if exclusions.iter().any(|ex| matches_name(ex, name)) {
return None; // Excluded
}
}
// Check rule normally
}
// Pattern 4: Numeric threshold
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let threshold = ctx.options().max_depth.unwrap_or(3);
if depth > threshold {
return Some(());
}
None
}rust
// Pattern 1: Boolean option with default false
#[derive(Default)]
struct MyOptions {
allow_something: Option<bool>,
}
impl Rule for MyRule {
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let allow = ctx.options().allow_something.unwrap_or(false);
if allow { return None; }
// ...
}
}
// Pattern 2: Enum option with default
#[derive(Default)]
enum Mode {
#[default]
Strict,
Loose,
}
// Pattern 3: Collection option (exclusions)
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let options = ctx.options();
if let Some(exclusions) = &options.exclusions {
if exclusions.iter().any(|ex| matches_name(ex, name)) {
return None; // Excluded
}
}
// Check rule normally
}
// Pattern 4: Numeric threshold
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let threshold = ctx.options().max_depth.unwrap_or(3);
if depth > threshold {
return Some(());
}
None
}Tips
提示
- Minimize options: Only add when truly needed
- Memory efficiency: Use not
Box<[Box<str>]>for arraysVec<String> - Optional wrapping: All option fields should be for proper merging
Option<T> - Serde attributes: Always use and
rename_all = "camelCase"deny_unknown_fields - Schema generation: Use
#[cfg_attr(feature = "schema", derive(JsonSchema))] - Default trait: Implement or derive for option types
Default - Testing: Test with multiple option combinations
- Documentation: Document each option with examples
- Codegen: Run after adding options
just gen-analyzer
- 最小化选项:仅在真正必要时添加
- 内存效率:数组使用而非
Box<[Box<str>]>Vec<String> - 可选包裹:所有选项字段都应为以支持正确合并
Option<T> - Serde属性:始终使用和
rename_all = "camelCase"deny_unknown_fields - Schema生成:使用
#[cfg_attr(feature = "schema", derive(JsonSchema))] - Default trait:为选项类型实现或派生
Default - 测试:测试多种选项组合
- 文档:为每个选项添加示例文档
- 代码生成:添加选项后运行
just gen-analyzer
Configuration Merging Example
配置合并示例
json5
// shared.jsonc (extended configuration)
{
"linter": {
"rules": {
"nursery": {
"myRule": {
"options": {
"behavior": "A",
"exclusions": ["foo"]
}
}
}
}
}
}
// biome.jsonc (user configuration)
{
"extends": ["./shared.jsonc"],
"linter": {
"rules": {
"nursery": {
"myRule": {
"options": {
"threshold": 30,
"exclusions": ["bar"] // Replaces ["foo"], doesn't append
}
}
}
}
}
}
// Result after merging:
// behavior: "A" (from shared)
// threshold: 30 (from user)
// exclusions: ["bar"] (user replaces shared)json5
// shared.jsonc (extended configuration)
{
"linter": {
"rules": {
"nursery": {
"myRule": {
"options": {
"behavior": "A",
"exclusions": ["foo"]
}
}
}
}
}
}
// biome.jsonc (user configuration)
{
"extends": ["./shared.jsonc"],
"linter": {
"rules": {
"nursery": {
"myRule": {
"options": {
"threshold": 30,
"exclusions": ["bar"] // Replaces ["foo"], doesn't append
}
}
}
}
}
}
// Result after merging:
// behavior: "A" (from shared)
// threshold: 30 (from user)
// exclusions: ["bar"] (user replaces shared)References
参考资料
- Analyzer guide: § Rule Options
crates/biome_analyze/CONTRIBUTING.md - Options crate:
crates/biome_rule_options/ - Deserialize macros:
crates/biome_deserialize_macros/ - Example rules with options: Search for in
type Options =cratesbiome_*_analyze
- 分析器指南:§ Rule Options
crates/biome_analyze/CONTRIBUTING.md - 选项crate:
crates/biome_rule_options/ - 反序列化宏:
crates/biome_deserialize_macros/ - 带选项的示例规则:在crates中搜索
biome_*_analyzetype Options =