import ts , { SignatureKind } from "typescript" ; import { TypeModel , typeVisitor , TypeModelObject } from "ts-type-visitor" ; import { createSuperStructDotSuperstructPropAccess , createSuperStructValidatorForm } from "./validator" ; const flatten = < T > ( arr ) => arr . reduce ( ( acc , curr ) => [ ... acc , ... curr ] , [ ] ) ; const createSuperStructValidator = ( typeModel , functionName , strictNullChecks , customValidators ? ) => { const customValidatorsObjectLiteralProps = [ ] ; if ( customValidators && customValidators . size > 0 ) { customValidators ?. forEach ( funcName => { customValidatorsObjectLiteralProps . push ( ts . createPropertyAssignment ( funcName , ts . createIdentifier ( funcName ) ) ) ; } ) ; } const superstructStructVariable = ts . createVariableStatement ( undefined , [ ts . createVariableDeclaration ( "struct" , undefined , ts . createCall ( createSuperStructDotSuperstructPropAccess ( ) , undefined , [ ts . createObjectLiteral ( [ ts . createPropertyAssignment ( "types" , ts . createObjectLiteral ( customValidatorsObjectLiteralProps ) ) ] ) ] ) ) ] ) ; const superstructValidator = ts . createCall ( ts . createIdentifier ( "struct" ) , undefined , [ createSuperStructValidatorForm ( typeModel , false , strictNullChecks , customValidators ) ] ) ; const superstructValidatorVariable = ts . createVariableStatement ( undefined , [ ts . createVariableDeclaration ( "validator" , undefined , superstructValidator ) ] ) ; const validatorCall = ts . createCall ( ts . createIdentifier ( "validator" ) , undefined , [ ts . createIdentifier ( "jsonObj" ) ] ) ; const body = ts . createBlock ( [ superstructStructVariable , superstructValidatorVariable , ts . createReturn ( validatorCall ) ] , true ) ; const validateFunc = ts . createFunctionDeclaration ( undefined , undefined , undefined , functionName , undefined , [ ts . createParameter ( undefined , undefined , undefined , "jsonObj" , undefined , undefined , undefined ) ] , undefined , body ) ; return validateFunc ; } ; type CallToImplement = { typeModel : TypeModel ; functionName : string ; customValidators : Map < ts . Type , string > ; } ; function isOurModule ( moduleName ) { if ( process . env . SUPERSTRUCT_TS_TRANSFORMER_ENV === "debug" ) { return moduleName == "../index" ; } return moduleName == "superstruct-ts-transformer" ; } const createVisitor = ( ctx , sourceFile , checker ) => { const typeModels = new Map < ts . SourceFile , CallToImplement [ ] > ( ) ; const visitor = ( node ) => { const pass = ( ) => ts . visitEachChild ( node , visitor , ctx ) ; if ( ts . isImportDeclaration ( node ) && ts . isStringLiteral ( node . moduleSpecifier ) && isOurModule ( node . moduleSpecifier . text ) ) { const moduleTarget = ctx . getCompilerOptions ( ) . module ; if ( moduleTarget === ts . ModuleKind . CommonJS ) { return ts . createVariableStatement ( [ ts . createModifier ( ts . SyntaxKind . ConstKeyword ) ] , [ ts . createVariableDeclaration ( "superstruct" , undefined , ts . createCall ( ts . createIdentifier ( "require" ) , undefined , [ ts . createLiteral ( "superstruct" ) ] ) ) ] ) ; } else if ( moduleTarget === ts . ModuleKind . ES2015 || moduleTarget === ts . ModuleKind . ESNext ) { const superstructStructImportClause = ts . createImportClause ( undefined , ts . createNamespaceImport ( ts . createIdentifier ( "superstruct" ) ) ) ; return ts . createImportDeclaration ( undefined , node . modifiers , superstructStructImportClause , ts . createStringLiteral ( "superstruct" ) ) ; } else { throw new Error ( "superstruct-ts-transformer doesn't support module targets other than CommonJS and ES2015+" ) ; } } if ( ts . isSourceFile ( node ) ) { const newFileNode = ts . visitEachChild ( node , visitor , ctx ) ; const options = ctx . getCompilerOptions ( ) ; const newValidators = flatten ( Array . from ( typeModels . values ( ) ) . map ( callsToImplement => callsToImplement . map ( callToImplement => createSuperStructValidator ( callToImplement . typeModel , callToImplement . functionName , options . strict || options . strictNullChecks || false , callToImplement . customValidators ) ) ) ) ; const fileNodeWithValidators = ts . updateSourceFileNode ( newFileNode , [ ... newFileNode . statements , ... newValidators ] ) ; return fileNodeWithValidators ; } if ( ts . isCallExpression ( node ) && ts . isIdentifier ( node . expression ) && node . typeArguments && node . typeArguments . length > 0 && node . arguments . length > 0 ) { const sym = checker . getSymbolAtLocation ( node . expression ) ; if ( ! ! sym && sym . declarations . some ( decl => ts . isNamedImports ( decl . parent ) && ts . isImportClause ( decl . parent . parent ) && ts . isImportDeclaration ( decl . parent . parent . parent ) && ts . isStringLiteral ( decl . parent . parent . parent . moduleSpecifier ) && isOurModule ( decl . parent . parent . parent . moduleSpecifier . text ) ) ) { const typeToValidateAgainst = checker . getTypeFromTypeNode ( node . typeArguments [ 0 ] ) ; const typeModel = typeVisitor ( checker , typeToValidateAgainst ) ; const typeToValidateAgainstStr = checker . typeToString ( typeToValidateAgainst ) . replace ( /\[/g , "ARRAY_" ) . replace ( /\]/g , "_ENDARRAY" ) . replace ( /\s/g , "_" ) . replace ( /[\,]/g , "" ) ; const functionName = node . expression . text ; const newFunctionName = `${ functionName }_${ typeToValidateAgainstStr }` ; let customValidators = new Map < ts . Type , string > ( ) ; const customValidatorsArg = node . arguments [ 1 ] ; if ( customValidatorsArg && ts . isArrayLiteralExpression ( customValidatorsArg ) ) { customValidatorsArg . elements . forEach ( element => { const sym = checker . getSymbolAtLocation ( element ) ; const type = checker . getTypeAtLocation ( element ) ; const sigs = checker . getSignaturesOfType ( type , SignatureKind . Call ) ; const decl = sigs [ 0 ] ?. declaration ; const typeNode = decl ?. type ; const typeNeeded = typeNode && ts . isTypeNode ( typeNode ) ? checker . getTypeFromTypeNode ( typeNode ) : undefined ; if ( typeNode && ts . isTypePredicateNode ( typeNode ) && typeNode . type ) { customValidators . set ( checker . getTypeFromTypeNode ( typeNode . type ) , element . getText ( ) ) ; } } ) ; } const newCallToImplement = { typeModel , functionName : newFunctionName , customValidators } ; if ( typeModels . has ( sourceFile ) ) { typeModels . set ( sourceFile , [ ... typeModels . get ( sourceFile ) ! , newCallToImplement ] ) ; } else { typeModels . set ( sourceFile , [ newCallToImplement ] ) ; } return ts . createCall ( ts . createIdentifier ( newFunctionName ) , undefined , node . arguments . slice ( 0 , 1 ) ) ; } } return pass ( ) ; } ; return visitor ; } ; export const createValidatorTransformer = ( program ) => ( ctx ) : ts . Transformer < ts . SourceFile > => ( sf ) => ts . visitNode ( sf , createVisitor ( ctx , sf , program . getTypeChecker ( ) ) ) ;