Type utilities for working with TypeScript + ESLint together
—
Tools for working with TypeScript's type constraint system and contextual typing. These utilities are essential for understanding generic types, type inference, and the relationships between types in different contexts.
Functions for resolving generic type constraints and understanding type parameter relationships.
/**
* Resolves a node's type, returning the type's generic constraint if it has one.
* Warning: If the type is generic without a constraint, returns the type as-is
* rather than `unknown`. Check for ts.TypeFlags.TypeParameter to detect this case.
*/
function getConstrainedTypeAtLocation(
services: ParserServicesWithTypeInformation,
node: TSESTree.Node
): ts.Type;Usage Examples:
import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
import * as ts from "typescript";
// In an ESLint rule
export default {
create(context) {
const services = context.parserServices;
return {
FunctionDeclaration(node) {
// Get the constrained type for a function parameter
if (node.params.length > 0) {
const param = node.params[0];
const constrainedType = getConstrainedTypeAtLocation(services, param);
// Check if it's a type parameter without constraint
if (constrainedType.flags & ts.TypeFlags.TypeParameter) {
// Handle unconstrained type parameter
context.report({
node: param,
messageId: "unconstrainedGeneric"
});
}
}
}
};
}
};Functions for understanding the contextual type expectations in TypeScript expressions.
/**
* Returns the contextual type of a given node - the type of the target
* the node is going into (e.g., function parameter type, variable declaration type)
*/
function getContextualType(checker: ts.TypeChecker, node: ts.Expression): ts.Type | undefined;Usage Examples:
import { getContextualType } from "@typescript-eslint/type-utils";
// In an ESLint rule checking function arguments
export default {
create(context) {
const services = context.parserServices;
const checker = services.program.getTypeChecker();
return {
CallExpression(node) {
// Check each argument against its expected contextual type
node.arguments.forEach((arg, index) => {
const tsArg = services.esTreeNodeToTSNodeMap.get(arg);
const contextualType = getContextualType(checker, tsArg);
if (contextualType) {
const actualType = checker.getTypeAtLocation(tsArg);
// Compare actual vs expected type
if (!checker.isTypeAssignableTo(actualType, contextualType)) {
context.report({
node: arg,
messageId: "typeArgumentMismatch",
data: {
expected: checker.typeToString(contextualType),
actual: checker.typeToString(actualType)
}
});
}
}
});
}
};
}
};// Example: Analyzing generic constraints in function declarations
import { getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
import * as ts from "typescript";
function analyzeGenericConstraints(
services: ParserServicesWithTypeInformation,
functionNode: TSESTree.FunctionDeclaration
) {
if (functionNode.typeParameters) {
functionNode.typeParameters.params.forEach(typeParam => {
const constrainedType = getConstrainedTypeAtLocation(services, typeParam);
if (constrainedType.flags & ts.TypeFlags.TypeParameter) {
// Unconstrained type parameter
console.log(`Unconstrained type parameter: ${typeParam.name.name}`);
} else {
// Has constraint
const checker = services.program.getTypeChecker();
const constraintName = checker.typeToString(constrainedType);
console.log(`Type parameter ${typeParam.name.name} constrained to: ${constraintName}`);
}
});
}
}// Example: Validating array literal elements against contextual type
import { getContextualType } from "@typescript-eslint/type-utils";
function validateArrayLiteralTypes(
services: ParserServicesWithTypeInformation,
arrayNode: TSESTree.ArrayExpression
) {
const checker = services.program.getTypeChecker();
const tsArrayNode = services.esTreeNodeToTSNodeMap.get(arrayNode);
const contextualType = getContextualType(checker, tsArrayNode);
if (contextualType && checker.isArrayType(contextualType)) {
const elementType = checker.getTypeArguments(contextualType as ts.TypeReference)[0];
arrayNode.elements.forEach(element => {
if (element) {
const tsElement = services.esTreeNodeToTSNodeMap.get(element);
const elementActualType = checker.getTypeAtLocation(tsElement);
if (!checker.isTypeAssignableTo(elementActualType, elementType)) {
console.log("Array element type mismatch");
}
}
});
}
}// Example: Understanding type inference in variable declarations
import { getContextualType, getConstrainedTypeAtLocation } from "@typescript-eslint/type-utils";
function analyzeTypeInference(
services: ParserServicesWithTypeInformation,
variableDeclarator: TSESTree.VariableDeclarator
) {
const checker = services.program.getTypeChecker();
// Get the declared type constraint
if (variableDeclarator.id.typeAnnotation) {
const declaredType = getConstrainedTypeAtLocation(services, variableDeclarator.id);
console.log(`Declared type: ${checker.typeToString(declaredType)}`);
}
// Get the contextual type from initializer
if (variableDeclarator.init) {
const tsInit = services.esTreeNodeToTSNodeMap.get(variableDeclarator.init);
const contextualType = getContextualType(checker, tsInit);
if (contextualType) {
console.log(`Contextual type: ${checker.typeToString(contextualType)}`);
}
// Compare inferred vs contextual
const inferredType = checker.getTypeAtLocation(tsInit);
console.log(`Inferred type: ${checker.typeToString(inferredType)}`);
}
}// Analyze generic function constraints and usage
function analyzeGenericFunction(node: TSESTree.FunctionDeclaration, services: ParserServicesWithTypeInformation) {
const checker = services.program.getTypeChecker();
// Check type parameters
if (node.typeParameters) {
node.typeParameters.params.forEach(param => {
const constrainedType = getConstrainedTypeAtLocation(services, param);
// Analyze constraint
});
}
// Check parameter contextual types
node.params.forEach(param => {
if (param.type === "Identifier" && param.init) {
const tsInit = services.esTreeNodeToTSNodeMap.get(param.init);
const contextualType = getContextualType(checker, tsInit);
// Validate default parameter against contextual type
}
});
}Install with Tessl CLI
npx tessl i tessl/npm-typescript-eslint--type-utils