/eslint-migrate-options
Guide for implementing ESLint-to-Biome rule option migrators inside `biome migrate eslint`. Use whenever you add or update a Biome lint rule that has an ESLint source rule with configurable options, need to deserialize plugin-specific ESLint options, or need custom migration
$ npx -y skills add biomejs/biome --skill eslint-migrate-options --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/eslint-migrate-options
Context preview
The summary Claude sees to decide when to auto-load this skill.
Guide for implementing ESLint-to-Biome rule option migrators inside `biome migrate eslint`. Use whenever you add or update a Biome lint rule that has an ESLint source rule with configurable options, need to deserialize plugin-specific ESLint options, or need custom migration
SKILL.md
eslint-migrate-options.SKILL.mdname: eslint-migrate-options
description: Guide for implementing ESLint-to-Biome rule option migrators inside `biome migrate eslint`. Use whenever you add or update a Biome lint rule that has an ESLint source rule with configurable options, need to deserialize plugin-specific ESLint options, or need custom migration logic beyond the auto-generated severity mapping.
compatibility: Designed for coding agents working on the Biome codebase (github.com/biomejs/biome).
Purpose
Use this skill when a Biome lint rule already exists and `biome migrate eslint` should preserve more than just the rule severity.
This skill is specifically for cases where an ESLint rule has options that need to be:
- deserialized from ESLint config
- translated into Biome rule options
- wired into the migrate pipeline
- tested through migrator spec fixtures without depending on CLI tests
Do not use this skill for severity-only migrations. Those are usually covered by the generated rule mapping in `eslint_any_rule_to_biome.rs`.
Before You Edit
Confirm these points first:
1. The target Biome rule already exists and already has its own options type in `crates/biome_rule_options/src/`. 2. The Biome rule metadata already declares the ESLint source rule, so severity-only migration exists or can be generated. 3. The ESLint rule really has user-facing options worth preserving. 4. You have checked the ESLint rule docs or source so you know the exact option shape, defaults, and any plugin-specific quirks.
If any of those are missing, fix that first before adding a migrator.
Mental Model
The migrate pipeline has two layers:
1. Generated severity mapping: `eslint_any_rule_to_biome.rs` 2. Hand-written option migration: plugin-specific structs plus a custom arm in `migrate_eslint_rule()`
The generated file already handles the common case:
{
"some-rule": "error"
}Add a custom migrator only when a config like this should keep its options:
{
"some-rule": ["error", { "someOption": true }]
}Key Files
| File | Role | | - | - | | `crates/biome_cli/src/execute/migrate/eslint_eslint.rs` | Shared ESLint config model, `Rule` enum, `RuleConf<T>`, deserialization entry points | | `crates/biome_cli/src/execute/migrate/eslint_unicorn.rs` | `eslint-plugin-unicorn` option structs and conversions | | `crates/biome_cli/src/execute/migrate/eslint_typescript.rs` | `@typescript-eslint` option structs and conversions | | `crates/biome_cli/src/execute/migrate/eslint_jsxa11y.rs` | `jsx-a11y` option structs and conversions | | `crates/biome_cli/src/execute/migrate/eslint_to_biome.rs` | Main conversion logic, including `migrate_eslint_rule()` | | `crates/biome_cli/tests/specs/migrate_eslint/` | Fixture-driven snapshot tests for custom ESLint migrators | | `crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs` | Generated severity mapping for all known ESLint-backed rules | | `xtask/codegen/src/generate_migrate_eslint.rs` | Codegen for the generated rule mapping |
Use the plugin-specific file that matches the source ESLint rule. Keep option structs close to similar migrators so future edits stay discoverable.
Recommended Workflow
Step 1: Inspect an Existing Migrator First
Before writing anything new, find a nearby rule that already migrates options. Reuse its shape if the target rule is in the same plugin or has the same Biome configuration type (`RuleConfiguration` vs `RuleFixConfiguration`).
This saves time and helps match the patterns already used in `migrate_eslint_rule()`.
Step 2: Model the ESLint Options Exactly
Add structs in the correct plugin file. Match ESLint's option payload shape, not Biome's.
use biome_deserialize_macros::Deserializable;
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct EslintMyRuleOptions {
some_option: Option<u8>,
another_option: bool,
nested: EslintMyRuleNestedOptions,
}
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct EslintMyRuleNestedOptions {
threshold: Option<u8>,
}Guidelines:
- Use snake_case Rust field names; `Deserializable` handles camelCase JSON keys.
- Use `Option<T>` for fields that can be omitted.
- Keep unsupported ESLint fields in the struct if they appear in the config shape; ignore them later during conversion.
- Prefer mirroring the real JSON nesting instead of flattening early.
Step 3: Convert ESLint Options Into Biome Options
Implement `From<Eslint...Options> for biome_rule_options::...` in the same plugin file.
impl From<EslintMyRuleOptions> for my_rule::MyRuleOptions {
fn from(value: EslintMyRuleOptions) -> Self {
Self {
some_option: value.some_option,
different_name: Some(value.another_option),
threshold: value.nested.threshold,
}
}
}Focus on semantic mapping, not field-for-field copying:
- rename concepts when ESLint and Biome use different names
- drop unsupported knobs deliberately
- preserve defaults only when they match Biome's behavior
- add small helper functions when the conversion needs filtering or normalization
If an ESLint option should only be emitted when at least one nested field is set, use a helper that returns `Option<_>` rather than constructing empty Biome option objects.
Step 4: Add a Typed `Rule` Variant
In `eslint_eslint.rs`, add a `Rule` enum variant using `RuleConf<T>`:
pub(crate) enum Rule {
// ...
MyPluginMyRule(RuleConf<eslint_my_plugin::EslintMyRuleOptions>),
}Then update both of these places:
- `Rule::name()` so the variant returns the ESLint rule name
- `Rules::deserialize` so the ESLint rule string deserializes into your typed variant before the catch-all fallback
Example:
Self::MyPluginMyRule(_) => Cow::Borrowed("my-plugin/my-rule"),"my-plugin/my-rule" => {
if let Some(conf) = RuleConf::deserialize(ctx, &value, name) {
result.insert(Rule::MyPluginMyRule(conf));Read more
name: eslint-migrate-options description: Guide for implementing ESLint-to-Biome rule option migrators inside `biome migrate eslint`. Use whenever you add or update a Biome lint rule that has an ESLint source rule with configurable options, need to deserialize plugin-specific ESLint options, or need custom migration logic beyond the auto-generated severity mapping. compatibility: Designed for coding agents working on the Biome codebase (github.com/biomejs/biome).
Purpose
Use this skill when a Biome lint rule already exists and `biome migrate eslint` should preserve more than just the rule severity.
This skill is specifically for cases where an ESLint rule has options that need to be:
- deserialized from ESLint config
- translated into Biome rule options
- wired into the migrate pipeline
- tested through migrator spec fixtures without depending on CLI tests
Do not use this skill for severity-only migrations. Those are usually covered by the generated rule mapping in `eslint_any_rule_to_biome.rs`.
Before You Edit
Confirm these points first:
1. The target Biome rule already exists and already has its own options type in `crates/biome_rule_options/src/`. 2. The Biome rule metadata already declares the ESLint source rule, so severity-only migration exists or can be generated. 3. The ESLint rule really has user-facing options worth preserving. 4. You have checked the ESLint rule docs or source so you know the exact option shape, defaults, and any plugin-specific quirks.
If any of those are missing, fix that first before adding a migrator.
Mental Model
The migrate pipeline has two layers:
1. Generated severity mapping: `eslint_any_rule_to_biome.rs` 2. Hand-written option migration: plugin-specific structs plus a custom arm in `migrate_eslint_rule()`
The generated file already handles the common case:
{
"some-rule": "error"
}Add a custom migrator only when a config like this should keep its options:
{
"some-rule": ["error", { "someOption": true }]
}Key Files
| File | Role | | - | - | | `crates/biome_cli/src/execute/migrate/eslint_eslint.rs` | Shared ESLint config model, `Rule` enum, `RuleConf<T>`, deserialization entry points | | `crates/biome_cli/src/execute/migrate/eslint_unicorn.rs` | `eslint-plugin-unicorn` option structs and conversions | | `crates/biome_cli/src/execute/migrate/eslint_typescript.rs` | `@typescript-eslint` option structs and conversions | | `crates/biome_cli/src/execute/migrate/eslint_jsxa11y.rs` | `jsx-a11y` option structs and conversions | | `crates/biome_cli/src/execute/migrate/eslint_to_biome.rs` | Main conversion logic, including `migrate_eslint_rule()` | | `crates/biome_cli/tests/specs/migrate_eslint/` | Fixture-driven snapshot tests for custom ESLint migrators | | `crates/biome_cli/src/execute/migrate/eslint_any_rule_to_biome.rs` | Generated severity mapping for all known ESLint-backed rules | | `xtask/codegen/src/generate_migrate_eslint.rs` | Codegen for the generated rule mapping |
Use the plugin-specific file that matches the source ESLint rule. Keep option structs close to similar migrators so future edits stay discoverable.
Recommended Workflow
Step 1: Inspect an Existing Migrator First
Before writing anything new, find a nearby rule that already migrates options. Reuse its shape if the target rule is in the same plugin or has the same Biome configuration type (`RuleConfiguration` vs `RuleFixConfiguration`).
This saves time and helps match the patterns already used in `migrate_eslint_rule()`.
Step 2: Model the ESLint Options Exactly
Add structs in the correct plugin file. Match ESLint's option payload shape, not Biome's.
use biome_deserialize_macros::Deserializable;
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct EslintMyRuleOptions {
some_option: Option<u8>,
another_option: bool,
nested: EslintMyRuleNestedOptions,
}
#[derive(Clone, Debug, Default, Deserializable)]
pub(crate) struct EslintMyRuleNestedOptions {
threshold: Option<u8>,
}Guidelines:
- Use snake_case Rust field names; `Deserializable` handles camelCase JSON keys.
- Use `Option<T>` for fields that can be omitted.
- Keep unsupported ESLint fields in the struct if they appear in the config shape; ignore them later during conversion.
- Prefer mirroring the real JSON nesting instead of flattening early.
Step 3: Convert ESLint Options Into Biome Options
Implement `From<Eslint...Options> for biome_rule_options::...` in the same plugin file.
impl From<EslintMyRuleOptions> for my_rule::MyRuleOptions {
fn from(value: EslintMyRuleOptions) -> Self {
Self {
some_option: value.some_option,
different_name: Some(value.another_option),
threshold: value.nested.threshold,
}
}
}Focus on semantic mapping, not field-for-field copying:
- rename concepts when ESLint and Biome use different names
- drop unsupported knobs deliberately
- preserve defaults only when they match Biome's behavior
- add small helper functions when the conversion needs filtering or normalization
If an ESLint option should only be emitted when at least one nested field is set, use a helper that returns `Option<_>` rather than constructing empty Biome option objects.
Step 4: Add a Typed `Rule` Variant
In `eslint_eslint.rs`, add a `Rule` enum variant using `RuleConf<T>`:
pub(crate) enum Rule {
// ...
MyPluginMyRule(RuleConf<eslint_my_plugin::EslintMyRuleOptions>),
}Then update both of these places:
- `Rule::name()` so the variant returns the ESLint rule name
- `Rules::deserialize` so the ESLint rule string deserializes into your typed variant before the catch-all fallback
Example:
Self::MyPluginMyRule(_) => Cow::Borrowed("my-plugin/my-rule"),"my-plugin/my-rule" => {
if let Some(conf) = RuleConf::deserialize(ctx, &value, name) {
result.insert(Rule::MyPluginMyRule(conf));A toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP.
Repo: biomejs/biome
Other skills on biome.
- /biome-code-review
Static, read-only code review of a Biome (github.com/biomejs/biome) pull request, local branch, commit range, diff, or working tree being prepared as a PR. Applies Biome's own conventions - nursery placement and naming for lint rules, diagnostics quality, allocation and borrow
Open skill - /biome-developer
General development best practices and common gotchas when working on Biome. Use for avoiding common mistakes, understanding Biome-specific patterns (AST, syntax nodes, string extraction, embedded languages), and learning technical tips.
Open skill - /changeset
Guide for creating and writing proper changesets for Biome PRs. Use when a PR introduces user-visible changes (bug fixes, new features, rule changes, formatter changes, parser changes) that need a changeset entry for the CHANGELOG. Trigger when creating changesets, writing
Open skill - /diagnostics-development
Guide for creating high-quality, user-friendly diagnostics in Biome. Use when creating diagnostics for lint rules, adding helpful advice to error messages, implementing code frame displays, or improving diagnostic quality.
Open skill - /doc-comments
How to write inline comments, rustdoc, and module documentation in the Biome codebase. The audience is Biome developers reading the source, not end users. Use whenever writing or editing `//` comments, `///` item docs, or `//!` module docs — including comments added incidentally
Open skill - /formatter-development
Guide for implementing formatting rules using Biome's IR-based formatter infrastructure. Use when implementing formatting for new syntax nodes, handling comments in formatted output, writing or debugging formatter snapshot tests, diagnosing idempotency failures, or comparing
Open skill

