Type utilities for working with TypeScript + ESLint together
—
Specialized predicates for identifying built-in TypeScript types like Promise, Error, and readonly utility types. These functions help recognize common patterns and built-in type constructs in TypeScript code.
Functions for identifying Promise-related types and patterns.
/**
* Checks if a type is Promise-like (extends Promise)
* Example: class DerivedClass extends Promise<number> {} - DerivedClass is Promise-like
*/
function isPromiseLike(program: ts.Program, type: ts.Type): boolean;
/**
* Checks if a type is PromiseConstructor-like
* Example: const value = Promise; value.reject - value is PromiseConstructor-like
*/
function isPromiseConstructorLike(program: ts.Program, type: ts.Type): boolean;Usage Examples:
import { isPromiseLike, isPromiseConstructorLike } from "@typescript-eslint/type-utils";
// In an ESLint rule checking async patterns
export default {
create(context) {
const services = context.parserServices;
const program = services.program;
const checker = program.getTypeChecker();
return {
VariableDeclarator(node) {
if (node.init) {
const tsNode = services.esTreeNodeToTSNodeMap.get(node.init);
const type = checker.getTypeAtLocation(tsNode);
if (isPromiseLike(program, type)) {
// Handle Promise-like types
context.report({
node,
messageId: "promiseDetected",
data: { typeName: checker.typeToString(type) }
});
}
if (isPromiseConstructorLike(program, type)) {
// Handle Promise constructor references
context.report({
node,
messageId: "promiseConstructorUsage"
});
}
}
}
};
}
};Functions for identifying Error-related types and inheritance patterns.
/**
* Checks if a type extends the Error class
* Example: class Foo extends Error {} - new Foo() is Error-like
*/
function isErrorLike(program: ts.Program, type: ts.Type): boolean;
/**
* Checks if a type is a readonly Error type (like Readonly<Error>)
*/
function isReadonlyErrorLike(program: ts.Program, type: ts.Type): boolean;Usage Examples:
import { isErrorLike, isReadonlyErrorLike } from "@typescript-eslint/type-utils";
// In an ESLint rule for error handling
export default {
create(context) {
const services = context.parserServices;
const program = services.program;
const checker = program.getTypeChecker();
return {
ThrowStatement(node) {
const tsNode = services.esTreeNodeToTSNodeMap.get(node.argument);
const type = checker.getTypeAtLocation(tsNode);
if (!isErrorLike(program, type)) {
context.report({
node: node.argument,
messageId: "throwNonError"
});
}
},
CatchClause(node) {
if (node.param) {
const tsNode = services.esTreeNodeToTSNodeMap.get(node.param);
const type = checker.getTypeAtLocation(tsNode);
if (isReadonlyErrorLike(program, type)) {
context.report({
node: node.param,
messageId: "readonlyErrorInCatch"
});
}
}
}
};
}
};Functions for identifying TypeScript's built-in readonly utility types.
/**
* Checks if a type is a Readonly type alias
* Example: type T = Readonly<{ foo: 'bar' }> - T is ReadonlyTypeLike
*/
function isReadonlyTypeLike(
program: ts.Program,
type: ts.Type,
predicate?: (subType: { aliasSymbol: ts.Symbol; aliasTypeArguments: readonly ts.Type[] } & ts.Type) => boolean
): boolean;Usage Examples:
import { isReadonlyTypeLike } from "@typescript-eslint/type-utils";
// Check for Readonly utility type usage
export default {
create(context) {
const services = context.parserServices;
const program = services.program;
const checker = program.getTypeChecker();
return {
TSTypeReference(node) {
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const type = checker.getTypeAtLocation(tsNode);
if (isReadonlyTypeLike(program, type)) {
// Found Readonly<T> usage
context.report({
node,
messageId: "readonlyUtilityType"
});
}
}
};
}
};Generic functions for building custom builtin type checkers.
/**
* Generic function to check if a type matches a built-in type alias with a predicate
*/
function isBuiltinTypeAliasLike(
program: ts.Program,
type: ts.Type,
predicate: (subType: {aliasSymbol: ts.Symbol; aliasTypeArguments: readonly ts.Type[]} & ts.Type) => boolean
): boolean;
/**
* Checks if a type is like a built-in symbol with the given name(s)
*/
function isBuiltinSymbolLike(
program: ts.Program,
type: ts.Type,
symbolName: string | string[]
): boolean;
/**
* Recursive helper function for built-in symbol-like checks.
* Handles inheritance, unions, intersections, and type parameters.
*/
function isBuiltinSymbolLikeRecurser(
program: ts.Program,
type: ts.Type,
predicate: (subType: ts.Type) => boolean | null
): boolean;Usage Examples:
import {
isBuiltinTypeAliasLike,
isBuiltinSymbolLike,
isBuiltinSymbolLikeRecurser
} from "@typescript-eslint/type-utils";
// Custom builtin type checker for Record<K, V>
function isRecordLike(program: ts.Program, type: ts.Type): boolean {
return isBuiltinTypeAliasLike(program, type, (subType) => {
return subType.aliasSymbol?.name === 'Record' &&
subType.aliasTypeArguments?.length === 2;
});
}
// Check for Map or Set types
function isMapOrSetLike(program: ts.Program, type: ts.Type): boolean {
return isBuiltinSymbolLike(program, type, ['Map', 'Set']);
}
// Custom predicate-based checker
function isIterableLike(program: ts.Program, type: ts.Type): boolean {
return isBuiltinSymbolLikeRecurser(program, type, (subType) => {
// Check if type has Symbol.iterator method
const checker = program.getTypeChecker();
const iteratorSymbol = checker.getPropertyOfType(subType, '__@iterator');
return iteratorSymbol ? true : null;
});
}
// Usage in ESLint rule
export default {
create(context) {
const services = context.parserServices;
const program = services.program;
const checker = program.getTypeChecker();
return {
VariableDeclarator(node) {
if (node.init) {
const tsNode = services.esTreeNodeToTSNodeMap.get(node.init);
const type = checker.getTypeAtLocation(tsNode);
if (isRecordLike(program, type)) {
// Handle Record types
}
if (isMapOrSetLike(program, type)) {
// Handle Map/Set types
}
if (isIterableLike(program, type)) {
// Handle iterable types
}
}
}
};
}
};// Example: Creating a checker for all array-like builtin types
import { isBuiltinSymbolLikeRecurser } from "@typescript-eslint/type-utils";
function isArrayLikeBuiltin(program: ts.Program, type: ts.Type): boolean {
return isBuiltinSymbolLikeRecurser(program, type, (subType) => {
const checker = program.getTypeChecker();
const symbol = subType.symbol || subType.aliasSymbol;
if (!symbol) return null;
const arrayLikeNames = [
'Array', 'ReadonlyArray', 'Int8Array', 'Uint8Array',
'Int16Array', 'Uint16Array', 'Int32Array', 'Uint32Array',
'Float32Array', 'Float64Array', 'ArrayBuffer'
];
return arrayLikeNames.includes(symbol.name) ? true : null;
});
}// Example: Comprehensive Promise pattern analysis
import { isPromiseLike, isPromiseConstructorLike } from "@typescript-eslint/type-utils";
function analyzePromisePatterns(
program: ts.Program,
type: ts.Type,
checker: ts.TypeChecker
): {
isPromise: boolean;
isConstructor: boolean;
isThenable: boolean;
} {
const isPromise = isPromiseLike(program, type);
const isConstructor = isPromiseConstructorLike(program, type);
// Check for thenable (has .then method)
const thenProperty = checker.getPropertyOfType(type, 'then');
const isThenable = !!thenProperty;
return { isPromise, isConstructor, isThenable };
}// Example: Analyzing error type hierarchies
import { isErrorLike, isReadonlyErrorLike } from "@typescript-eslint/type-utils";
function analyzeErrorHierarchy(
program: ts.Program,
type: ts.Type,
checker: ts.TypeChecker
): {
isError: boolean;
isReadonlyError: boolean;
errorName: string | null;
customError: boolean;
} {
const isError = isErrorLike(program, type);
const isReadonlyError = isReadonlyErrorLike(program, type);
let errorName: string | null = null;
let customError = false;
if (isError) {
const symbol = type.symbol || type.aliasSymbol;
errorName = symbol?.name || null;
// Check if it's a custom error (not built-in Error)
customError = errorName !== 'Error' && errorName !== null;
}
return { isError, isReadonlyError, errorName, customError };
}Install with Tessl CLI
npx tessl i tessl/npm-typescript-eslint--type-utils