Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v22.x backport] typescript syntax detection #56912

Open
wants to merge 9 commits into
base: v22.x-staging
Choose a base branch
from
21 changes: 19 additions & 2 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1414,8 +1414,23 @@ added: v12.0.0
-->

This configures Node.js to interpret `--eval` or `STDIN` input as CommonJS or
as an ES module. Valid values are `"commonjs"` or `"module"`. The default is
`"commonjs"` unless [`--experimental-default-type=module`][] is used.
as an ES module. Valid values are `"commonjs"`, `"module"`, `"module-typescript"` and `"commonjs-typescript"`.
The `"-typescript"` values are available only in combination with the flag `--experimental-strip-types`.
The default is `"commonjs"` unless [`--experimental-default-type=module`][] is used.
If `--experimental-strip-types` is enabled and `--input-type` is not provided,
Node.js will try to detect the syntax with the following steps:

1. Run the input as CommonJS.
2. If step 1 fails, run the input as an ES module.
3. If step 2 fails with a SyntaxError, strip the types.
4. If step 3 fails with an error code [`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`][]
or [`ERR_INVALID_TYPESCRIPT_SYNTAX`][],
throw the error from step 2, including the TypeScript error in the message,
else run as CommonJS.
5. If step 4 fails, run the input as an ES module.

To avoid the delay of multiple syntax detection passes, the `--input-type=type` flag can be used to specify
how the `--eval` input should be interpreted.

The REPL does not support this option. Usage of `--input-type=module` with
[`--print`][] will throw an error, as `--print` does not support ES module
Expand Down Expand Up @@ -3712,6 +3727,8 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`Atomics.wait()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait
[`Buffer`]: buffer.md#class-buffer
[`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man3.0/man3/CRYPTO_secure_malloc_init.html
[`ERR_INVALID_TYPESCRIPT_SYNTAX`]: errors.md#err_invalid_typescript_syntax
[`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`]: errors.md#err_unsupported_typescript_syntax
[`NODE_OPTIONS`]: #node_optionsoptions
[`NO_COLOR`]: https://no-color.org
[`SlowBuffer`]: buffer.md#class-slowbuffer
Expand Down
24 changes: 20 additions & 4 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2105,12 +2105,16 @@
### `ERR_INVALID_TYPESCRIPT_SYNTAX`

<!-- YAML
added: v22.10.0
added:
- v23.0.0
- v22.10.0
aduh95 marked this conversation as resolved.
Show resolved Hide resolved
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/56610

Check warning on line 2113 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: This error is no longer thrown on valid yet unsupported syntax.
-->

The provided TypeScript syntax is not valid or unsupported.
This could happen when using TypeScript syntax that requires
transformation with [type-stripping][].
The provided TypeScript syntax is not valid.

<a id="ERR_INVALID_URI"></a>

