# Lint Rule Development

> Use this skill when creating or modifying Biome lint rules or assists, including analyzer queries, semantic bindings, rule state, code actions, fix safety, options, registration, and end-user rule rustdoc. Also load diagnostics-development for substantial message/advice design and testing-codegen for fixture or snapshot mechanics.

- Skill: `biomejs/lint-rule-development` (Agent Skill)
- Install (CLI): `npx skillmds@latest add biomejs/lint-rule-development`
- Raw SKILL.md: https://api.skillmd.com/api/skills/biomejs/lint-rule-development/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: biomejs (https://skillmd.com/u/biomejs)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/biomejs/lint-rule-development

---


# Lint Rule Development

Follow the current analyzer architecture in `crates/biome_analyze/CONTRIBUTING.md`. Read only the sections relevant to the rule rather than loading the entire guide by default.

## Workflow

1. Find two or three current rules in the same language and group with a similar query or action.
2. Generate the matching lint-rule or assist scaffolding when adding analyzer behavior.
3. Implement the narrowest query and state needed to decide whether to signal.
4. Add a diagnostic and, when safe, an action.
5. Add focused valid and invalid fixtures, then inspect snapshots.
6. Run required analyzer codegen and the narrowest tests.

Scaffolding commands:

```shell
just new-js-lintrule useMyRule
just new-css-lintrule useMyRule
just new-json-lintrule useMyRule
just new-graphql-lintrule useMyRule
```

New lint rules start in `nursery`. They are patch changes targeting `main`, because nursery rules do not follow normal feature versioning. Load `changeset` for the release entry.

For a new assist, use the language's `new-*-assistrule` recipe, for example:

```shell
just new-js-assistrule useMyAction
just new-json-assistrule useMyAction
```

The generator places assists under `src/assist/source/`; they do not use lint groups or the nursery policy. A new assist is a user-facing feature and normally requires a minor changeset targeting `next`. A bug fix to an existing assist follows normal bug-fix policy. Check `justfile` for the languages with assist scaffolding.

## Query and State

Choose the least expensive query that answers the rule:

- `Ast<Node>` for syntax-local checks;
- `Semantic<Node>` when bindings, references, scopes, or globals are required;
- a service query only when the fact is owned by that service;
- type inference only when syntax and the semantic model cannot answer the question.

The analyzer guide's query and service sections are canonical for available APIs.

`run()` should decide whether to emit a signal. Keep action-only work in `action()` so it is not performed for every candidate node.

Prefer ranges, syntax nodes, tokens, and compact enums in `State`. A `String`, `Box<str>`, or collection built from syntax text often indicates avoidable allocation; load `syntax-text-handling` before owning source text.

For rules matching a global identifier, prove the reference resolves to the global rather than a local shadow. Cover every relevant member of node unions and framework-specific syntax families.

## Diagnostics

Every diagnostic answers three separate questions:

1. What condition was found?
2. Why is it a problem?
3. What should the user do?

The message answers the first question. Advice answers the second and, when no action exists, the third. A code action and its label normally answer the third when an automated fix exists.

Load `diagnostics-development` for message structure, markup, details, advice, categories, and standalone `Diagnostic` types. Do not duplicate its guidance in the rule implementation.

## Actions

Build mutations in `action()`. Set `FixKind::Safe` only when no reachable input changes behavior. If safety depends on assumptions the rule cannot prove, use `Unsafe`.

Test that applying the action:

- produces valid syntax;
- preserves comments and trivia;
- does not trigger the same rule again;
- preserves semantics for a safe fix;
- handles every syntax variant accepted by `run()`.

Use existing rules with the same mutation shape as API examples.

## Options

Add options only for a real semantic mode or established conflicting preference. Do not add speculative flexibility.

Use the rule's generated or existing file under `crates/biome_rule_options/src/` as the source of truth. Follow the analyzer guide sections **Rule Options**, **Merge**, and **Documenting Options** for current derives, merge behavior, configuration examples, and rustdoc requirements.

Check these integration points:

- the rule's `type Options`;
- defaults preserve behavior when the option is absent;
- extended configurations merge according to the option type's contract;
- fixture directories with `options.json` cover each behavior;
- rule rustdoc states every option's default and shows it in use;
- `just gen-rules` and `just gen-configuration` update registrations and configuration.

If the source ESLint rule has options that `biome migrate eslint` should preserve, load `eslint-migrate-options`.

## Rule Documentation

Rustdoc inside `declare_lint_rule!` and `declare_assist_rule!` is end-user website content, not internal API documentation.

- Start with a single-line behavior summary.
- Explain why the reported pattern is problematic.
- Put `### Invalid` examples before `### Valid` examples.
- Mark examples according to the current rules-check syntax.
- For every option, state the default, show configuration, and show an applied example.
- Do not use `ignore` merely to avoid validation.

Inspect neighboring rule documentation and the analyzer guide before writing examples.

## Testing and Generation

Load `testing-codegen` for fixture naming, expectation comments, `.jsonc` cases, snapshot review, and pruning.

Typical focused commands:

```shell
just test-lintrule useMyRule
just gen-rules
just gen-configuration
```

Run `just f` and `just l` as required by `AGENTS.md`.

## Review Checklist

- The query is no broader than needed.
- Globals and shadowing are handled through the semantic model.
- `State` avoids unnecessary owned source text.
- Action-only work is deferred to `action()`.
- Safe fixes preserve semantics.
- Defaults preserve existing behavior.
- Valid, invalid, option, suppression, and fix cases cover the changed branches.
- Required generated artifacts are present.
- User-facing diagnostics and rustdoc explain behavior rather than implementation.

## References

- Analyzer guide: `crates/biome_analyze/CONTRIBUTING.md`
- Rule implementations: `crates/biome_*_analyze/src/lint/`
- Rule options: `crates/biome_rule_options/src/`
- Test fixtures: `crates/biome_*_analyze/tests/specs/`

