type-compiler
Version:
A TypeScript compiler plugin for enhanced runtime type checking and analysis with Zod validation
278 lines (277 loc) • 13.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.typeToZodSchema = typeToZodSchema;
exports.generateFunctionParamsValidator = generateFunctionParamsValidator;
exports.generateClassValidators = generateClassValidators;
exports.processTypeWithWorker = processTypeWithWorker;
const typescript_1 = __importDefault(require("typescript"));
const logger_1 = require("./logger");
/**
* Determines if a special field validator should be applied to a property
* and returns the appropriate Zod validator expression
*/
function applySpecialFieldValidation(propertyName, defaultValidator, options, parentTypeName) {
// If no options are provided, return the default
if (!options) {
return defaultValidator;
}
// First check for contextual validators if parent type name is provided
if (parentTypeName && options.contextualValidators) {
// Check for exact parent type name match first
const parentTypeEntry = options.contextualValidators[parentTypeName];
if (parentTypeEntry && typeof parentTypeEntry === 'object') {
// Check if this is a direct mapping (not pattern-based)
if (!('pattern' in parentTypeEntry)) {
// It's a record of field validators
const typeValidators = parentTypeEntry;
if (propertyName in typeValidators) {
const validator = typeValidators[propertyName];
// Check if it's a string or an object with validator and errorMessage
if (typeof validator === 'string') {
return validator;
}
else if (validator && typeof validator === 'object' && 'validator' in validator) {
const validatorStr = validator.validator;
// Apply custom error message if provided
if (validator.errorMessage) {
return `${validatorStr}.message("${validator.errorMessage}")`;
}
return validatorStr;
}
}
}
}
// Then check for pattern-based parent type names
for (const [typePattern, typeConfig] of Object.entries(options.contextualValidators)) {
if (typeConfig && typeof typeConfig === 'object' && 'pattern' in typeConfig && typeConfig.pattern) {
try {
const regex = new RegExp(typePattern);
if (regex.test(parentTypeName)) {
// Type assert to access fields safely
const patternConfig = typeConfig;
const fields = patternConfig.fields;
if (propertyName in fields) {
const validator = fields[propertyName];
// Check if it's a string or an object with validator and errorMessage
if (typeof validator === 'string') {
return validator;
}
else if (validator && typeof validator === 'object' && 'validator' in validator) {
const validatorStr = validator.validator;
// Apply custom error message if provided
if (validator.errorMessage) {
return `${validatorStr}.message("${validator.errorMessage}")`;
}
return validatorStr;
}
}
}
}
catch (error) {
// Handle invalid regex patterns gracefully
console.warn(`Invalid regex pattern in contextualValidators: "${typePattern}"`);
}
}
}
}
// Check special field validators
if (options.specialFieldValidators) {
// First check for exact field name match
if (propertyName in options.specialFieldValidators) {
const validator = options.specialFieldValidators[propertyName];
// Check if it's a string or an object with validator and errorMessage
if (typeof validator === 'string') {
return validator;
}
else if (validator && typeof validator === 'object' && 'validator' in validator && !('pattern' in validator)) {
const validatorStr = validator.validator;
// Apply custom error message if provided
if (validator.errorMessage) {
return `${validatorStr}.message("${validator.errorMessage}")`;
}
return validatorStr;
}
}
// Then check for pattern-based field names
for (const [fieldPattern, validatorConfig] of Object.entries(options.specialFieldValidators)) {
if (validatorConfig && typeof validatorConfig === 'object' && 'pattern' in validatorConfig && validatorConfig.pattern) {
try {
const regex = new RegExp(fieldPattern);
if (regex.test(propertyName)) {
const patternValidator = validatorConfig;
const validatorStr = patternValidator.validator;
// Apply custom error message if provided
if (patternValidator.errorMessage) {
return `${validatorStr}.message("${patternValidator.errorMessage}")`;
}
return validatorStr;
}
}
catch (error) {
// Handle invalid regex patterns gracefully
console.warn(`Invalid regex pattern in specialFieldValidators: "${fieldPattern}"`);
}
}
}
}
// If no special validator was found, return the default
return defaultValidator;
}
/**
* Convert a TypeScript type to a Zod schema
*/
function typeToZodSchema(type, typeChecker, program, options) {
logger_1.logger.trace(`Converting type to Zod schema: ${typeChecker.typeToString(type)}`);
// For demonstration, let's enhance our simplified implementation
// In a real implementation, we would analyze the properties of the type
// and apply special validators based on property names
// Check if the type has properties (like an interface or class)
if (type.getProperties && type.getProperties().length > 0) {
const properties = type.getProperties();
const propertySchemas = [];
// Get the parent type name if available
let parentTypeName;
if (type.symbol && type.symbol.name) {
parentTypeName = type.symbol.name;
}
// Process each property
for (const property of properties) {
// Get the property name - handle both Symbol and property objects
const propertyName = typeof property.getName === 'function'
? property.getName()
: (property.name || String(property.escapedName || ''));
// Get the property type
let propertyType;
try {
if (property.valueDeclaration && typeof typeChecker.getTypeOfSymbolAtLocation === 'function') {
propertyType = typeChecker.getTypeOfSymbolAtLocation(property, property.valueDeclaration);
}
else if (typeof typeChecker.getTypeOfSymbol === 'function') {
propertyType = typeChecker.getTypeOfSymbol(property);
}
else {
// Fallback for tests
propertyType = { flags: 0 };
}
}
catch (e) {
// Fallback for tests
propertyType = { flags: 0 };
}
// Start with a basic validator based on the property type
const baseValidator = 'z.any()';
// In a real implementation, we would analyze the property type here
// and convert it to the appropriate Zod schema
// Apply any special field validation
const finalValidator = applySpecialFieldValidation(propertyName, baseValidator, options, parentTypeName);
// Add to property schemas
propertySchemas.push(`${propertyName}: ${finalValidator}`);
}
// Return a Zod object schema with the processed properties
if (propertySchemas.length > 0) {
return `z.object({\n ${propertySchemas.join(',\n ')}\n})`;
}
}
// For simplicity, return a basic object schema for other cases
return `z.object({})`;
}
/**
* Generate a validator for function parameters
*/
function generateFunctionParamsValidator(node, typeChecker, options) {
const params = node.parameters;
const paramsObj = {};
logger_1.logger.debug(`Generating validator for function parameters: ${node.name?.getText() || 'anonymous'}`);
// Generate schemas for each parameter
for (const param of params) {
if (!param.name || !typescript_1.default.isIdentifier(param.name) || !param.type) {
logger_1.logger.trace(`Skipping parameter with no name or type`);
continue;
}
const paramName = param.name.text;
const paramType = typeChecker.getTypeFromTypeNode(param.type);
logger_1.logger.trace(`Processing parameter: ${paramName}`);
const paramSchema = typeToZodSchema(paramType, typeChecker);
paramsObj[paramName] = paramSchema;
}
// Create a validator function
return `z.object({
${Object.entries(paramsObj).map(([name, schema]) => `${name}: ${schema}`).join(',\n ')}
})`;
}
/**
* Generate validators for class methods
*/
function generateClassValidators(node, typeChecker, zodSchemaPrefix = 'z', options) {
const validators = [];
if (!node.name) {
logger_1.logger.warn(`Skipping class validation for class with no name`);
return validators;
}
const className = node.name.text;
logger_1.logger.debug(`Generating validators for class: ${className}`);
// Find methods that should be validated
for (const member of node.members) {
if (!typescript_1.default.isMethodDeclaration(member)) {
continue;
}
if (!member.name || !typescript_1.default.isIdentifier(member.name)) {
logger_1.logger.trace(`Skipping method with no name`);
continue;
}
const methodName = member.name.text;
// Skip methods that don't need validation
if (methodName === 'constructor' ||
member.parameters.length === 0) {
logger_1.logger.trace(`Skipping method: ${methodName} (constructor or no parameters)`);
continue;
}
logger_1.logger.trace(`Generating validator for method: ${className}.${methodName}`);
// Generate validator for this method
const schema = generateFunctionParamsValidator(member, typeChecker, options);
// Create a variable statement for the validator
const validatorName = `${zodSchemaPrefix}${className}${methodName}Params`;
const statement = typescript_1.default.factory.createVariableStatement([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], typescript_1.default.factory.createVariableDeclarationList([typescript_1.default.factory.createVariableDeclaration(validatorName, undefined, undefined, typescript_1.default.factory.createIdentifier(schema))], typescript_1.default.NodeFlags.Const));
validators.push(statement);
}
logger_1.logger.debug(`Generated ${validators.length} validators for class ${className}`);
return validators;
}
/**
* Process a type with a worker thread
*/
function processTypeWithWorker(type, typeChecker, options, workerPool) {
try {
// Start timer for performance logging
const endTimer = logger_1.logger.startTimer(`Process type: ${typeChecker.typeToString(type)}`);
if (!workerPool) {
// Fallback to synchronous processing
logger_1.logger.debug(`No worker pool available, processing type synchronously`);
const result = typeToZodSchema(type, typeChecker);
endTimer(); // End the timer
return result;
}
logger_1.logger.debug(`Processing type with worker pool: ${typeChecker.typeToString(type)}`);
// In a real implementation, we would:
// 1. Serialize the type information into a plain object
// 2. Send to a worker thread via workerPool.processType
// 3. Wait for the result
// For this demo, we'll use a placeholder
const result = 'z.lazy(() => z.object({}))';
// Update metrics
logger_1.logger.incrementMetric('workerTasksProcessed');
endTimer(); // End the timer
return result;
}
catch (error) {
logger_1.logger.error('Error processing type with worker', {
error: error instanceof Error ? error.message : String(error),
type: typeChecker.typeToString(type)
});
// Fallback to synchronous processing
return typeToZodSchema(type, typeChecker);
}
}