Loading...
Loading...
Guide for working with Biome's module graph and type inference system. Use when implementing type-aware lint rules or working on TypeScript support. Examples:<example>User needs to understand type resolution for a lint rule</example><example>User is working on the module graph infrastructure</example><example>User wants to implement type inference for a new feature</example>
npx skill4agent add biomejs/biome type-inferencecrates/biome_js_type_info/CONTRIBUTING.mdArcTypeReferenceTypeDataenum TypeData {
Unknown, // Inference not implemented
UnknownKeyword, // Explicit 'unknown' keyword
String, // String type
Number, // Number type
Function(FunctionType), // Function with parameters
Object(ObjectType), // Object with properties
Reference, // Reference to another type
// ... many more variants
}TypeReferenceenum TypeReference {
Qualifier(TypeReferenceQualifier), // Name-based reference
Resolved(ResolvedTypeId), // Resolved to type ID
Import(TypeImportQualifier), // Import reference
Unknown, // Could not resolve
}a + bTypeData::TypeofExpression(TypeofExpression::Addition {
left: TypeReference::from(TypeReferenceQualifier::from_name("a")),
right: TypeReference::from(TypeReferenceQualifier::from_name("b"))
})local_inference.rsTypeReference::QualifierTypeReference::ResolvedTypeReference::ImportArrayPromiseTypeReference::Unknownjs_module_info/collector.rsTypeReference::ImportTypeReference::Resolvedjs_module_info/scoped_resolver.rs// 1. For tests
HardcodedSymbolResolver
// 2. For globals (Array, Promise, etc.)
GlobalsResolver
// 3. For thin inference (single module)
JsModuleInfoCollector
// 4. For full inference (across modules)
ModuleResolveruse 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
}
}a + bTypeData::NumberTypeData::NumberTypeData::Stringflattening.rsuse biome_analyze::Semantic;
use biome_js_type_info::{TypeResolver, TypeData};
impl Rule for MyTypeRule {
type Query = Semantic<JsCallExpression>;
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();
let model = ctx.model();
// Get type resolver from model
let resolver = model.type_resolver();
// Get type of expression
let expr_type = node.callee().ok()?.infer_type(resolver);
// Check the type
match expr_type.as_raw_data() {
TypeData::Function(_) => { /* valid */ },
TypeData::Unknown => { /* might be valid, can't tell */ },
_ => { return Some(()); /* not callable */ }
}
None
}
}fn is_array_type(resolver: &impl TypeResolver, type_ref: TypeReference) -> bool {
let resolved = resolver.resolve_type(type_ref);
// Follow references
let data = match resolved.as_raw_data() {
TypeData::Reference(ref_to) => resolver.resolve_type(*ref_to),
other => resolved,
};
// Check if it's an Array
matches!(data.as_raw_data(), TypeData::Array(_))
}fn analyze_function(resolver: &impl TypeResolver, type_ref: TypeReference) {
let resolved = resolver.resolve_type(type_ref);
if let TypeData::Function(func_type) = resolved.as_raw_data() {
// Access parameters
for param in func_type.parameters() {
let param_type = resolver.resolve_type(param.type_ref());
// Analyze parameter type
}
// Access return type
let return_type = resolver.resolve_type(func_type.return_type());
}
}Arcstruct ResolvedTypeId(ResolverId, TypeId)TypeIdResolverIdResolvedTypeData&TypeData// Good - tracks resolver context
let resolved_data: ResolvedTypeData = resolver.resolve_type(type_ref);
// Be careful - loses resolver context
let raw_data: &TypeData = resolved_data.as_raw_data();
// Can't resolve nested TypeReferences without ResolverId!TypeData::UnknownTypeData::ReferenceResolvedTypeDataTypeData.d.ts// Pattern 1: Resolve and flatten
let type_ref = expr.infer_type(resolver);
let flattened = type_ref.flatten(resolver);
// Pattern 2: Check if type matches
fn is_string_type(resolver: &impl TypeResolver, type_ref: TypeReference) -> bool {
let resolved = resolver.resolve_type(type_ref);
matches!(resolved.as_raw_data(), TypeData::String)
}
// Pattern 3: Handle unknown gracefully
match resolved.as_raw_data() {
TypeData::Unknown | TypeData::UnknownKeyword => {
// Can't verify, assume valid
return None;
}
TypeData::String => { /* handle */ }
_ => { /* handle */ }
}crates/biome_js_type_info/CONTRIBUTING.mdcrates/biome_module_graph/crates/biome_js_type_info/src/resolver.rscrates/biome_js_type_info/src/flattening.rs