Expand Down Expand Up @@ -3103,6 +3107,18 @@
}
```

<a id="ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX"></a>

### `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`

<!-- YAML
added: REPLACEME
-->

The provided TypeScript syntax is unsupported.
This could happen when using TypeScript syntax that requires
transformation with [type-stripping][].

<a id="ERR_USE_AFTER_CLOSE"></a>

### `ERR_USE_AFTER_CLOSE`
Expand Down
4 changes: 2 additions & 2 deletions doc/api/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,10 @@ import { fn, FnParams } from './fn.ts';

### Non-file forms of input

Type stripping can be enabled for `--eval`. The module system
Type stripping can be enabled for `--eval` and STDIN. The module system
will be determined by `--input-type`, as it is for JavaScript.

TypeScript syntax is unsupported in the REPL, STDIN input, `--print`, `--check`, and
TypeScript syntax is unsupported in the REPL, `--check`, and
`inspect`.

### Source maps
Expand Down
1 change: 1 addition & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1841,6 +1841,7 @@ E('ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING',
E('ERR_UNSUPPORTED_RESOLVE_REQUEST',
'Failed to resolve module specifier "%s" from "%s": Invalid relative URL or base scheme is not hierarchical.',
TypeError);
E('ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX', '%s', SyntaxError);
E('ERR_USE_AFTER_CLOSE', '%s was closed', Error);

// This should probably be a `TypeError`.
Expand Down
35 changes: 28 additions & 7 deletions lib/internal/main/eval_stdin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const { getOptionValue } = require('internal/options');

const {
evalModuleEntryPoint,
evalTypeScript,
parseAndEvalCommonjsTypeScript,
parseAndEvalModuleTypeScript,
evalScript,
readStdin,
} = require('internal/process/execution');
Expand All @@ -25,14 +28,32 @@ readStdin((code) => {

const print = getOptionValue('--print');
const shouldLoadESM = getOptionValue('--import').length > 0;
if (getOptionValue('--input-type') === 'module' ||
(getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) {
const inputType = getOptionValue('--input-type');
const tsEnabled = getOptionValue('--experimental-strip-types');
if (inputType === 'module' ||
(getOptionValue('--experimental-default-type') === 'module' &&
inputType !== 'commonjs')) {
evalModuleEntryPoint(code, print);
} else if (inputType === 'module-typescript' && tsEnabled) {
parseAndEvalModuleTypeScript(code, print);
} else {
evalScript('[stdin]',
code,
getOptionValue('--inspect-brk'),
print,
shouldLoadESM);

let evalFunction;
if (inputType === 'commonjs') {
evalFunction = evalScript;
} else if (inputType === 'commonjs-typescript' && tsEnabled) {
evalFunction = parseAndEvalCommonjsTypeScript;
} else if (tsEnabled) {
evalFunction = evalTypeScript;
} else {
// Default to commonjs.
evalFunction = evalScript;
}

evalFunction('[stdin]',
code,
getOptionValue('--inspect-brk'),
print,
shouldLoadESM);
}
});
53 changes: 36 additions & 17 deletions lib/internal/main/eval_string.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,37 @@ const {
prepareMainThreadExecution,
markBootstrapComplete,
} = require('internal/process/pre_execution');
const { evalModuleEntryPoint, evalScript } = require('internal/process/execution');
const {
evalModuleEntryPoint,
evalTypeScript,
parseAndEvalCommonjsTypeScript,
parseAndEvalModuleTypeScript,
evalScript,
} = require('internal/process/execution');
const { addBuiltinLibsToObject } = require('internal/modules/helpers');
const { stripTypeScriptModuleTypes } = require('internal/modules/typescript');
const { getOptionValue } = require('internal/options');

prepareMainThreadExecution();
addBuiltinLibsToObject(globalThis, '<eval>');
markBootstrapComplete();

const code = getOptionValue('--eval');
const source = getOptionValue('--experimental-strip-types') ?
stripTypeScriptModuleTypes(code) :
code;

const print = getOptionValue('--print');
const shouldLoadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0;
if (getOptionValue('--input-type') === 'module' ||
(getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) {
evalModuleEntryPoint(source, print);
const inputType = getOptionValue('--input-type');
const tsEnabled = getOptionValue('--experimental-strip-types');
if (inputType === 'module' ||
(getOptionValue('--experimental-default-type') === 'module' && inputType !== 'commonjs')) {
evalModuleEntryPoint(code, print);
} else if (inputType === 'module-typescript' && tsEnabled) {
parseAndEvalModuleTypeScript(code, print);
} else {
// For backward compatibility, we want the identifier crypto to be the
// `node:crypto` module rather than WebCrypto.
const isUsingCryptoIdentifier =
getOptionValue('--experimental-global-webcrypto') &&
RegExpPrototypeExec(/\bcrypto\b/, source) !== null;
getOptionValue('--experimental-global-webcrypto') &&
RegExpPrototypeExec(/\bcrypto\b/, code) !== null;
const shouldDefineCrypto = isUsingCryptoIdentifier && internalBinding('config').hasOpenSSL;

if (isUsingCryptoIdentifier && !shouldDefineCrypto) {
Expand All @@ -52,11 +58,24 @@ if (getOptionValue('--input-type') === 'module' ||
};
ObjectDefineProperty(object, name, { __proto__: null, set: setReal });
}
evalScript('[eval]',
shouldDefineCrypto ? (
print ? `let crypto=require("node:crypto");{${source}}` : `(crypto=>{{${source}}})(require('node:crypto'))`
) : source,
getOptionValue('--inspect-brk'),
print,
shouldLoadESM);

let evalFunction;
if (inputType === 'commonjs') {
evalFunction = evalScript;
} else if (inputType === 'commonjs-typescript' && tsEnabled) {
evalFunction = parseAndEvalCommonjsTypeScript;
} else if (tsEnabled) {
evalFunction = evalTypeScript;
} else {
// Default to commonjs.
evalFunction = evalScript;
}

evalFunction('[eval]',
shouldDefineCrypto ? (
print ? `let crypto=require("node:crypto");{${code}}` : `(crypto=>{{${code}}})(require('node:crypto'))`
) : code,
getOptionValue('--inspect-brk'),
print,
shouldLoadESM);
}
27 changes: 26 additions & 1 deletion lib/internal/main/worker_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ const { setupMainThreadPort } = require('internal/worker/messaging');
const {
onGlobalUncaughtException,
evalScript,
evalTypeScript,
evalModuleEntryPoint,
parseAndEvalCommonjsTypeScript,
parseAndEvalModuleTypeScript,
} = require('internal/process/execution');

let debug = require('internal/util/debuglog').debuglog('worker', (fn) => {
Expand Down Expand Up @@ -166,7 +169,29 @@ port.on('message', (message) => {
value: filename,
});
ArrayPrototypeSplice(process.argv, 1, 0, name);
evalScript(name, filename);
const tsEnabled = getOptionValue('--experimental-strip-types');
const inputType = getOptionValue('--input-type');

if (inputType === 'module-typescript' && tsEnabled) {
// This is a special case where we want to parse and eval the
// TypeScript code as a module
parseAndEvalModuleTypeScript(filename, false);
break;
}

let evalFunction;
if (inputType === 'commonjs') {
evalFunction = evalScript;
} else if (inputType === 'commonjs-typescript' && tsEnabled) {
evalFunction = parseAndEvalCommonjsTypeScript;
} else if (tsEnabled) {
evalFunction = evalTypeScript;
} else {
// Default to commonjs.
evalFunction = evalScript;
}

evalFunction(name, filename);
break;
}

Expand Down
1 change: 0 additions & 1 deletion lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,6 @@ function initializeCJS() {

const tsEnabled = getOptionValue('--experimental-strip-types');
if (tsEnabled) {
emitExperimentalWarning('Type Stripping');
Module._extensions['.cts'] = loadCTS;
Module._extensions['.ts'] = loadTS;
}
Expand Down
32 changes: 30 additions & 2 deletions lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,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 @@ -232,6 +248,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
3 changes: 0 additions & 3 deletions lib/internal/modules/esm/translators.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ translators.set('require-commonjs', (url, source, isMain) => {
// Handle CommonJS modules referenced by `require` calls.
// This translator function must be sync, as `require` is sync.
translators.set('require-commonjs-typescript', (url, source, isMain) => {
emitExperimentalWarning('Type Stripping');
assert(cjsParse);
const code = stripTypeScriptModuleTypes(stringify(source), url);
return createCJSModuleWrap(url, code);
Expand Down Expand Up @@ -457,7 +456,6 @@ translators.set('wasm', async function(url, source) {

// Strategy for loading a commonjs TypeScript module
translators.set('commonjs-typescript', function(url, source) {
emitExperimentalWarning('Type Stripping');
assertBufferSource(source, true, 'load');
const code = stripTypeScriptModuleTypes(stringify(source), url);
debug(`Translating TypeScript ${url}`);
Expand All @@ -466,7 +464,6 @@ translators.set('commonjs-typescript', function(url, source) {

// Strategy for loading an esm TypeScript module
translators.set('module-typescript', function(url, source) {
emitExperimentalWarning('Type Stripping');
assertBufferSource(source, true, 'load');
const code = stripTypeScriptModuleTypes(stringify(source), url);
debug(`Translating TypeScript ${url}`);
Expand Down
Loading
Loading