type-compiler
Version:
A TypeScript compiler plugin for enhanced runtime type checking and analysis with Zod validation
138 lines (137 loc) • 7.77 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createZodTransformer = createZodTransformer;
exports.createTypeCompilerPlugin = createTypeCompilerPlugin;
const typescript_1 = __importDefault(require("typescript"));
const type_processor_1 = require("./type-processor");
const utils_1 = require("./utils");
const parallel_1 = require("./parallel");
const logger_1 = require("./logger");
/**
* Create a transformer for generating Zod schemas
*/
function createZodTransformer(program, options) {
const typeChecker = program.getTypeChecker();
return (context) => {
return (sourceFile) => {
// Skip declaration files, node_modules, test files, etc.
if (!(0, utils_1.shouldProcessFile)(sourceFile.fileName, options.excludePatterns || [])) {
logger_1.logger.debug(`Skipping file: ${sourceFile.fileName}`);
return sourceFile;
}
logger_1.logger.info(`Processing file: ${sourceFile.fileName}`);
const zodDeclarations = [];
const workerPool = options.parallelProcessing ? (0, parallel_1.getWorkerPool)(options) : null;
if (options.parallelProcessing) {
logger_1.logger.info('Parallel processing is enabled, but operating synchronously for now.');
}
// First pass: collect all type nodes to process
const typeNodes = [];
const visitor = (node) => {
// Only process type declarations
if (typescript_1.default.isInterfaceDeclaration(node) ||
typescript_1.default.isTypeAliasDeclaration(node) ||
typescript_1.default.isEnumDeclaration(node) ||
typescript_1.default.isClassDeclaration(node)) {
// Check if we should process this type based on options
if (!node.name)
return node;
const typeName = node.name.text;
const shouldProcess = (0, utils_1.shouldProcessType)(node, options.excludedTypes || []);
// Skip types that shouldn't be processed
if (!shouldProcess) {
logger_1.logger.trace(`Skipping type: ${typeName}`);
return node;
}
// Only export types that were also exported in the source
const isTypeExported = (0, utils_1.isExported)(node);
const exportedName = isTypeExported ? typeName : `_${typeName}`;
logger_1.logger.debug(`Found type to process: ${typeName}${isTypeExported ? ' (exported)' : ''}`);
// Add to the list of types to process
typeNodes.push({ declaration: node, exportedName });
}
return typescript_1.default.visitEachChild(node, visitor, context);
};
// Run the visitor to collect type nodes
const visitedSourceFile = typescript_1.default.visitNode(sourceFile, visitor);
// Second pass: generate Zod schemas for each type
logger_1.logger.debug(`Processing ${typeNodes.length} types in ${sourceFile.fileName}`);
const startTime = Date.now();
for (const { declaration, exportedName } of typeNodes) {
try {
// Get the full type symbol and name
const type = typeChecker.getTypeAtLocation(declaration);
if (!type) {
logger_1.logger.warn(`Could not get type for ${exportedName}`);
continue;
}
// Check if we should use worker pool
const zodSchema = (0, type_processor_1.typeToZodSchema)(type, typeChecker, program, options);
if (!zodSchema) {
logger_1.logger.warn(`Could not generate schema for ${exportedName}`);
continue;
}
logger_1.logger.trace(`Generated schema for ${exportedName}`);
// Create the export statement for the Zod schema
const factory = context.factory;
const zodVarName = `${exportedName}Schema`;
// Handle export modifiers for different declaration types
let exportModifiers = undefined;
if (typescript_1.default.isInterfaceDeclaration(declaration) ||
typescript_1.default.isTypeAliasDeclaration(declaration) ||
typescript_1.default.isClassDeclaration(declaration) ||
typescript_1.default.isEnumDeclaration(declaration)) {
if (declaration.modifiers && declaration.modifiers.some(mod => mod.kind === typescript_1.default.SyntaxKind.ExportKeyword)) {
exportModifiers = [factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)];
}
}
const zodDeclaration = factory.createVariableStatement(exportModifiers, factory.createVariableDeclarationList([
factory.createVariableDeclaration(zodVarName, undefined, undefined, factory.createIdentifier(zodSchema)),
], typescript_1.default.NodeFlags.Const));
zodDeclarations.push(zodDeclaration);
// Update metrics
logger_1.logger.incrementMetric('typesProcessed');
}
catch (error) {
logger_1.logger.error(`Error generating schema for ${declaration.getText()}`, {
error: error instanceof Error ? error.message : String(error)
});
}
}
// Log processing time
const duration = Date.now() - startTime;
logger_1.logger.debug(`Generated ${zodDeclarations.length} schemas in ${duration}ms`);
// If no Zod declarations were generated, return the original source file
if (zodDeclarations.length === 0) {
logger_1.logger.debug(`No schemas generated for ${sourceFile.fileName}`);
return sourceFile;
}
// Add Zod import if needed
const skipZodImport = !!options.skipZodImport;
const updatedStatements = [
...(skipZodImport ? [] : [createZodImport(context)]),
...visitedSourceFile.statements,
...zodDeclarations,
];
logger_1.logger.incrementMetric('filesProcessed');
return context.factory.updateSourceFile(sourceFile, updatedStatements, visitedSourceFile.isDeclarationFile, visitedSourceFile.referencedFiles, visitedSourceFile.typeReferenceDirectives, visitedSourceFile.hasNoDefaultLib, visitedSourceFile.libReferenceDirectives);
};
};
}
function createZodImport(context) {
const factory = context.factory;
return factory.createImportDeclaration(undefined, factory.createImportClause(false, undefined, factory.createNamedImports([
factory.createImportSpecifier(false, undefined, factory.createIdentifier('z')),
])), factory.createStringLiteral('zod'), undefined);
}
/**
* Creates TypeScript transformer factories for the TypeCompiler plugin
*/
function createTypeCompilerPlugin(options = {}) {
return options.generateZodSchemas
? [(program) => createZodTransformer(program, options)]
: [];
}