1
1
#!/usr/bin/env node
2
2
3
3
import { join , resolve , dirname } from 'path'
4
- import { start , Recoverable } from 'repl'
5
4
import { inspect } from 'util'
6
5
import Module = require ( 'module' )
7
6
import arg = require ( 'arg' )
8
- import { diffLines } from 'diff'
9
- import { Script } from 'vm'
10
- import { readFileSync , statSync , realpathSync } from 'fs'
11
- import { homedir } from 'os'
12
- import { VERSION , TSError , parse , Service , register } from './index'
13
-
14
- /**
15
- * Eval filename for REPL/debug.
16
- */
17
- const EVAL_FILENAME = `[eval].ts`
18
-
19
- /**
20
- * Eval state management.
21
- */
22
- class EvalState {
23
- input = ''
24
- output = ''
25
- version = 0
26
- lines = 0
27
-
28
- constructor ( public path : string ) { }
29
- }
7
+ import {
8
+ EVAL_FILENAME ,
9
+ EvalState ,
10
+ createRepl ,
11
+ ReplService
12
+ } from './repl'
13
+ import { VERSION , TSError , parse , register } from './index'
30
14
31
15
/**
32
16
* Main `bin` functionality.
@@ -160,6 +144,8 @@ export function main (argv: string[] = process.argv.slice(2), entrypointArgs: Re
160
144
/** Unresolved. May point to a symlink, not realpath. May be missing file extension */
161
145
const scriptPath = args . _ . length ? resolve ( cwd , args . _ [ 0 ] ) : undefined
162
146
const state = new EvalState ( scriptPath || join ( cwd , EVAL_FILENAME ) )
147
+ const replService = createRepl ( { state } )
148
+ const { evalAwarePartialHost } = replService
163
149
164
150
// Register the TypeScript compiler instance.
165
151
const service = register ( {
@@ -180,29 +166,13 @@ export function main (argv: string[] = process.argv.slice(2), entrypointArgs: Re
180
166
ignoreDiagnostics,
181
167
compilerOptions,
182
168
require : argsRequire ,
183
- readFile : code !== undefined
184
- ? ( path : string ) => {
185
- if ( path === state . path ) return state . input
186
-
187
- try {
188
- return readFileSync ( path , 'utf8' )
189
- } catch ( err ) { /* Ignore. */ }
190
- }
191
- : undefined ,
192
- fileExists : code !== undefined
193
- ? ( path : string ) => {
194
- if ( path === state . path ) return true
195
-
196
- try {
197
- const stats = statSync ( path )
198
- return stats . isFile ( ) || stats . isFIFO ( )
199
- } catch ( err ) {
200
- return false
201
- }
202
- }
203
- : undefined
169
+ readFile : code !== undefined ? evalAwarePartialHost . readFile : undefined ,
170
+ fileExists : code !== undefined ? evalAwarePartialHost . fileExists : undefined
204
171
} )
205
172
173
+ // Bind REPL service to ts-node compiler service (chicken-and-egg problem)
174
+ replService . setService ( service )
175
+
206
176
// Output project information.
207
177
if ( version >= 2 ) {
208
178
console . log ( `ts-node v${ VERSION } ` )
@@ -222,19 +192,19 @@ export function main (argv: string[] = process.argv.slice(2), entrypointArgs: Re
222
192
223
193
// Execute the main contents (either eval, script or piped).
224
194
if ( code !== undefined && ! interactive ) {
225
- evalAndExit ( service , state , module , code , print )
195
+ evalAndExit ( replService , module , code , print )
226
196
} else {
227
197
if ( args . _ . length ) {
228
198
Module . runMain ( )
229
199
} else {
230
200
// Piping of execution _only_ occurs when no other script is specified.
231
201
// --interactive flag forces REPL
232
202
if ( interactive || process . stdin . isTTY ) {
233
- startRepl ( service , state , code )
203
+ replService . start ( code )
234
204
} else {
235
205
let buffer = code || ''
236
206
process . stdin . on ( 'data' , ( chunk : Buffer ) => buffer += chunk )
237
- process . stdin . on ( 'end' , ( ) => evalAndExit ( service , state , module , buffer , print ) )
207
+ process . stdin . on ( 'end' , ( ) => evalAndExit ( replService , module , buffer , print ) )
238
208
}
239
209
}
240
210
}
@@ -284,7 +254,7 @@ function getCwd (dir?: string, scriptMode?: boolean, scriptPath?: string) {
284
254
/**
285
255
* Evaluate a script.
286
256
*/
287
- function evalAndExit ( service : Service , state : EvalState , module : Module , code : string , isPrinted : boolean ) {
257
+ function evalAndExit ( replService : ReplService , module : Module , code : string , isPrinted : boolean ) {
288
258
let result : any
289
259
290
260
; ( global as any ) . __filename = module . filename
@@ -294,7 +264,7 @@ function evalAndExit (service: Service, state: EvalState, module: Module, code:
294
264
; ( global as any ) . require = module . require . bind ( module )
295
265
296
266
try {
297
- result = _eval ( service , state , code )
267
+ result = replService . evalCode ( code )
298
268
} catch ( error ) {
299
269
if ( error instanceof TSError ) {
300
270
console . error ( error )
@@ -309,203 +279,6 @@ function evalAndExit (service: Service, state: EvalState, module: Module, code:
309
279
}
310
280
}
311
281
312
- /**
313
- * Evaluate the code snippet.
314
- */
315
- function _eval ( service : Service , state : EvalState , input : string ) {
316
- const lines = state . lines
317
- const isCompletion = ! / \n $ / . test ( input )
318
- const undo = appendEval ( state , input )
319
- let output : string
320
-
321
- try {
322
- output = service . compile ( state . input , state . path , - lines )
323
- } catch ( err ) {
324
- undo ( )
325
- throw err
326
- }
327
-
328
- // Use `diff` to check for new JavaScript to execute.
329
- const changes = diffLines ( state . output , output )
330
-
331
- if ( isCompletion ) {
332
- undo ( )
333
- } else {
334
- state . output = output
335
- }
336
-
337
- return changes . reduce ( ( result , change ) => {
338
- return change . added ? exec ( change . value , state . path ) : result
339
- } , undefined )
340
- }
341
-
342
- /**
343
- * Execute some code.
344
- */
345
- function exec ( code : string , filename : string ) {
346
- const script = new Script ( code , { filename : filename } )
347
-
348
- return script . runInThisContext ( )
349
- }
350
-
351
- /**
352
- * Start a CLI REPL.
353
- */
354
- function startRepl ( service : Service , state : EvalState , code ?: string ) {
355
- // Eval incoming code before the REPL starts.
356
- if ( code ) {
357
- try {
358
- _eval ( service , state , `${ code } \n` )
359
- } catch ( err ) {
360
- console . error ( err )
361
- process . exit ( 1 )
362
- }
363
- }
364
-
365
- const repl = start ( {
366
- prompt : '> ' ,
367
- input : process . stdin ,
368
- output : process . stdout ,
369
- // Mimicking node's REPL implementation: https://github.com/nodejs/node/blob/168b22ba073ee1cbf8d0bcb4ded7ff3099335d04/lib/internal/repl.js#L28-L30
370
- terminal : process . stdout . isTTY && ! parseInt ( process . env . NODE_NO_READLINE ! , 10 ) ,
371
- eval : replEval ,
372
- useGlobal : true
373
- } )
374
-
375
- /**
376
- * Eval code from the REPL.
377
- */
378
- function replEval ( code : string , _context : any , _filename : string , callback : ( err : Error | null , result ? : any ) = > any ) {
379
- let err : Error | null = null
380
- let result : any
381
-
382
- // TODO: Figure out how to handle completion here.
383
- if ( code === '.scope' ) {
384
- callback ( err )
385
- return
386
- }
387
-
388
- try {
389
- result = _eval ( service , state , code )
390
- } catch ( error ) {
391
- if ( error instanceof TSError ) {
392
- // Support recoverable compilations using >= node 6.
393
- if ( Recoverable && isRecoverable ( error ) ) {
394
- err = new Recoverable ( error )
395
- } else {
396
- console . error ( error )
397
- }
398
- } else {
399
- err = error
400
- }
401
- }
402
-
403
- return callback ( err , result )
404
- }
405
-
406
- // Bookmark the point where we should reset the REPL state.
407
- const resetEval = appendEval ( state , '' )
408
-
409
- function reset ( ) {
410
- resetEval ( )
411
-
412
- // Hard fix for TypeScript forcing `Object.defineProperty(exports, ...)`.
413
- exec ( 'exports = module.exports' , state . path )
414
- }
415
-
416
- reset ( )
417
- repl . on ( 'reset' , reset )
418
-
419
- repl . defineCommand ( 'type' , {
420
- help : 'Check the type of a TypeScript identifier' ,
421
- action : function ( identifier : string ) {
422
- if ( ! identifier ) {
423
- repl . displayPrompt ( )
424
- return
425
- }
426
-
427
- const undo = appendEval ( state , identifier )
428
- const { name, comment } = service . getTypeInfo ( state . input , state . path , state . input . length )
429
-
430
- undo ( )
431
-
432
- if ( name ) repl . outputStream . write ( `${ name } \n` )
433
- if ( comment ) repl . outputStream . write ( `${ comment } \n` )
434
- repl . displayPrompt ( )
435
- }
436
- } )
437
-
438
- // Set up REPL history when available natively via node.js >= 11.
439
- if ( repl . setupHistory ) {
440
- const historyPath = process . env . TS_NODE_HISTORY || join ( homedir ( ) , '.ts_node_repl_history' )
441
-
442
- repl . setupHistory ( historyPath , err => {
443
- if ( ! err ) return
444
-
445
- console . error ( err )
446
- process . exit ( 1 )
447
- } )
448
- }
449
- }
450
-
451
- /**
452
- * Append to the eval instance and return an undo function.
453
- */
454
- function appendEval ( state : EvalState , input : string ) {
455
- const undoInput = state . input
456
- const undoVersion = state . version
457
- const undoOutput = state . output
458
- const undoLines = state . lines
459
-
460
- // Handle ASI issues with TypeScript re-evaluation.
461
- if ( undoInput . charAt ( undoInput . length - 1 ) === '\n' && / ^ \s * [ \/ \[ ( ` - ] / . test ( input ) && ! / ; \s * $ / . test ( undoInput ) ) {
462
- state . input = `${ state . input . slice ( 0 , - 1 ) } ;\n`
463
- }
464
-
465
- state . input += input
466
- state . lines += lineCount ( input )
467
- state . version ++
468
-
469
- return function ( ) {
470
- state . input = undoInput
471
- state . output = undoOutput
472
- state . version = undoVersion
473
- state . lines = undoLines
474
- }
475
- }
476
-
477
- /**
478
- * Count the number of lines.
479
- */
480
- function lineCount ( value : string ) {
481
- let count = 0
482
-
483
- for ( const char of value ) {
484
- if ( char === '\n' ) {
485
- count ++
486
- }
487
- }
488
-
489
- return count
490
- }
491
-
492
- const RECOVERY_CODES : Set < number > = new Set ( [
493
- 1003 , // "Identifier expected."
494
- 1005 , // "')' expected."
495
- 1109 , // "Expression expected."
496
- 1126 , // "Unexpected end of text."
497
- 1160 , // "Unterminated template literal."
498
- 1161 , // "Unterminated regular expression literal."
499
- 2355 // "A function whose declared type is neither 'void' nor 'any' must return a value."
500
- ] )
501
-
502
- /**
503
- * Check if a function can recover gracefully.
504
- */
505
- function isRecoverable ( error : TSError ) {
506
- return error . diagnosticCodes . every ( code => RECOVERY_CODES . has ( code ) )
507
- }
508
-
509
282
/** Safe `hasOwnProperty` */
510
283
function hasOwnProperty ( object : any , property : string ) : boolean {
511
284
return Object . prototype . hasOwnProperty . call ( object , property )
0 commit comments