Skip to content

Commit

Permalink
fixup! split esm compile and eval
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-ippolito committed Dec 19, 2024
1 parent 878d49c commit eae5489
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 13 deletions.
32 changes: 30 additions & 2 deletions lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,25 @@ class ModuleLoader {
}
}

async eval(source, url, isEntryPoint = false) {
/**
*
* @param {string} source Source code of the module.
* @param {string} url URL of the module.
* @returns {object} The module wrap object.
*/
createModuleWrap(source, url) {
return compileSourceTextModule(url, source, this);
}

/**
*
* @param {string} url URL of the module.
* @param {object} wrap Module wrap object.
* @param {boolean} isEntryPoint Whether the module is the entry point.
* @returns {Promise<object>} The module object.
*/
async executeModuleJob(url, wrap, isEntryPoint = false) {
const { ModuleJob } = require('internal/modules/esm/module_job');
const wrap = compileSourceTextModule(url, source, this);
const module = await onImport.tracePromise(async () => {
const job = new ModuleJob(
this, url, undefined, wrap, false, false);
Expand All @@ -235,6 +251,18 @@ class ModuleLoader {
};
}

/**
*
* @param {string} source Source code of the module.
* @param {string} url URL of the module.
* @param {boolean} isEntryPoint Whether the module is the entry point.
* @returns {Promise<object>} The module object.
*/
eval(source, url, isEntryPoint = false) {
const wrap = this.createModuleWrap(source, url);
return this.executeModuleJob(url, wrap, isEntryPoint);
}

/**
* Get a (possibly not yet fully linked) module job from the cache, or create one and return its Promise.
* @param {string} specifier The module request of the module to be resolved. Typically, what's
Expand Down
27 changes: 16 additions & 11 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ function evalTypeScript(name, source, breakFirstLine, print, shouldLoadESM = fal
/**
* Wrapper of evalModuleEntryPoint
*
* This function wraps the evaluation of the source code in a try-catch block.
* If the source code fails to be evaluated, it will retry evaluating the source code
* This function wraps the compilation of the source code in a try-catch block.
* If the source code fails to be compiled, it will retry transpiling the source code
* with the TypeScript parser.
* @param {string} source The source code to evaluate
* @param {boolean} print If the result should be printed
Expand All @@ -329,34 +329,39 @@ function evalTypeScriptModuleEntryPoint(source, print) {

return require('internal/modules/run_main').runEntryPointWithESMLoader(
async (loader) => {
const url = getEvalModuleUrl();
let moduleWrap;
try {
// Await here to catch the error and rethrow it with the typescript error message.
return await loader.eval(source, getEvalModuleUrl(), true);
// Compile the module to check for syntax errors.
moduleWrap = loader.createModuleWrap(source, url);
} catch (originalError) {
// If it's not a SyntaxError, rethrow it.
if (!isError(originalError) || originalError.name !== 'SyntaxError') {
throw originalError;
}

let strippedSource;
try {
const url = getEvalModuleUrl();
const strippedSource = stripTypeScriptModuleTypes(source, url, false);
const result = await loader.eval(strippedSource, url, true);
// Emit the experimental warning after the code was successfully evaluated.
strippedSource = stripTypeScriptModuleTypes(source, url, false);
// If the moduleWrap was successfully created, execute the module job.
// outside the try-catch block to avoid catching runtime errors.
moduleWrap = loader.createModuleWrap(strippedSource, url);
// Emit the experimental warning after the code was successfully compiled.
emitExperimentalWarning('Type Stripping');
return result;
} catch (tsError) {
// If its not an error, or it's not an invalid typescript syntax error, rethrow it.
if (!isError(tsError) || tsError?.code !== 'ERR_INVALID_TYPESCRIPT_SYNTAX') {
throw tsError;
}

try {
originalError.stack = `${tsError.message}\n\n${originalError.stack}`;
} catch { /* Ignore potential errors coming from `stack` getter/setter */ }

throw originalError;
}
}
// If the moduleWrap was successfully created either with by just compiling
// or after transpilation, execute the module job.
return loader.executeModuleJob(url, moduleWrap, true);
},
);
};
Expand Down
14 changes: 14 additions & 0 deletions test/es-module/test-typescript-eval.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,17 @@ test('typescript code is throwing an error', async () => {
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

test('typescript ESM code is throwing a syntax error at runtime', async () => {
const result = await spawnPromisified(process.execPath, [
'--experimental-strip-types',
'--eval',
'import util from "node:util"; function foo(){}; throw new SyntaxError(foo<Number>(1));']);
// Trick by passing ambiguous syntax to trigger to see if evaluated in TypeScript or JavaScript
// If evaluated in JavaScript `foo<Number>(1)` is evaluated as `foo < Number > (1)`
// result in false
// If evaluated in TypeScript `foo<Number>(1)` is evaluated as `foo(1)`
match(result.stderr, /SyntaxError: false/);
strictEqual(result.stdout, '');
strictEqual(result.code, 1);
});

0 comments on commit eae5489

Please sign in to comment.