Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,17 @@ added:

Enable experimental support for the QUIC protocol.

### `--experimental-repl-typescript`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.0 - Early development

Enable experimental support for TypeScript stripping in the REPL. When enabled, REPL
input must be valid TypeScript to be executed.

### `--experimental-sea-config`

<!-- YAML
Expand Down Expand Up @@ -3844,6 +3855,7 @@ one is included in the list below.
* `--experimental-package-map`
* `--experimental-print-required-tla`
* `--experimental-quic`
* `--experimental-repl-typescript`
* `--experimental-require-module`
* `--experimental-shadow-realm`
* `--experimental-specifier-resolution`
Expand Down
7 changes: 7 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,11 @@ this flag allows Node.js to locate and print their locations.
.It Fl -experimental-quic
Enable experimental support for the QUIC protocol.
.
.It Fl -experimental-repl-typescript
Enable experimental support for TypeScript stripping in the REPL. Input that is
valid JavaScript is evaluated as JavaScript before falling back to TypeScript
type stripping.
.
.It Fl -experimental-sea-config
Use this flag to generate a blob that can be injected into the Node.js
binary to produce a single executable application. See the documentation
Expand Down Expand Up @@ -1984,6 +1989,8 @@ one is included in the list below.
.It
\fB--experimental-quic\fR
.It
\fB--experimental-repl-typescript\fR
.It
\fB--experimental-require-module\fR
.It
\fB--experimental-shadow-realm\fR
Expand Down
23 changes: 21 additions & 2 deletions lib/internal/repl/eval.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ const {
transformModuleSyntax,
} = require('internal/repl/transform');
const { getReplInspector, prepareContext } = require('internal/repl/inspector');
const { getOptionValue } = require('internal/options');

let debug = require('internal/util/debuglog').debuglog('repl', (fn) => {
debug = fn;
});

const blankRegExp = /^\s*$/;
const experimentalREPLTypeScript =
getOptionValue('--experimental-repl-typescript');

// Matches the first stack frame that belongs to the REPL
const internalFrameRegExp =
Expand Down Expand Up @@ -119,11 +122,27 @@ function createReplEval(repl) {
installDynamicImport(context);
prepareContext(context);

let code = rawCode;

if (experimentalREPLTypeScript) {
try {
const { stripTypeScriptModuleTypes } =
require('internal/modules/typescript');
code = stripTypeScriptModuleTypes(code, file);
} catch (err) {
if (err.code !== 'ERR_INVALID_TYPESCRIPT_SYNTAX' &&
err.code !== 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX') {
throw err;
}
// Let the evaluator report the original JavaScript syntax error. It
// also determines whether incomplete input is recoverable.
Comment on lines +137 to +138

@Renegade334 Renegade334 Jul 31, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a multiline perspective, this approach will still only allow multiline input to be pure JS, as SWC InvalidSyntax errors will leave the input untransformed, and input containing TS-only syntax will then fail both JS execution and the Acorn recoverable parsing test.

// Triggers multiline continuation
function foo(

// Triggers "uncaught SyntaxError"
function bar<T>(

The inconsistency would be really jarring, and I wonder if we need to just disable multiline in TS mode, and explicitly signpost users to .editor.

Alternatively, we could try and identify recoverable SWC InvalidSyntax errors from the initial strip attempt, but this would still have some inconsistencies with JS multiline. Truly unparseable partial input usually errors in SWC with some flavour of "unexpected eof", but there are cases like const baz: string whose "stripped" JS equivalents are considered recoverable, but which SWC doesn't report in this way.

}
}

// Wrap bare object literals so `{ a: 1 }` is evaluated as an expression
// rather than a block statement.
let code = rawCode;
if (isObjectLiteral(code) && isValidSyntax(code)) {
code = `(${StringPrototypeTrim(rawCode)})\n`;
code = `(${StringPrototypeTrim(code)})\n`;
}

// Rewrite ES module syntax into runnable script form.
Expand Down
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
kAllowedInEnvvar,
false,
OptionNamespaces::kPermissionNamespace);
AddOption("--experimental-repl-typescript",
"experimental TypeScript support in the REPL",
&EnvironmentOptions::experimental_repl_typescript,
kAllowedInEnvvar);
AddOption("--experimental-vm-modules",
"experimental ES Module support in vm module",
&EnvironmentOptions::experimental_vm_modules,
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ class EnvironmentOptions : public Options {
bool allow_wasi = false;
bool allow_ffi = false;
bool allow_worker_threads = false;
bool experimental_repl_typescript = EXPERIMENTALS_DEFAULT_VALUE;
bool experimental_vm_modules = EXPERIMENTALS_DEFAULT_VALUE;
bool async_context_frame = true;
bool expose_internals = false;
Expand Down
20 changes: 20 additions & 0 deletions test/parallel/test-repl-typescript.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Flags: --experimental-repl-typescript

import * as common from '../common/index.mjs';
import assert from 'node:assert';
import { startNewREPLServer } from '../common/repl.js';

common.skipIfInspectorDisabled();

const { run, output } = startNewREPLServer({
terminal: false,
prompt: '> ',
});

await run('let x: number = 3\n');
assert.match(output.accumulator, /undefined\n> /);
output.accumulator = '';

await run('x\n');
assert.match(output.accumulator, /3\n> /);
output.accumulator = '';
Loading