@@ -4,23 +4,6 @@ namespace evaluator {
4
4
const sourceFile = vpath . combine ( vfs . srcFolder , "source.ts" ) ;
5
5
const sourceFileJs = vpath . combine ( vfs . srcFolder , "source.js" ) ;
6
6
7
- function compile ( sourceText : string , options ?: ts . CompilerOptions ) {
8
- const fs = vfs . createFromFileSystem ( Harness . IO , /*ignoreCase*/ false ) ;
9
- fs . writeFileSync ( sourceFile , sourceText ) ;
10
- const compilerOptions : ts . CompilerOptions = {
11
- target : ts . ScriptTarget . ES5 ,
12
- module : ts . ModuleKind . CommonJS ,
13
- lib : [ "lib.esnext.d.ts" , "lib.dom.d.ts" ] ,
14
- ...options
15
- } ;
16
- const host = new fakes . CompilerHost ( fs , compilerOptions ) ;
17
- return compiler . compileFiles ( host , [ sourceFile ] , compilerOptions ) ;
18
- }
19
-
20
- function noRequire ( id : string ) {
21
- throw new Error ( `Module '${ id } ' could not be found.` ) ;
22
- }
23
-
24
7
// Define a custom "Symbol" constructor to attach missing built-in symbols without
25
8
// modifying the global "Symbol" constructor
26
9
const FakeSymbol : SymbolConstructor = ( ( description ?: string ) => Symbol ( description ) ) as any ;
@@ -32,8 +15,17 @@ namespace evaluator {
32
15
// Add "asyncIterator" if missing
33
16
if ( ! ts . hasProperty ( FakeSymbol , "asyncIterator" ) ) Object . defineProperty ( FakeSymbol , "asyncIterator" , { value : Symbol . for ( "Symbol.asyncIterator" ) , configurable : true } ) ;
34
17
35
- export function evaluateTypeScript ( sourceText : string , options ?: ts . CompilerOptions , globals ?: Record < string , any > ) {
36
- const result = compile ( sourceText , options ) ;
18
+ export function evaluateTypeScript ( source : string | { files : vfs . FileSet , rootFiles : string [ ] , main : string } , options ?: ts . CompilerOptions , globals ?: Record < string , any > ) {
19
+ if ( typeof source === "string" ) source = { files : { [ sourceFile ] : source } , rootFiles : [ sourceFile ] , main : sourceFile } ;
20
+ const fs = vfs . createFromFileSystem ( Harness . IO , /*ignoreCase*/ false , { files : source . files } ) ;
21
+ const compilerOptions : ts . CompilerOptions = {
22
+ target : ts . ScriptTarget . ES5 ,
23
+ module : ts . ModuleKind . CommonJS ,
24
+ lib : [ "lib.esnext.d.ts" , "lib.dom.d.ts" ] ,
25
+ ...options
26
+ } ;
27
+ const host = new fakes . CompilerHost ( fs , compilerOptions ) ;
28
+ const result = compiler . compileFiles ( host , source . rootFiles , compilerOptions ) ;
37
29
if ( ts . some ( result . diagnostics ) ) {
38
30
assert . ok ( /*value*/ false , "Syntax error in evaluation source text:\n" + ts . formatDiagnostics ( result . diagnostics , {
39
31
getCanonicalFileName : file => file ,
@@ -42,29 +34,100 @@ namespace evaluator {
42
34
} ) ) ;
43
35
}
44
36
45
- const output = result . getOutput ( sourceFile , "js" ) ! ;
37
+ const output = result . getOutput ( source . main , "js" ) ! ;
46
38
assert . isDefined ( output ) ;
47
39
48
- return evaluateJavaScript ( output . text , globals , output . file ) ;
40
+ globals = { Symbol : FakeSymbol , ...globals } ;
41
+ return createLoader ( fs , globals ) ( output . file ) ;
49
42
}
50
43
51
- export function evaluateJavaScript ( sourceText : string , globals ?: Record < string , any > , sourceFile = sourceFileJs ) {
52
- globals = { Symbol : FakeSymbol , ...globals } ;
44
+ function createLoader ( fs : vfs . FileSystem , globals : Record < string , any > ) {
45
+ interface Module {
46
+ exports : any ;
47
+ }
53
48
54
- const globalNames : string [ ] = [ ] ;
55
- const globalArgs : any [ ] = [ ] ;
56
- for ( const name in globals ) {
57
- if ( ts . hasProperty ( globals , name ) ) {
58
- globalNames . push ( name ) ;
59
- globalArgs . push ( globals [ name ] ) ;
49
+ const moduleCache = new ts . Map < string , Module > ( ) ;
50
+ return load ;
51
+
52
+ function evaluate ( text : string , file : string , module : Module ) {
53
+ const globalNames : string [ ] = [ ] ;
54
+ const globalArgs : any [ ] = [ ] ;
55
+ for ( const name in globals ) {
56
+ if ( ts . hasProperty ( globals , name ) ) {
57
+ globalNames . push ( name ) ;
58
+ globalArgs . push ( globals [ name ] ) ;
59
+ }
60
+ }
61
+ const base = vpath . dirname ( file ) ;
62
+ const localRequire = ( id : string ) => requireModule ( id , base ) ;
63
+ const evaluateText = `(function (module, exports, require, __dirname, __filename, ${ globalNames . join ( ", " ) } ) { ${ text } })` ;
64
+ // eslint-disable-next-line no-eval
65
+ const evaluateThunk = ( void 0 , eval ) ( evaluateText ) as ( module : any , exports : any , require : ( id : string ) => any , dirname : string , filename : string , ...globalArgs : any [ ] ) => void ;
66
+ evaluateThunk . call ( globals , module , module . exports , localRequire , vpath . dirname ( file ) , file , FakeSymbol , ...globalArgs ) ;
67
+ }
68
+
69
+ function loadModule ( file : string ) : Module {
70
+ if ( ! ts . isExternalModuleNameRelative ( file ) ) throw new Error ( `Module '${ file } ' could not be found.` ) ;
71
+ let module = moduleCache . get ( file ) ;
72
+ if ( module ) return module ;
73
+ moduleCache . set ( file , module = { exports : { } } ) ;
74
+ try {
75
+ const sourceText = fs . readFileSync ( file , "utf8" ) ;
76
+ evaluate ( sourceText , file , module ) ;
77
+ return module ;
78
+ }
79
+ catch ( e ) {
80
+ moduleCache . delete ( file ) ;
81
+ throw e ;
60
82
}
61
83
}
62
84
63
- const evaluateText = `(function (module, exports, require, __dirname, __filename, ${ globalNames . join ( ", " ) } ) { ${ sourceText } })` ;
64
- // eslint-disable-next-line no-eval
65
- const evaluateThunk = ( void 0 , eval ) ( evaluateText ) as ( module : any , exports : any , require : ( id : string ) => any , dirname : string , filename : string , ...globalArgs : any [ ] ) => void ;
66
- const module : { exports : any ; } = { exports : { } } ;
67
- evaluateThunk . call ( globals , module , module . exports , noRequire , vpath . dirname ( sourceFile ) , sourceFile , FakeSymbol , ...globalArgs ) ;
68
- return module . exports ;
85
+ function isFile ( file : string ) {
86
+ return fs . existsSync ( file ) && fs . statSync ( file ) . isFile ( ) ;
87
+ }
88
+
89
+ function loadAsFile ( file : string ) : Module | undefined {
90
+ if ( isFile ( file ) ) return loadModule ( file ) ;
91
+ if ( isFile ( file + ".js" ) ) return loadModule ( file + ".js" ) ;
92
+ return undefined ;
93
+ }
94
+
95
+ function loadIndex ( dir : string ) : Module | undefined {
96
+ const indexFile = vpath . resolve ( dir , "index.js" ) ;
97
+ if ( isFile ( indexFile ) ) return loadModule ( indexFile ) ;
98
+ return undefined ;
99
+ }
100
+
101
+ function loadAsDirectory ( dir : string ) : Module | undefined {
102
+ const packageFile = vpath . resolve ( dir , "package.json" ) ;
103
+ if ( isFile ( packageFile ) ) {
104
+ const text = fs . readFileSync ( packageFile , "utf8" ) ;
105
+ const json = JSON . parse ( text ) ;
106
+ if ( json . main ) {
107
+ const main = vpath . resolve ( dir , json . main ) ;
108
+ const result = loadAsFile ( main ) || loadIndex ( main ) ;
109
+ if ( result === undefined ) throw new Error ( "Module not found" ) ;
110
+ }
111
+ }
112
+ return loadIndex ( dir ) ;
113
+ }
114
+
115
+ function requireModule ( id : string , base : string ) {
116
+ if ( ! ts . isExternalModuleNameRelative ( id ) ) throw new Error ( `Module '${ id } ' could not be found.` ) ;
117
+ const file = vpath . resolve ( base , id ) ;
118
+ const module = loadAsFile ( file ) || loadAsDirectory ( file ) ;
119
+ if ( ! module ) throw new Error ( `Module '${ id } ' could not be found.` ) ;
120
+ return module . exports ;
121
+ }
122
+
123
+ function load ( file : string ) {
124
+ return requireModule ( file , fs . cwd ( ) ) ;
125
+ }
126
+ }
127
+
128
+ export function evaluateJavaScript ( sourceText : string , globals ?: Record < string , any > , sourceFile = sourceFileJs ) {
129
+ globals = { Symbol : FakeSymbol , ...globals } ;
130
+ const fs = new vfs . FileSystem ( /*ignoreCase*/ false , { files : { [ sourceFile ] : sourceText } } ) ;
131
+ return createLoader ( fs , globals ) ( sourceFile ) ;
69
132
}
70
133
}
0 commit comments