Skip to content

feat(minify): optimize Error constructors by removing 'new' keyword - #22493

Merged
Jarred-Sumner merged 23 commits into
mainfrom
claude/minify-error-constructors
Sep 9, 2025
Merged

Jarred-Sumner merged 23 commits into
mainfrom
claude/minify-error-constructors

Conversation

@robobun

@robobun robobun commented Sep 8, 2025 •

Copy link
Copy Markdown
Collaborator

Summary

  • Refactored maybeMarkConstructorAsPure to minifyGlobalConstructor that returns ?Expr
  • Added minification optimizations for global constructors that work identically with/without new
  • Converts constructors to more compact forms: new Object() → {}, new Array() → [], etc.
  • Fixed issue where minification was incorrectly applied to runtime node_modules code

Details

This PR refactors the existing maybeMarkConstructorAsPure function to minifyGlobalConstructor and changes it to return an optional expression. This enables powerful minification optimizations for global constructors.

Optimizations Added:

1. Error Constructors (4 bytes saved each)

  • new Error(...) → Error(...)
  • new TypeError(...) → TypeError(...)
  • new SyntaxError(...) → SyntaxError(...)
  • new RangeError(...) → RangeError(...)
  • new ReferenceError(...) → ReferenceError(...)
  • new EvalError(...) → EvalError(...)
  • new URIError(...) → URIError(...)
  • new AggregateError(...) → AggregateError(...)

2. Object Constructor

  • new Object() → {} (11 bytes saved)
  • new Object({a: 1}) → {a: 1} (11 bytes saved)
  • new Object([1, 2]) → [1, 2] (11 bytes saved)
  • new Object(null) → {} (15 bytes saved)
  • new Object(undefined) → {} (20 bytes saved)

3. Array Constructor

  • new Array() → [] (10 bytes saved)
  • new Array(1, 2, 3) → [1, 2, 3] (9 bytes saved)
  • new Array(5) → Array(5) (4 bytes saved, preserves sparse array semantics)

4. Function and RegExp Constructors

  • new Function(...) → Function(...) (4 bytes saved)
  • new RegExp(...) → RegExp(...) (4 bytes saved)

Important Fixes:

  • Added check to prevent minification of node_modules code at runtime (only applies during bundling)
  • Preserved sparse array semantics for new Array(number)
  • Extracted callFromNew helper to reduce code duplication

Size Impact:

  • React SSR bundle: 463 bytes saved
  • Each optimization safely preserves JavaScript semantics

Test plan

✅ All tests pass:

  • Added comprehensive tests in bundler_minify.test.ts
  • Verified Error constructors work identically with/without new
  • Tested Object/Array literal conversions
  • Ensured sparse array semantics are preserved
  • Updated source map positions in bundler_npm.test.ts

🤖 Generated with Claude Code

Refactored `maybeMarkConstructorAsPure` to `minifyGlobalConstructor` to return an optional expression instead of mutating in place. Added optimization that converts `new Error(...)` to `Error(...)` for all Error constructor types, saving 4 bytes per call.

Changes:
- Renamed and refactored `maybeMarkConstructorAsPure` to `minifyGlobalConstructor`
- Function now returns `?Expr` instead of void for better composability
- Added Error, TypeError, SyntaxError, RangeError, ReferenceError, EvalError, and URIError to the optimization list
- These constructors behave identically with or without 'new', so we can safely remove it
- Preserves existing pure constructor marking for Date, Map, Set, etc.

Tests added to verify:
- All Error types are correctly optimized
- Runtime behavior is preserved
- Other constructors still get @__PURE__ annotations

This optimization is particularly useful in minified production code where Error constructors are commonly used.

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

Co-Authored-By: Claude <noreply@anthropic.com>
@robobun

robobun commented Sep 8, 2025 •

Copy link
Copy Markdown
Collaborator Author
Updated 8:09 PM PT - Sep 8th, 2025

❌ @dylan-conway, your commit ae2bf8a has 2 failures in Build #25472:


🧪   To try this PR locally:

bunx bun-pr 22493

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

bun-22493 --bun

@coderabbitai

coderabbitai Bot commented Sep 8, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Adds extensive global-constructor minification: expands KnownGlobal enums, replaces maybeMarkConstructorAsPure with minifyGlobalConstructor that may return an inline js_ast.Expr (allocator, loc, minify_whitespace); visitExpr substitutes returned expressions; parser/runtime flags updated and numerous tests/snapshots/sourcemaps rebased.

Changes

Cohort / File(s) Summary
AST minification: globals
src/ast/KnownGlobal.zig, src/ast/visitExpr.zig
Expand KnownGlobal with many Error types and globals (AggregateError, Array, Object, Function, RegExp, etc.). Add callFromNew helper. Replace maybeMarkConstructorAsPure with minifyGlobalConstructor(allocator, e, symbols, loc, minify_whitespace) -> ?js_ast.Expr and implement detailed constructor-to-call/literal minification and purity hints. visitExpr now substitutes returned inline expressions.
Parser / runtime flags
src/bundler/ParseTask.zig, src/runtime.zig
Propagate minify_whitespace into JS parser options (opts.features.minify_whitespace). Replace Runtime.Features flags minify_syntax and minify_identifiers with minify_whitespace: bool = false.
Bundler tests / sourcemaps
test/bundler/bundler_minify.test.ts, test/bundler/bundler_npm.test.ts
Add clusters of minify tests covering Error/global constructor optimizations (note: duplicate clusters present). Re-baseline sourcemap mappings and expected emitted size for npm bundler test.
JS runtime / util snapshots
test/js/bun/test/stack.test.ts, test/js/bun/test/test-error-code-done-callback.test.ts, test/js/bun/test/test-test.test.ts, test/js/bun/util/inspect-error.test.js, test/js/bun/util/inspect.test.js, test/js/bun/util/reportError.test.ts, test/js/web/console/console-log.test.ts
Update inline snapshots and expected stack-frame columns/indentation to reflect shifted source positions/formatting; no behavioral or public API changes.
CLI hot sourcemap test
test/cli/hot/hot.test.ts
Adjust expected sourcemap column to account for presence of new token in thrown expression.

Suggested reviewers

  • Jarred-Sumner

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description does not follow the repository’s required template, as it uses custom headings like “## Summary” and “## Details” instead of the mandated “### What does this PR do?” and “### How did you verify your code works?”. Please restructure the description to include the exact template headings (“### What does this PR do?” and “### How did you verify your code works?”) and move the summary, detailed change list, and test verification steps accordingly.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title is concise, follows the conventional “feat(scope): description” format, and accurately highlights a significant change—the optimization of Error constructors by removing the new keyword—which is a clear part of this changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/minify-error-constructors

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
test/bundler/bundler_minify.test.ts (1)

759-800: Runtime-behavior check is useful; keep tests data-driven

Consider table-driven cases (describe.each) for the three Error types to reduce duplication and make it easier to extend.

src/ast/KnownGlobal.zig (2)

186-222: Minor: remove unused alias

The alias below appears unused and could be dropped to avoid noise.

Apply this diff:

-const Map = std.AutoHashMapUnmanaged;

21-41: Optional: consider AggregateError (with verification first)

If spec-conformant across targets (ECMA262/engines), AggregateError might also be safely callable without new. If validated, you could add it to KnownGlobal and the switch list; otherwise, keeping it out is safer.

Would you like me to check spec/engine behavior for AggregateError() vs new AggregateError() before we include it?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 7a19927 and 59fe054.

📒 Files selected for processing (3)
  • src/ast/KnownGlobal.zig (13 hunks)
  • src/ast/visitExpr.zig (1 hunks)
  • test/bundler/bundler_minify.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
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/ast/visitExpr.zig
  • src/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/visitExpr.zig
  • src/ast/KnownGlobal.zig
test/**

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

Place all tests under the test/ directory

Files:

  • test/bundler/bundler_minify.test.ts
test/bundler/**/*

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

Place bundler/transpiler/CSS/bun build tests under test/bundler/

Files:

  • test/bundler/bundler_minify.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/bundler/bundler_minify.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/bundler/bundler_minify.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/bundler/bundler_minify.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/bundler/bundler_minify.test.ts
🧠 Learnings (7)
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-09-08T00:41:12.041Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.041Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add a corresponding test case in test/v8/v8.test.ts that invokes checkSameOutput with the new function

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
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/bundler/bundler_minify.test.ts
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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:

  • test/bundler/bundler_minify.test.ts
  • src/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-09-08T00:41:12.041Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.041Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add new V8 API mangled symbols (without leading underscore) to src/symbols.txt

Applied to files:

  • src/ast/KnownGlobal.zig
🧬 Code graph analysis (1)
test/bundler/bundler_minify.test.ts (1)
test/bundler/expectBundled.ts (1)
  • itBundled (1694-1728)
🔇 Additional comments (6)
src/ast/visitExpr.zig (1)

1494-1496: LGTM: early return on minified constructor is correct

Rewriting here and short-circuiting when minify_syntax is enabled looks good. Flags/args/loc are preserved via KnownGlobal.minifyGlobalConstructor, and DCE semantics are maintained.

test/bundler/bundler_minify.test.ts (2)

693-757: Good coverage for optimization and non-Error constructors

Solid assertions for all Error variants and for preserving /* @PURE */ on Date/Map/Set. This should catch regressions in both rewrite and purity marking.


802-849: Semantics parity test is on point

Nice verification that call vs. construct is indistinguishable for Error-family instances and stacks.

src/ast/KnownGlobal.zig (3)

11-17: Enum additions look right

Adding the Error-family variants matches the new optimization surface. No conflicts with existing names.


21-41: Safe rewrite: new Error(...) → Error(...)

The guardrails (identifier-only target + unbound symbol + KnownGlobal match) make this safe. Copying args, loc, and can_be_unwrapped_if_unused preserves behavior and DCE. One small check: confirm the struct field names are correct across types:

  • E.Call uses close_paren_loc
  • E.New provides close_parens_loc

If these differ (paren vs. parens), compilation will fail; otherwise, all good.


42-127: Purity marking preserved for Weak{Map,Set}/Set/Date/etc.

