Zodsheriff Codebase Prompt

Hrishi Olickel·

⚠️ Warning: This is a very large file (3,634 lines). Loading may take a moment.

This is the complete zodsheriff codebase used as the prompt in the Something Weird is Happening in AI benchmark for both the README generation task and the code review task.

Zodsheriff is a tool for validating Zod schema definitions to ensure they're safe and follow best practices. Learn more at github.com/SouthBridgeAI/zodsheriff.


Text
<src/argument-validator.ts> L1: import { L2: type Node, L3: type Expression, L4: type CallExpression, L5: type ArrowFunctionExpression, L6: type FunctionExpression, L7: type ArrayExpression, L8: type ObjectExpression, L9: type StringLiteral, L10: type RegExpLiteral, L11: isCallExpression, L12: isMemberExpression, L13: type Identifier, L14: } from "@babel/types"; L15: import { allowedZodMethods, allowedChainMethods } from "./zod-method-names"; L16: import type { ValidationConfig } from "./types"; L17: import { ResourceManager } from "./resource-manager"; L18: import { IssueReporter, IssueSeverity } from "./reporting"; L19: import { validateObjectExpression } from "./object-validator"; L20: import safeRegex from "safe-regex"; L21: L22: /** L23: * Validates arguments passed to Zod schema methods L24: * Ensures arguments are safe and match expected patterns L25: */ L26: export class ArgumentValidator { L27: private readonly resourceManager: ResourceManager; L28: private readonly issueReporter: IssueReporter; L29: L30: constructor( L31: private readonly config: ValidationConfig, L32: resourceManager?: ResourceManager, L33: issueReporter?: IssueReporter, L34: ) { L35: this.resourceManager = resourceManager ?? new ResourceManager(config); L36: this.issueReporter = issueReporter ?? new IssueReporter(); L37: } L38: L39: /** L40: * Method-specific argument validation rules L41: */ L42: private static readonly METHOD_RULES: Record<string, ArgumentRule> = { L43: refine: { L44: minArgs: 1, L45: maxArgs: 2, L46: allowFunction: true, L47: allowSchema: false, L48: validateFunction: true, L49: }, L50: transform: { L51: minArgs: 1, L52: maxArgs: 1, L53: allowFunction: true, L54: allowSchema: false, L55: validateFunction: true, L56: }, L57: pipe: { L58: minArgs: 1, L59: maxArgs: 1, L60: allowFunction: false, L61: allowSchema: true, L62: validateFunction: false, L63: }, L64: regex: { L65: minArgs: 1, L66: maxArgs: 2, L67: allowFunction: false, L68: allowSchema: false, L69: validateRegex: true, L70: }, L71: object: { L72: minArgs: 1, L73: maxArgs: 1, L74: allowFunction: false, L75: allowSchema: false, L76: }, L77: // Add more method rules as needed L78: }; L79: L80: /** L81: * Validates arguments for a specific method call L82: */ L83: public validateMethodArguments( L84: node: CallExpression, L85: methodName: string, L86: ): boolean { L87: const rules = ArgumentValidator.METHOD_RULES[methodName]; L88: if (!rules) { L89: return true; // No specific rules for this method L90: } L91: L92: try { L93: // Check argument count L94: if (!this.validateArgumentCount(node, rules)) { L95: return false; L96: } L97: L98: // Validate each argument L99: return node.arguments.every((arg, index) => L100: this.validateArgument(arg, rules, methodName, index), L101: ); L102: } catch (error) { L103: if (error instanceof Error) { L104: this.issueReporter.reportIssue( L105: node, L106: error.message, L107: node.type, L108: IssueSeverity.ERROR, L109: ); L110: } L111: return false; L112: } L113: } L114: L115: /** L116: * Validates a single argument against rules L117: */ L118: private validateArgument( L119: arg: Node, L120: rules: ArgumentRule, L121: methodName: string, L122: index: number, L123: ): boolean { L124: this.resourceManager.incrementNodeCount(); L125: L126: // If the method requires the first argument to be a function L127: // (e.g., refine or transform), and it's not a function, L128: // return false immediately. L129: if (methodName === "refine" && index === 0 && !isFunction(arg)) { L130: this.issueReporter.reportIssue( L131: arg, L132: ```text The first argument to ${methodName} must be a function ```text , L133: arg.type, L134: IssueSeverity.ERROR, L135: ); L136: return false; L137: } L138: L139: // Handle different argument types L140: if (isFunction(arg)) { L141: return this.validateFunctionArgument(arg, rules); L142: } L143: L144: if (isObjectExpression(arg)) { L145: // If this method does not allow objects (unless explicitly stated), L146: // and we got an object when a function was expected, fail. L147: // For ```text refine ```text , only the second argument (options) might be an object. L148: // If index is 0 and we are here, we already handled above. If index > 0, you may allow. L149: if (methodName === "refine" && index === 0) { L150: return false; L151: } L152: return validateObjectExpression(arg, 0, this.config, []).isValid; L153: } L154: L155: if (isArrayExpression(arg)) { L156: return this.validateArrayArgument(arg); L157: } L158: L159: if (isLiteral(arg)) { L160: return this.validateLiteralArgument(arg as Expression); L161: } L162: L163: if (isIdentifier(arg)) { L164: return this.validateIdentifierArgument(arg); L165: } L166: L167: if (isCallExpression(arg)) { L168: const callee = arg.callee; L169: if ( L170: isMemberExpression(callee) && L171: isIdentifier(callee.object) && L172: callee.object.name === "z" L173: ) { L174: if (isIdentifier(callee.property)) { L175: const methodName = callee.property.name; L176: if ( L177: allowedZodMethods.has(methodName) || L178: allowedChainMethods.has(methodName) L179: ) { L180: return true; L181: } L182: } else { L183: return false; L184: } L185: } else if (isIdentifier(callee) && callee.name === "z") { L186: return true; L187: } L188: } L189: L190: // Unknown argument type L191: this.issueReporter.reportIssue( L192: arg, L193: ```text Unexpected argument type for method ${methodName}: ${arg.type} ```text , L194: arg.type, L195: IssueSeverity.ERROR, L196: ); L197: return false; L198: } L199: L200: /** L201: * Validates function arguments (for refine/transform) L202: */ L203: private validateFunctionArgument( L204: node: ArrowFunctionExpression | FunctionExpression, L205: rules: ArgumentRule, L206: ): boolean { L207: if (!rules.allowFunction) { L208: this.issueReporter.reportIssue( L209: node, L210: "Function arguments not allowed for this method", L211: node.type, L212: IssueSeverity.ERROR, L213: ); L214: return false; L215: } L216: L217: if (rules.validateFunction) { L218: return this.validateFunctionBody(node); L219: } L220: L221: return true; L222: } L223: L224: /** L225: * Validates function bodies for safety L226: */ L227: private validateFunctionBody( L228: node: ArrowFunctionExpression | FunctionExpression, L229: ): boolean { L230: // Don't allow async functions L231: if (node.async) { L232: this.issueReporter.reportIssue( L233: node, L234: "Async functions not allowed in schema validation", L235: node.type, L236: IssueSeverity.ERROR, L237: ); L238: return false; L239: } L240: L241: // Don't allow generators L242: if (node.generator) { L243: this.issueReporter.reportIssue( L244: node, L245: "Generator functions not allowed in schema validation", L246: node.type, L247: IssueSeverity.ERROR, L248: ); L249: return false; L250: } L251: L252: // Validate function body L253: return this.validateFunctionStatements(node.body); L254: } L255: L256: /** L257: * Validates statements within a function body L258: */ L259: private validateFunctionStatements(node: Node): boolean { L260: // TODO: Implement based on your security requirements L261: return true; // Placeholder L262: } L263: L264: /** L265: * Validates array arguments L266: */ L267: private validateArrayArgument(node: ArrayExpression): boolean { L268: // Check array size L269: if (node.elements.length > this.config.maxPropertiesPerObject) { L270: this.issueReporter.reportIssue( L271: node, L272: ```text Array exceeds maximum size of ${this.config.maxPropertiesPerObject} ```text , L273: node.type, L274: IssueSeverity.ERROR, L275: ); L276: return false; L277: } L278: L279: // Validate each element L280: return node.elements.every((element) => { L281: if (!element) return true; // Skip sparse array elements L282: return this.validateArgument( L283: element, L284: { allowFunction: false, allowSchema: false }, L285: "array", L286: 0, L287: ); L288: }); L289: } L290: L291: /** L292: * Validates literal arguments (string, number, boolean, etc.) L293: */ L294: private validateLiteralArgument(node: Expression): boolean { L295: if (isStringLiteral(node)) { L296: return node.value.length <= this.config.maxStringLength; L297: } L298: L299: if (isRegExpLiteral(node)) { L300: return this.validateRegexLiteral(node); L301: } L302: L303: // Other literals are generally safe L304: return true; L305: } L306: L307: /** L308: * Validates regex literals for safety L309: */ L310: private validateRegexLiteral(node: RegExpLiteral): boolean { L311: try { L312: // Check regex pattern length L313: if (node.pattern.length > this.config.maxStringLength) { L314: this.issueReporter.reportIssue( L315: node, L316: "Regex pattern too long", L317: node.type, L318: IssueSeverity.ERROR, L319: ); L320: return false; L321: } L322: L323: if (!safeRegex(node.pattern)) { L324: this.issueReporter.reportIssue( L325: node, L326: "Regex pattern is not safe (as reported by safe-regex)", L327: node.type, L328: IssueSeverity.ERROR, L329: ); L330: return false; L331: } L332: L333: // Could add additional regex safety checks here L334: return true; L335: } catch { L336: return false; L337: } L338: } L339: L340: /** L341: * Validates identifier arguments L342: */ L343: private validateIdentifierArgument(node: Identifier): boolean { L344: // Could add checks for specific identifiers here L345: return true; L346: } L347: L348: /** L349: * Validates argument count against rules L350: */ L351: private validateArgumentCount( L352: node: CallExpression, L353: rules: ArgumentRule, L354: ): boolean { L355: const { minArgs, maxArgs } = rules; L356: const argCount = node.arguments.length; L357: L358: if (minArgs !== undefined && argCount < minArgs) { L359: this.issueReporter.reportIssue( L360: node, L361: ```text Too few arguments. Expected at least ${minArgs}, got ${argCount} ```text , L362: node.type, L363: IssueSeverity.ERROR, L364: ); L365: return false; L366: } L367: L368: if (maxArgs !== undefined && argCount > maxArgs) { L369: this.issueReporter.reportIssue( L370: node, L371: ```text Too many arguments. Expected at most ${maxArgs}, got ${argCount} ```text , L372: node.type, L373: IssueSeverity.ERROR, L374: ); L375: return false; L376: } L377: L378: return true; L379: } L380: } L381: L382: /** L383: * Rules for method argument validation L384: */ L385: interface ArgumentRule { L386: minArgs?: number; L387: maxArgs?: number; L388: allowFunction?: boolean; L389: allowSchema?: boolean; L390: validateFunction?: boolean; L391: validateRegex?: boolean; L392: } L393: L394: // Type guards L395: function isFunction( L396: node: Node, L397: ): node is ArrowFunctionExpression | FunctionExpression { L398: return ( L399: node.type === "ArrowFunctionExpression" || L400: node.type === "FunctionExpression" L401: ); L402: } L403: L404: function isObjectExpression(node: Node): node is ObjectExpression { L405: return node.type === "ObjectExpression"; L406: } L407: L408: function isArrayExpression(node: Node): node is ArrayExpression { L409: return node.type === "ArrayExpression"; L410: } L411: L412: function isStringLiteral(node: Node): node is StringLiteral { L413: return node.type === "StringLiteral"; L414: } L415: L416: function isRegExpLiteral(node: Node): node is RegExpLiteral { L417: return node.type === "RegExpLiteral"; L418: } L419: L420: function isLiteral(node: Node): boolean { L421: return ( L422: node.type === "StringLiteral" || L423: node.type === "NumericLiteral" || L424: node.type === "BooleanLiteral" || L425: node.type === "RegExpLiteral" || L426: node.type === "NullLiteral" L427: ); L428: } L429: L430: function isIdentifier(node: Node): node is Identifier { L431: return node.type === "Identifier"; L432: } L433: </src/argument-validator.ts> <src/chain-validator.ts> L1: import type { L2: Node, L3: CallExpression, L4: MemberExpression, L5: Identifier, L6: Expression, L7: } from "@babel/types"; L8: import type { ValidationConfig } from "./types"; L9: import { ResourceManager } from "./resource-manager"; L10: import { IssueReporter, IssueSeverity } from "./reporting"; L11: import { allowedChainMethods, allowedZodMethods } from "./zod-method-names"; L12: import { ArgumentValidator } from "./argument-validator"; L13: L14: /** L15: * Validates method chains in Zod schemas L16: * Ensures proper chaining depth and only allowed methods are used L17: */ L18: export class ChainValidator { L19: private readonly resourceManager: ResourceManager; L20: private readonly issueReporter: IssueReporter; L21: private readonly argumentValidator: ArgumentValidator; L22: L23: constructor( L24: private readonly config: ValidationConfig, L25: resourceManager?: ResourceManager, L26: issueReporter?: IssueReporter, L27: argumentValidator?: ArgumentValidator, L28: ) { L29: this.resourceManager = resourceManager ?? new ResourceManager(config); L30: this.issueReporter = issueReporter ?? new IssueReporter(); L31: this.argumentValidator = L32: argumentValidator ?? L33: new ArgumentValidator(config, this.resourceManager, this.issueReporter); L34: } L35: L36: /** L37: * Validates a chain of method calls starting from a node L38: * @param node - The starting node of the chain L39: * @returns boolean indicating if the chain is valid L40: */ L41: public validateChain(node: Node): boolean { L42: try { L43: return this.validateChainNode(node, 0); L44: } catch (error) { L45: if (error instanceof Error) { L46: this.issueReporter.reportIssue( L47: node, L48: error.message, L49: node.type, L50: IssueSeverity.ERROR, L51: ); L52: } L53: return false; L54: } L55: } L56: L57: /** L58: * Recursively validates a node in the method chain L59: * @param node - Current node to validate L60: * @param depth - Current depth in the chain L61: * @returns boolean indicating if the node and its chain are valid L62: */ L63: private validateChainNode(node: Node, depth: number): boolean { L64: this.resourceManager.incrementNodeCount(); L65: L66: // Enforce chain depth here if you want to fail gracefully instead of throwing L67: if (depth > this.config.maxChainDepth) { L68: this.issueReporter.reportIssue( L69: node, L70: ```text Chain nesting depth exceeded maximum of ${this.config.maxChainDepth} ```text , L71: node.type, L72: IssueSeverity.ERROR, L73: ); L74: return false; L75: } L76: L77: this.resourceManager.trackDepth(depth, "chain"); L78: L79: if (isIdentifier(node)) { L80: return this.validateIdentifier(node); L81: } L82: L83: if (isMemberExpression(node)) { L84: return this.validateMemberExpression(node, depth); L85: } L86: L87: if (isCallExpression(node)) { L88: return this.validateCallExpression(node, depth); L89: } L90: L91: this.issueReporter.reportIssue( L92: node, L93: ```text Unexpected node type in chain: ${node.type} ```text , L94: node.type, L95: IssueSeverity.ERROR, L96: ); L97: return false; L98: } L99: L100: /** L101: * Validates a call expression node (e.g., z.string(), .optional()) L102: */ L103: private validateCallExpression(node: CallExpression, depth: number): boolean { L104: // Validate the callee first L105: const callee = node.callee as Expression; L106: L107: if (!this.validateChainNode(callee, depth + 1)) { L108: return false; L109: } L110: L111: // Get the method name being called L112: const methodName = this.getMethodName(callee); L113: if (!methodName) { L114: this.issueReporter.reportIssue( L115: node, L116: "Unable to determine method name", L117: node.type, L118: IssueSeverity.ERROR, L119: ); L120: return false; L121: } L122: L123: // Validate method arguments if needed L124: if (this.requiresArgumentValidation(methodName)) { L125: return this.validateMethodArguments(node, methodName); L126: } L127: L128: return true; L129: } L130: L131: /** L132: * Validates a member expression (e.g., z.string, .optional) L133: */ L134: private validateMemberExpression( L135: node: MemberExpression, L136: depth: number, L137: ): boolean { L138: if (node.computed) { L139: this.issueReporter.reportIssue( L140: node, L141: "Computed properties not allowed in chain", L142: node.type, L143: IssueSeverity.ERROR, L144: ); L145: return false; L146: } L147: L148: // Validate the object part of the member expression L149: if (!this.validateChainNode(node.object, depth + 1)) { L150: return false; L151: } L152: L153: // Validate the property name L154: if (!isIdentifier(node.property)) { L155: this.issueReporter.reportIssue( L156: node.property, L157: "Property must be an identifier", L158: node.property.type, L159: IssueSeverity.ERROR, L160: ); L161: return false; L162: } L163: L164: const methodName = node.property.name; L165: if (!this.isMethodAllowed(methodName)) { L166: this.issueReporter.reportIssue( L167: node, L168: ```text Method not allowed in chain: ${methodName} ```text , L169: node.type, L170: IssueSeverity.ERROR, L171: "Use only allowed Zod methods", L172: ); L173: return false; L174: } L175: L176: return true; L177: } L178: L179: /** L180: * Validates an identifier node (should be 'z') L181: */ L182: private validateIdentifier(node: Identifier): boolean { L183: if (node.name !== "z") { L184: this.issueReporter.reportIssue( L185: node, L186: ```text Chain must start with 'z', found: ${node.name} ```text , L187: node.type, L188: IssueSeverity.ERROR, L189: ); L190: return false; L191: } L192: return true; L193: } L194: L195: /** L196: * Gets the method name from a node L197: */ L198: private getMethodName(node: Expression): string | null { L199: if (isIdentifier(node)) { L200: return node.name; L201: } L202: if (isMemberExpression(node) && isIdentifier(node.property)) { L203: return node.property.name; L204: } L205: return null; L206: } L207: L208: /** L209: * Checks if a method name is allowed L210: */ L211: private isMethodAllowed(methodName: string): boolean { L212: return ( L213: allowedZodMethods.has(methodName) || allowedChainMethods.has(methodName) L214: ); L215: } L216: L217: /** L218: * Checks if a method requires argument validation L219: */ L220: private requiresArgumentValidation(methodName: string): boolean { L221: // Add methods that need argument validation L222: return ["refine", "transform", "pipe", "object"].includes(methodName); L223: } L224: L225: /** L226: * Validates arguments for specific methods L227: */ L228: private validateMethodArguments( L229: node: CallExpression, L230: methodName: string, L231: ): boolean { L232: return this.argumentValidator.validateMethodArguments(node, methodName); L233: } L234: } L235: L236: // Type guards L237: function isIdentifier(node: Node): node is Identifier { L238: return node.type === "Identifier"; L239: } L240: L241: function isMemberExpression(node: Node): node is MemberExpression { L242: return node.type === "MemberExpression"; L243: } L244: L245: function isCallExpression(node: Node): node is CallExpression { L246: return node.type === "CallExpression"; L247: } L248: </src/chain-validator.ts> <src/index.ts> L1: import type { Issue } from "./reporting"; L2: import { validateSchema } from "./schema-validator"; L3: import { createConfig, relaxedConfig, type ValidationConfig } from "./types"; L4: L5: // Core validators and types L6: export { SchemaValidator, validateSchema } from "./schema-validator"; L7: export type { ValidationConfig, PropertySafetyConfig } from "./types"; L8: export { IssueReporter, IssueSeverity, type Issue } from "./reporting"; L9: export { L10: ResourceManager, L11: ValidationError, L12: type ResourceStats, L13: } from "./resource-manager"; L14: L15: // Preset configurations L16: export { L17: extremelySafeConfig, L18: mediumConfig, L19: relaxedConfig, L20: createConfig, L21: } from "./types"; L22: L23: // Main validation function with default config L24: export async function validateZodSchema( L25: schemaCode: string, L26: config: Partial<ValidationConfig> = {}, L27: ): Promise<{ L28: isValid: boolean; L29: cleanedCode: string; L30: issues: Array<Issue>; L31: }> { L32: const finalConfig = createConfig(relaxedConfig, config); L33: return validateSchema(schemaCode, finalConfig); L34: } L35: L36: // Example usage in comments L37: /* L38: import { validateZodSchema, extremelySafeConfig } from 'zod-validator'; L39: L40: // Using default config (medium) L41: const result = await validateZodSchema( ```text ```text L42: import { z } from 'zod'; L43: export const userSchema = z.object({ L44: name: z.string(), L45: age: z.number() L46: }); L47: ```text ); ```text L48: L49: // Using specific config L50: const resultWithConfig = await validateZodSchema(schemaCode, { L51: timeoutMs: 2000, L52: maxNodeCount: 5000 L53: }); L54: L55: // Using preset config L56: const safeResult = await validateZodSchema(schemaCode, extremelySafeConfig); L57: */ L58: </src/index.ts> <src/object-validator.ts> L1: import type { L2: Node, L3: ObjectExpression, L4: ObjectProperty, L5: ObjectMethod, L6: Identifier, L7: StringLiteral, L8: } from "@babel/types"; L9: import type { ValidationConfig } from "./types"; L10: import { type Issue, IssueSeverity } from "./reporting"; L11: L12: /** L13: * Result of validating a node, including any issues found L14: */ L15: interface ValidationResult { L16: isValid: boolean; L17: issues: Issue[]; L18: } L19: L20: /** L21: * Cache for validation results to avoid re-processing nodes L22: * Uses WeakMap to allow garbage collection of processed nodes L23: */ L24: const validationCache = new WeakMap<Node, ValidationResult>(); L25: L26: /** L27: * Validates an object expression against configured safety rules L28: * Checks property count, depth, and property name safety L29: * L30: * @param obj - The object expression node to validate L31: * @param depth - Current depth in the object hierarchy L32: * @param config - Validation configuration settings L33: * @param parentNodes - Stack of parent nodes for context L34: * @returns ValidationResult indicating if the object is valid L35: */ L36: export function validateObjectExpression( L37: obj: ObjectExpression, L38: depth: number, L39: config: ValidationConfig, L40: parentNodes: Node[] = [], L41: ): ValidationResult { L42: // Check cache first L43: const cached = validationCache.get(obj); L44: if (config.enableCaching && cached) { L45: return cached; L46: } L47: L48: const issues: Issue[] = []; L49: L50: // Check depth L51: if (depth >= config.maxObjectDepth) { L52: issues.push({ L53: line: obj.loc?.start.line ?? -1, L54: column: obj.loc?.start.column, L55: message: ```text Object exceeds maximum nesting depth of ${config.maxObjectDepth} ```text , L56: severity: IssueSeverity.ERROR, L57: nodeType: "ObjectExpression", L58: }); L59: return cacheAndReturn(obj, config, { isValid: false, issues }); L60: } L61: L62: // Check property count L63: if (obj.properties.length > config.maxPropertiesPerObject) { L64: issues.push({ L65: line: obj.loc?.start.line ?? -1, L66: column: obj.loc?.start.column, L67: message: ```text Object exceeds maximum property count of ${config.maxPropertiesPerObject} ```text , L68: severity: IssueSeverity.ERROR, L69: nodeType: "ObjectExpression", L70: }); L71: return cacheAndReturn(obj, config, { isValid: false, issues }); L72: } L73: L74: // Validate each property L75: for (const prop of obj.properties) { L76: if (prop.type === "ObjectProperty") { L77: const propResult = validateProperty(prop, config, [...parentNodes, obj]); L78: if (!propResult.isValid) { L79: issues.push(...propResult.issues); L80: return cacheAndReturn(obj, config, { isValid: false, issues }); L81: } L82: // Check if the property value is also an object L83: if (prop.value && prop.value.type === "ObjectExpression") { L84: const nestedResult = validateObjectExpression( L85: prop.value, L86: depth + 1, L87: config, L88: [...parentNodes, obj], L89: ); L90: if (!nestedResult.isValid) { L91: issues.push(...nestedResult.issues); L92: return cacheAndReturn(obj, config, { isValid: false, issues }); L93: } L94: } L95: } else if (prop.type === "ObjectMethod") { L96: const propResult = validateProperty(prop, config, [...parentNodes, obj]); L97: if (!propResult.isValid) { L98: issues.push(...propResult.issues); L99: return cacheAndReturn(obj, config, { isValid: false, issues }); L100: } L101: } else if (prop.type === "SpreadElement") { L102: // Already handled above L103: issues.push({ L104: line: prop.loc?.start.line ?? -1, L105: column: prop.loc?.start.column, L106: message: "Spread elements are not allowed in objects", L107: severity: IssueSeverity.ERROR, L108: nodeType: "SpreadElement", L109: }); L110: return cacheAndReturn(obj, config, { isValid: false, issues }); L111: } L112: } L113: L114: return cacheAndReturn(obj, config, { isValid: true, issues }); L115: } L116: L117: /** L118: * Validates a single object property or method L119: * Checks for unsafe property names, getters/setters, and computed properties L120: * L121: * @param prop - The property node to validate L122: * @param config - Validation configuration settings L123: * @param parentNodes - Stack of parent nodes for context L124: * @returns ValidationResult indicating if the property is valid L125: */ L126: function validateProperty( L127: prop: ObjectProperty | ObjectMethod, L128: config: ValidationConfig, L129: parentNodes: Node[], L130: ): ValidationResult { L131: const issues: Issue[] = []; L132: L133: // Check for computed properties L134: if (prop.computed && !config.allowComputedProperties) { L135: issues.push({ L136: line: prop.loc?.start.line ?? -1, L137: column: prop.loc?.start.column, L138: message: "Computed properties are not allowed", L139: severity: IssueSeverity.ERROR, L140: nodeType: prop.type, L141: }); L142: return { isValid: false, issues }; L143: } L144: L145: // Check for getters/setters L146: if (isObjectMethod(prop) && (prop.kind === "get" || prop.kind === "set")) { L147: issues.push({ L148: line: prop.loc?.start.line ?? -1, L149: column: prop.loc?.start.column, L150: message: "Getter/setter methods are not allowed", L151: severity: IssueSeverity.ERROR, L152: nodeType: "ObjectMethod", L153: }); L154: return { isValid: false, issues }; L155: } L156: L157: // Validate property name L158: const nameResult = validatePropertyName(prop.key, config); L159: if (!nameResult.isValid) { L160: issues.push(...nameResult.issues); L161: return { isValid: false, issues }; L162: } L163: L164: return { isValid: true, issues }; L165: } L166: L167: /** L168: * Validates a property name against safety rules L169: * Checks against allowed/denied lists and prefixes L170: * L171: * @param key - The property key node to validate L172: * @param config - Validation configuration settings L173: * @returns ValidationResult indicating if the property name is safe L174: */ L175: function validatePropertyName( L176: key: Node, L177: config: ValidationConfig, L178: ): ValidationResult { L179: // Only handle identifier and string literal keys L180: if (!isIdentifier(key) && !isStringLiteral(key)) { L181: return { L182: isValid: false, L183: issues: [ L184: { L185: line: key.loc?.start.line ?? -1, L186: column: key.loc?.start.column, L187: message: "Property key must be an identifier or string literal", L188: severity: IssueSeverity.ERROR, L189: nodeType: key.type, L190: }, L191: ], L192: }; L193: } L194: L195: const name = isIdentifier(key) ? key.name : key.value; L196: const { propertySafety } = config; L197: L198: if (name === "__proto__") { L199: return { L200: isValid: false, L201: issues: [ L202: { L203: line: key.loc?.start.line ?? -1, L204: column: key.loc?.start.column, L205: message: ```text Property name '${name}' is not allowed ```text , L206: severity: IssueSeverity.ERROR, L207: nodeType: key.type, L208: }, L209: ], L210: }; L211: } L212: L213: // Check against denied properties L214: if (propertySafety.deniedProperties.has(name)) { L215: return { L216: isValid: false, L217: issues: [ L218: { L219: line: key.loc?.start.line ?? -1, L220: column: key.loc?.start.column, L221: message: ```text Property name '${name}' is not allowed ```text , L222: severity: IssueSeverity.WARNING, L223: nodeType: key.type, L224: }, L225: ], L226: }; L227: } L228: L229: // Check against denied prefixes L230: if (propertySafety.deniedPrefixes.some((prefix) => name.startsWith(prefix))) { L231: return { L232: isValid: false, L233: issues: [ L234: { L235: line: key.loc?.start.line ?? -1, L236: column: key.loc?.start.column, L237: message: ```text Property name '${name}' uses a forbidden prefix ```text , L238: severity: IssueSeverity.ERROR, L239: nodeType: key.type, L240: }, L241: ], L242: }; L243: } L244: L245: // If we're using a whitelist, check against allowed properties L246: if ( L247: propertySafety.allowedProperties.size > 0 && L248: !propertySafety.allowedProperties.has(name) L249: ) { L250: return { L251: isValid: false, L252: issues: [ L253: { L254: line: key.loc?.start.line ?? -1, L255: column: key.loc?.start.column, L256: message: ```text Property name '${name}' is not in the allowed list ```text , L257: severity: IssueSeverity.ERROR, L258: nodeType: key.type, L259: }, L260: ], L261: }; L262: } L263: L264: return { isValid: true, issues: [] }; L265: } L266: L267: /** L268: * Type guards for node types L269: */ L270: function isObjectProperty(node: Node): node is ObjectProperty { L271: return node.type === "ObjectProperty"; L272: } L273: L274: function isObjectMethod(node: Node): node is ObjectMethod { L275: return node.type === "ObjectMethod"; L276: } L277: L278: function isIdentifier(node: Node): node is Identifier { L279: return node.type === "Identifier"; L280: } L281: L282: function isStringLiteral(node: Node): node is StringLiteral { L283: return node.type === "StringLiteral"; L284: } L285: L286: /** L287: * Helper to cache and return validation results L288: */ L289: function cacheAndReturn( L290: obj: ObjectExpression, // Add obj parameter L291: config: ValidationConfig, // Add config parameter L292: result: ValidationResult, L293: ): ValidationResult { L294: if (config.enableCaching) { L295: validationCache.set(obj, result); L296: } L297: return result; L298: } L299: </src/object-validator.ts> <src/reporting.ts> L1: import type { Node } from "@babel/types"; L2: L3: /** L4: * Represents a validation issue found during processing L5: */ L6: export interface Issue { L7: line: number; L8: column?: number; L9: message: string; L10: nodeType: string; L11: suggestion?: string; L12: severity: IssueSeverity; L13: source?: string; L14: } L15: L16: /** L17: * Severity levels for validation issues L18: */ L19: export enum IssueSeverity { L20: ERROR = "error", L21: WARNING = "warning", L22: INFO = "info", L23: } L24: L25: /** L26: * Class to manage validation issues and reporting L27: */ L28: export class IssueReporter { L29: private issues: Issue[] = []; L30: L31: /** L32: * Reports a new validation issue L33: */ L34: public reportIssue( L35: node: Node, L36: message: string, L37: nodeType: string, L38: severity: IssueSeverity = IssueSeverity.ERROR, L39: suggestion?: string, L40: ): void { L41: this.issues.push({ L42: line: node.loc?.start.line ?? -1, L43: column: node.loc?.start.column, L44: message, L45: nodeType, L46: suggestion, L47: severity, L48: source: this.getSourceSnippet(node), L49: }); L50: } L51: L52: /** L53: * Gets all reported issues L54: */ L55: public getIssues(): Issue[] { L56: return [...this.issues]; L57: } L58: L59: /** L60: * Gets issues filtered by severity L61: */ L62: public getIssuesBySeverity(severity: IssueSeverity): Issue[] { L63: return this.issues.filter((issue) => issue.severity === severity); L64: } L65: L66: /** L67: * Checks if there are any issues of ERROR severity L68: */ L69: public hasErrors(): boolean { L70: return this.issues.some((issue) => issue.severity === IssueSeverity.ERROR); L71: } L72: L73: /** L74: * Clears all reported issues L75: */ L76: public clear(): void { L77: this.issues = []; L78: } L79: L80: /** L81: * Gets a formatted report of all issues L82: */ L83: public getFormattedReport(): string { L84: return this.issues.map((issue) => this.formatIssue(issue)).join("\n\n"); L85: } L86: L87: private formatIssue(issue: Issue): string { L88: const location = issue.column L89: ? ```text ${issue.line}:${issue.column} ```text L90: : ```text line ${issue.line} ```text ; L91: L92: let report = ```text ${issue.severity.toUpperCase()}: ${issue.message} (${ ```text L93: issue.nodeType L94: }) at ${location} ```text ; ```text L95: L96: if (issue.source) { L97: report += ```text \n${issue.source} ```text ; L98: } L99: L100: if (issue.suggestion) { L101: report += ```text \nSuggestion: ${issue.suggestion} ```text ; L102: } L103: L104: return report; L105: } L106: L107: private getSourceSnippet(node: Node): string | undefined { L108: // Implementation would require access to source code L109: // Could be injected through constructor L110: return undefined; L111: } L112: } L113: L114: /** L115: * Global issue reporter instance L116: * Could also be instantiated per validation run if needed L117: */ L118: export const globalIssueReporter = new IssueReporter(); L119: L120: /** L121: * Convenience function for reporting issues L122: */ L123: export function reportIssue( L124: node: Node, L125: message: string, L126: nodeType: string, L127: suggestion?: string, L128: ): void { L129: globalIssueReporter.reportIssue( L130: node, L131: message, L132: nodeType, L133: IssueSeverity.ERROR, L134: suggestion, L135: ); L136: } L137: </src/reporting.ts> <src/resource-manager.ts> L1: import type { Node } from "@babel/types"; L2: import { type ValidationConfig, relaxedConfig } from "./types"; L3: /** L4: * Manages resource usage and enforces limits during validation L5: * Tracks node count, execution time, and validates against configured limits L6: */ L7: export class ResourceManager { L8: private nodeCount = 0; L9: private startTime: number; L10: readonly config: ValidationConfig; L11: private lastTimeoutCheck: number = Date.now(); L12: private readonly CHECK_INTERVAL_MS = 100; // Check every 100ms L13: L14: // Track resource usage per validation level to handle nested validations L15: private readonly depthMap: Map<number, number> = new Map(); L16: L17: constructor(config: ValidationConfig) { L18: this.config = config; L19: this.startTime = Date.now(); L20: } L21: L22: /** L23: * Resets all counters and timers L24: * Should be called before starting a new validation L25: */ L26: public reset(): void { L27: this.nodeCount = 0; L28: this.startTime = Date.now(); L29: this.depthMap.clear(); L30: } L31: L32: /** L33: * Checks if execution has exceeded configured timeout L34: * @throws {ValidationError} if timeout is exceeded L35: */ L36: public checkTimeout(): void { L37: const elapsed = Date.now() - this.startTime; L38: if (elapsed > this.config.timeoutMs) { L39: throw new ValidationError( L40: ```text Validation timeout exceeded (${this.config.timeoutMs}ms) ```text , L41: "Timeout", L42: ); L43: } L44: } L45: L46: /** L47: * Checks if execution has exceeded configured timeout L48: * @throws {ValidationError} if timeout is exceeded L49: */ L50: public checkTimeoutAggressive(): void { L51: const elapsed = Date.now() - this.startTime; L52: if (elapsed > this.config.timeoutMs * 0.9) { L53: // 90% of timeout L54: throw new ValidationError( L55: ```text Operation approaching timeout limit ```text , L56: "Timeout", L57: ); L58: } L59: } L60: L61: /** L62: * Runs an async operation with timeout check L63: * @param operation - Operation to run L64: * @returns Result of operation L65: */ L66: public async withTimeoutCheck<T>(operation: () => Promise<T>): Promise<T> { L67: this.checkTimeoutAggressive(); L68: const result = await operation(); L69: this.checkTimeout(); L70: return result; L71: } L72: L73: /** L74: * Runs a synchronous operation with timeout check L75: * Uses Node.js worker threads for CPU-bound operations L76: * @param operation - Operation to run L77: * @returns Result of operation L78: * @throws {ValidationError} if timeout is exceeded L79: **/ L80: public withTimeoutCheckSync<T>(operation: () => T): T { L81: this.checkTimeoutAggressive(); L82: const result = operation(); L83: this.checkTimeout(); L84: return result; L85: } L86: L87: /** L88: * Increments node count and checks against limit L89: * @throws {ValidationError} if node count exceeds maximum L90: */ L91: public incrementNodeCount(): void { L92: this.nodeCount++; L93: L94: // Only check timeout periodically L95: const now = Date.now(); L96: if (now - this.lastTimeoutCheck > this.CHECK_INTERVAL_MS) { L97: this.checkTimeout(); L98: this.lastTimeoutCheck = now; L99: } L100: L101: if (this.nodeCount > this.config.maxNodeCount) { L102: throw new ValidationError( L103: ```text Node count exceeded maximum of ${this.config.maxNodeCount} ```text , L104: "NodeLimit", L105: ); L106: } L107: } L108: L109: /** L110: * Tracks and validates nested depth of validations L111: * @param depth - Current depth level L112: * @param type - Type of depth being tracked (e.g., 'object', 'chain') L113: * @throws {ValidationError} if depth exceeds maximum L114: */ L115: public trackDepth( L116: depth: number, L117: type: "object" | "chain" | "argument", L118: ): void { L119: const currentCount = this.depthMap.get(depth) || 0; L120: this.depthMap.set(depth, currentCount + 1); L121: L122: const maxDepth = this.getMaxDepthForType(type); L123: if (depth > maxDepth) { L124: throw new ValidationError( L125: ```text ${type} nesting depth exceeded maximum of ${maxDepth} ```text , L126: "DepthLimit", L127: ); L128: } L129: } L130: L131: /** L132: * Validates size of a collection (array, object properties, etc.) L133: * @param size - Size to validate L134: * @param maxSize - Maximum allowed size L135: * @param type - Type of collection being validated L136: * @throws {ValidationError} if size exceeds maximum L137: */ L138: public validateSize(size: number, maxSize: number, type: string): void { L139: if (size > maxSize) { L140: throw new ValidationError( L141: ```text ${type} size exceeded maximum of ${maxSize} ```text , L142: "SizeLimit", L143: ); L144: } L145: } L146: L147: /** L148: * Returns current resource usage statistics L149: */ L150: public getStats(): ResourceStats { L151: return { L152: nodeCount: this.nodeCount, L153: executionTime: Date.now() - this.startTime, L154: maxDepthReached: Math.max(...this.depthMap.keys(), 0), L155: }; L156: } L157: L158: private getMaxDepthForType(type: "object" | "chain" | "argument"): number { L159: switch (type) { L160: case "object": L161: return this.config.maxObjectDepth; L162: case "chain": L163: return this.config.maxChainDepth; L164: case "argument": L165: return this.config.maxArgumentNesting; L166: default: L167: throw new Error( ```text Unknown depth type: ${type} ```text ); L168: } L169: } L170: } L171: L172: /** L173: * Custom error class for validation failures L174: */ L175: export class ValidationError extends Error { L176: constructor( L177: message: string, L178: public readonly type: ValidationErrorType, L179: public readonly node?: Node, L180: ) { L181: super(message); L182: this.name = "ValidationError"; L183: } L184: } L185: L186: /** L187: * Types of validation errors that can occur L188: */ L189: export type ValidationErrorType = L190: | "Timeout" L191: | "NodeLimit" L192: | "DepthLimit" L193: | "SizeLimit"; L194: L195: /** L196: * Statistics about resource usage during validation L197: */ L198: export interface ResourceStats { L199: nodeCount: number; L200: executionTime: number; L201: maxDepthReached: number; L202: } L203: L204: /** L205: * Utility class for running operations with timeout L206: */ L207: export class TimeoutRunner { L208: constructor(private readonly timeoutMs: number) {} L209: L210: /** L211: * Runs an async operation with timeout L212: * @param operation - Operation to run L213: * @returns Result of operation L214: * @throws {ValidationError} if timeout is exceeded L215: */ L216: public async runWithTimeout<T>(operation: () => Promise<T>): Promise<T> { L217: const timeoutPromise = new Promise<never>((_, reject) => { L218: setTimeout(() => { L219: reject( L220: new ValidationError( L221: ```text Operation timed out after ${this.timeoutMs}ms ```text , L222: "Timeout", L223: ), L224: ); L225: }, this.timeoutMs); L226: }); L227: L228: return Promise.race([operation(), timeoutPromise]); L229: } L230: L231: /** L232: * Runs a synchronous operation with timeout L233: * Uses Node.js worker threads for CPU-bound operations L234: */ L235: public runSync<T>(operation: () => T): T { L236: // TODO: Implementation should use Worker threads L237: throw new Error("Not implemented"); L238: } L239: } L240: L241: /** L242: * Factory function to create a ResourceManager with optional initial config L243: */ L244: export function createResourceManager( L245: config?: Partial<ValidationConfig>, L246: ): ResourceManager { L247: return new ResourceManager({ L248: ...relaxedConfig, L249: ...config, L250: }); L251: } L252: </src/resource-manager.ts> <src/run.ts> L1: import * as fs from "fs"; L2: import * as path from "path"; L3: import { stdin as input } from "node:process"; L4: import * as readline from "node:readline"; L5: import clipboardy from "clipboardy"; L6: import { validateZodSchema } from "."; L7: import { extremelySafeConfig, mediumConfig, relaxedConfig } from "./types"; L8: L9: interface CliOptions { L10: stdin: boolean; L11: clipboard: boolean; L12: config: "extremelySafe" | "medium" | "relaxed"; L13: cleanOnly: boolean; L14: json: boolean; L15: help: boolean; L16: } L17: L18: async function readFromStdin(): Promise<string> { L19: const rl = readline.createInterface({ input }); L20: const lines: string[] = []; L21: for await (const line of rl) { L22: lines.push(line); L23: } L24: return lines.join("\n"); L25: } L26: L27: function printHelp(): void { L28: console.log( ```text Usage: zodsheriff [options] [file] ```text L29: L30: Options: L31: --stdin Read schema from standard input L32: --clipboard Read schema from system clipboard L33: --config <level> Set validation config: extremelySafe | medium | relaxed (default: relaxed) L34: --clean-only Output only the cleaned schema L35: --json Output result in JSON format L36: --help Show this help message L37: L38: Examples: L39: zodsheriff schema.ts L40: zodsheriff --stdin < schema.ts L41: zodsheriff --clipboard L42: zodsheriff --config medium schema.ts L43: zodsheriff --clean-only schema.ts ```text ); ```text L44: } L45: L46: async function readInput( L47: options: CliOptions, L48: inputFilePath?: string L49: ): Promise<string> { L50: if (options.stdin) { L51: return readFromStdin(); L52: } L53: if (options.clipboard) { L54: return clipboardy.read(); L55: } L56: if (inputFilePath) { L57: return fs.readFileSync(path.resolve(inputFilePath), "utf8"); L58: } L59: throw new Error( L60: "No input specified. Use --stdin, --clipboard, or provide a file path." L61: ); L62: } L63: L64: async function main() { L65: const args = process.argv.slice(2); L66: const options: CliOptions = { L67: stdin: false, L68: clipboard: false, L69: config: "relaxed", L70: cleanOnly: false, L71: json: false, L72: help: false, L73: }; L74: L75: let inputFilePath: string | undefined; L76: L77: // Parse arguments L78: for (let i = 0; i < args.length; i++) { L79: const arg = args[i]; L80: if (arg === "--help") { L81: printHelp(); L82: process.exit(0); L83: } L84: if (arg === "--stdin") { L85: options.stdin = true; L86: } else if (arg === "--clipboard") { L87: options.clipboard = true; L88: } else if (arg === "--config") { L89: const val = args[i + 1]; L90: if (!val || !["extremelySafe", "medium", "relaxed"].includes(val)) { L91: console.error( L92: "Invalid config value. Must be one of: extremelySafe, medium, relaxed" L93: ); L94: process.exit(1); L95: } L96: options.config = val as CliOptions["config"]; L97: i++; L98: } else if (arg === "--clean-only") { L99: options.cleanOnly = true; L100: } else if (arg === "--json") { L101: options.json = true; L102: } else if (!arg.startsWith("--")) { L103: inputFilePath = arg; L104: } L105: } L106: L107: try { L108: const schemaCode = await readInput(options, inputFilePath); L109: const configMap = { L110: extremelySafe: extremelySafeConfig, L111: medium: mediumConfig, L112: relaxed: relaxedConfig, L113: }; L114: L115: const result = await validateZodSchema( L116: schemaCode, L117: configMap[options.config] L118: ); L119: L120: if (options.json) { L121: console.log(JSON.stringify(result, null, 2)); L122: process.exit(result.isValid ? 0 : 1); L123: } L124: L125: if (options.cleanOnly) { L126: if (result.cleanedCode) { L127: console.log(result.cleanedCode); L128: process.exit(0); L129: } L130: console.error("No cleaned code generated due to validation errors."); L131: process.exit(1); L132: } L133: L134: // Standard output L135: if (!result.isValid) { L136: console.log("❌ Validation failed."); L137: if (result.issues.length > 0) { L138: console.log("Issues:"); L139: for (const issue of result.issues) { L140: console.log( L141: ```text - ${issue.severity.toUpperCase()}: ${issue.message} (at line ${ ```text L142: issue.line L143: }, node: ${issue.nodeType}) ```text ```text L144: ); L145: } L146: } L147: } else { L148: console.log("✅ Validation passed."); L149: } L150: L151: if (result.cleanedCode) { L152: console.log("Cleaned schema:"); L153: console.log(result.cleanedCode); L154: } L155: L156: process.exit(result.isValid ? 0 : 1); L157: } catch (err) { L158: console.error("Error:", err instanceof Error ? err.message : err); L159: process.exit(1); L160: } L161: } L162: L163: main().catch((err) => { L164: console.error("Fatal error:", err instanceof Error ? err.message : err); L165: process.exit(1); L166: }); L167: </src/run.ts> <src/schema-validator.ts> L1: import { parse } from "@babel/parser"; L2: import _traverse from "@babel/traverse"; L3: import _generate from "@babel/generator"; L4: import { L5: type Node, L6: type File, L7: type Statement, L8: type Expression, L9: type VariableDeclaration, L10: type VariableDeclarator, L11: exportNamedDeclaration, L12: } from "@babel/types"; L13: import type { ValidationConfig } from "./types"; L14: import { ResourceManager } from "./resource-manager"; L15: import { IssueReporter, IssueSeverity } from "./reporting"; L16: import { ChainValidator } from "./chain-validator"; L17: import { ArgumentValidator } from "./argument-validator"; L18: L19: // Handle ESM default export L20: const traverse = (_traverse as any).default || _traverse; L21: const generate = (_generate as any).default || _generate; L22: L23: /** L24: * SchemaValidator class L25: * Main class responsible for validating Zod schema definitions. L26: * Coordinates validation of imports, schema structure, and method chains L27: * while enforcing configured safety limits. L28: */ L29: export class SchemaValidator { L30: private readonly resourceManager: ResourceManager; L31: private readonly issueReporter: IssueReporter; L32: private readonly chainValidator: ChainValidator; L33: private readonly argumentValidator: ArgumentValidator; L34: L35: constructor( L36: private readonly config: ValidationConfig, L37: resourceManager?: ResourceManager, L38: issueReporter?: IssueReporter L39: ) { L40: this.resourceManager = resourceManager ?? new ResourceManager(config); L41: this.issueReporter = issueReporter ?? new IssueReporter(); L42: this.chainValidator = new ChainValidator( L43: config, L44: this.resourceManager, L45: this.issueReporter L46: ); L47: this.argumentValidator = new ArgumentValidator( L48: config, L49: this.resourceManager, L50: this.issueReporter L51: ); L52: } L53: L54: /** L55: * Main schema validation method L56: * L57: * Process: L58: * 1. Parses input code to AST L59: * 2. Validates imports L60: * 3. Processes and validates declarations L61: * 4. Removes invalid nodes L62: * 5. Auto-exports valid schemas L63: * 6. Generates cleaned output L64: * L65: * Notes: L66: * - Returns isValid: false if any errors are found L67: * - Still returns cleaned code containing valid schemas L68: * - Reports all validation issues found L69: * L70: * @param schemaCode - The schema code to validate L71: * @returns Promise<ValidationResult> with validation status, cleaned code, and issues L72: */ L73: public async validateSchema(schemaCode: string): Promise<ValidationResult> { L74: this.resourceManager.reset(); L75: this.issueReporter.clear(); L76: L77: try { L78: // Parse the code L79: const ast = await this.parseCode(schemaCode); L80: if (!ast) { L81: return { L82: isValid: false, L83: cleanedCode: "", L84: issues: this.issueReporter.getIssues(), L85: }; L86: } L87: L88: let hasValidSchemas = false; L89: let hasErrors = false; L90: const nodesToRemove = new Set<Node>(); L91: L92: // First check for required zod import L93: const hasZodImport = this.validateZodImport(ast); L94: if (!hasZodImport) { L95: hasErrors = true; L96: } L97: L98: // Traverse and validate the AST L99: traverse(ast, { L100: ImportDeclaration: (path) => { L101: if (path.node.source.value !== "zod") { L102: this.issueReporter.reportIssue( L103: path.node, L104: ```text Invalid import from '${path.node.source.value}'. Only 'zod' imports are allowed. ```text , L105: "ImportDeclaration", L106: IssueSeverity.ERROR L107: ); L108: nodesToRemove.add(path.node); L109: hasErrors = true; L110: } L111: }, L112: L113: VariableDeclaration: (path) => { L114: const isValid = this.validateVariableDeclaration(path.node); L115: L116: if (!isValid) { L117: nodesToRemove.add(path.node); L118: hasErrors = true; L119: } else { L120: // Check if this contains any schema declarations L121: const hasSchema = path.node.declarations.some((decl) => L122: this.isSchemaDeclaration(decl) L123: ); L124: if (hasSchema) { L125: hasValidSchemas = true; L126: // If it's valid and not already exported, wrap it in an export L127: if ( L128: !path.parent || L129: path.parent.type !== "ExportNamedDeclaration" L130: ) { L131: const exportDecl = exportNamedDeclaration(path.node, []); L132: path.replaceWith(exportDecl); L133: } L134: } else { L135: nodesToRemove.add(path.node); L136: } L137: } L138: }, L139: L140: Statement: (path) => { L141: if (!this.isAllowedStatement(path.node)) { L142: this.issueReporter.reportIssue( L143: path.node, L144: ```text Invalid statement type: ${path.node.type} ```text , L145: path.node.type, L146: IssueSeverity.ERROR L147: ); L148: nodesToRemove.add(path.node); L149: hasErrors = true; L150: } L151: }, L152: }); L153: L154: // Remove invalid nodes L155: traverse(ast, { L156: enter(path) { L157: if (nodesToRemove.has(path.node)) { L158: path.remove(); L159: } L160: }, L161: }); L162: L163: // Generate cleaned code if we found any valid schemas L164: let cleanedCode = ""; L165: if (hasValidSchemas) { L166: const generated = generate(ast, { L167: comments: true, L168: compact: false, L169: }); L170: cleanedCode = generated.code; L171: } L172: L173: return { L174: isValid: !hasErrors, L175: cleanedCode, L176: issues: this.issueReporter.getIssues(), L177: }; L178: } catch (error) { L179: this.handleError(error); L180: return { L181: isValid: false, L182: cleanedCode: "", L183: issues: this.issueReporter.getIssues(), L184: }; L185: } L186: } L187: L188: /** L189: * Validates that the code properly imports 'z' from 'zod' L190: * L191: * Checks for: L192: * - Presence of zod import L193: * - Correct import specifier ('z') L194: * - No other imports from other modules L195: * L196: * @param ast - The AST to validate L197: * @returns boolean indicating if import is valid L198: */ L199: private validateZodImport(ast: File): boolean { L200: const hasZodImport = ast.program.body.some((node) => { L201: if (node.type !== "ImportDeclaration") return false; L202: if (node.source.value !== "zod") return false; L203: L204: return node.specifiers.some((spec) => { L205: return ( L206: (spec.type === "ImportDefaultSpecifier" || L207: spec.type === "ImportSpecifier") && L208: spec.local.name === "z" L209: ); L210: }); L211: }); L212: L213: if (!hasZodImport) { L214: this.issueReporter.reportIssue( L215: { type: "File", loc: { start: { line: 1, column: 0 } } } as Node, L216: "Missing 'z' import from 'zod'", L217: "File", L218: IssueSeverity.ERROR L219: ); L220: } L221: L222: return hasZodImport; L223: } L224: L225: /** L226: * Validates a variable declaration node to ensure it meets schema requirements L227: * L228: * Validates that: L229: * - Declaration uses 'const' L230: * - Has a proper initializer (not undefined or missing) L231: * - Schema initialization is valid L232: * L233: * @param node - The variable declaration to validate L234: * @returns boolean indicating if the declaration is valid L235: */ L236: private validateVariableDeclaration(node: VariableDeclaration): boolean { L237: // Only allow const declarations L238: if (node.kind !== "const") { L239: this.issueReporter.reportIssue( L240: node, L241: "Schema declarations must use 'const'", L242: "VariableDeclaration", L243: IssueSeverity.ERROR L244: ); L245: return false; L246: } L247: L248: let isValid = true; L249: for (const declarator of node.declarations) { L250: // Check for missing initializer L251: if (!declarator.init) { L252: this.issueReporter.reportIssue( L253: declarator, L254: "Schema declaration must have an initializer", L255: "VariableDeclarator", L256: IssueSeverity.ERROR L257: ); L258: isValid = false; L259: continue; L260: } L261: L262: // Check for undefined initializer L263: if ( L264: declarator.init.type === "Identifier" && L265: declarator.init.name === "undefined" L266: ) { L267: this.issueReporter.reportIssue( L268: declarator, L269: "Schema declaration must have an initializer", L270: "VariableDeclarator", L271: IssueSeverity.ERROR L272: ); L273: isValid = false; L274: continue; L275: } L276: L277: // For schema declarations, validate the initialization L278: if (this.isSchemaDeclaration(declarator)) { L279: if (!this.validateSchemaExpression(declarator.init)) { L280: isValid = false; L281: } L282: } L283: } L284: L285: return isValid; L286: } L287: L288: /** L289: * Determines if a variable declarator represents a schema declaration L290: * L291: * Checks: L292: * - Variable name (contains 'schema') L293: * - Initialization pattern (z.* or schema-like call expression) L294: * L295: * @param declarator - The variable declarator to check L296: * @returns boolean indicating if this is a schema declaration L297: */ L298: private isSchemaDeclaration(declarator: VariableDeclarator): boolean { L299: if (!declarator.init) return false; L300: L301: // Check for explicit schema naming L302: if ( L303: declarator.id.type === "Identifier" && L304: declarator.id.name.toLowerCase().includes("schema") L305: ) { L306: return true; L307: } L308: L309: // Check initialization pattern L310: const init = declarator.init; L311: return ( L312: init.type === "CallExpression" || L313: (init.type === "MemberExpression" && L314: init.object.type === "Identifier" && L315: init.object.name === "z") L316: ); L317: } L318: L319: /** L320: * Parses input code into an AST L321: * L322: * @param code - The code to parse L323: * @returns Promise<File | null> The parsed AST or null if parsing fails L324: */ L325: private async parseCode(code: string): Promise<File | null> { L326: try { L327: return parse(code.trim(), { L328: sourceType: "module", L329: plugins: ["typescript"], L330: tokens: true, L331: }); L332: } catch (error) { L333: this.issueReporter.reportIssue( L334: { type: "File", loc: { start: { line: 1, column: 0 } } } as Node, L335: ```text Failed to parse schema: ${ ```text L336: error instanceof Error ? error.message : "Unknown error" L337: } ```text , ```text L338: "File", L339: IssueSeverity.ERROR L340: ); L341: return null; L342: } L343: } L344: L345: /** L346: * Validates a schema expression L347: * Currently delegates to chain validator for method chain validation L348: * L349: * @param node - The expression to validate L350: * @returns boolean indicating if the expression is valid L351: */ L352: private validateSchemaExpression(node: Expression): boolean { L353: return this.chainValidator.validateChain(node); L354: } L355: L356: /** L357: * Checks if a statement type is allowed in schema definitions L358: * L359: * @param node - The statement to check L360: * @returns boolean indicating if the statement type is allowed L361: */ L362: private isAllowedStatement(node: Statement): boolean { L363: return ( L364: node.type === "ImportDeclaration" || L365: node.type === "ExportNamedDeclaration" || L366: node.type === "VariableDeclaration" || L367: node.type === "ExportDefaultDeclaration" L368: ); L369: } L370: L371: /** L372: * Handles errors during validation L373: * Converts errors to validation issues L374: * L375: * @param error - The error to handle L376: */ L377: private handleError(error: unknown): void { L378: const message = error instanceof Error ? error.message : "Unknown error"; L379: this.issueReporter.reportIssue( L380: { type: "File", loc: { start: { line: 1, column: 0 } } } as Node, L381: ```text Validation error: ${message} ```text , L382: "File", L383: IssueSeverity.ERROR L384: ); L385: } L386: } L387: L388: /** L389: * Result of schema validation L390: */ L391: interface ValidationResult { L392: /** Whether the schema is valid (no errors found) */ L393: isValid: boolean; L394: /** The cleaned and formatted schema code */ L395: cleanedCode: string; L396: /** Array of validation issues found */ L397: issues: Array<{ L398: line: number; L399: column?: number; L400: message: string; L401: nodeType: string; L402: severity: IssueSeverity; L403: suggestion?: string; L404: }>; L405: } L406: L407: /** L408: * Convenience function to validate a schema L409: * L410: * @param schemaCode - The schema code to validate L411: * @param config - The validation configuration to use L412: * @returns Promise<ValidationResult> L413: */ L414: export async function validateSchema( L415: schemaCode: string, L416: config: ValidationConfig L417: ): Promise<ValidationResult> { L418: const validator = new SchemaValidator(config); L419: return validator.validateSchema(schemaCode); L420: } L421: </src/schema-validator.ts> <src/types.ts> L1: /** L2: * Core configuration interface for validation settings L3: */ L4: export interface ValidationConfig { L5: // Timeout settings L6: timeoutMs: number; L7: L8: // Resource limits L9: maxNodeCount: number; L10: maxObjectDepth: number; L11: maxChainDepth: number; L12: maxArgumentNesting: number; L13: maxPropertiesPerObject: number; L14: maxStringLength: number; L15: L16: // Performance settings L17: enableParallelProcessing: boolean; L18: maxConcurrentValidations: number; L19: enableCaching: boolean; L20: L21: // Safety settings L22: allowLoops: boolean; L23: allowComputedProperties: boolean; L24: allowTemplateExpressions: boolean; L25: propertySafety: PropertySafetyConfig; L26: L27: // Runtime checks L28: addRuntimeProtection: boolean; L29: } L30: L31: /** L32: * Configuration for property name safety checks L33: */ L34: export interface PropertySafetyConfig { L35: allowedPrefixes: string[]; L36: deniedPrefixes: string[]; L37: allowedProperties: Set<string>; L38: deniedProperties: Set<string>; L39: } L40: L41: /** L42: * Predefined validation configurations L43: */ L44: export const extremelySafeConfig: ValidationConfig = { L45: timeoutMs: 1000, L46: maxNodeCount: 1000, L47: maxObjectDepth: 3, L48: maxChainDepth: 3, L49: maxArgumentNesting: 2, L50: maxPropertiesPerObject: 20, L51: maxStringLength: 100, L52: enableParallelProcessing: false, L53: maxConcurrentValidations: 1, L54: enableCaching: true, L55: allowLoops: false, L56: allowComputedProperties: false, L57: allowTemplateExpressions: false, L58: propertySafety: { L59: allowedPrefixes: [], L60: deniedPrefixes: ["_", "$"], L61: allowedProperties: new Set(["type", "value", "items"]), L62: deniedProperties: new Set(["__proto__", "constructor", "prototype"]), L63: }, L64: addRuntimeProtection: true, L65: }; L66: L67: export const mediumConfig: ValidationConfig = { L68: timeoutMs: 5000, // 5 seconds L69: maxNodeCount: 10000, L70: maxObjectDepth: 5, L71: maxChainDepth: 5, L72: maxArgumentNesting: 4, L73: maxPropertiesPerObject: 100, L74: maxStringLength: 1000, L75: enableParallelProcessing: true, L76: maxConcurrentValidations: 4, L77: enableCaching: true, L78: allowLoops: true, L79: allowComputedProperties: false, L80: allowTemplateExpressions: true, L81: propertySafety: { L82: allowedPrefixes: [], L83: deniedPrefixes: ["__"], // Only block double underscore L84: allowedProperties: new Set(), // Empty = allow all except denied L85: deniedProperties: new Set([ L86: "__proto__", L87: "constructor", L88: "prototype", L89: "eval", L90: "arguments", L91: "process", L92: "global", L93: "window", L94: "document", L95: ]), L96: }, L97: addRuntimeProtection: true, L98: }; L99: L100: export const relaxedConfig: ValidationConfig = { L101: timeoutMs: 30000, // 30 seconds L102: maxNodeCount: 1000000, L103: maxObjectDepth: 10, L104: maxChainDepth: 10, L105: maxArgumentNesting: 8, L106: maxPropertiesPerObject: 1000, L107: maxStringLength: 10000, L108: enableParallelProcessing: true, L109: maxConcurrentValidations: 8, L110: enableCaching: true, L111: allowLoops: true, L112: allowComputedProperties: true, L113: allowTemplateExpressions: true, L114: propertySafety: { L115: allowedPrefixes: [], // Allow all prefixes L116: deniedPrefixes: ["__"], // Still block double underscore for safety L117: allowedProperties: new Set(), // Empty = allow all except denied L118: deniedProperties: new Set([ L119: "__proto__", L120: "constructor", // Minimal safety - just block prototype pollution L121: ]), L122: }, L123: addRuntimeProtection: false, // Trust the code more in relaxed mode L124: }; L125: L126: // Helper to combine configs with overrides L127: export function createConfig( L128: baseConfig: ValidationConfig, L129: overrides?: Partial<ValidationConfig>, L130: ): ValidationConfig { L131: return { L132: ...baseConfig, L133: ...overrides, L134: // Deep merge for nested objects L135: propertySafety: { L136: ...baseConfig.propertySafety, L137: ...overrides?.propertySafety, L138: // Ensure Sets are properly merged L139: allowedProperties: new Set([ L140: ...Array.from(baseConfig.propertySafety.allowedProperties), L141: ...(overrides?.propertySafety?.allowedProperties || []), L142: ]), L143: deniedProperties: new Set([ L144: ...Array.from(baseConfig.propertySafety.deniedProperties), L145: ...(overrides?.propertySafety?.deniedProperties || []), L146: ]), L147: }, L148: }; L149: } L150: L151: /** L152: * Location information for nodes L153: */ L154: export interface Location { L155: line: number; L156: column: number; L157: } L158: L159: /** L160: * Base interface for all validation context L161: */ L162: export interface ValidationContext { L163: config: ValidationConfig; L164: parentNodes: Node[]; L165: depth: number; L166: } L167: </src/types.ts> <src/zod-method-names.ts> L1: export const allowedZodMethods = new Set([ L2: // Primitives L3: "string", L4: "number", L5: "boolean", L6: "date", L7: "bigint", L8: "symbol", L9: L10: // Empty types L11: "undefined", L12: "null", L13: "void", L14: L15: // Catch-all types L16: "any", L17: "unknown", L18: "never", L19: L20: // Complex types L21: "array", L22: "object", L23: "union", L24: "discriminatedUnion", L25: "intersection", L26: "tuple", L27: "record", L28: "map", L29: "set", L30: "function", L31: "promise", L32: L33: // Special types L34: "enum", L35: "nativeEnum", L36: "literal", L37: "lazy", L38: L39: // Coercion L40: "coerce", L41: L42: // Effects L43: "optional", L44: "nullable", L45: "nullish", L46: "transform", L47: "default", L48: "catch", L49: "preprocess", L50: L51: // Custom L52: "custom", L53: L54: // Type helpers L55: "instanceof", L56: ]); L57: L58: export const allowedChainMethods = new Set([ L59: // String specific validations L60: "min", L61: "max", L62: "length", L63: "email", L64: "url", L65: "emoji", L66: "uuid", L67: "cuid", L68: "cuid2", L69: "ulid", L70: "regex", L71: "includes", L72: "startsWith", L73: "endsWith", L74: "datetime", L75: "ip", L76: "cidr", L77: "trim", L78: "toLowerCase", L79: "toUpperCase", L80: "date", L81: "time", L82: "duration", L83: "base64", L84: "nanoid", L85: L86: // Number specific validations L87: "gt", L88: "gte", L89: "lt", L90: "lte", L91: "int", L92: "positive", L93: "negative", L94: "nonpositive", L95: "nonnegative", L96: "multipleOf", L97: "finite", L98: "safe", L99: L100: // Array/Set methods L101: "nonempty", L102: "size", L103: "element", L104: L105: // Effects and transforms L106: "optional", L107: "nullable", L108: "nullish", L109: "transform", L110: "default", L111: "catch", L112: "preprocess", L113: "refine", L114: "superRefine", L115: "pipe", L116: "brand", L117: "readonly", L118: L119: // Object methods L120: "partial", L121: "deepPartial", L122: "required", L123: "passthrough", L124: "strict", L125: "strip", L126: "catchall", L127: "pick", L128: "omit", L129: "extend", L130: "merge", L131: "keyof", L132: "shape", L133: L134: // Common operations L135: "describe", L136: "or", L137: "and", L138: L139: // Type conversions L140: "array", L141: "promise", L142: ]); L143: </src/zod-method-names.ts> <tests/argument-validator.test.ts> L1: import { ArgumentValidator } from "../src/argument-validator"; L2: import { L3: TestResourceManager, L4: createTestConfig, L5: parseCallExpression, L6: } from "./test-utils"; L7: import { parse } from "@babel/parser"; L8: import { CallExpression } from "@babel/types"; L9: L10: describe("ArgumentValidator", () => { L11: let validator: ArgumentValidator; L12: let resourceManager: TestResourceManager; L13: L14: beforeEach(() => { L15: const config = createTestConfig(); L16: resourceManager = new TestResourceManager(config); L17: validator = new ArgumentValidator(config, resourceManager); L18: }); L19: L20: function parseAndValidate(code: string, methodName: string): boolean { L21: const ast = parse(code); L22: const stmt = ast.program.body[0]; L23: if (stmt.type !== "ExpressionStatement") L24: throw new Error("Expected expression statement"); L25: const expr = stmt.expression; L26: if (expr.type !== "CallExpression") L27: throw new Error("Expected call expression"); L28: return validator.validateMethodArguments(expr, methodName); L29: } L30: L31: describe("validateMethodArguments", () => { L32: it("should validate refine method arguments with a proper function", () => { L33: const result = parseAndValidate( L34: ```text schema.refine((val) => val > 0) ```text , L35: "refine" L36: ); L37: expect(result).toBe(true); L38: }); L39: L40: it("should reject refine method arguments if function is async", () => { L41: const result = parseAndValidate( L42: ```text schema.refine(async (val) => val > 0) ```text , L43: "refine" L44: ); L45: expect(result).toBe(false); L46: }); L47: L48: it("should reject refine method arguments if function is a generator", () => { L49: const result = parseAndValidate( L50: ```text schema.refine(function* (val) { yield val; }) ```text , L51: "refine" L52: ); L53: expect(result).toBe(false); L54: }); L55: L56: it("should validate transform method arguments with a simple synchronous function", () => { L57: const result = parseAndValidate( L58: ```text schema.transform(val => val.toString()) ```text , L59: "transform" L60: ); L61: expect(result).toBe(true); L62: }); L63: L64: it("should validate pipe method arguments if it’s a schema call", () => { L65: // Assume pipe allows schema as argument (as per rules) L66: const result = parseAndValidate( ```text schema.pipe(z.string()) ```text , "pipe"); L67: expect(result).toBe(true); L68: }); L69: L70: it("should reject pipe method arguments if it’s a function (not allowed for pipe)", () => { L71: const result = parseAndValidate( ```text schema.pipe((val) => val) ```text , "pipe"); L72: expect(result).toBe(false); L73: }); L74: L75: it("should validate regex method arguments with a safe regex", () => { L76: const result = parseAndValidate( ```text schema.regex(/^[a-z]+$/) ```text , "regex"); L77: expect(result).toBe(true); L78: }); L79: L80: it("should reject regex method arguments if pattern is not safe", () => { L81: // Example of a catastrophic regex L82: const result = parseAndValidate( ```text schema.regex(/^(a+)+$/) ```text , "regex"); L83: expect(result).toBe(false); L84: }); L85: L86: it("should allow a literal argument (e.g., number) for methods with no specific restrictions", () => { L87: // For an unknown method with no rules, we just return true L88: const callExpr = parseCallExpression( ```text schema.unknownMethod(42) ```text ); L89: const result = validator.validateMethodArguments( L90: callExpr, L91: "unknownMethod" L92: ); L93: // No rules for unknownMethod, should return true per current logic L94: expect(result).toBe(true); L95: }); L96: L97: it("should reject too many arguments for transform (max 1)", () => { L98: const result = parseAndValidate( L99: ```text schema.transform(val => val, "extra") ```text , L100: "transform" L101: ); L102: expect(result).toBe(false); L103: }); L104: L105: it("should reject too few arguments for refine (min 1)", () => { L106: const result = parseAndValidate( ```text schema.refine() ```text , "refine"); L107: expect(result).toBe(false); L108: }); L109: }); L110: }); L111: </tests/argument-validator.test.ts> <tests/chain-validator.test.ts> L1: import { ChainValidator } from "../src/chain-validator"; L2: import { TestResourceManager, createTestConfig } from "./test-utils"; L3: import { parse } from "@babel/parser"; L4: import { ExpressionStatement, Node } from "@babel/types"; L5: import { IssueReporter } from "../src/reporting"; L6: import { ArgumentValidator } from "../src/argument-validator"; L7: L8: describe("ChainValidator", () => { L9: let validator: ChainValidator; L10: let resourceManager: TestResourceManager; L11: let issueReporter: IssueReporter; L12: let argumentValidator: ArgumentValidator; L13: L14: beforeEach(() => { L15: const config = createTestConfig(); L16: resourceManager = new TestResourceManager(config); L17: issueReporter = new IssueReporter(); L18: argumentValidator = new ArgumentValidator( L19: config, L20: resourceManager, L21: issueReporter L22: ); L23: validator = new ChainValidator( L24: config, L25: resourceManager, L26: issueReporter, L27: argumentValidator L28: ); L29: }); L30: L31: function parseExpression(code: string): Node { L32: const ast = parse(code, { sourceType: "module", plugins: ["typescript"] }); L33: const stmt = ast.program.body[0] as ExpressionStatement; L34: return stmt.expression; L35: } L36: L37: it("should validate a simple chain starting with z", () => { L38: const node = parseExpression( ```text z.string() ```text ); L39: const result = validator.validateChain(node); L40: expect(result).toBe(true); L41: expect(issueReporter.getIssues()).toHaveLength(0); L42: }); L43: L44: it("should fail if chain does not start with z identifier", () => { L45: const node = parseExpression( ```text x.string() ```text ); L46: const result = validator.validateChain(node); L47: expect(result).toBe(false); L48: const issues = issueReporter.getIssues(); L49: expect(issues).toHaveLength(1); L50: expect(issues[0].message).toContain("Chain must start with 'z'"); L51: }); L52: L53: it("should validate allowed zod methods in chain", () => { L54: const node = parseExpression( ```text z.string().min(5).max(10) ```text ); L55: const result = validator.validateChain(node); L56: expect(result).toBe(true); L57: expect(issueReporter.getIssues()).toHaveLength(0); L58: }); L59: L60: it("should report issue for not allowed method in chain", () => { L61: const node = parseExpression( ```text z.string().someForbiddenMethod() ```text ); L62: const result = validator.validateChain(node); L63: expect(result).toBe(false); L64: const issues = issueReporter.getIssues(); L65: expect(issues).toHaveLength(1); L66: expect(issues[0].message).toContain("Method not allowed in chain"); L67: }); L68: L69: it("should handle deeply chained calls within maxChainDepth", () => { L70: // Allowed depth is 10 in relaxed config, try a chain of length 5 L71: const node = parseExpression( ```text z.string().min(1).max(2).trim().email() ```text ); L72: const result = validator.validateChain(node); L73: expect(result).toBe(true); L74: }); L75: L76: it("should fail if chain depth exceeds maxChainDepth", () => { L77: // Set a lower maxChainDepth L78: const config = createTestConfig({ maxChainDepth: 2 }); L79: validator = new ChainValidator( L80: config, L81: resourceManager, L82: issueReporter, L83: argumentValidator L84: ); L85: L86: const node = parseExpression( ```text z.string().min(1).max(2).trim() ```text ); L87: const result = validator.validateChain(node); L88: expect(result).toBe(false); L89: L90: const issues = issueReporter.getIssues(); L91: expect( L92: issues.some((issue) => L93: issue.message.includes("Chain nesting depth exceeded") L94: ) L95: ).toBe(true); L96: }); L97: L98: it("should report error for computed member expression properties", () => { L99: const node = parseExpression( ```text z.string()[methodName]() ```text ); L100: const result = validator.validateChain(node); L101: expect(result).toBe(false); L102: const issues = issueReporter.getIssues(); L103: expect(issues[0].message).toContain( L104: "Computed properties not allowed in chain" L105: ); L106: }); L107: }); L108: </tests/chain-validator.test.ts> <tests/complex.test.ts> L1: import { parse } from "@babel/parser"; L2: import { ExpressionStatement, Node } from "@babel/types"; L3: import { SchemaValidator } from "../src/schema-validator"; L4: import { validateObjectExpression } from "../src/object-validator"; L5: import { ChainValidator } from "../src/chain-validator"; L6: import { ArgumentValidator } from "../src/argument-validator"; L7: import { IssueReporter } from "../src/reporting"; L8: import { createTestConfig, TestResourceManager } from "./test-utils"; L9: L10: function parseExpression(code: string): Node { L11: const ast = parse(code, { sourceType: "module", plugins: ["typescript"] }); L12: const stmt = ast.program.body[0] as ExpressionStatement; L13: if (!stmt || stmt.type !== "ExpressionStatement") { L14: throw new Error("Expected an expression statement"); L15: } L16: return stmt.expression; L17: } L18: L19: describe("Complex Validation Tests", () => { L20: let config: ReturnType<typeof createTestConfig>; L21: let resourceManager: TestResourceManager; L22: let issueReporter: IssueReporter; L23: let chainValidator: ChainValidator; L24: let argumentValidator: ArgumentValidator; L25: let schemaValidator: SchemaValidator; L26: L27: beforeEach(() => { L28: config = createTestConfig(); L29: resourceManager = new TestResourceManager(config); L30: issueReporter = new IssueReporter(); L31: argumentValidator = new ArgumentValidator( L32: config, L33: resourceManager, L34: issueReporter L35: ); L36: chainValidator = new ChainValidator( L37: config, L38: resourceManager, L39: issueReporter, L40: argumentValidator L41: ); L42: schemaValidator = new SchemaValidator( L43: config, L44: resourceManager, L45: issueReporter L46: ); L47: }); L48: L49: function parseAndValidate(code: string, methodName: string): boolean { L50: const ast = parse(code); L51: const stmt = ast.program.body[0]; L52: if (stmt.type !== "ExpressionStatement") L53: throw new Error("Expected expression statement"); L54: const expr = stmt.expression; L55: if (expr.type !== "CallExpression") L56: throw new Error("Expected call expression"); L57: return argumentValidator.validateMethodArguments(expr, methodName); L58: } L59: L60: // ----------------------- L61: // Argument Validation Tests L62: // ----------------------- L63: describe("ArgumentValidator - complex scenarios", () => { L64: it("should validate refine with two arguments and a complex function body", () => { L65: const code = ```text ```text L66: schema.refine( L67: function(val) { L68: const result = val > 0; L69: return result; L70: }, L71: { message: "Value must be positive" } L72: ); L73: ```text ; ```text L74: const result = parseAndValidate(code, "refine"); L75: expect(result).toBe(true); L76: }); L77: L78: it("should validate a transform function with nested internal calls", () => { L79: const code = ```text ```text L80: schema.transform((val) => { L81: function helper(x) { return x.toString().toUpperCase(); } L82: return helper(val); L83: }) L84: ```text ; ```text L85: const result = parseAndValidate(code, "transform"); L86: expect(result).toBe(true); L87: }); L88: L89: it("should reject a complex unsafe regex with flags", () => { L90: const code = ```text schema.regex(/^(a+)+$/mi) ```text ; L91: const result = parseAndValidate(code, "regex"); L92: expect(result).toBe(false); L93: }); L94: L95: it("should fail if refine is given a non-function (object) argument", () => { L96: const code = ```text ```text L97: schema.refine({ L98: notAFunction: true L99: }); L100: ```text ; ```text L101: // 'refine' expects a function, not an object. L102: const result = parseAndValidate(code, "refine"); L103: expect(result).toBe(false); L104: }); L105: }); L106: L107: // ----------------------- L108: // Chain Validation Tests L109: // ----------------------- L110: describe("ChainValidator - complex scenarios", () => { L111: it("should detect a disallowed method at the end of a long allowed chain", () => { L112: const node = parseExpression( L113: ```text z.string().min(1).max(10).someForbiddenMethod() ```text L114: ); L115: const result = chainValidator.validateChain(node); L116: expect(result).toBe(false); L117: const issues = issueReporter.getIssues(); L118: expect(issues[0].message).toContain("Method not allowed in chain"); L119: }); L120: L121: it("should reject complex member expressions with computed properties deep in chain", () => { L122: const node = parseExpression( ```text z.object()[dynamicProp].shape() ```text ); L123: const result = chainValidator.validateChain(node); L124: expect(result).toBe(false); L125: expect(issueReporter.getIssues()[0].message).toContain( L126: "Computed properties not allowed in chain" L127: ); L128: }); L129: L130: it("should fail if chain depth exceeded with multiple valid methods", () => { L131: const shallowConfig = createTestConfig({ maxChainDepth: 2 }); L132: const shallowResourceManager = new TestResourceManager(shallowConfig); L133: const shallowIssueReporter = new IssueReporter(); L134: const shallowArgumentValidator = new ArgumentValidator( L135: shallowConfig, L136: shallowResourceManager, L137: shallowIssueReporter L138: ); L139: const shallowChainValidator = new ChainValidator( L140: shallowConfig, L141: shallowResourceManager, L142: shallowIssueReporter, L143: shallowArgumentValidator L144: ); L145: const node = parseExpression( ```text z.string().min(1).max(2).trim().email() ```text ); L146: const result = shallowChainValidator.validateChain(node); L147: expect(result).toBe(false); L148: expect( L149: shallowIssueReporter L150: .getIssues() L151: .some((issue) => L152: issue.message.includes("Chain nesting depth exceeded") L153: ) L154: ).toBe(true); L155: }); L156: }); L157: L158: // ----------------------- L159: // Object Validation Tests L160: // ----------------------- L161: describe("ObjectValidator - complex scenarios", () => { L162: function parseObjectExpression(code: string) { L163: const ast = parse(code); L164: const stmt = ast.program.body[0] as ExpressionStatement; L165: if (!stmt || stmt.type !== "ExpressionStatement") { L166: throw new Error("Expected expression statement"); L167: } L168: const expr = stmt.expression; L169: if (expr.type !== "ObjectExpression") { L170: throw new Error("Expected object expression"); L171: } L172: return expr; L173: } L174: L175: it("should detect unsafe property at a deeper nesting level", () => { L176: const code = ```text ```text L177: ({ L178: safeProp: 1, L179: nested: { L180: allowed: 2, L181: deeper: { L182: constructor: "not allowed here" L183: } L184: } L185: }) L186: ```text ; ```text L187: const objExpr = parseObjectExpression(code); L188: const result = validateObjectExpression(objExpr, 0, config); L189: expect(result.isValid).toBe(false); L190: expect( L191: result.issues.some((issue) => issue.message.includes("is not allowed")) L192: ).toBe(true); L193: }); L194: L195: it("should reject objects with spread elements", () => { L196: const code = ```text ```text L197: ({ L198: ...spreadData L199: }) L200: ```text ; ```text L201: const objExpr = parseObjectExpression(code); L202: const result = validateObjectExpression(objExpr, 0, config); L203: expect(result.isValid).toBe(false); L204: expect(result.issues[0].message).toContain( L205: "Spread elements are not allowed" L206: ); L207: }); L208: }); L209: L210: // ----------------------- L211: // Schema Validator Tests L212: // ----------------------- L213: describe("SchemaValidator - complex scenarios", () => { L214: it("should handle multiple schema declarations and fail if one is invalid", async () => { L215: const code = ```text ```text L216: import { z } from 'zod'; L217: const validSchema = z.string(); L218: const invalidSchema = undefined; L219: export const anotherSchema = z.number(); L220: ```text ; ```text L221: const result = await schemaValidator.validateSchema(code); L222: expect(result.isValid).toBe(false); L223: expect( L224: result.issues.some((issue) => L225: issue.message.includes("Schema declaration must have an initializer") L226: ) L227: ).toBe(true); L228: }); L229: L230: it("should fail if z is not imported from zod", async () => { L231: const code = ```text ```text L232: import { z as differentName } from 'zod'; L233: export const testSchema = differentName.string(); L234: ```text ; ```text L235: const result = await schemaValidator.validateSchema(code); L236: expect(result.isValid).toBe(false); L237: expect( L238: result.issues.some((issue) => L239: issue.message.includes("Missing 'z' import from 'zod'") L240: ) L241: ).toBe(true); L242: }); L243: L244: it("should fail if something else is imported from another library", async () => { L245: const code = ```text ```text L246: import { z } from 'zod'; L247: import x from 'not-zod'; L248: const testSchema = z.string(); L249: ```text ; ```text L250: const result = await schemaValidator.validateSchema(code); L251: expect(result.isValid).toBe(false); L252: expect( L253: result.issues.some((issue) => L254: issue.message.includes("Only 'zod' imports are allowed") L255: ) L256: ).toBe(true); L257: }); L258: }); L259: }); L260: </tests/complex.test.ts> <tests/object-validator.test.ts> L1: import { validateObjectExpression } from "../src/object-validator"; L2: import { createTestConfig } from "./test-utils"; L3: import { parse } from "@babel/parser"; L4: import { ObjectExpression, ExpressionStatement } from "@babel/types"; L5: import { IssueSeverity } from "../src/reporting"; L6: L7: describe("ObjectValidator", () => { L8: const config = createTestConfig(); L9: L10: function parseObjectExpression(code: string): ObjectExpression { L11: const ast = parse(code); L12: const stmt = ast.program.body[0]; L13: if (!stmt || stmt.type !== "ExpressionStatement") { L14: throw new Error("Expected expression statement"); L15: } L16: const expr = (stmt as ExpressionStatement).expression; L17: if (expr.type !== "ObjectExpression") { L18: throw new Error("Expected object expression"); L19: } L20: return expr; L21: } L22: L23: it("should validate safe object expressions", () => { L24: const code = ```text ({ ```text L25: name: "test", L26: age: 42, L27: tags: ["a", "b"] L28: }) ```text ; ```text L29: const objExpr = parseObjectExpression(code); L30: const result = validateObjectExpression(objExpr, 0, config); L31: expect(result.isValid).toBe(true); L32: expect(result.issues).toHaveLength(0); L33: }); L34: L35: it("should reject computed properties when not allowed", () => { L36: const code = ```text ({ ```text L37: ["computed"]: "value" L38: }) ```text ; ```text L39: const objExpr = parseObjectExpression(code); L40: const result = validateObjectExpression(objExpr, 0, config); L41: expect(result.isValid).toBe(false); L42: expect(result.issues[0].message).toContain( L43: "Computed properties are not allowed" L44: ); L45: }); L46: L47: it("should reject too many properties", () => { L48: const customConfig = createTestConfig({ maxPropertiesPerObject: 1 }); L49: const code = ```text ({ a: 1, b: 2 }) ```text ; L50: const objExpr = parseObjectExpression(code); L51: const result = validateObjectExpression(objExpr, 0, customConfig); L52: expect(result.isValid).toBe(false); L53: expect(result.issues[0].message).toContain( L54: "Object exceeds maximum property count of 1" L55: ); L56: }); L57: L58: it("should reject object exceeding max depth", () => { L59: const customConfig = createTestConfig({ maxObjectDepth: 1 }); L60: const code = ```text ({ ```text L61: nested: { L62: another: "value" L63: } L64: }) ```text ; ```text L65: const objExpr = parseObjectExpression(code); L66: const result = validateObjectExpression(objExpr, 0, customConfig); L67: expect(result.isValid).toBe(false); L68: expect(result.issues[0].message).toContain( L69: "Object exceeds maximum nesting depth" L70: ); L71: }); L72: L73: it("should reject unsafe property names (deniedProperties)", () => { L74: const code = ```text ({ ```text L75: constructor: "test" L76: }) ```text ; ```text L77: const objExpr = parseObjectExpression(code); L78: const result = validateObjectExpression(objExpr, 0, config); L79: expect(result.isValid).toBe(false); L80: expect(result.issues[0].message).toContain( L81: "Property name 'constructor' is not allowed" L82: ); L83: expect(result.issues[0].severity).toBe(IssueSeverity.WARNING); L84: }); L85: L86: it("should reject property name starting with a denied prefix", () => { L87: const customConfig = createTestConfig({ L88: propertySafety: { L89: ...config.propertySafety, L90: deniedPrefixes: ["_"], L91: }, L92: }); L93: const code = ```text ({ ```text L94: _secret: "data" L95: }) ```text ; ```text L96: const objExpr = parseObjectExpression(code); L97: const result = validateObjectExpression(objExpr, 0, customConfig); L98: expect(result.isValid).toBe(false); L99: expect(result.issues[0].message).toContain( L100: "Property name '_secret' uses a forbidden prefix" L101: ); L102: }); L103: }); L104: </tests/object-validator.test.ts> <tests/reporting.test.ts> L1: import { IssueReporter, IssueSeverity } from "../src/reporting"; L2: import { Node } from "@babel/types"; L3: L4: describe("IssueReporter", () => { L5: let reporter: IssueReporter; L6: L7: beforeEach(() => { L8: reporter = new IssueReporter(); L9: }); L10: L11: it("should report issues correctly", () => { L12: const dummyNode: Node = { L13: type: "Identifier", L14: loc: { start: { line: 5, column: 10 } }, L15: } as any; L16: reporter.reportIssue( L17: dummyNode, L18: "Test message", L19: "Identifier", L20: IssueSeverity.WARNING, L21: "Try something else" L22: ); L23: const issues = reporter.getIssues(); L24: expect(issues).toHaveLength(1); L25: expect(issues[0].message).toBe("Test message"); L26: expect(issues[0].line).toBe(5); L27: expect(issues[0].column).toBe(10); L28: expect(issues[0].severity).toBe(IssueSeverity.WARNING); L29: expect(issues[0].suggestion).toBe("Try something else"); L30: }); L31: L32: it("should get formatted report", () => { L33: const dummyNode: Node = { L34: type: "CallExpression", L35: loc: { start: { line: 1, column: 1 } }, L36: } as any; L37: reporter.reportIssue( L38: dummyNode, L39: "Another message", L40: "CallExpression", L41: IssueSeverity.ERROR L42: ); L43: const report = reporter.getFormattedReport(); L44: expect(report).toContain("ERROR: Another message (CallExpression) at 1:1"); L45: }); L46: L47: it("should filter issues by severity", () => { L48: const node: Node = { L49: type: "Literal", L50: loc: { start: { line: 2, column: 3 } }, L51: } as any; L52: reporter.reportIssue(node, "Error issue", "Literal", IssueSeverity.ERROR); L53: reporter.reportIssue( L54: node, L55: "Warning issue", L56: "Literal", L57: IssueSeverity.WARNING L58: ); L59: L60: const errors = reporter.getIssuesBySeverity(IssueSeverity.ERROR); L61: const warnings = reporter.getIssuesBySeverity(IssueSeverity.WARNING); L62: L63: expect(errors).toHaveLength(1); L64: expect(warnings).toHaveLength(1); L65: }); L66: L67: it("should detect errors with hasErrors", () => { L68: const node: Node = { L69: type: "Literal", L70: loc: { start: { line: 2, column: 2 } }, L71: } as any; L72: expect(reporter.hasErrors()).toBe(false); L73: reporter.reportIssue(node, "Some error", "Literal", IssueSeverity.ERROR); L74: expect(reporter.hasErrors()).toBe(true); L75: }); L76: L77: it("should clear issues", () => { L78: const node: Node = { L79: type: "Literal", L80: loc: { start: { line: 3, column: 4 } }, L81: } as any; L82: reporter.reportIssue(node, "Some issue", "Literal", IssueSeverity.ERROR); L83: expect(reporter.getIssues()).toHaveLength(1); L84: reporter.clear(); L85: expect(reporter.getIssues()).toHaveLength(0); L86: }); L87: }); L88: </tests/reporting.test.ts> <tests/resource-manager.test.ts> L1: import { ResourceManager, ValidationError } from "../src/resource-manager"; L2: import { createTestConfig } from "./test-utils"; L3: L4: describe("ResourceManager", () => { L5: let manager: ResourceManager; L6: L7: beforeEach(() => { L8: const config = createTestConfig({ timeoutMs: 100 }); L9: manager = new ResourceManager(config); L10: }); L11: L12: it("should increment node count without exceeding", () => { L13: for (let i = 0; i < 10; i++) { L14: manager.incrementNodeCount(); L15: } L16: const stats = manager.getStats(); L17: expect(stats.nodeCount).toBe(10); L18: }); L19: L20: it("should throw ValidationError when node count exceeded", () => { L21: const config = createTestConfig({ maxNodeCount: 5 }); L22: manager = new ResourceManager(config); L23: L24: expect(() => { L25: for (let i = 0; i < 6; i++) { L26: manager.incrementNodeCount(); L27: } L28: }).toThrowError(ValidationError); L29: }); L30: L31: it("should throw ValidationError when timeout is exceeded", () => { L32: const config = createTestConfig({ timeoutMs: 1 }); L33: manager = new ResourceManager(config); L34: L35: const originalDateNow = Date.now; L36: jest.spyOn(Date, "now").mockImplementation(() => originalDateNow() + 2000); L37: L38: expect(() => manager.checkTimeout()).toThrowError(ValidationError); L39: }); L40: L41: it("should track depth and throw if exceeded", () => { L42: const config = createTestConfig({ maxObjectDepth: 1 }); L43: manager = new ResourceManager(config); L44: L45: // Depth allowed is 1, let's do depth = 2 L46: expect(() => manager.trackDepth(2, "object")).toThrowError(ValidationError); L47: }); L48: L49: it("should validate size and throw if exceeded", () => { L50: expect(() => manager.validateSize(101, 100, "array")).toThrowError( L51: ValidationError L52: ); L53: }); L54: L55: it("should return stats", () => { L56: manager.incrementNodeCount(); L57: const stats = manager.getStats(); L58: expect(stats.nodeCount).toBeGreaterThan(0); L59: expect(stats.executionTime).toBeGreaterThanOrEqual(0); L60: }); L61: }); L62: </tests/resource-manager.test.ts> <tests/schema-validator.test.ts> L1: import { SchemaValidator } from "../src/schema-validator"; L2: import { L3: TestResourceManager, L4: createTestConfig, L5: createSchemaInput, L6: expectValidationIssues, L7: testSchemas, L8: } from "./test-utils"; L9: import { IssueReporter } from "../src/reporting"; L10: L11: describe("SchemaValidator", () => { L12: let resourceManager: TestResourceManager; L13: let issueReporter: IssueReporter; L14: let validator: SchemaValidator; L15: L16: beforeEach(() => { L17: const config = createTestConfig(); L18: resourceManager = new TestResourceManager(config); L19: issueReporter = new IssueReporter(); L20: validator = new SchemaValidator(config, resourceManager, issueReporter); L21: }); L22: L23: it("should validate a simple schema", async () => { L24: const result = await validator.validateSchema( L25: createSchemaInput(testSchemas.basic) L26: ); L27: expect(result.isValid).toBe(true); L28: expect(result.issues).toHaveLength(0); L29: }); L30: L31: it("should handle schemas missing z import", async () => { L32: const code = ```text export const testSchema = z.object({}); ```text ; L33: // Missing import statement L34: const result = await validator.validateSchema(code); L35: expect(result.isValid).toBe(false); L36: expect(result.issues[0].message).toContain("Missing 'z' import from 'zod'"); L37: }); L38: L39: it("should remove invalid imports and report issues", async () => { L40: const code = ```text ```text L41: import { something } from 'somewhere'; L42: import { z } from 'zod'; L43: export const testSchema = z.string(); L44: ```text ; ```text L45: const result = await validator.validateSchema(code); L46: expect(result.isValid).toBe(false); L47: expect(result.issues[0].message).toContain( L48: "Only 'zod' imports are allowed" L49: ); L50: }); L51: L52: it("should reject variable declarations not using const", async () => { L53: const code = ```text ```text L54: import { z } from 'zod'; L55: var testSchema = z.string(); L56: ```text ; ```text L57: const result = await validator.validateSchema(code); L58: expect(result.isValid).toBe(false); L59: expect(result.issues[0].message).toContain( L60: "Schema declarations must use 'const'" L61: ); L62: }); L63: L64: it("should reject schema declaration without initializer", async () => { L65: const code = ```text ```text L66: import { z } from 'zod'; L67: const testSchema = undefined; L68: ```text ; ```text L69: const result = await validator.validateSchema(code); L70: expect(result.isValid).toBe(false); L71: expect(result.issues[0].message).toContain( L72: "Schema declaration must have an initializer" L73: ); L74: }); L75: L76: it("should reject invalid statements", async () => { L77: const code = ```text ```text L78: import { z } from 'zod'; L79: function notAllowed() {} L80: const testSchema = z.string(); L81: ```text ; ```text L82: const result = await validator.validateSchema(code); L83: expect(result.isValid).toBe(false); L84: expect( L85: result.issues.some((issue) => L86: issue.message.includes("Invalid statement type") L87: ) L88: ).toBe(true); L89: }); L90: L91: it("should fail when node count exceeds limit", async () => { L92: resourceManager.setMockNodeCount(1000); L93: const result = await validator.validateSchema( L94: createSchemaInput(testSchemas.complex) L95: ); L96: expect(result.isValid).toBe(false); L97: expectValidationIssues(result.issues, [{ message: "Node count exceeded" }]); L98: }); L99: L100: it("should fail on timeout", async () => { L101: resourceManager.triggerTimeout(); L102: const result = await validator.validateSchema( L103: createSchemaInput(testSchemas.basic) L104: ); L105: expect(result.isValid).toBe(false); L106: expectValidationIssues(result.issues, [{ message: "Timeout triggered" }]); L107: }); L108: L109: it("should generate cleaned code if valid", async () => { L110: const code = ```text ```text L111: import { z } from 'zod'; L112: const userSchema = z.object({ name: z.string() }); L113: ```text ; ```text L114: const result = await validator.validateSchema(code); L115: expect(result.isValid).toBe(true); L116: expect(result.cleanedCode).toContain("z.object({"); L117: expect(result.cleanedCode).toContain("name: z.string()"); L118: }); L119: }); L120: </tests/schema-validator.test.ts> <tests/test-utils.ts> L1: import { ValidationConfig, createConfig, relaxedConfig } from "../src/types"; L2: import { ResourceManager } from "../src/resource-manager"; L3: import { parse } from "@babel/parser"; L4: import { CallExpression } from "@babel/types"; L5: L6: /** L7: * Creates a mock resource manager for testing L8: * Allows controlling timeouts, node counts etc. L9: */ L10: export class TestResourceManager extends ResourceManager { L11: private mockNodeCount = 0; L12: private mockTimeoutTriggered = false; L13: L14: constructor(config: ValidationConfig) { L15: super(config); L16: } L17: L18: public setMockNodeCount(count: number) { L19: this.mockNodeCount = count; L20: } L21: L22: public triggerTimeout() { L23: this.mockTimeoutTriggered = true; L24: } L25: L26: public override incrementNodeCount(): void { L27: if (this.mockTimeoutTriggered) { L28: throw new Error("Timeout triggered"); L29: } L30: if (this.mockNodeCount >= this.config.maxNodeCount) { L31: throw new Error("Node count exceeded"); L32: } L33: this.mockNodeCount++; L34: } L35: } L36: L37: /** L38: * Helper to parse code into a CallExpression AST node L39: */ L40: export function parseCallExpression(code: string): CallExpression { L41: const ast = parse(code); L42: const stmt = ast.program.body[0]; L43: if (stmt.type !== "ExpressionStatement") { L44: throw new Error("Expected expression statement"); L45: } L46: const expr = stmt.expression; L47: if (expr.type !== "CallExpression") { L48: throw new Error("Expected call expression"); L49: } L50: return expr; L51: } L52: L53: /** L54: * Creates a test configuration with specific overrides L55: */ L56: export function createTestConfig( L57: overrides?: Partial<ValidationConfig> L58: ): ValidationConfig { L59: return createConfig(relaxedConfig, { L60: timeoutMs: 1000, L61: maxNodeCount: 100, L62: allowComputedProperties: false, // Explicitly disable computed properties for tests L63: ...overrides, L64: }); L65: } L66: L67: /** L68: * Helper to create schema validation inputs L69: */ L70: export function createSchemaInput(schema: string): string { L71: // Ensure proper formatting and no extra whitespace L72: return ```text import { z } from 'zod';\nexport const testSchema = ${schema}; ```text ; L73: } L74: L75: /** L76: * Verifies that validation produces expected issues L77: */ L78: export function expectValidationIssues( L79: issues: Array<{ message: string; nodeType: string }>, L80: expectedIssues: Array<Partial<{ message: string; nodeType: string }>> L81: ) { L82: // Check that all expected issues are present, in any order L83: expectedIssues.forEach((expected) => { L84: const matchingIssue = issues.find((issue) => { L85: if (expected.message && !issue.message.includes(expected.message)) { L86: return false; L87: } L88: if (expected.nodeType && issue.nodeType !== expected.nodeType) { L89: return false; L90: } L91: return true; L92: }); L93: L94: if (!matchingIssue) { L95: throw new Error( L96: ```text Expected to find issue matching ${JSON.stringify(expected)}\n ```text + L97: ```text Actual issues: ${JSON.stringify(issues, null, 2)} ```text L98: ); L99: } L100: }); L101: } L102: L103: /** L104: * Common test schemas L105: */ L106: export const testSchemas = { L107: basic: ```text z.object({ name: z.string() }) ```text , L108: unsafe: ```text z.object({ constructor: z.function() }) ```text , L109: complex: ```text ```text L110: z.object({ L111: id: z.string().uuid(), L112: data: z.record(z.string(), z.any()), L113: meta: z.object({ L114: created: z.date(), L115: tags: z.array(z.string()) L116: }) L117: }) L118: ```text , ```text L119: }; L120: </tests/test-utils.ts> <tests/data/testSchema1.ts> L1: import { z } from "zod"; L2: L3: let a = 12; L4: L5: const userProfileSchema = z.object({ L6: avatar_hash: z.string(), L7: image_72: z.string().url(), L8: first_name: z.string(), L9: real_name: z.string(), L10: display_name: z.string(), L11: team: z.string(), L12: name: z.string(), L13: is_restricted: z.boolean(), L14: is_ultra_restricted: z.boolean(), L15: }); L16: L17: callFunc(); L18: L19: const richTextSectionElementSchema = z.object({ L20: type: z.literal("text").or(z.literal("link")), L21: text: z.string().optional(), L22: url: z.string().url().optional(), L23: }); L24: L25: const richTextSectionSchema = z.object({ L26: type: z.literal("rich_text_section"), L27: elements: z.array(richTextSectionElementSchema), L28: }); L29: L30: const richTextListSchema = z.object({ L31: type: z.literal("rich_text_list"), L32: elements: z.array(richTextSectionSchema), L33: style: z.literal("bullet"), L34: indent: z.number(), L35: border: z.number(), L36: }); L37: L38: const richTextElementSchema = z.object({ L39: type: z.literal("rich_text"), L40: block_id: z.string(), L41: elements: z.array(richTextSectionSchema.or(richTextListSchema)), L42: }); L43: L44: const videoBlockSchema = z.object({ L45: type: z.literal("video"), L46: block_id: z.string(), L47: video_url: z.string().url(), L48: thumbnail_url: z.string().url(), L49: alt_text: z.string(), L50: title: z.object({ L51: type: z.literal("plain_text"), L52: text: z.string(), L53: emoji: z.boolean(), L54: }), L55: title_url: z.string().url(), L56: author_name: z.string(), L57: provider_name: z.string(), L58: provider_icon_url: z.string().url(), L59: }); L60: L61: const sectionBlockSchema = z.object({ L62: type: z.literal("section"), L63: block_id: z.string(), L64: text: z.object({ L65: type: z.literal("plain_text"), L66: text: z.string(), L67: emoji: z.boolean(), L68: }), L69: }); L70: L71: const attachmentSchema = z.object({ L72: id: z.number(), L73: blocks: z.array(videoBlockSchema.or(sectionBlockSchema)), L74: fallback: z.string(), L75: bot_id: z.string(), L76: app_unfurl_url: z.string().url(), L77: is_app_unfurl: z.boolean(), L78: app_id: z.string(), L79: }); L80: L81: const messageSchema = z.object({ L82: user: z.string(), L83: type: z.literal("message"), L84: ts: z.string(), L85: client_msg_id: z.string(), L86: text: z.string(), L87: team: z.string(), L88: user_team: z.string(), L89: source_team: z.string(), L90: user_profile: userProfileSchema, L91: attachments: z.array(attachmentSchema).optional(), L92: blocks: z.array(richTextElementSchema), L93: edited: z L94: .object({ L95: user: z.string(), L96: ts: z.string(), L97: }) L98: .optional(), L99: thread_ts: z.string().optional(), L100: parent_user_id: z.string().optional(), L101: }); L102: L103: export default messageSchema; L104: </tests/data/testSchema1.ts> <package.json> L1: { L2: "name": "zodsheriff", L3: "version": "0.0.1", L4: "author": "Hrishi Olickel <twitter-@hrishioa> (https://olickel.com)", L5: "repository": { L6: "type": "git", L7: "url": "git+https://github.com/southbridgeai/zodsheriff.git" L8: }, L9: "main": "./dist/index.cjs", L10: "bin": { L11: "zodsheriff": "./dist/run.js" L12: }, L13: "module": "./dist/index.js", L14: "devDependencies": { L15: "@biomejs/biome": "^1.9.4", L16: "@swc/core": "^1.7.26", L17: "@types/bun": "^1.1.10", L18: "@types/jest": "^29.5.14", L19: "@types/node": "^22.7.4", L20: "jest": "^29.7.0", L21: "ts-jest": "^29.2.5", L22: "tsup": "^8.3.0", L23: "typescript": "^5.6.2" L24: }, L25: "exports": { L26: ".": { L27: "import": { L28: "types": "./dist/index.d.mts", L29: "default": "./dist/index.js" L30: }, L31: "require": { L32: "types": "./dist/index.d.cts", L33: "default": "./dist/index.cjs" L34: } L35: } L36: }, L37: "description": "Validation for LLM-generated typescript zod schemas", L38: "files": [ L39: "dist", L40: "package.json" L41: ], L42: "license": "CC By-NC 4.0", L43: "scripts": { L44: "build": "tsup src/index.ts src/run.ts && tsc --emitDeclarationOnly --declaration --declarationDir dist && mv dist/index.d.ts dist/index.d.mts && cp dist/index.d.mts dist/index.d.cts", L45: "test": "jest", L46: "cli": "node dist/run.js" L47: }, L48: "type": "module", L49: "types": "./dist/index.d.cts", L50: "dependencies": { L51: "@babel/generator": "^7.26.3", L52: "@babel/parser": "^7.26.3", L53: "@babel/traverse": "^7.26.4", L54: "clipboardy": "^4.0.0", L55: "safe-regex": "^2.1.1" L56: } L57: } L58: </package.json> evaluate this code for me. I have this code that's been written with the intent of parsing unsafe code that contains schemas, and to remove offending lines and return a clean schema. We want to prevent arbitrary code execution within reason, while keeping comments and reporting issues as we go. Critique the code for unusued things, potential improvements, edge cases, issues, etc and ease of use in detail.