/type-inference
Guide for working with Biome's module graph and type inference system. Use when implementing type-aware lint rules, understanding type resolution, working on the module graph infrastructure, or implementing type inference for new features.
$ npx -y skills add biomejs/biome --skill type-inference --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
/type-inference
Context preview
The summary Claude sees to decide when to auto-load this skill.
Guide for working with Biome's module graph and type inference system. Use when implementing type-aware lint rules, understanding type resolution, working on the module graph infrastructure, or implementing type inference for new features.
SKILL.md
type-inference.SKILL.mdname: type-inference
description: Guide for working with Biome's module graph and type inference system. Use when implementing type-aware lint rules, understanding type resolution, working on the module graph infrastructure, or implementing type inference for new features.
compatibility: Designed for coding agents working on the Biome codebase (github.com/biomejs/biome).
Purpose
Use this skill when working with Biome's type inference system and module graph. Covers type references, resolution phases, and the architecture designed for IDE performance.
Prerequisites
1. Read `crates/biome_js_type_info/CONTRIBUTING.md` for architecture details 2. Understand Biome's focus on IDE support and instant updates 3. Familiarity with TypeScript type system concepts
Key Concepts
Module Graph Constraint
**Critical rule**: No module may copy or clone data from another module, not even behind `Arc`.
**Why**: Any module can be updated at any time (IDE file changes). Copying data would create stale references that are hard to invalidate.
**Solution**: Use `TypeReference` instead of direct type references.
Type Data Structure
Types are stored in `TypeData` enum with many variants:
// Simplified — see crates/biome_js_type_info/src/type_data.rs for the full enum
enum TypeData {
Unknown, // Inference not implemented
Global, // Global type reference
BigInt, Boolean, Null, Number, // Primitive types
String, Symbol, Undefined,
Function(Box<Function>), // Function with parameters
Object(Box<Object>), // Object with properties
Class(Box<Class>), // Class definition
Interface(Box<Interface>), // Interface definition
Union(Box<Union>), // Union type (A | B)
Intersection(Box<Intersection>), // Intersection type (A & B)
Tuple(Box<Tuple>), // Tuple type
Literal(Box<Literal>), // Literal type ("foo", 42)
Reference(TypeReference), // Reference to another type
TypeofExpression(Box<TypeofExpression>), // typeof an expression
// ... plus Conditional, Generic, TypeOperator, InstanceOf,
// keyword variants (AnyKeyword, NeverKeyword, VoidKeyword, etc.)
}Type References
Instead of direct type references, use `TypeReference`:
enum TypeReference {
Qualifier(Box<TypeReferenceQualifier>), // Name-based reference
Resolved(ResolvedTypeId), // Resolved to type ID
Import(Box<TypeImportQualifier>), // Import reference
}**Note:** There is no `Unknown` variant. Unknown types are represented as `TypeReference::Resolved(GLOBAL_UNKNOWN_ID)`. Use `TypeReference::unknown()` to create one.
Type Resolution Phases
1. Local Inference
**What**: Derives types from expressions without surrounding context.
**Example**: For `a + b`, creates:
TypeData::TypeofExpression(TypeofExpression::Addition {
left: TypeReference::from(TypeReferenceQualifier::from_name("a")),
right: TypeReference::from(TypeReferenceQualifier::from_name("b"))
})**Where**: Implemented in `local_inference.rs`
**Output**: Types with unresolved `TypeReference::Qualifier` references
2. Module-Level ("Thin") Inference
**What**: Resolves references within a single module's scope.
**Process**: 1. Takes results from local inference 2. Looks up qualifiers in local scopes 3. Converts to `TypeReference::Resolved` if found locally 4. Converts to `TypeReference::Import` if from import statement 5. Falls back to globals (like `Array`, `Promise`) 6. Uses `TypeReference::unknown()` if nothing is found
**Where**: Implemented in `js_module_info/collector.rs`
**Output**: Types with resolved local references, import markers, or unknown
3. Full Inference
**What**: Resolves import references across module boundaries.
**Process**: 1. Has access to entire module graph 2. Resolves `TypeReference::Import` by following imports 3. Converts to `TypeReference::Resolved` after following imports
**Where**: The Salsa-backed implementation starts at `db/queries/type_inference.rs::infer_module_types` and uses helpers under `db/type_inference/`. `js_module_info/module_resolver.rs` contains the legacy `TypeResolver`-based path.
**Caching**: `infer_module_types` is tracked by Salsa. Imported module results are dependencies, so Salsa invalidates affected importers after a change.
Working with Type Resolvers
Available Resolvers
// 1. For tests
HardcodedSymbolResolver
// 2. For globals (Array, Promise, etc.)
GlobalsResolver
// 3. For thin inference (single module)
JsModuleInfoCollector
// 4. For full inference (across modules)
ModuleResolver
Using a Resolver
use biome_js_type_info::{TypeResolver, ResolvedTypeData};
fn analyze_type(resolver: &impl TypeResolver, type_ref: TypeReference) {
// Resolve the reference
let resolved_data: ResolvedTypeData = resolver.resolve_type(type_ref);
// Get raw data for pattern matching
match resolved_data.as_raw_data() {
TypeData::String => { /* handle string */ },
TypeData::Number => { /* handle number */ },
TypeData::Function(func) => { /* handle function */ },
_ => { /* handle others */ }
}
// Resolve nested references
if let TypeData::Reference(inner_ref) = resolved_data.as_raw_data() {
let inner_data = resolver.resolve_type(*inner_ref);
// Process inner type
}
}Type Flattening
**What**: Converts complex type expressions to concrete types.
**Example**: After resolving `a + b`:
- If both are `TypeData::Number` → Flatten to `TypeData::Number`
- Otherwise → Usually flatten to `TypeData::String`
**Where**: Implemented in `flattening.rs`
Common Workflows
Implement Type-Aware Lint Rule
use biome_analyze::Semantic;
use biome_js_type_info::{TypeResolver, TRead more
name: type-inference description: Guide for working with Biome's module graph and type inference system. Use when implementing type-aware lint rules, understanding type resolution, working on the module graph infrastructure, or implementing type inference for new features. compatibility: Designed for coding agents working on the Biome codebase (github.com/biomejs/biome).
Purpose
Use this skill when working with Biome's type inference system and module graph. Covers type references, resolution phases, and the architecture designed for IDE performance.
Prerequisites
1. Read `crates/biome_js_type_info/CONTRIBUTING.md` for architecture details 2. Understand Biome's focus on IDE support and instant updates 3. Familiarity with TypeScript type system concepts
Key Concepts
Module Graph Constraint
**Critical rule**: No module may copy or clone data from another module, not even behind `Arc`.
**Why**: Any module can be updated at any time (IDE file changes). Copying data would create stale references that are hard to invalidate.
**Solution**: Use `TypeReference` instead of direct type references.
Type Data Structure
Types are stored in `TypeData` enum with many variants:
// Simplified — see crates/biome_js_type_info/src/type_data.rs for the full enum
enum TypeData {
Unknown, // Inference not implemented
Global, // Global type reference
BigInt, Boolean, Null, Number, // Primitive types
String, Symbol, Undefined,
Function(Box<Function>), // Function with parameters
Object(Box<Object>), // Object with properties
Class(Box<Class>), // Class definition
Interface(Box<Interface>), // Interface definition
Union(Box<Union>), // Union type (A | B)
Intersection(Box<Intersection>), // Intersection type (A & B)
Tuple(Box<Tuple>), // Tuple type
Literal(Box<Literal>), // Literal type ("foo", 42)
Reference(TypeReference), // Reference to another type
TypeofExpression(Box<TypeofExpression>), // typeof an expression
// ... plus Conditional, Generic, TypeOperator, InstanceOf,
// keyword variants (AnyKeyword, NeverKeyword, VoidKeyword, etc.)
}Type References
Instead of direct type references, use `TypeReference`:
enum TypeReference {
Qualifier(Box<TypeReferenceQualifier>), // Name-based reference
Resolved(ResolvedTypeId), // Resolved to type ID
Import(Box<TypeImportQualifier>), // Import reference
}**Note:** There is no `Unknown` variant. Unknown types are represented as `TypeReference::Resolved(GLOBAL_UNKNOWN_ID)`. Use `TypeReference::unknown()` to create one.
Type Resolution Phases
1. Local Inference
**What**: Derives types from expressions without surrounding context.
**Example**: For `a + b`, creates:
TypeData::TypeofExpression(TypeofExpression::Addition {
left: TypeReference::from(TypeReferenceQualifier::from_name("a")),
right: TypeReference::from(TypeReferenceQualifier::from_name("b"))
})**Where**: Implemented in `local_inference.rs`
**Output**: Types with unresolved `TypeReference::Qualifier` references
2. Module-Level ("Thin") Inference
**What**: Resolves references within a single module's scope.
**Process**: 1. Takes results from local inference 2. Looks up qualifiers in local scopes 3. Converts to `TypeReference::Resolved` if found locally 4. Converts to `TypeReference::Import` if from import statement 5. Falls back to globals (like `Array`, `Promise`) 6. Uses `TypeReference::unknown()` if nothing is found
**Where**: Implemented in `js_module_info/collector.rs`
**Output**: Types with resolved local references, import markers, or unknown
3. Full Inference
**What**: Resolves import references across module boundaries.
**Process**: 1. Has access to entire module graph 2. Resolves `TypeReference::Import` by following imports 3. Converts to `TypeReference::Resolved` after following imports
**Where**: The Salsa-backed implementation starts at `db/queries/type_inference.rs::infer_module_types` and uses helpers under `db/type_inference/`. `js_module_info/module_resolver.rs` contains the legacy `TypeResolver`-based path.
**Caching**: `infer_module_types` is tracked by Salsa. Imported module results are dependencies, so Salsa invalidates affected importers after a change.
Working with Type Resolvers
Available Resolvers
// 1. For tests HardcodedSymbolResolver // 2. For globals (Array, Promise, etc.) GlobalsResolver // 3. For thin inference (single module) JsModuleInfoCollector // 4. For full inference (across modules) ModuleResolver
Using a Resolver
use biome_js_type_info::{TypeResolver, ResolvedTypeData};
fn analyze_type(resolver: &impl TypeResolver, type_ref: TypeReference) {
// Resolve the reference
let resolved_data: ResolvedTypeData = resolver.resolve_type(type_ref);
// Get raw data for pattern matching
match resolved_data.as_raw_data() {
TypeData::String => { /* handle string */ },
TypeData::Number => { /* handle number */ },
TypeData::Function(func) => { /* handle function */ },
_ => { /* handle others */ }
}
// Resolve nested references
if let TypeData::Reference(inner_ref) = resolved_data.as_raw_data() {
let inner_data = resolver.resolve_type(*inner_ref);
// Process inner type
}
}Type Flattening
**What**: Converts complex type expressions to concrete types.
**Example**: After resolving `a + b`:
- If both are `TypeData::Number` → Flatten to `TypeData::Number`
- Otherwise → Usually flatten to `TypeData::String`
**Where**: Implemented in `flattening.rs`
Common Workflows
Implement Type-Aware Lint Rule
use biome_analyze::Semantic;
use biome_js_type_info::{TypeResolver, TA 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 - /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
Open skill

