diff --git a/test-parity/node-suite/repl/README.md b/test-parity/node-suite/repl/README.md index 227d254168..1c8b3d50ca 100644 --- a/test-parity/node-suite/repl/README.md +++ b/test-parity/node-suite/repl/README.md @@ -1,4 +1,109 @@ -# 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 programmatic +`node:repl` behavior. 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. + +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 | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `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 at process shutdown, after REPL close and history-handle work settle. + +## 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..557b26172d --- /dev/null +++ b/test-parity/node-suite/repl/commands/builtin-commands.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 }); +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 commands[name]?.action, + typeof 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..d7e1deaf55 --- /dev/null +++ b/test-parity/node-suite/repl/commands/define-function.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 }); +function action() {} +console.log(server.defineCommand("hello", action)); +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 new file mode 100644 index 0000000000..2802bcf81d --- /dev/null +++ b/test-parity/node-suite/repl/commands/define-object.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 }); +const command = { help: "show help", action() {} }; +console.log(server.defineCommand("hello", command)); +const helloCommand = server.commands?.hello; +console.log(helloCommand === command); +console.log(helloCommand?.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..0bb25778f5 --- /dev/null +++ b/test-parity/node-suite/repl/context/context-bindings.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, useGlobal: false }); +console.log(typeof server.context.require); +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/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..20fc7175c2 --- /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..dcb9bde205 --- /dev/null +++ b/test-parity/node-suite/repl/history/setup-history-isolated.ts @@ -0,0 +1,42 @@ +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 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() {}, + 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(); + } +}); 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..71ab294526 --- /dev/null +++ b/test-parity/node-suite/repl/history/setup-history-options.ts @@ -0,0 +1,47 @@ +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 = () => { + 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() {}, + 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(); + } +}); +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,