Logic mirrors prior behavior while returning null to indicate “no unwrap-expression.” This keeps the optimization focused and non-invasive.

Enhanced the minifier to convert constructors to more efficient forms:
- `new Object()` → `{}`
- `new Object({a: 1})` → `{a: 1}` (unwraps the argument)
- `new Object(null/undefined)` → `{}`
- `new Array()` → `[]`
- `new Array(1, 2, 3)` → `[1, 2, 3]`
- `new Array(n)` → `Array(n)` (preserved for sparse arrays)
- Added Array, Object, Function, and RegExp to the list of constructors that work without 'new'

These optimizations provide even better size savings compared to just removing 'new'.

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
src/ast/KnownGlobal.zig (2)

73-80: Reduce duplication: helper to build Call from New

You repeat the New→Call construction in several branches. A tiny helper improves readability and reduces error surface for field names.

Example (outside the switch):

inline fn callFromNew(e: *E.New, loc: logger.Loc) js_ast.Expr {
    const call = E.Call{
        .target = e.target,
        .args = e.args,
        .close_paren_loc = e.close_parens_loc,
        .can_be_unwrapped_if_unused = e.can_be_unwrapped_if_unused,
    };
    return js_ast.Expr.init(E.Call, call, loc);
}

Then replace repeated blocks with return callFromNew(e, loc);

Also applies to: 98-106, 111-118


311-314: Remove unused alias

const Map = std.AutoHashMapUnmanaged is unused and may trigger Zig’s unused-constant error; drop it.

-const Map = std.AutoHashMapUnmanaged;
test/bundler/bundler_minify.test.ts (2)

759-800: Avoid mixing compile-time capture with runtime capture

