Type utilities for working with TypeScript + ESLint together
—
Tools for working with TypeScript symbols, declarations, and their relationships to source files and libraries. These utilities help understand the origin and context of types and values in TypeScript code.
Functions for finding and analyzing TypeScript declarations.
/**
* Gets the declaration for the given variable
*/
function getDeclaration(
services: ParserServicesWithTypeInformation,
node: TSESTree.Node
): ts.Declaration | null;Usage Examples:
import { getDeclaration } from "@typescript-eslint/type-utils";
// In an ESLint rule analyzing variable declarations
export default {
create(context) {
const services = context.parserServices;
return {
Identifier(node) {
const declaration = getDeclaration(services, node);
if (declaration) {
// Analyze the declaration
switch (declaration.kind) {
case ts.SyntaxKind.VariableDeclaration:
console.log("Variable declaration found");
break;
case ts.SyntaxKind.FunctionDeclaration:
console.log("Function declaration found");
break;
case ts.SyntaxKind.ClassDeclaration:
console.log("Class declaration found");
break;
case ts.SyntaxKind.InterfaceDeclaration:
console.log("Interface declaration found");
break;
case ts.SyntaxKind.TypeAliasDeclaration:
console.log("Type alias declaration found");
break;
}
// Check declaration location
const sourceFile = declaration.getSourceFile();
console.log(`Declared in: ${sourceFile.fileName}`);
}
}
};
}
};Functions for determining where symbols and types come from.
/**
* Checks if a symbol comes from TypeScript's default library
*/
function isSymbolFromDefaultLibrary(
program: ts.Program,
symbol: ts.Symbol | undefined
): boolean;Usage Examples:
import { isSymbolFromDefaultLibrary } from "@typescript-eslint/type-utils";
// In an ESLint rule checking for built-in vs user-defined types
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);
const symbol = type.symbol || type.aliasSymbol;
if (isSymbolFromDefaultLibrary(program, symbol)) {
// It's a built-in TypeScript type
context.report({
node,
messageId: "builtinTypeUsage",
data: { typeName: symbol?.name }
});
} else {
// It's a user-defined type
context.report({
node,
messageId: "customTypeUsage",
data: { typeName: symbol?.name }
});
}
}
};
}
};Functions for analyzing the source files where symbols are defined.
/**
* Gets the source file for a given node
* @deprecated Use node.getSourceFile() directly instead
*/
function getSourceFileOfNode(node: ts.Node): ts.SourceFile;Usage Examples:
import { getSourceFileOfNode } from "@typescript-eslint/type-utils";
// Note: This function is deprecated, prefer node.getSourceFile()
function analyzeNodeLocation(tsNode: ts.Node): {
fileName: string;
isDeclarationFile: boolean;
isExternal: boolean;
} {
// Preferred approach
const sourceFile = tsNode.getSourceFile();
// Deprecated approach (for reference)
// const sourceFile = getSourceFileOfNode(tsNode);
return {
fileName: sourceFile.fileName,
isDeclarationFile: sourceFile.isDeclarationFile,
isExternal: sourceFile.fileName.includes('node_modules')
};
}// Example: Comprehensive declaration analysis
import { getDeclaration, isSymbolFromDefaultLibrary } from "@typescript-eslint/type-utils";
function analyzeDeclarationContext(
services: ParserServicesWithTypeInformation,
node: TSESTree.Identifier
): {
hasDeclaration: boolean;
declarationKind: string | null;
isBuiltin: boolean;
sourceFile: string | null;
isExternal: boolean;
} {
const program = services.program;
const checker = program.getTypeChecker();
const declaration = getDeclaration(services, node);
if (!declaration) {
return {
hasDeclaration: false,
declarationKind: null,
isBuiltin: false,
sourceFile: null,
isExternal: false
};
}
// Get the symbol for builtin check
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const type = checker.getTypeAtLocation(tsNode);
const symbol = type.symbol || type.aliasSymbol;
const sourceFile = declaration.getSourceFile();
return {
hasDeclaration: true,
declarationKind: ts.SyntaxKind[declaration.kind],
isBuiltin: isSymbolFromDefaultLibrary(program, symbol),
sourceFile: sourceFile.fileName,
isExternal: sourceFile.fileName.includes('node_modules')
};
}// Example: Tracking symbol dependencies across files
import { getDeclaration, isSymbolFromDefaultLibrary } from "@typescript-eslint/type-utils";
interface SymbolDependency {
name: string;
sourceFile: string;
isBuiltin: boolean;
isExternal: boolean;
declarationKind: string;
}
function trackSymbolDependencies(
services: ParserServicesWithTypeInformation,
rootNode: TSESTree.Node
): SymbolDependency[] {
const dependencies: SymbolDependency[] = [];
const visited = new Set<string>();
const program = services.program;
const checker = program.getTypeChecker();
function visitNode(node: TSESTree.Node) {
if (node.type === "Identifier") {
const declaration = getDeclaration(services, node);
if (declaration) {
const sourceFile = declaration.getSourceFile();
const key = `${node.name}:${sourceFile.fileName}`;
if (!visited.has(key)) {
visited.add(key);
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const type = checker.getTypeAtLocation(tsNode);
const symbol = type.symbol || type.aliasSymbol;
dependencies.push({
name: node.name,
sourceFile: sourceFile.fileName,
isBuiltin: isSymbolFromDefaultLibrary(program, symbol),
isExternal: sourceFile.fileName.includes('node_modules'),
declarationKind: ts.SyntaxKind[declaration.kind]
});
}
}
}
// Recursively visit child nodes
// (Implementation would traverse AST)
}
visitNode(rootNode);
return dependencies;
}// Example: Classifying symbols by their origin
import { isSymbolFromDefaultLibrary, getDeclaration } from "@typescript-eslint/type-utils";
enum SymbolOrigin {
Builtin = "builtin",
External = "external",
UserCode = "userCode",
Unknown = "unknown"
}
function classifySymbolOrigin(
services: ParserServicesWithTypeInformation,
node: TSESTree.Identifier
): SymbolOrigin {
const program = services.program;
const checker = program.getTypeChecker();
// Check if it's a builtin TypeScript type
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
const type = checker.getTypeAtLocation(tsNode);
const symbol = type.symbol || type.aliasSymbol;
if (isSymbolFromDefaultLibrary(program, symbol)) {
return SymbolOrigin.Builtin;
}
// Check declaration location
const declaration = getDeclaration(services, node);
if (!declaration) {
return SymbolOrigin.Unknown;
}
const sourceFile = declaration.getSourceFile();
const fileName = sourceFile.fileName;
if (fileName.includes('node_modules')) {
return SymbolOrigin.External;
}
return SymbolOrigin.UserCode;
}
// Usage in ESLint rule
export default {
create(context) {
const services = context.parserServices;
return {
Identifier(node) {
const origin = classifySymbolOrigin(services, node);
switch (origin) {
case SymbolOrigin.Builtin:
// Handle builtin types
break;
case SymbolOrigin.External:
// Handle external library symbols
break;
case SymbolOrigin.UserCode:
// Handle user-defined symbols
break;
case SymbolOrigin.Unknown:
// Handle unknown symbols
break;
}
}
};
}
};// Example: Analyzing relationships between declarations
import { getDeclaration } from "@typescript-eslint/type-utils";
interface DeclarationRelationship {
node: TSESTree.Identifier;
declaration: ts.Declaration;
parent: ts.Declaration | null;
children: ts.Declaration[];
siblings: ts.Declaration[];
}
function analyzeDeclarationRelationships(
services: ParserServicesWithTypeInformation,
identifiers: TSESTree.Identifier[]
): DeclarationRelationship[] {
const relationships: DeclarationRelationship[] = [];
identifiers.forEach(node => {
const declaration = getDeclaration(services, node);
if (declaration) {
const parent = declaration.parent;
const children: ts.Declaration[] = [];
const siblings: ts.Declaration[] = [];
// Find children (for classes, interfaces, etc.)
if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) {
declaration.members?.forEach(member => {
if (ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member)) {
children.push(member);
}
});
}
// Find siblings (other declarations in same scope)
if (parent && 'statements' in parent) {
(parent as any).statements?.forEach((stmt: ts.Statement) => {
if (stmt !== declaration && ts.isDeclaration(stmt)) {
siblings.push(stmt);
}
});
}
relationships.push({
node,
declaration,
parent: parent as ts.Declaration || null,
children,
siblings
});
}
});
return relationships;
}Install with Tessl CLI
npx tessl i tessl/npm-typescript-eslint--type-utils