Skip to content
Development
Skill

/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

From plugin
biome
26k12 skills
Install
$ npx -y skills add biomejs/biome --skill eslint-migrate-options --agent claude-code

How 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.md
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));
Read more
Ships withbiome

A toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP.

Get the whole plugin
Stats
25,543
Stars
1,180
Forks
Active
Maintenance
Rust
Language
Apache-2.0
License
43m ago
Last commit
3y ago
Created

Repo: biomejs/biome

Other skills on biome.