This test defines a capture function and also uses the capture: [...] expectation. The compile-time capture harness won’t apply when capture is shadowed. Prefer asserting only run.stdout here.

   itBundled("minify/ErrorConstructorWithVariables", {
@@
-    capture: [
-      "val",
-      "e1.message",
-      "e2.message",
-      "e3.message",
-      "e1 instanceof Error",
-      "e2 instanceof TypeError",
-      "e3 instanceof SyntaxError",
-      "e.message",
-    ],
     minifySyntax: true,
     target: "bun",

802-849: Same here: prefer runtime assertions only

This test also defines capture(). Keep only run.stdout (it’s already present and sufficient).

   itBundled("minify/ErrorConstructorPreservesSemantics", {
@@
-    capture: [
-      "val",
-      "e1 instanceof Error",
-      "e2 instanceof Error",
-      'e1.message === "with new"',
-      'e2.message === "without new"',
-      'typeof e1.stack === "string"',
-      'typeof e2.stack === "string"',
-      "withNew.constructor === withoutNew.constructor",
-    ],
     minifySyntax: true,
     target: "bun",
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 59fe054 and b5f92c9.

📒 Files selected for processing (2)
  • src/ast/KnownGlobal.zig (13 hunks)
  • test/bundler/bundler_minify.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
test/**

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

Place all tests under the test/ directory

Files:

  • test/bundler/bundler_minify.test.ts
test/bundler/**/*

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

Place bundler/transpiler/CSS/bun build tests under test/bundler/

Files:

  • test/bundler/bundler_minify.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/bundler/bundler_minify.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/bundler/bundler_minify.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/bundler/bundler_minify.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/bundler/bundler_minify.test.ts
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/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/KnownGlobal.zig
🧠 Learnings (8)
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-09-08T00:41:12.041Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.041Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add a corresponding test case in test/v8/v8.test.ts that invokes checkSameOutput with the new function

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-09-08T00:41:12.041Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.041Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add new V8 API mangled symbols (without leading underscore) to src/symbols.txt

Applied to files:

  • src/ast/KnownGlobal.zig
🧬 Code graph analysis (1)
test/bundler/bundler_minify.test.ts (1)
test/bundler/expectBundled.ts (1)
  • itBundled (1694-1728)
🔇 Additional comments (5)
src/ast/KnownGlobal.zig (4)

11-21: Enum extensions look good

Adding Error, TypeError, SyntaxError, RangeError, ReferenceError, EvalError, URIError, Array, Object, Function, RegExp is consistent with the new optimizer scope.


35-45: Safe 'new Error(...)' → 'Error(...)' rewrite

This transformation is spec-safe for all built-in Error constructors and the implementation looks correct.


47-80: Object constructor handling is sound

  • 0 args → {} and special-casing {}/[]/null/undefined are correct.
  • The generic “remove new” fallback for other cases preserves semantics.

25-33: Verified: minifyGlobalConstructor unwrapping and close_parens_loc mapping are correct
All call sites use if (…)?|minified| to handle the optional return, and e.close_parens_loc (plural) correctly assigns to E.Call.close_paren_loc (singular).

test/bundler/bundler_minify.test.ts (1)

694-757: Good coverage of Error constructor minification

Covers all built-in Error types, complex args, and preserves @PURE for Date/Map/Set. Nice.

Comment thread src/ast/KnownGlobal.zig
Comment thread src/ast/KnownGlobal.zig Outdated
Comment on lines +108 to +118
.Function, .RegExp => {
// Just remove 'new' for Function and RegExp
// RegExp literal conversion would require parsing the pattern string
const call = E.Call{
.target = e.target,
.args = e.args,
.close_paren_loc = e.close_parens_loc,
.can_be_unwrapped_if_unused = e.can_be_unwrapped_if_unused,
};
return js_ast.Expr.init(E.Call, call, loc);
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Bug: RegExp rewrite can change semantics for RegExp inputs

new RegExp(re) creates a copy; RegExp(re) returns re itself when flags are undefined. Rewriting new RegExp(/abc/) → RegExp(/abc/) returns the same instance, changing identity and lastIndex behavior.

Guard the optimization:

-            .Function, .RegExp => {
-                // Just remove 'new' for Function and RegExp
+            .Function => {
+                // Just remove 'new' for Function
                 // RegExp literal conversion would require parsing the pattern string
                 const call = E.Call{
                     .target = e.target,
                     .args = e.args,
                     .close_paren_loc = e.close_parens_loc,
                     .can_be_unwrapped_if_unused = e.can_be_unwrapped_if_unused,
                 };
                 return js_ast.Expr.init(E.Call, call, loc);
             },
+            .RegExp => {
+                const n = e.args.len;
+                if (n == 1) {
+                    const arg0 = e.args.ptr[0];
+                    // If the first arg is a RegExp literal/expression and no flags, preserve 'new'
+                    if (arg0.data == .e_reg_exp) {
+                        return null;
+                    }
+                }
+                const call = E.Call{
+                    .target = e.target,
+                    .args = e.args,
+                    .close_paren_loc = e.close_parens_loc,
+                    .can_be_unwrapped_if_unused = e.can_be_unwrapped_if_unused,
+                };
+                return js_ast.Expr.init(E.Call, call, loc);
+            },

Also add tests to cover new RegExp(/a/) identity vs RegExp(/a/).

📝 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
.Function, .RegExp => {
// Just remove 'new' for Function and RegExp
// RegExp literal conversion would require parsing the pattern string
const call = E.Call{
.target = e.target,
.args = e.args,
.close_paren_loc = e.close_parens_loc,
.can_be_unwrapped_if_unused = e.can_be_unwrapped_if_unused,
};
return js_ast.Expr.init(E.Call, call, loc);
},
.Function => {
// Just remove 'new' for Function
// RegExp literal conversion would require parsing the pattern string
const call = E.Call{
.target = e.target,
.args = e.args,
.close_paren_loc = e.close_parens_loc,
.can_be_unwrapped_if_unused = e.can_be_unwrapped_if_unused,
};
return js_ast.Expr.init(E.Call, call, loc);
},
.RegExp => {
const n = e.args.len;
if (n == 1) {
const arg0 = e.args.ptr[0];
// If the first arg is a RegExp literal/expression and no flags, preserve 'new'
if (arg0.data == .e_reg_exp) {
return null;
}
}
const call = E.Call{
.target = e.target,
.args = e.args,
.close_paren_loc = e.close_parens_loc,
.can_be_unwrapped_if_unused = e.can_be_unwrapped_if_unused,
};
return js_ast.Expr.init(E.Call, call, loc);
},
🤖 Prompt for AI Agents
In src/ast/KnownGlobal.zig around lines 108 to 118, the rewrite that strips
'new' from Function and RegExp calls incorrectly converts new RegExp(re) into
RegExp(re), changing semantics for RegExp inputs because new RegExp(re) produces
a copy while RegExp(re) returns the same instance when flags are undefined. Fix
by guarding the optimization: only remove 'new' for RegExp when the first
argument is absent or provably not a RegExp (e.g., not a RegExp literal and not
statically typed as RegExp); if the first arg may be a RegExp value, leave the
Call as new RegExp(...). Add unit tests asserting identity and lastIndex
behavior that new RegExp(/a/) !== /a/ and new RegExp(/a/).lastIndex is
independent from the original, whereas RegExp(/a/) returns the same instance.

Comment on lines +851 to +906
itBundled("minify/AdditionalGlobalConstructorOptimization", {
files: {
"/entry.js": /* js */ `
// Test Array constructor
capture(new Array());
capture(new Array(3));
capture(new Array(1, 2, 3));

// Test Object constructor
capture(new Object());
capture(new Object(null));
capture(new Object({ a: 1 }));

// Test Function constructor
capture(new Function("return 42"));
capture(new Function("a", "b", "return a + b"));

// Test RegExp constructor
capture(new RegExp("test"));
capture(new RegExp("test", "gi"));
capture(new RegExp(/abc/));

// Test with variables
const pattern = "\\d+";
capture(new RegExp(pattern));

// Test that other constructors are preserved
capture(new Date());
capture(new Map());
capture(new Set());
`,
},
capture: [
"[]", // new Array() -> []
"Array(3)", // new Array(3) stays as Array(3) because it creates sparse array
`[
1,
2,
3
]`, // new Array(1, 2, 3) -> [1, 2, 3]
"{}", // new Object() -> {}
"{}", // new Object(null) -> {}
"{ a: 1 }", // new Object({ a: 1 }) -> { a: 1 }
'Function("return 42")',
'Function("a", "b", "return a + b")',
'RegExp("test")',
'RegExp("test", "gi")',
"RegExp(/abc/)",
"RegExp(pattern)",
"/* @__PURE__ */ new Date",
"/* @__PURE__ */ new Map",
"/* @__PURE__ */ new Set",
],
minifySyntax: true,
target: "bun",
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add a guard case for RegExp(RegExp) semantics

Please add a test to ensure new RegExp(/abc/) is NOT rewritten (it must clone, not return the same instance), while RegExp("abc") and RegExp("abc","g") can be safely de-newed.

   itBundled("minify/AdditionalGlobalConstructorOptimization", {
@@
         // Test RegExp constructor
         capture(new RegExp("test"));
         capture(new RegExp("test", "gi"));
         capture(new RegExp(/abc/));
+        // Semantics: new RegExp(re) must clone; RegExp(re) returns the same instance
+        const re = /xyz/g;
+        const a = new RegExp(re);
+        const b = RegExp(re);
+        console.log(a === re, b === re);
@@
     capture: [
@@
       "RegExp(/abc/)",
+      // Runtime check: expect "false\ntrue"
     ],
     minifySyntax: true,
     target: "bun",
+    run: { stdout: "false\ntrue" },
   });

Committable suggestion skipped: line range outside the PR's diff.

Comment thread test/bundler/bundler_minify.test.ts
The minifier optimizations reduced the output size by 463 bytes, which shifted source map column positions. Updated the expected values to match the new optimized output.

Changes:
- Updated source map column positions (shifted left due to smaller output)
- Updated expected file size from 222174 to 221711 bytes
- All tests now pass with the new optimizations

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
test/bundler/bundler_npm.test.ts (1)

70-70: Exact filesize assertion may churn; add rationale or consider a less brittle check

The new 221711 value makes sense given the minification, but exact byte assertions tend to change with upstream deps. At minimum, add a short comment to capture why the size changed.

Apply this diff to document the rationale:

     expectExactFilesize: {
+      // Bundle shrank due to removing "new " from Error constructors (minifySyntax).
       "out/entry.js": 221711,
     },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 b5f92c9 and 30ebd57.

📒 Files selected for processing (1)
  • test/bundler/bundler_npm.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
test/**

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

Place all tests under the test/ directory

Files:

  • test/bundler/bundler_npm.test.ts
test/bundler/**/*

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

Place bundler/transpiler/CSS/bun build tests under test/bundler/

Files:

  • test/bundler/bundler_npm.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/bundler/bundler_npm.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/bundler/bundler_npm.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/bundler/bundler_npm.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/bundler/bundler_npm.test.ts
🧠 Learnings (7)
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/sourcemap.test.ts : sourcemap.test.ts should verify source map correctness in dev

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-09-08T04:44:59.085Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.085Z
Learning: Applies to test/**/*.test.{ts,tsx} : Prefer snapshot tests over exact stdout equality assertions

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-09-08T04:44:59.085Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.085Z
Learning: Applies to test/**/*.test.{ts,tsx} : In tests, use normalizeBunSnapshot when asserting snapshots

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/css.test.ts : css.test.ts should contain CSS bundling tests in dev mode

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-09-08T00:41:12.041Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.041Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add a corresponding test case in test/v8/v8.test.ts that invokes checkSameOutput with the new function

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/react-spa.test.ts : react-spa.test.ts should contain React SPA, react-refresh, and basic server component transform tests

Applied to files:

  • test/bundler/bundler_npm.test.ts
🔇 Additional comments (1)
test/bundler/bundler_npm.test.ts (1)

60-66: Sourcemap offsets updated — consistent with Error constructor minification. LGTM

The decreased column offsets align with dropping the 'new ' prefix on Error constructors. Anchors still target the same source symbols/strings, so this snapshot update looks correct.

Claude Bot and others added 2 commits September 8, 2025 10:36
Updated source map positions and file sizes to account for combined optimizations:
- typeof undefined optimization from main (saves 60 bytes)
- constructor optimizations from this PR (saves additional 403 bytes)
- Total savings: 463 bytes on React SSR bundle

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
test/bundler/bundler_npm.test.ts (1)

70-70: Exact filesize assertion is brittle; consider softening it.

Exact byte counts tend to vary with unrelated tweaks and across platforms. Prefer a snapshot or a ≤ threshold (if harness supports it) to reduce churn.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 30ebd57 and f3fd6ec.

📒 Files selected for processing (3)
  • src/ast/visitExpr.zig (1 hunks)
  • test/bundler/bundler_minify.test.ts (1 hunks)
  • test/bundler/bundler_npm.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/bundler/bundler_minify.test.ts
🧰 Additional context used
📓 Path-based instructions (8)
test/**

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

Place all tests under the test/ directory

Files:

  • test/bundler/bundler_npm.test.ts
test/bundler/**/*

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

Place bundler/transpiler/CSS/bun build tests under test/bundler/

Files:

  • test/bundler/bundler_npm.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/bundler/bundler_npm.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/bundler/bundler_npm.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/bundler/bundler_npm.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/bundler/bundler_npm.test.ts
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/ast/visitExpr.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/visitExpr.zig
🧠 Learnings (7)
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/sourcemap.test.ts : sourcemap.test.ts should verify source map correctness in dev

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-09-08T04:44:59.085Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.085Z
Learning: Applies to test/**/*.test.{ts,tsx} : Prefer snapshot tests over exact stdout equality assertions

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-09-08T04:44:59.085Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.085Z
Learning: Applies to test/**/*.test.{ts,tsx} : In tests, use normalizeBunSnapshot when asserting snapshots

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/css.test.ts : css.test.ts should contain CSS bundling tests in dev mode

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-09-08T00:41:12.041Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.041Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add a corresponding test case in test/v8/v8.test.ts that invokes checkSameOutput with the new function

Applied to files:

  • test/bundler/bundler_npm.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/react-spa.test.ts : react-spa.test.ts should contain React SPA, react-refresh, and basic server component transform tests

Applied to files:

  • test/bundler/bundler_npm.test.ts
⏰ 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 (2)
test/bundler/bundler_npm.test.ts (1)

60-66: Snapshot mappings updated to reflect size/offset shifts — looks correct.

Offsets shrink consistently with the constructor minification change; no red flags.

src/ast/visitExpr.zig (1)

1495-1497: minifyGlobalConstructor preserves the original can_be_unwrapped_if_unused flag on returned E.Call (including new Error …), matching previous behavior; no changes required.

Claude Bot and others added 3 commits September 8, 2025 11:00
- Removed unused Map alias in KnownGlobal.zig
- Added AggregateError to the list of Error constructors that can be optimized
- Verified AggregateError behaves identically with and without 'new'
- Added tests for AggregateError optimization

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

Co-Authored-By: Claude <noreply@anthropic.com>
Per CodeRabbit feedback, extracted repeated New→Call conversion pattern
into a helper function to improve readability and reduce error surface.

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
src/ast/KnownGlobal.zig (2)

81-99: Bug: new Array(x) → [x] can change semantics when x may be a number.

With n == 1, x being numeric at runtime must not become [x]. Current guard arg.data != .e_number is insufficient (e.g., const n = 3 is an identifier, not .e_number).

Use a stricter proof that the single arg is definitely non‑numeric (or a literal object/array), else strip only new:

-                // new Array(1, 2, 3) -> [1, 2, 3]
-                // But NOT new Array(3) which creates an array with 3 empty slots
-                if (n > 1 or (n == 1 and e.args.ptr[0].data != .e_number)) {
-                    var array = E.Array{};
-                    array.items = e.args;
-                    return js_ast.Expr.init(E.Array, array, loc);
-                }
+                // new Array(1, 2, 3) -> [1, 2, 3]
+                if (n > 1) {
+                    var array_lit = E.Array{};
+                    array_lit.items = e.args;
+                    return js_ast.Expr.init(E.Array, array_lit, loc);
+                }
+                // n == 1: only emit literal when arg is provably non-numeric
+                if (n == 1) {
+                    const arg0 = e.args.ptr[0];
+                    switch (arg0.knownPrimitive()) {
+                        .string, .null, .undefined, .boolean => {
+                            var array_lit = E.Array{};
+                            array_lit.items = e.args;
+                            return js_ast.Expr.init(E.Array, array_lit, loc);
+                        },
+                        else => {},
+                    }
+                    switch (arg0.data) {
+                        .e_object, .e_array => {
+                            var array_lit = E.Array{};
+                            array_lit.items = e.args;
+                            return js_ast.Expr.init(E.Array, array_lit, loc);
+                        },
+                        else => {},
+                    }
+                }
                 // For new Array(number), just remove 'new'
                 return callFromNew(e, loc);

Please add tests covering const n = 3; new Array(n) and let x = 'a'; new Array(x).


101-105: Bug: Removing ‘new’ from RegExp can change identity semantics.

new RegExp(re) clones; RegExp(re) returns re when flags are undefined. Don’t strip new for the 1‑arg RegExp‑literal case.

Split the cases:

-            .Function, .RegExp => {
-                // Just remove 'new' for Function and RegExp
-                // RegExp literal conversion would require parsing the pattern string
-                return callFromNew(e, loc);
-            },
+            .Function => {
+                // Safe to strip 'new'
+                return callFromNew(e, loc);
+            },
+            .RegExp => {
+                const n = e.args.len;
+                if (n == 1 and e.args.ptr[0].data == .e_reg_exp) {
+                    // Preserve 'new' to avoid returning the same instance
+                    return null;
+                }
+                return callFromNew(e, loc);
+            },

Add tests asserting new RegExp(/a/) !== RegExp(/a/) and independent lastIndex.

🧹 Nitpick comments (1)
src/ast/KnownGlobal.zig (1)

36-44: Unused allocator parameter.

allocator is not used (explicitly discarded). Either drop it from the signature or put it to use; keeping unused params adds churn to all call sites.

Apply this if you decide to remove it:

-pub noinline fn minifyGlobalConstructor(allocator: std.mem.Allocator, noalias e: *E.New, symbols: []const Symbol, loc: logger.Loc) ?js_ast.Expr {
-    _ = allocator;
+pub noinline fn minifyGlobalConstructor(noalias e: *E.New, symbols: []const Symbol, loc: logger.Loc) ?js_ast.Expr {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 8170f7d and 852115e.

📒 Files selected for processing (1)
  • src/ast/KnownGlobal.zig (12 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
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/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/KnownGlobal.zig
🧠 Learnings (4)
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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/ast/KnownGlobal.zig
🔇 Additional comments (12)
src/ast/KnownGlobal.zig (12)

11-22: Good expansion of KnownGlobal with Error family and others.

Covers all standard Error constructors and common globals needed for rewrites.


26-34: Nice helper extraction (callFromNew).

Keeps emission consistent and reduces duplication.


47-50: Safe ‘new’-stripping for Error constructors.

Gate on .unbound ensures we only touch the real globals. This rewrite is semantically safe and size‑positive.


52-79: Object() rewrite looks correct.

  • new Object() → {}
  • new Object(obj/arr/null/undefined) handled precisely
  • other cases fall back to call form

All sound.


106-137: Purity markings for WeakSet/WeakMap look consistent.

Zero-arg and trivial arg cases marked .if_unused; others preserved.


141-166: Date purity handling LGTM.

Only marks pure where arguments are known primitives; otherwise conservatively impure.


168-191: Set purity handling LGTM.

Matches existing strategy; avoids risky iterator cases.


193-203: Headers: conservative and fine.

Marks zero-arg pure; no unsafe rewrites.


205-233: Response: conservative and fine.

Zero-arg and primitive‑arg cases marked pure; no rewrites.


234-248: TextEncoder/Decoder purity LGTM.

No risky transforms; purity only for zero-arg.


250-286: Map purity handling LGTM.

Guards iterator side effects; handles nested array literal tuples precisely.


293-297: Imports setup is correct.

std needed for the allocator type; logger alias used for loc typing.

When optimizing `new Array(x)` where x is a number, we must not convert it to `[x]`
because they have different semantics:
- `new Array(5)` creates a sparse array with 5 empty slots
- `[5]` creates an array with one element: the number 5

This fix uses .knownPrimitive() to detect when the single argument is a number and
preserves the Array(x) form in that case, while still converting non-numeric single
arguments to array literals for better minification.

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (4)
src/ast/KnownGlobal.zig (2)

81-99: Bug: single-arg new Array(x) may be rewritten unsafely

Using knownPrimitive() != .number will treat unknown/indeterminate values as “non-number” and incorrectly rewrite new Array(x) to [x] (changes semantics when x is numeric at runtime). Guard only when we can prove the single arg is definitely not a number (string/null/undefined/boolean). Leave other single-arg cases as a call form.

Apply this fix:

-                // new Array(1, 2, 3) -> [1, 2, 3]
-                // But NOT new Array(3) which creates an array with 3 empty slots
-                if (n > 1 or (n == 1 and e.args.ptr[0].knownPrimitive() != .number)) {
-                    var array = E.Array{};
-                    array.items = e.args;
-                    return js_ast.Expr.init(E.Array, array, loc);
-                }
-
-                // For new Array(number), just remove 'new'
-                return callFromNew(e, loc);
+                // new Array(1, 2, 3) -> [1, 2, 3]
+                if (n > 1) {
+                    var array_lit = E.Array{};
+                    array_lit.items = e.args;
+                    return js_ast.Expr.init(E.Array, array_lit, loc);
+                }
+
+                // n == 1: only emit literal when we can prove it's not a number at runtime
+                const arg0 = e.args.ptr[0];
+                switch (arg0.knownPrimitive()) {
+                    .string, .null, .undefined, .boolean => {
+                        var array_lit = E.Array{};
+                        array_lit.items = e.args;
+                        return js_ast.Expr.init(E.Array, array_lit, loc);
+                    },
+                    // .number or unknown -> preserve semantics by only removing 'new'
+                    else => {
+                        return callFromNew(e, loc);
+                    },
+                }

101-105: Bug: RegExp rewrite can change semantics for RegExp inputs

new RegExp(re) clones; RegExp(re) returns re itself when flags are undefined. Always stripping new breaks identity/lastIndex behavior.

Split Function and RegExp, and guard the single-arg RegExp-literal case:

-            .Function, .RegExp => {
-                // Just remove 'new' for Function and RegExp
-                // RegExp literal conversion would require parsing the pattern string
-                return callFromNew(e, loc);
-            },
+            .Function => {
+                // Just remove 'new' for Function
+                return callFromNew(e, loc);
+            },
+            .RegExp => {
+                const n = e.args.len;
+                if (n == 1) {
+                    const arg0 = e.args.ptr[0];
+                    // If arg is a RegExp literal, preserve 'new' to keep clone semantics
+                    if (arg0.data == .e_reg_exp) {
+                        return null;
+                    }
+                }
+                return callFromNew(e, loc);
+            },

Add a runtime test (see test file suggestion) to lock this in.

test/bundler/bundler_minify.test.ts (2)

857-934: Fix test to preserve 'new' for RegExp(/re/) and add a runtime identity check

As-is, this test expects new RegExp(/abc/) to de-new, which is incorrect (should clone, not return the same instance).

Apply:

@@
-        // Test RegExp constructor
+        // Test RegExp constructor
         capture(new RegExp("test"));
         capture(new RegExp("test", "gi"));
-        capture(new RegExp(/abc/));
+        // If first arg is a RegExp, 'new' must be preserved to clone
+        capture(new RegExp(/abc/));
+        // Identity semantics check
+        const re = /xyz/g;
+        const a = new RegExp(re);
+        const b = RegExp(re);
+        console.log(a === re, b === re);
@@
-      'RegExp("test", "gi")',
-      "RegExp(/abc/)",
+      'RegExp("test")',
+      'RegExp("test", "gi")',
+      "new RegExp(/abc/)",
       "RegExp(pattern)",
@@
-    minifySyntax: true,
-    target: "bun",
+    minifySyntax: true,
+    target: "bun",
+    run: { stdout: "false\ntrue" },

This pairs with the Zig fix to guard RegExp.


936-991: Add safety test: single-arg Array(variable) must not become a literal

Covers the common regression where new Array(n) is rewritten to [n] when n is unknown at build time.

Suggested patch:

@@
         const a1 = new Array(1, 2, 3);
         const a2 = Array(1, 2, 3);
         capture(JSON.stringify(a1) === JSON.stringify(a2));
         capture(a1.constructor === a2.constructor);
 
+        // Single-arg variable: must preserve sparse semantics when numeric
+        const n = 3;
+        const a3 = new Array(n);
+        const a4 = Array(n);
+        capture(a3.length === a4.length && a3.length === 3 && 0 in a3 === false);
+
@@
-      "a1.constructor === a2.constructor",
+      "a1.constructor === a2.constructor",
+      "a3.length === a4.length && a3.length === 3 && 0 in a3 === !1",
@@
-    run: {
-      stdout: "true\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue",
-    },
+    run: { stdout: "true\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue" },

This will fail with the current Zig logic, surfacing the bug early.

🧹 Nitpick comments (1)
test/bundler/bundler_minify.test.ts (1)

808-855: Optional: Add AggregateError parity check

To fully exercise the Error family, add a quick AggregateError new vs call comparison.

Proposed addition inside this test:

@@
         const errors = [
           [new TypeError("t1"), TypeError("t2")],
           [new SyntaxError("s1"), SyntaxError("s2")],
           [new RangeError("r1"), RangeError("r2")],
+          [new AggregateError([], "a1"), AggregateError([], "a2")],
         ];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 852115e and 9a381e5.

📒 Files selected for processing (2)
  • src/ast/KnownGlobal.zig (12 hunks)
  • test/bundler/bundler_minify.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
test/**

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

Place all tests under the test/ directory

Files:

  • test/bundler/bundler_minify.test.ts
test/bundler/**/*

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

Place bundler/transpiler/CSS/bun build tests under test/bundler/

Files:

  • test/bundler/bundler_minify.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/bundler/bundler_minify.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/bundler/bundler_minify.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/bundler/bundler_minify.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/bundler/bundler_minify.test.ts
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/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/KnownGlobal.zig
🧠 Learnings (6)
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-09-07T05:41:52.546Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-09-07T05:41:52.546Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{js,ts} : Prefer JSC intrinsics and `$`-prefixed private APIs for performance and safety (e.g., `$Array`, `$newArrayWithSize`, `$putByIdDirectPrivate`, `$assert`, `$debug`)

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-09-07T05:41:52.546Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-09-07T05:41:52.546Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{js,ts} : Use `$isObject` and throw appropriate TypeErrors for constructor/initializer inputs that must be objects

Applied to files:

  • test/bundler/bundler_minify.test.ts
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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/ast/KnownGlobal.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
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/ast/KnownGlobal.zig
🧬 Code graph analysis (1)
test/bundler/bundler_minify.test.ts (1)
test/bundler/expectBundled.ts (1)
  • itBundled (1694-1728)
🔇 Additional comments (9)
src/ast/KnownGlobal.zig (6)

11-22: LGTM: Adding Error-family and other globals to KnownGlobal

Enum expansion looks correct and aligns with the new optimization goals.


26-34: LGTM: Helper to convert New → Call

callFromNew cleanly centralizes the emission; preserves can_be_unwrapped_if_unused.


36-44: API refactor to return ?Expr is sound

Early-exit on unbound symbols and optional rewrite make the visitor simpler.


47-50: Safe to strip 'new' for Error constructors

Spec allows calling Error, TypeError, SyntaxError, RangeError, ReferenceError, EvalError, URIError, AggregateError without new.

If we want belt-and-suspenders, add a small runtime test for AggregateError to ensure cross-engine parity (see test suggestion below).


52-79: LGTM: Object constructor rewrites preserve semantics

  • 0 args → {}
  • 1 arg object/array → identity
  • null/undefined → {}
    All other cases: remove 'new'. Looks correct.

293-297: Minor: imports/logger alias look good

Consistent with surrounding modules.

test/bundler/bundler_minify.test.ts (3)

695-763: LGTM: Error constructors are optimized and pure annotations preserved for others

Good coverage across Error types and interactions with other constructors.


765-806: LGTM: Variable/instanceof/try-catch paths validate behavior

Runtime assertions are helpful here.


808-855: LGTM: Error() vs new Error() equivalence is validated

Nice constructor equality checks and stack presence assertions.

Claude Bot and others added 5 commits September 8, 2025 14:48
The global constructor optimization was being applied to all code when
minification was enabled, including node_modules loaded at runtime. This
caused issues with packages like proxy-addr (used by Express) that rely
on specific array creation patterns.

Added check to skip minification for node_modules code to ensure runtime
compatibility while still optimizing user code during bundling.
The minification optimization changes source map positions and file sizes
in the bundler_npm test. Updated expected values to match the new output.
Comment thread src/ast/visitExpr.zig Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/ast/visitExpr.zig (1)

1495-1497: Minify guard present (matches prior guidance)

The minification is correctly guarded by p.options.features.minify_syntax as suggested in prior feedback.

test/bundler/bundler_minify.test.ts (2)

936-991: Add safety test for single-arg Array(variable) case

Guard against regressions that might rewrite new Array(n) into [n] when n is unknown at build time.

Apply:

@@
         capture(a1.constructor === a2.constructor);
@@
         const sparse = new Array(5);
         capture(sparse.length === 5);
         capture(0 in sparse === false); // No element at index 0
         capture(JSON.stringify(sparse) === "[null,null,null,null,null]");
+
+        // Single-arg variable case: must preserve sparse semantics
+        const n = 3;
+        const a3 = new Array(n);
+        const a4 = Array(n);
+        capture(a3.length === a4.length && a3.length === 3 && a3[0] === undefined);
@@
     capture: [
       "val",
       "JSON.stringify(a1) === JSON.stringify(a2)",
       "a1.constructor === a2.constructor",
       "sparse.length === 5",
       "0 in sparse === !1",
       'JSON.stringify(sparse) === "[null,null,null,null,null]"',
+      "a3.length === a4.length && a3.length === 3 && a3[0] === undefined",
       "typeof o1 === typeof o2",
       "o1.constructor === o2.constructor",
       "typeof f1 === typeof f2",
       "f1() === f2()",
       "r1.source === r2.source",
       "r1.flags === r2.flags",
     ],
@@
-    run: {
-      stdout: "true\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue",
-    },
+    run: {
+      stdout: "true\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue\ntrue",
+    },

858-934: Preserve RegExp identity semantics; don’t rewrite new RegExp(/abc/)

Rewriting new RegExp(/abc/) to RegExp(/abc/) changes identity and lastIndex behavior. Keep new when the first arg is a RegExp literal; add a runtime test to lock this down.

Apply:

@@
-        // Test RegExp constructor
+        // Test RegExp constructor
         capture(new RegExp("test"));
         capture(new RegExp("test", "gi"));
-        capture(new RegExp(/abc/));
+        // Literal RegExp: must NOT de-new (identity differs)
+        capture(new RegExp(/abc/));
+
+        // Semantics: new RegExp(re) must clone; RegExp(re) returns the same instance
+        const re = /xyz/g;
+        const a = new RegExp(re);
+        const b = RegExp(re);
+        console.log(a === re, b === re);
@@
-      'RegExp("test", "gi")',
-      "RegExp(/abc/)",
+      'RegExp("test", "gi")',
+      // Preserve 'new' for literal RegExp
+      "new RegExp(/abc/)",
       "RegExp(pattern)",
@@
-    target: "bun",
+    target: "bun",
+    run: { stdout: "false\ntrue" },
🧹 Nitpick comments (2)
test/js/bun/test/test-error-code-done-callback.test.ts (1)

52-52: Column offsets updated across snapshots

The higher column values reflect the removal of “new” in constructor calls. Consider normalizing columns (like you already normalize times and paths) to reduce future churn, but this is optional.

Also applies to: 62-62, 72-72, 82-82, 92-92, 102-102, 112-112, 122-122, 132-132

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

736-736: Avoid hard-coding exact column in first frame (optional)

To make this resilient to future minifier tweaks, normalize the “:line:column” bit before asserting (similar to console-log test). Example diff:

-      if (process.platform === "win32") {
-        expect(stackLines[0]).toContain(`<dir>\\my-test.test.js:5:15`.replace("<dir>", test_dir));
-      }
+      if (process.platform === "win32") {
+        expect(stackLines[0].replace(/:\d+:\d+$/, ":NN:NN")).toContain(
+          `<dir>\\my-test.test.js:5:NN`.replace("<dir>", test_dir),
+        );
+      }
-      if (process.platform !== "win32") {
-        expect(stackLines[0]).toContain(`<dir>/my-test.test.js:5:15`.replace("<dir>", test_dir));
-      }
+      if (process.platform !== "win32") {
+        expect(stackLines[0].replace(/:\d+:\d+$/, ":NN:NN")).toContain(
+          `<dir>/my-test.test.js:5:NN`.replace("<dir>", test_dir),
+        );
+      }

Also applies to: 739-739

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 7a19927 and 91153ba.

📒 Files selected for processing (10)
  • src/ast/KnownGlobal.zig (12 hunks)
  • src/ast/visitExpr.zig (1 hunks)
  • test/bundler/bundler_minify.test.ts (1 hunks)
  • test/js/bun/test/stack.test.ts (1 hunks)
  • test/js/bun/test/test-error-code-done-callback.test.ts (9 hunks)
  • test/js/bun/test/test-test.test.ts (1 hunks)
  • test/js/bun/util/inspect-error.test.js (5 hunks)
  • test/js/bun/util/inspect.test.js (1 hunks)
  • test/js/bun/util/reportError.test.ts (1 hunks)
  • test/js/web/console/console-log.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
test/**

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

Place all tests under the test/ directory

Files:

  • test/js/bun/test/stack.test.ts
  • test/js/bun/test/test-error-code-done-callback.test.ts
  • test/js/bun/test/test-test.test.ts
  • test/js/bun/util/inspect.test.js
  • test/js/web/console/console-log.test.ts
  • test/js/bun/util/reportError.test.ts
  • test/bundler/bundler_minify.test.ts
  • test/js/bun/util/inspect-error.test.js
test/js/**/*.{js,ts}

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

Place JavaScript and TypeScript tests under test/js/

Files:

  • test/js/bun/test/stack.test.ts
  • test/js/bun/test/test-error-code-done-callback.test.ts
  • test/js/bun/test/test-test.test.ts
  • test/js/bun/util/inspect.test.js
  • test/js/web/console/console-log.test.ts
  • test/js/bun/util/reportError.test.ts
  • test/js/bun/util/inspect-error.test.js
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/test/stack.test.ts
  • test/js/bun/test/test-error-code-done-callback.test.ts
  • test/js/bun/test/test-test.test.ts
  • test/js/bun/util/inspect.test.js
  • test/js/bun/util/reportError.test.ts
  • test/js/bun/util/inspect-error.test.js
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/js/bun/test/stack.test.ts
  • test/js/bun/test/test-error-code-done-callback.test.ts
  • test/js/bun/test/test-test.test.ts
  • test/js/bun/util/inspect.test.js
  • test/js/web/console/console-log.test.ts
  • test/js/bun/util/reportError.test.ts
  • test/bundler/bundler_minify.test.ts
  • test/js/bun/util/inspect-error.test.js
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/js/bun/test/stack.test.ts
  • test/js/bun/test/test-error-code-done-callback.test.ts
  • test/js/bun/test/test-test.test.ts
  • test/js/web/console/console-log.test.ts
  • test/js/bun/util/reportError.test.ts
  • test/bundler/bundler_minify.test.ts
test/js/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Organize unit tests for specific features under test/js/ by module

Files:

  • test/js/bun/test/stack.test.ts
  • test/js/bun/test/test-error-code-done-callback.test.ts
  • test/js/bun/test/test-test.test.ts
  • test/js/bun/util/inspect.test.js
  • test/js/web/console/console-log.test.ts
  • test/js/bun/util/reportError.test.ts
  • test/js/bun/util/inspect-error.test.js
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/js/bun/test/stack.test.ts
  • test/js/bun/test/test-error-code-done-callback.test.ts
  • test/js/bun/test/test-test.test.ts
  • test/js/web/console/console-log.test.ts
  • test/js/bun/util/reportError.test.ts
  • test/bundler/bundler_minify.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/js/bun/test/stack.test.ts
  • test/js/bun/test/test-error-code-done-callback.test.ts
  • test/js/bun/test/test-test.test.ts
  • test/js/bun/util/inspect.test.js
  • test/js/web/console/console-log.test.ts
  • test/js/bun/util/reportError.test.ts
  • test/bundler/bundler_minify.test.ts
  • test/js/bun/util/inspect-error.test.js
test/js/web/**/*.{js,ts}

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

Place Web API tests under test/js/web/, separated by category

Files:

  • test/js/web/console/console-log.test.ts
test/bundler/**/*

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

Place bundler/transpiler/CSS/bun build tests under test/bundler/

Files:

  • test/bundler/bundler_minify.test.ts
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/ast/visitExpr.zig
  • src/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/visitExpr.zig
  • src/ast/KnownGlobal.zig
🧬 Code graph analysis (1)
test/bundler/bundler_minify.test.ts (1)
test/bundler/expectBundled.ts (1)
  • itBundled (1694-1728)
🔇 Additional comments (15)
test/js/bun/test/stack.test.ts (1)

81-81: Snapshot column update looks correct

The shift to originalColumn: 18 matches the new constructor-minification behavior. No action needed.

test/js/web/console/console-log.test.ts (1)

133-133: Caret alignment snapshot update is fine

The adjusted indentation under the error line reflects the new stack formatting. Looks good.

test/js/bun/util/inspect.test.js (1)

664-664: Inline snapshot caret shift acknowledged

Updated caret position aligns with changed column offsets. No further changes needed.

test/js/bun/util/reportError.test.ts (1)

24-27: Snapshot now points to updated locations

The caret and stack frame positions match the new formatting. Approved.

test/js/bun/util/inspect-error.test.js (1)

16-16: Snapshot position adjustments look consistent

All updated line/column references align with constructor-call minification and minified fixture shifts. Good to go.

Also applies to: 18-18, 24-24, 26-26, 121-121, 154-154, 169-169

src/ast/visitExpr.zig (2)

1495-1497: Early-returning the minified expression is fine; verify call-visit side effects aren’t needed

Returning a Call/other expression from e_new is correct, but double-check:

  • Flags like can_be_unwrapped_if_unused are preserved (previously set by maybeMarkConstructorAsPure).
  • Any metadata (loc/close_paren_loc) needed for accurate sourcemaps is set on the returned node.

If either isn’t guaranteed by KnownGlobal.minifyGlobalConstructor, consider re-visiting the returned expr once (p.visitExpr) or setting the flags explicitly in the returned node.


1495-1497: Runtime/node_modules gating

The PR mentions not applying runtime minification to node_modules. Since this call site doesn’t pass source-context, please confirm minifyGlobalConstructor internally checks runtime vs bundling and node_modules paths before transforming; otherwise, we may need to thread that context in.

test/bundler/bundler_minify.test.ts (3)

695-763: LGTM: Error constructors “de-newed” with correct captures

Covers all Error types and includes sanity checks for non-Error constructors. Looks good.


765-806: LGTM: Variable-based Error construction and instanceof checks

Runtime assertions verify semantics; concise and solid.


808-855: LGTM: Parity between Error() and new Error()

Good coverage of message, stack presence, and constructor equality.

src/ast/KnownGlobal.zig (5)

26-34: Helper extraction is clean

callFromNew reduces duplication and improves clarity.


46-50: LGTM: Error-family “de-new” is semantics-preserving

Calling Error constructors without new is equivalent; good byte savings.


125-156: Pure annotations for WeakSet/WeakMap (0-arg and some 1-arg) are fine

Conservative and safe; no change requested.


157-210: Date/Set purity marking is reasonable

Pure when args are empty or trivially coercible; returns null to let DCE work. Looks good.


224-305: Headers/Response/TextDecoder/TextEncoder/Map: conservative purity

Approach is sound; maintains side-effect safety.

Comment thread src/ast/KnownGlobal.zig
Claude Bot and others added 2 commits September 8, 2025 22:16
- Add safety test for single-arg Array(variable) case to ensure sparse array semantics are preserved
- Extend single-argument Array() handling in KnownGlobal.zig to convert object/array literals (e.g., new Array({}) -> [{}], new Array([1]) -> [[1]])
- Refactor code style to use inline struct initialization for cleaner code
- Update sourcemap positions and file size in bundler_npm.test.ts to reflect optimization changes

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (3)
src/ast/KnownGlobal.zig (3)

145-151: Incorrectly marking new WeakSet(null) / new WeakMap(null) as pure (these throw)

Passing null triggers a TypeError via iterator lookup; it’s not safe to DCE.

Apply:

-                        .e_null, .e_undefined => {
-                            // "new WeakSet(null)" is pure
-                            // "new WeakSet(void 0)" is pure
-                            e.can_be_unwrapped_if_unused = .if_unused;
-                        },
+                        .e_undefined => {
+                            // "new WeakSet(void 0)" / "new WeakMap(void 0)" is pure
+                            e.can_be_unwrapped_if_unused = .if_unused;
+                        },
+                        .e_null => {
+                            // "new WeakSet(null)" / "new WeakMap(null)" throws; do not mark pure
+                        },

Please mirror the comment text for WeakMap as well.


206-213: new Set(null) is not pure (throws); refine purity for array inputs

  • null throws via iterator access; don’t mark pure.
  • Consider only treating new Set([]) as pure; non-empty arrays perform iteration/add which we typically avoid DCE-ing.

Apply:

-                        .e_array, .e_null, .e_undefined => {
-                            // "new Set([a, b, c])" is pure
-                            // "new Set(null)" is pure
-                            // "new Set(void 0)" is pure
-                            e.can_be_unwrapped_if_unused = .if_unused;
-                        },
+                        .e_undefined => {
+                            // "new Set(void 0)" is pure
+                            e.can_be_unwrapped_if_unused = .if_unused;
+                        },
+                        .e_null => {
+                            // "new Set(null)" throws; not pure
+                        },
+                        .e_array => |array| {
+                            if (array.items.len == 0) {
+                                // "new Set([])" is pure
+                                e.can_be_unwrapped_if_unused = .if_unused;
+                            }
+                        },

Add tests asserting (() => new Set(null)) throws, and that new Set([]) may be DCE’d.


289-296: new Map(null) is not pure (throws); keep undefined as pure

Same iterator semantics as Set; please don’t mark null as pure. Optionally restrict array-literal handling to [] only for consistency with Set/WeakSet.

Apply:

-                        .e_null, .e_undefined => {
-                            // "new Map(null)" is pure
-                            // "new Map(void 0)" is pure
-                            e.can_be_unwrapped_if_unused = .if_unused;
-                        },
+                        .e_undefined => {
+                            // "new Map(void 0)" is pure
+                            e.can_be_unwrapped_if_unused = .if_unused;
+                        },
+                        .e_null => {
+                            // "new Map(null)" throws; not pure
+                        },

And (optional) narrow the .e_array case to only mark new Map([]) as pure.

♻️ Duplicate comments (1)
src/ast/KnownGlobal.zig (1)

129-133: Do not strip new from RegExp when the first arg is a RegExp (identity/lastIndex semantics change)

new RegExp(re) creates a copy; RegExp(re) returns re when flags are undefined. This changes identity and lastIndex. Guard this case.

Apply:

-            .Function, .RegExp => {
-                // Just remove 'new' for Function and RegExp
-                // RegExp literal conversion would require parsing the pattern string
-                return callFromNew(e, loc);
-            },
+            .Function => {
+                // Just remove 'new' for Function
+                return callFromNew(e, loc);
+            },
+            .RegExp => {
+                const n = e.args.len;
+                if (n >= 1) {
+                    const arg0 = e.args.ptr[0];
+                    if (arg0.data == .e_reg_exp) {
+                        // If flags are absent or explicitly undefined, RegExp(re[, undefined]) returns `re` (no copy)
+                        if (n == 1) return null;
+                        const flags = e.args.ptr[1];
+                        switch (flags.knownPrimitive()) {
+                            .undefined => return null,
+                            // If flags is definitely a string (or any non-undefined), removing `new` is safe.
+                            .string, .boolean, .number, .bigint, .null => {},
+                            else => return null, // unknown: be conservative
+                        }
+                    }
+                }
+                return callFromNew(e, loc);
+            },

Please add tests covering:

  • const r = /a/g; const a = new RegExp(r); a !== r;
  • const r = /a/g; const b = RegExp(r); b === r;
  • RegExp(r, "i") !== r.
🧹 Nitpick comments (2)
src/ast/KnownGlobal.zig (2)

81-127: Array single-arg handling is now safe; consider also literalizing Symbols

The “definitely-not-a-number” check is solid. Optional: treat .symbol from knownPrimitive() as safe to literalize too (Symbols are never numbers).

Apply:

-                    switch (primitive) {
-                        .null, .undefined, .boolean, .string, .bigint => {
+                    switch (primitive) {
+                        .null, .undefined, .boolean, .string, .bigint, .symbol => {
                             // These are definitely not numbers, safe to convert
                             return js_ast.Expr.init(E.Array, .{ .items = e.args }, loc);
                         },

278-314: Map array-arg purity heuristic may be too liberal

Today it allows any non-empty array-of-arrays. To be conservative and consistent with Set/WeakSet, consider limiting to [] only (optional).

If you want, I can draft tests to measure the impact and catch edge-cases around iterator monkey-patching.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 943a112 and d27a2fc.

📒 Files selected for processing (1)
  • src/ast/KnownGlobal.zig (12 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
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/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/KnownGlobal.zig
🔇 Additional comments (10)
src/ast/KnownGlobal.zig (10)

11-22: Enum additions look good and cover the targeted constructors

Set is comprehensive for this PR’s scope.


26-34: Good helper extraction (callFromNew)

Removes duplication and preserves call metadata.


36-44: Guarding on unbound globals is correct

Skipping shadowed bindings avoids semantic changes.


45-51: Safe to drop new for Error-family constructors

Spec preserves behavior when called as functions; nice win.


52-80: Object constructor transforms are sound

  • new Object() → {} and unwrapping object/array args are semantics-preserving.
  • null/undefined → {} is correct.

167-174: Date purity notes LGTM

0-arg and primitive single-arg cases marked pure only-if-unused is reasonable.


221-231: Headers purity marking LGTM

Conservative and safe.


233-261: Response purity marking LGTM

0-arg and primitive single-arg: fine to DCE if unused.


262-276: TextEncoder/TextDecoder purity marking LGTM

Only 0-arg treated as pure-if-unused; sensible.


321-329: Imports/aliasing look correct

No issues.

- new RegExp(re) creates a copy, but RegExp(re) returns the same instance
- This affects object identity and lastIndex behavior
- The semantics are too complex to safely optimize
- Added detailed comment explaining why RegExp optimization is disabled
- Updated test expectations to preserve new RegExp() calls

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

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
test/bundler/bundler_minify.test.ts (1)

857-934: Add explicit RegExp identity test (clone vs same-instance).

To guard against future regressions if RegExp de-new is reintroduced, add an identity check: new RegExp(re) must not be rewritten, while RegExp(re) returns the same instance.

   itBundled("minify/AdditionalGlobalConstructorOptimization", {
@@
         capture(new RegExp(/abc/));
+        // Identity semantics: new RegExp(re) must clone; RegExp(re) returns the same instance
+        const re = /xyz/g;
+        const a = new RegExp(re);
+        const b = RegExp(re);
+        console.log(a === re, b === re);
@@
     capture: [
@@
       "new RegExp(/abc/)",
+      // Expect: "false\ntrue"
     ],
     minifySyntax: true,
     target: "bun",
+    run: { stdout: "false\ntrue" },
   });
🧹 Nitpick comments (2)
src/ast/KnownGlobal.zig (2)

52-80: Object() transformations are safe; add tiny readability tweak.

  • Returning the arg for .e_object/.e_array retains identity semantics.
  • null/undefined → {} is correct.
    Optional: early-return on n == 1 to reduce nesting.
             .Object => {
                 const n = e.args.len;

                 if (n == 0) {
                     // new Object() -> {}
                     return js_ast.Expr.init(E.Object, E.Object{}, loc);
                 }

-                if (n == 1) {
+                if (n == 1) {
                     const arg = e.args.ptr[0];
                     switch (arg.data) {
                         .e_object, .e_array => {
                             // new Object({a: 1}) -> {a: 1}
                             // new Object([1, 2]) -> [1, 2]
                             return arg;
                         },
                         .e_null, .e_undefined => {
                             // new Object(null) -> {}
                             // new Object(undefined) -> {}
                             return js_ast.Expr.init(E.Object, E.Object{}, loc);
                         },
                         else => {},
                     }
-                }
-
-                // For other cases, just remove 'new'
-                return callFromNew(e, loc);
+                    // Other primitives: just remove 'new'
+                    return callFromNew(e, loc);
+                }
+                // n > 1: just remove 'new'
+                return callFromNew(e, loc);
             },

133-140: Consider re-enabling safe RegExp de-new only where semantics can’t change.

You currently skip all RegExp optimizations to avoid identity pitfalls. That’s safe but leaves bytes on the table. We can safely strip new except when there is exactly one argument that is a RegExp literal/expression with no flags.

Apply within this block:

-            .RegExp => {
-                // Don't optimize RegExp - the semantics are too complex:
-                // - new RegExp(re) creates a copy, but RegExp(re) returns the same instance
-                // - This affects object identity and lastIndex behavior
-                // - The difference only applies when flags are undefined
-                // Keep the original new RegExp() call to preserve correct semantics
-                return null;
-            },
+            .RegExp => {
+                const n = e.args.len;
+                if (n == 1) {
+                    const arg0 = e.args.ptr[0];
+                    // If the arg is a RegExp literal/expression and no flags, preserve 'new' to clone
+                    if (arg0.data == .e_reg_exp) return null;
+                }
+                // 0 args or 1+ args where first is not a RegExp: safe to remove 'new'
+                return callFromNew(e, loc);
+            },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 d27a2fc and 84ebe74.

📒 Files selected for processing (3)
  • src/ast/KnownGlobal.zig (12 hunks)
  • test/bundler/bundler_minify.test.ts (1 hunks)
  • test/bundler/bundler_npm.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/bundler/bundler_npm.test.ts
🧰 Additional context used
📓 Path-based instructions (8)
test/**

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

Place all tests under the test/ directory

Files:

  • test/bundler/bundler_minify.test.ts
test/bundler/**/*

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

Place bundler/transpiler/CSS/bun build tests under test/bundler/

Files:

  • test/bundler/bundler_minify.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/bundler/bundler_minify.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/bundler/bundler_minify.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/bundler/bundler_minify.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/bundler/bundler_minify.test.ts
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/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/KnownGlobal.zig
🧬 Code graph analysis (1)
test/bundler/bundler_minify.test.ts (1)
test/bundler/expectBundled.ts (1)
  • itBundled (1694-1728)
🔇 Additional comments (11)
src/ast/KnownGlobal.zig (7)

11-22: Enum expansion looks correct and aligns with intended minifications.

Covers all built-in Error types plus Array/Object/Function/RegExp. No concerns.


26-34: Helper extraction LGTM; improves reuse and keeps emit logic consistent.

Reusing can_be_unwrapped_if_unused is important for DCE. Nice.


36-45: Good shadowing guard via .unbound.

Prevents breaking user-shadowed globals. Returning ?js_ast.Expr is a clean interface for replacements vs purity-only cases.


46-51: Safe ‘de-new’ for Error constructors.

Built-in Error constructors are callable and produce equivalent instances; replacing with calls preserves behavior and saves bytes.


81-127: Array single-arg handling looks careful; one edge-case note.

  • 0 args → [] and multi-arg → literal are correct.
  • Single-arg: only literalize when definitely not a number or when object/array literal — good.
    Note: BigInt is correctly treated as “not a Number” in the single-arg path (Array(1n) → [1n]). All good.

141-226: Purity markings for WeakSet/WeakMap/Date/Set/Headers/Response/TextEncoder/Decoder/Map are consistent with esbuild-style rules.

Marking as if_unused only, and avoiding replacements, keeps semantics. Looks good.

Also applies to: 228-321


328-333: Imports and aliases OK.

No unused symbols after _ = allocator;. Formatting consistent.

test/bundler/bundler_minify.test.ts (4)

695-763: Covers all Error constructors and ensures only intended constructors are minified.

Matches implementation (RegExp remains with new, Date/Map/Set pure-annotated). Good.


765-806: Variable/instanceof coverage for Error constructors is solid.

Validates semantics after de-newing. LGTM.


808-855: Behavioral parity test for Error vs new Error is helpful.

Covers stack/message/constructor parity. Nice.


936-998: Nice: sparse array semantics and single-arg variable guarded.

Covers the tricky Array(n) path and ensures no literalization when n is unknown. LGTM.

Comment thread src/ast/KnownGlobal.zig Outdated
}

// For new Array(number), just remove 'new'
return callFromNew(e, loc);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is unreachable. i think this should be switch (n) { 0 => {...}, 1 => {...}, else => {...}}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/ast/KnownGlobal.zig (1)

151-162: Function: OK to strip ‘new’; RegExp: good to skip

  • new Function(...) → Function(...) is safe.
  • Skipping RegExp rewrite avoids identity/lastIndex traps. Good call.
🧹 Nitpick comments (2)
src/ast/KnownGlobal.zig (2)

36-45: Avoid double-return pattern around switch

return switch (...) { ... return ...; } mixes expression-return with early returns, making the outer return switch redundant and potentially confusing. Prefer returning per-arm without wrapping the switch in a return.

Example:

-        return switch (constructor) {
+        switch (constructor) {
             .Error, ... => {
-                return callFromNew(e, loc);
+                return callFromNew(e, loc);
             },
             // ...
-        };
+        }

350-357: Import of js_parser only for Prefill — may be removable

If you switch to E.Missing as suggested, the js_parser import can likely be dropped from this file.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 1237cc8 and db6e0ed.

📒 Files selected for processing (4)
  • src/ast/KnownGlobal.zig (12 hunks)
  • src/ast/visitExpr.zig (1 hunks)
  • src/bundler/ParseTask.zig (1 hunks)
  • src/runtime.zig (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ast/visitExpr.zig
🧰 Additional context used
📓 Path-based instructions (2)
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/bundler/ParseTask.zig
  • src/runtime.zig
  • src/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/bundler/ParseTask.zig
  • src/runtime.zig
  • src/ast/KnownGlobal.zig
⏰ 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 (5)
src/bundler/ParseTask.zig (1)

1174-1180: Propagate whitespace-minify flag — LGTM

Hooking opts.features.minify_whitespace to transpiler.options.minify_whitespace is correct and scoped to parsing.

src/ast/KnownGlobal.zig (4)

46-49: Error-family: correct and safe to strip ‘new’

new Error(...) ≡ Error(...) across engines; byte-saving with identical semantics.


51-78: Object constructor rewrites look good

  • new Object() → {} is correct.
  • new Object(objOrArray) → argument is returned unchanged — correct.
  • new Object(null|undefined) → {} — correct fallback.
  • Other cases: removing new preserves semantics.

Please ensure tests cover: new Object(Symbol('x')), new Object(1n), new Object('s') (all should remain a wrapper when stripping new).


164-343: Purity flags for WeakSet/WeakMap/Date/Set/Map/Headers/Response/TextEncoder/Decoder — LGTM

Marking known pure cases with .if_unused enables DCE without changing semantics. Coverage looks conservative.

Add tests proving purity for:

  • new Set() and new Map() dropped when unused.
  • new WeakSet([]) doesn’t throw and is removable when unused.
  • new Response('') unused is removable.

80-149: Verify array hole representation and mutation safety

  • Confirm that using js_parser.Prefill.Data.EMissing here produces valid hole elements or switch to E.Missing if parser‐internal types aren’t permitted.
  • Ensure listManaged returns a cloned list—clearing and appending holes must not mutate the original e.args.
  • Add a brief comment explaining the 0–10 threshold choice for inlining holes.
  • Add tests for Array(3).length === 3, [,, ,].length === 3, and 0 in Array(3) === false vs [,,].

Comment thread src/runtime.zig
Comment on lines 169 to 173
minify_syntax: bool = false,
minify_identifiers: bool = false,
minify_whitespace: bool = false,
dead_code_elimination: bool = true,

Copy link
Copy Markdown
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

Include minify_whitespace in the runtime transpiler cache key

Features.minify_whitespace affects emitted code but isn’t part of hash_fields_for_runtime_transpiler. Toggling the flag may return stale cached output.

Apply:

         const hash_fields_for_runtime_transpiler = .{
             .top_level_await,
             .auto_import_jsx,
             .allow_runtime,
             .inlining,
             .commonjs_named_exports,
-            .minify_syntax,
-            .minify_identifiers,
+            .minify_syntax,
+            .minify_identifiers,
+            .minify_whitespace,
             .dead_code_elimination,
             .set_breakpoint_on_first_line,
             .trim_unused_imports,
             .dont_bundle_twice,
             .commonjs_at_runtime,
             .emit_decorator_metadata,
             .lower_using,

Run to ensure no other cache-key lists exist that also need this field:

Also applies to: 212-230


🏁 Script executed:

#!/bin/bash
rg -nP --type=zig -C2 'hash_fields_for_runtime_transpiler|Wyhash|hashForRuntimeTranspiler'

Length of output: 40602


Include minify_whitespace in runtime transpiler cache key

src/runtime.zig lines 212–230:

@@ 212
             .minify_syntax,
             .minify_identifiers,
+            .minify_whitespace,
             .dead_code_elimination,
📝 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
minify_syntax: bool = false,
minify_identifiers: bool = false,
minify_whitespace: bool = false,
dead_code_elimination: bool = true,
.minify_syntax,
.minify_identifiers,
.minify_whitespace,
.dead_code_elimination,
🤖 Prompt for AI Agents
In src/runtime.zig around lines 169-173 and 212-230, the minify_whitespace
boolean option is defined but not included in the runtime transpiler cache key;
add minify_whitespace to the cache key construction where minify_syntax,
minify_identifiers, and dead_code_elimination are included so the cache
differentiates builds with different whitespace minification. Ensure you append
a deterministic representation of the boolean (e.g.,
"minify_whitespace=true"/"minify_whitespace=false" or a single byte) into the
same buffer or hash input used for the key, update any key-length or hash
calculations accordingly, and run/update tests that assert cache behavior.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
test/bundler/bundler_minify.test.ts (1)

857-934: Add explicit RegExp identity semantics test

Please include the identity/clone distinction to lock this in: new RegExp(re) must not be rewritten while RegExp(re) returns the same instance when flags are omitted. This guards future regressions.

Apply within this test block:

@@
         // Test RegExp constructor
         capture(new RegExp("test"));
         capture(new RegExp("test", "gi"));
         capture(new RegExp(/abc/));
+
+        // Identity semantics: new RegExp(re) clones; RegExp(re) returns the same instance (no flags)
+        const re = /xyz/g;
+        const a = new RegExp(re);
+        const b = RegExp(re);
+        console.log(a === re, b === re);
@@
     capture: [
@@
       'new RegExp("test")',
       'new RegExp("test", "gi")',
       "new RegExp(/abc/)",
       "new RegExp(pattern)",
       "/* @__PURE__ */ new Date",
       "/* @__PURE__ */ new Map",
       "/* @__PURE__ */ new Set",
     ],
     minifySyntax: true,
     target: "bun",
+    run: { stdout: "false\ntrue" },
🧹 Nitpick comments (3)
test/bundler/bundler_minify.test.ts (2)

936-972: Nice whitespace-gated holes optimization matrix

Great set of cases. Consider adding 0.0 and -0 to document integer detection and that both collapse to [] in this mode.


974-1036: Good semantics lock for Array/Object/Function/RegExp

Optional: add a primitive-wrapper case to ensure new Object(1) → Object(1) only (and not a literal), and still matches constructor/type semantics.

Apply inside this test:

@@
         // Test Object semantics
         const o1 = new Object();
         const o2 = Object();
         capture(typeof o1 === typeof o2);
         capture(o1.constructor === o2.constructor);
+
+        // Primitive wrapper semantics preserved
+        const p1 = new Object(1);
+        const p2 = Object(1);
+        capture(p1 instanceof Number && p2 instanceof Number);
+        capture(Object.prototype.toString.call(p1) === Object.prototype.toString.call(p2));
@@
     capture: [
       "val",
       "JSON.stringify(a1) === JSON.stringify(a2)",
       "a1.constructor === a2.constructor",
       "sparse.length === 5",
       "0 in sparse === !1",
       'JSON.stringify(sparse) === "[null,null,null,null,null]"',
       "a3.length === a4.length && a3.length === 3 && a3[0] === void 0",
       "typeof o1 === typeof o2",
       "o1.constructor === o2.constructor",
+      "p1 instanceof Number && p2 instanceof Number",
+      "Object.prototype.toString.call(p1) === Object.prototype.toString.call(p2)",
       "typeof f1 === typeof f2",
       "f1() === f2()",
       "r1.source === r2.source",
       "r1.flags === r2.flags",
     ],
src/ast/KnownGlobal.zig (1)

80-149: Array rules look right; simplify the 0–10 check for maintainability

Logic preserves sparse semantics and only literalizes when provably non-number (including object/array literals). The 0–10 numeric whitelist is correct but verbose; use an integer-range check to reduce churn.

Apply within the .number arm:

-                                if (
-                                // only want this with whitespace minification
-                                minify_whitespace and
-                                    (val == 0 or
-                                        val == 1 or
-                                        val == 2 or
-                                        val == 3 or
-                                        val == 4 or
-                                        val == 5 or
-                                        val == 6 or
-                                        val == 7 or
-                                        val == 8 or
-                                        val == 9 or
-                                        val == 10))
-                                {
+                                if (minify_whitespace) {
+                                    const intval: usize = @intFromFloat(val);
+                                    if (@as(f64, @floatFromInt(intval)) == val and intval <= 10) {
                                      const arg_loc = arg.loc;
                                      var list = e.args.listManaged(allocator);
                                      list.clearRetainingCapacity();
-                                    bun.handleOom(list.appendNTimes(js_ast.Expr{ .data = js_parser.Prefill.Data.EMissing, .loc = arg_loc }, @intFromFloat(val)));
-                                    return js_ast.Expr.init(E.Array, .{ .items = .fromList(list) }, loc);
-                                }
+                                    bun.handleOom(list.appendNTimes(
+                                        js_ast.Expr{ .data = js_parser.Prefill.Data.EMissing, .loc = arg_loc },
+                                        intval,
+                                    ));
+                                    return js_ast.Expr.init(E.Array, .{ .items = .fromList(list) }, loc);
+                                    }
+                                }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • 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 db6e0ed and ae2bf8a.

📒 Files selected for processing (2)
  • src/ast/KnownGlobal.zig (12 hunks)
  • test/bundler/bundler_minify.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
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/ast/KnownGlobal.zig
**/*.zig

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

**/*.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

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

Files:

  • src/ast/KnownGlobal.zig
test/**

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

Place all tests under the test/ directory

Files:

  • test/bundler/bundler_minify.test.ts
test/bundler/**/*

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

Place bundler/transpiler/CSS/bun build tests under test/bundler/

Files:

  • test/bundler/bundler_minify.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/bundler/bundler_minify.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/bundler/bundler_minify.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/bundler/bundler_minify.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/bundler/bundler_minify.test.ts
🧬 Code graph analysis (1)
test/bundler/bundler_minify.test.ts (1)
test/bundler/expectBundled.ts (1)
  • itBundled (1694-1728)
🔇 Additional comments (10)
test/bundler/bundler_minify.test.ts (3)

695-763: Solid coverage for de-newing Error constructors

The expectations align with the implementation (strip new for Error-family; keep pure annotations on Date/Map/Set). Looks good.


765-806: Runtime assertions validate message/instanceof semantics

Good sanity checks; the stdout expectations exercise real behavior beyond snapshots.


808-855: Parities verified for Error() vs new Error()

Nice cross-check of constructor parity and stack presence. No issues.

src/ast/KnownGlobal.zig (7)

11-22: Enum expansion looks correct

New variants cover the constructors used by the minifier. No issues spotted.


26-34: Helper extraction is clean

callFromNew preserves args/paren loc/unwrapping flags; good reuse point.


36-43: Safe gating on unbound global identifiers

Early-exit on non-unbound symbols avoids rewriting shadowed constructors. Good.


44-49: De-newing Error-family is safe and wins bytes

Correctly funnels through callFromNew. Matches JS semantics.


51-79: Object() rewrites preserve semantics; unwraps for literal args are correct

  • {} for 0-arg and null/undefined is valid.
  • Returning the single object/array arg maintains identity.
    Fallback to Object(...) for other primitives is appropriate.

151-162: Leaving RegExp alone avoids subtle semantic traps

Good call to not de-new RegExp. Tests should include identity behavior to prevent regressions (suggested in test comments).

If desired, I can add the corresponding test case in this PR.


166-314: Purity marking for built-ins is conservative and safe

The .can_be_unwrapped_if_unused = .if_unused annotations look correct across Date/Set/Map/Weak* and web APIs. No rewrite is attempted where semantics can vary.

@Jarred-Sumner
Jarred-Sumner merged commit 20dddd1 into main Sep 9, 2025
58 of 61 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the claude/minify-error-constructors branch September 9, 2025 22:00
@coderabbitai coderabbitai Bot mentioned this pull request Sep 18, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Oct 11, 2025
3 of 5 tasks
Jarred-Sumner pushed a commit that referenced this pull request Sep 7, 2026
…1828)

### Problem
- With `minify_syntax`, `new Array(5, ...rest)` becomes `[5, ...rest]`.
When `rest` is empty the original means `new Array(5)`, five holes, and
the fold is `[5]`. For `const none = []; const a = new Array(5,
...none); console.log(a.length, 0 in a)` Node prints `5 false`. Bun
1.4.3 prints `1 true` under `bun run` and in `bun build --minify-syntax`
output.
- The cause is the more-than-one-argument branch of
`KnownGlobal::minify_global_constructor`
(`src/ast/known_global.rs:244`). It folds the arguments into a literal
without checking for a spread, so the argument count can differ at
runtime.

### Fix
- If any argument is a spread, emit `Array(5, ...rest)` instead of a
literal. This is the `call_from_new` form the single-argument branch
already uses when the argument may be a number.
- Correct because `Array` called as a function behaves like `new Array`
(ECMA-262 23.1.1). Only the literal fold was unsound.
- `EXPECTED_VERSION` in `RuntimeTranspilerCache.rs` moves to 29: the
runtime transpiler enables `minify_syntax`, so its cached output
changes.
- Verified: `test/bundler/bundler_minify.test.ts` (one case captures the
output, one runs it, both fail on 1.4.3). Also `bundler_npm.test.ts` and
`minify-new-array-with-if.test.ts`.

### Background
- `minify_global_constructor` is the byte-saving rewrite from #22493:
`new Object()` to `{}`, `new Array(1, 2)` to `[1, 2]`, and `new` dropped
from constructors that behave the same when called.
- `new Array(n)` with one number makes a sparse array of length `n`. Any
other argument list makes an array of the arguments. A spread hides
which case applies until runtime.
- This hunk comes from #37388, closed in favour of #41580. It is
independent of the stack-frame change there.

<details><summary>Notes</summary>

- `new Array(...xs)` alone was already safe: it is the single-argument
branch and stays a call.
- #41580 and #41159 also bump the transpiler cache version. Whichever
lands second bumps again.
- #41580 gates `minify_global_constructor` on `bundle`, which hides this
under `bun run` but not in `bun build --minify` output. This PR fixes
the fold itself, so it is needed either way.
</details>

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
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.

3 participants