Skip to content

Conversation

@dylan-conway
Copy link
Member

@dylan-conway dylan-conway commented Aug 27, 2025

What does this PR do?

This PR adds Bun.YAML.stringify. The stringifier will double quote strings only when necessary (looks for keywords, numbers, or containing non-printable or escaped characters). Anchors and aliases are detected by object equality, and anchor name is chosen from property name, array item, or the root collection.

import { YAML } from "bun"

YAML.stringify(null) // null
YAML.stringify("hello YAML"); // "hello YAML"
YAML.stringify("123.456"); // "\"123.456\""

// anchors and aliases
const userInfo = { name: "bun" };
const obj = { user1: { userInfo }, user2: { userInfo } };
YAML.stringify(obj, null, 2);
// # output
// user1: 
//   userInfo: 
//     &userInfo
//     name: bun
// user2: 
//   userInfo: 
//     *userInfo

// will handle cycles
const obj = {};
obj.cycle = obj;
YAML.stringify(obj, null, 2);
// # output
// &root
// cycle:
//   *root

// default no space
const obj = { one: { two: "three" } };
YAML.stringify(obj);
// # output
// {one: {two: three}}

How did you verify your code works?

Added tests for basic use and edgecases

Summary by CodeRabbit

  • New Features

    • Added YAML.stringify to the YAML API, producing YAML from JavaScript values with quoting, anchors, and indentation support.
  • Improvements

    • YAML.parse now accepts a wider range of inputs, including Buffer, ArrayBuffer, TypedArrays, DataView, Blob/File, and SharedArrayBuffer, with better error propagation and stack protection.
  • Tests

    • Extensive new tests for YAML.parse and YAML.stringify across data types, edge cases, anchors/aliases, deep nesting, and round-trip scenarios.
  • Chores

    • Added a YAML stringify benchmark script covering multiple libraries and data shapes.

@robobun
Copy link
Collaborator

robobun commented Aug 27, 2025

Updated 9:16 PM PT - Aug 29th, 2025

@dylan-conway, your commit 45bb398 has 2 failures in Build #24321:


🧪   To try this PR locally:

bunx bun-pr 22183

That installs a local version of the PR into your bun-22183 executable, so you can run:

bun-22183 --bun

Claude Bot and others added 12 commits August 27, 2025 09:43
- Add basic data type tests (null, boolean, number, string)
- Add string quoting tests for YAML special characters
- Add array and object serialization tests
- Add comprehensive round-trip tests ensuring parse/stringify compatibility
- Add reference handling tests for anchors and aliases
- Add edge case tests (BigInt errors, function handling, sparse arrays)
- Add error handling tests (replacer parameter rejection)
- Add real-world configuration examples
- Skip self-referencing test to avoid build issues

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
- std.unicode instances reduced from 33 to 27
- undefined field defaults reduced from 260 to 259

This is a positive change - the banned words were reduced in the codebase.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@dylan-conway dylan-conway requested a review from alii August 28, 2025 04:10
@oven-sh oven-sh locked and limited conversation to collaborators Aug 28, 2025
@oven-sh oven-sh unlocked this conversation Aug 28, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 30, 2025

Walkthrough

Adds YAML.stringify to the YAML API with a full Zig-based serializer, anchor/alias handling, quoting, and indentation controls. Introduces a cross-language StringBuilder binding (Zig/C++) used by stringify. Enhances parse to accept Blob/buffer inputs. Adds unwrapping of boxed primitives, iterator throw-scope fixes, and dynamic CatchScope sizing. Updates build files, typings, benchmarks, and tests.

Changes

Cohort / File(s) Summary
YAML stringify feature
src/bun.js/api/YAMLObject.zig
Implements YAML.stringify, anchor/alias tracking, quoting rules, indentation modes; registers stringify on YAMLObject; extends parse to accept BlobOrStringOrBuffer; stack overflow/error propagation updates.
StringBuilder bindings (Zig/C++)
src/bun.js/bindings/StringBuilder.zig, src/bun.js/bindings/StringBuilderBinding.cpp, src/bun.js/bindings/WTF.zig, src/bun.js/bindings/headers-handwritten.h, src/bun.js/bindings/helpers.h, src/bun.js/bindings/BunString.cpp
Adds a WTF::StringBuilder binding with init/deinit/append/toString/capacity; exposes via WTF.StringBuilder; integrates BunString and ZigString append helpers; exports C entry points.
Build system updates
cmake/sources/CxxSources.txt, cmake/sources/ZigSources.txt
Includes new binding sources: StringBuilderBinding.cpp and StringBuilder.zig in build lists.
Type declarations and tests
packages/bun-types/bun.d.ts, test/integration/bun-types/fixture/yaml.ts, test/js/bun/yaml/yaml.test.ts
Adds Bun.YAML.stringify typings; extends TS fixture with positive/negative cases; migrates tests to YAML import, adds broad parse input coverage and extensive stringify tests.
Benchmarks
bench/yaml/yaml-stringify.mjs
Adds benchmark comparing Bun.YAML.stringify (if available), js-yaml, and yaml across varied data shapes; groups and runs benchmarks.
JSCore interop and exception handling
src/bun.js/bindings/bindings.cpp, src/bun.js/bindings/JSValue.zig, src/bun.js/bindings/JSPropertyIterator.cpp, src/bun.js/bindings/CatchScopeBinding.cpp, src/bun.js/bindings/CatchScope.zig
Adds unwrapBoxedPrimitive (C++ and Zig extern) for Number/String/Boolean/BigInt objects; refactors property iterator to use ThrowScope consistently; introduces configurable CatchScope size/alignment with compile-time checks and dynamic Zig layout.
Core error alias
src/bun.zig
Makes StackOverflow error alias public.
String internals
src/string.zig
Adjusts charAt for 8-bit WTF strings to use latin1Slice; removes public methods charAtU8 and SliceWithUnderlyingString.sliceZ.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant JS as JS Caller
  participant YAML as YAML.stringify
  participant SB as WTF.StringBuilder (binding)
  participant VM as JS VM/ThrowScope

  JS->>YAML: stringify(value, replacer?, space?)
  YAML->>VM: open Catch/Throw scopes
  YAML->>YAML: Analyze graph (anchors/aliases)
  YAML->>SB: init()
  loop Serialize nodes
    YAML->>YAML: Decide quoting/indentation
    YAML->>SB: append(...variant...)
  end
  YAML->>SB: toString(global)
  SB-->>YAML: EncodedJSValue (string) or throw OOM
  YAML-->>JS: YAML string
  alt error (stack overflow / JS error)
    YAML->>VM: propagate exception
    VM-->>JS: throw
  end
Loading
sequenceDiagram
  autonumber
  participant JS as JS Caller
  participant YAML as YAML.parse
  participant IO as Blob/Buffer views
  participant VM as JS VM

  JS->>YAML: parse(input: Blob|String|BufferView|...)
  YAML->>IO: BlobOrStringOrBuffer.fromJS
  alt non-string input
    YAML->>YAML: convert to string (as needed)
  end
  YAML->>VM: parse YAML text
  VM-->>JS: JS value
Loading
sequenceDiagram
  autonumber
  participant JS as JSValue
  participant Bind as unwrapBoxedPrimitive (C++)
  participant Zig as JSValue.zig

  JS->>Zig: unwrapBoxedPrimitive(global)
  Zig->>Bind: JSC__JSValue__unwrapBoxedPrimitive(...)
  Bind->>Bind: if Number/String/Boolean/BigInt object → extract primitive
  Bind-->>Zig: EncodedJSValue
  Zig-->>JS: JSValue (primitive or original)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

I tap my paws and spin a yarn,
Of anchors, quotes, and maps well-drawn;
A builder hums, its bytes aligned,
It weaves the YAML, neatly signed.
Unwraps, escapes—no fuss, no fright—
Benchmarks moon-hop through the night.
Thump-thump: shipped. All strings delight.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff4231c and 45bb398.

