Files
bun.sh/test/cli/repl.test.ts
Claude Bot 261f429fd1 feat(repl): add JSC-based autocomplete for object properties
The REPL now provides intelligent autocomplete for object properties
by dynamically querying the JSC runtime. When typing `obj.` and pressing
Tab, the REPL will show available properties from the actual object.

Features:
- Property completion for any object (e.g., `Bun.`, `console.`)
- Navigates nested paths (e.g., `Bun.file.`)
- Includes both own properties and prototype chain

Also adds tests for class persistence and destructuring to verify
the full AST transforms work correctly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 20:44:24 +00:00

308 lines
8.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
describe("bun repl", () => {
test("evaluates simple expressions", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("1 + 2\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("supports Bun globals", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("typeof Bun\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("object");
expect(exitCode).toBe(0);
});
test("Bun.version is available", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("Bun.version\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should contain a version string
expect(stdout).toMatch(/\d+\.\d+\.\d+/);
expect(exitCode).toBe(0);
});
test("let declarations persist across lines", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("let x = 5\n");
proc.stdin.write("x * 2\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("10");
expect(exitCode).toBe(0);
});
test("const declarations persist across lines", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("const y = 7\n");
proc.stdin.write("y + 3\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("10");
expect(exitCode).toBe(0);
});
test("function declarations persist", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("function add(a, b) { return a + b }\n");
proc.stdin.write("add(2, 3)\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("5");
expect(exitCode).toBe(0);
});
test(".help command works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write(".help\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("REPL Commands");
expect(stdout).toContain(".exit");
expect(stdout).toContain(".clear");
expect(exitCode).toBe(0);
});
test(".timing command toggles timing display", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write(".timing\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("Timing");
expect(exitCode).toBe(0);
});
test("error handling shows error message", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("throw new Error('test error')\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Error should be displayed in stderr
const output = stdout + stderr;
expect(output).toContain("test error");
expect(exitCode).toBe(0);
});
test("object literals are displayed", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("({ foo: 'bar' })\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("foo");
expect(stdout).toContain("bar");
expect(exitCode).toBe(0);
});
test("arrays are displayed", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("[1, 2, 3]\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("1");
expect(stdout).toContain("2");
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("undefined is not printed for statements", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("let z = 10\n");
proc.stdin.write("z\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The second line should show 10
expect(stdout).toContain("10");
expect(exitCode).toBe(0);
});
test("multiline input with semicolons", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("var a = 1; var b = 2; a + b\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
test("async/await works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("await Promise.resolve(42)\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("42");
expect(exitCode).toBe(0);
});
test("class declarations persist", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("class Calculator { add(a, b) { return a + b } }\n");
proc.stdin.write("new Calculator().add(3, 7)\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("10");
expect(exitCode).toBe(0);
});
test("destructuring works", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "repl"],
env: bunEnv,
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
});
proc.stdin.write("const { a, b } = { a: 1, b: 2 }\n");
proc.stdin.write("a + b\n");
proc.stdin.end();
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("3");
expect(exitCode).toBe(0);
});
});