2
2
3
3
const {
4
4
RegExpPrototypeExec,
5
+ StringPrototypeIndexOf,
6
+ StringPrototypeSlice,
5
7
Symbol,
6
8
globalThis,
7
9
} = primordials ;
@@ -17,6 +19,7 @@ const {
17
19
} = require ( 'internal/errors' ) ;
18
20
const { pathToFileURL } = require ( 'internal/url' ) ;
19
21
const { exitCodes : { kGenericUserError } } = internalBinding ( 'errors' ) ;
22
+ const { stripTypeScriptModuleTypes } = require ( 'internal/modules/typescript' ) ;
20
23
21
24
const {
22
25
executionAsyncId,
@@ -32,6 +35,7 @@ const { getOptionValue } = require('internal/options');
32
35
const {
33
36
makeContextifyScript, runScriptInThisContext,
34
37
} = require ( 'internal/vm' ) ;
38
+ const { emitExperimentalWarning, isError } = require ( 'internal/util' ) ;
35
39
// shouldAbortOnUncaughtToggle is a typed array for faster
36
40
// communication with JS.
37
41
const { shouldAbortOnUncaughtToggle } = internalBinding ( 'util' ) ;
@@ -70,21 +74,14 @@ function evalModuleEntryPoint(source, print) {
70
74
}
71
75
72
76
function evalScript ( name , body , breakFirstLine , print , shouldLoadESM = false ) {
73
- const CJSModule = require ( 'internal/modules/cjs/loader' ) . Module ;
74
-
75
- const cwd = tryGetCwd ( ) ;
76
77
const origModule = globalThis . module ; // Set e.g. when called from the REPL.
77
-
78
- const module = new CJSModule ( name ) ;
79
- module . filename = path . join ( cwd , name ) ;
80
- module . paths = CJSModule . _nodeModulePaths ( cwd ) ;
81
-
78
+ const module = createModule ( name ) ;
82
79
const baseUrl = pathToFileURL ( module . filename ) . href ;
83
80
84
- if ( getOptionValue ( '--experimental-detect-module' ) &&
85
- getOptionValue ( '--input-type' ) === '' && getOptionValue ( '--experimental-default-type ' ) === '' &&
86
- containsModuleSyntax ( body , name , null , 'no CJS variables' ) ) {
87
- return evalModuleEntryPoint ( body , print ) ;
81
+ if ( shouldUseModuleEntryPoint ( name , body ) ) {
82
+ return getOptionValue ( '--experimental-strip-types ' ) ?
83
+ evalTypeScriptModuleEntryPoint ( body , print ) :
84
+ evalModuleEntryPoint ( body , print ) ;
88
85
}
89
86
90
87
const runScript = ( ) => {
@@ -99,23 +96,8 @@ function evalScript(name, body, breakFirstLine, print, shouldLoadESM = false) {
99
96
globalThis . __filename = name ;
100
97
RegExpPrototypeExec ( / ^ / , '' ) ; // Necessary to reset RegExp statics before user code runs.
101
98
const result = module . _compile ( script , `${ name } -wrapper` ) ( ( ) => {
102
- const hostDefinedOptionId = Symbol ( name ) ;
103
- async function importModuleDynamically ( specifier , _ , importAttributes ) {
104
- const cascadedLoader = require ( 'internal/modules/esm/loader' ) . getOrInitializeCascadedLoader ( ) ;
105
- return cascadedLoader . import ( specifier , baseUrl , importAttributes ) ;
106
- }
107
- const script = makeContextifyScript (
108
- body , // code
109
- name , // filename,
110
- 0 , // lineOffset
111
- 0 , // columnOffset,
112
- undefined , // cachedData
113
- false , // produceCachedData
114
- undefined , // parsingContext
115
- hostDefinedOptionId , // hostDefinedOptionId
116
- importModuleDynamically , // importModuleDynamically
117
- ) ;
118
- return runScriptInThisContext ( script , true , ! ! breakFirstLine ) ;
99
+ const compiledScript = compileScript ( name , body , baseUrl ) ;
100
+ return runScriptInThisContext ( compiledScript , true , ! ! breakFirstLine ) ;
119
101
} ) ;
120
102
if ( print ) {
121
103
const { log } = require ( 'internal/console/global' ) ;
@@ -238,10 +220,283 @@ function readStdin(callback) {
238
220
} ) ;
239
221
}
240
222
223
+ /**
224
+ * Adds the TS message to the error stack.
225
+ *
226
+ * At the 3rd line of the stack, the message is added.
227
+ * @param {string } originalStack The stack to decorate
228
+ * @param {string } newMessage the message to add to the error stack
229
+ * @returns {void }
230
+ */
231
+ function decorateCJSErrorWithTSMessage ( originalStack , newMessage ) {
232
+ let index ;
233
+ for ( let i = 0 ; i < 3 ; i ++ ) {
234
+ index = StringPrototypeIndexOf ( originalStack , '\n' , index + 1 ) ;
235
+ }
236
+ return StringPrototypeSlice ( originalStack , 0 , index ) +
237
+ '\n' + newMessage +
238
+ StringPrototypeSlice ( originalStack , index ) ;
239
+ }
240
+
241
+ /**
242
+ *
243
+ * Wrapper of evalScript
244
+ *
245
+ * This function wraps the evaluation of the source code in a try-catch block.
246
+ * If the source code fails to be evaluated, it will retry evaluating the source code
247
+ * with the TypeScript parser.
248
+ *
249
+ * If the source code fails to be evaluated with the TypeScript parser,
250
+ * it will rethrow the original error, adding the TypeScript error message to the stack.
251
+ *
252
+ * This way we don't change the behavior of the code, but we provide a better error message
253
+ * in case of a typescript error.
254
+ * @param {string } name The name of the file
255
+ * @param {string } source The source code to evaluate
256
+ * @param {boolean } breakFirstLine Whether to break on the first line
257
+ * @param {boolean } print If the result should be printed
258
+ * @param {boolean } shouldLoadESM If the code should be loaded as an ESM module
259
+ * @returns {void }
260
+ */
261
+ function evalTypeScript ( name , source , breakFirstLine , print , shouldLoadESM = false ) {
262
+ const origModule = globalThis . module ; // Set e.g. when called from the REPL.
263
+ const module = createModule ( name ) ;
264
+ const baseUrl = pathToFileURL ( module . filename ) . href ;
265
+
266
+ if ( shouldUseModuleEntryPoint ( name , source ) ) {
267
+ return evalTypeScriptModuleEntryPoint ( source , print ) ;
268
+ }
269
+
270
+ let compiledScript ;
271
+ // This variable can be modified if the source code is stripped.
272
+ let sourceToRun = source ;
273
+ try {
274
+ compiledScript = compileScript ( name , source , baseUrl ) ;
275
+ } catch ( originalError ) {
276
+ // If it's not a SyntaxError, rethrow it.
277
+ if ( ! isError ( originalError ) || originalError . name !== 'SyntaxError' ) {
278
+ throw originalError ;
279
+ }
280
+ try {
281
+ sourceToRun = stripTypeScriptModuleTypes ( source , name , false ) ;
282
+ // Retry the CJS/ESM syntax detection after stripping the types.
283
+ if ( shouldUseModuleEntryPoint ( name , sourceToRun ) ) {
284
+ return evalTypeScriptModuleEntryPoint ( source , print ) ;
285
+ }
286
+ // If the ContextifiedScript was successfully created, execute it.
287
+ // outside the try-catch block to avoid catching runtime errors.
288
+ compiledScript = compileScript ( name , sourceToRun , baseUrl ) ;
289
+ // Emit the experimental warning after the code was successfully evaluated.
290
+ emitExperimentalWarning ( 'Type Stripping' ) ;
291
+ } catch ( tsError ) {
292
+ // If its not an error, or it's not an invalid typescript syntax error, rethrow it.
293
+ if ( ! isError ( tsError ) || tsError ?. code !== 'ERR_INVALID_TYPESCRIPT_SYNTAX' ) {
294
+ throw tsError ;
295
+ }
296
+
297
+ try {
298
+ originalError . stack = decorateCJSErrorWithTSMessage ( originalError . stack , tsError . message ) ;
299
+ } catch { /* Ignore potential errors coming from `stack` getter/setter */ }
300
+ throw originalError ;
301
+ }
302
+ }
303
+
304
+ if ( shouldLoadESM ) {
305
+ return require ( 'internal/modules/run_main' ) . runEntryPointWithESMLoader (
306
+ ( ) => runScriptInContext ( name ,
307
+ sourceToRun ,
308
+ breakFirstLine ,
309
+ print ,
310
+ module ,
311
+ baseUrl ,
312
+ compiledScript ,
313
+ origModule ) ) ;
314
+ }
315
+
316
+ runScriptInContext ( name , sourceToRun , breakFirstLine , print , module , baseUrl , compiledScript , origModule ) ;
317
+ }
318
+
319
+ /**
320
+ * Wrapper of evalModuleEntryPoint
321
+ *
322
+ * This function wraps the compilation of the source code in a try-catch block.
323
+ * If the source code fails to be compiled, it will retry transpiling the source code
324
+ * with the TypeScript parser.
325
+ * @param {string } source The source code to evaluate
326
+ * @param {boolean } print If the result should be printed
327
+ * @returns {Promise } The module evaluation promise
328
+ */
329
+ function evalTypeScriptModuleEntryPoint ( source , print ) {
330
+ if ( print ) {
331
+ throw new ERR_EVAL_ESM_CANNOT_PRINT ( ) ;
332
+ }
333
+
334
+ RegExpPrototypeExec ( / ^ / , '' ) ; // Necessary to reset RegExp statics before user code runs.
335
+
336
+ return require ( 'internal/modules/run_main' ) . runEntryPointWithESMLoader (
337
+ async ( loader ) => {
338
+ const url = getEvalModuleUrl ( ) ;
339
+ let moduleWrap ;
340
+ try {
341
+ // Compile the module to check for syntax errors.
342
+ moduleWrap = loader . createModuleWrap ( source , url ) ;
343
+ } catch ( originalError ) {
344
+ // If it's not a SyntaxError, rethrow it.
345
+ if ( ! isError ( originalError ) || originalError . name !== 'SyntaxError' ) {
346
+ throw originalError ;
347
+ }
348
+ let strippedSource ;
349
+ try {
350
+ strippedSource = stripTypeScriptModuleTypes ( source , url , false ) ;
351
+ // If the moduleWrap was successfully created, execute the module job.
352
+ // outside the try-catch block to avoid catching runtime errors.
353
+ moduleWrap = loader . createModuleWrap ( strippedSource , url ) ;
354
+ // Emit the experimental warning after the code was successfully compiled.
355
+ emitExperimentalWarning ( 'Type Stripping' ) ;
356
+ } catch ( tsError ) {
357
+ // If its not an error, or it's not an invalid typescript syntax error, rethrow it.
358
+ if ( ! isError ( tsError ) || tsError ?. code !== 'ERR_INVALID_TYPESCRIPT_SYNTAX' ) {
359
+ throw tsError ;
360
+ }
361
+ try {
362
+ originalError . stack = `${ tsError . message } \n\n${ originalError . stack } ` ;
363
+ } catch { /* Ignore potential errors coming from `stack` getter/setter */ }
364
+
365
+ throw originalError ;
366
+ }
367
+ }
368
+ // If the moduleWrap was successfully created either with by just compiling
369
+ // or after transpilation, execute the module job.
370
+ return loader . executeModuleJob ( url , moduleWrap , true ) ;
371
+ } ,
372
+ ) ;
373
+ } ;
374
+
375
+ /**
376
+ *
377
+ * Function used to shortcut when `--input-type=module-typescript` is set.
378
+ * @param {string } source
379
+ * @param {boolean } print
380
+ */
381
+ function parseAndEvalModuleTypeScript ( source , print ) {
382
+ // We know its a TypeScript module, we can safely emit the experimental warning.
383
+ const strippedSource = stripTypeScriptModuleTypes ( source , getEvalModuleUrl ( ) ) ;
384
+ evalModuleEntryPoint ( strippedSource , print ) ;
385
+ }
386
+
387
+ /**
388
+ * Function used to shortcut when `--input-type=commonjs-typescript` is set
389
+ * @param {string } name The name of the file
390
+ * @param {string } source The source code to evaluate
391
+ * @param {boolean } breakFirstLine Whether to break on the first line
392
+ * @param {boolean } print If the result should be printed
393
+ * @param {boolean } shouldLoadESM If the code should be loaded as an ESM module
394
+ * @returns {void }
395
+ */
396
+ function parseAndEvalCommonjsTypeScript ( name , source , breakFirstLine , print , shouldLoadESM = false ) {
397
+ // We know its a TypeScript module, we can safely emit the experimental warning.
398
+ const strippedSource = stripTypeScriptModuleTypes ( source , getEvalModuleUrl ( ) ) ;
399
+ evalScript ( name , strippedSource , breakFirstLine , print , shouldLoadESM ) ;
400
+ }
401
+
402
+ /**
403
+ *
404
+ * @param {string } name - The filename of the script.
405
+ * @param {string } body - The code of the script.
406
+ * @param {string } baseUrl Path of the parent importing the module.
407
+ * @returns {ContextifyScript } The created contextify script.
408
+ */
409
+ function compileScript ( name , body , baseUrl ) {
410
+ const hostDefinedOptionId = Symbol ( name ) ;
411
+ async function importModuleDynamically ( specifier , _ , importAttributes ) {
412
+ const cascadedLoader = require ( 'internal/modules/esm/loader' ) . getOrInitializeCascadedLoader ( ) ;
413
+ return cascadedLoader . import ( specifier , baseUrl , importAttributes ) ;
414
+ }
415
+ return makeContextifyScript (
416
+ body , // code
417
+ name , // filename,
418
+ 0 , // lineOffset
419
+ 0 , // columnOffset,
420
+ undefined , // cachedData
421
+ false , // produceCachedData
422
+ undefined , // parsingContext
423
+ hostDefinedOptionId , // hostDefinedOptionId
424
+ importModuleDynamically , // importModuleDynamically
425
+ ) ;
426
+ }
427
+
428
+ /**
429
+ * @param {string } name - The filename of the script.
430
+ * @param {string } body - The code of the script.
431
+ * @returns {boolean } Whether the module entry point should be evaluated as a module.
432
+ */
433
+ function shouldUseModuleEntryPoint ( name , body ) {
434
+ return getOptionValue ( '--experimental-detect-module' ) && getOptionValue ( '--experimental-default-type' ) === '' &&
435
+ getOptionValue ( '--input-type' ) === '' &&
436
+ containsModuleSyntax ( body , name , null , 'no CJS variables' ) ;
437
+ }
438
+
439
+ /**
440
+ *
441
+ * @param {string } name - The filename of the script.
442
+ * @returns {import('internal/modules/esm/loader').CJSModule } The created module.
443
+ */
444
+ function createModule ( name ) {
445
+ const CJSModule = require ( 'internal/modules/cjs/loader' ) . Module ;
446
+ const cwd = tryGetCwd ( ) ;
447
+ const module = new CJSModule ( name ) ;
448
+ module . filename = path . join ( cwd , name ) ;
449
+ module . paths = CJSModule . _nodeModulePaths ( cwd ) ;
450
+ return module ;
451
+ }
452
+
453
+ /**
454
+ *
455
+ * @param {string } name - The filename of the script.
456
+ * @param {string } body - The code of the script.
457
+ * @param {boolean } breakFirstLine Whether to break on the first line
458
+ * @param {boolean } print If the result should be printed
459
+ * @param {import('internal/modules/esm/loader').CJSModule } module The module
460
+ * @param {string } baseUrl Path of the parent importing the module.
461
+ * @param {object } compiledScript The compiled script.
462
+ * @param {any } origModule The original module.
463
+ * @returns {void }
464
+ */
465
+ function runScriptInContext ( name , body , breakFirstLine , print , module , baseUrl , compiledScript , origModule ) {
466
+ // Create wrapper for cache entry
467
+ const script = `
468
+ globalThis.module = module;
469
+ globalThis.exports = exports;
470
+ globalThis.__dirname = __dirname;
471
+ globalThis.require = require;
472
+ return (main) => main();
473
+ ` ;
474
+ globalThis . __filename = name ;
475
+ RegExpPrototypeExec ( / ^ / , '' ) ; // Necessary to reset RegExp statics before user code runs.
476
+ const result = module . _compile ( script , `${ name } -wrapper` ) ( ( ) => {
477
+ // If the script was already compiled, use it.
478
+ return runScriptInThisContext (
479
+ compiledScript ,
480
+ true , ! ! breakFirstLine ) ;
481
+ } ) ;
482
+ if ( print ) {
483
+ const { log } = require ( 'internal/console/global' ) ;
484
+
485
+ process . on ( 'exit' , ( ) => {
486
+ log ( result ) ;
487
+ } ) ;
488
+ }
489
+ if ( origModule !== undefined )
490
+ globalThis . module = origModule ;
491
+ }
492
+
241
493
module . exports = {
494
+ parseAndEvalCommonjsTypeScript,
495
+ parseAndEvalModuleTypeScript,
242
496
readStdin,
243
497
tryGetCwd,
244
498
evalModuleEntryPoint,
499
+ evalTypeScript,
245
500
evalScript,
246
501
onGlobalUncaughtException : createOnGlobalUncaughtException ( ) ,
247
502
setUncaughtExceptionCaptureCallback,
0 commit comments