@@ -3,54 +3,90 @@ import { homedir } from 'os'
3
3
import { join } from 'path'
4
4
import { Recoverable , start } from 'repl'
5
5
import { Script } from 'vm'
6
- import { Register , TSError } from './index'
6
+ import { Register , CreateOptions , TSError } from './index'
7
+ import { readFileSync , statSync } from 'fs'
7
8
8
9
/**
9
10
* Eval filename for REPL/debug.
11
+ * @internal
10
12
*/
11
13
export const EVAL_FILENAME = `[eval].ts`
12
14
13
- /**
14
- * @param service - TypeScript compiler instance
15
- * @param state - Eval state management
16
- *
17
- * @returns an evaluator for the node REPL
18
- */
19
- export function createReplService (
20
- service : Register ,
21
- state : EvalState = new EvalState ( join ( process . cwd ( ) , EVAL_FILENAME ) )
22
- ) {
23
- return {
24
- /**
25
- * Eval code from the REPL.
26
- */
27
- eval : function ( code : string , _context : any , _filename : string , callback : ( err : Error | null , result ?: any ) => any ) {
28
- let err : Error | null = null
29
- let result : any
30
-
31
- // TODO: Figure out how to handle completion here.
32
- if ( code === '.scope' ) {
33
- callback ( err )
34
- return
35
- }
15
+ export interface ReplService {
16
+ readonly state : EvalState
17
+ setService ( service : Register ) : void
18
+ evalCode ( code : string ) : void
19
+ /**
20
+ * eval implementation compatible with node's REPL API
21
+ */
22
+ nodeReplEval ( code : string , _context : any , _filename : string , callback : ( err : Error | null , result ?: any ) => any ) : void
23
+ evalStateAwareHostFunctions : EvalStateAwareHostFunctions
24
+ /** Start a node REPL */
25
+ start ( code ?: string ) : void
26
+ }
27
+
28
+ export interface CreateReplServiceOptions {
29
+ service ?: Register
30
+ state ?: EvalState
31
+ }
32
+
33
+ export function createReplService ( options : CreateReplServiceOptions = { } ) {
34
+ let service = options . service
35
+ const state = options . state ?? new EvalState ( join ( process . cwd ( ) , EVAL_FILENAME ) )
36
+ const evalStateAwareHostFunctions = createEvalStateAwareHostFunctions ( state )
37
+
38
+ const replService : ReplService = {
39
+ state : options . state ?? new EvalState ( join ( process . cwd ( ) , EVAL_FILENAME ) ) ,
40
+ setService,
41
+ evalCode,
42
+ nodeReplEval,
43
+ evalStateAwareHostFunctions,
44
+ start
45
+ }
46
+ return replService
47
+
48
+ function setService ( _service : Register ) {
49
+ service = _service
50
+ }
51
+
52
+ function evalCode ( code : string ) {
53
+ return _eval ( service ! , state , code )
54
+ }
55
+
56
+ /**
57
+ * Eval code from the REPL.
58
+ */
59
+ function nodeReplEval ( code : string , _context : any , _filename : string , callback : ( err : Error | null , result ?: any ) => any ) {
60
+ let err : Error | null = null
61
+ let result : any
62
+
63
+ // TODO: Figure out how to handle completion here.
64
+ if ( code === '.scope' ) {
65
+ callback ( err )
66
+ return
67
+ }
36
68
37
- try {
38
- result = _eval ( service , state , code )
39
- } catch ( error ) {
40
- if ( error instanceof TSError ) {
41
- // Support recoverable compilations using >= node 6.
42
- if ( Recoverable && isRecoverable ( error ) ) {
43
- err = new Recoverable ( error )
44
- } else {
45
- console . error ( error )
46
- }
69
+ try {
70
+ result = evalCode ( code )
71
+ } catch ( error ) {
72
+ if ( error instanceof TSError ) {
73
+ // Support recoverable compilations using >= node 6.
74
+ if ( Recoverable && isRecoverable ( error ) ) {
75
+ err = new Recoverable ( error )
47
76
} else {
48
- err = error
77
+ console . error ( error )
49
78
}
79
+ } else {
80
+ err = error
50
81
}
51
-
52
- return callback ( err , result )
53
82
}
83
+
84
+ return callback ( err , result )
85
+ }
86
+
87
+ function start ( code ?: string ) {
88
+ // TODO assert that service is set; remove all ! postfixes
89
+ return startRepl ( replService , service ! , state , code )
54
90
}
55
91
}
56
92
@@ -63,13 +99,36 @@ export class EvalState {
63
99
version = 0
64
100
lines = 0
65
101
66
- constructor ( public path : string ) { }
102
+ constructor ( public path : string ) { }
103
+ }
104
+
105
+ export type EvalStateAwareHostFunctions = Pick < CreateOptions , 'readFile' | 'fileExists' >
106
+
107
+ export function createEvalStateAwareHostFunctions ( state : EvalState ) : EvalStateAwareHostFunctions {
108
+ function readFile ( path : string ) {
109
+ if ( path === state . path ) return state . input
110
+
111
+ try {
112
+ return readFileSync ( path , 'utf8' )
113
+ } catch ( err ) { /* Ignore. */ }
114
+ }
115
+ function fileExists ( path : string ) {
116
+ if ( path === state . path ) return true
117
+
118
+ try {
119
+ const stats = statSync ( path )
120
+ return stats . isFile ( ) || stats . isFIFO ( )
121
+ } catch ( err ) {
122
+ return false
123
+ }
124
+ }
125
+ return { readFile, fileExists }
67
126
}
68
127
69
128
/**
70
129
* Evaluate the code snippet.
71
130
*/
72
- export function _eval ( service : Register , state : EvalState , input : string ) {
131
+ function _eval ( service : Register , state : EvalState , input : string ) {
73
132
const lines = state . lines
74
133
const isCompletion = ! / \n $ / . test ( input )
75
134
const undo = appendEval ( state , input )
@@ -99,7 +158,7 @@ export function _eval (service: Register, state: EvalState, input: string) {
99
158
/**
100
159
* Execute some code.
101
160
*/
102
- export function exec ( code : string , filename : string ) {
161
+ function exec ( code : string , filename : string ) {
103
162
const script = new Script ( code , { filename : filename } )
104
163
105
164
return script . runInThisContext ( )
@@ -108,11 +167,11 @@ export function exec (code: string, filename: string) {
108
167
/**
109
168
* Start a CLI REPL.
110
169
*/
111
- export function startRepl ( service : Register , state : EvalState , code ?: string ) {
170
+ function startRepl ( replService : ReplService , service : Register , state : EvalState , code ?: string ) {
112
171
// Eval incoming code before the REPL starts.
113
172
if ( code ) {
114
173
try {
115
- _eval ( service , state , `${ code } \n` )
174
+ replService . evalCode ( `${ code } \n` )
116
175
} catch ( err ) {
117
176
console . error ( err )
118
177
process . exit ( 1 )
@@ -125,7 +184,7 @@ export function startRepl (service: Register, state: EvalState, code?: string) {
125
184
output : process . stdout ,
126
185
// Mimicking node's REPL implementation: https://github.com/nodejs/node/blob/168b22ba073ee1cbf8d0bcb4ded7ff3099335d04/lib/internal/repl.js#L28-L30
127
186
terminal : process . stdout . isTTY && ! parseInt ( process . env . NODE_NO_READLINE ! , 10 ) ,
128
- eval : createReplService ( service , state ) . eval ,
187
+ eval : replService . nodeReplEval ,
129
188
useGlobal : true
130
189
} )
131
190
0 commit comments