import * as ts from 'typescript' ; import { evaluate as evaluateExpression , partiallyEvaluate as partiallyEvaluateExpression , createScope , } from '../evaluator' ; export function evaluate ( expression , files = { } , globals = { } ) { return evaluateInternal ( undefined , expression , files , globals ) ; } export function partiallyEvaluate ( expression , shouldEvaluate , files = { } , globals = { } ) { return evaluateInternal ( shouldEvaluate , expression , files , globals ) ; } function evaluateInternal ( shouldEvaluate , expression , files = { } , globals = { } ) { const outputs = { } ; const rand = Math . random ( ) . toString ( ) . replace ( /[^0-9]+/g , '' ) ; files [ 'entry.ts' ] += '\nconst expressionToBeEvaluated' + rand + ' = ' + expression + ';' ; let result ; function transformer ( program ) { return ( context ) => ( file ) => { return visitNodeAndChildren ( file , program , context ) ; } ; } function visitNodeAndChildren ( node , program , context ) { return ts . visitEachChild ( visitNode ( node , program ) , childNode => visitNodeAndChildren ( childNode , program , context ) , context ) ; } function visitNode ( node , program ) { if ( ts . isVariableDeclaration ( node ) ) { if ( ts . isIdentifier ( node . name ) && node . initializer && node . name . text === 'expressionToBeEvaluated' + rand ) { if ( shouldEvaluate === undefined ) { result = evaluateExpression ( node . initializer , program , createScope ( undefined , globals ) ) ; } else { result = partiallyEvaluateExpression ( node . initializer , shouldEvaluate , program , createScope ( undefined , globals ) ) ; } } } return node ; } const compilerOptions = { noEmitOnError : true , target : ts . ScriptTarget . Latest , moduleResolution : ts . ModuleResolutionKind . NodeJs , lib : [ 'lib.es2018.d.ts' , 'lib.dom.d.ts' ] , types : [ ] , jsx : ts . JsxEmit . Preserve , } ; const compilerHost = ts . createCompilerHost ( compilerOptions ) ; const customCompilerHost = { ... compilerHost , resolveModuleNames ( moduleNames , containingFile , _ , __ , options ) { const resolvedModules = [ ] ; for ( const moduleName of moduleNames ) { const localFileName = `${ moduleName . slice ( 2 ) }.ts` ; if ( localFileName in files ) { resolvedModules . push ( { resolvedFileName : localFileName } ) ; continue ; } const resolvedModule = ts . resolveModuleName ( moduleName , containingFile , options , { fileExists ( fileName ) { return ts . sys . fileExists ( fileName ) ; } , readFile ( fileName ) { return ts . sys . readFile ( fileName ) ; } , } ) ; if ( resolvedModule . resolvedModule ) { resolvedModules . push ( resolvedModule . resolvedModule ) ; } } return resolvedModules ; } , getSourceFile ( fileName , languageVersion , onError , shouldCreateNewSourceFile ) { if ( fileName in files ) { return ts . createSourceFile ( fileName , files [ fileName ] , ts . ScriptTarget . Latest ) ; } const sourceFile = compilerHost . getSourceFile ( fileName , languageVersion , onError , shouldCreateNewSourceFile ) ; if ( ! sourceFile ) { console . log ( 'TS asked for file' , fileName , 'but that was not passed in to the compile function' ) ; } return sourceFile ; } , writeFile ( fileName , data ) { outputs [ fileName ] = data ; } , } ; const program = ts . createProgram ( Object . keys ( files ) , compilerOptions , customCompilerHost ) ; const transformers = { before : [ transformer ( program ) ] , after : [ ] , } ; const writeFileCallback = ( fileName , data ) => { outputs [ fileName ] = data ; } ; const { emitSkipped , diagnostics } = program . emit ( undefined , writeFileCallback , undefined , false , transformers ) ; if ( emitSkipped ) { throw new Error ( diagnostics . map ( diagnostic => diagnostic . messageText ) . join ( '\n' ) ) ; } return result ; }