typescript-runtime-schemas
Version:
A TypeScript schema generation tool that extracts Zod schemas from TypeScript source files with runtime validation support. Generate validation schemas directly from your existing TypeScript types with support for computed types and constraint-based valid
838 lines • 34.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeResolver = void 0;
exports.resolveTypesFromSource = resolveTypesFromSource;
exports.getResolvedType = getResolvedType;
exports.resolveTypesFromInput = resolveTypesFromInput;
exports.getResolvedTypeFromInput = getResolvedTypeFromInput;
exports.resolveTypesFromDirectory = resolveTypesFromDirectory;
exports.resolveTypesFromGlob = resolveTypesFromGlob;
exports.resolveTypesFromFile = resolveTypesFromFile;
exports.getResolvedTypeFromFile = getResolvedTypeFromFile;
exports.getResolvedTypeFromDirectory = getResolvedTypeFromDirectory;
const ts_morph_1 = require("ts-morph");
/**
* TypeScript type resolver that can resolve computed types like Pick, Omit, etc. using ts-morph
*/
class TypeResolver {
constructor(sourceCode, compilerOptions) {
// Create a new ts-morph project
this.project = new ts_morph_1.Project({
compilerOptions: compilerOptions || {
target: ts_morph_1.ScriptTarget.ES2020,
module: ts_morph_1.ModuleKind.CommonJS,
strict: true,
},
useInMemoryFileSystem: true,
});
// Create a virtual source file
const sourceFile = this.project.createSourceFile("temp.ts", sourceCode);
this.sourceFiles = [sourceFile];
}
/**
* Create TypeResolver from an existing ts-morph project with multiple files
*/
static fromProject(project, sourceFiles) {
const resolver = Object.create(TypeResolver.prototype);
resolver.project = project;
resolver.sourceFiles = sourceFiles;
return resolver;
}
/**
* Get the primary source file (for backward compatibility)
*/
get sourceFile() {
return this.sourceFiles[0];
}
/**
* Resolve all type aliases in the source code to their computed forms
* @param predicate Optional function to filter which types should be resolved.
* Types for which the predicate returns false will be returned as-is.
*/
resolveTypes(predicate) {
const resolvedDeclarations = [];
// Process all source files
for (const sourceFile of this.sourceFiles) {
// Process interfaces first (they don't need resolution, just include them)
const interfaces = sourceFile.getInterfaces();
for (const interfaceDecl of interfaces) {
const interfaceName = interfaceDecl.getName();
resolvedDeclarations.push(`type ${interfaceName} = ${this.resolveInterfaceDeclaration(interfaceDecl)}`);
}
// Process type aliases
const typeAliases = sourceFile.getTypeAliases();
for (const typeAlias of typeAliases) {
const typeName = typeAlias.getName();
// If no predicate or predicate returns true, resolve the type
if (!predicate || predicate(typeName, typeAlias)) {
const resolvedType = this.resolveTypeAlias(typeAlias);
resolvedDeclarations.push(`type ${typeName} = ${resolvedType}`);
}
else {
// If predicate returns false, return original declaration
const originalDeclaration = typeAlias.getText();
resolvedDeclarations.push(originalDeclaration);
}
}
}
return resolvedDeclarations.join("\n");
}
/**
* Get a specific resolved type by name (searches all files)
*/
getResolvedType(typeName) {
// Search through all source files for type aliases
for (const sourceFile of this.sourceFiles) {
const typeAlias = sourceFile.getTypeAlias(typeName);
if (typeAlias) {
return this.resolveTypeAlias(typeAlias);
}
}
// Search through all source files for interfaces
for (const sourceFile of this.sourceFiles) {
const interfaceDecl = sourceFile.getInterface(typeName);
if (interfaceDecl) {
return this.resolveInterfaceDeclaration(interfaceDecl);
}
}
throw new Error(`Type ${typeName} not found in any source file`);
}
/**
* Resolve a specific type alias to its computed form
*/
resolveTypeAlias(typeAlias) {
const typeNode = typeAlias.getTypeNode();
if (!typeNode) {
throw new Error("Type alias has no type node");
}
return this.resolveTypeNode(typeNode);
}
/**
* Resolve an interface declaration to its type literal form
*/
resolveInterfaceDeclaration(interfaceDecl) {
const properties = [];
for (const member of interfaceDecl.getMembers()) {
// Use Node.isPropertySignature instead of magic number
if (member.getKind() === 167 || member.getName) {
// PropertySignature
const name = member.getName ? member.getName() : null;
const typeNode = member.getTypeNode ? member.getTypeNode() : null;
const isOptional = member.hasQuestionToken
? member.hasQuestionToken()
: false;
if (name && typeNode) {
const optionalMarker = isOptional ? "?" : "";
const typeText = this.resolveTypeNode(typeNode);
properties.push(`${name}${optionalMarker}: ${typeText}`);
}
}
}
return `{ ${properties.join("; ")} }`;
}
/**
* Resolve a type node to its string representation
*/
resolveTypeNode(typeNode) {
// Handle type references (like Pick<User, 'name'>)
if (ts_morph_1.Node.isTypeReference(typeNode)) {
return this.resolveTypeReference(typeNode);
}
// Handle type literals (like { id: number; name: string; })
if (ts_morph_1.Node.isTypeLiteral(typeNode)) {
return this.resolveTypeLiteral(typeNode);
}
// Handle intersection types (like User & { role: Role })
if (ts_morph_1.Node.isIntersectionTypeNode(typeNode)) {
return this.resolveIntersectionType(typeNode);
}
// Handle union types (like string | number)
if (ts_morph_1.Node.isUnionTypeNode(typeNode)) {
return this.resolveUnionType(typeNode);
}
// For other types, return the text representation
return typeNode.getText();
}
/**
* Resolve type references like Pick<User, 'name' | 'email'>
*/
resolveTypeReference(typeRef) {
if (!ts_morph_1.Node.isTypeReference(typeRef)) {
return typeRef.getText();
}
const typeName = typeRef.getTypeName().getText();
const typeArgs = typeRef.getTypeArguments();
// Handle utility types with key selection
if ((typeName === "Pick" || typeName === "Omit") && typeArgs.length >= 2) {
return this.resolveUtilityType(typeName, typeArgs);
}
// Handle single-argument utility types
if ((typeName === "Partial" ||
typeName === "Required" ||
typeName === "Readonly" ||
typeName === "NonNullable") &&
typeArgs.length >= 1) {
return this.resolveSingleArgUtilityType(typeName, typeArgs[0]);
}
// Handle Record<K, V>
if (typeName === "Record" && typeArgs.length >= 2) {
return this.resolveRecordType(typeArgs[0], typeArgs[1]);
}
// Handle Exclude<T, U> and Extract<T, U>
if ((typeName === "Exclude" || typeName === "Extract") &&
typeArgs.length >= 2) {
return this.resolveExcludeExtractType(typeName, typeArgs[0], typeArgs[1]);
}
// Handle ReturnType<T>
if (typeName === "ReturnType" && typeArgs.length >= 1) {
return this.resolveReturnType(typeArgs[0]);
}
// Handle Parameters<T>
if (typeName === "Parameters" && typeArgs.length >= 1) {
return this.resolveParametersType(typeArgs[0]);
}
// Handle ConstructorParameters<T>
if (typeName === "ConstructorParameters" && typeArgs.length >= 1) {
return this.resolveConstructorParametersType(typeArgs[0]);
}
// Handle InstanceType<T>
if (typeName === "InstanceType" && typeArgs.length >= 1) {
return this.resolveInstanceType(typeArgs[0]);
}
// Handle ThisParameterType<T> and OmitThisParameter<T>
if ((typeName === "ThisParameterType" || typeName === "OmitThisParameter") &&
typeArgs.length >= 1) {
return this.resolveThisParameterType(typeName, typeArgs[0]);
}
// Handle Array<T>
if (typeName === "Array" && typeArgs.length >= 1) {
const elementType = this.resolveTypeNode(typeArgs[0]);
return `${elementType}[]`;
}
// Handle Promise<T>
if (typeName === "Promise" && typeArgs.length >= 1) {
const valueType = this.resolveTypeNode(typeArgs[0]);
return `Promise<${valueType}>`;
}
// Handle generic types defined across all source files
for (const sourceFile of this.sourceFiles) {
const baseTypeAlias = sourceFile.getTypeAlias(typeName);
if (baseTypeAlias) {
return this.resolveGenericTypeAlias(baseTypeAlias, typeArgs);
}
}
// If we can't resolve it, return the original text
return typeRef.getText();
}
/**
* Resolve utility types like Pick and Omit
*/
resolveUtilityType(utilityName, typeArgs) {
const baseTypeArg = typeArgs[0];
const keysArg = typeArgs[1];
// Get the base type
const baseType = this.getBaseTypeProperties(baseTypeArg);
if (!baseType) {
return `{ /* Could not resolve ${utilityName} */ }`;
}
// Extract the keys
const selectedKeys = this.extractKeys(keysArg);
// Filter properties based on utility type
const resultProperties = [];
for (const [propName, propType, isOptional] of baseType) {
const shouldInclude = utilityName === "Pick"
? selectedKeys.includes(propName)
: !selectedKeys.includes(propName);
if (shouldInclude) {
const optionalMarker = isOptional ? "?" : "";
resultProperties.push(`${propName}${optionalMarker}: ${propType}`);
}
}
return `{ ${resultProperties.join("; ")} }`;
}
/**
* Resolve Partial or Required utility types
*/
resolvePartialOrRequired(utilityName, baseTypeArg) {
const baseType = this.getBaseTypeProperties(baseTypeArg);
if (!baseType) {
return `{ /* Could not resolve ${utilityName} */ }`;
}
const resultProperties = [];
for (const [propName, propType, isOptional] of baseType) {
let optionalMarker = "";
if (utilityName === "Partial") {
optionalMarker = "?"; // Make all properties optional
}
else if (utilityName === "Required") {
optionalMarker = ""; // Make all properties required (remove optional marker)
}
else {
optionalMarker = isOptional ? "?" : ""; // Preserve original optionality
}
resultProperties.push(`${propName}${optionalMarker}: ${propType}`);
}
return `{ ${resultProperties.join("; ")} }`;
}
/**
* Get properties from a base type (either a type literal or a type alias reference)
*/
getBaseTypeProperties(baseTypeArg) {
// If it's a type reference, resolve it first
if (ts_morph_1.Node.isTypeReference(baseTypeArg)) {
const typeName = baseTypeArg.getTypeName().getText();
// Search across all source files for the type alias
for (const sourceFile of this.sourceFiles) {
const typeAlias = sourceFile.getTypeAlias(typeName);
if (typeAlias) {
const typeNode = typeAlias.getTypeNode();
if (typeNode) {
if (ts_morph_1.Node.isTypeLiteral(typeNode)) {
return this.extractPropertiesFromTypeLiteral(typeNode);
}
// Handle intersection types in type aliases
if (ts_morph_1.Node.isIntersectionTypeNode(typeNode)) {
return this.getPropertiesFromIntersection(typeNode);
}
// Handle other type references (recursive resolution)
if (ts_morph_1.Node.isTypeReference(typeNode)) {
return this.getBaseTypeProperties(typeNode);
}
}
}
}
// Search across all source files for interfaces
for (const sourceFile of this.sourceFiles) {
const interfaceDecl = sourceFile.getInterface(typeName);
if (interfaceDecl) {
return this.extractPropertiesFromInterface(interfaceDecl);
}
}
// Try to resolve through the TypeScript type checker for imported types
const properties = this.resolveImportedTypeProperties(baseTypeArg);
if (properties) {
return properties;
}
}
// If it's a type literal, extract properties directly
if (ts_morph_1.Node.isTypeLiteral(baseTypeArg)) {
return this.extractPropertiesFromTypeLiteral(baseTypeArg);
}
// Handle intersection types
if (ts_morph_1.Node.isIntersectionTypeNode(baseTypeArg)) {
return this.getPropertiesFromIntersection(baseTypeArg);
}
return null;
}
/**
* Extract properties from an interface declaration
*/
extractPropertiesFromInterface(interfaceDecl) {
const properties = [];
for (const member of interfaceDecl.getMembers()) {
// Use the same logic as resolveInterfaceDeclaration
if (member.getKind() === 167 || member.getName) {
// PropertySignature
const name = member.getName ? member.getName() : null;
const typeNode = member.getTypeNode ? member.getTypeNode() : null;
const isOptional = member.hasQuestionToken
? member.hasQuestionToken()
: false;
if (name && typeNode) {
const typeText = this.resolveTypeNode(typeNode);
properties.push([name, typeText, isOptional]);
}
}
}
return properties;
}
/**
* Extract properties from a type literal
*/
extractPropertiesFromTypeLiteral(typeLiteral) {
if (!ts_morph_1.Node.isTypeLiteral(typeLiteral)) {
return [];
}
const properties = [];
for (const member of typeLiteral.getMembers()) {
if (ts_morph_1.Node.isPropertySignature(member)) {
const name = member.getName();
const typeNode = member.getTypeNode();
const isOptional = member.hasQuestionToken();
if (name && typeNode) {
const typeText = this.resolveTypeNode(typeNode);
properties.push([name, typeText, isOptional]);
}
}
}
return properties;
}
/**
* Extract keys from a union type like 'name' | 'email'
*/
extractKeys(keysArg) {
const keys = [];
// Handle union types
if (ts_morph_1.Node.isUnionTypeNode(keysArg)) {
for (const unionMember of keysArg.getTypeNodes()) {
if (ts_morph_1.Node.isLiteralTypeNode(unionMember)) {
const literal = unionMember.getLiteral();
if (ts_morph_1.Node.isStringLiteral(literal)) {
keys.push(literal.getLiteralValue());
}
}
}
}
// Handle single string literal
else if (ts_morph_1.Node.isLiteralTypeNode(keysArg)) {
const literal = keysArg.getLiteral();
if (ts_morph_1.Node.isStringLiteral(literal)) {
keys.push(literal.getLiteralValue());
}
}
return keys;
}
/**
* Resolve type literals to their string representation
*/
resolveTypeLiteral(typeLiteral) {
if (!ts_morph_1.Node.isTypeLiteral(typeLiteral)) {
return typeLiteral.getText();
}
const properties = [];
for (const member of typeLiteral.getMembers()) {
if (ts_morph_1.Node.isPropertySignature(member)) {
const name = member.getName();
const typeNode = member.getTypeNode();
const isOptional = member.hasQuestionToken();
if (name && typeNode) {
const optionalMarker = isOptional ? "?" : "";
const typeText = this.resolveTypeNode(typeNode);
properties.push(`${name}${optionalMarker}: ${typeText}`);
}
}
}
return `{ ${properties.join("; ")} }`;
}
/**
* Resolve single-argument utility types like Partial, Required, Readonly, NonNullable
*/
resolveSingleArgUtilityType(utilityName, baseTypeArg) {
if (utilityName === "Partial" || utilityName === "Required") {
return this.resolvePartialOrRequired(utilityName, baseTypeArg);
}
if (utilityName === "Readonly") {
return this.resolveReadonlyType(baseTypeArg);
}
if (utilityName === "NonNullable") {
return this.resolveNonNullableType(baseTypeArg);
}
return `${utilityName}<${this.resolveTypeNode(baseTypeArg)}>`;
}
/**
* Resolve Readonly<T> utility type
*/
resolveReadonlyType(baseTypeArg) {
const baseType = this.getBaseTypeProperties(baseTypeArg);
if (!baseType) {
return `Readonly<${this.resolveTypeNode(baseTypeArg)}>`;
}
const resultProperties = [];
for (const [propName, propType, isOptional] of baseType) {
const optionalMarker = isOptional ? "?" : "";
resultProperties.push(`readonly ${propName}${optionalMarker}: ${propType}`);
}
return `{ ${resultProperties.join("; ")} }`;
}
/**
* Resolve NonNullable<T> utility type
*/
resolveNonNullableType(baseTypeArg) {
const resolvedType = this.resolveTypeNode(baseTypeArg);
// Remove null and undefined from union types
if (resolvedType.includes(" | null") ||
resolvedType.includes(" | undefined")) {
return resolvedType
.replace(/ \| null/g, "")
.replace(/ \| undefined/g, "")
.replace(/null \| /g, "")
.replace(/undefined \| /g, "");
}
return resolvedType;
}
/**
* Resolve Record<K, V> utility type
*/
resolveRecordType(keysArg, valueArg) {
const keys = this.extractKeys(keysArg);
const valueType = this.resolveTypeNode(valueArg);
if (keys.length > 0) {
const properties = keys.map((key) => `${key}: ${valueType}`);
return `{ ${properties.join("; ")} }`;
}
// If we can't extract specific keys, return a generic record type
const keyType = this.resolveTypeNode(keysArg);
return `{ [key: ${keyType}]: ${valueType} }`;
}
/**
* Resolve Exclude<T, U> and Extract<T, U> utility types
*/
resolveExcludeExtractType(utilityName, typeArg, excludeArg) {
const baseType = this.resolveTypeNode(typeArg);
const excludeType = this.resolveTypeNode(excludeArg);
// For simple cases, we can try to resolve union types
if (baseType.includes(" | ")) {
const unionTypes = baseType.split(" | ").map((t) => t.trim());
const excludeTypes = excludeType.includes(" | ")
? excludeType.split(" | ").map((t) => t.trim())
: [excludeType];
let resultTypes;
if (utilityName === "Exclude") {
resultTypes = unionTypes.filter((t) => !excludeTypes.includes(t));
}
else {
// Extract
resultTypes = unionTypes.filter((t) => excludeTypes.includes(t));
}
return resultTypes.length > 1
? resultTypes.join(" | ")
: resultTypes[0] || "never";
}
// For complex cases, return the original utility type
return `${utilityName}<${baseType}, ${excludeType}>`;
}
/**
* Resolve ReturnType<T> utility type
*/
resolveReturnType(functionTypeArg) {
const functionType = this.resolveTypeNode(functionTypeArg);
// Try to extract return type from function signature
const arrowMatch = functionType.match(/=>\s*(.+)$/);
if (arrowMatch) {
return arrowMatch[1].trim();
}
// For complex cases, return the utility type
return `ReturnType<${functionType}>`;
}
/**
* Resolve Parameters<T> utility type
*/
resolveParametersType(functionTypeArg) {
const functionType = this.resolveTypeNode(functionTypeArg);
// Try to extract parameters from function signature
const paramsMatch = functionType.match(/\(([^)]*)\)/);
if (paramsMatch) {
const params = paramsMatch[1].trim();
if (!params) {
return "[]";
}
// Parse parameters and create tuple type
const paramTypes = params.split(",").map((p) => {
const colonIndex = p.lastIndexOf(":");
return colonIndex > -1 ? p.substring(colonIndex + 1).trim() : p.trim();
});
return `[${paramTypes.join(", ")}]`;
}
return `Parameters<${functionType}>`;
}
/**
* Resolve ConstructorParameters<T> utility type
*/
resolveConstructorParametersType(constructorTypeArg) {
const constructorType = this.resolveTypeNode(constructorTypeArg);
// Try to extract constructor parameters
const constructorMatch = constructorType.match(/new\s*\(([^)]*)\)/);
if (constructorMatch) {
const params = constructorMatch[1].trim();
if (!params) {
return "[]";
}
const paramTypes = params.split(",").map((p) => {
const colonIndex = p.lastIndexOf(":");
return colonIndex > -1 ? p.substring(colonIndex + 1).trim() : p.trim();
});
return `[${paramTypes.join(", ")}]`;
}
return `ConstructorParameters<${constructorType}>`;
}
/**
* Resolve InstanceType<T> utility type
*/
resolveInstanceType(constructorTypeArg) {
const constructorType = this.resolveTypeNode(constructorTypeArg);
// Try to extract instance type from constructor
const instanceMatch = constructorType.match(/new\s*\([^)]*\)\s*:\s*(.+)$/);
if (instanceMatch) {
return instanceMatch[1].trim();
}
return `InstanceType<${constructorType}>`;
}
/**
* Resolve ThisParameterType<T> and OmitThisParameter<T> utility types
*/
resolveThisParameterType(utilityName, functionTypeArg) {
const functionType = this.resolveTypeNode(functionTypeArg);
// Try to extract this parameter
const thisMatch = functionType.match(/\(this:\s*([^,)]+)/);
if (thisMatch) {
if (utilityName === "ThisParameterType") {
return thisMatch[1].trim();
}
else {
// OmitThisParameter
// Remove this parameter from function signature
return functionType.replace(/\(this:\s*[^,)]+,?\s*/, "(");
}
}
if (utilityName === "ThisParameterType") {
return "unknown";
}
return `${utilityName}<${functionType}>`;
}
/**
* Resolve generic type aliases with type arguments
*/
resolveGenericTypeAlias(typeAlias, typeArgs) {
const typeParams = typeAlias.getTypeParameters();
if (typeParams.length === 0 || typeArgs.length === 0) {
return this.resolveTypeAlias(typeAlias);
}
// Create a mapping of type parameters to type arguments
const typeParamMap = new Map();
for (let i = 0; i < Math.min(typeParams.length, typeArgs.length); i++) {
const paramName = typeParams[i].getName();
const argType = this.resolveTypeNode(typeArgs[i]);
typeParamMap.set(paramName, argType);
}
// Get the type node and substitute type parameters
const typeNode = typeAlias.getTypeNode();
if (!typeNode) {
return typeAlias.getName();
}
return this.substituteTypeParameters(typeNode, typeParamMap);
}
/**
* Resolve intersection types (A & B)
*/
resolveIntersectionType(intersectionNode) {
if (!ts_morph_1.Node.isIntersectionTypeNode(intersectionNode)) {
return intersectionNode.getText();
}
const typeNodes = intersectionNode.getTypeNodes();
const resolvedTypes = typeNodes.map((typeNode) => this.resolveTypeNode(typeNode));
// Try to merge object types if possible
const objectTypes = [];
const nonObjectTypes = [];
for (let i = 0; i < typeNodes.length; i++) {
const typeNode = typeNodes[i];
const resolvedType = resolvedTypes[i];
// Check if this is an object type we can merge
const properties = this.getBaseTypeProperties(typeNode);
if (properties) {
objectTypes.push(properties);
}
else {
nonObjectTypes.push(resolvedType);
}
}
// If we have object types to merge, merge them
if (objectTypes.length > 0) {
const mergedProperties = new Map();
// Merge all object properties
for (const properties of objectTypes) {
for (const [propName, propType, isOptional] of properties) {
mergedProperties.set(propName, [propType, isOptional]);
}
}
// Create merged object type
const mergedProps = Array.from(mergedProperties.entries()).map(([name, [type, optional]]) => {
const optionalMarker = optional ? "?" : "";
return `${name}${optionalMarker}: ${type}`;
});
const mergedObject = `{ ${mergedProps.join("; ")} }`;
// If there are non-object types, combine them
if (nonObjectTypes.length > 0) {
return [mergedObject, ...nonObjectTypes].join(" & ");
}
return mergedObject;
}
// If no object types to merge, just join with &
return resolvedTypes.join(" & ");
}
/**
* Resolve union types (A | B)
*/
resolveUnionType(unionNode) {
if (!ts_morph_1.Node.isUnionTypeNode(unionNode)) {
return unionNode.getText();
}
const typeNodes = unionNode.getTypeNodes();
const resolvedTypes = typeNodes.map((typeNode) => this.resolveTypeNode(typeNode));
return resolvedTypes.join(" | ");
}
/**
* Get properties from intersection types by merging all constituent types
*/
getPropertiesFromIntersection(intersectionNode) {
if (!ts_morph_1.Node.isIntersectionTypeNode(intersectionNode)) {
return null;
}
const allProperties = new Map();
for (const typeNode of intersectionNode.getTypeNodes()) {
const properties = this.getBaseTypeProperties(typeNode);
if (properties) {
for (const [propName, propType, isOptional] of properties) {
allProperties.set(propName, [propType, isOptional]);
}
}
}
return Array.from(allProperties.entries()).map(([name, [type, optional]]) => [name, type, optional]);
}
/**
* Try to resolve imported type properties using TypeScript's type checker
*/
resolveImportedTypeProperties(typeRef) {
if (!ts_morph_1.Node.isTypeReference(typeRef)) {
return null;
}
try {
// Get the type from the TypeScript type checker
const type = typeRef.getType();
const symbol = type.getSymbol();
if (!symbol) {
return null;
}
// Try to get the source declarations
const declarations = symbol.getDeclarations();
if (!declarations || declarations.length === 0) {
return null;
}
// Look for interface or type alias declarations
for (const declaration of declarations) {
if (ts_morph_1.Node.isInterfaceDeclaration(declaration)) {
return this.extractPropertiesFromInterface(declaration);
}
if (ts_morph_1.Node.isTypeAliasDeclaration(declaration)) {
const typeNode = declaration.getTypeNode();
if (typeNode && ts_morph_1.Node.isTypeLiteral(typeNode)) {
return this.extractPropertiesFromTypeLiteral(typeNode);
}
}
}
}
catch (error) {
// If type checking fails, return null to fall back to other methods
return null;
}
return null;
}
/**
* Substitute type parameters in a type node
*/
substituteTypeParameters(typeNode, typeParamMap) {
// Handle type references
if (ts_morph_1.Node.isTypeReference(typeNode)) {
const typeName = typeNode.getTypeName().getText();
// If this is a type parameter, substitute it
if (typeParamMap.has(typeName)) {
return typeParamMap.get(typeName);
}
// Otherwise, recursively resolve with substitution
const typeArgs = typeNode.getTypeArguments();
if (typeArgs.length > 0) {
const resolvedArgs = typeArgs.map((arg) => this.substituteTypeParameters(arg, typeParamMap));
return `${typeName}<${resolvedArgs.join(", ")}>`;
}
return typeName;
}
// Handle type literals
if (ts_morph_1.Node.isTypeLiteral(typeNode)) {
const properties = [];
for (const member of typeNode.getMembers()) {
if (ts_morph_1.Node.isPropertySignature(member)) {
const name = member.getName();
const typeNodeMember = member.getTypeNode();
const isOptional = member.hasQuestionToken();
if (name && typeNodeMember) {
const optionalMarker = isOptional ? "?" : "";
const typeText = this.substituteTypeParameters(typeNodeMember, typeParamMap);
properties.push(`${name}${optionalMarker}: ${typeText}`);
}
}
}
return `{ ${properties.join("; ")} }`;
}
// For other node types, check if it's a simple identifier that needs substitution
const text = typeNode.getText();
if (typeParamMap.has(text)) {
return typeParamMap.get(text);
}
return text;
}
}
exports.TypeResolver = TypeResolver;
/**
* Convenience function to resolve types from source code
*/
function resolveTypesFromSource(sourceCode, predicate) {
const resolver = new TypeResolver(sourceCode);
return resolver.resolveTypes(predicate);
}
/**
* Convenience function to get a specific resolved type
*/
function getResolvedType(sourceCode, typeName) {
const resolver = new TypeResolver(sourceCode);
return resolver.getResolvedType(typeName);
}
// Import the factory and related types for enhanced convenience functions
const type_resolver_factory_1 = require("./type-resolver-factory");
/**
* Enhanced convenience function to resolve types from various input types
*/
async function resolveTypesFromInput(input, predicate, options) {
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(input, options);
return resolver.resolveTypes(predicate);
}
/**
* Enhanced convenience function to get a specific resolved type from various input types
*/
async function getResolvedTypeFromInput(input, typeName, options) {
const resolver = await type_resolver_factory_1.TypeResolverFactory.create(input, options);
return resolver.getResolvedType(typeName);
}
/**
* Convenience function to resolve types from a directory
*/
async function resolveTypesFromDirectory(dirPath, options, predicate) {
return resolveTypesFromInput(dirPath, predicate, options);
}
/**
* Convenience function to resolve types using glob patterns
*/
async function resolveTypesFromGlob(dirPath, glob, exclude, predicate, compilerOptions) {
return resolveTypesFromInput(dirPath, predicate, {
glob,
exclude,
compilerOptions,
});
}
/**
* Convenience function to resolve types from a single file
*/
async function resolveTypesFromFile(filePath, predicate, compilerOptions) {
return resolveTypesFromInput(filePath, predicate, { compilerOptions });
}
/**
* Convenience function to get a specific type from a file
*/
async function getResolvedTypeFromFile(filePath, typeName, compilerOptions) {
return getResolvedTypeFromInput(filePath, typeName, { compilerOptions });
}
/**
* Convenience function to get a specific type from a directory
*/
async function getResolvedTypeFromDirectory(dirPath, typeName, options) {
return getResolvedTypeFromInput(dirPath, typeName, options);
}
//# sourceMappingURL=type-resolver.js.map