mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +00:00
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>
This commit is contained in:
@@ -674,14 +674,34 @@ pub const Repl = struct {
|
||||
|
||||
// Check if this is a property access
|
||||
if (strings.lastIndexOfChar(word, '.')) |dot_pos| {
|
||||
// Property completion
|
||||
// Property completion - get object properties from JSC
|
||||
const obj_name = word[0..dot_pos];
|
||||
const prop_prefix = word[dot_pos + 1 ..];
|
||||
|
||||
// Get object from global
|
||||
_ = obj_name;
|
||||
_ = prop_prefix;
|
||||
// TODO: Get object properties from JSC
|
||||
// Try to get the object by evaluating the path
|
||||
const obj_value = self.getObjectForPath(obj_name) orelse return completions.toOwnedSlice(self.allocator);
|
||||
|
||||
// Get property names from JSC
|
||||
if (obj_value.isObject()) {
|
||||
if (obj_value.getObject()) |js_obj| {
|
||||
var prop_iter = jsc.JSPropertyIterator(.{
|
||||
.skip_empty_name = true,
|
||||
.include_value = false,
|
||||
.own_properties_only = false, // Include prototype properties
|
||||
}).init(self.global, js_obj) catch return completions.toOwnedSlice(self.allocator);
|
||||
defer prop_iter.deinit();
|
||||
|
||||
while (prop_iter.next() catch null) |name| {
|
||||
const name_str = name.toOwnedSlice(self.allocator) catch continue;
|
||||
// Filter by prefix
|
||||
if (prop_prefix.len == 0 or strings.startsWith(name_str, prop_prefix)) {
|
||||
try completions.append(self.allocator, name_str);
|
||||
} else {
|
||||
self.allocator.free(name_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Global completion
|
||||
// Add JavaScript globals
|
||||
@@ -750,6 +770,25 @@ pub const Repl = struct {
|
||||
c == '_' or c == '$';
|
||||
}
|
||||
|
||||
/// Resolve an object path like "Bun.file" or "console" to a JSValue
|
||||
fn getObjectForPath(self: *Self, path: []const u8) ?JSValue {
|
||||
if (path.len == 0) return null;
|
||||
|
||||
// Split path by dots and navigate
|
||||
var current = self.global.toJSValue();
|
||||
var it = std.mem.splitScalar(u8, path, '.');
|
||||
|
||||
while (it.next()) |segment| {
|
||||
if (segment.len == 0) continue;
|
||||
|
||||
// Get property from current object
|
||||
const prop = current.get(self.global, segment) catch return null;
|
||||
current = prop orelse return null;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Execution
|
||||
// ========================================================================
|
||||
|
||||
@@ -266,4 +266,42 @@ describe("bun repl", () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user