Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions crates/biome_analyze/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The analyzer allows implementors to create **three different** types of rules:

- [Analyzer](#analyzer)
* [Table of Contents](#table-of-contents)
* [Understanding Biome Linter](#understanding-biome-linter)
* [Creating a Rule](#creating-a-rule)
+ [Guidelines](#guidelines)
- [Naming Conventions for Rules](#naming-conventions-for-rules)
Expand Down Expand Up @@ -56,6 +57,15 @@ The analyzer allows implementors to create **three different** types of rules:
+ [Committing your work](#committing-your-work)
+ [Sidenote: Deprecating a rule](#sidenote-deprecating-a-rule)

## Understanding Biome Linter

Biome linter is meant to work *across languages*, which means that a rule can work within multiple languages.
That's why **it's important to choose a good name for your rule**. If the name of the rule is very generic, it means that
it could potentially be implemented for multiple languages. However, if a rule is meant for a specific language, you should
choose a name that is more specific.

Understanding this is important because it might have repercussions on how rule options will be applied to different languages.

## Creating a Rule

When creating or updating a rule, you need to be aware that there's a lot of generated code inside our toolchain.
Expand Down Expand Up @@ -539,6 +549,12 @@ Some rules may allow customization [using per-rule options in `biome.json`](http
>
> If provided, options should follow our [technical philosophy](https://biomejs.dev/internals/philosophy/#technical).

Rule options must be placed inside the crate `biome_rule_options`. If you run the command `just gen-analyzer`, the codegen
should have created a new file inside the crate that has the name of your rule. For example, if the rule name is `useThisConvention`
you should see a file `use_this_convention.rs` inside `biome_rule_options/lib`. Inside this file you'll see a struct called `UseThisConventionOptions`.

Use this struct to add the options.

##### Options for our example rule

Let's assume that the rule we want to implement supports the following options:
Expand Down Expand Up @@ -603,11 +619,13 @@ for you.
With these types in place, you can set the associated type `Options` of the rule:

```rust
impl Rule for MyRule {
use biome_rule_options::use_my_rule::UseMyRuleOptions;

impl Rule for UseMyRule {
type Query = Semantic<JsCallExpression>;
type State = Fix;
type Signals = Vec<Self::State>;
type Options = MyRuleOptions;
type Options = UseMyRuleOptions;
}
```

Expand Down Expand Up @@ -639,18 +657,19 @@ Also, we use other `serde` macros to adjust the JSON configuration:
- `deny_unknown_fields`: it raises an error if the configuration contains extraneous fields.
- `default`: it uses the `Default` value when the field is missing from `biome.json`. This macro makes the field optional.

Because we use `schemars`to generate a JSON schema for `biome.json`, our options type must support the `schemars::JsonSchema` trait as well.
Because we use `schemars` to generate a JSON schema for `biome.json`, our options type must support the `schemars::JsonSchema` trait as well.

You can simply use the derive macros provided by `serde`, `biome_deserialize` and `schemars` to generate the necessary implementations automatically:

```rust
// crates/biome_rule_options/lib/use_my_rule.rs
use biome_deserialize_macros::Deserializable;
use serde::{Deserialize, Serialize};

#[derive(Debug, Default, Clone, Serialize, Deserialize, Deserializable)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, default)]
pub struct MyRuleOptions {
pub struct UseMyRuleOptions {
#[serde(default, skip_serializing_if = "is_default")]
main_behavior: Behavior,

Expand All @@ -659,13 +678,15 @@ pub struct MyRuleOptions {
}

#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub enum Behavior {
#[default]
A,
B,
C,
}

const fn is_default() -> bool { true }
```

##### Testing & Documenting Rule Options
Expand Down