📒 Files selected for processing (4)
  • src/bun.js/bindings/CatchScope.zig (1 hunks)
  • src/bun.js/bindings/JSValue.zig (1 hunks)
  • src/bun.js/bindings/StringBuilder.zig (1 hunks)
  • src/string.zig (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/string.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/CatchScope.zig
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dylan/yaml-stringify

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (10)
src/bun.js/bindings/headers-handwritten.h (1)

84-85: Forward-declare WTF::StringBuilder to avoid include-order fragility

This header references WTF::StringBuilder by reference but does not forward-declare it. Add a forward declaration alongside the existing WTF::String and StringImpl declarations to make the header self-contained.

Add near Line 15:

namespace WTF {
class String;
class StringBuilder; // add
}

And/or near Line 32:

namespace WTF {
class StringImpl;
class String;
class StringBuilder; // add
}
src/bun.js/bindings/JSValue.zig (1)

1274-1284: Good addition: unwraps boxed primitives with proper ThrowScope usage.

Exception propagation via CatchScope looks correct and consistent with nearby patterns.

Consider marking the binding for debug accounting:

 pub fn unwrapBoxedPrimitive(this: JSValue, global: *JSGlobalObject) JSError!JSValue {
-    var scope: CatchScope = undefined;
+    jsc.markBinding(@src());
+    var scope: CatchScope = undefined;
test/integration/bun-types/fixture/yaml.ts (1)

6-10: Type surface validated; add one positive check for the space argument.

Optional: include number/string space cases to lock API overloading.

 expectType(Bun.YAML.stringify({ abc: "def"})).is<string>();
+expectType(Bun.YAML.stringify({ a: 1 }, null, 2)).is<string>();
+expectType(Bun.YAML.stringify({ a: 1 }, null, "  ")).is<string>();
 // @ts-expect-error
 expectType(Bun.YAML.stringify("hi", {})).is<string>();
 // @ts-expect-error
 expectType(Bun.YAML.stringify("hi", null, 123n)).is<string>();
packages/bun-types/bun.d.ts (2)

639-641: Correct parse example typo (“123” → “null”)

The doc shows YAML.parse("123") twice with different outputs. Use "null" for the second line.

- * console.log(YAML.parse("123")) // null
+ * console.log(YAML.parse("null")) // null

673-677: Tighten alias example formatting

YAML alias as a scalar value should be on the same line as the key.

- * // obj:
- * //   *root
+ * // obj: *root
bench/yaml/yaml-stringify.mjs (2)

185-210: Exercise real anchors by sharing the same object

objectWithAnchors currently inlines copies, so libraries won’t emit anchors/aliases. Share the same object in multiple places to benchmark anchor detection meaningfully.

-const objectWithAnchors = {
-  defaults: {
-    adapter: "postgresql",
-    host: "localhost",
-    port: 5432,
-  },
-  development: {
-    adapter: "postgresql",
-    host: "localhost",
-    port: 5432,
-    database: "dev_db",
-  },
-  test: {
-    adapter: "postgresql",
-    host: "localhost",
-    port: 5432,
-    database: "test_db",
-  },
-  production: {
-    adapter: "postgresql",
-    host: "prod.example.com",
-    port: 5432,
-    database: "prod_db",
-  },
-};
+const shared = { adapter: "postgresql", host: "localhost", port: 5432 };
+// Multiple references to the same object should produce one anchor and aliases
+const objectWithAnchors = {
+  defaults: shared,
+  development: shared,
+  test: shared,
+  production: shared,
+};

271-276: Remove misleading UTC comment

Date strings with “Z” are UTC; the “Adjusted for UTC-5” comment is incorrect.

-  timestamp: new Date("2024-01-15T15:30:00.123456789Z"), // Adjusted for UTC-5
+  timestamp: new Date("2024-01-15T15:30:00.123456789Z"),
src/bun.js/api/YAMLObject.zig (1)

782-902: Number-detector accepts incomplete numbers (e.g., "+", "-", ".", "1e").

stringIsNumber returns true when the string ends immediately after a sign, dot, or exponent, which over-quotes some strings. Tighten terminal conditions.

-            '.' => {
+            '.' => {
                 if (dot or base != .dec) {
                     offset.* = i;
                     return false;
                 }
                 dot = true;
                 i += 1;
-                if (i < str.length()) {
+                if (i < str.length()) {
                     continue :next str.charAt(i);
                 }
-                return true;
+                return false; // lone '.' is not a number
             },
@@
-            '+' => {
+            '+' => {
                 if (@"+") {
                     offset.* = i;
                     return false;
                 }
                 @"+" = true;
                 i += 1;
-                if (i < str.length()) {
+                if (i < str.length()) {
                     continue :next str.charAt(i);
                 }
-                return true;
+                return false; // '+' alone is not a number
             },
@@
-            '-' => {
+            '-' => {
                 if (@"-") {
                     offset.* = i;
                     return false;
                 }
                 @"-" = true;
                 i += 1;
-                if (i < str.length()) {
+                if (i < str.length()) {
                     continue :next str.charAt(i);
                 }
-                return true;
+                return false; // '-' alone is not a number
             },
@@
-            'e',
-            'E',
+            'e',
+            'E',
             => {
                 if (base == .oct or (e and base == .dec)) {
                     offset.* = i;
                     return false;
                 }
                 e = true;
                 i += 1;
-                if (i < str.length()) {
+                if (i < str.length()) {
                     continue :next str.charAt(i);
                 }
-                return true;
+                return false; // trailing exponent is not a number
             },
test/js/bun/yaml/yaml.test.ts (2)

2161-2172: Sparse arrays in tests trip linters; prefer explicit undefined.

Biome flags sparse arrays. To keep intent and avoid lints, fill holes with undefined.

-  sparse: [1, , , 4],
+  sparse: [1, undefined, undefined, 4],

And:

-const arr = [1, , 3, undefined, 5];
+const arr = [1, undefined, 3, undefined, 5];

983-1013: Perf: heavy stringify loop in test; consider reducing iterations.

10k iterations can be slow/flaky on CI. If this isn’t specifically a perf test, reduce to 1k.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d189759 and bf271da.

📒 Files selected for processing (18)
  • bench/yaml/yaml-stringify.mjs (1 hunks)
  • cmake/sources/CxxSources.txt (1 hunks)
  • cmake/sources/ZigSources.txt (1 hunks)
  • packages/bun-types/bun.d.ts (1 hunks)
  • src/bun.js/api/YAMLObject.zig (6 hunks)
  • src/bun.js/bindings/BunString.cpp (1 hunks)
  • src/bun.js/bindings/JSPropertyIterator.cpp (2 hunks)
  • src/bun.js/bindings/JSValue.zig (1 hunks)
  • src/bun.js/bindings/StringBuilder.zig (1 hunks)
  • src/bun.js/bindings/StringBuilderBinding.cpp (1 hunks)
  • src/bun.js/bindings/WTF.zig (1 hunks)
  • src/bun.js/bindings/bindings.cpp (2 hunks)
  • src/bun.js/bindings/headers-handwritten.h (1 hunks)
  • src/bun.js/bindings/helpers.h (1 hunks)
  • src/bun.zig (1 hunks)
  • src/string.zig (1 hunks)
  • test/integration/bun-types/fixture/yaml.ts (1 hunks)
  • test/js/bun/yaml/yaml.test.ts (18 hunks)
🧰 Additional context used
📓 Path-based instructions (18)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript with Prettier (bun run prettier)

Files:

  • test/integration/bun-types/fixture/yaml.ts
  • packages/bun-types/bun.d.ts
  • test/js/bun/yaml/yaml.test.ts
test/integration/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Place integration tests under /test/integration/

Files:

  • test/integration/bun-types/fixture/yaml.ts
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/integration/bun-types/fixture/yaml.ts
  • test/js/bun/yaml/yaml.test.ts
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/integration/bun-types/fixture/yaml.ts
  • test/js/bun/yaml/yaml.test.ts
**/*.{cpp,cxx,cc,hpp,hxx,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Format C++ sources/headers with clang-format (bun run clang-format)

Files:

  • src/bun.js/bindings/helpers.h
  • src/bun.js/bindings/headers-handwritten.h
  • src/bun.js/bindings/JSPropertyIterator.cpp
  • src/bun.js/bindings/BunString.cpp
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/StringBuilderBinding.cpp
src/bun.js/bindings/**/*.{cpp,hpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.{cpp,hpp,h}: When implementing JavaScript classes in C++, create Foo, FooPrototype, and FooConstructor for public constructors
Define properties using JSC HashTableValue arrays for JS class bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for bound classes

Files:

  • src/bun.js/bindings/helpers.h
  • src/bun.js/bindings/headers-handwritten.h
  • src/bun.js/bindings/JSPropertyIterator.cpp
  • src/bun.js/bindings/BunString.cpp
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/StringBuilderBinding.cpp
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/helpers.h
  • src/bun.js/bindings/headers-handwritten.h
  • src/bun.js/bindings/JSPropertyIterator.cpp
  • src/bun.js/bindings/BunString.cpp
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/StringBuilderBinding.cpp
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.zig: Format Zig files with zig fmt (bun run zig-format)
In Zig code, manage memory carefully: use the correct allocator and defer for cleanup

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/bindings/WTF.zig
  • src/bun.zig
  • src/string.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/YAMLObject.zig
  • src/bun.js/bindings/StringBuilder.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/bun.js/bindings/WTF.zig
  • src/bun.zig
  • src/string.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/YAMLObject.zig
  • src/bun.js/bindings/StringBuilder.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/bindings/WTF.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/YAMLObject.zig
  • src/bun.js/bindings/StringBuilder.zig
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Place tests in test/ and ensure filenames end with .test.ts or .test.tsx
Always use port: 0 in tests; never hardcode port numbers or use custom random-port functions
Prefer snapshot tests using normalizeBunSnapshot(...).toMatchInlineSnapshot(...) over exact string comparisons
Never write tests that assert absence of crashes (e.g., no 'panic' or 'uncaught exception' in output)
Avoid shell commands like find/grep in tests; use Bun.Glob and built-in tools instead

Files:

  • test/js/bun/yaml/yaml.test.ts
test/js/bun/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Put Bun-specific API tests under test/js/bun/

Files:

  • test/js/bun/yaml/yaml.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Use bun:test in files that end in *.test.ts
Prefer async/await over callbacks in tests
For single-callback flows in tests, use Promise.withResolvers()
Do not set timeouts on tests; rely on Bun’s built-in timeouts
Use tempDirWithFiles from harness to create temporary directories/files in tests
Organize tests with describe blocks
Always assert exit codes and error scenarios in tests (e.g., proc.exited, expect(...).not.toBe(0), toThrow())
Use describe.each for parameterized tests; toMatchSnapshot for snapshots; and beforeAll/afterEach/beforeEach for setup/teardown
Avoid flaky tests: never wait for arbitrary time; wait for conditions to be met

Files:

  • test/js/bun/yaml/yaml.test.ts
test/{**/*.test.ts,**/*-fixture.ts}

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/{**/*.test.ts,**/*-fixture.ts}: When spawning Bun processes, use bunExe() and bunEnv from harness
Use using or await using for Bun APIs (e.g., Bun.spawn, Bun.listen, Bun.connect, Bun.serve) to ensure cleanup
Never hardcode port numbers; use port: 0 to get a random port
Use Buffer.alloc(count, fill).toString() instead of "A".repeat(count) for large/repetitive strings
Import testing utilities from harness (bunExe, bunEnv, tempDirWithFiles, tmpdirSync, isMacOS, isWindows, isPosix, gcTick, withoutAggressiveGC)

Files:

  • test/js/bun/yaml/yaml.test.ts
test/js/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place JavaScript and TypeScript tests under test/js/

Files:

  • test/js/bun/yaml/yaml.test.ts
test/js/bun/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Files:

  • test/js/bun/yaml/yaml.test.ts
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/JSPropertyIterator.cpp
  • src/bun.js/bindings/BunString.cpp
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/StringBuilderBinding.cpp
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/YAMLObject.zig
🧠 Learnings (29)
📓 Common learnings
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Cache structures in ZigGlobalObject for bound classes
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : For properties marked cache: true, use the generated Zig accessors (<Prop>NameSetCached/GetCached) to work with GC-owned values
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to test/js/bun/**/*.test.{ts,tsx} : Put Bun-specific API tests under test/js/bun/

Applied to files:

  • test/integration/bun-types/fixture/yaml.ts
📚 Learning: 2025-08-30T00:12:56.792Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.792Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/integration/bun-types/fixture/yaml.ts
  • test/js/bun/yaml/yaml.test.ts
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • cmake/sources/ZigSources.txt
  • src/bun.js/bindings/helpers.h
  • cmake/sources/CxxSources.txt
  • src/bun.js/bindings/WTF.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • cmake/sources/ZigSources.txt
  • src/bun.js/bindings/helpers.h
  • cmake/sources/CxxSources.txt
  • src/bun.js/bindings/WTF.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/YAMLObject.zig
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:11:57.056Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/**/js_*.zig : Implement JavaScript bindings in a Zig file named with a js_ prefix (e.g., js_smtp.zig, js_your_feature.zig)

Applied to files:

  • cmake/sources/ZigSources.txt
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Cache structures in ZigGlobalObject for bound classes

Applied to files:

  • cmake/sources/ZigSources.txt
  • src/bun.js/bindings/helpers.h
  • cmake/sources/CxxSources.txt
  • src/bun.js/bindings/WTF.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:11:57.056Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • cmake/sources/ZigSources.txt
  • src/bun.js/bindings/headers-handwritten.h
  • src/bun.js/bindings/WTF.zig
  • src/bun.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/api/YAMLObject.zig
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.zig : Declare the extern C symbol in Zig and export a Zig-friendly alias for use

Applied to files:

  • cmake/sources/ZigSources.txt
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • cmake/sources/ZigSources.txt
  • src/bun.js/bindings/headers-handwritten.h
  • src/bun.js/bindings/WTF.zig
  • src/bun.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/api/YAMLObject.zig
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • cmake/sources/ZigSources.txt
  • src/bun.js/bindings/helpers.h
  • src/bun.js/bindings/WTF.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/api/YAMLObject.zig
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Create Bun JSC class bindings by pairing a TypeScript class interface with a Zig implementation and relying on the generator to produce glue code

Applied to files:

  • cmake/sources/ZigSources.txt
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : For properties marked cache: true, use the generated Zig accessors (<Prop>NameSetCached/GetCached) to work with GC-owned values

Applied to files:

  • cmake/sources/ZigSources.txt
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • cmake/sources/ZigSources.txt
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/api/YAMLObject.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/helpers.h
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Define properties using JSC HashTableValue arrays for JS class bindings

Applied to files:

  • src/bun.js/bindings/helpers.h
  • cmake/sources/CxxSources.txt
  • src/bun.js/bindings/JSPropertyIterator.cpp
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/StringBuilderBinding.cpp
  • src/bun.js/api/YAMLObject.zig
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Add iso subspaces for classes with C++ fields

Applied to files:

  • cmake/sources/CxxSources.txt
  • src/bun.js/bindings/JSPropertyIterator.cpp
  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : When implementing JavaScript classes in C++, create Foo, FooPrototype, and FooConstructor for public constructors

Applied to files:

  • cmake/sources/CxxSources.txt
  • src/bun.js/bindings/StringBuilderBinding.cpp
📚 Learning: 2025-08-30T00:11:57.056Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/**/{js_bindings,BunObject}.classes.ts : Add JSDoc comments to JavaScript binding class definitions

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.cpp : Provide an extern "C" Bun__<Type>__toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Applied to files:

  • cmake/sources/CxxSources.txt
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/bindings/StringBuilderBinding.cpp
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:11:57.056Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/bun.js/bindings/BunObject+exports.h : When adding a new global module or getter (e.g., SMTP), ensure its name is added exactly once to FOR_EACH_GETTER

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.cpp
📚 Learning: 2025-08-30T00:11:57.056Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/bun.js/bindings/BunObject+exports.h : Register new getters by adding an entry to the FOR_EACH_GETTER macro in BunObject+exports.h

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.cpp
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Implement getters as get<PropertyName>(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.cpp
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/YAMLObject.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.cpp
  • src/bun.js/bindings/bindings.cpp
  • src/bun.js/api/YAMLObject.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)

Applied to files:

  • src/bun.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/api/YAMLObject.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.cpp : Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:07:13.612Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-08-30T00:07:13.612Z
Learning: Applies to test/{**/*.test.ts,**/*-fixture.ts} : Import testing utilities from harness (bunExe, bunEnv, tempDirWithFiles, tmpdirSync, isMacOS, isWindows, isPosix, gcTick, withoutAggressiveGC)

Applied to files:

  • src/bun.js/api/YAMLObject.zig
📚 Learning: 2025-08-30T00:12:56.792Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.792Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Applied to files:

  • src/bun.js/api/YAMLObject.zig
📚 Learning: 2025-08-30T00:07:13.612Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-08-30T00:07:13.612Z
Learning: Applies to test/{**/*.test.ts,**/*-fixture.ts} : When spawning Bun processes, use bunExe() and bunEnv from harness

Applied to files:

  • src/bun.js/api/YAMLObject.zig
🧬 Code graph analysis (3)
src/bun.js/bindings/helpers.h (1)
src/bun.js/bindings/BunString.cpp (2)
  • appendToBuilder (720-733)
  • appendToBuilder (720-720)
src/bun.js/bindings/bindings.cpp (1)
src/bun.js/bindings/webcore/SerializedScriptValue.cpp (26)
  • value (1052-1058)
  • value (1052-1052)
  • value (1060-1066)
  • value (1060-1060)
  • value (1067-1073)
  • value (1067-1067)
  • value (1155-1198)
  • value (1155-1155)
  • value (1220-1230)
  • value (1220-1220)
  • value (1553-2071)
  • value (1553-1553)
  • object (1075-1089)
  • object (1075-1075)
  • object (1091-1095)
  • object (1091-1091)
  • object (1097-1103)
  • object (1097-1097)
  • object (1105-1111)
  • object (1105-1105)
  • object (1147-1153)
  • object (1147-1147)
  • object (3502-3505)
  • object (3502-3502)
  • object (3507-3510)
  • object (3507-3507)
src/bun.js/bindings/StringBuilderBinding.cpp (1)
src/bun.js/bindings/bindings.cpp (2)
  • globalObject (2525-2529)
  • globalObject (2525-2525)
🪛 Biome (2.1.2)
test/js/bun/yaml/yaml.test.ts

[error] 2163-2163: This array contains an empty slots..

The presences of empty slots may cause incorrect information and might be a typo.
Unsafe fix: Replace hole with undefined

(lint/suspicious/noSparseArray)


[error] 2209-2209: This array contains an empty slots..

The presences of empty slots may cause incorrect information and might be a typo.
Unsafe fix: Replace hole with undefined

(lint/suspicious/noSparseArray)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (15)
cmake/sources/CxxSources.txt (1)

201-201: Add StringBuilderBinding.cpp to build — looks good

File is correctly included among bun.js/bindings.

cmake/sources/ZigSources.txt (1)

203-203: Include StringBuilder.zig in Zig sources — looks good

Placed right after static_export.zig; ordering is fine.

src/bun.zig (1)

3723-3724: Make StackOverflow error set public — LGTM

Public alias enables external references (e.g., YAML.stringify) without changing behavior.

src/bun.js/bindings/bindings.cpp (1)

39-39: Good: required header for BigInt wrapper detection.

Including JavaScriptCore/BigIntObject.h is necessary for the new unboxing path. No issues.

src/string.zig (1)

869-872: Fix: 8-bit WTF strings now indexed as Latin-1, not UTF-8.

This aligns charAt with WTF 8-bit semantics and avoids mis-indexing non-ASCII.

src/bun.js/bindings/JSPropertyIterator.cpp (2)

133-154: Safer unwinding: RELEASE_AND_RETURN on exceptional paths.

Early returns now release ThrowScope and fetch values consistently. Good change.


158-181: Non-observable path matches observable path’s scope discipline.

ThrowScope placement and releases look correct; no behavior regressions spotted.

src/bun.js/bindings/WTF.zig (1)

40-41: StringBuilder bindings are correctly referenced in build sources. ZigSources.txt and CxxSources.txt include the new files.

src/bun.js/bindings/BunString.cpp (1)

720-733: Method looks good; relies on fixed Zig helper

appendToBuilder forwards correctly to Zig::appendToBuilder and handles all tags. Once the helper handles external pointers and the size limit, this is solid.

Please ensure the BunString::appendToBuilder declaration exists in headers-handwritten.h and that StringBuilderBinding.cpp uses it (grep for “appendToBuilder(WTF::StringBuilder&)”).

packages/bun-types/bun.d.ts (1)

648-679: API surface LGTM; replacer gating is clear

Typing and docs for YAML.stringify align with runtime: replacer intentionally unsupported, space matches JSON semantics.

src/bun.js/bindings/StringBuilder.zig (1)

69-77: Good: JS exception propagation via CatchScope.

CatchScope + returnIfException around toString is correct and matches Bun’s error plumbing.

src/bun.js/bindings/StringBuilderBinding.cpp (1)

61-73: LGTM: OOM mapping to JS and string materialization.

Overflow path throws OOM and returns empty EncodedJSValue; toString + jsString(vm, str) is fine.

src/bun.js/api/YAMLObject.zig (2)

223-234: Anchor name map may store ephemeral slices; verify key ownership.

prop_names.getOrPut(prop_value.prop_name.byteSlice()) risks storing a slice backed by a transient String. Confirm bun.StringHashMap copies/owns key bytes. If not, store the String itself (retain/deref) or dup the bytes.

I can switch prop_names to a map keyed by String and handle refs if needed.


368-377: Nice: clamp space indent and honor both numeric and string space.

Space union + clamping to max 10 is aligned with JSON.stringify semantics and keeps output bounded.

test/js/bun/yaml/yaml.test.ts (1)

2091-2099: Inconsistent expectation: quoting “y” in sealed object.

Sealed vs frozen doesn’t change key quoting rules. With current stringNeedsQuotes, key "y" should not be quoted. Please confirm expected output or adjust the assertion.

Comment on lines +279 to +301
if (unwrapped.isNumber()) {
if (unwrapped.isInt32()) {
this.builder.append(.int, unwrapped.asInt32());
return;
}

const num = unwrapped.asNumber();
if (std.math.isNegativeInf(num)) {
this.builder.append(.latin1, "-.inf");
// } else if (std.math.isPositiveInf(num)) {
// builder.append(.latin1, "+.inf");
} else if (std.math.isInf(num)) {
this.builder.append(.latin1, ".inf");
} else if (std.math.isNan(num)) {
this.builder.append(.latin1, ".nan");
} else if (std.math.isNegativeZero(num)) {
this.builder.append(.latin1, "-0");
} else if (std.math.isPositiveZero(num)) {
this.builder.append(.latin1, "+0");
} else {
this.builder.append(.double, num);
}
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: positive zero prints “+0”; tests expect “0”.

YAML.stringify(+0) should emit "0". Emit "0" for +0, keep "-0" for negative zero.

-            } else if (std.math.isPositiveZero(num)) {
-                this.builder.append(.latin1, "+0");
+            } else if (std.math.isPositiveZero(num)) {
+                this.builder.append(.latin1, "0");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (unwrapped.isNumber()) {
if (unwrapped.isInt32()) {
this.builder.append(.int, unwrapped.asInt32());
return;
}
const num = unwrapped.asNumber();
if (std.math.isNegativeInf(num)) {
this.builder.append(.latin1, "-.inf");
// } else if (std.math.isPositiveInf(num)) {
// builder.append(.latin1, "+.inf");
} else if (std.math.isInf(num)) {
this.builder.append(.latin1, ".inf");
} else if (std.math.isNan(num)) {
this.builder.append(.latin1, ".nan");
} else if (std.math.isNegativeZero(num)) {
this.builder.append(.latin1, "-0");
} else if (std.math.isPositiveZero(num)) {
this.builder.append(.latin1, "+0");
} else {
this.builder.append(.double, num);
}
return;
@@ src/bun.js/api/YAMLObject.zig: around line 295
} else if (std.math.isNegativeZero(num)) {
this.builder.append(.latin1, "-0");
- } else if (std.math.isPositiveZero(num)) {
} else if (std.math.isPositiveZero(num)) {
this.builder.append(.latin1, "0");
} else {
this.builder.append(.double, num);
}
🤖 Prompt for AI Agents
In src/bun.js/api/YAMLObject.zig around lines 279 to 301, the code currently
emits "+0" for positive zero but tests expect "0"; change the positive-zero
handling so that std.math.isPositiveZero(num) appends "0" instead of "+0" while
preserving the existing std.math.isNegativeZero(num) branch that emits "-0".
Ensure the negative-zero check remains before the positive-zero check and update
the builder.append call for the positive-zero branch to use the latin1 "0"
literal.

Comment on lines +515 to +524
const clamped = if (space_str.length() > 10)
space_str.substringWithLen(0, 10)
else
space_str;

this.builder.ensureUnusedCapacity(indent_count * clamped.length());
for (0..indent_count) |_| {
this.builder.append(.string, clamped);
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Leak: repeatedly allocating clamped indent string on every newline.

substringWithLen allocates a new String each call and isn’t deref’d here. Clamp once in Space.init and reuse the stored String.

Apply both diffs:

  1. Clamp in Space.init and own the clamped string:
-            if (space_value.isString()) {
-                const str = try space_value.toBunString(global);
-                if (str.length() == 0) {
-                    str.deref();
-                    return .minified;
-                }
-                return .{ .str = str };
-            }
+            if (space_value.isString()) {
+                var str = try space_value.toBunString(global);
+                if (str.length() == 0) {
+                    str.deref();
+                    return .minified;
+                }
+                if (str.length() > 10) {
+                    const clamped = str.substringWithLen(0, 10);
+                    str.deref();
+                    str = clamped;
+                }
+                return .{ .str = str };
+            }
  1. Use the stored string in newline without allocating:
-            .str => |space_str| {
+            .str => |space_str| {
                 this.builder.append(.lchar, '\n');
-
-                const clamped = if (space_str.length() > 10)
-                    space_str.substringWithLen(0, 10)
-                else
-                    space_str;
-
-                this.builder.ensureUnusedCapacity(indent_count * clamped.length());
+                this.builder.ensureUnusedCapacity(indent_count * space_str.length());
                 for (0..indent_count) |_| {
-                    this.builder.append(.string, clamped);
+                    this.builder.append(.string, space_str);
                 }
             },

Comment on lines +2100 to +2121
// Taken from unwrapBoxedPrimitive in JSONObject.cpp in WebKit
extern "C" JSC::EncodedJSValue JSC__JSValue__unwrapBoxedPrimitive(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
{
JSValue value = JSValue::decode(encodedValue);

if (!value.isObject()) {
return JSValue::encode(value);
}

JSObject* object = asObject(value);

if (object->inherits<NumberObject>()) {
return JSValue::encode(jsNumber(object->toNumber(globalObject)));
}
if (object->inherits<StringObject>())
return JSValue::encode(object->toString(globalObject));
if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>())
return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());

return JSValue::encode(object);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Unwrap via internalValue to avoid exceptions and match WebKit behavior; consider SymbolObject.

  • Using toNumber()/toString() can allocate and throw; internalValue() is faster and side-effect free for boxed primitives. This also mirrors WebKit’s approach in similar code paths.
  • Optional: handle SymbolObject the same way (internalValue), if YAML.stringify should treat boxed symbols as primitives.

Apply this diff to simplify and harden the unwrapping:

-// Taken from unwrapBoxedPrimitive in JSONObject.cpp in WebKit
-extern "C" JSC::EncodedJSValue JSC__JSValue__unwrapBoxedPrimitive(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
+// Taken from (and aligned with) WebKit behavior for boxed primitives
+extern "C" JSC::EncodedJSValue JSC__JSValue__unwrapBoxedPrimitive(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
 {
     JSValue value = JSValue::decode(encodedValue);

     if (!value.isObject()) {
         return JSValue::encode(value);
     }

     JSObject* object = asObject(value);

-    if (object->inherits<NumberObject>()) {
-        return JSValue::encode(jsNumber(object->toNumber(globalObject)));
-    }
-    if (object->inherits<StringObject>())
-        return JSValue::encode(object->toString(globalObject));
-    if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>())
-        return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());
+    // For boxed primitives, unwrap the underlying primitive without invoking conversions.
+    if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>()
+        || object->inherits<StringObject>() || object->inherits<NumberObject>()) {
+        return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());
+    }

     return JSValue::encode(object);
 }

Optional SymbolObject support (if desired by YAML.stringify semantics):

+#include "JavaScriptCore/SymbolObject.h"
...
-    if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>()
-        || object->inherits<StringObject>() || object->inherits<NumberObject>()) {
+    if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>()
+        || object->inherits<StringObject>() || object->inherits<NumberObject>()
+        || object->inherits<SymbolObject>()) {
         return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());
     }

Run to confirm all call sites don’t rely on exceptions from toString()/toNumber():


🏁 Script executed:

#!/bin/bash
# Find usages of the new unboxer and nearby exception handling.
rg -nC3 -g '!**/dist/**' -g '!**/build/**' 'JSC__JSValue__unwrapBoxedPrimitive|unwrapBoxedPrimitive' \
  && rg -nC2 -g '!**/dist/**' -g '!**/build/**' 'toString\(|toNumber\(' src/bun.js/bindings

Length of output: 208740


Use internalValue() for all boxed primitives to avoid allocations/exceptions

  • In src/bun.js/bindings/bindings.cpp at JSC__JSValue__unwrapBoxedPrimitive, merge NumberObject, StringObject, BooleanObject, and BigIntObject cases into a single internalValue() branch instead of separate toNumber()/toString() calls.
  • Confirmed unwrapBoxedPrimitive is only consumed in JSValue.zig and YAMLObject.zig; no caller relies on exceptions from toNumber()/toString().
  • Optional: include SymbolObject in the inherits check if YAML.stringify should treat boxed symbols as primitives.
-// Taken from unwrapBoxedPrimitive in JSONObject.cpp in WebKit
-extern "C" JSC::EncodedJSValue JSC__JSValue__unwrapBoxedPrimitive(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
+// Taken from (and aligned with) WebKit behavior for boxed primitives
+extern "C" JSC::EncodedJSValue JSC__JSValue__unwrapBoxedPrimitive(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
 {
     JSValue value = JSValue::decode(encodedValue);
     if (!value.isObject()) {
-        return JSValue::encode(value);
+        return JSValue::encode(value);
     }
     JSObject* object = asObject(value);
-    if (object->inherits<NumberObject>()) {
-        return JSValue::encode(jsNumber(object->toNumber(globalObject)));
-    }
-    if (object->inherits<StringObject>())
-        return JSValue::encode(object->toString(globalObject));
-    if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>())
-        return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());
+    // Unwrap primitive value without allocations or side effects.
+    if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>()
+        || object->inherits<StringObject>() || object->inherits<NumberObject>()) {
+        return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());
+    }
     return JSValue::encode(object);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Taken from unwrapBoxedPrimitive in JSONObject.cpp in WebKit
extern "C" JSC::EncodedJSValue JSC__JSValue__unwrapBoxedPrimitive(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
{
JSValue value = JSValue::decode(encodedValue);
if (!value.isObject()) {
return JSValue::encode(value);
}
JSObject* object = asObject(value);
if (object->inherits<NumberObject>()) {
return JSValue::encode(jsNumber(object->toNumber(globalObject)));
}
if (object->inherits<StringObject>())
return JSValue::encode(object->toString(globalObject));
if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>())
return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());
return JSValue::encode(object);
}
// Taken from (and aligned with) WebKit behavior for boxed primitives
extern "C" JSC::EncodedJSValue JSC__JSValue__unwrapBoxedPrimitive(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
{
JSValue value = JSValue::decode(encodedValue);
if (!value.isObject()) {
return JSValue::encode(value);
}
JSObject* object = asObject(value);
// Unwrap primitive value without allocations or side effects.
if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>()
|| object->inherits<StringObject>() || object->inherits<NumberObject>()) {
return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());
}
return JSValue::encode(object);
}
🤖 Prompt for AI Agents
In src/bun.js/bindings/bindings.cpp around lines 2100-2121, the
unwrapBoxedPrimitive implementation currently handles NumberObject,
StringObject, BooleanObject, and BigIntObject via separate
toNumber()/toString()/toNumber-style calls which can cause extra
allocations/exceptions; replace those separate branches with a single branch
that checks object->inherits<...>() for all boxed primitive wrapper types and
returns JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue()) so
the internalValue() is used for NumberObject, StringObject, BooleanObject and
BigIntObject (and optionally SymbolObject if desired), removing the explicit
toNumber()/toString() calls and keeping the final fallback to encode(object).

Comment on lines +186 to +203
static void appendToBuilder(ZigString str, WTF::StringBuilder& builder)
{
if (str.len == 0 || str.ptr == nullptr) {
return;
}
if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
WTF::String converted = WTF::String::fromUTF8ReplacingInvalidSequences(std::span { untag(str.ptr), str.len });
builder.append(converted);
return;
}
if (isTaggedUTF16Ptr(str.ptr)) {
builder.append({ reinterpret_cast<const char16_t*>(untag(str.ptr)), str.len });
return;
}

builder.append({ untag(str.ptr), str.len });
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Fix leak and handle external ZigString in appendToBuilder

When str.ptr is an external pointer (bit 62), appendToBuilder copies from the external buffer but never frees it, unlike toString() which wires a free callback via ExternalStringImpl. Also, the synthetic allocation limit guard is missing here.

  • Add explicit handling for isTaggedExternalPtr() before UTF-16 check.
  • After appending, call ZigString__freeGlobal(...) to release the external buffer.
  • Mirror Bun__stringSyntheticAllocationLimit checks to avoid oversized appends.
 static void appendToBuilder(ZigString str, WTF::StringBuilder& builder)
 {
     if (str.len == 0 || str.ptr == nullptr) {
         return;
     }
-    if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
+    if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
         WTF::String converted = WTF::String::fromUTF8ReplacingInvalidSequences(std::span { untag(str.ptr), str.len });
         builder.append(converted);
         return;
     }
-    if (isTaggedUTF16Ptr(str.ptr)) {
-        builder.append({ reinterpret_cast<const char16_t*>(untag(str.ptr)), str.len });
-        return;
-    }
+    // External buffers must be freed after copying into the builder
+    if (isTaggedExternalPtr(str.ptr)) [[unlikely]] {
+        if (str.len > Bun__stringSyntheticAllocationLimit) [[unlikely]] {
+            free_global_string(nullptr, untagVoid(str.ptr), static_cast<unsigned>(str.len));
+            return;
+        }
+        if (isTaggedUTF16Ptr(str.ptr)) {
+            builder.append({ reinterpret_cast<const char16_t*>(untag(str.ptr)), str.len });
+        } else {
+            builder.append({ untag(str.ptr), str.len });
+        }
+        free_global_string(nullptr, untagVoid(str.ptr), static_cast<unsigned>(str.len));
+        return;
+    }
 
-    builder.append({ untag(str.ptr), str.len });
+    if (str.len > Bun__stringSyntheticAllocationLimit) [[unlikely]] {
+        return;
+    }
+    if (isTaggedUTF16Ptr(str.ptr)) {
+        builder.append({ reinterpret_cast<const char16_t*>(untag(str.ptr)), str.len });
+    } else {
+        builder.append({ untag(str.ptr), str.len });
+    }
 }

Comment on lines +1891 to +1902
test("handles symbols", () => {
const sym = Symbol("test");
expect(YAML.stringify(sym)).toBe(undefined);

const obj = {
[sym]: "symbol key value",
normalKey: "normal value",
symbolValue: sym,
};
// Symbol keys are not enumerable, symbol values are undefined
expect(YAML.stringify(obj, null, 2)).toBe("normalKey: normal value\ntest: symbol key value");
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Symbol-key behavior expectation doesn’t match implementation.

Test expects a symbol-keyed property to appear as “test: …”, but the stringifier iterates only string keys via JSPropertyIterator. Either (a) change expectation to omit symbol-keyed entries, or (b) extend iteration to include symbol keys (Reflect.ownKeys + filter).

I can provide a patch to include enumerable symbol keys if desired.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bun.js/bindings/CatchScopeBinding.cpp (1)

18-20: Make the size/alignment guards release-crashing to catch mismatches in all builds.

Debug-only ASSERTs won’t fire in release; use RELEASE_ASSERT so Zig/C++ layout mismatches never slip through.

Apply this diff:

-    ASSERT(size >= sizeof(CatchScope));
-    ASSERT(alignment >= alignof(CatchScope));
-    ASSERT((uintptr_t)ptr % alignment == 0);
+    RELEASE_ASSERT(size >= sizeof(CatchScope));
+    RELEASE_ASSERT(alignment >= alignof(CatchScope));
+    RELEASE_ASSERT((uintptr_t)ptr % alignment == 0);

Optional: expose helpers so Zig can sanity-check (or allocate) using true values at runtime:

extern "C" size_t CatchScope__sizeof() { return sizeof(CatchScope); }
extern "C" size_t CatchScope__alignof() { return alignof(CatchScope); }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bf271da and 025eeb9.

📒 Files selected for processing (2)
  • src/bun.js/bindings/CatchScopeBinding.cpp (1 hunks)
  • src/bun.js/bindings/StringBuilderBinding.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/bun.js/bindings/StringBuilderBinding.cpp
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{cpp,cxx,cc,hpp,hxx,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Format C++ sources/headers with clang-format (bun run clang-format)

Files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
src/bun.js/bindings/**/*.{cpp,hpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.{cpp,hpp,h}: When implementing JavaScript classes in C++, create Foo, FooPrototype, and FooConstructor for public constructors
Define properties using JSC HashTableValue arrays for JS class bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for bound classes

Files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
🧠 Learnings (9)
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Cache structures in ZigGlobalObject for bound classes

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Add iso subspaces for classes with C++ fields

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.cpp : Initialize the LazyClassStructure in GlobalObject::finishCreation and visit it in GlobalObject::visitChildrenImpl

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/bun.js/bindings/CatchScopeBinding.cpp (1)

5-15: Remove ABI-fragile static_asserts; they mismatch Zig’s gate and break non-matching targets (likely CI red).

Hard-coding size/alignment via ExpectedCatchScope* and enforcing with static_assert is brittle. You already validate at runtime in CatchScope__construct; rely on that instead.

Apply:

-#if ENABLE(EXCEPTION_SCOPE_VERIFICATION)
-#define ExpectedCatchScopeSize 56
-#define ExpectedCatchScopeAlignment 8
-#else
-#define ExpectedCatchScopeSize 8
-#define ExpectedCatchScopeAlignment 8
-#endif
-
-static_assert(sizeof(CatchScope) == ExpectedCatchScopeSize, "CatchScope.zig assumes CatchScope is 56 bytes");
-static_assert(alignof(CatchScope) == ExpectedCatchScopeAlignment, "CatchScope.zig assumes CatchScope is 8-byte aligned");
🧹 Nitpick comments (4)
src/bun.js/bindings/CatchScope.zig (3)

42-45: Remove self-assignment of the entire struct; it’s redundant and risky.

Reassigning self.* copies bytes onto itself after placement-new; just set location when assertions are enabled.

Apply:

-        self.* = .{
-            .bytes = self.bytes,
-            .location = if (Environment.ci_assert) &self.bytes[0],
-        };
+        if (Environment.ci_assert) {
+            self.location = &self.bytes[0];
+        }

117-121: Clear assertion-only pointer on deinit (debug hygiene).

Optional: poison or undef location to catch use-after-free in CI builds.

Apply:

     pub fn deinit(self: *CatchScope) void {
         if (comptime Environment.ci_assert) bun.assert(self.location == &self.bytes[0]);
         CatchScope__destruct(&self.bytes);
         self.bytes = undefined;
+        if (Environment.ci_assert) {
+            self.location = undefined;
+        }
     }

2-3: Align CatchScope size/alignment gate between Zig and C++
Zig picks 56 bytes when Environment.allow_assert || Environment.enable_asan, but the C++ binding uses #if ENABLE(EXCEPTION_SCOPE_VERIFICATION) to choose 56 vs 8. These can drift—consolidate to one shared compile‐time flag so both sides remain in sync.

src/bun.js/bindings/CatchScopeBinding.cpp (1)

25-29: Strengthen runtime validation in constructor.

Mirror the other functions by also checking actual pointer alignment against alignof(CatchScope).

Apply:

     // validate that Zig is correct about what the size and alignment should be
     ASSERT(size >= sizeof(CatchScope));
     ASSERT(alignment >= alignof(CatchScope));
     ASSERT((uintptr_t)ptr % alignment == 0);
+    ASSERT((uintptr_t)ptr % alignof(CatchScope) == 0);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 025eeb9 and bba38f8.

📒 Files selected for processing (2)
  • src/bun.js/bindings/CatchScope.zig (1 hunks)
  • src/bun.js/bindings/CatchScopeBinding.cpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.zig: Format Zig files with zig fmt (bun run zig-format)
In Zig code, manage memory carefully: use the correct allocator and defer for cleanup

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/bindings/CatchScope.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/bun.js/bindings/CatchScope.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/bindings/CatchScope.zig
**/*.{cpp,cxx,cc,hpp,hxx,h}

📄 CodeRabbit inference engine (CLAUDE.md)

Format C++ sources/headers with clang-format (bun run clang-format)

Files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
src/bun.js/bindings/**/*.{cpp,hpp,h}

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.{cpp,hpp,h}: When implementing JavaScript classes in C++, create Foo, FooPrototype, and FooConstructor for public constructors
Define properties using JSC HashTableValue arrays for JS class bindings
Add iso subspaces for classes with C++ fields
Cache structures in ZigGlobalObject for bound classes

Files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
🧠 Learnings (7)
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Cache structures in ZigGlobalObject for bound classes

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.cpp : Initialize the LazyClassStructure in GlobalObject::finishCreation and visit it in GlobalObject::visitChildrenImpl

Applied to files:

  • src/bun.js/bindings/CatchScopeBinding.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (1)
src/bun.js/bindings/CatchScopeBinding.cpp (1)

31-38: Constructor path LGTM with verification mode.

Placement-new with location info is correct; else-branch properly omits unused params.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bun.js/bindings/CatchScope.zig (1)

26-42: Fix: .init sets a dangling/mismatched self-location pointer (breaks all CI assertions).

You construct into a local scope and then return a copy while storing location = &scope.bytes[0]. After return, location points to the callee’s stack and will never equal &self.bytes[0]. All ci_assert checks will fail (and it’s UB to keep that dangling pointer).

Construct directly into the returned object and set location from that same object:

-    pub inline fn init(global: *jsc.JSGlobalObject, src: std.builtin.SourceLocation) CatchScope {
-        var scope: CatchScope = undefined;
-        CatchScope__construct(
-            &scope.bytes,
-            global,
-            src.fn_name,
-            src.file,
-            src.line,
-            size,
-            alignment,
-        );
-
-        return .{
-            .bytes = scope.bytes,
-            .location = if (Environment.ci_assert) &scope.bytes[0],
-        };
-    }
+    pub inline fn init(global: *jsc.JSGlobalObject, src: std.builtin.SourceLocation) CatchScope {
+        var ret: CatchScope = undefined;
+        CatchScope__construct(
+            &ret.bytes,
+            global,
+            src.fn_name,
+            src.file,
+            src.line,
+            size,
+            alignment,
+        );
+        if (comptime Environment.ci_assert) ret.location = &ret.bytes[0];
+        return ret;
+    }

Also applies to: 38-41

♻️ Duplicate comments (1)
src/bun.js/bindings/StringBuilder.zig (1)

3-7: Fix: in-place storage is undersized and likely misaligned (memory corruption risk).

24 bytes is far smaller than WTF::StringBuilder on x64/aarch64, and align(8) may be insufficient. This will trample memory and plausibly explains CI crashes. Either (A) switch to a pointer-handle design, or (B) raise size/alignment and enforce with C++ static_asserts. Below is the minimal stopgap:

-const size = 24;
-const alignment = 8;
+// Sized/aligned for WTF::StringBuilder; enforced by C++ static_asserts.
+const size = 80;
+const alignment = 16;

-bytes: [size]u8 align(alignment),
+bytes: [size]u8 align(alignment),

Add on the C++ side (StringBuilderBinding.cpp or a shared header):

static_assert(sizeof(WTF::StringBuilder) <= 80, "Zig storage too small");
static_assert(alignof(WTF::StringBuilder) <= 16, "Zig storage misaligned");
🧹 Nitpick comments (4)
src/bun.js/bindings/AnyPromise.zig (1)

74-79: LGTM: consistent inline CatchScope init.

Minor optional: instead of debugAssert, consider propagating JS errors to the caller (returning bun.JSError!void or converting to a rejected promise), but that’s outside this PR’s scope.

src/bun.js/bindings/JSPromise.zig (1)

180-186: LGTM: inline CatchScope init matches the refactor.

As above, optional improvement is to propagate exceptions from the wrap rather than only asserting.

src/bun.js/bindings/JSValue.zig (1)

1273-1282: unwrapBoxedPrimitive C++ binding verified; add tests

C++ symbol JSC__JSValue__unwrapBoxedPrimitive is declared once in src/bun.js/bindings/bindings.cpp:2101 and matches the extern fn in JSValue.zig; no duplicates. Add unit tests covering Number, Boolean, String, BigInt wrappers and primitive inputs.

src/bun.js/bindings/StringBuilder.zig (1)

69-77: Rename parameter to globalObject to follow bindings convention.

Aligns with repository guidelines for JS bindings.

-pub fn toString(this: *StringBuilder, global: *JSGlobalObject) JSError!JSValue {
-    var scope: jsc.CatchScope = .init(global, @src());
+pub fn toString(this: *StringBuilder, globalObject: *JSGlobalObject) JSError!JSValue {
+    var scope: jsc.CatchScope = .init(globalObject, @src());
     defer scope.deinit();
 
-    const result = StringBuilder__toString(&this.bytes, global);
+    const result = StringBuilder__toString(&this.bytes, globalObject);
     try scope.returnIfException();
     return result;
 }
-extern fn StringBuilder__toString(*anyopaque, global: *JSGlobalObject) JSValue;
+extern fn StringBuilder__toString(*anyopaque, globalObject: *JSGlobalObject) JSValue;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bba38f8 and ff4231c.

📒 Files selected for processing (17)
  • src/bun.js/ModuleLoader.zig (1 hunks)
  • src/bun.js/VirtualMachine.zig (1 hunks)
  • src/bun.js/api/JSBundler.zig (1 hunks)
  • src/bun.js/api/html_rewriter.zig (1 hunks)
  • src/bun.js/bindings/AnyPromise.zig (1 hunks)
  • src/bun.js/bindings/CatchScope.zig (6 hunks)
  • src/bun.js/bindings/JSObject.zig (1 hunks)
  • src/bun.js/bindings/JSPromise.zig (1 hunks)
  • src/bun.js/bindings/JSPropertyIterator.zig (1 hunks)
  • src/bun.js/bindings/JSValue.zig (6 hunks)
  • src/bun.js/bindings/StringBuilder.zig (1 hunks)
  • src/bun.js/bindings/VM.zig (1 hunks)
  • src/bun.js/event_loop.zig (1 hunks)
  • src/bun.js/event_loop/JSCScheduler.zig (1 hunks)
  • src/bun.js/jsc/host_fn.zig (3 hunks)
  • src/codegen/cppbind.ts (2 hunks)
  • src/string.zig (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/string.zig
🧰 Additional context used
📓 Path-based instructions (5)
**/*.zig

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.zig: Format Zig files with zig fmt (bun run zig-format)
In Zig code, manage memory carefully: use the correct allocator and defer for cleanup

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/ModuleLoader.zig
  • src/bun.js/VirtualMachine.zig
  • src/bun.js/event_loop/JSCScheduler.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/bun.js/bindings/VM.zig
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/bindings/AnyPromise.zig
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/event_loop.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/CatchScope.zig
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/ModuleLoader.zig
  • src/bun.js/VirtualMachine.zig
  • src/bun.js/event_loop/JSCScheduler.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/bun.js/bindings/VM.zig
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/bindings/AnyPromise.zig
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/event_loop.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/CatchScope.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/ModuleLoader.zig
  • src/bun.js/VirtualMachine.zig
  • src/bun.js/event_loop/JSCScheduler.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/bun.js/bindings/VM.zig
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/bindings/AnyPromise.zig
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/event_loop.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/CatchScope.zig
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript with Prettier (bun run prettier)

Files:

  • src/codegen/cppbind.ts
src/{**/js_*.zig,bun.js/api/**/*.zig}

📄 CodeRabbit inference engine (.cursor/rules/registering-bun-modules.mdc)

Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Files:

  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/api/JSBundler.zig
🧠 Learnings (21)
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Implement getters as get<PropertyName>(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/ModuleLoader.zig
  • src/bun.js/VirtualMachine.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/bun.js/bindings/VM.zig
  • src/codegen/cppbind.ts
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/bindings/AnyPromise.zig
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/event_loop.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/codegen/cppbind.ts
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/event_loop.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/VirtualMachine.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/bun.js/bindings/VM.zig
  • src/codegen/cppbind.ts
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/event_loop.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/bun.js/bindings/VM.zig
  • src/codegen/cppbind.ts
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:11:57.056Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/bun.js/bindings/VM.zig
  • src/codegen/cppbind.ts
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : For properties marked cache: true, use the generated Zig accessors (<Prop>NameSetCached/GetCached) to work with GC-owned values

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Cache structures in ZigGlobalObject for bound classes

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/codegen/cppbind.ts
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/VirtualMachine.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/bun.js/bindings/VM.zig
  • src/codegen/cppbind.ts
  • src/bun.js/api/html_rewriter.zig
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/event_loop.zig
  • src/bun.js/bindings/JSValue.zig
  • src/bun.js/bindings/CatchScope.zig
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to src/bun.js/bindings/**/*.{cpp,hpp,h} : Define properties using JSC HashTableValue arrays for JS class bindings

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/JSPropertyIterator.zig
  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/bindings/JSPromise.zig
  • src/codegen/cppbind.ts
  • src/bun.js/bindings/AnyPromise.zig
  • src/bun.js/bindings/JSObject.zig
  • src/bun.js/api/JSBundler.zig
  • src/bun.js/bindings/StringBuilder.zig
  • src/bun.js/bindings/JSValue.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.cpp : Initialize the LazyClassStructure in GlobalObject::finishCreation and visit it in GlobalObject::visitChildrenImpl

Applied to files:

  • src/bun.js/jsc/host_fn.zig
  • src/codegen/cppbind.ts
  • src/bun.js/bindings/JSObject.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern

Applied to files:

  • src/bun.js/jsc/host_fn.zig
  • src/bun.js/bindings/JSObject.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/codegen/cppbind.ts
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:11:57.056Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/**/js_*.zig : Implement JavaScript bindings in a Zig file named with a js_ prefix (e.g., js_smtp.zig, js_your_feature.zig)

Applied to files:

  • src/codegen/cppbind.ts
  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Create Bun JSC class bindings by pairing a TypeScript class interface with a Zig implementation and relying on the generator to produce glue code

Applied to files:

  • src/codegen/cppbind.ts
📚 Learning: 2025-08-30T00:13:36.784Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.784Z
Learning: Applies to src/bun.js/**/*.zig : In finalize() for objects holding JS references, release them using .deref() before destroy

Applied to files:

  • src/bun.js/bindings/JSObject.zig
📚 Learning: 2025-08-30T00:05:37.983Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-30T00:05:37.983Z
Learning: Applies to **/*.zig : In Zig code, manage memory carefully: use the correct allocator and defer for cleanup

Applied to files:

  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:11:57.056Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.056Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code

Applied to files:

  • src/bun.js/bindings/StringBuilder.zig
📚 Learning: 2025-08-30T00:11:00.878Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.878Z
Learning: Applies to **/*.cpp : Provide an extern "C" Bun__<Type>__toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Applied to files:

  • src/bun.js/bindings/StringBuilder.zig
🔇 Additional comments (27)
src/bun.js/event_loop/JSCScheduler.zig (1)

7-7: Inline ExceptionValidationScope init is correct

Matches the project-wide pattern; lifecycle is preserved with defer. No issues.

src/bun.js/event_loop.zig (1)

458-458: Inline CatchScope init LGTM

Initialization is now idiomatic; deinit still deferred and existing exception checks remain intact.

src/bun.js/api/html_rewriter.zig (1)

897-897: CatchScope inline init LGTM

Consistent with the refactor; scope lifetime and downstream exception handling are unchanged.

src/codegen/cppbind.ts (1)

673-673: Approve inline scope initialization change

No legacy two-step scope initialization patterns remain in the Zig codegen; both CatchScope and ExceptionValidationScope use inline .init(...) with defer scope.deinit(). Change is sound.

src/bun.js/jsc/host_fn.zig (1)

93-95: Inline scope init across host call shims looks good

toJSHostCall, fromJSHostCall, and fromJSHostCallGeneric now use .init(...); the assert/return checks still follow immediately with deferred deinit. All good.

Also applies to: 117-119, 133-135

src/bun.js/bindings/CatchScope.zig (1)

2-2: Verify Zig/C++ size/alignment contract across all builds.

size depends on allow_assert/enable_asan. Ensure CatchScopeBinding.cpp’s static_asserts use the identical condition set so size/alignment match on macOS/Linux/Windows (asan/non-asan, debug/release). Mismatches explain the cross-platform clang errors in CI.

src/bun.js/ModuleLoader.zig (1)

473-481: LGTM: inline CatchScope construction is the right pattern.

Once .init is fixed to set location against the returned object, this site will be safe. Keeping the variable in-place and deferring deinit() is correct.

src/bun.js/VirtualMachine.zig (1)

536-540: LGTM: scope around no-side-effects stringify.

The defer to clear a pending exception is correct; with the .init fix, the self-location assertion will hold.

src/bun.js/bindings/JSObject.zig (1)

144-149: Inline CatchScope init is correct and clearer.

The change preserves semantics (handles "undefined with exception" correctly) and simplifies the pattern.

src/bun.js/bindings/VM.zig (2)

152-158: throwError flow looks right; explicit error path is good.

Pre-assert, throw, post-assert, then return error.JSError is consistent and easy to reason about.


152-158: Bindings verified – the C++ CatchScope shim’s size, alignment, constructor signature, and exported symbols match Zig’s CatchScope and ExceptionValidationScope definitions, and the .init(...) usage in VM.zig correctly maps to those externs.

src/bun.js/bindings/JSPropertyIterator.zig (2)

122-127: Good: switched to designated .init(...) for CatchScope.

Maintains behavior with simpler initialization.


130-135: Same here: consistent scope init style.

Reads cleaner; exception propagation unchanged.

src/bun.js/api/JSBundler.zig (1)

1092-1104: LGTM: CatchScope inline init in runOnEndCallbacks.

Pattern matches the rest of the refactor; exception check remains in place.

src/bun.js/bindings/JSValue.zig (6)

1203-1208: Use of ExceptionValidationScope with .init(...) is correct.

Keeps the toStringOrNull contract explicit and tightens assertions.


1389-1394: LGTM: inline CatchScope for getIfPropertyExistsFromPath.

Correct propagation and unchanged semantics.


1417-1421: LGTM: scope init in _then2.

Keeps the debug assert while simplifying setup.


1424-1428: LGTM: scope init in then.

Consistent with the rest of the file.


1496-1504: LGTM: getOwn uses inline CatchScope.

Exception handling remains explicit; return shape unchanged.


1598-1615: LGTM: getStringish scope init.

The ordering (toBunString, errdefer, returnIfException) is correct.

src/bun.js/bindings/StringBuilder.zig (7)

15-19: deinit path OK once address stability is guaranteed.

This looks fine and will pair with destroy() per above. Ensure C++ deinit tolerates being called exactly once.


20-44: Enum/type mapping looks solid.

Covers all append variants needed by the binding surface.


46-58: Append dispatch is clean.

Comptime switch is appropriate; signatures match externs.


84-90: Imports/readability LGTM.

No issues.


8-14: Verify and refactor StringBuilder init usage
No callsites for StringBuilder.init() (or the proposed initInPlace/create/destroy) were found—ensure there are no remaining return-by-value usages of this C++ wrapper and apply the in-place or heap-allocated pattern as needed.


79-83: No changes needed: ensureUnusedCapacity delegates to C++ reserveCapacity, which doesn’t return an error or allow catching OOM; surfacing allocation failure isn’t feasible.

Likely an incorrect or invalid review comment.


59-67: Signatures match between Zig externs and C++ definitions
C++ StringBuilder__appendLatin1/Utf16 take (WTF::StringBuilder*, const LChar*/UChar*, size_t) and Zig declares [*]const u8/u16 with usize; StringBuilder__appendString takes BunString by value matching Zig String; appendLChar/appendUChar use u8/u16 as expected. No ABI mismatches found.

This reverts commit ff4231c.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants