From 0b16752256bab002ea52976bf259a486937b139d Mon Sep 17 00:00:00 2001 From: TheHypnoo Date: Mon, 27 Jul 2026 04:05:35 +0200 Subject: [PATCH 1/2] test(repl): expand Node 26.5 parity coverage --- test-parity/node-suite/repl/README.md | 107 +++++++++++++++++- .../node-suite/repl/async/commands-reset.ts | 50 -------- .../repl/async/scripted-lifecycle.ts | 62 ---------- .../node-suite/repl/async/setup-history.ts | 39 ------- .../repl/classes/recoverable-default-error.ts | 5 + .../repl/classes/recoverable-prototype.ts | 16 +++ .../repl/classes/recoverable-shape.ts | 9 ++ .../repl-server-constructor-descriptor.ts | 12 ++ .../repl/classes/repl-server-inheritance.ts | 5 + .../repl/classes/repl-server-prototype.ts | 27 +++++ .../node-suite/repl/commands/break-command.ts | 33 ++++++ .../repl/commands/builtin-commands.ts | 28 +++++ .../repl/commands/clear-buffered-command.ts | 35 ++++++ .../node-suite/repl/commands/clear-command.ts | 38 +++++++ .../repl/commands/custom-action-receiver.ts | 24 ++++ .../repl/commands/define-function.ts | 24 ++++ .../node-suite/repl/commands/define-object.ts | 24 ++++ .../repl/commands/define-validation.ts | 26 +++++ .../repl/completion/builtin-module.ts | 24 ++++ .../repl/completion/context-property.ts | 25 ++++ .../completion/custom-completer-receiver.ts | 32 ++++++ .../node-suite/repl/completion/global-name.ts | 24 ++++ .../repl/context/context-bindings.ts | 24 ++++ .../node-suite/repl/context/create-context.ts | 26 +++++ .../repl/context/isolated-context.ts | 24 ++++ .../node-suite/repl/context/reset-context.ts | 26 +++++ .../node-suite/repl/context/reset-event.ts | 26 +++++ .../repl/context/underscore-descriptors.ts | 30 +++++ .../node-suite/repl/context/use-global.ts | 24 ++++ .../repl/eval/custom-callback-shape.ts | 32 ++++++ .../node-suite/repl/eval/direct-context.ts | 29 +++++ .../node-suite/repl/eval/direct-success.ts | 28 +++++ .../repl/eval/recoverable-syntax.ts | 30 +++++ .../node-suite/repl/eval/sloppy-mode.ts | 33 ++++++ .../node-suite/repl/eval/syntax-error.ts | 24 ++++ .../node-suite/repl/eval/top-level-await.ts | 28 +++++ .../repl/exports/builtin-module-aliases.ts | 7 ++ .../exports/builtin-modules-descriptors.ts | 12 ++ .../repl/exports/callable-metadata.ts | 5 + .../repl/exports/export-descriptors.ts | 22 ++++ .../repl/exports/is-valid-syntax.ts | 7 ++ .../node-suite/repl/exports/mode-symbols.ts | 13 +++ .../repl/exports/named-default-aliases.ts | 15 +++ .../node-suite/repl/exports/public-keys.ts | 3 + .../repl/history/setup-history-isolated.ts | 36 ++++++ .../repl/history/setup-history-options.ts | 44 +++++++ .../repl/history/setup-history-validation.ts | 26 +++++ .../repl/imports/module-metadata.ts | 33 ------ .../node-suite/repl/lifecycle/close-event.ts | 24 ++++ .../node-suite/repl/lifecycle/emit-result.ts | 23 ++++ .../repl/lifecycle/exit-close-order.ts | 27 +++++ .../node-suite/repl/lifecycle/exit-event.ts | 24 ++++ .../node-suite/repl/lifecycle/once-exit.ts | 25 ++++ .../repl/output/custom-writer-result.ts | 32 ++++++ .../repl/output/default-writer-colors.ts | 22 ++++ .../node-suite/repl/output/display-prompt.ts | 25 ++++ .../repl/output/ignore-undefined.ts | 36 ++++++ .../node-suite/repl/output/set-prompt.ts | 28 +++++ .../node-suite/repl/output/writer-options.ts | 6 + .../start/constructor-options-instance.ts | 22 ++++ .../node-suite/repl/start/custom-completer.ts | 23 ++++ .../node-suite/repl/start/custom-eval.ts | 27 +++++ .../node-suite/repl/start/custom-writer.ts | 22 ++++ .../repl/start/default-properties.ts | 32 ++++++ .../repl/start/domain-option-validation.ts | 25 ++++ .../repl/start/eval-sigint-validation.ts | 25 ++++ .../repl/start/explicit-properties.ts | 39 +++++++ .../node-suite/repl/start/legacy-overload.ts | 20 ++++ .../repl/start/options-return-instance.ts | 22 ++++ .../repl/start/stream-alias-descriptors.ts | 30 +++++ .../repl/start/stream-alias-setters.ts | 26 +++++ .../node-suite/repl/start/stream-aliases.ts | 22 ++++ .../node-suite/repl/start/stream-option.ts | 17 +++ test-parity/node_suite_baseline.json | 4 +- 74 files changed, 1715 insertions(+), 189 deletions(-) delete mode 100644 test-parity/node-suite/repl/async/commands-reset.ts delete mode 100644 test-parity/node-suite/repl/async/scripted-lifecycle.ts delete mode 100644 test-parity/node-suite/repl/async/setup-history.ts create mode 100644 test-parity/node-suite/repl/classes/recoverable-default-error.ts create mode 100644 test-parity/node-suite/repl/classes/recoverable-prototype.ts create mode 100644 test-parity/node-suite/repl/classes/recoverable-shape.ts create mode 100644 test-parity/node-suite/repl/classes/repl-server-constructor-descriptor.ts create mode 100644 test-parity/node-suite/repl/classes/repl-server-inheritance.ts create mode 100644 test-parity/node-suite/repl/classes/repl-server-prototype.ts create mode 100644 test-parity/node-suite/repl/commands/break-command.ts create mode 100644 test-parity/node-suite/repl/commands/builtin-commands.ts create mode 100644 test-parity/node-suite/repl/commands/clear-buffered-command.ts create mode 100644 test-parity/node-suite/repl/commands/clear-command.ts create mode 100644 test-parity/node-suite/repl/commands/custom-action-receiver.ts create mode 100644 test-parity/node-suite/repl/commands/define-function.ts create mode 100644 test-parity/node-suite/repl/commands/define-object.ts create mode 100644 test-parity/node-suite/repl/commands/define-validation.ts create mode 100644 test-parity/node-suite/repl/completion/builtin-module.ts create mode 100644 test-parity/node-suite/repl/completion/context-property.ts create mode 100644 test-parity/node-suite/repl/completion/custom-completer-receiver.ts create mode 100644 test-parity/node-suite/repl/completion/global-name.ts create mode 100644 test-parity/node-suite/repl/context/context-bindings.ts create mode 100644 test-parity/node-suite/repl/context/create-context.ts create mode 100644 test-parity/node-suite/repl/context/isolated-context.ts create mode 100644 test-parity/node-suite/repl/context/reset-context.ts create mode 100644 test-parity/node-suite/repl/context/reset-event.ts create mode 100644 test-parity/node-suite/repl/context/underscore-descriptors.ts create mode 100644 test-parity/node-suite/repl/context/use-global.ts create mode 100644 test-parity/node-suite/repl/eval/custom-callback-shape.ts create mode 100644 test-parity/node-suite/repl/eval/direct-context.ts create mode 100644 test-parity/node-suite/repl/eval/direct-success.ts create mode 100644 test-parity/node-suite/repl/eval/recoverable-syntax.ts create mode 100644 test-parity/node-suite/repl/eval/sloppy-mode.ts create mode 100644 test-parity/node-suite/repl/eval/syntax-error.ts create mode 100644 test-parity/node-suite/repl/eval/top-level-await.ts create mode 100644 test-parity/node-suite/repl/exports/builtin-module-aliases.ts create mode 100644 test-parity/node-suite/repl/exports/builtin-modules-descriptors.ts create mode 100644 test-parity/node-suite/repl/exports/callable-metadata.ts create mode 100644 test-parity/node-suite/repl/exports/export-descriptors.ts create mode 100644 test-parity/node-suite/repl/exports/is-valid-syntax.ts create mode 100644 test-parity/node-suite/repl/exports/mode-symbols.ts create mode 100644 test-parity/node-suite/repl/exports/named-default-aliases.ts create mode 100644 test-parity/node-suite/repl/exports/public-keys.ts create mode 100644 test-parity/node-suite/repl/history/setup-history-isolated.ts create mode 100644 test-parity/node-suite/repl/history/setup-history-options.ts create mode 100644 test-parity/node-suite/repl/history/setup-history-validation.ts delete mode 100644 test-parity/node-suite/repl/imports/module-metadata.ts create mode 100644 test-parity/node-suite/repl/lifecycle/close-event.ts create mode 100644 test-parity/node-suite/repl/lifecycle/emit-result.ts create mode 100644 test-parity/node-suite/repl/lifecycle/exit-close-order.ts create mode 100644 test-parity/node-suite/repl/lifecycle/exit-event.ts create mode 100644 test-parity/node-suite/repl/lifecycle/once-exit.ts create mode 100644 test-parity/node-suite/repl/output/custom-writer-result.ts create mode 100644 test-parity/node-suite/repl/output/default-writer-colors.ts create mode 100644 test-parity/node-suite/repl/output/display-prompt.ts create mode 100644 test-parity/node-suite/repl/output/ignore-undefined.ts create mode 100644 test-parity/node-suite/repl/output/set-prompt.ts create mode 100644 test-parity/node-suite/repl/output/writer-options.ts create mode 100644 test-parity/node-suite/repl/start/constructor-options-instance.ts create mode 100644 test-parity/node-suite/repl/start/custom-completer.ts create mode 100644 test-parity/node-suite/repl/start/custom-eval.ts create mode 100644 test-parity/node-suite/repl/start/custom-writer.ts create mode 100644 test-parity/node-suite/repl/start/default-properties.ts create mode 100644 test-parity/node-suite/repl/start/domain-option-validation.ts create mode 100644 test-parity/node-suite/repl/start/eval-sigint-validation.ts create mode 100644 test-parity/node-suite/repl/start/explicit-properties.ts create mode 100644 test-parity/node-suite/repl/start/legacy-overload.ts create mode 100644 test-parity/node-suite/repl/start/options-return-instance.ts create mode 100644 test-parity/node-suite/repl/start/stream-alias-descriptors.ts create mode 100644 test-parity/node-suite/repl/start/stream-alias-setters.ts create mode 100644 test-parity/node-suite/repl/start/stream-aliases.ts create mode 100644 test-parity/node-suite/repl/start/stream-option.ts diff --git a/test-parity/node-suite/repl/README.md b/test-parity/node-suite/repl/README.md index 227d254168..fc1dfc327b 100644 --- a/test-parity/node-suite/repl/README.md +++ b/test-parity/node-suite/repl/README.md @@ -1,4 +1,105 @@ -# node:repl parity cases +# `node:repl` parity evidence -Focused deterministic cases for Perry's `node:repl` compatibility surface. -These tests use in-memory stream-like objects and avoid terminal editor UX. +This suite contains 68 deterministic, single-contract fixtures for the public +programmatic `node:repl` API. It uses controlled in-memory streams, disables +colors, and never reads from a terminal or user input. + +## Fixed sources + +| Runtime | Source | Revision used | +| ------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| Node.js | `lib/repl.js`, `lib/internal/repl/*`, and the 136 REPL-related files under `test/` | v26.5.0, `bebd1b8d92bf4cc917844d6335ed1ecf9c2a75fb` | +| Deno | `ext/node/polyfills/repl.ts`, `ext/node/polyfills/internal/repl.ts`, and `tests/unit_node/repl_test.ts` | `34c46613cbe20450b74c0e8d4f0fd8f6f781d807` | +| Bun | `src/js/node/repl.js` and `test/js/node/test/common/repl.js` | `43d60f69c95a9f31591165816ced29b83e94673e` | +| Perry | `crates/perry-runtime/src/node_repl.rs` and the API manifest | `563c35951b347aabac3e093efd9c8b2af8ecd5d9` | + +The runnable comparison used Node 26.5.0, Deno 2.9.3, and Bun 1.2.18. Bun's +pinned source has since ported Node's REPL implementation, but the installed +1.2.18 runtime does not yet expose that surface. Source and runtime results are +therefore recorded separately rather than treated as the same claim. + +## Coverage + +| Directory | Contracts | Main Node evidence | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `exports`, `classes` | exports, descriptors, aliases, callable metadata, mode symbols, `Recoverable`, `REPLServer` inheritance and prototype | `lib/repl.js`, `test-repl-options.js`, `test-repl-recoverable.js` | +| `start` | object and legacy overloads, stream aliases, defaults, explicit options, custom callbacks, validation | `lib/repl.js`, `test-repl-options.js`, `test-repl-unsupported-option.js` | +| `context` | isolated and global contexts, REPL bindings, context creation, reset state and reset events | `test-repl-context.js`, `test-repl-reset-event.js` | +| `commands` | built-ins, `.break`, `.clear`, command registration, validation, action receiver and buffered input | `test-repl-definecommand.js`, `lib/repl.js` command setup | +| `eval` | direct callback results, context, filename, custom callback receiver, syntax recovery, sloppy mode and top-level await | `test-repl-custom-eval.js`, `test-repl-recoverable.js`, `test-repl-mode.js`, `test-repl-top-level-await.js` | +| `output` | writer defaults, custom writer output, `ignoreUndefined`, disabled colors and prompt methods | `test-repl-colors.js`, `test-repl-inspect-defaults.js`, `test-repl-setprompt.js` | +| `completion` | custom completer receiver plus stable built-in, global and context-property completion | `test-repl-tab-complete.js`, `test-repl-tab-complete-custom-completer.js` | +| `history` | legacy and object setup forms, bounded history, validation and isolated cleanup | `test-repl-programmatic-history.js`, `test-repl-programmatic-history-setup-history.js` | +| `lifecycle` | `exit`, `close`, once-listeners, emit result and exit-before-close order | `test-repl-close.js`, `test-repl-end-emits-exit.js` | + +The top-level-await fixture settles through the REPL eval callback. It uses no +timer or sleep. History fixtures create one private temporary directory and +remove it in `finally` after the setup callback. + +## Measured results + +Node 26.5.0 passed 68/68 on three runs. Each run produced the same combined +stdout, stderr, exit-code, and fixture-name SHA-256: +`a0e49a26d7348d12b13605321c2d66831a6ba3a70e21f1f9522c92d8eaed1fcc`. + +| Runtime | Pass | Output diff | Error | Compile failure | Crash | Timeout | +| ----------- | ---: | ----------: | ----: | --------------: | ----: | ------: | +| Node 26.5.0 | 68 | 0 | 0 | 0 | 0 | 0 | +| Perry | 11 | 57 | 0 | 0 | 0 | 0 | +| Deno 2.9.3 | 55 | 9 | 4 | n/a | 0 | 0 | +| Bun 1.2.18 | 1 | 2 | 65 | n/a | 0 | 0 | + +Perry produced the same 11/68 result on three focused runs. Its stable passes +cover `Recoverable` construction, `REPLServer` construction, `.clear`, custom +command receiver binding, `exit`/once listeners, prompt output, +`ignoreUndefined`, and both `start()` and `new REPLServer()` instance checks. + +Perry's remaining differences split into two groups: + +- The documented AOT boundary: there is no general JS evaluator, input-driven + loop, completion engine, or top-level-await evaluator. `write()` only handles + numeric literals, context number lookups, and one `+` operation. +- REPL API gaps independent of eval: export and prototype descriptors, legacy + overloads, stream aliases, option validation, full contexts, writers, + completion hooks, history loading, close behavior, and EventEmitter return + values. + +Deno matches most programmatic contracts. Its stable differences cover its extra +terminal-only `editor` command, context module metadata, top-level-await +callback behavior, export descriptors/keys, history loading/options, and the +removed `domain` option. Its four errors are missing named exports for `writer` +and `isValidSyntax` or the corresponding missing descriptor. + +Bun 1.2.18 only matches the built-in-module aliases. Most cases fail at import +time because that release does not expose the named REPL exports. The two +non-error differences are export descriptors and enumerable keys. Current Bun +source contains a newer Node-derived implementation; these runtime results do +not predict that unreleased code's pass count. + +## Exclusions and ownership + +- Real TTY, editor, reverse search, key handling, previews, paste mode, CLI + flags, inspector, signals, watch mode, sockets, ports, timing races and pummel + cases are excluded because they need terminal or process integration. +- `.save` and `.load` execution, interactive history navigation, history file + permissions and large histories are excluded because their primary contract + belongs to filesystem or readline behavior. This suite keeps only REPL setup + and command registration. +- VM execution, context descriptor semantics, console formatting, module + loading, util inspection, process listeners and readline editing remain owned + by the `vm`, `console`, `module`, `util`, `process` and `readline` suites. + REPL fixtures only check that REPL wires those values into its public surface. +- Dynamic imports, referrer paths, stack rendering and require-cache behavior + are excluded because they mix module resolution or volatile paths/stacks with + REPL behavior. +- Async exception routing and promise previews are excluded. Their Node tests + rely on domains, inspector state, process error hooks or timing. The direct + top-level-await callback is the only async case with a clear barrier. + +## Stop criterion + +All 136 Node 26.5.0 REPL-related files and the pinned Deno and Bun sources were +reviewed. The remaining upstream cases require an excluded terminal/process +facility, repeat a contract already covered here, belong to another module, or +need runtime eval that Perry explicitly does not claim. Add another fixture only +when upstream exposes a new deterministic, portable, REPL-owned contract. diff --git a/test-parity/node-suite/repl/async/commands-reset.ts b/test-parity/node-suite/repl/async/commands-reset.ts deleted file mode 100644 index d765dc0db6..0000000000 --- a/test-parity/node-suite/repl/async/commands-reset.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { start } from "node:repl"; - -const input = { - on() {}, - once() {}, - resume() {}, - pause() {}, - setEncoding() {}, - removeListener() {}, -}; -let output = ""; -const out = { - write(chunk: unknown) { - output += String(chunk); - return true; - }, - on() {}, - once() {}, - removeListener() {}, - columns: 80, - isTTY: false, -}; - -const server = start({ input, output: out, terminal: false, prompt: "p> ", useColors: false }); - -server.defineCommand("hello", { - help: "hello help", - action(name: string) { - console.log("action this server:", this === server); - this.outputStream.write("hello " + name.trim() + "\n"); - this.displayPrompt(); - }, -}); - -console.log("displayPrompt return:", server.displayPrompt(true) === undefined); -console.log("clearBuffered return:", server.clearBufferedCommand() === undefined); - -server.on("reset", (ctx: any) => { - console.log("reset event:", ctx === server.context, typeof ctx); - ctx.afterReset = 7; -}); -server.on("exit", () => { - console.log("exit fired"); - console.log("captured:", JSON.stringify(output)); -}); - -server.write(".hello world\n"); -server.write(".clear\n"); -server.write("afterReset + 1\n"); -server.write(".exit\n"); diff --git a/test-parity/node-suite/repl/async/scripted-lifecycle.ts b/test-parity/node-suite/repl/async/scripted-lifecycle.ts deleted file mode 100644 index 85ea652498..0000000000 --- a/test-parity/node-suite/repl/async/scripted-lifecycle.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { start, REPL_MODE_STRICT } from "node:repl"; - -const input = { - on() {}, - once() {}, - resume() {}, - pause() {}, - setEncoding() {}, - removeListener() {}, -}; -let output = ""; -const out = { - write(chunk: unknown) { - output += String(chunk); - return true; - }, - on() {}, - once() {}, - removeListener() {}, - columns: 80, - isTTY: false, -}; - -const server = start({ - input, - output: out, - terminal: false, - prompt: "p> ", - useColors: false, - ignoreUndefined: true, - replMode: REPL_MODE_STRICT, -}); - -server.context.value = 40; -console.log("server ctor:", server.constructor && server.constructor.name); -console.log( - "server methods:", - typeof server.write, - typeof server.defineCommand, - typeof server.displayPrompt, - typeof server.clearBufferedCommand, - typeof server.setupHistory, -); -console.log( - "server flags:", - server.editorMode, - server.useColors, - server.useGlobal, - server.ignoreUndefined, - typeof server.replMode, - server.replMode === REPL_MODE_STRICT, -); -console.log("server context value:", server.context.value); - -server.on("exit", () => { - console.log("exit fired"); - console.log("captured:", JSON.stringify(output)); -}); - -server.write("value + 2\n"); -server.write("undefined\n"); -server.write(".exit\n"); diff --git a/test-parity/node-suite/repl/async/setup-history.ts b/test-parity/node-suite/repl/async/setup-history.ts deleted file mode 100644 index ebbd8a3250..0000000000 --- a/test-parity/node-suite/repl/async/setup-history.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { start } from "node:repl"; - -const input = { - on() {}, - once() {}, - resume() {}, - pause() {}, - setEncoding() {}, - removeListener() {}, -}; -let output = ""; -const out = { - write(chunk: unknown) { - output += String(chunk); - return true; - }, - on() {}, - once() {}, - removeListener() {}, - columns: 80, - isTTY: false, -}; - -const historyPath = "/tmp/perry-repl-history-" + process.pid + ".txt"; -const server = start({ input, output: out, terminal: false, prompt: "p> ", useColors: false }); - -server.on("exit", () => { - console.log("exit fired"); - console.log("captured:", JSON.stringify(output)); -}); - -server.setupHistory(historyPath, (err: unknown, replServer: unknown) => { - console.log("history cb err null:", err === null); - console.log("history same server:", replServer === server); - console.log("history array:", Array.isArray(server.history)); - console.log("history size:", typeof server.historySize, server.historySize > 0); - server.write("1 + 1\n"); - server.write(".exit\n"); -}); diff --git a/test-parity/node-suite/repl/classes/recoverable-default-error.ts b/test-parity/node-suite/repl/classes/recoverable-default-error.ts new file mode 100644 index 0000000000..ac20edf463 --- /dev/null +++ b/test-parity/node-suite/repl/classes/recoverable-default-error.ts @@ -0,0 +1,5 @@ +import { Recoverable } from "node:repl"; + +const error = new Recoverable(); +console.log(error instanceof Recoverable, error instanceof SyntaxError); +console.log(error.err, error.name, error.message); diff --git a/test-parity/node-suite/repl/classes/recoverable-prototype.ts b/test-parity/node-suite/repl/classes/recoverable-prototype.ts new file mode 100644 index 0000000000..b924222bb0 --- /dev/null +++ b/test-parity/node-suite/repl/classes/recoverable-prototype.ts @@ -0,0 +1,16 @@ +import { Recoverable } from "node:repl"; + +console.log(Object.getPrototypeOf(Recoverable) === SyntaxError); +console.log( + Object.getPrototypeOf(Recoverable.prototype) === SyntaxError.prototype, +); +const descriptor = Object.getOwnPropertyDescriptor( + Recoverable.prototype, + "constructor", +)!; +console.log( + descriptor.value === Recoverable, + descriptor.enumerable, + descriptor.configurable, + descriptor.writable, +); diff --git a/test-parity/node-suite/repl/classes/recoverable-shape.ts b/test-parity/node-suite/repl/classes/recoverable-shape.ts new file mode 100644 index 0000000000..99fdb2d694 --- /dev/null +++ b/test-parity/node-suite/repl/classes/recoverable-shape.ts @@ -0,0 +1,9 @@ +import { Recoverable } from "node:repl"; + +const cause = new SyntaxError("incomplete"); +const error = new Recoverable(cause); +console.log(error instanceof Recoverable); +console.log(error instanceof SyntaxError); +console.log(error.err === cause); +console.log(error.name, error.message); +console.log(Object.keys(error).join(",")); diff --git a/test-parity/node-suite/repl/classes/repl-server-constructor-descriptor.ts b/test-parity/node-suite/repl/classes/repl-server-constructor-descriptor.ts new file mode 100644 index 0000000000..f9acfe8706 --- /dev/null +++ b/test-parity/node-suite/repl/classes/repl-server-constructor-descriptor.ts @@ -0,0 +1,12 @@ +import { REPLServer } from "node:repl"; + +const descriptor = Object.getOwnPropertyDescriptor( + REPLServer.prototype, + "constructor", +)!; +console.log(descriptor.value === REPLServer); +console.log( + descriptor.enumerable, + descriptor.configurable, + descriptor.writable, +); diff --git a/test-parity/node-suite/repl/classes/repl-server-inheritance.ts b/test-parity/node-suite/repl/classes/repl-server-inheritance.ts new file mode 100644 index 0000000000..8532c07233 --- /dev/null +++ b/test-parity/node-suite/repl/classes/repl-server-inheritance.ts @@ -0,0 +1,5 @@ +import { REPLServer } from "node:repl"; + +console.log(Object.getPrototypeOf(REPLServer).name); +console.log(Object.getPrototypeOf(REPLServer.prototype).constructor.name); +console.log(Object.getPrototypeOf(REPLServer.prototype) === Object.prototype); diff --git a/test-parity/node-suite/repl/classes/repl-server-prototype.ts b/test-parity/node-suite/repl/classes/repl-server-prototype.ts new file mode 100644 index 0000000000..d71ca5c28e --- /dev/null +++ b/test-parity/node-suite/repl/classes/repl-server-prototype.ts @@ -0,0 +1,27 @@ +import { REPLServer } from "node:repl"; + +for ( + const name of [ + "setupHistory", + "clearBufferedCommand", + "createContext", + "resetContext", + "displayPrompt", + "setPrompt", + "complete", + "completeOnEditorMode", + "defineCommand", + ] +) { + const descriptor = Object.getOwnPropertyDescriptor( + REPLServer.prototype, + name, + ); + console.log( + name, + typeof descriptor?.value, + descriptor?.enumerable, + descriptor?.configurable, + descriptor?.writable, + ); +} diff --git a/test-parity/node-suite/repl/commands/break-command.ts b/test-parity/node-suite/repl/commands/break-command.ts new file mode 100644 index 0000000000..04b10ea3ab --- /dev/null +++ b/test-parity/node-suite/repl/commands/break-command.ts @@ -0,0 +1,33 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +let captured = ""; +const output = { + write(chunk: unknown) { + captured += String(chunk); + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ + input, + output, + terminal: false, + prompt: "p> ", + useColors: false, +}); +captured = ""; +server.write("function unfinished() {\n"); +server.write(".break\n"); +server.write("1 + 1\n"); +console.log(JSON.stringify(captured)); diff --git a/test-parity/node-suite/repl/commands/builtin-commands.ts b/test-parity/node-suite/repl/commands/builtin-commands.ts new file mode 100644 index 0000000000..9a0c71a2b0 --- /dev/null +++ b/test-parity/node-suite/repl/commands/builtin-commands.ts @@ -0,0 +1,28 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +console.log(Object.keys(server.commands).sort().join(",")); +for (const name of ["break", "clear", "exit", "help", "load", "save"]) { + console.log( + name, + typeof server.commands[name].action, + typeof server.commands[name].help, + ); +} diff --git a/test-parity/node-suite/repl/commands/clear-buffered-command.ts b/test-parity/node-suite/repl/commands/clear-buffered-command.ts new file mode 100644 index 0000000000..44f5eb5ce6 --- /dev/null +++ b/test-parity/node-suite/repl/commands/clear-buffered-command.ts @@ -0,0 +1,35 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +let captured = ""; +const output = { + write(chunk: unknown) { + captured += String(chunk); + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ + input, + output, + terminal: false, + prompt: "p> ", + useColors: false, +}); +captured = ""; +server.write("function unfinished() {\n"); +console.log(JSON.stringify(captured)); +captured = ""; +console.log(server.clearBufferedCommand()); +server.write("1 + 1\n"); +console.log(JSON.stringify(captured)); diff --git a/test-parity/node-suite/repl/commands/clear-command.ts b/test-parity/node-suite/repl/commands/clear-command.ts new file mode 100644 index 0000000000..30392fb420 --- /dev/null +++ b/test-parity/node-suite/repl/commands/clear-command.ts @@ -0,0 +1,38 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +let captured = ""; +const output = { + write(chunk: unknown) { + captured += String(chunk); + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ + input, + output, + terminal: false, + prompt: "p> ", + useColors: false, +}); +server.context.marker = 42; +let resetContext: unknown; +server.once("reset", (context: unknown) => { + resetContext = context; +}); +captured = ""; +server.write(".clear\n"); +console.log(server.context.marker); +console.log(resetContext === server.context); +console.log(JSON.stringify(captured)); diff --git a/test-parity/node-suite/repl/commands/custom-action-receiver.ts b/test-parity/node-suite/repl/commands/custom-action-receiver.ts new file mode 100644 index 0000000000..7dfeae69ca --- /dev/null +++ b/test-parity/node-suite/repl/commands/custom-action-receiver.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, prompt: "" }); +server.defineCommand("hello", function (argument: string) { + console.log(this === server, JSON.stringify(argument)); +}); +server.write(".hello one two\n"); diff --git a/test-parity/node-suite/repl/commands/define-function.ts b/test-parity/node-suite/repl/commands/define-function.ts new file mode 100644 index 0000000000..028bf4aabd --- /dev/null +++ b/test-parity/node-suite/repl/commands/define-function.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +function action() {} +console.log(server.defineCommand("hello", action)); +console.log(server.commands.hello.action === action); +console.log(Object.keys(server.commands.hello).join(",")); diff --git a/test-parity/node-suite/repl/commands/define-object.ts b/test-parity/node-suite/repl/commands/define-object.ts new file mode 100644 index 0000000000..c507619f17 --- /dev/null +++ b/test-parity/node-suite/repl/commands/define-object.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +const command = { help: "show help", action() {} }; +console.log(server.defineCommand("hello", command)); +console.log(server.commands.hello === command); +console.log(server.commands.hello.help); diff --git a/test-parity/node-suite/repl/commands/define-validation.ts b/test-parity/node-suite/repl/commands/define-validation.ts new file mode 100644 index 0000000000..0b7c9e80ec --- /dev/null +++ b/test-parity/node-suite/repl/commands/define-validation.ts @@ -0,0 +1,26 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +try { + server.defineCommand("bad", { action: 1 } as any); +} catch (error: any) { + console.log(error.name, error.code); + console.log(error.message); +} diff --git a/test-parity/node-suite/repl/completion/builtin-module.ts b/test-parity/node-suite/repl/completion/builtin-module.ts new file mode 100644 index 0000000000..ea3778ca89 --- /dev/null +++ b/test-parity/node-suite/repl/completion/builtin-module.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.completer("pat", (error: unknown, result: [string[], string]) => { + console.log(error === null); + console.log(result[0].includes("path"), result[1]); +}); diff --git a/test-parity/node-suite/repl/completion/context-property.ts b/test-parity/node-suite/repl/completion/context-property.ts new file mode 100644 index 0000000000..c85addebdd --- /dev/null +++ b/test-parity/node-suite/repl/completion/context-property.ts @@ -0,0 +1,25 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.context.parityUniqueProperty = 1; +server.completer("parityUni", (error: unknown, result: [string[], string]) => { + console.log(error === null); + console.log(result[0].includes("parityUniqueProperty"), result[1]); +}); diff --git a/test-parity/node-suite/repl/completion/custom-completer-receiver.ts b/test-parity/node-suite/repl/completion/custom-completer-receiver.ts new file mode 100644 index 0000000000..3cddbc11bf --- /dev/null +++ b/test-parity/node-suite/repl/completion/custom-completer-receiver.ts @@ -0,0 +1,32 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +let server: any; +server = start({ + input, + output, + terminal: false, + completer(line: string, callback: Function) { + console.log(this === server, line); + callback(null, [["hello"], line]); + }, +}); +server.complete("he", (error: unknown, result: [string[], string]) => { + console.log(error === null, result[0].join(","), result[1]); +}); diff --git a/test-parity/node-suite/repl/completion/global-name.ts b/test-parity/node-suite/repl/completion/global-name.ts new file mode 100644 index 0000000000..e8a1b8a955 --- /dev/null +++ b/test-parity/node-suite/repl/completion/global-name.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.completer("conso", (error: unknown, result: [string[], string]) => { + console.log(error === null); + console.log(result[0].includes("console"), result[1]); +}); diff --git a/test-parity/node-suite/repl/context/context-bindings.ts b/test-parity/node-suite/repl/context/context-bindings.ts new file mode 100644 index 0000000000..bfea843b37 --- /dev/null +++ b/test-parity/node-suite/repl/context/context-bindings.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, useGlobal: false }); +console.log(typeof server.context.require); +console.log(typeof server.context.module, server.context.module.id); +console.log(typeof server.context.Buffer, typeof server.context.process); +console.log(typeof server.context.fs, typeof server.context.path); diff --git a/test-parity/node-suite/repl/context/create-context.ts b/test-parity/node-suite/repl/context/create-context.ts new file mode 100644 index 0000000000..4b0912f92e --- /dev/null +++ b/test-parity/node-suite/repl/context/create-context.ts @@ -0,0 +1,26 @@ +import { Console } from "node:console"; +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, useGlobal: false }); +const context = server.createContext(); +console.log(context !== server.context); +console.log(context.global === context); +console.log(context.console instanceof Console); +console.log(typeof context.require, typeof context.module); diff --git a/test-parity/node-suite/repl/context/isolated-context.ts b/test-parity/node-suite/repl/context/isolated-context.ts new file mode 100644 index 0000000000..1b50b35351 --- /dev/null +++ b/test-parity/node-suite/repl/context/isolated-context.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, useGlobal: false }); +console.log(server.context !== globalThis); +console.log(server.context.global === server.context); +console.log(server.context.Object !== Object); +console.log(server.context.console !== console); diff --git a/test-parity/node-suite/repl/context/reset-context.ts b/test-parity/node-suite/repl/context/reset-context.ts new file mode 100644 index 0000000000..21e4545af2 --- /dev/null +++ b/test-parity/node-suite/repl/context/reset-context.ts @@ -0,0 +1,26 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, useGlobal: false }); +const previous = server.context; +previous.marker = 42; +server.resetContext(); +console.log(server.context !== previous); +console.log(server.context.marker); +console.log(server.context.global === server.context); diff --git a/test-parity/node-suite/repl/context/reset-event.ts b/test-parity/node-suite/repl/context/reset-event.ts new file mode 100644 index 0000000000..3a498c3c95 --- /dev/null +++ b/test-parity/node-suite/repl/context/reset-event.ts @@ -0,0 +1,26 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, useGlobal: false }); +const previous = server.context; +server.on("reset", function (context: any) { + console.log(this === server); + console.log(context === server.context, context !== previous); +}); +server.resetContext(); diff --git a/test-parity/node-suite/repl/context/underscore-descriptors.ts b/test-parity/node-suite/repl/context/underscore-descriptors.ts new file mode 100644 index 0000000000..b6864ec9f1 --- /dev/null +++ b/test-parity/node-suite/repl/context/underscore-descriptors.ts @@ -0,0 +1,30 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +for (const name of ["_", "_error"]) { + const descriptor = Object.getOwnPropertyDescriptor(server.context, name)!; + console.log( + name, + descriptor.enumerable, + descriptor.configurable, + typeof descriptor.get, + typeof descriptor.set, + ); +} diff --git a/test-parity/node-suite/repl/context/use-global.ts b/test-parity/node-suite/repl/context/use-global.ts new file mode 100644 index 0000000000..318d2806a0 --- /dev/null +++ b/test-parity/node-suite/repl/context/use-global.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, useGlobal: true }); +console.log(server.context === globalThis); +const previous = server.context; +server.resetContext(); +console.log(server.context === globalThis, server.context === previous); diff --git a/test-parity/node-suite/repl/eval/custom-callback-shape.ts b/test-parity/node-suite/repl/eval/custom-callback-shape.ts new file mode 100644 index 0000000000..3554bcc8bd --- /dev/null +++ b/test-parity/node-suite/repl/eval/custom-callback-shape.ts @@ -0,0 +1,32 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +let server: any; +server = start({ + input, + output, + terminal: false, + prompt: "", + eval(code: string, context: unknown, filename: string, callback: Function) { + console.log(this === server); + console.log(JSON.stringify(code), context === server.context, filename); + callback(null, 42); + }, +}); +server.write("21 * 2\n"); diff --git a/test-parity/node-suite/repl/eval/direct-context.ts b/test-parity/node-suite/repl/eval/direct-context.ts new file mode 100644 index 0000000000..f7db78bfcb --- /dev/null +++ b/test-parity/node-suite/repl/eval/direct-context.ts @@ -0,0 +1,29 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.context.value = 40; +server.eval( + "value + 2\n", + server.context, + "fixture", + (error: unknown, value: unknown) => { + console.log(error === null, value); + }, +); diff --git a/test-parity/node-suite/repl/eval/direct-success.ts b/test-parity/node-suite/repl/eval/direct-success.ts new file mode 100644 index 0000000000..923aa900ac --- /dev/null +++ b/test-parity/node-suite/repl/eval/direct-success.ts @@ -0,0 +1,28 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.eval( + "20 + 22\n", + server.context, + "fixture", + (error: unknown, value: unknown) => { + console.log(error === null, value); + }, +); diff --git a/test-parity/node-suite/repl/eval/recoverable-syntax.ts b/test-parity/node-suite/repl/eval/recoverable-syntax.ts new file mode 100644 index 0000000000..3bb5867ef3 --- /dev/null +++ b/test-parity/node-suite/repl/eval/recoverable-syntax.ts @@ -0,0 +1,30 @@ +import { Recoverable, start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.eval( + "function unfinished() {\n", + server.context, + "fixture", + (error: any) => { + console.log(error instanceof Recoverable); + console.log(error instanceof SyntaxError); + console.log(error.err instanceof SyntaxError); + }, +); diff --git a/test-parity/node-suite/repl/eval/sloppy-mode.ts b/test-parity/node-suite/repl/eval/sloppy-mode.ts new file mode 100644 index 0000000000..74e3e74257 --- /dev/null +++ b/test-parity/node-suite/repl/eval/sloppy-mode.ts @@ -0,0 +1,33 @@ +import { REPL_MODE_SLOPPY, start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ + input, + output, + terminal: false, + replMode: REPL_MODE_SLOPPY, +}); +server.eval( + "undeclaredSloppy = 7\n", + server.context, + "fixture", + (error: unknown, value: unknown) => { + console.log(error === null, value, server.context.undeclaredSloppy); + }, +); diff --git a/test-parity/node-suite/repl/eval/syntax-error.ts b/test-parity/node-suite/repl/eval/syntax-error.ts new file mode 100644 index 0000000000..9a11181920 --- /dev/null +++ b/test-parity/node-suite/repl/eval/syntax-error.ts @@ -0,0 +1,24 @@ +import { Recoverable, start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.eval("const = 1\n", server.context, "fixture", (error: any) => { + console.log(error instanceof SyntaxError, error instanceof Recoverable); + console.log(error.name); +}); diff --git a/test-parity/node-suite/repl/eval/top-level-await.ts b/test-parity/node-suite/repl/eval/top-level-await.ts new file mode 100644 index 0000000000..2260dc27b3 --- /dev/null +++ b/test-parity/node-suite/repl/eval/top-level-await.ts @@ -0,0 +1,28 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.eval( + "await Promise.resolve(42)\n", + server.context, + "fixture", + (error: unknown, value: unknown) => { + console.log(error === null, value); + }, +); diff --git a/test-parity/node-suite/repl/exports/builtin-module-aliases.ts b/test-parity/node-suite/repl/exports/builtin-module-aliases.ts new file mode 100644 index 0000000000..dc08bfdd6f --- /dev/null +++ b/test-parity/node-suite/repl/exports/builtin-module-aliases.ts @@ -0,0 +1,7 @@ +import repl from "node:repl"; + +console.log(Array.isArray(repl.builtinModules)); +console.log(Array.isArray((repl as any)._builtinLibs)); +console.log(repl.builtinModules === (repl as any)._builtinLibs); +console.log(repl.builtinModules.includes("fs")); +console.log(repl.builtinModules.includes("node:fs")); diff --git a/test-parity/node-suite/repl/exports/builtin-modules-descriptors.ts b/test-parity/node-suite/repl/exports/builtin-modules-descriptors.ts new file mode 100644 index 0000000000..2c9fb523f7 --- /dev/null +++ b/test-parity/node-suite/repl/exports/builtin-modules-descriptors.ts @@ -0,0 +1,12 @@ +import repl from "node:repl"; + +for (const name of ["builtinModules", "_builtinLibs"]) { + const descriptor = Object.getOwnPropertyDescriptor(repl, name)!; + console.log( + name, + descriptor.enumerable, + descriptor.configurable, + typeof descriptor.get, + typeof descriptor.set, + ); +} diff --git a/test-parity/node-suite/repl/exports/callable-metadata.ts b/test-parity/node-suite/repl/exports/callable-metadata.ts new file mode 100644 index 0000000000..a52cbb00f0 --- /dev/null +++ b/test-parity/node-suite/repl/exports/callable-metadata.ts @@ -0,0 +1,5 @@ +import { Recoverable, REPLServer, start, writer } from "node:repl"; + +for (const value of [start, writer, REPLServer, Recoverable]) { + console.log(value.name, value.length); +} diff --git a/test-parity/node-suite/repl/exports/export-descriptors.ts b/test-parity/node-suite/repl/exports/export-descriptors.ts new file mode 100644 index 0000000000..09dc394755 --- /dev/null +++ b/test-parity/node-suite/repl/exports/export-descriptors.ts @@ -0,0 +1,22 @@ +import repl from "node:repl"; + +for ( + const name of [ + "start", + "writer", + "REPLServer", + "REPL_MODE_SLOPPY", + "REPL_MODE_STRICT", + "Recoverable", + "isValidSyntax", + ] +) { + const descriptor = Object.getOwnPropertyDescriptor(repl, name)!; + console.log( + name, + descriptor.enumerable, + descriptor.configurable, + descriptor.writable, + typeof descriptor.value, + ); +} diff --git a/test-parity/node-suite/repl/exports/is-valid-syntax.ts b/test-parity/node-suite/repl/exports/is-valid-syntax.ts new file mode 100644 index 0000000000..0f0b43f610 --- /dev/null +++ b/test-parity/node-suite/repl/exports/is-valid-syntax.ts @@ -0,0 +1,7 @@ +import { isValidSyntax } from "node:repl"; + +console.log(isValidSyntax("1 + 1")); +console.log(isValidSyntax("const value =")); +console.log(isValidSyntax("function value() {")); +console.log(isValidSyntax("await Promise.resolve(1)")); +console.log(isValidSyntax("return 1")); diff --git a/test-parity/node-suite/repl/exports/mode-symbols.ts b/test-parity/node-suite/repl/exports/mode-symbols.ts new file mode 100644 index 0000000000..38ba99f272 --- /dev/null +++ b/test-parity/node-suite/repl/exports/mode-symbols.ts @@ -0,0 +1,13 @@ +import { REPL_MODE_SLOPPY, REPL_MODE_STRICT } from "node:repl"; + +console.log( + typeof REPL_MODE_SLOPPY, + REPL_MODE_SLOPPY.description, + Symbol.keyFor(REPL_MODE_SLOPPY), +); +console.log( + typeof REPL_MODE_STRICT, + REPL_MODE_STRICT.description, + Symbol.keyFor(REPL_MODE_STRICT), +); +console.log(REPL_MODE_SLOPPY === REPL_MODE_STRICT); diff --git a/test-parity/node-suite/repl/exports/named-default-aliases.ts b/test-parity/node-suite/repl/exports/named-default-aliases.ts new file mode 100644 index 0000000000..e136a84ad6 --- /dev/null +++ b/test-parity/node-suite/repl/exports/named-default-aliases.ts @@ -0,0 +1,15 @@ +import repl, * as namespace from "node:repl"; +import { + Recoverable, + REPL_MODE_SLOPPY, + REPL_MODE_STRICT, + REPLServer, + start, +} from "node:repl"; + +console.log(repl === namespace.default); +console.log(repl.start === start); +console.log(repl.REPLServer === REPLServer); +console.log(repl.Recoverable === Recoverable); +console.log(repl.REPL_MODE_SLOPPY === REPL_MODE_SLOPPY); +console.log(repl.REPL_MODE_STRICT === REPL_MODE_STRICT); diff --git a/test-parity/node-suite/repl/exports/public-keys.ts b/test-parity/node-suite/repl/exports/public-keys.ts new file mode 100644 index 0000000000..690bfb7dcc --- /dev/null +++ b/test-parity/node-suite/repl/exports/public-keys.ts @@ -0,0 +1,3 @@ +import repl from "node:repl"; + +console.log(Object.keys(repl).sort().join(",")); diff --git a/test-parity/node-suite/repl/history/setup-history-isolated.ts b/test-parity/node-suite/repl/history/setup-history-isolated.ts new file mode 100644 index 0000000000..98394e3d53 --- /dev/null +++ b/test-parity/node-suite/repl/history/setup-history-isolated.ts @@ -0,0 +1,36 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { start } from "node:repl"; + +const directory = mkdtempSync(join(tmpdir(), "perry-repl-")); +const historyPath = join(directory, "history"); +writeFileSync(historyPath, "2 + 2\n1 + 1\n"); +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.setupHistory(historyPath, (error: unknown, value: unknown) => { + try { + console.log(error === null, value === server); + console.log(server.history.length, server.history[0], server.history[1]); + console.log(server.historySize); + } finally { + if (typeof server.close === "function") server.close(); + rmSync(directory, { recursive: true, force: true }); + } +}); diff --git a/test-parity/node-suite/repl/history/setup-history-options.ts b/test-parity/node-suite/repl/history/setup-history-options.ts new file mode 100644 index 0000000000..8cadaa32a3 --- /dev/null +++ b/test-parity/node-suite/repl/history/setup-history-options.ts @@ -0,0 +1,44 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { start } from "node:repl"; + +const directory = mkdtempSync(join(tmpdir(), "perry-repl-")); +const historyPath = join(directory, "history"); +writeFileSync(historyPath, "3\n2\n1\n"); +const cleanup = () => rmSync(directory, { recursive: true, force: true }); +process.once("exit", cleanup); +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +const result = server.setupHistory({ + filePath: historyPath, + size: 2, + removeHistoryDuplicates: false, +}, (error: unknown, value: unknown) => { + try { + console.log(error === null, value === server); + console.log(server.history.length, server.history.join(",")); + console.log(server.historySize); + } finally { + if (typeof server.close === "function") server.close(); + process.removeListener("exit", cleanup); + cleanup(); + } +}); +console.log(result); diff --git a/test-parity/node-suite/repl/history/setup-history-validation.ts b/test-parity/node-suite/repl/history/setup-history-validation.ts new file mode 100644 index 0000000000..50115473bd --- /dev/null +++ b/test-parity/node-suite/repl/history/setup-history-validation.ts @@ -0,0 +1,26 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +try { + server.setupHistory({ filePath: "", size: "invalid" } as any); +} catch (error: any) { + console.log(error.name, error.code); + console.log(error.message); +} diff --git a/test-parity/node-suite/repl/imports/module-metadata.ts b/test-parity/node-suite/repl/imports/module-metadata.ts deleted file mode 100644 index 53b9c1bbf4..0000000000 --- a/test-parity/node-suite/repl/imports/module-metadata.ts +++ /dev/null @@ -1,33 +0,0 @@ -import repl, { Recoverable, REPLServer, REPL_MODE_SLOPPY, REPL_MODE_STRICT } from "node:repl"; - -const inner = new SyntaxError("more"); -const recoverable = new Recoverable(inner); -const plainSyntax: any = new SyntaxError("plain"); -plainSyntax.err = inner; - -function hasModule(name: string): boolean { - for (const builtin of repl.builtinModules) { - if (builtin === name) { - return true; - } - } - return false; -} - -console.log("builtinModules array:", Array.isArray(repl.builtinModules)); -console.log("builtinModules has fs:", hasModule("fs")); -console.log("builtinModules has node:fs:", hasModule("node:fs")); -console.log( - "modes symbols:", - typeof REPL_MODE_SLOPPY, - typeof REPL_MODE_STRICT, - REPL_MODE_SLOPPY === REPL_MODE_STRICT, -); -console.log("recoverable instanceof Recoverable:", recoverable instanceof Recoverable); -console.log("recoverable instanceof SyntaxError:", recoverable instanceof SyntaxError); -console.log("recoverable err identity:", recoverable.err === inner); -console.log("recoverable err syntax:", recoverable.err instanceof SyntaxError); -console.log("recoverable keys:", Object.keys(recoverable).join(",")); -console.log("recoverable constructor name:", Recoverable.name, Recoverable.length); -console.log("plain syntax recoverable:", plainSyntax instanceof Recoverable); -console.log("plain object repl server:", ({ context: {} } instanceof REPLServer)); diff --git a/test-parity/node-suite/repl/lifecycle/close-event.ts b/test-parity/node-suite/repl/lifecycle/close-event.ts new file mode 100644 index 0000000000..318ffce2a1 --- /dev/null +++ b/test-parity/node-suite/repl/lifecycle/close-event.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +server.on("close", function () { + console.log(this === server, server.closed); +}); +console.log(server.close()); diff --git a/test-parity/node-suite/repl/lifecycle/emit-result.ts b/test-parity/node-suite/repl/lifecycle/emit-result.ts new file mode 100644 index 0000000000..a44596768c --- /dev/null +++ b/test-parity/node-suite/repl/lifecycle/emit-result.ts @@ -0,0 +1,23 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +console.log(server.emit("parity-event")); +server.on("parity-event", () => {}); +console.log(server.emit("parity-event")); diff --git a/test-parity/node-suite/repl/lifecycle/exit-close-order.ts b/test-parity/node-suite/repl/lifecycle/exit-close-order.ts new file mode 100644 index 0000000000..7ec55bf5ae --- /dev/null +++ b/test-parity/node-suite/repl/lifecycle/exit-close-order.ts @@ -0,0 +1,27 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, prompt: "" }); +const events: string[] = []; +server.on("exit", () => events.push("exit")); +server.on("close", () => { + events.push("close"); + console.log(events.join(",")); +}); +server.write(".exit\n"); diff --git a/test-parity/node-suite/repl/lifecycle/exit-event.ts b/test-parity/node-suite/repl/lifecycle/exit-event.ts new file mode 100644 index 0000000000..dd14300b0a --- /dev/null +++ b/test-parity/node-suite/repl/lifecycle/exit-event.ts @@ -0,0 +1,24 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, prompt: "" }); +server.on("exit", function () { + console.log(this === server, arguments.length); +}); +server.write(".exit\n"); diff --git a/test-parity/node-suite/repl/lifecycle/once-exit.ts b/test-parity/node-suite/repl/lifecycle/once-exit.ts new file mode 100644 index 0000000000..d72bfdbd58 --- /dev/null +++ b/test-parity/node-suite/repl/lifecycle/once-exit.ts @@ -0,0 +1,25 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, prompt: "" }); +let count = 0; +server.once("exit", () => count++); +server.emit("exit"); +server.emit("exit"); +console.log(count); diff --git a/test-parity/node-suite/repl/output/custom-writer-result.ts b/test-parity/node-suite/repl/output/custom-writer-result.ts new file mode 100644 index 0000000000..09e94c9862 --- /dev/null +++ b/test-parity/node-suite/repl/output/custom-writer-result.ts @@ -0,0 +1,32 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +let captured = ""; +const output = { + write(chunk: unknown) { + captured += String(chunk); + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ + input, + output, + terminal: false, + prompt: "", + writer(value: unknown) { + return `value:${value}`; + }, +}); +server.write("21 + 21\n"); +console.log(JSON.stringify(captured)); diff --git a/test-parity/node-suite/repl/output/default-writer-colors.ts b/test-parity/node-suite/repl/output/default-writer-colors.ts new file mode 100644 index 0000000000..5c8b591497 --- /dev/null +++ b/test-parity/node-suite/repl/output/default-writer-colors.ts @@ -0,0 +1,22 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, useColors: false }); +console.log(server.useColors); +console.log(server.writer.options.colors); diff --git a/test-parity/node-suite/repl/output/display-prompt.ts b/test-parity/node-suite/repl/output/display-prompt.ts new file mode 100644 index 0000000000..18cff9410c --- /dev/null +++ b/test-parity/node-suite/repl/output/display-prompt.ts @@ -0,0 +1,25 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +let captured = ""; +const output = { + write(chunk: unknown) { + captured += String(chunk); + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, prompt: "p> " }); +captured = ""; +console.log(server.displayPrompt(true)); +console.log(JSON.stringify(captured)); diff --git a/test-parity/node-suite/repl/output/ignore-undefined.ts b/test-parity/node-suite/repl/output/ignore-undefined.ts new file mode 100644 index 0000000000..b3d2f6d23e --- /dev/null +++ b/test-parity/node-suite/repl/output/ignore-undefined.ts @@ -0,0 +1,36 @@ +import { start } from "node:repl"; + +function run(ignoreUndefined: boolean) { + const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, + }; + let captured = ""; + const output = { + write(chunk: unknown) { + captured += String(chunk); + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, + }; + const server = start({ + input, + output, + terminal: false, + prompt: "", + useColors: false, + ignoreUndefined, + }); + server.write("undefined\n"); + return captured; +} + +console.log(JSON.stringify(run(false))); +console.log(JSON.stringify(run(true))); diff --git a/test-parity/node-suite/repl/output/set-prompt.ts b/test-parity/node-suite/repl/output/set-prompt.ts new file mode 100644 index 0000000000..d651e1a642 --- /dev/null +++ b/test-parity/node-suite/repl/output/set-prompt.ts @@ -0,0 +1,28 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +let captured = ""; +const output = { + write(chunk: unknown) { + captured += String(chunk); + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false, prompt: "first> " }); +console.log(server.getPrompt()); +console.log(server.setPrompt("next> ")); +console.log(server.getPrompt()); +captured = ""; +server.displayPrompt(); +console.log(JSON.stringify(captured)); diff --git a/test-parity/node-suite/repl/output/writer-options.ts b/test-parity/node-suite/repl/output/writer-options.ts new file mode 100644 index 0000000000..a5c2e272ad --- /dev/null +++ b/test-parity/node-suite/repl/output/writer-options.ts @@ -0,0 +1,6 @@ +import { writer } from "node:repl"; + +console.log(typeof writer.options); +console.log(writer.options.showProxy); +console.log(typeof writer.options.depth, typeof writer.options.colors); +console.log(writer({ value: 42 })); diff --git a/test-parity/node-suite/repl/start/constructor-options-instance.ts b/test-parity/node-suite/repl/start/constructor-options-instance.ts new file mode 100644 index 0000000000..1a4a1b5ad9 --- /dev/null +++ b/test-parity/node-suite/repl/start/constructor-options-instance.ts @@ -0,0 +1,22 @@ +import { REPLServer } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = new REPLServer({ input, output, terminal: false }); +console.log(server instanceof REPLServer); +console.log(server.constructor === REPLServer); diff --git a/test-parity/node-suite/repl/start/custom-completer.ts b/test-parity/node-suite/repl/start/custom-completer.ts new file mode 100644 index 0000000000..597143e725 --- /dev/null +++ b/test-parity/node-suite/repl/start/custom-completer.ts @@ -0,0 +1,23 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const completer = (_line: string, callback: Function) => + callback(null, [[], ""]); +const server = start({ input, output, terminal: false, completer }); +console.log(server.completer === completer); diff --git a/test-parity/node-suite/repl/start/custom-eval.ts b/test-parity/node-suite/repl/start/custom-eval.ts new file mode 100644 index 0000000000..38aed4f286 --- /dev/null +++ b/test-parity/node-suite/repl/start/custom-eval.ts @@ -0,0 +1,27 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const evaluate = ( + _code: string, + _context: unknown, + _filename: string, + callback: Function, +) => callback(null, 1); +const server = start({ input, output, terminal: false, eval: evaluate }); +console.log(typeof server.eval, server.eval === evaluate); diff --git a/test-parity/node-suite/repl/start/custom-writer.ts b/test-parity/node-suite/repl/start/custom-writer.ts new file mode 100644 index 0000000000..76f08428ce --- /dev/null +++ b/test-parity/node-suite/repl/start/custom-writer.ts @@ -0,0 +1,22 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const writer = (value: unknown) => String(value); +const server = start({ input, output, terminal: false, writer }); +console.log(server.writer === writer); diff --git a/test-parity/node-suite/repl/start/default-properties.ts b/test-parity/node-suite/repl/start/default-properties.ts new file mode 100644 index 0000000000..c30cdcb242 --- /dev/null +++ b/test-parity/node-suite/repl/start/default-properties.ts @@ -0,0 +1,32 @@ +import { REPL_MODE_SLOPPY, start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +console.log( + server.terminal, + server.useColors, + server.useGlobal, + server.ignoreUndefined, +); +console.log( + server.editorMode, + server.breakEvalOnSigint, + server.allowBlockingCompletions, +); +console.log(server.replMode === REPL_MODE_SLOPPY, server.historySize); diff --git a/test-parity/node-suite/repl/start/domain-option-validation.ts b/test-parity/node-suite/repl/start/domain-option-validation.ts new file mode 100644 index 0000000000..c48ce1f32d --- /dev/null +++ b/test-parity/node-suite/repl/start/domain-option-validation.ts @@ -0,0 +1,25 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +try { + start({ input, output, terminal: false, domain: false } as any); +} catch (error: any) { + console.log(error.name, error.code); + console.log(error.message); +} diff --git a/test-parity/node-suite/repl/start/eval-sigint-validation.ts b/test-parity/node-suite/repl/start/eval-sigint-validation.ts new file mode 100644 index 0000000000..9474b73034 --- /dev/null +++ b/test-parity/node-suite/repl/start/eval-sigint-validation.ts @@ -0,0 +1,25 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +try { + start({ input, output, terminal: false, breakEvalOnSigint: true, eval() {} }); +} catch (error: any) { + console.log(error.name, error.code); + console.log(error.message); +} diff --git a/test-parity/node-suite/repl/start/explicit-properties.ts b/test-parity/node-suite/repl/start/explicit-properties.ts new file mode 100644 index 0000000000..79dbfd76be --- /dev/null +++ b/test-parity/node-suite/repl/start/explicit-properties.ts @@ -0,0 +1,39 @@ +import { REPL_MODE_STRICT, start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ + input, + output, + terminal: false, + useColors: true, + useGlobal: true, + ignoreUndefined: true, + breakEvalOnSigint: true, + allowBlockingCompletions: true, + replMode: REPL_MODE_STRICT, + historySize: 47, +}); +console.log( + server.terminal, + server.useColors, + server.useGlobal, + server.ignoreUndefined, +); +console.log(server.breakEvalOnSigint, server.allowBlockingCompletions); +console.log(server.replMode === REPL_MODE_STRICT, server.historySize); diff --git a/test-parity/node-suite/repl/start/legacy-overload.ts b/test-parity/node-suite/repl/start/legacy-overload.ts new file mode 100644 index 0000000000..3dde554009 --- /dev/null +++ b/test-parity/node-suite/repl/start/legacy-overload.ts @@ -0,0 +1,20 @@ +import { start } from "node:repl"; + +let captured = ""; +const stream = { + write(chunk: unknown) { + captured += String(chunk); + return true; + }, + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, + isTTY: false, +}; +const server = start("legacy> ", stream as any); +console.log(server.input === stream, server.output === stream); +console.log(server.getPrompt()); +console.log(JSON.stringify(captured)); diff --git a/test-parity/node-suite/repl/start/options-return-instance.ts b/test-parity/node-suite/repl/start/options-return-instance.ts new file mode 100644 index 0000000000..5dc342538e --- /dev/null +++ b/test-parity/node-suite/repl/start/options-return-instance.ts @@ -0,0 +1,22 @@ +import { REPLServer, start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +console.log(server instanceof REPLServer); +console.log(server.constructor === REPLServer); diff --git a/test-parity/node-suite/repl/start/stream-alias-descriptors.ts b/test-parity/node-suite/repl/start/stream-alias-descriptors.ts new file mode 100644 index 0000000000..3de8ade4cb --- /dev/null +++ b/test-parity/node-suite/repl/start/stream-alias-descriptors.ts @@ -0,0 +1,30 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +for (const name of ["inputStream", "outputStream"]) { + const descriptor = Object.getOwnPropertyDescriptor(server, name)!; + console.log( + name, + descriptor.enumerable, + descriptor.configurable, + typeof descriptor.get, + typeof descriptor.set, + ); +} diff --git a/test-parity/node-suite/repl/start/stream-alias-setters.ts b/test-parity/node-suite/repl/start/stream-alias-setters.ts new file mode 100644 index 0000000000..72e09f91f6 --- /dev/null +++ b/test-parity/node-suite/repl/start/stream-alias-setters.ts @@ -0,0 +1,26 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +const nextInput = { marker: "input", pause() {} }; +const nextOutput = { marker: "output" }; +server.inputStream = nextInput as any; +server.outputStream = nextOutput as any; +console.log(server.input === nextInput, server.inputStream === nextInput); +console.log(server.output === nextOutput, server.outputStream === nextOutput); diff --git a/test-parity/node-suite/repl/start/stream-aliases.ts b/test-parity/node-suite/repl/start/stream-aliases.ts new file mode 100644 index 0000000000..030c5c3b91 --- /dev/null +++ b/test-parity/node-suite/repl/start/stream-aliases.ts @@ -0,0 +1,22 @@ +import { start } from "node:repl"; + +const input = { + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, +}; +const output = { + write() { + return true; + }, + on() {}, + once() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ input, output, terminal: false }); +console.log(server.input === input, server.inputStream === input); +console.log(server.output === output, server.outputStream === output); diff --git a/test-parity/node-suite/repl/start/stream-option.ts b/test-parity/node-suite/repl/start/stream-option.ts new file mode 100644 index 0000000000..615b178ad1 --- /dev/null +++ b/test-parity/node-suite/repl/start/stream-option.ts @@ -0,0 +1,17 @@ +import { start } from "node:repl"; + +const stream = { + write() { + return true; + }, + on() {}, + once() {}, + resume() {}, + pause() {}, + setEncoding() {}, + removeListener() {}, + isTTY: false, +}; +const server = start({ stream, terminal: false } as any); +console.log(server.input === stream, server.output === stream); +console.log(server.inputStream === stream, server.outputStream === stream); diff --git a/test-parity/node_suite_baseline.json b/test-parity/node_suite_baseline.json index b2287fc0bc..b6d33bf6b9 100644 --- a/test-parity/node_suite_baseline.json +++ b/test-parity/node_suite_baseline.json @@ -142,8 +142,8 @@ "total": 12 }, "repl": { - "pass": 4, - "total": 4 + "pass": 11, + "total": 68 }, "sea": { "pass": 1, From b18982da8b8ac5cbf4a96914d0cdeef2c62e57b2 Mon Sep 17 00:00:00 2001 From: TheHypnoo Date: Mon, 27 Jul 2026 09:07:11 +0200 Subject: [PATCH 2/2] test(repl): address parity review findings --- test-parity/node-suite/repl/README.md | 12 ++++++++---- .../node-suite/repl/commands/builtin-commands.ts | 7 ++++--- .../node-suite/repl/commands/define-function.ts | 5 +++-- .../node-suite/repl/commands/define-object.ts | 5 +++-- .../node-suite/repl/context/context-bindings.ts | 3 ++- test-parity/node-suite/repl/eval/syntax-error.ts | 2 +- .../repl/history/setup-history-isolated.ts | 8 +++++++- .../node-suite/repl/history/setup-history-options.ts | 9 ++++++--- 8 files changed, 34 insertions(+), 17 deletions(-) diff --git a/test-parity/node-suite/repl/README.md b/test-parity/node-suite/repl/README.md index fc1dfc327b..1c8b3d50ca 100644 --- a/test-parity/node-suite/repl/README.md +++ b/test-parity/node-suite/repl/README.md @@ -1,8 +1,8 @@ # `node:repl` parity evidence -This suite contains 68 deterministic, single-contract fixtures for the public -programmatic `node:repl` API. It uses controlled in-memory streams, disables -colors, and never reads from a terminal or user input. +This suite contains 68 deterministic, single-contract fixtures for programmatic +`node:repl` behavior. It uses controlled in-memory streams, disables colors, and +never reads from a terminal or user input. ## Fixed sources @@ -18,6 +18,10 @@ pinned source has since ported Node's REPL implementation, but the installed 1.2.18 runtime does not yet expose that surface. Source and runtime results are therefore recorded separately rather than treated as the same claim. +Node 26.5.0 implements and tests `allowBlockingCompletions`, but its REPL API +documentation does not list the option. The property fixtures record it as an +implementation contract rather than a public API claim. + ## Coverage | Directory | Contracts | Main Node evidence | @@ -34,7 +38,7 @@ therefore recorded separately rather than treated as the same claim. The top-level-await fixture settles through the REPL eval callback. It uses no timer or sleep. History fixtures create one private temporary directory and -remove it in `finally` after the setup callback. +remove it at process shutdown, after REPL close and history-handle work settle. ## Measured results diff --git a/test-parity/node-suite/repl/commands/builtin-commands.ts b/test-parity/node-suite/repl/commands/builtin-commands.ts index 9a0c71a2b0..557b26172d 100644 --- a/test-parity/node-suite/repl/commands/builtin-commands.ts +++ b/test-parity/node-suite/repl/commands/builtin-commands.ts @@ -18,11 +18,12 @@ const output = { isTTY: false, }; const server = start({ input, output, terminal: false }); -console.log(Object.keys(server.commands).sort().join(",")); +const commands = server.commands ?? {}; +console.log(Object.keys(commands).sort().join(",")); for (const name of ["break", "clear", "exit", "help", "load", "save"]) { console.log( name, - typeof server.commands[name].action, - typeof server.commands[name].help, + typeof commands[name]?.action, + typeof commands[name]?.help, ); } diff --git a/test-parity/node-suite/repl/commands/define-function.ts b/test-parity/node-suite/repl/commands/define-function.ts index 028bf4aabd..d7e1deaf55 100644 --- a/test-parity/node-suite/repl/commands/define-function.ts +++ b/test-parity/node-suite/repl/commands/define-function.ts @@ -20,5 +20,6 @@ const output = { const server = start({ input, output, terminal: false }); function action() {} console.log(server.defineCommand("hello", action)); -console.log(server.commands.hello.action === action); -console.log(Object.keys(server.commands.hello).join(",")); +const helloCommand = server.commands?.hello; +console.log(helloCommand?.action === action); +console.log(Object.keys(helloCommand ?? {}).join(",")); diff --git a/test-parity/node-suite/repl/commands/define-object.ts b/test-parity/node-suite/repl/commands/define-object.ts index c507619f17..2802bcf81d 100644 --- a/test-parity/node-suite/repl/commands/define-object.ts +++ b/test-parity/node-suite/repl/commands/define-object.ts @@ -20,5 +20,6 @@ const output = { const server = start({ input, output, terminal: false }); const command = { help: "show help", action() {} }; console.log(server.defineCommand("hello", command)); -console.log(server.commands.hello === command); -console.log(server.commands.hello.help); +const helloCommand = server.commands?.hello; +console.log(helloCommand === command); +console.log(helloCommand?.help); diff --git a/test-parity/node-suite/repl/context/context-bindings.ts b/test-parity/node-suite/repl/context/context-bindings.ts index bfea843b37..0bb25778f5 100644 --- a/test-parity/node-suite/repl/context/context-bindings.ts +++ b/test-parity/node-suite/repl/context/context-bindings.ts @@ -19,6 +19,7 @@ const output = { }; const server = start({ input, output, terminal: false, useGlobal: false }); console.log(typeof server.context.require); -console.log(typeof server.context.module, server.context.module.id); +const contextModule = server.context.module; +console.log(typeof contextModule, contextModule?.id); console.log(typeof server.context.Buffer, typeof server.context.process); console.log(typeof server.context.fs, typeof server.context.path); diff --git a/test-parity/node-suite/repl/eval/syntax-error.ts b/test-parity/node-suite/repl/eval/syntax-error.ts index 9a11181920..20fc7175c2 100644 --- a/test-parity/node-suite/repl/eval/syntax-error.ts +++ b/test-parity/node-suite/repl/eval/syntax-error.ts @@ -20,5 +20,5 @@ const output = { const server = start({ input, output, terminal: false }); server.eval("const = 1\n", server.context, "fixture", (error: any) => { console.log(error instanceof SyntaxError, error instanceof Recoverable); - console.log(error.name); + console.log(error?.name); }); diff --git a/test-parity/node-suite/repl/history/setup-history-isolated.ts b/test-parity/node-suite/repl/history/setup-history-isolated.ts index 98394e3d53..dcb9bde205 100644 --- a/test-parity/node-suite/repl/history/setup-history-isolated.ts +++ b/test-parity/node-suite/repl/history/setup-history-isolated.ts @@ -6,6 +6,13 @@ import { start } from "node:repl"; const directory = mkdtempSync(join(tmpdir(), "perry-repl-")); const historyPath = join(directory, "history"); writeFileSync(historyPath, "2 + 2\n1 + 1\n"); +const cleanup = () => { + process.removeListener("beforeExit", cleanup); + process.removeListener("exit", cleanup); + rmSync(directory, { recursive: true, force: true }); +}; +process.once("beforeExit", cleanup); +process.once("exit", cleanup); const input = { on() {}, once() {}, @@ -31,6 +38,5 @@ server.setupHistory(historyPath, (error: unknown, value: unknown) => { console.log(server.historySize); } finally { if (typeof server.close === "function") server.close(); - rmSync(directory, { recursive: true, force: true }); } }); diff --git a/test-parity/node-suite/repl/history/setup-history-options.ts b/test-parity/node-suite/repl/history/setup-history-options.ts index 8cadaa32a3..71ab294526 100644 --- a/test-parity/node-suite/repl/history/setup-history-options.ts +++ b/test-parity/node-suite/repl/history/setup-history-options.ts @@ -6,7 +6,12 @@ import { start } from "node:repl"; const directory = mkdtempSync(join(tmpdir(), "perry-repl-")); const historyPath = join(directory, "history"); writeFileSync(historyPath, "3\n2\n1\n"); -const cleanup = () => rmSync(directory, { recursive: true, force: true }); +const cleanup = () => { + process.removeListener("beforeExit", cleanup); + process.removeListener("exit", cleanup); + rmSync(directory, { recursive: true, force: true }); +}; +process.once("beforeExit", cleanup); process.once("exit", cleanup); const input = { on() {}, @@ -37,8 +42,6 @@ const result = server.setupHistory({ console.log(server.historySize); } finally { if (typeof server.close === "function") server.close(); - process.removeListener("exit", cleanup); - cleanup(); } }); console.log(result);