UNPKG

type-compiler

Version:

A TypeScript compiler plugin for enhanced runtime type checking and analysis with Zod validation

138 lines (137 loc) 7.77 kB
"use strict"; 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)] : []; }