Add Bun.wrapAnsi() for text wrapping with ANSI escape code preservation - #26061
Conversation
|
Updated 4:45 AM PT - Jan 16th, 2026
❌ @autofix-ci[bot], your commit 6a4674a has 3 failures in
🧪 To try this PR locally: bunx bun-pr 26061That installs a local version of the PR into your bun-26061 --bun |
WalkthroughAdds Bun.wrapAnsi with full C++ binding and helpers, TypeScript types, tests, benchmarks, width/ANSI utilities, and a bench dependency for wrap-ansi; wires a new JS export and exposes low-level visible-width functions to native code. Changes
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (1)src/**/*.zig📄 CodeRabbit inference engine (src/CLAUDE.md)
Files:
🧠 Learnings (6)📓 Common learnings📚 Learning: 2026-01-07T23:45:24.551ZApplied to files:
📚 Learning: 2026-01-07T23:45:24.551ZApplied to files:
📚 Learning: 2025-09-06T03:37:41.154ZApplied to files:
📚 Learning: 2025-08-30T09:09:18.384ZApplied to files:
📚 Learning: 2026-01-05T16:32:07.551ZApplied to files:
🔇 Additional comments (1)
✏️ Tip: You can disable this entire section by setting Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Fix all issues with AI agents
In `@packages/bun-types/bun.d.ts`:
- Around line 613-645: The JSDoc for WrapAnsiOptions.wordWrap is incorrect: when
false it still wraps to fill the column and will split words at character
boundaries rather than only breaking at explicit newlines; update the JSDoc on
the wordWrap property (interface WrapAnsiOptions, symbol wordWrap) to say that
true prefers wrapping at word boundaries when possible, and false performs
character-level wrapping to fill each line and will split words as needed (do
not claim it only wraps at explicit newlines).
In `@src/string/immutable/wrap_ansi.zig`:
- Around line 25-38: Replace the byte-by-byte CRLF normalization loop with a
bulk replace using std.mem.replace: allocate or create a mutable copy of input
(used in place of the manual append loop), call std.mem.replace to replace the
pattern "\r\n" with "\n" into the normalized buffer (reference symbols:
normalized, input, std.mem.replace), and adjust the resulting slice/length based
on the replace result (account for the number of replacements to compute the
final normalized length) before using/deinitializing normalized; this keeps
behavior identical but is more efficient for large inputs.
- Around line 193-216: The code currently swallows allocation failures with
`catch {}` when appending to `new_row` and `trailing_ansi`, risking silent
corruption; change those `catch {}` usages to propagate errors (use
`try`/`return`/! as appropriate) for all allocator operations involving
`new_row.append`, `new_row.appendSlice`, and `trailing_ansi.append`, and ensure
the final `new_row.appendSlice(allocator, trailing_ansi.items)` also propagates
errors; adjust cleanup so `trailing_ansi.deinit(allocator)` and
`row.deinit(allocator)` still run on error (use defer or explicit cleanup) and
update the function's caller to handle the propagated error (the caller that
invokes this wrap/ANSI routine) accordingly.
In `@test/js/bun/util/wrapAnsi.npm.test.ts`:
- Around line 25-38: The regex literals in stripAnsi and hasAnsi use raw control
characters (ESC/BEL) which Biome flags; update both to use RegExp constructors
built from string patterns instead (e.g., build the ESC as "\\u001B" and BEL as
"\\u0007" inside the pattern string) so the patterns remain identical but avoid
embedding control chars; modify stripAnsi to use new RegExp("...","g") and
hasAnsi to use new RegExp("...") while preserving their original flags and
replacements.
In `@test/js/bun/util/wrapAnsi.test.ts`:
- Around line 165-177: The tests currently only assert Bun.wrapAnsi returns a
string but don't verify wrapping changes when ambiguousIsNarrow toggles; update
the "ambiguousIsNarrow option" tests to call Bun.wrapAnsi with the same input
(e.g., "αβγ" and a specific column width) for both the default (or explicit
ambiguousIsNarrow: true) and ambiguousIsNarrow: false, then assert the two
returned strings differ (and optionally assert expected line breaks or lengths
to demonstrate wide vs narrow behavior), referencing Bun.wrapAnsi in the
"default treats ambiguous as narrow" and "ambiguousIsNarrow false treats as
wide" cases so the test fails if wrapping behavior doesn't change.
- Around line 145-163: The tests in wrapAnsi.test.ts only assert the return type
and not correctness; update the "handles tabs", "handles Windows line endings",
and "handles consecutive spaces" tests to assert concrete expected outputs from
Bun.wrapAnsi: for example call Bun.wrapAnsi("a\tb", 10) and
expect(result).toBe("a\tb"), call Bun.wrapAnsi("hello\r\nworld", 10) and
expect(result).toBe("hello\r\nworld") (or assert that CRLF is preserved with
expect(result).toContain("\r\n") if normalization is intended), and call
Bun.wrapAnsi("hello world", 10) and expect(result).toBe("hello world") (or
assert the sequence of spaces is preserved with
expect(result).toMatch(/hello\s{4}world/)); modify whichever of these match the
library's intended behavior and replace the typeof assertions with these
concrete expects referencing Bun.wrapAnsi and the test names.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
bench/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
bench/package.jsonbench/runner.mjsbench/snippets/wrap-ansi.jspackages/bun-types/bun.d.tssrc/bun.js/api/BunObject.bind.tssrc/bun.js/api/BunObject.zigsrc/bun.js/bindings/BunObject.cppsrc/string/immutable/wrap_ansi.zigtest/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.test.ts?(x)
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.ts?(x): Never usebun testdirectly - always usebun bd testto run tests with debug build changes
For single-file tests, prefer-eflag overtempDir
For multi-file tests, prefertempDirandBun.spawnover single-file tests
UsenormalizeBunSnapshotto normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
UsetempDirfromharnessto create temporary directories - do not usetmpdirSyncorfs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not usesetTimeoutin tests; instead await the condition to be met
Verify tests fail withUSE_SYSTEM_BUN=1 bun test <file>and pass withbun bd test <file>- tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with.test.tsor.test.tsx
Avoid shell commands likefindorgrepin tests - use Bun's Glob and built-in tools instead
Files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
test/**/*.test.ts?(x)
📄 CodeRabbit inference engine (CLAUDE.md)
Always use
port: 0in tests - do not hardcode ports or use custom random port number functions
Files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}
📄 CodeRabbit inference engine (test/CLAUDE.md)
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Usebun bd test <...test file>to run tests with compiled code changes. Do not usebun testas it will not include your changes.
Usebun:testfor files ending in*.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, usebun bd <file>instead ofbun bd test <file>since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.
Files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
**/*.zig
📄 CodeRabbit inference engine (CLAUDE.md)
In Zig code, be careful with allocators and use defer for cleanup
Files:
src/string/immutable/wrap_ansi.zigsrc/bun.js/api/BunObject.zig
src/**/*.zig
📄 CodeRabbit inference engine (src/CLAUDE.md)
src/**/*.zig: Use the#prefix for private fields in Zig structs, e.g.,struct {#foo: u32 };
Use Decl literals in Zig, e.g.,const decl: Decl = .{ .binding = 0, .value = 0 };
Place@importstatements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use@import()inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Files:
src/string/immutable/wrap_ansi.zigsrc/bun.js/api/BunObject.zig
src/bun.js/bindings/**/*.cpp
📄 CodeRabbit inference engine (CLAUDE.md)
src/bun.js/bindings/**/*.cpp: Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Define properties using HashTableValue arrays in C++ JavaScript class bindings
Add iso subspaces for C++ classes with fields in JavaScript class bindings
Cache structures in ZigGlobalObject for JavaScript class bindings
Files:
src/bun.js/bindings/BunObject.cpp
🧠 Learnings (40)
📓 Common learnings
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Write JS builtins for Bun's Node.js compatibility and APIs, and run `bun bd` after changes
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output
📚 Learning: 2025-11-20T19:51:32.288Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24880
File: packages/bun-vscode/package.json:382-385
Timestamp: 2025-11-20T19:51:32.288Z
Learning: In the Bun repository, dependencies may be explicitly added to package.json files (even when not directly imported in code) to force version upgrades on transitive dependencies, particularly as part of Aikido security scanner remediation to ensure vulnerable transitive dependencies resolve to patched versions.
Applied to files:
bench/package.json
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tsbench/snippets/wrap-ansi.jstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `normalizeBunSnapshot` to normalize snapshot output of tests
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Verify tests fail with `USE_SYSTEM_BUN=1 bun test <file>` and pass with `bun bd test <file>` - tests are invalid if they pass with USE_SYSTEM_BUN=1
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-11-14T16:07:01.064Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.tspackages/bun-types/bun.d.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Never use `bun test` directly - always use `bun bd test` to run tests with debug build changes
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Ensure V8 API tests compare identical C++ code output between Node.js and Bun through the test suite validation process
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.ts
📚 Learning: 2025-09-30T22:53:19.887Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 23117
File: src/bun.js/test/snapshot.zig:265-276
Timestamp: 2025-09-30T22:53:19.887Z
Learning: In Bun's snapshot testing (src/bun.js/test/snapshot.zig), multiple inline snapshots at the same line and column (same call position) must have identical values. However, multiple inline snapshots on the same line at different columns are allowed to have different values. The check is position-specific (line+col), not line-wide.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.ts
📚 Learning: 2025-10-30T21:52:04.707Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tssrc/string/immutable/wrap_ansi.zigsrc/bun.js/api/BunObject.zigtest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-09-24T05:48:59.872Z
Learnt from: nektro
Repo: oven-sh/bun PR: 22806
File: scripts/runner.node.mjs:687-689
Timestamp: 2025-09-24T05:48:59.872Z
Learning: In the Bun codebase, the `startGroup` utility function in scripts/runner.node.mjs automatically closes any previously open group when called, so `startGroup("End")` correctly closes the final test group and keeps subsequent output ungrouped.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.ts
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.
Applied to files:
src/string/immutable/wrap_ansi.zig
📚 Learning: 2025-11-11T22:55:04.070Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24571
File: src/css/selectors/parser.zig:908-916
Timestamp: 2025-11-11T22:55:04.070Z
Learning: In oven-sh/bun, CSS serialization uses an arena allocator. In src/css/selectors/parser.zig, functions like PseudoClass.toCss and PseudoElement.toCss intentionally do not call deinit on std.Io.Writer.Allocating, scratch buffers, or css.Printer because dest.allocator is an arena and these temporaries are reclaimed when the CSS pass completes. Only debug-only paths (e.g., DeclarationBlock.DebugFmt in src/css/declaration.zig) may explicitly deinit.
Applied to files:
src/string/immutable/wrap_ansi.zig
📚 Learning: 2025-09-02T18:27:23.279Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22227
File: src/collections/multi_array_list.zig:24-24
Timestamp: 2025-09-02T18:27:23.279Z
Learning: The `#allocator` syntax in bun's custom Zig implementation is valid and does not require quoting with @"#allocator". Private fields using the `#` prefix work correctly throughout the codebase without special quoting syntax.
Applied to files:
src/string/immutable/wrap_ansi.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.
Applied to files:
src/string/immutable/wrap_ansi.zigsrc/bun.js/api/BunObject.zig
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/bun.js/api/BunObject.zigsrc/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms
Applied to files:
src/bun.js/api/BunObject.zigsrc/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>
Applied to files:
src/bun.js/api/BunObject.zigsrc/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-03T20:43:06.996Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/test/snapshot.zig:19-19
Timestamp: 2025-11-03T20:43:06.996Z
Learning: In Bun's Zig codebase, when storing JSValue objects in collections like ArrayList, use `jsc.Strong.Optional` (not raw JSValue). When adding values, wrap them with `jsc.Strong.Optional.create(value, globalThis)`. In cleanup code, iterate the collection calling `.deinit()` on each Strong.Optional item before calling `.deinit()` on the ArrayList itself. This pattern automatically handles GC protection. See examples in src/bun.js/test/ScopeFunctions.zig and src/bun.js/node/node_cluster_binding.zig.
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add symbol names with leading underscore and semicolons in braces to src/symbols.dyn for each new V8 API method
Applied to files:
src/bun.js/api/BunObject.zigsrc/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Write JS builtins for Bun's Node.js compatibility and APIs, and run `bun bd` after changes
Applied to files:
src/bun.js/api/BunObject.zigbench/snippets/wrap-ansi.js
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add symbol names without leading underscore to src/symbols.txt for each new V8 API method
Applied to files:
src/bun.js/api/BunObject.zigsrc/bun.js/bindings/BunObject.cpp
📚 Learning: 2026-01-07T23:45:24.551Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2026-01-07T23:45:24.551Z
Learning: Applies to src/**/*.zig : Place `import` statements at the bottom of the file in Zig (auto formatter will handle positioning)
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2026-01-07T23:45:24.551Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2026-01-07T23:45:24.551Z
Learning: Applies to src/**/*.zig : Never use `import()` inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2025-08-30T09:09:18.384Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 22231
File: src/bundler/bundle_v2.zig:48-48
Timestamp: 2025-08-30T09:09:18.384Z
Learning: In Zig, when a module exports a top-level struct, import("./Module.zig") directly returns that struct type and can be used as a type alias without needing to access a field within the module. This is a common pattern in the Bun codebase.
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for C++ classes with fields in JavaScript class bindings
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Create V8 class headers with .h extension following the pattern V8ClassName.h that include pragma once, v8.h, V8Local.h, V8Isolate.h, and declare classes extending from Data with BUN_EXPORT static methods
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Define properties using HashTableValue arrays in C++ JavaScript class bindings
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-09-20T05:35:57.318Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: src/bun.js/bindings/headers.h:729-731
Timestamp: 2025-09-20T05:35:57.318Z
Learning: symbols.txt in the Bun codebase is specifically for V8 API mangled symbols (without leading underscore), not for general Bun host functions declared with BUN_DECLARE_HOST_FUNCTION. Host functions are handled through different build mechanisms.
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Do not set a timeout on tests. Bun already has timeouts built-in.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-09-20T03:39:41.770Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.770Z
Learning: Bun's test runner supports async describe callbacks, unlike Jest/Vitest where describe callbacks must be synchronous. The syntax `describe("name", async () => { ... })` is valid in Bun.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-08T13:48:02.430Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: test/js/bun/tarball/extract.test.ts:107-111
Timestamp: 2025-10-08T13:48:02.430Z
Learning: In Bun's test runner, use `expect(async () => { await ... }).toThrow()` to assert async rejections. Unlike Jest/Vitest, Bun does not require `await expect(...).rejects.toThrow()` - the async function wrapper with `.toThrow()` is the correct pattern for async error assertions in Bun tests.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-25T17:20:19.041Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 24063
File: test/js/bun/telemetry/server-header-injection.test.ts:5-20
Timestamp: 2025-10-25T17:20:19.041Z
Learning: In the Bun telemetry codebase, tests are organized into two distinct layers: (1) Internal API tests in test/js/bun/telemetry/ use numeric InstrumentKind enum values to test Zig↔JS injection points and low-level integration; (2) Public API tests in packages/bun-otel/test/ use string InstrumentKind values ("http", "fetch", etc.) to test the public-facing BunSDK and instrumentation APIs. This separation allows internal tests to use efficient numeric enums for refactoring flexibility while the public API maintains a developer-friendly string-based interface.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-19T02:52:37.412Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/tsconfig.json:1-15
Timestamp: 2025-10-19T02:52:37.412Z
Learning: In the Bun repository, packages under packages/ (e.g., bun-otel) can follow a TypeScript-first pattern where package.json exports point directly to .ts files (not compiled .js files). Bun natively runs TypeScript, so consumers import .ts sources directly and receive full type information without needing compiled .d.ts declaration files. For such packages, adding "declaration": true or "outDir" in tsconfig.json is unnecessary and would break the export structure.
<!-- [remove_learning]
ceedde95-980e-4898-a2c6-40ff73913664
Applied to files:
packages/bun-types/bun.d.ts
🧬 Code graph analysis (3)
test/js/bun/util/wrapAnsi.npm.test.ts (1)
bench/snippets/wrap-ansi.js (3)
red(10-10)green(11-11)blue(12-12)
bench/snippets/wrap-ansi.js (2)
bench/runner.mjs (5)
summary(17-17)summary(17-17)bench(15-15)bench(15-15)run(7-13)src/bun.js/api/BunObject.bind.ts (1)
wrapAnsi(45-53)
packages/bun-types/bun.d.ts (1)
src/bun.js/api/BunObject.bind.ts (2)
WrapAnsiOptions(38-43)wrapAnsi(45-53)
🪛 Biome (2.1.2)
test/js/bun/util/wrapAnsi.npm.test.ts
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 37-37: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
🔇 Additional comments (16)
bench/package.json (1)
21-21: LGTM!The
wrap-ansidependency is correctly added for benchmarking the newBun.wrapAnsi()API against the npm package.bench/runner.mjs (1)
17-17: LGTM!The
summaryexport follows the established pattern for exposing Mitata utilities (bench,group).src/bun.js/api/BunObject.bind.ts (1)
38-53: LGTM!The
WrapAnsiOptionsdictionary andwrapAnsifunction binding are well-structured and follow the established pattern fromStringWidthOptions/stringWidth. All option defaults align with the PR objectives and match wrap-ansi compatibility.src/bun.js/bindings/BunObject.cpp (1)
805-805: LGTM!The
wrapAnsiLUT entry correctly follows the established pattern for generated functions inBunObject. The placement near related ANSI functions (stripANSI,stringWidth) is logical, andDontDelete|Function 2appropriately reflects the required argument count.bench/snippets/wrap-ansi.js (1)
1-103: LGTM!Comprehensive benchmark suite with excellent coverage:
- Plain text at various lengths
- ANSI SGR color codes
- Full-width Unicode (Japanese)
- Emoji with surrogate pairs
- OSC 8 hyperlinks
hardwrap andtrim: falseoptionsThe structured
summary()groupings provide clear npm vs Bun comparisons.test/js/bun/util/wrapAnsi.npm.test.ts (2)
1-24: License header inclusion looks correct and complete.
39-255: Test coverage is strong and the “Bun vs npm wrap-ansi” expectation deltas are clearly documented. The explicit notes around ANSI close/reopen behavior and the malformed-ANSI expectation are particularly helpful for future maintainers.packages/bun-types/bun.d.ts (1)
647-702: Type shape/signature looks consistent with the runtime binding (options + defaults) and existing bun.d.ts patterns.src/bun.js/api/BunObject.zig (2)
1384-1408: LGTM! Clean implementation with proper memory management.The function correctly:
- Handles empty input as an edge case
- Uses
deferfor cleanup of bothinputandresultallocations- Properly maps options from the JS binding struct to the Zig wrap options
- Handles OOM errors consistently with
throwOutOfMemoryValue()
2076-2076: Import correctly placed at bottom of file.Follows the Zig coding guidelines for the Bun codebase.
src/string/immutable/wrap_ansi.zig (4)
479-499: SGR parser only captures first code in compound sequences.For sequences like
\x1b[1;31m(bold + red), this only returns1, missing the31. This limits style preservation to single-code sequences.Given the PR notes that the implementation "differs in some edge cases from npm wrap-ansi but yields visually equivalent output", this may be an intentional simplification.
Consider whether compound SGR sequences (e.g.,
\x1b[1;4;31mfor bold+underline+red) are used in practice and if the current behavior is acceptable.
540-573: LGTM! Robust character width calculation.The function properly:
- Has an ASCII fast path for performance
- Handles UTF-8 multibyte sequences correctly
- Gracefully falls back for invalid sequences
- Delegates to
visibleCodepointWidthfor Unicode width handling
603-606: Imports correctly placed at bottom of file.Follows the Bun Zig coding guidelines.
575-601: Good basic test coverage for the Zig implementation.The tests properly use
std.testing.allocatoranddeferfor cleanup. The comprehensive edge case testing is appropriately delegated to the JS test suite.test/js/bun/util/wrapAnsi.test.ts (2)
3-30: LGTM! Good coverage of basic wrapping behavior.Tests properly cover word boundaries, empty input, no-wrap scenarios, and the edge case of
columns = 0.
61-97: LGTM! Comprehensive ANSI escape code testing.Good coverage of:
- Simple color preservation
- Color continuity across line breaks (checking for close/restore codes)
- Multiple colors and styles
- Width calculation excluding ANSI codes
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| interface WrapAnsiOptions { | ||
| /** | ||
| * If `true`, break words in the middle if they don't fit on a line. | ||
| * If `false`, only break at word boundaries. | ||
| * | ||
| * @default false | ||
| */ | ||
| hard?: boolean; | ||
|
|
||
| /** | ||
| * If `true`, wrap at word boundaries when possible. | ||
| * If `false`, don't perform word wrapping (only wrap at explicit newlines). | ||
| * | ||
| * @default true | ||
| */ | ||
| wordWrap?: boolean; | ||
|
|
||
| /** | ||
| * If `true`, trim leading and trailing whitespace from each line. | ||
| * If `false`, preserve whitespace. | ||
| * | ||
| * @default true | ||
| */ | ||
| trim?: boolean; | ||
|
|
||
| /** | ||
| * When it's ambiguous and `true`, count ambiguous width characters as 1 character wide. | ||
| * If `false`, count them as 2 characters wide. | ||
| * | ||
| * @default true | ||
| */ | ||
| ambiguousIsNarrow?: boolean; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
wrap-ansi npm package options wordWrap hard trim semantics documentation
💡 Result:
Summary of wrap-ansi options and their semantics:
-
hard (boolean, default false): when false, wrapping is "soft" (long words may extend past the column width). When true, lines are hard-wrapped at the column width (words will be broken to fit). (npmjs.com)
-
wordWrap (boolean, default true): when true, the wrapper prefers to split at spaces so words don’t extend past the column width. When false, wrapping fills each line completely and will split words as necessary. (npmjs.com)
-
trim (boolean, default true): when true (the default), whitespace at the start/end of all output lines is removed. Set to false to preserve leading/trailing whitespace. (npmjs.com)
If you want, I can show short examples demonstrating each option.
Citations:
- 1: https://www.npmjs.com/package/wrap-ansi?utm_source=openai
- 2: https://www.npmjs.com/package/wrap-ansi?utm_source=openai
- 3: https://www.npmjs.com/package/wrap-ansi?utm_source=openai
🏁 Script executed:
# Find test files related to wrapAnsi in the repository
fd -t f 'test|spec' | head -20
find . -type f -name '*wrap*' -o -name '*test*' | grep -i wrap | head -20Repository: oven-sh/bun
Length of output: 2075
🏁 Script executed:
# Search for wrapAnsi usage and tests in the codebase
rg -i 'wrapAnsi|wrap-ansi' --type ts --type js -A 5 -B 5 | head -100Repository: oven-sh/bun
Length of output: 5829
🏁 Script executed:
cat -n ./test/js/bun/util/wrapAnsi.test.ts | head -150Repository: oven-sh/bun
Length of output: 6003
🏁 Script executed:
cat -n ./test/js/bun/util/wrapAnsi.npm.test.ts | head -150Repository: oven-sh/bun
Length of output: 7475
Update wordWrap JSDoc: it still wraps by character when false, not "only at explicit newlines".
The current documentation for wordWrap: false is inaccurate. Tests demonstrate that wordWrap: false still wraps text to fill the column width, splitting words as needed (character-level wrapping). This matches the upstream wrap-ansi semantics: "wrapping fills each line completely and will split words as necessary."
Proposed fix
/**
* If `true`, wrap at word boundaries when possible.
- * If `false`, don't perform word wrapping (only wrap at explicit newlines).
+ * If `false`, wrap by character (may split words) instead of preferring word boundaries.
*
* `@default` true
*/
wordWrap?: boolean;📝 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.
| interface WrapAnsiOptions { | |
| /** | |
| * If `true`, break words in the middle if they don't fit on a line. | |
| * If `false`, only break at word boundaries. | |
| * | |
| * @default false | |
| */ | |
| hard?: boolean; | |
| /** | |
| * If `true`, wrap at word boundaries when possible. | |
| * If `false`, don't perform word wrapping (only wrap at explicit newlines). | |
| * | |
| * @default true | |
| */ | |
| wordWrap?: boolean; | |
| /** | |
| * If `true`, trim leading and trailing whitespace from each line. | |
| * If `false`, preserve whitespace. | |
| * | |
| * @default true | |
| */ | |
| trim?: boolean; | |
| /** | |
| * When it's ambiguous and `true`, count ambiguous width characters as 1 character wide. | |
| * If `false`, count them as 2 characters wide. | |
| * | |
| * @default true | |
| */ | |
| ambiguousIsNarrow?: boolean; | |
| } | |
| interface WrapAnsiOptions { | |
| /** | |
| * If `true`, break words in the middle if they don't fit on a line. | |
| * If `false`, only break at word boundaries. | |
| * | |
| * `@default` false | |
| */ | |
| hard?: boolean; | |
| /** | |
| * If `true`, wrap at word boundaries when possible. | |
| * If `false`, wrap by character (may split words) instead of preferring word boundaries. | |
| * | |
| * `@default` true | |
| */ | |
| wordWrap?: boolean; | |
| /** | |
| * If `true`, trim leading and trailing whitespace from each line. | |
| * If `false`, preserve whitespace. | |
| * | |
| * `@default` true | |
| */ | |
| trim?: boolean; | |
| /** | |
| * When it's ambiguous and `true`, count ambiguous width characters as 1 character wide. | |
| * If `false`, count them as 2 characters wide. | |
| * | |
| * `@default` true | |
| */ | |
| ambiguousIsNarrow?: boolean; | |
| } |
🤖 Prompt for AI Agents
In `@packages/bun-types/bun.d.ts` around lines 613 - 645, The JSDoc for
WrapAnsiOptions.wordWrap is incorrect: when false it still wraps to fill the
column and will split words at character boundaries rather than only breaking at
explicit newlines; update the JSDoc on the wordWrap property (interface
WrapAnsiOptions, symbol wordWrap) to say that true prefers wrapping at word
boundaries when possible, and false performs character-level wrapping to fill
each line and will split words as needed (do not claim it only wraps at explicit
newlines).
| // Normalize \r\n to \n | ||
| var normalized: std.ArrayListUnmanaged(u8) = .{}; | ||
| defer normalized.deinit(allocator); | ||
|
|
||
| var i: usize = 0; | ||
| while (i < input.len) { | ||
| if (i + 1 < input.len and input[i] == '\r' and input[i + 1] == '\n') { | ||
| try normalized.append(allocator, '\n'); | ||
| i += 2; | ||
| } else { | ||
| try normalized.append(allocator, input[i]); | ||
| i += 1; | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider using std.mem.replace for CRLF normalization.
The current byte-by-byte approach works correctly but could be more efficient for large inputs using a bulk operation.
♻️ Optional optimization
- // Normalize \r\n to \n
- var normalized: std.ArrayListUnmanaged(u8) = .{};
- defer normalized.deinit(allocator);
-
- var i: usize = 0;
- while (i < input.len) {
- if (i + 1 < input.len and input[i] == '\r' and input[i + 1] == '\n') {
- try normalized.append(allocator, '\n');
- i += 2;
- } else {
- try normalized.append(allocator, input[i]);
- i += 1;
- }
- }
+ // Normalize \r\n to \n
+ var normalized: std.ArrayListUnmanaged(u8) = .{};
+ defer normalized.deinit(allocator);
+ try normalized.appendSlice(allocator, input);
+ _ = std.mem.replace(u8, normalized.items, "\r\n", "\n");Note: This would require adjusting the slice length after replacement since std.mem.replace returns the count. The current approach is simpler and correct.
🤖 Prompt for AI Agents
In `@src/string/immutable/wrap_ansi.zig` around lines 25 - 38, Replace the
byte-by-byte CRLF normalization loop with a bulk replace using std.mem.replace:
allocate or create a mutable copy of input (used in place of the manual append
loop), call std.mem.replace to replace the pattern "\r\n" with "\n" into the
normalized buffer (reference symbols: normalized, input, std.mem.replace), and
adjust the resulting slice/length based on the replace result (account for the
number of replacements to compute the final normalized length) before
using/deinitializing normalized; this keeps behavior identical but is more
efficient for large inputs.
| while (words_iter2.next()) |word| { | ||
| if (idx < last) { | ||
| if (idx > 0) { | ||
| new_row.append(allocator, ' ') catch {}; | ||
| } | ||
| new_row.appendSlice(allocator, word) catch {}; | ||
| } else { | ||
| // Append ANSI sequences from trailing words | ||
| for (word) |c| { | ||
| if (c == '\x1b' or trailing_ansi.items.len > 0) { | ||
| trailing_ansi.append(allocator, c) catch {}; | ||
| } | ||
| } | ||
| } | ||
| idx += 1; | ||
| } | ||
|
|
||
| // Append trailing ANSI codes | ||
| new_row.appendSlice(allocator, trailing_ansi.items) catch {}; | ||
| trailing_ansi.deinit(allocator); | ||
|
|
||
| // Replace row content | ||
| row.deinit(allocator); | ||
| row.* = new_row; |
There was a problem hiding this comment.
Silent allocation failures could cause data corruption.
Using catch {} to swallow allocation errors means the function silently produces incomplete or incorrect output if memory allocation fails. This is inconsistent with the error handling elsewhere in this file.
🛠️ Suggested fix: propagate errors
-fn stringVisibleTrimSpacesRight(allocator: std.mem.Allocator, row: *std.ArrayListUnmanaged(u8)) void {
+fn stringVisibleTrimSpacesRight(allocator: std.mem.Allocator, row: *std.ArrayListUnmanaged(u8)) !void {
// ... existing code until rebuild ...
while (words_iter2.next()) |word| {
if (idx < last) {
if (idx > 0) {
- new_row.append(allocator, ' ') catch {};
+ try new_row.append(allocator, ' ');
}
- new_row.appendSlice(allocator, word) catch {};
+ try new_row.appendSlice(allocator, word);
} else {
// Append ANSI sequences from trailing words
for (word) |c| {
if (c == '\x1b' or trailing_ansi.items.len > 0) {
- trailing_ansi.append(allocator, c) catch {};
+ try trailing_ansi.append(allocator, c);
}
}
}
idx += 1;
}
// Append trailing ANSI codes
- new_row.appendSlice(allocator, trailing_ansi.items) catch {};
+ try new_row.appendSlice(allocator, trailing_ansi.items);Then update the caller at line 161:
- stringVisibleTrimSpacesRight(allocator, row);
+ try stringVisibleTrimSpacesRight(allocator, row);🤖 Prompt for AI Agents
In `@src/string/immutable/wrap_ansi.zig` around lines 193 - 216, The code
currently swallows allocation failures with `catch {}` when appending to
`new_row` and `trailing_ansi`, risking silent corruption; change those `catch
{}` usages to propagate errors (use `try`/`return`/! as appropriate) for all
allocator operations involving `new_row.append`, `new_row.appendSlice`, and
`trailing_ansi.append`, and ensure the final `new_row.appendSlice(allocator,
trailing_ansi.items)` also propagates errors; adjust cleanup so
`trailing_ansi.deinit(allocator)` and `row.deinit(allocator)` still run on error
(use defer or explicit cleanup) and update the function's caller to handle the
propagated error (the caller that invokes this wrap/ANSI routine) accordingly.
| import { expect, test } from "bun:test"; | ||
|
|
||
| // ANSI color helpers (equivalent to chalk with level 1) | ||
| const red = (s: string) => `\u001B[31m${s}\u001B[39m`; | ||
| const green = (s: string) => `\u001B[32m${s}\u001B[39m`; | ||
| const blue = (s: string) => `\u001B[34m${s}\u001B[39m`; | ||
| const bgGreen = (s: string) => `\u001B[42m${s}\u001B[49m`; | ||
| const bgRed = (s: string) => `\u001B[41m${s}\u001B[49m`; | ||
| const black = (s: string) => `\u001B[30m${s}\u001B[39m`; | ||
|
|
||
| // Helper functions | ||
| const stripAnsi = (s: string) => s.replace(/\u001B\[[0-9;]*m|\u001B\]8;;[^\u0007]*\u0007/g, ""); | ||
| const hasAnsi = (s: string) => /\u001B\[[0-9;]*m/.test(s); | ||
|
|
There was a problem hiding this comment.
Fix Biome “control character in regex” errors (likely CI-blocking). Biome flags the regex literals using ESC/BEL on Line 36-37; switch to RegExp constructed from strings to satisfy the linter while keeping behavior unchanged.
Proposed fix
// Helper functions
-const stripAnsi = (s: string) => s.replace(/\u001B\[[0-9;]*m|\u001B\]8;;[^\u0007]*\u0007/g, "");
-const hasAnsi = (s: string) => /\u001B\[[0-9;]*m/.test(s);
+const stripAnsiRe = new RegExp("\\x1B\\[[0-9;]*m|\\x1B\\]8;;[^\\x07]*\\x07", "g");
+const hasAnsiRe = new RegExp("\\x1B\\[[0-9;]*m");
+const stripAnsi = (s: string) => s.replace(stripAnsiRe, "");
+const hasAnsi = (s: string) => hasAnsiRe.test(s);🧰 Tools
🪛 Biome (2.1.2)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 37-37: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
🤖 Prompt for AI Agents
In `@test/js/bun/util/wrapAnsi.npm.test.ts` around lines 25 - 38, The regex
literals in stripAnsi and hasAnsi use raw control characters (ESC/BEL) which
Biome flags; update both to use RegExp constructors built from string patterns
instead (e.g., build the ESC as "\\u001B" and BEL as "\\u0007" inside the
pattern string) so the patterns remain identical but avoid embedding control
chars; modify stripAnsi to use new RegExp("...","g") and hasAnsi to use new
RegExp("...") while preserving their original flags and replacements.
| describe("wordWrap option", () => { | ||
| test("wordWrap false disables wrapping", () => { | ||
| // Without wordWrap, only explicit newlines should cause breaks | ||
| const result = Bun.wrapAnsi("hello world", 5, { wordWrap: false }); | ||
| // The behavior may vary - just check it doesn't crash | ||
| expect(typeof result).toBe("string"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Weak assertion for wordWrap=false behavior.
The test only checks that the result is a string, which doesn't validate the actual behavior. Consider documenting the expected behavior or adding a more specific assertion.
💡 Suggested improvement
describe("wordWrap option", () => {
test("wordWrap false disables wrapping", () => {
- // Without wordWrap, only explicit newlines should cause breaks
const result = Bun.wrapAnsi("hello world", 5, { wordWrap: false });
- // The behavior may vary - just check it doesn't crash
- expect(typeof result).toBe("string");
+ // With wordWrap false, words are not broken at boundaries
+ // but characters may still be wrapped if they exceed column width
+ expect(result).toContain("hello");
+ expect(result).toContain("world");
});
});📝 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.
| describe("wordWrap option", () => { | |
| test("wordWrap false disables wrapping", () => { | |
| // Without wordWrap, only explicit newlines should cause breaks | |
| const result = Bun.wrapAnsi("hello world", 5, { wordWrap: false }); | |
| // The behavior may vary - just check it doesn't crash | |
| expect(typeof result).toBe("string"); | |
| }); | |
| }); | |
| describe("wordWrap option", () => { | |
| test("wordWrap false disables wrapping", () => { | |
| const result = Bun.wrapAnsi("hello world", 5, { wordWrap: false }); | |
| // With wordWrap false, words are not broken at boundaries | |
| // but characters may still be wrapped if they exceed column width | |
| expect(result).toContain("hello"); | |
| expect(result).toContain("world"); | |
| }); | |
| }); |
| describe("edge cases", () => { | ||
| test("handles tabs", () => { | ||
| const input = "a\tb"; | ||
| const result = Bun.wrapAnsi(input, 10); | ||
| expect(typeof result).toBe("string"); | ||
| }); | ||
|
|
||
| test("handles Windows line endings", () => { | ||
| const input = "hello\r\nworld"; | ||
| const result = Bun.wrapAnsi(input, 10); | ||
| expect(typeof result).toBe("string"); | ||
| }); | ||
|
|
||
| test("handles consecutive spaces", () => { | ||
| const input = "hello world"; | ||
| const result = Bun.wrapAnsi(input, 10); | ||
| expect(typeof result).toBe("string"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Edge case tests only verify no crash, not correctness.
These tests check that the function returns a string but don't validate the actual output. Consider adding specific expectations for at least one case.
💡 Example improvement for tabs test
test("handles tabs", () => {
const input = "a\tb";
const result = Bun.wrapAnsi(input, 10);
- expect(typeof result).toBe("string");
+ expect(result).toContain("a");
+ expect(result).toContain("b");
});🤖 Prompt for AI Agents
In `@test/js/bun/util/wrapAnsi.test.ts` around lines 145 - 163, The tests in
wrapAnsi.test.ts only assert the return type and not correctness; update the
"handles tabs", "handles Windows line endings", and "handles consecutive spaces"
tests to assert concrete expected outputs from Bun.wrapAnsi: for example call
Bun.wrapAnsi("a\tb", 10) and expect(result).toBe("a\tb"), call
Bun.wrapAnsi("hello\r\nworld", 10) and expect(result).toBe("hello\r\nworld") (or
assert that CRLF is preserved with expect(result).toContain("\r\n") if
normalization is intended), and call Bun.wrapAnsi("hello world", 10) and
expect(result).toBe("hello world") (or assert the sequence of spaces is
preserved with expect(result).toMatch(/hello\s{4}world/)); modify whichever of
these match the library's intended behavior and replace the typeof assertions
with these concrete expects referencing Bun.wrapAnsi and the test names.
| describe("ambiguousIsNarrow option", () => { | ||
| test("default treats ambiguous as narrow", () => { | ||
| // By default, ambiguous width chars should be treated as width 1 | ||
| const result1 = Bun.wrapAnsi("αβγ", 3); | ||
| // Greek letters are ambiguous width | ||
| expect(typeof result1).toBe("string"); | ||
| }); | ||
|
|
||
| test("ambiguousIsNarrow false treats as wide", () => { | ||
| const result = Bun.wrapAnsi("αβγ", 3, { ambiguousIsNarrow: false }); | ||
| expect(typeof result).toBe("string"); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
ambiguousIsNarrow tests don't verify width difference.
The tests check that the function runs without error but don't verify that setting ambiguousIsNarrow: false actually changes the wrapping behavior. Greek letters treated as wide (2 columns each) vs narrow (1 column each) should produce different wrapping results.
💡 Suggested improvement
describe("ambiguousIsNarrow option", () => {
test("default treats ambiguous as narrow", () => {
- // By default, ambiguous width chars should be treated as width 1
- const result1 = Bun.wrapAnsi("αβγ", 3);
- // Greek letters are ambiguous width
- expect(typeof result1).toBe("string");
+ // Greek letters are ambiguous width, treated as 1 column each by default
+ // "αβγ" = 3 columns, should fit in width 3
+ const result = Bun.wrapAnsi("αβγ", 3, { hard: true });
+ expect(result).toBe("αβγ"); // No wrap needed
});
test("ambiguousIsNarrow false treats as wide", () => {
- const result = Bun.wrapAnsi("αβγ", 3, { ambiguousIsNarrow: false });
- expect(typeof result).toBe("string");
+ // When treated as wide, each letter = 2 columns
+ // "αβγ" = 6 columns, should wrap at width 4
+ const result = Bun.wrapAnsi("αβγ", 4, { hard: true, ambiguousIsNarrow: false });
+ expect(result).toContain("\n"); // Should wrap
});
});🤖 Prompt for AI Agents
In `@test/js/bun/util/wrapAnsi.test.ts` around lines 165 - 177, The tests
currently only assert Bun.wrapAnsi returns a string but don't verify wrapping
changes when ambiguousIsNarrow toggles; update the "ambiguousIsNarrow option"
tests to call Bun.wrapAnsi with the same input (e.g., "αβγ" and a specific
column width) for both the default (or explicit ambiguousIsNarrow: true) and
ambiguousIsNarrow: false, then assert the two returned strings differ (and
optionally assert expected line breaks or lengths to demonstrate wide vs narrow
behavior), referencing Bun.wrapAnsi in the "default treats ambiguous as narrow"
and "ambiguousIsNarrow false treats as wide" cases so the test fails if wrapping
behavior doesn't change.
|
Very cool. Are you sure we don’t need to use any Unicode-aware word iteration? Is space character good enough? also I imagine doing this in C++ would be faster because then it can use StringBuilder and avoid utf8 -> utf16 conversion. And it’s a little easier to write SIMD for some parts of this since it can use WebKit’s helper functions |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/bun.js/bindings/wrapAnsi.cpp`:
- Around line 1185-1190: The current code casts the result of
columnsValue.toIntegerOrInfinity(...) directly to size_t, which will wrap
negative values; to fix, first store the result in a signed/integer type (e.g.,
int64_t or double) from toIntegerOrInfinity(globalObject), check
RETURN_IF_EXCEPTION(scope, {}) as before, then validate: if the numeric result
is negative set columns = 0, if it is positive infinity set columns = SIZE_MAX
(or otherwise clamp to a reasonable max), otherwise cast the non-negative finite
value to size_t and assign to columns; update references to columnsValue,
columns, and toIntegerOrInfinity accordingly.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
src/bun.js/api/BunObject.zigsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cppsrc/bun.js/bindings/wrapAnsi.h
🧰 Additional context used
📓 Path-based instructions (3)
src/bun.js/bindings/**/*.cpp
📄 CodeRabbit inference engine (CLAUDE.md)
src/bun.js/bindings/**/*.cpp: Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Define properties using HashTableValue arrays in C++ JavaScript class bindings
Add iso subspaces for C++ classes with fields in JavaScript class bindings
Cache structures in ZigGlobalObject for JavaScript class bindings
Files:
src/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
**/*.zig
📄 CodeRabbit inference engine (CLAUDE.md)
In Zig code, be careful with allocators and use defer for cleanup
Files:
src/bun.js/api/BunObject.zig
src/**/*.zig
📄 CodeRabbit inference engine (src/CLAUDE.md)
src/**/*.zig: Use the#prefix for private fields in Zig structs, e.g.,struct {#foo: u32 };
Use Decl literals in Zig, e.g.,const decl: Decl = .{ .binding = 0, .value = 0 };
Place@importstatements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use@import()inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Files:
src/bun.js/api/BunObject.zig
🧠 Learnings (27)
📓 Common learnings
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/api/BunObject.zigsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Create V8 class headers with .h extension following the pattern V8ClassName.h that include pragma once, v8.h, V8Local.h, V8Isolate.h, and declare classes extending from Data with BUN_EXPORT static methods
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for C++ classes with fields in JavaScript class bindings
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Create V8 class implementations with .cpp extension following the pattern V8ClassName.cpp that include the header, v8_compatibility_assertions.h, use ASSERT_V8_TYPE_LAYOUT_MATCHES macro, and implement methods using isolate->currentHandleScope()->createLocal<T>() for handle creation
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/api/BunObject.zigsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add symbol names with leading underscore and semicolons in braces to src/symbols.dyn for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/api/BunObject.zigsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/api/BunObject.zigsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add symbol names without leading underscore to src/symbols.txt for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/api/BunObject.zigsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-20T05:35:57.318Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: src/bun.js/bindings/headers.h:729-731
Timestamp: 2025-09-20T05:35:57.318Z
Learning: symbols.txt in the Bun codebase is specifically for V8 API mangled symbols (without leading underscore), not for general Bun host functions declared with BUN_DECLARE_HOST_FUNCTION. Host functions are handled through different build mechanisms.
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JavaScript class bindings
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/api/BunObject.zig
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use localToJSValue() to convert V8 handles to JSC values and perform JSC operations within V8 method implementations
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Write JS builtins for Bun's Node.js compatibility and APIs, and run `bun bd` after changes
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/api/BunObject.zig
📚 Learning: 2025-12-23T06:50:31.577Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25429
File: src/bun.js/bindings/helpers.h:422-422
Timestamp: 2025-12-23T06:50:31.577Z
Learning: In Bun's C++ bindings, when returning an empty JSC::Identifier and a VM is accessible, prefer using vm.propertyNames->emptyIdentifier over constructing with JSC::Identifier(JSC::Identifier::EmptyIdentifierFlag::EmptyIdentifier). The cached identifier from the VM's property names table is more efficient and consistent with WebKit upgrade patterns. Apply this guidance to src/bun.js/bindings/helpers.h and similar header files in the same bindings directory (i.e., any file that constructs an EmptyIdentifier).
Applied to files:
src/bun.js/bindings/wrapAnsi.h
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8-module/main.cpp : Register new V8 API test functions in the Init method using NODE_SET_METHOD with exports object
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-10-30T21:52:04.707Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-12-23T06:50:41.142Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25429
File: src/bun.js/bindings/helpers.h:422-422
Timestamp: 2025-12-23T06:50:41.142Z
Learning: In Bun's C++ bindings (src/bun.js/bindings/helpers.h and similar files), when returning an empty JSC::Identifier and a VM is accessible, prefer using `vm.propertyNames->emptyIdentifier` over constructing with `JSC::Identifier(JSC::Identifier::EmptyIdentifierFlag::EmptyIdentifier)`. The cached identifier from the VM's property names table is more efficient and consistent with WebKit upgrade patterns.
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use V8_UNIMPLEMENTED() macro for functions not yet implemented in V8 compatibility classes
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8-module/main.cpp : Create test functions in test/v8/v8-module/main.cpp that take FunctionCallbackInfo<Value> parameter, use the test V8 API, print results for comparison with Node.js, and return Undefined
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-08-30T09:09:18.384Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 22231
File: src/bundler/bundle_v2.zig:48-48
Timestamp: 2025-08-30T09:09:18.384Z
Learning: In Zig, when a module exports a top-level struct, import("./Module.zig") directly returns that struct type and can be used as a type alias without needing to access a field within the module. This is a common pattern in the Bun codebase.
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2025-11-20T19:51:32.288Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24880
File: packages/bun-vscode/package.json:382-385
Timestamp: 2025-11-20T19:51:32.288Z
Learning: In the Bun repository, dependencies may be explicitly added to package.json files (even when not directly imported in code) to force version upgrades on transitive dependencies, particularly as part of Aikido security scanner remediation to ensure vulnerable transitive dependencies resolve to patched versions.
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Code generation happens automatically during the build process - bundled modules can be reloaded without rebuilding Zig by running `bun run build`
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2026-01-07T23:45:24.551Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2026-01-07T23:45:24.551Z
Learning: Applies to src/**/*.zig : Place `import` statements at the bottom of the file in Zig (auto formatter will handle positioning)
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2026-01-07T23:45:24.551Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/CLAUDE.md:0-0
Timestamp: 2026-01-07T23:45:24.551Z
Learning: Applies to src/**/*.zig : Never use `import()` inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.
Applied to files:
src/bun.js/api/BunObject.zig
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
🪛 Cppcheck (2.19.0)
src/bun.js/bindings/wrapAnsi.cpp
[information] 42-42: Include file
(missingInclude)
[information] 52-52: Include file
(missingIncludeSystem)
[information] 59-59: Include file
(missingIncludeSystem)
[error] 66-66: failed to evaluate #if condition, undefined function-like macro invocation
(syntaxError)
⏰ 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 (17)
src/bun.js/bindings/wrapAnsi.h (1)
1-9: LGTM!The header follows the Bun codebase conventions with proper include guard, namespace scoping, and use of
JSC_DECLARE_HOST_FUNCTIONmacro.src/bun.js/bindings/BunObject.cpp (2)
80-80: LGTM!The host function declaration follows the existing pattern (alongside
jsFunctionBunStripANSI) and is properly scoped within theBunnamespace.
806-806: LGTM!The LUT entry is correctly configured with
DontDelete|Function 3, matching the expected API signature ofBun.wrapAnsi(string, columns, options?).src/bun.js/bindings/wrapAnsi.cpp (13)
1-18: LGTM!The includes and
WrapAnsiOptionsstruct are well-organized with appropriate defaults matching the documented API (hard: false,wordWrap: true,trim: true,ambiguousIsNarrow: true).
24-114: LGTM!Comprehensive zero-width codepoint detection covering combining marks, invisible characters, variation selectors, and script-specific combining marks. The
constexpr ifoptimization for 8-bit characters is a nice touch.
116-265: LGTM!Comprehensive full-width codepoint detection with proper early exit optimization. The coverage of CJK, Hangul, emoji, and supplementary planes aligns with standard terminal width behavior.
267-331: LGTM!The ambiguous width handling and
getVisibleWidthcomposition are correct. The subset approach for ambiguous characters is a reasonable performance trade-off.
350-383: UTF-8 decoding is lenient with malformed sequences.The decoder doesn't validate continuation bytes beyond masking. While this won't crash, malformed UTF-8 like
[0xC2, 0x00]would produce incorrect codepoints rather than replacement characters. This matches the pragmatic approach of other text processing in Bun, but worth noting for edge cases.
406-568: LGTM!The ANSI escape sequence handling is well-structured with SIMD-accelerated scanning and a complete state machine for CSI, OSC, DCS, and other sequences. The design mirrors
stripANSI.cppfor consistency.
635-690: LGTM with note on escape detection.The
trimLeadingSpacesmethod correctly preserves ANSI codes while removing whitespace. The simplified escape detection (checking onlymand0x07terminators) covers the common SGR and OSC cases, which is sufficient for typical terminal styling.
696-769: LGTM!The word wrapping logic correctly handles ANSI escapes (including OSC 8 hyperlinks), tracks visible width, and has a nice edge case fix at the end to merge rows containing only escape codes.
771-830: LGTM!Trailing whitespace trimming correctly preserves ANSI escape codes while removing non-visible trailing content.
914-988: LGTM!The style preservation logic correctly closes and reopens ANSI styles (including hyperlinks) around line breaks. This ensures terminal robustness as noted in the PR description, even if the byte representation differs slightly from the npm package.
994-1098: LGTM!The line processing logic handles the various wrapping modes (hard, wordWrap, trim) correctly with proper word boundary detection and column limit enforcement.
1104-1159: LGTM!The main implementation correctly handles edge cases (empty input, zero columns), normalizes line endings, and processes lines with proper ANSI style preservation across wrapped output.
1219-1228: LGTM!The encoding dispatch correctly handles both Latin1 (8-bit) and UTF-16 (16-bit) string representations, and the result is properly wrapped as a JSString.
src/bun.js/api/BunObject.zig (1)
2050-2050: This review comment is based on incorrect context.The
wrap_ansiimport referenced at line 2050 does not exist insrc/bun.js/api/BunObject.zig. Line 2050 containsconst std =@import("std");, and nowrap_ansiorwrapAnsireferences appear anywhere in the file.Likely an incorrect or invalid review comment.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| // ============================================================================ | ||
|
|
||
| template<typename Char> | ||
| static size_t stringWidth(const Char* start, const Char* end, bool ambiguousIsNarrow) |
There was a problem hiding this comment.
lol this should use the existing code to do this and not another implementation
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@src/bun.js/bindings/wrapAnsi.cpp`:
- Around line 518-539: END_CODE named as 39 is misleading because it represents
the default foreground SGR value, not a general reset; rename the constant
(e.g., SGR_DEFAULT_FOREGROUND or DEFAULT_FOREGROUND_CODE) and update all
references (such as in parseSgrCode usage sites) to the new name, and add a
short comment noting that SGR reset is 0 to avoid confusion.
- Around line 35-68: The decodeUTF8 function currently assembles multi-byte
sequences without validating that each continuation byte matches the 10xxxxxx
pattern, so malformed input can produce invalid codepoints; update decodeUTF8
to, after computing seqLen and before assembling cp, verify for each
continuation byte ptr[i] (i=1..seqLen-1) that (static_cast<uint8_t>(ptr[i]) &
0xC0) == 0x80 and if any check fails set outLen = 1 and return 0xFFFD; after
assembling cp also validate against overlong encodings, UTF-16 surrogate range
(0xD800–0xDFFF), and max codepoint > 0x10FFFF and return 0xFFFD if invalid,
otherwise set outLen = seqLen and return cp (references: function decodeUTF8,
helper utf8SequenceLength, variables ptr, available, outLen).
- Around line 414-420: When starting a new row in the wrap logic (the block that
calls rows.push_back(Row<Char>()); rows.back().append(it, it + charLen);) you
reset the visible width counter vis to 0 but fail to account for the width of
the character just appended; change the fix so vis is initialized/updated with
the character's displayed width (use charWidth) after appending instead of
setting it to 0 (i.e., set vis = charWidth or vis += charWidth as appropriate),
keeping the existing handling for isInsideEscape and multibyte append calls that
use charLen and it.
In `@src/string/immutable/visible.zig`:
- Around line 1152-1156: The UTF-8 export Bun__visibleWidthExcludeANSI_utf8
currently ignores ambiguous_as_wide whereas the UTF-16 path forwards it to
visible.width.exclude_ansi_colors.utf16, causing inconsistent semantics; fix by
propagating the ambiguous_as_wide flag through the UTF‑8 path—either add/use an
overload of visible.width.exclude_ansi_colors.utf8 that accepts an
ambiguousAsWide parameter and call it with ambiguous_as_wide from
Bun__visibleWidthExcludeANSI_utf8, or if such an API cannot be added, explicitly
document the difference and remove/mark the unused parameter to avoid the
misleading identical C signature; reference Bun__visibleWidthExcludeANSI_utf8,
ambiguous_as_wide, visible.width.exclude_ansi_colors.utf8 and
visible.width.exclude_ansi_colors.utf16 when making the change.
♻️ Duplicate comments (1)
src/bun.js/bindings/wrapAnsi.cpp (1)
867-872: Handle negative and infinite column values.This was flagged in a previous review.
toIntegerOrInfinitycan return negative values (which wrap to largesize_t) or infinity. Consider clamping:Suggested fix
// Get columns size_t columns = 0; if (!columnsValue.isUndefined()) { - columns = static_cast<size_t>(columnsValue.toIntegerOrInfinity(globalObject)); + double colsDouble = columnsValue.toIntegerOrInfinity(globalObject); RETURN_IF_EXCEPTION(scope, {}); + if (colsDouble > 0 && std::isfinite(colsDouble)) + columns = static_cast<size_t>(std::min(colsDouble, static_cast<double>(SIZE_MAX))); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- 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 f070780 and 14d492a81a640b75477c8e96452a03d8fff3f55d.
📒 Files selected for processing (2)
src/bun.js/bindings/wrapAnsi.cppsrc/string/immutable/visible.zig
🧰 Additional context used
📓 Path-based instructions (3)
src/bun.js/bindings/**/*.cpp
📄 CodeRabbit inference engine (CLAUDE.md)
src/bun.js/bindings/**/*.cpp: Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Define properties using HashTableValue arrays in C++ JavaScript class bindings
Add iso subspaces for C++ classes with fields in JavaScript class bindings
Cache structures in ZigGlobalObject for JavaScript class bindings
Files:
src/bun.js/bindings/wrapAnsi.cpp
**/*.zig
📄 CodeRabbit inference engine (CLAUDE.md)
In Zig code, be careful with allocators and use defer for cleanup
Files:
src/string/immutable/visible.zig
src/**/*.zig
📄 CodeRabbit inference engine (src/CLAUDE.md)
src/**/*.zig: Use the#prefix for private fields in Zig structs, e.g.,struct {#foo: u32 };
Use Decl literals in Zig, e.g.,const decl: Decl = .{ .binding = 0, .value = 0 };
Place@importstatements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use@import()inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Files:
src/string/immutable/visible.zig
🧠 Learnings (21)
📓 Common learnings
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Write JS builtins for Bun's Node.js compatibility and APIs, and run `bun bd` after changes
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms
Applied to files:
src/bun.js/bindings/wrapAnsi.cppsrc/string/immutable/visible.zig
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for C++ classes with fields in JavaScript class bindings
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add symbol names without leading underscore to src/symbols.txt for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-20T05:35:57.318Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: src/bun.js/bindings/headers.h:729-731
Timestamp: 2025-09-20T05:35:57.318Z
Learning: symbols.txt in the Bun codebase is specifically for V8 API mangled symbols (without leading underscore), not for general Bun host functions declared with BUN_DECLARE_HOST_FUNCTION. Host functions are handled through different build mechanisms.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add symbol names with leading underscore and semicolons in braces to src/symbols.dyn for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Create V8 class headers with .h extension following the pattern V8ClassName.h that include pragma once, v8.h, V8Local.h, V8Isolate.h, and declare classes extending from Data with BUN_EXPORT static methods
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/bun.js/bindings/wrapAnsi.cppsrc/string/immutable/visible.zig
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-10T00:57:09.173Z
Learnt from: franciscop
Repo: oven-sh/bun PR: 24514
File: src/bun.js/api/crypto/PasswordObject.zig:86-101
Timestamp: 2025-11-10T00:57:09.173Z
Learning: In Bun's Zig codebase (PasswordObject.zig), when validating the parallelism parameter for Argon2, the upper limit is set to 65535 (2^16 - 1) rather than using `std.math.maxInt(u24)` because the latter triggers Zig's truncation limit checks. The value 65535 is a practical upper bound that avoids compiler issues while being sufficient for thread parallelism use cases.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use localToJSValue() to convert V8 handles to JSC values and perform JSC operations within V8 method implementations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Create V8 class implementations with .cpp extension following the pattern V8ClassName.cpp that include the header, v8_compatibility_assertions.h, use ASSERT_V8_TYPE_LAYOUT_MATCHES macro, and implement methods using isolate->currentHandleScope()->createLocal<T>() for handle creation
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8-module/main.cpp : Create test functions in test/v8/v8-module/main.cpp that take FunctionCallbackInfo<Value> parameter, use the test V8 API, print results for comparison with Node.js, and return Undefined
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-30T21:52:04.707Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2025-10-15T20:19:38.580Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23680
File: cmake/targets/BuildBun.cmake:822-822
Timestamp: 2025-10-15T20:19:38.580Z
Learning: In the Bun codebase, FFI is compiled with tcc (TinyCC), which barely supports C99. The headers `src/bun.js/api/FFI.h` and `src/bun.js/api/ffi-stdbool.h` are only used for FFI compilation with tcc, not for the main Bun target. Therefore, C23 compatibility concerns (such as bool/true/false keyword conflicts) do not apply to these FFI headers.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2025-10-15T20:19:37.256Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23680
File: cmake/targets/BuildBun.cmake:822-822
Timestamp: 2025-10-15T20:19:37.256Z
Learning: In the Bun codebase, FFI (Foreign Function Interface) code is compiled separately using TinyCC (tcc), which barely supports C99. Headers like src/bun.js/api/FFI.h and src/bun.js/api/ffi-stdbool.h are only used for FFI compilation with tcc, not with the main compiler. Therefore, C standard changes to the main Bun target do not affect FFI code compilation.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2025-11-12T04:11:52.293Z
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 24622
File: src/deps/uws/us_socket_t.zig:112-113
Timestamp: 2025-11-12T04:11:52.293Z
Learning: In Bun's Zig codebase, when passing u32 values to C FFI functions that expect c_uint parameters, no explicit intCast is needed because c_uint is equivalent to u32 on Bun's target platforms and Zig allows implicit coercion between equivalent types. This pattern is used consistently throughout src/deps/uws/us_socket_t.zig in functions like setTimeout, setLongTimeout, and setKeepalive.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.
Applied to files:
src/string/immutable/visible.zig
🪛 Cppcheck (2.19.0)
src/bun.js/bindings/wrapAnsi.cpp
[information] 42-42: Include file
(missingInclude)
[information] 52-52: Include file
(missingIncludeSystem)
[information] 59-59: Include file
(missingIncludeSystem)
[error] 66-66: failed to evaluate #if condition, undefined function-like macro invocation
(syntaxError)
🔇 Additional comments (5)
src/string/immutable/visible.zig (1)
1164-1173: LGTM for Latin-1 and codepoint width exports.The Latin-1 version correctly omits the
ambiguous_as_wideparameter since Latin-1 has no ambiguous-width characters. TheBun__codepointWidthexport properly delegates to the existing implementation.src/bun.js/bindings/wrapAnsi.cpp (4)
272-284: Good refactor: Properly delegates to Zig visible width implementation.This addresses the review feedback to reuse existing code rather than reimplementing width calculation. The Zig implementation provides SIMD-optimized width calculation.
595-670: LGTM: ANSI style preservation logic is well-structured.The logic correctly tracks SGR codes and OSC 8 hyperlinks, closing them before newlines and reopening after. The character casting from
ChartoUCharis correct since Latin-1 values fit within UTF-16.
874-899: LGTM: Options parsing is well-implemented.The code properly handles exceptions after each property access and uses appropriate type conversions. Default values are cleanly defined in the
WrapAnsiOptionsstruct.
786-841: Implementation looks solid overall.The main implementation correctly handles:
- Empty input and zero columns edge cases
- CRLF to LF normalization
- Per-line processing with proper ANSI preservation
The memory reservation strategy is conservative, which is appropriate for text wrapping that may need additional space.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| static char32_t decodeUTF8(const Latin1Character* ptr, size_t available, size_t& outLen) | ||
| { | ||
| uint8_t byte = static_cast<uint8_t>(ptr[0]); | ||
|
|
||
| if (byte < 0x80) { | ||
| outLen = 1; | ||
| return byte; | ||
| } | ||
|
|
||
| uint8_t seqLen = utf8SequenceLength(byte); | ||
| if (seqLen > available) { | ||
| outLen = 1; | ||
| return 0xFFFD; // Replacement character | ||
| } | ||
|
|
||
| char32_t cp = 0; | ||
| switch (seqLen) { | ||
| case 2: | ||
| cp = ((byte & 0x1F) << 6) | (ptr[1] & 0x3F); | ||
| break; | ||
| case 3: | ||
| cp = ((byte & 0x0F) << 12) | ((ptr[1] & 0x3F) << 6) | (ptr[2] & 0x3F); | ||
| break; | ||
| case 4: | ||
| cp = ((byte & 0x07) << 18) | ((ptr[1] & 0x3F) << 12) | ((ptr[2] & 0x3F) << 6) | (ptr[3] & 0x3F); | ||
| break; | ||
| default: | ||
| outLen = 1; | ||
| return 0xFFFD; | ||
| } | ||
|
|
||
| outLen = seqLen; | ||
| return cp; | ||
| } |
There was a problem hiding this comment.
Missing continuation byte validation in UTF-8 decoding.
The decoding doesn't validate that continuation bytes have the correct 10xxxxxx pattern. Malformed UTF-8 input could produce incorrect codepoints instead of the replacement character.
Suggested validation
static char32_t decodeUTF8(const Latin1Character* ptr, size_t available, size_t& outLen)
{
uint8_t byte = static_cast<uint8_t>(ptr[0]);
if (byte < 0x80) {
outLen = 1;
return byte;
}
uint8_t seqLen = utf8SequenceLength(byte);
if (seqLen > available) {
outLen = 1;
return 0xFFFD; // Replacement character
}
+ // Validate continuation bytes
+ for (size_t i = 1; i < seqLen; ++i) {
+ if ((ptr[i] & 0xC0) != 0x80) {
+ outLen = 1;
+ return 0xFFFD;
+ }
+ }
+
char32_t cp = 0;
switch (seqLen) {🧰 Tools
🪛 Cppcheck (2.19.0)
[information] 42-42: Include file
(missingInclude)
[information] 52-52: Include file
(missingIncludeSystem)
[information] 59-59: Include file
(missingIncludeSystem)
[error] 66-66: failed to evaluate #if condition, undefined function-like macro invocation
(syntaxError)
🤖 Prompt for AI Agents
In `@src/bun.js/bindings/wrapAnsi.cpp` around lines 35 - 68, The decodeUTF8
function currently assembles multi-byte sequences without validating that each
continuation byte matches the 10xxxxxx pattern, so malformed input can produce
invalid codepoints; update decodeUTF8 to, after computing seqLen and before
assembling cp, verify for each continuation byte ptr[i] (i=1..seqLen-1) that
(static_cast<uint8_t>(ptr[i]) & 0xC0) == 0x80 and if any check fails set outLen
= 1 and return 0xFFFD; after assembling cp also validate against overlong
encodings, UTF-16 surrogate range (0xD800–0xDFFF), and max codepoint > 0x10FFFF
and return 0xFFFD if invalid, otherwise set outLen = seqLen and return cp
(references: function decodeUTF8, helper utf8SequenceLength, variables ptr,
available, outLen).
| void trimLeadingSpaces() | ||
| { | ||
| size_t removeCount = 0; | ||
| bool inEscape = false; | ||
|
|
||
| // Count leading spaces (preserving ANSI) | ||
| for (size_t i = 0; i < m_data.size(); ++i) { | ||
| Char c = m_data[i]; | ||
| if (c == 0x1b) { | ||
| inEscape = true; | ||
| continue; | ||
| } | ||
| if (inEscape) { | ||
| if (c == 'm' || c == 0x07) | ||
| inEscape = false; | ||
| continue; | ||
| } | ||
| if (c == ' ' || c == '\t') | ||
| removeCount++; | ||
| else | ||
| break; | ||
| } | ||
|
|
||
| if (removeCount == 0) | ||
| return; | ||
|
|
||
| // Remove spaces while preserving ANSI codes | ||
| std::vector<Char> newData; | ||
| newData.reserve(m_data.size() - removeCount); | ||
|
|
||
| inEscape = false; | ||
| size_t removed = 0; | ||
|
|
||
| for (size_t i = 0; i < m_data.size(); ++i) { | ||
| Char c = m_data[i]; | ||
| if (c == 0x1b) { | ||
| inEscape = true; | ||
| newData.push_back(c); | ||
| continue; | ||
| } | ||
| if (inEscape) { | ||
| if (c == 'm' || c == 0x07) | ||
| inEscape = false; | ||
| newData.push_back(c); | ||
| continue; | ||
| } | ||
| if ((c == ' ' || c == '\t') && removed < removeCount) { | ||
| removed++; | ||
| continue; | ||
| } | ||
| newData.push_back(c); | ||
| } | ||
|
|
||
| m_data = std::move(newData); | ||
| } |
There was a problem hiding this comment.
Incomplete OSC sequence terminator handling in trimLeadingSpaces.
The escape sequence detection only handles BEL (0x07) and m terminators, but OSC sequences can also be terminated by ST (ESC \). This could cause incorrect behavior when trimming strings containing OSC sequences with ST terminators.
Suggested fix
for (size_t i = 0; i < m_data.size(); ++i) {
Char c = m_data[i];
if (c == 0x1b) {
inEscape = true;
+ // Check for ST terminator (ESC \) ending previous sequence
+ if (i + 1 < m_data.size() && m_data[i + 1] == '\\') {
+ newData.push_back(c);
+ continue;
+ }
continue;
}
if (inEscape) {
- if (c == 'm' || c == 0x07)
+ if (c == 'm' || c == 0x07 || c == 0x9c)
inEscape = false;
continue;
}| static constexpr uint32_t END_CODE = 39; | ||
|
|
||
| template<typename Char> | ||
| static std::optional<uint32_t> parseSgrCode(const Char* start, const Char* end) | ||
| { | ||
| if (end - start < 3 || start[0] != 0x1b || start[1] != '[') | ||
| return std::nullopt; | ||
|
|
||
| uint32_t code = 0; | ||
| for (const Char* it = start + 2; it < end; ++it) { | ||
| Char c = *it; | ||
| if (c >= '0' && c <= '9') { | ||
| code = code * 10 + (c - '0'); | ||
| } else if (c == 'm') { | ||
| return code; | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return std::nullopt; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Minor: Consider renaming END_CODE for clarity.
END_CODE = 39 represents "default foreground color", not a general reset code. The SGR reset code is actually 0. While the logic at line 625 correctly handles both, the constant name could be misleading.
-static constexpr uint32_t END_CODE = 39;
+static constexpr uint32_t DEFAULT_FOREGROUND_CODE = 39;🤖 Prompt for AI Agents
In `@src/bun.js/bindings/wrapAnsi.cpp` around lines 518 - 539, END_CODE named as
39 is misleading because it represents the default foreground SGR value, not a
general reset; rename the constant (e.g., SGR_DEFAULT_FOREGROUND or
DEFAULT_FOREGROUND_CODE) and update all references (such as in parseSgrCode
usage sites) to the new name, and add a short comment noting that SGR reset is 0
to avoid confusion.
| export fn Bun__visibleWidthExcludeANSI_utf8(ptr: [*]const u8, len: usize, ambiguous_as_wide: bool) usize { | ||
| _ = ambiguous_as_wide; // UTF-8 version doesn't use this parameter | ||
| const input = ptr[0..len]; | ||
| return visible.width.exclude_ansi_colors.utf8(input); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the exported functions mentioned in the review
sed -n '1149,1173p' src/string/immutable/visible.zigRepository: oven-sh/bun
Length of output: 1161
🏁 Script executed:
# Check the UTF-16 version to see if it actually uses ambiguous_as_wide
sed -n '1159,1165p' src/string/immutable/visible.zigRepository: oven-sh/bun
Length of output: 433
🏁 Script executed:
# Examine the underlying width functions referenced
rg -n "fn width|exclude_ansi_colors" src/string/immutable/visible.zig | head -20Repository: oven-sh/bun
Length of output: 626
🏁 Script executed:
# Check the implementation of utf8 and utf16 width functions
sed -n '1120,1145p' src/string/immutable/visible.zigRepository: oven-sh/bun
Length of output: 866
🏁 Script executed:
# Look for the visibleUTF8WidthFn function around line 752
sed -n '745,785p' src/string/immutable/visible.zigRepository: oven-sh/bun
Length of output: 1493
API inconsistency: ambiguous_as_wide is ignored for UTF-8 but used for UTF-16.
The UTF-8 export ignores the ambiguous_as_wide parameter while the UTF-16 version (line 1161) passes it through. The exported C functions have identical signatures, but the underlying Zig implementations differ: visible.width.exclude_ansi_colors.utf8() doesn't accept an ambiguousAsWide parameter at all, whereas visible.width.exclude_ansi_colors.utf16() does. This means users won't get consistent width calculations for ambiguous-width characters across different string encodings.
Consider either:
- Propagating the parameter through the UTF-8 path to match the UTF-16 behavior, or
- Documenting this as intentional behavior
🤖 Prompt for AI Agents
In `@src/string/immutable/visible.zig` around lines 1152 - 1156, The UTF-8 export
Bun__visibleWidthExcludeANSI_utf8 currently ignores ambiguous_as_wide whereas
the UTF-16 path forwards it to visible.width.exclude_ansi_colors.utf16, causing
inconsistent semantics; fix by propagating the ambiguous_as_wide flag through
the UTF‑8 path—either add/use an overload of
visible.width.exclude_ansi_colors.utf8 that accepts an ambiguousAsWide parameter
and call it with ambiguous_as_wide from Bun__visibleWidthExcludeANSI_utf8, or if
such an API cannot be added, explicitly document the difference and remove/mark
the unused parameter to avoid the misleading identical C signature; reference
Bun__visibleWidthExcludeANSI_utf8, ambiguous_as_wide,
visible.width.exclude_ansi_colors.utf8 and
visible.width.exclude_ansi_colors.utf16 when making the change.
| } | ||
|
|
||
| // Normalize \r\n to \n | ||
| std::vector<Char> normalized; |
There was a problem hiding this comment.
WTF::Vector should be used instead
12b0c94 to
4ee402e
Compare
Implement a wrap-ansi compatible text wrapping function that:
- Wraps text to fit within specified column width
- Preserves ANSI escape codes (SGR colors/styles, OSC 8 hyperlinks)
- Respects Unicode display widths (full-width chars, emoji)
- Supports options: hard, wordWrap, trim, ambiguousIsNarrow
The implementation closes and reopens ANSI codes around line breaks
for robust terminal compatibility. This differs slightly from the NPM
package in edge cases but produces visually equivalent output.
Tests include 27 custom tests and 23 tests ported from the NPM package
(MIT licensed, credited in file header).
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
- Port wrapAnsi implementation from Zig to C++ using WebKit's StringBuilder
- Use std::vector for internal row management
- Maintain ANSI escape code preservation across line breaks
- Support OSC 8 hyperlinks
- Handle full-width characters, emoji, and zero-width characters
- Keep the same API and test coverage (all 27 tests pass)
This addresses the review feedback requesting C++ implementation for:
- Better performance using StringBuilder
- Avoiding UTF-8 to UTF-16 conversion overhead
- Easier SIMD optimization potential
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 2
Claude-Permission-Prompts: 1
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
- Remove redundant character width calculation code from C++
- Add Zig exports for visible width functions that C++ can call
- Keep UTF-8/UTF-16 decoding utilities needed for hard wrap
- Reuse existing SIMD-optimized visible width calculation from visible.zig
This addresses the review feedback to use existing code instead of
reimplementing width calculation.
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 1
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
Move common ANSI escape sequence handling code to a shared header:
- isEscapeCharacter() - check for ANSI escape introducers
- findEscapeCharacter() - SIMD-optimized escape sequence search
- consumeANSI() - state machine to consume ANSI sequences
This eliminates ~170 lines of duplicate code between wrapAnsi.cpp
and stripANSI.cpp.
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 1
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
4ee402e to
96c3913
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/bun.js/bindings/wrapAnsi.cpp`:
- Around line 706-731: The options parsing repeatedly calls
JSC::Identifier::fromString for each property which is wasteful; replace those
per-call string-to-identifier conversions with cached identifiers (e.g., static
or VM-provided JSC::Identifier instances) and use them when looking up
properties on optionsValue/optionsObj; update the WrapAnsiOptions parsing block
(symbols: WrapAnsiOptions, optionsValue, optionsObj, Identifier::fromString,
ambiguousIsNarrow, wordWrap, trim, hard) to use the cached identifiers so each
call reuses the same JSC::Identifier objects instead of recreating them.
♻️ Duplicate comments (5)
src/bun.js/bindings/wrapAnsi.cpp (5)
246-249: Width tracking incorrect after line wrap.When a new row is created and a character is appended,
visis reset to 0 but should reflect the width of the just-appended character. This could cause off-by-one errors in column calculations.Suggested fix
} else if (!isInsideEscape) { rows.push_back(Row<Char>()); rows.back().append(it, it + charLen); - vis = 0; + vis = charWidth; } else {
35-68: Missing UTF-8 continuation byte validation.The
decodeUTF8function doesn't validate that continuation bytes follow the10xxxxxxpattern. Malformed UTF-8 (e.g.,\xC0\xC0) could produce incorrect codepoints instead of replacement characters.Suggested validation
char32_t cp = 0; + // Validate continuation bytes + for (size_t i = 1; i < seqLen; ++i) { + if ((static_cast<uint8_t>(ptr[i]) & 0xC0) != 0x80) { + outLen = 1; + return 0xFFFD; + } + } + switch (seqLen) {
699-704: Handle negative and infinite column values.
toIntegerOrInfinitycan return negative values (wrapping to largesize_t) or positive infinity. The current code doesn't guard against these edge cases.Suggested fix
// Get columns size_t columns = 0; if (!columnsValue.isUndefined()) { - columns = static_cast<size_t>(columnsValue.toIntegerOrInfinity(globalObject)); + double colsDouble = columnsValue.toIntegerOrInfinity(globalObject); RETURN_IF_EXCEPTION(scope, {}); + if (colsDouble > 0 && std::isfinite(colsDouble)) + columns = static_cast<size_t>(colsDouble); }
122-126: Consider usingWTF::Vectorinstead ofstd::vector.Per reviewer feedback,
WTF::Vectoris preferred in the Bun codebase for consistency and potential performance benefits with WebKit's memory allocators.Suggested change
template<typename Char> class Row { public: - std::vector<Char> m_data; + WTF::Vector<Char> m_data;This would require updating the method calls (e.g.,
push_back→append,insert→ appropriate WTF::Vector methods).
161-164: Incomplete ANSI sequence terminator detection.The escape sequence detection only handles
m(SGR) and BEL (0x07) terminators, but ANSI sequences can also end with ST (0x9c) or the two-character sequence ESC\. This could cause incorrect trimming behavior with OSC sequences using ST terminators.Suggested fix
if (inEscape) { - if (c == 'm' || c == 0x07) + if (c == 'm' || c == 0x07 || c == 0x9c) inEscape = false; + // Handle ESC \ (ST) terminator + if (c == '\\' && i > 0 && m_data[i-1] == 0x1b) + inEscape = false; continue; }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- 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 12b0c94f334e79e1199d616009043bcc5db9cc79 and 4ee402e3b966742fcaa72c048ba2a28f6854fc54.
📒 Files selected for processing (3)
src/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/wrapAnsi.cpp
🧰 Additional context used
📓 Path-based instructions (1)
src/bun.js/bindings/**/*.cpp
📄 CodeRabbit inference engine (CLAUDE.md)
src/bun.js/bindings/**/*.cpp: Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Define properties using HashTableValue arrays in C++ JavaScript class bindings
Add iso subspaces for C++ classes with fields in JavaScript class bindings
Cache structures in ZigGlobalObject for JavaScript class bindings
Files:
src/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/wrapAnsi.cpp
🧠 Learnings (20)
📓 Common learnings
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25462
File: src/ast/visitExpr.zig:1644-1695
Timestamp: 2025-12-11T02:11:47.024Z
Learning: In Bun's bundler feature flag implementation (src/ast/visitExpr.zig), the validation for feature() flag names intentionally only rejects UTF-16 strings (checking `is_utf16`) while allowing UTF-8 strings, even though the error message says "must be an ASCII string". This is the intended behavior and should not be changed to enforce strict ASCII validation.
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Write JS builtins for Bun's Node.js compatibility and APIs, and run `bun bd` after changes
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms
Applied to files:
src/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for C++ classes with fields in JavaScript class bindings
Applied to files:
src/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-30T21:52:04.707Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Applied to files:
src/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Create V8 class headers with .h extension following the pattern V8ClassName.h that include pragma once, v8.h, V8Local.h, V8Isolate.h, and declare classes extending from Data with BUN_EXPORT static methods
Applied to files:
src/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Create V8 class implementations with .cpp extension following the pattern V8ClassName.cpp that include the header, v8_compatibility_assertions.h, use ASSERT_V8_TYPE_LAYOUT_MATCHES macro, and implement methods using isolate->currentHandleScope()->createLocal<T>() for handle creation
Applied to files:
src/bun.js/bindings/ANSIHelpers.h
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add symbol names without leading underscore to src/symbols.txt for each new V8 API method
Applied to files:
src/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.
Applied to files:
src/bun.js/bindings/ANSIHelpers.h
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add symbol names with leading underscore and semicolons in braces to src/symbols.dyn for each new V8 API method
Applied to files:
src/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-15T20:19:37.256Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23680
File: cmake/targets/BuildBun.cmake:822-822
Timestamp: 2025-10-15T20:19:37.256Z
Learning: In the Bun codebase, FFI (Foreign Function Interface) code is compiled separately using TinyCC (tcc), which barely supports C99. Headers like src/bun.js/api/FFI.h and src/bun.js/api/ffi-stdbool.h are only used for FFI compilation with tcc, not with the main compiler. Therefore, C standard changes to the main Bun target do not affect FFI code compilation.
Applied to files:
src/bun.js/bindings/ANSIHelpers.h
📚 Learning: 2025-12-23T06:50:31.577Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25429
File: src/bun.js/bindings/helpers.h:422-422
Timestamp: 2025-12-23T06:50:31.577Z
Learning: In Bun's C++ bindings, when returning an empty JSC::Identifier and a VM is accessible, prefer using vm.propertyNames->emptyIdentifier over constructing with JSC::Identifier(JSC::Identifier::EmptyIdentifierFlag::EmptyIdentifier). The cached identifier from the VM's property names table is more efficient and consistent with WebKit upgrade patterns. Apply this guidance to src/bun.js/bindings/helpers.h and similar header files in the same bindings directory (i.e., any file that constructs an EmptyIdentifier).
Applied to files:
src/bun.js/bindings/ANSIHelpers.h
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-20T05:35:57.318Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: src/bun.js/bindings/headers.h:729-731
Timestamp: 2025-09-20T05:35:57.318Z
Learning: symbols.txt in the Bun codebase is specifically for V8 API mangled symbols (without leading underscore), not for general Bun host functions declared with BUN_DECLARE_HOST_FUNCTION. Host functions are handled through different build mechanisms.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-10T00:57:09.173Z
Learnt from: franciscop
Repo: oven-sh/bun PR: 24514
File: src/bun.js/api/crypto/PasswordObject.zig:86-101
Timestamp: 2025-11-10T00:57:09.173Z
Learning: In Bun's Zig codebase (PasswordObject.zig), when validating the parallelism parameter for Argon2, the upper limit is set to 65535 (2^16 - 1) rather than using `std.math.maxInt(u24)` because the latter triggers Zig's truncation limit checks. The value 65535 is a practical upper bound that avoids compiler issues while being sufficient for thread parallelism use cases.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-11T02:11:47.024Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25462
File: src/ast/visitExpr.zig:1644-1695
Timestamp: 2025-12-11T02:11:47.024Z
Learning: In Bun's bundler feature flag implementation (src/ast/visitExpr.zig), the validation for feature() flag names intentionally only rejects UTF-16 strings (checking `is_utf16`) while allowing UTF-8 strings, even though the error message says "must be an ASCII string". This is the intended behavior and should not be changed to enforce strict ASCII validation.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use localToJSValue() to convert V8 handles to JSC values and perform JSC operations within V8 method implementations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
🧬 Code graph analysis (1)
src/bun.js/bindings/stripANSI.cpp (1)
src/bun.js/bindings/ANSIHelpers.h (2)
findEscapeCharacter(29-56)consumeANSI(64-189)
🪛 Cppcheck (2.19.0)
src/bun.js/bindings/wrapAnsi.cpp
[information] 42-42: Include file
(missingInclude)
[information] 52-52: Include file
(missingIncludeSystem)
[information] 59-59: Include file
(missingIncludeSystem)
[error] 66-66: failed to evaluate #if condition, undefined function-like macro invocation
(syntaxError)
🔇 Additional comments (9)
src/bun.js/bindings/ANSIHelpers.h (3)
10-25: LGTM! Comprehensive escape character detection.The function correctly identifies all ANSI escape sequence introducers including ESC (0x1b), CSI (0x9b), OSC (0x9d), DCS (0x90), SOS (0x98), PM (0x9e), and APC (0x9f) per ECMA-48.
27-56: LGTM! Well-optimized SIMD search with correct fallback.The SIMD mask approach efficiently filters candidate escape characters by checking bit patterns common to 0x10-0x1f and 0x90-0x9f ranges. The scalar fallback for remaining bytes ensures correctness.
63-187: LGTM! Robust ANSI sequence state machine.The state machine correctly handles:
- CSI sequences with proper final byte detection (0x40-0x7e)
- OSC sequences with BEL, ST, and ESC \ terminators
- ST-terminated sequences (DCS, SOS, PM, APC)
- Two-byte XTerm sequences
Returning
endfor unterminated sequences is the appropriate behavior for partial input handling.src/bun.js/bindings/stripANSI.cpp (1)
3-3: LGTM! Clean refactor to shared ANSI helpers.The delegation to
ANSI::findEscapeCharacterandANSI::consumeANSIeliminates code duplication while maintaining the same behavior. This aligns with the PR's goal of extracting shared ANSI parsing utilities.Also applies to: 26-26, 44-44
src/bun.js/bindings/wrapAnsi.cpp (5)
352-371:parseSgrCodeonly handles single-parameter SGR sequences.The function parses only the first numeric parameter and stops at the first non-digit. Complex SGR sequences like
\x1b[38;5;196m(256-color) or\x1b[1;31m(bold red) will only return the first code. This may be intentional for basic style tracking, but compound styles won't be fully preserved.Is this limitation acceptable for the use case, or should multi-parameter SGR sequences be supported?
427-502: LGTM! Robust ANSI style preservation across line breaks.The
joinRowsWithAnsiPreservationfunction correctly:
- Closes active styles before newlines using appropriate reset codes
- Reopens styles after newlines to maintain visual continuity
- Handles both SGR codes and OSC 8 hyperlinks
508-612: LGTM! Comprehensive line processing with multiple wrapping modes.The
processLinefunction correctly handles the interaction betweenhard,wordWrap, andtrimoptions with appropriate break decisions based on remaining column space.
618-673: LGTM! Clean main implementation flow.The
wrapAnsiImplfunction correctly:
- Handles edge cases (columns == 0, empty input)
- Normalizes line endings before processing
- Processes each line separately with ANSI preservation
Note: The
std::vectorusage on line 631 was already flagged for replacement withWTF::Vector.
104-116: Zig width calculation exports verified as correct.The template correctly dispatches to the appropriate Zig function based on character size. Parameter types match the Zig export signatures, and the
!ambiguousIsNarrowinversion is intentional (C++ parameter is inverted because Zig expectsambiguous_as_wide). UTF-8 version ignores the parameter, UTF-16 uses it.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| // Parse options | ||
| WrapAnsiOptions options; | ||
| if (optionsValue.isObject()) { | ||
| JSC::JSObject* optionsObj = optionsValue.toObject(globalObject); | ||
| RETURN_IF_EXCEPTION(scope, {}); | ||
|
|
||
| JSC::JSValue hardValue = optionsObj->get(globalObject, JSC::Identifier::fromString(vm, "hard"_s)); | ||
| RETURN_IF_EXCEPTION(scope, {}); | ||
| if (!hardValue.isUndefined()) | ||
| options.hard = hardValue.toBoolean(globalObject); | ||
|
|
||
| JSC::JSValue wordWrapValue = optionsObj->get(globalObject, JSC::Identifier::fromString(vm, "wordWrap"_s)); | ||
| RETURN_IF_EXCEPTION(scope, {}); | ||
| if (!wordWrapValue.isUndefined()) | ||
| options.wordWrap = wordWrapValue.toBoolean(globalObject); | ||
|
|
||
| JSC::JSValue trimValue = optionsObj->get(globalObject, JSC::Identifier::fromString(vm, "trim"_s)); | ||
| RETURN_IF_EXCEPTION(scope, {}); | ||
| if (!trimValue.isUndefined()) | ||
| options.trim = trimValue.toBoolean(globalObject); | ||
|
|
||
| JSC::JSValue ambiguousValue = optionsObj->get(globalObject, JSC::Identifier::fromString(vm, "ambiguousIsNarrow"_s)); | ||
| RETURN_IF_EXCEPTION(scope, {}); | ||
| if (!ambiguousValue.isUndefined()) | ||
| options.ambiguousIsNarrow = ambiguousValue.toBoolean(globalObject); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider caching property identifiers for options parsing.
Creating Identifier::fromString for each option lookup on every call is slightly inefficient. For a frequently-called API, consider using cached identifiers from the VM's built-in strings or a static structure.
🤖 Prompt for AI Agents
In `@src/bun.js/bindings/wrapAnsi.cpp` around lines 706 - 731, The options parsing
repeatedly calls JSC::Identifier::fromString for each property which is
wasteful; replace those per-call string-to-identifier conversions with cached
identifiers (e.g., static or VM-provided JSC::Identifier instances) and use them
when looking up properties on optionsValue/optionsObj; update the
WrapAnsiOptions parsing block (symbols: WrapAnsiOptions, optionsValue,
optionsObj, Identifier::fromString, ambiguousIsNarrow, wordWrap, trim, hard) to
use the cached identifiers so each call reuses the same JSC::Identifier objects
instead of recreating them.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@packages/bun-types/bun.d.ts`:
- Around line 647-688: Update the JSDoc for the wrapAnsi function to explicitly
state that CRLF line endings ("\r\n") are normalized to LF ("\n") before
wrapping; mention that this normalization occurs on the input string (parameter:
input) and that wrapped output will use "\n" for line breaks, and note that this
normalization is performed before any other options (e.g., hard wrapping, ANSI
preservation) are applied so callers can rely on consistent line-ending behavior
from wrapAnsi.
In `@src/bun.js/bindings/wrapAnsi.cpp`:
- Around line 630-641: Replace the std::vector usage for the normalized buffer
with WTF::Vector to follow WebKit conventions: change std::vector<Char>
normalized to WTF::Vector<Char> normalized, call
normalized.reserveCapacity(input.size()) (or ensureCapacity if that pattern is
used in the file) instead of reserve, keep the same push_back usage and loop
logic, and add the appropriate `#include` for WTF::Vector (e.g. <wtf/Vector.h> or
"wtf/Vector.h") so the file compiles.
- Around line 352-371: parseSgrCode currently only returns a single simple
numeric SGR and thus misses compound/extended sequences (e.g. "38;5;n" and
"38;2;r;g;b"), and getCloseCode only maps basic color codes so extended colors
are lost across wraps; update parseSgrCode to parse full semicolon-separated SGR
parameter lists (returning either a vector/list of uint32_t or an encoded
representation) and update getCloseCode to accept and handle extended color
params (at least preserving "38;5;n" and "38;2;r;g;b" sequences when computing
closing codes), or normalize/propagate unknown parameter sequences unchanged;
modify callers that expect a single uint32_t (e.g., places using parseSgrCode's
return) to handle the new representation so extended color styles are preserved
across wrapped lines.
In `@src/string/immutable/wrap_ansi.zig`:
- Around line 219-286: parseSgrCode, joinRowsWithAnsiPreservation, parseOsc8Url,
stringWidth, and wrapWord have three related bugs: SGR parsing stops at first
non-digit, only a single escape_code is tracked losing concurrent styles, and
OSC8 termination only checks BEL not the ST sequence. Fix parseSgrCode to
collect the full semicolon-separated parameter list (e.g., parse until the final
'm' and return all params/bytes, not just the first numeric token), change
joinRowsWithAnsiPreservation to track active SGR state as a collection
(stack/set/list of codes) and replay the entire active-code sequence when
restoring styles after a line break, and make OSC8 handling consistent by
recognizing both BEL (0x07) and the ST terminator (ESC followed by '\\') in
parseOsc8Url, stringWidth, and wrapWord so escapes terminate correctly and
width/wrapping and style restoration work for both BEL- and ST-terminated OSC8
sequences.
♻️ Duplicate comments (12)
test/js/bun/util/wrapAnsi.npm.test.ts (1)
36-37: Fix Biome "control character in regex" errors.This was flagged in a previous review. Use
RegExpconstructor with escaped strings to satisfy the linter:Proposed fix
-const stripAnsi = (s: string) => s.replace(/\u001B\[[0-9;]*m|\u001B\]8;;[^\u0007]*\u0007/g, ""); -const hasAnsi = (s: string) => /\u001B\[[0-9;]*m/.test(s); +const stripAnsiRe = new RegExp("\\x1B\\[[0-9;]*m|\\x1B\\]8;;[^\\x07]*\\x07", "g"); +const hasAnsiRe = new RegExp("\\x1B\\[[0-9;]*m"); +const stripAnsi = (s: string) => s.replace(stripAnsiRe, ""); +const hasAnsi = (s: string) => hasAnsiRe.test(s);packages/bun-types/bun.d.ts (1)
622-628: FixWrapAnsiOptions.wordWrapJSDoc:falseis not “only explicit newlines”.The current description is misleading vs expected wrap-ansi semantics (character wrapping/splitting words when needed).
Proposed diff
/** * If `true`, wrap at word boundaries when possible. - * If `false`, don't perform word wrapping (only wrap at explicit newlines). + * If `false`, wrap by character (may split words) instead of preferring word boundaries. * * `@default` true */ wordWrap?: boolean;test/js/bun/util/wrapAnsi.test.ts (3)
42-49: StrengthenwordWrap: falsetest and fix the misleading comment.Right now it doesn’t validate behavior (and the comment is likely incorrect).
Proposed diff
describe("wordWrap option", () => { - test("wordWrap false disables wrapping", () => { - // Without wordWrap, only explicit newlines should cause breaks - const result = Bun.wrapAnsi("hello world", 5, { wordWrap: false }); - // The behavior may vary - just check it doesn't crash - expect(typeof result).toBe("string"); + test("wordWrap false wraps by character (may split words)", () => { + expect(Bun.wrapAnsi("abcdefghij", 4, { wordWrap: false })).toBe("abcd\nefgh\nij"); }); });
145-163: Replace edge-case “typeof” assertions with observable expectations (esp. CRLF normalization).Proposed diff
describe("edge cases", () => { test("handles tabs", () => { const input = "a\tb"; const result = Bun.wrapAnsi(input, 10); - expect(typeof result).toBe("string"); + expect(result).toBe("a\tb"); }); test("handles Windows line endings", () => { const input = "hello\r\nworld"; const result = Bun.wrapAnsi(input, 10); - expect(typeof result).toBe("string"); + // wrapAnsi normalizes CRLF to LF + expect(result).toBe("hello\nworld"); }); test("handles consecutive spaces", () => { const input = "hello world"; - const result = Bun.wrapAnsi(input, 10); - expect(typeof result).toBe("string"); + const result = Bun.wrapAnsi(input, 20); + expect(result).toBe("hello world"); }); });
165-177: MakeambiguousIsNarrowtests assert a wrapping difference.Proposed diff
describe("ambiguousIsNarrow option", () => { test("default treats ambiguous as narrow", () => { - // By default, ambiguous width chars should be treated as width 1 - const result1 = Bun.wrapAnsi("αβγ", 3); - // Greek letters are ambiguous width - expect(typeof result1).toBe("string"); + const result = Bun.wrapAnsi("αβγ", 4, { hard: true, ambiguousIsNarrow: true }); + expect(result).toBe("αβγ"); }); test("ambiguousIsNarrow false treats as wide", () => { - const result = Bun.wrapAnsi("αβγ", 3, { ambiguousIsNarrow: false }); - expect(typeof result).toBe("string"); + const result = Bun.wrapAnsi("αβγ", 4, { hard: true, ambiguousIsNarrow: false }); + expect(result).toBe("αβ\nγ"); }); });src/string/immutable/wrap_ansi.zig (1)
158-163: Do not swallow allocator failures instringVisibleTrimSpacesRight(silent corruption).
catch {}on append operations can silently drop bytes and produce invalid output under memory pressure.Proposed diff
- if (options.trim) { - for (rows.items) |*row| { - stringVisibleTrimSpacesRight(allocator, row); - } - } + if (options.trim) { + for (rows.items) |*row| { + try stringVisibleTrimSpacesRight(allocator, row); + } + } /// Trim trailing spaces ignoring invisible sequences -fn stringVisibleTrimSpacesRight(allocator: std.mem.Allocator, row: *std.ArrayListUnmanaged(u8)) void { +fn stringVisibleTrimSpacesRight(allocator: std.mem.Allocator, row: *std.ArrayListUnmanaged(u8)) !void { // Split by spaces and find last word with visible content var words_iter = std.mem.splitScalar(u8, row.items, ' '); var last: usize = 0; var count: usize = 0; @@ // Rebuild with only words up to last, keeping trailing ANSI codes var new_row: std.ArrayListUnmanaged(u8) = .{}; + errdefer new_row.deinit(allocator); var words_iter2 = std.mem.splitScalar(u8, row.items, ' '); var idx: usize = 0; var trailing_ansi: std.ArrayListUnmanaged(u8) = .{}; + defer trailing_ansi.deinit(allocator); while (words_iter2.next()) |word| { if (idx < last) { if (idx > 0) { - new_row.append(allocator, ' ') catch {}; + try new_row.append(allocator, ' '); } - new_row.appendSlice(allocator, word) catch {}; + try new_row.appendSlice(allocator, word); } else { // Append ANSI sequences from trailing words for (word) |c| { if (c == '\x1b' or trailing_ansi.items.len > 0) { - trailing_ansi.append(allocator, c) catch {}; + try trailing_ansi.append(allocator, c); } } } idx += 1; } // Append trailing ANSI codes - new_row.appendSlice(allocator, trailing_ansi.items) catch {}; - trailing_ansi.deinit(allocator); + try new_row.appendSlice(allocator, trailing_ansi.items); // Replace row content row.deinit(allocator); row.* = new_row; }Also applies to: 169-217
src/string/immutable/visible.zig (1)
1149-1156: API inconsistency:ambiguous_as_wideis unused for UTF-8 path.The UTF-8 export accepts
ambiguous_as_widebut ignores it (line 1153), while the UTF-16 version (line 1161) passes it through. This creates inconsistent behavior across encodings for ambiguous-width characters.Consider either propagating the parameter through the UTF-8 path or documenting this as intentional behavior.
src/bun.js/bindings/wrapAnsi.cpp (5)
35-68: Missing continuation byte validation in UTF-8 decoding.The
decodeUTF8function doesn't validate that continuation bytes match the10xxxxxxpattern. Malformed UTF-8 could produce incorrect codepoints.Suggested validation
char32_t cp = 0; + // Validate continuation bytes have 10xxxxxx pattern + for (size_t i = 1; i < seqLen; ++i) { + if ((static_cast<uint8_t>(ptr[i]) & 0xC0) != 0x80) { + outLen = 1; + return 0xFFFD; + } + } + switch (seqLen) {
122-125: Consider usingWTF::Vectorinstead ofstd::vector.Per maintainer feedback,
WTF::Vectorshould be preferred in WebKit/Bun bindings for consistency and to leverage WebKit's optimized allocator.
149-203: Incomplete escape sequence terminator handling intrimLeadingSpaces.The escape detection only handles
'm'(SGR final byte) and BEL (0x07), but:
- CSI sequences can end with any byte in 0x40-0x7E range, not just
'm'- OSC sequences can also be terminated by ST (
ESC \or0x9c)This could cause incorrect trimming behavior with non-SGR CSI sequences.
246-252: Width tracking is incorrect after line wrap.When a new row is started and a character is appended (lines 247-249),
visis set to 0 but should be set tocharWidthto account for the character just appended.Suggested fix
} else if (!isInsideEscape) { rows.push_back(Row<Char>()); rows.back().append(it, it + charLen); - vis = 0; + vis = charWidth; } else {
699-704: Handle negative and infinite column values.
toIntegerOrInfinitycan return negative values or infinity, which would cause issues when cast tosize_t. Negative values wrap to large unsigned values, and infinity becomes undefined behavior.Suggested fix
// Get columns size_t columns = 0; if (!columnsValue.isUndefined()) { - columns = static_cast<size_t>(columnsValue.toIntegerOrInfinity(globalObject)); + double colsDouble = columnsValue.toIntegerOrInfinity(globalObject); RETURN_IF_EXCEPTION(scope, {}); + if (colsDouble > 0 && std::isfinite(colsDouble)) + columns = static_cast<size_t>(std::min(colsDouble, static_cast<double>(SIZE_MAX))); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- 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 4ee402e3b966742fcaa72c048ba2a28f6854fc54 and 96c3913.
⛔ Files ignored due to path filters (1)
bench/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
bench/package.jsonbench/runner.mjsbench/snippets/wrap-ansi.jspackages/bun-types/bun.d.tssrc/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/wrapAnsi.cppsrc/bun.js/bindings/wrapAnsi.hsrc/string/immutable/visible.zigsrc/string/immutable/wrap_ansi.zigtest/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.test.ts?(x)
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.ts?(x): Never usebun testdirectly - always usebun bd testto run tests with debug build changes
For single-file tests, prefer-eflag overtempDir
For multi-file tests, prefertempDirandBun.spawnover single-file tests
UsenormalizeBunSnapshotto normalize snapshot output of tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
UsetempDirfromharnessto create temporary directories - do not usetmpdirSyncorfs.mkdtempSync
When spawning processes in tests, expect stdout before expecting exit code for more useful error messages on test failure
Do not write flaky tests - do not usesetTimeoutin tests; instead await the condition to be met
Verify tests fail withUSE_SYSTEM_BUN=1 bun test <file>and pass withbun bd test <file>- tests are invalid if they pass with USE_SYSTEM_BUN=1
Test files must end with.test.tsor.test.tsx
Avoid shell commands likefindorgrepin tests - use Bun's Glob and built-in tools instead
Files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
test/**/*.test.ts?(x)
📄 CodeRabbit inference engine (CLAUDE.md)
Always use
port: 0in tests - do not hardcode ports or use custom random port number functions
Files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}
📄 CodeRabbit inference engine (test/CLAUDE.md)
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Usebun bd test <...test file>to run tests with compiled code changes. Do not usebun testas it will not include your changes.
Usebun:testfor files ending in*.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, usebun bd <file>instead ofbun bd test <file>since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.
Files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
src/bun.js/bindings/**/*.cpp
📄 CodeRabbit inference engine (CLAUDE.md)
src/bun.js/bindings/**/*.cpp: Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Define properties using HashTableValue arrays in C++ JavaScript class bindings
Add iso subspaces for C++ classes with fields in JavaScript class bindings
Cache structures in ZigGlobalObject for JavaScript class bindings
Files:
src/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/wrapAnsi.cpp
**/*.zig
📄 CodeRabbit inference engine (CLAUDE.md)
In Zig code, be careful with allocators and use defer for cleanup
Files:
src/string/immutable/visible.zigsrc/string/immutable/wrap_ansi.zig
src/**/*.zig
📄 CodeRabbit inference engine (src/CLAUDE.md)
src/**/*.zig: Use the#prefix for private fields in Zig structs, e.g.,struct {#foo: u32 };
Use Decl literals in Zig, e.g.,const decl: Decl = .{ .binding = 0, .value = 0 };
Place@importstatements at the bottom of the file in Zig (auto formatter will handle positioning)
Never use@import()inline inside functions in Zig; always place imports at the bottom of the file or containing struct
Files:
src/string/immutable/visible.zigsrc/string/immutable/wrap_ansi.zig
🧠 Learnings (63)
📓 Common learnings
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
📚 Learning: 2025-11-20T19:51:32.288Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24880
File: packages/bun-vscode/package.json:382-385
Timestamp: 2025-11-20T19:51:32.288Z
Learning: In the Bun repository, dependencies may be explicitly added to package.json files (even when not directly imported in code) to force version upgrades on transitive dependencies, particularly as part of Aikido security scanner remediation to ensure vulnerable transitive dependencies resolve to patched versions.
Applied to files:
bench/package.json
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tsbench/snippets/wrap-ansi.jstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-14T21:08:10.406Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/js/node/test/parallel/CLAUDE.md:0-0
Timestamp: 2026-01-14T21:08:10.406Z
Learning: These are Node.js compatibility tests not written by Bun and cannot be modified
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tsbench/snippets/wrap-ansi.jstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Use `normalizeBunSnapshot` to normalize snapshot output of tests
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Verify tests fail with `USE_SYSTEM_BUN=1 bun test <file>` and pass with `bun bd test <file>` - tests are invalid if they pass with USE_SYSTEM_BUN=1
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Never use `bun test` directly - always use `bun bd test` to run tests with debug build changes
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Do not set a timeout on tests. Bun already has timeouts built-in.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-12-11T02:11:47.024Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25462
File: src/ast/visitExpr.zig:1644-1695
Timestamp: 2025-12-11T02:11:47.024Z
Learning: In Bun's bundler feature flag implementation (src/ast/visitExpr.zig), the validation for feature() flag names intentionally only rejects UTF-16 strings (checking `is_utf16`) while allowing UTF-8 strings, even though the error message says "must be an ASCII string". This is the intended behavior and should not be changed to enforce strict ASCII validation.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tssrc/string/immutable/visible.zigsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-30T21:52:04.707Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tssrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/ANSIHelpers.hsrc/string/immutable/visible.zigsrc/bun.js/bindings/stripANSI.cpptest/js/bun/util/wrapAnsi.test.tssrc/bun.js/bindings/wrapAnsi.cppsrc/string/immutable/wrap_ansi.zig
📚 Learning: 2025-09-30T22:53:19.887Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 23117
File: src/bun.js/test/snapshot.zig:265-276
Timestamp: 2025-09-30T22:53:19.887Z
Learning: In Bun's snapshot testing (src/bun.js/test/snapshot.zig), multiple inline snapshots at the same line and column (same call position) must have identical values. However, multiple inline snapshots on the same line at different columns are allowed to have different values. The check is position-specific (line+col), not line-wide.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-09-24T05:48:59.872Z
Learnt from: nektro
Repo: oven-sh/bun PR: 22806
File: scripts/runner.node.mjs:687-689
Timestamp: 2025-09-24T05:48:59.872Z
Learning: In the Bun codebase, the `startGroup` utility function in scripts/runner.node.mjs automatically closes any previously open group when called, so `startGroup("End")` correctly closes the final test group and keeps subsequent output ungrouped.
Applied to files:
test/js/bun/util/wrapAnsi.npm.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/ANSIHelpers.hsrc/string/immutable/visible.zigsrc/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Create V8 class headers with .h extension following the pattern V8ClassName.h that include pragma once, v8.h, V8Local.h, V8Isolate.h, and declare classes extending from Data with BUN_EXPORT static methods
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for C++ classes with fields in JavaScript class bindings
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/ANSIHelpers.hsrc/bun.js/bindings/stripANSI.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Create V8 class implementations with .cpp extension following the pattern V8ClassName.cpp that include the header, v8_compatibility_assertions.h, use ASSERT_V8_TYPE_LAYOUT_MATCHES macro, and implement methods using isolate->currentHandleScope()->createLocal<T>() for handle creation
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/ANSIHelpers.h
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add symbol names with leading underscore and semicolons in braces to src/symbols.dyn for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/string/immutable/visible.zigsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-20T05:35:57.318Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: src/bun.js/bindings/headers.h:729-731
Timestamp: 2025-09-20T05:35:57.318Z
Learning: symbols.txt in the Bun codebase is specifically for V8 API mangled symbols (without leading underscore), not for general Bun host functions declared with BUN_DECLARE_HOST_FUNCTION. Host functions are handled through different build mechanisms.
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add symbol names without leading underscore to src/symbols.txt for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JavaScript class bindings
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use localToJSValue() to convert V8 handles to JSC values and perform JSC operations within V8 method implementations
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-15T20:19:37.256Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23680
File: cmake/targets/BuildBun.cmake:822-822
Timestamp: 2025-10-15T20:19:37.256Z
Learning: In the Bun codebase, FFI (Foreign Function Interface) code is compiled separately using TinyCC (tcc), which barely supports C99. Headers like src/bun.js/api/FFI.h and src/bun.js/api/ffi-stdbool.h are only used for FFI compilation with tcc, not with the main compiler. Therefore, C standard changes to the main Bun target do not affect FFI code compilation.
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/string/immutable/visible.zig
📚 Learning: 2025-12-23T06:50:31.577Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25429
File: src/bun.js/bindings/helpers.h:422-422
Timestamp: 2025-12-23T06:50:31.577Z
Learning: In Bun's C++ bindings, when returning an empty JSC::Identifier and a VM is accessible, prefer using vm.propertyNames->emptyIdentifier over constructing with JSC::Identifier(JSC::Identifier::EmptyIdentifierFlag::EmptyIdentifier). The cached identifier from the VM's property names table is more efficient and consistent with WebKit upgrade patterns. Apply this guidance to src/bun.js/bindings/helpers.h and similar header files in the same bindings directory (i.e., any file that constructs an EmptyIdentifier).
Applied to files:
src/bun.js/bindings/wrapAnsi.hsrc/bun.js/bindings/ANSIHelpers.h
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Write JS builtins for Bun's Node.js compatibility and APIs, and run `bun bd` after changes
Applied to files:
bench/snippets/wrap-ansi.jssrc/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8-module/main.cpp : Register new V8 API test functions in the Init method using NODE_SET_METHOD with exports object
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-12-23T06:50:41.142Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25429
File: src/bun.js/bindings/helpers.h:422-422
Timestamp: 2025-12-23T06:50:41.142Z
Learning: In Bun's C++ bindings (src/bun.js/bindings/helpers.h and similar files), when returning an empty JSC::Identifier and a VM is accessible, prefer using `vm.propertyNames->emptyIdentifier` over constructing with `JSC::Identifier(JSC::Identifier::EmptyIdentifierFlag::EmptyIdentifier)`. The cached identifier from the VM's property names table is more efficient and consistent with WebKit upgrade patterns.
Applied to files:
src/bun.js/bindings/BunObject.cppsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use V8_UNIMPLEMENTED() macro for functions not yet implemented in V8 compatibility classes
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8-module/main.cpp : Create test functions in test/v8/v8-module/main.cpp that take FunctionCallbackInfo<Value> parameter, use the test V8 API, print results for comparison with Node.js, and return Undefined
Applied to files:
src/bun.js/bindings/BunObject.cpp
📚 Learning: 2025-11-14T16:07:01.064Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Applied to files:
packages/bun-types/bun.d.tstest/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-19T02:52:37.412Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/tsconfig.json:1-15
Timestamp: 2025-10-19T02:52:37.412Z
Learning: In the Bun repository, packages under packages/ (e.g., bun-otel) can follow a TypeScript-first pattern where package.json exports point directly to .ts files (not compiled .js files). Bun natively runs TypeScript, so consumers import .ts sources directly and receive full type information without needing compiled .d.ts declaration files. For such packages, adding "declaration": true or "outDir" in tsconfig.json is unnecessary and would break the export structure.
<!-- [remove_learning]
ceedde95-980e-4898-a2c6-40ff73913664
Applied to files:
packages/bun-types/bun.d.ts
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.
Applied to files:
src/bun.js/bindings/ANSIHelpers.h
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.
Applied to files:
src/string/immutable/visible.zigsrc/bun.js/bindings/wrapAnsi.cppsrc/string/immutable/wrap_ansi.zig
📚 Learning: 2025-10-16T17:32:03.074Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/install/PackageManager/PackageManagerOptions.zig:187-193
Timestamp: 2025-10-16T17:32:03.074Z
Learning: In Bun's codebase (particularly in files like src/install/PackageManager/PackageManagerOptions.zig), mixing bun.EnvVar.*.get() and bun.EnvVar.*.platformGet() for environment variable lookups is intentional and safe. The code is protected by compile-time platform checks (Environment.isWindows, etc.), and compilation will fail if the wrong function is used on the wrong platform. This pattern should not be flagged as a consistency issue.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2025-11-12T04:11:52.293Z
Learnt from: cirospaciari
Repo: oven-sh/bun PR: 24622
File: src/deps/uws/us_socket_t.zig:112-113
Timestamp: 2025-11-12T04:11:52.293Z
Learning: In Bun's Zig codebase, when passing u32 values to C FFI functions that expect c_uint parameters, no explicit intCast is needed because c_uint is equivalent to u32 on Bun's target platforms and Zig allows implicit coercion between equivalent types. This pattern is used consistently throughout src/deps/uws/us_socket_t.zig in functions like setTimeout, setLongTimeout, and setKeepalive.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2025-11-10T00:57:09.173Z
Learnt from: franciscop
Repo: oven-sh/bun PR: 24514
File: src/bun.js/api/crypto/PasswordObject.zig:86-101
Timestamp: 2025-11-10T00:57:09.173Z
Learning: In Bun's Zig codebase (PasswordObject.zig), when validating the parallelism parameter for Argon2, the upper limit is set to 65535 (2^16 - 1) rather than using `std.math.maxInt(u24)` because the latter triggers Zig's truncation limit checks. The value 65535 is a practical upper bound that avoids compiler issues while being sufficient for thread parallelism use cases.
Applied to files:
src/string/immutable/visible.zigsrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-04T02:04:43.094Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 22278
File: src/ast/E.zig:980-1003
Timestamp: 2025-09-04T02:04:43.094Z
Learning: In Bun's Zig codebase, `as(i32, u8_or_u16_value)` is sufficient for casting u8/u16 to i32 in comparison operations. `intCast` is not required in this context, and the current casting approach compiles successfully.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2025-10-16T21:24:52.779Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23710
File: src/crash_handler.zig:1415-1423
Timestamp: 2025-10-16T21:24:52.779Z
Learning: When a boolean EnvVar in src/envvars.zig is defined with a default value (e.g., `.default = false`), the `get()` method returns `bool` instead of `?bool`. This means you cannot distinguish between "environment variable not set" and "environment variable explicitly set to the default value". For opt-out scenarios where detection of explicit setting is needed (like `BUN_ENABLE_CRASH_REPORTING` on platforms where crash reporting defaults to enabled), either: (1) don't provide a default value so `get()` returns `?bool`, or (2) use the returned boolean directly instead of only checking if it's true.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2025-10-15T20:19:38.580Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 23680
File: cmake/targets/BuildBun.cmake:822-822
Timestamp: 2025-10-15T20:19:38.580Z
Learning: In the Bun codebase, FFI is compiled with tcc (TinyCC), which barely supports C99. The headers `src/bun.js/api/FFI.h` and `src/bun.js/api/ffi-stdbool.h` are only used for FFI compilation with tcc, not for the main Bun target. Therefore, C23 compatibility concerns (such as bool/true/false keyword conflicts) do not apply to these FFI headers.
Applied to files:
src/string/immutable/visible.zig
📚 Learning: 2026-01-05T16:32:07.551Z
Learnt from: alii
Repo: oven-sh/bun PR: 25474
File: src/bun.js/event_loop/Sigusr1Handler.zig:0-0
Timestamp: 2026-01-05T16:32:07.551Z
Learning: In Zig codebases (e.g., Bun), treat std.posix.sigaction as returning void and do not perform runtime error handling for its failure. The Zig standard library views sigaction failures as programmer errors (unreachable) because they only occur with invalid signals like SIGKILL/SIGSTOP. Apply this pattern across Zig files that call sigaction (e.g., crash_handler.zig, main.zig, filter_run.zig, process.zig) and ensure failures are not handled as recoverable errors; prefer reaching an explicit unreachable/compile-time assumption when such failures are detected.
Applied to files:
src/string/immutable/visible.zigsrc/string/immutable/wrap_ansi.zig
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-08T13:48:02.430Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: test/js/bun/tarball/extract.test.ts:107-111
Timestamp: 2025-10-08T13:48:02.430Z
Learning: In Bun's test runner, use `expect(async () => { await ... }).toThrow()` to assert async rejections. Unlike Jest/Vitest, Bun does not require `await expect(...).rejects.toThrow()` - the async function wrapper with `.toThrow()` is the correct pattern for async error assertions in Bun tests.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Ensure V8 API tests compare identical C++ code output between Node.js and Bun through the test suite validation process
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-09-20T00:58:38.042Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: test/js/valkey/valkey.test.ts:561-564
Timestamp: 2025-09-20T00:58:38.042Z
Learning: For test/js/valkey/valkey.test.ts, do not comment on synchronous throw assertions for async Redis methods (like ctx.redis.set(), ctx.redis.unsubscribe(), etc.) - Bun's Redis client implementation differs from Node.js and can throw synchronously even for async methods. The maintainer has explicitly requested to stop looking at this error pattern.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.test.ts?(x) : Avoid shell commands like `find` or `grep` in tests - use Bun's Glob and built-in tools instead
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-18T05:23:24.403Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/telemetry-server.test.ts:91-100
Timestamp: 2025-10-18T05:23:24.403Z
Learning: In the Bun codebase, telemetry tests (test/js/bun/telemetry-*.test.ts) should focus on telemetry API behavior: configure/disable/isEnabled, callback signatures and invocation, request ID correlation, and error handling. HTTP protocol behaviors like status code normalization (e.g., 200 with empty body → 204) should be tested in HTTP server tests (test/js/bun/http/), not in telemetry tests. Keep separation of concerns: telemetry tests verify the telemetry API contract; HTTP tests verify HTTP semantics.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures rather than tests themselves.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-09-20T03:39:41.770Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.770Z
Learning: Bun's test runner supports async describe callbacks, unlike Jest/Vitest where describe callbacks must be synchronous. The syntax `describe("name", async () => { ... })` is valid in Bun.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-25T17:20:19.041Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 24063
File: test/js/bun/telemetry/server-header-injection.test.ts:5-20
Timestamp: 2025-10-25T17:20:19.041Z
Learning: In the Bun telemetry codebase, tests are organized into two distinct layers: (1) Internal API tests in test/js/bun/telemetry/ use numeric InstrumentKind enum values to test Zig↔JS injection points and low-level integration; (2) Public API tests in packages/bun-otel/test/ use string InstrumentKind values ("http", "fetch", etc.) to test the public-facing BunSDK and instrumentation APIs. This separation allows internal tests to use efficient numeric enums for refactoring flexibility while the public API maintains a developer-friendly string-based interface.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-03T20:43:06.996Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/test/snapshot.zig:19-19
Timestamp: 2025-11-03T20:43:06.996Z
Learning: In Bun's Zig codebase, when storing JSValue objects in collections like ArrayList, use `jsc.Strong.Optional` (not raw JSValue). When adding values, wrap them with `jsc.Strong.Optional.create(value, globalThis)`. In cleanup code, iterate the collection calling `.deinit()` on each Strong.Optional item before calling `.deinit()` on the ArrayList itself. This pattern automatically handles GC protection. See examples in src/bun.js/test/ScopeFunctions.zig and src/bun.js/node/node_cluster_binding.zig.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use JSC intrinsics (prefixed with `$`) such as `$Array.from()`, `$isCallable()`, and `$newArrayWithSize()` for performance-critical operations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use JSC::WriteBarrier for heap-allocated references in V8 objects and implement visitChildren() for custom heap objects to support garbage collection
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to **/*.zig : In Zig code, be careful with allocators and use defer for cleanup
Applied to files:
src/string/immutable/wrap_ansi.zig
📚 Learning: 2025-09-02T18:09:21.647Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22227
File: src/allocators/allocation_scope.zig:79-85
Timestamp: 2025-09-02T18:09:21.647Z
Learning: In Bun's Zig codebase, prefer simple, direct error handling patterns. When an error type has only one variant (like FreeError with only NotAllocated), use a switch statement rather than generic catch blocks to leverage compile-time knowledge and avoid unnecessary complexity. Avoid duplicating function calls across error paths when a cleaner structure is possible.
Applied to files:
src/string/immutable/wrap_ansi.zig
📚 Learning: 2025-09-02T18:27:23.279Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 22227
File: src/collections/multi_array_list.zig:24-24
Timestamp: 2025-09-02T18:27:23.279Z
Learning: The `#allocator` syntax in bun's custom Zig implementation is valid and does not require quoting with @"#allocator". Private fields using the `#` prefix work correctly throughout the codebase without special quoting syntax.
Applied to files:
src/string/immutable/wrap_ansi.zig
📚 Learning: 2025-11-11T22:55:04.070Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24571
File: src/css/selectors/parser.zig:908-916
Timestamp: 2025-11-11T22:55:04.070Z
Learning: In oven-sh/bun, CSS serialization uses an arena allocator. In src/css/selectors/parser.zig, functions like PseudoClass.toCss and PseudoElement.toCss intentionally do not call deinit on std.Io.Writer.Allocating, scratch buffers, or css.Printer because dest.allocator is an arena and these temporaries are reclaimed when the CSS pass completes. Only debug-only paths (e.g., DeclarationBlock.DebugFmt in src/css/declaration.zig) may explicitly deinit.
Applied to files:
src/string/immutable/wrap_ansi.zig
🧬 Code graph analysis (3)
test/js/bun/util/wrapAnsi.npm.test.ts (1)
bench/snippets/wrap-ansi.js (3)
red(10-10)green(11-11)blue(12-12)
bench/snippets/wrap-ansi.js (1)
bench/runner.mjs (5)
summary(17-17)summary(17-17)bench(15-15)bench(15-15)run(7-13)
src/bun.js/bindings/wrapAnsi.cpp (1)
src/bun.js/api/BunObject.bind.ts (1)
stringWidth(30-36)
🪛 Biome (2.1.2)
test/js/bun/util/wrapAnsi.npm.test.ts
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 36-36: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
[error] 37-37: Unexpected control character in a regular expression.
Control characters are unusual and potentially incorrect inputs, so they are disallowed.
(lint/suspicious/noControlCharactersInRegex)
🪛 Cppcheck (2.19.0)
src/bun.js/bindings/wrapAnsi.cpp
[information] 42-42: Include file
(missingInclude)
[information] 52-52: Include file
(missingIncludeSystem)
[information] 59-59: Include file
(missingIncludeSystem)
[error] 66-66: failed to evaluate #if condition, undefined function-like macro invocation
(syntaxError)
🔇 Additional comments (28)
bench/package.json (1)
21-21: LGTM!The
wrap-ansidependency addition is appropriate for benchmarking the newBun.wrapAnsi()implementation against the npm package.src/bun.js/bindings/wrapAnsi.h (1)
1-9: LGTM!The header follows the standard Bun binding pattern for host functions. The
#pragma onceguard,root.hinclude, andJSC_DECLARE_HOST_FUNCTIONmacro usage are correct.bench/runner.mjs (1)
17-17: LGTM!The
summaryexport follows the existing pattern for re-exporting Mitata utilities and is used correctly by the new wrap-ansi benchmark.src/bun.js/bindings/stripANSI.cpp (2)
3-3: Good refactoring to centralize ANSI handling.Extracting the ANSI parsing utilities into
ANSIHelpers.hpromotes code reuse betweenstripANSIand the newwrapAnsiimplementation.
25-49: LGTM!The updated
stripANSIfunction correctly delegates to the sharedANSI::findEscapeCharacterandANSI::consumeANSIhelpers while maintaining the same logic flow and safety invariants.bench/snippets/wrap-ansi.js (4)
1-2: LGTM!Imports are correct. The
summaryexport fromrunner.mjsis properly used for grouping benchmark comparisons.
4-35: Well-designed test fixtures.The fixtures provide comprehensive coverage including plain text of varying lengths, ANSI SGR color codes, full-width Japanese characters, emoji, and OSC 8 hyperlinks. This ensures the benchmark exercises all key code paths in the wrap-ansi implementation.
37-101: Comprehensive benchmark coverage.The benchmark groups systematically compare npm and Bun implementations across all relevant scenarios: varying text lengths, ANSI colors, hard wrapping, full-width characters, emoji, hyperlinks, and trim options. The use of
summary()groups enables clear side-by-side performance comparisons.
103-103: LGTM!Correctly awaits the benchmark run.
src/bun.js/bindings/BunObject.cpp (2)
79-81: LGTM!The declaration follows the established pattern used for similar host functions in this file (e.g.,
jsFunctionBunStripANSIon line 79).
805-806: LGTM!The LUT entry is correctly added with arity 3 matching the API signature
(string, columns, options?), placed appropriately near the relatedstripANSIentry.test/js/bun/util/wrapAnsi.npm.test.ts (8)
1-23: LGTM!Proper MIT license attribution for the ported tests from the wrap-ansi npm package.
28-33: LGTM!ANSI color helpers are correctly implemented and consistent with the pattern used in the benchmark file.
49-73: LGTM!Tests correctly verify both the exact output with ANSI codes preserved and the line length constraints.
103-128: LGTM!Good coverage of hard wrapping behavior including word breaking and ANSI-only row handling.
164-171: Good documentation of implementation difference.The comment clearly explains why Bun's output differs from the npm package while producing visually equivalent results. This is helpful for future maintainers.
173-182: LGTM!Good coverage of Unicode handling including fullwidth characters and surrogate pairs.
225-243: LGTM!Excellent coverage of OSC 8 hyperlink wrapping, including complex scenarios with nested colors and emoji.
250-255: LGTM!Good coverage of CRLF normalization behavior.
src/string/immutable/wrap_ansi.zig (1)
59-70: Confirm whether space-only splitting is sufficient for Bun’s intended wrap semantics.The implementation splits words only on
' '(not tabs/Unicode whitespace/word boundaries). This matches the open question in the PR discussion; please confirm this is intended (and covered by the ported wrap-ansi tests for parity).Also applies to: 98-156
src/bun.js/bindings/ANSIHelpers.h (4)
1-8: LGTM! Clean header structure.The header uses
#pragma once, includes necessary dependencies (root.hfor common definitions,SIMDHelpers.hfor SIMD operations), and properly namespaces the utilities underBun::ANSI.
9-25: LGTM! Comprehensive escape character detection.The function correctly identifies all ANSI escape sequence introducers: ESC (0x1b) and the C1 control codes (CSI, OSC, DCS, SOS, PM, APC) per ECMA-48 specification.
27-56: LGTM! Well-designed SIMD optimization with correct fallback.The SIMD mask efficiently identifies candidate escape characters in the 0x10-0x1f and 0x90-0x9f ranges. The scalar fallback with
isEscapeCharacterthen precisely filters to actual escape introducers. This two-phase approach is a good performance trade-off.
58-187: LGTM! Robust ANSI sequence state machine.The state machine correctly handles:
- CSI sequences (ESC [ ... final byte 0x40-0x7E)
- OSC sequences with BEL, ST (0x9c), or ESC \ terminators
- DCS/SOS/PM/APC sequences requiring ST termination
- Two-byte XTerm sequences (ESC + space/punctuation + char)
- Consecutive escape sequences (via State::start loop-back)
The implementation properly returns
endfor unterminated sequences, which is safe behavior.src/string/immutable/visible.zig (1)
1158-1173: LGTM! UTF-16, Latin-1, and codepoint width exports are correct.
- UTF-16 export correctly passes through
ambiguous_as_wide- Latin-1 export appropriately omits the ambiguous parameter since Latin-1 (0x00-0xFF) contains no East Asian ambiguous-width characters
Bun__codepointWidthis a clean wrapper for per-codepoint width evaluationsrc/bun.js/bindings/wrapAnsi.cpp (3)
70-90: LGTM! UTF-16 surrogate pair decoding is correct.The function properly handles high surrogates (0xD800-0xDBFF) followed by low surrogates (0xDC00-0xDFFF) and correctly computes the supplementary plane codepoint.
427-502: LGTM! ANSI style preservation logic is sound.The function correctly:
- Tracks the current SGR code and OSC 8 hyperlink URL
- Closes styles before newlines and reopens them after
- Uses
getCloseCodefor proper SGR reset sequencesThe single-code tracking is a known limitation (see SGR parsing comment), but for common styling use cases this works well.
733-742: LGTM! Encoding-aware dispatch is correct.The binding correctly dispatches to the appropriate implementation based on string encoding (
is8Bit()for Latin1/UTF-8, otherwise UTF-16), preserving the original encoding characteristics through the wrapping process.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| /** | ||
| * Wrap a string to fit within the specified column width, preserving ANSI escape codes. | ||
| * | ||
| * This function is designed to be compatible with the popular "wrap-ansi" NPM package. | ||
| * | ||
| * Features: | ||
| * - Preserves ANSI escape codes (colors, styles) across line breaks | ||
| * - Supports SGR codes (colors, bold, italic, etc.) and OSC 8 hyperlinks | ||
| * - Respects Unicode display widths (full-width characters, emoji) | ||
| * - Word wrapping at word boundaries (configurable) | ||
| * | ||
| * @category Utilities | ||
| * | ||
| * @param input The string to wrap | ||
| * @param columns The maximum column width | ||
| * @param options Wrapping options | ||
| * @returns The wrapped string | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { wrapAnsi } from "bun"; | ||
| * | ||
| * console.log(wrapAnsi("hello world", 5)); | ||
| * // Output: | ||
| * // hello | ||
| * // world | ||
| * | ||
| * // Preserves ANSI colors across line breaks | ||
| * console.log(wrapAnsi("\u001b[31mhello world\u001b[0m", 5)); | ||
| * // Output: | ||
| * // \u001b[31mhello\u001b[0m | ||
| * // \u001b[31mworld\u001b[0m | ||
| * | ||
| * // Hard wrap long words | ||
| * console.log(wrapAnsi("abcdefghij", 3, { hard: true })); | ||
| * // Output: | ||
| * // abc | ||
| * // def | ||
| * // ghi | ||
| * // j | ||
| * ``` | ||
| */ |
There was a problem hiding this comment.
Document CRLF normalization in wrapAnsi() JSDoc.
PR notes say \r\n is normalized to \n, but the API docs don’t mention it.
Proposed diff
* Features:
* - Preserves ANSI escape codes (colors, styles) across line breaks
* - Supports SGR codes (colors, bold, italic, etc.) and OSC 8 hyperlinks
* - Respects Unicode display widths (full-width characters, emoji)
+ * - Normalizes CRLF (`\r\n`) to LF (`\n`)
* - Word wrapping at word boundaries (configurable)🤖 Prompt for AI Agents
In `@packages/bun-types/bun.d.ts` around lines 647 - 688, Update the JSDoc for the
wrapAnsi function to explicitly state that CRLF line endings ("\r\n") are
normalized to LF ("\n") before wrapping; mention that this normalization occurs
on the input string (parameter: input) and that wrapped output will use "\n" for
line breaks, and note that this normalization is performed before any other
options (e.g., hard wrapping, ANSI preservation) are applied so callers can rely
on consistent line-ending behavior from wrapAnsi.
| template<typename Char> | ||
| static std::optional<uint32_t> parseSgrCode(const Char* start, const Char* end) | ||
| { | ||
| if (end - start < 3 || start[0] != 0x1b || start[1] != '[') | ||
| return std::nullopt; | ||
|
|
||
| uint32_t code = 0; | ||
| for (const Char* it = start + 2; it < end; ++it) { | ||
| Char c = *it; | ||
| if (c >= '0' && c <= '9') { | ||
| code = code * 10 + (c - '0'); | ||
| } else if (c == 'm') { | ||
| return code; | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| return std::nullopt; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd src/bun.js/bindings && wc -l wrapAnsi.cppRepository: oven-sh/bun
Length of output: 71
🏁 Script executed:
cd src/bun.js/bindings && cat -n wrapAnsi.cpp | sed -n '340,380p'Repository: oven-sh/bun
Length of output: 1596
🏁 Script executed:
cd src/bun.js/bindings && grep -n "parseSgrCode" wrapAnsi.cpp | head -20Repository: oven-sh/bun
Length of output: 240
🏁 Script executed:
cd src/bun.js/bindings && cat -n wrapAnsi.cpp | sed -n '1,50p'Repository: oven-sh/bun
Length of output: 1912
🏁 Script executed:
cd src/bun.js/bindings && cat -n wrapAnsi.cpp | sed -n '445,475p'Repository: oven-sh/bun
Length of output: 1575
🏁 Script executed:
cd src/bun.js/bindings && cat -n wrapAnsi.cpp | sed -n '400,445p'Repository: oven-sh/bun
Length of output: 1437
🏁 Script executed:
cd src/bun.js/bindings && cat -n wrapAnsi.cpp | sed -n '475,510p'Repository: oven-sh/bun
Length of output: 1686
🏁 Script executed:
cd src/bun.js/bindings && grep -n "38;5\|38;2\|extended\|compound" wrapAnsi.cppRepository: oven-sh/bun
Length of output: 37
🏁 Script executed:
cd src/bun.js/bindings && cat -n wrapAnsi.cpp | sed -n '385,425p'Repository: oven-sh/bun
Length of output: 1096
SGR parsing doesn't handle extended color codes.
parseSgrCode only parses simple numeric codes and doesn't support compound SGR sequences like 38;5;n (256-color) or 38;2;r;g;b (true color). Additionally, getCloseCode only maps standard color codes (30–37, 40–47, 90–97, 100–107) and text attributes (1–9), leaving extended colors unsupported. This means extended color styles won't be properly preserved across wrapped lines.
Consider whether this limitation is acceptable for the use case or if more complete SGR parsing is needed for full ANSI style preservation.
🤖 Prompt for AI Agents
In `@src/bun.js/bindings/wrapAnsi.cpp` around lines 352 - 371, parseSgrCode
currently only returns a single simple numeric SGR and thus misses
compound/extended sequences (e.g. "38;5;n" and "38;2;r;g;b"), and getCloseCode
only maps basic color codes so extended colors are lost across wraps; update
parseSgrCode to parse full semicolon-separated SGR parameter lists (returning
either a vector/list of uint32_t or an encoded representation) and update
getCloseCode to accept and handle extended color params (at least preserving
"38;5;n" and "38;2;r;g;b" sequences when computing closing codes), or
normalize/propagate unknown parameter sequences unchanged; modify callers that
expect a single uint32_t (e.g., places using parseSgrCode's return) to handle
the new representation so extended color styles are preserved across wrapped
lines.
| pub fn wrapAnsi(allocator: std.mem.Allocator, input: []const u8, columns: usize, options: WrapOptions) ![]u8 { | ||
| if (columns == 0) { | ||
| return allocator.dupe(u8, input); | ||
| } | ||
|
|
||
| // Normalize \r\n to \n | ||
| var normalized: std.ArrayListUnmanaged(u8) = .{}; | ||
| defer normalized.deinit(allocator); | ||
|
|
||
| var i: usize = 0; | ||
| while (i < input.len) { | ||
| if (i + 1 < input.len and input[i] == '\r' and input[i + 1] == '\n') { | ||
| try normalized.append(allocator, '\n'); | ||
| i += 2; | ||
| } else { | ||
| try normalized.append(allocator, input[i]); | ||
| i += 1; | ||
| } | ||
| } | ||
|
|
||
| var result: std.ArrayListUnmanaged(u8) = .{}; | ||
| errdefer result.deinit(allocator); | ||
|
|
||
| // Split by newlines and process each line | ||
| var lines = std.mem.splitScalar(u8, normalized.items, '\n'); | ||
| var first_line = true; | ||
|
|
||
| while (lines.next()) |line| { | ||
| if (!first_line) { | ||
| try result.append(allocator, '\n'); | ||
| } | ||
| first_line = false; | ||
|
|
||
| try execLine(allocator, line, columns, options, &result); | ||
| } | ||
|
|
||
| return result.toOwnedSlice(allocator); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Avoid unconditional allocation/copy for CRLF normalization.
wrapAnsi() currently copies all input into normalized even when there’s no \r\n.
Proposed diff
pub fn wrapAnsi(allocator: std.mem.Allocator, input: []const u8, columns: usize, options: WrapOptions) ![]u8 {
if (columns == 0) {
return allocator.dupe(u8, input);
}
- // Normalize \r\n to \n
- var normalized: std.ArrayListUnmanaged(u8) = .{};
- defer normalized.deinit(allocator);
-
- var i: usize = 0;
- while (i < input.len) {
- if (i + 1 < input.len and input[i] == '\r' and input[i + 1] == '\n') {
- try normalized.append(allocator, '\n');
- i += 2;
- } else {
- try normalized.append(allocator, input[i]);
- i += 1;
- }
- }
+ // Normalize \r\n to \n (only allocate if needed)
+ const needs_normalize = std.mem.indexOf(u8, input, "\r\n") != null;
+ var normalized: std.ArrayListUnmanaged(u8) = .{};
+ defer normalized.deinit(allocator);
+ const normalized_slice: []const u8 = blk: {
+ if (!needs_normalize) break :blk input;
+ var i: usize = 0;
+ while (i < input.len) {
+ if (i + 1 < input.len and input[i] == '\r' and input[i + 1] == '\n') {
+ try normalized.append(allocator, '\n');
+ i += 2;
+ } else {
+ try normalized.append(allocator, input[i]);
+ i += 1;
+ }
+ }
+ break :blk normalized.items;
+ };
var result: std.ArrayListUnmanaged(u8) = .{};
errdefer result.deinit(allocator);
// Split by newlines and process each line
- var lines = std.mem.splitScalar(u8, normalized.items, '\n');
+ var lines = std.mem.splitScalar(u8, normalized_slice, '\n');| /// Wrap a word across multiple rows (character by character) | ||
| fn wrapWord(allocator: std.mem.Allocator, rows: *std.ArrayListUnmanaged(std.ArrayListUnmanaged(u8)), word: []const u8, columns: usize, options: WrapOptions) !void { | ||
| var is_inside_escape = false; | ||
| var is_inside_link_escape = false; | ||
|
|
||
| var vis = rowWidth(&rows.items[rows.items.len - 1], options.ambiguous_is_narrow); | ||
|
|
||
| var i: usize = 0; | ||
| while (i < word.len) { | ||
| var char_len: usize = 1; | ||
|
|
||
| if (word[i] == '\x1b') { | ||
| is_inside_escape = true; | ||
| // Check if it's a hyperlink escape | ||
| if (i + 4 < word.len and std.mem.startsWith(u8, word[i + 1 ..], "]8;;")) { | ||
| is_inside_link_escape = true; | ||
| } | ||
| } | ||
|
|
||
| const char_width = if (!is_inside_escape) blk: { | ||
| const result = getCharWidth(word[i..], !options.ambiguous_is_narrow); | ||
| char_len = result.bytes; | ||
| break :blk result.width; | ||
| } else 0; | ||
|
|
||
| if (!is_inside_escape and vis + char_width <= columns) { | ||
| try rows.items[rows.items.len - 1].appendSlice(allocator, word[i .. i + char_len]); | ||
| } else if (!is_inside_escape) { | ||
| try rows.append(allocator, .{}); | ||
| try rows.items[rows.items.len - 1].appendSlice(allocator, word[i .. i + char_len]); | ||
| vis = 0; | ||
| } else { | ||
| // Inside escape, just append | ||
| try rows.items[rows.items.len - 1].append(allocator, word[i]); | ||
| } | ||
|
|
||
| if (is_inside_escape) { | ||
| if (is_inside_link_escape) { | ||
| if (word[i] == 0x07) { // BEL | ||
| is_inside_escape = false; | ||
| is_inside_link_escape = false; | ||
| } | ||
| } else if (word[i] == 'm') { | ||
| is_inside_escape = false; | ||
| } | ||
| i += 1; | ||
| continue; | ||
| } | ||
|
|
||
| vis += char_width; | ||
|
|
||
| if (vis == columns and i + char_len < word.len) { | ||
| try rows.append(allocator, .{}); | ||
| vis = 0; | ||
| } | ||
|
|
||
| i += char_len; | ||
| } | ||
|
|
||
| // Handle edge case: last row is only ANSI escape codes | ||
| if (vis == 0 and rows.items[rows.items.len - 1].items.len > 0 and rows.items.len > 1) { | ||
| if (rows.pop()) |popped| { | ||
| var last_row = popped; | ||
| try rows.items[rows.items.len - 1].appendSlice(allocator, last_row.items); | ||
| last_row.deinit(allocator); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
wc -l src/string/immutable/wrap_ansi.zigRepository: oven-sh/bun
Length of output: 93
🏁 Script executed:
cat -n src/string/immutable/wrap_ansi.zig | head -100Repository: oven-sh/bun
Length of output: 3971
🏁 Script executed:
# Find the functions mentioned in the review
rg -n "fn parseSgrCode|fn joinRowsWithAnsiPreservation|fn parseOsc8Url|fn stringWidth" src/string/immutable/wrap_ansi.zigRepository: oven-sh/bun
Length of output: 395
🏁 Script executed:
sed -n '480,501p' src/string/immutable/wrap_ansi.zigRepository: oven-sh/bun
Length of output: 538
🏁 Script executed:
sed -n '502,550p' src/string/immutable/wrap_ansi.zigRepository: oven-sh/bun
Length of output: 1234
🏁 Script executed:
sed -n '366,403p' src/string/immutable/wrap_ansi.zigRepository: oven-sh/bun
Length of output: 1153
🏁 Script executed:
sed -n '404,479p' src/string/immutable/wrap_ansi.zigRepository: oven-sh/bun
Length of output: 2841
Multiple ANSI/OSC8 parsing issues limit real-world compatibility and can break wrap + style restoration.
The codebase has three interconnected problems:
-
parseSgrCode() cannot parse parametric SGR codes: It breaks on the first non-digit character (line 494), so
38;2;255;0;0m(24-bit color) captures only38, losing full color specification. -
Single escape_code state drops concurrent styles:
joinRowsWithAnsiPreservation()tracks only oneescape_codevariable (line 418). When bold (1) and color (31) are both active, only the last applied code is stored. At line breaks, the earlier style is lost—only the final code is restored. -
OSC8 terminator handling is inconsistent and incomplete:
parseOsc8Url()stops at ESC (\x1b, line 508) but ST isESC \(two bytes), making the check incompletestringWidth()andwrapWord()check only BEL (0x07, lines 377 and 248) for OSC8 termination, missing ST entirely- This inconsistency means OSC8 sequences terminated by ST (the ANSI standard) may not close properly, affecting width calculation and wrapping
Recommend:
- Extend
parseSgrCode()to track all semicolon-separated parameters, or restructure to store full SGR state - Track all active SGR codes (not just the last one) in
joinRowsWithAnsiPreservation()to replay full style context after line breaks - Consistently handle both BEL and proper ST (
\x1b\) acrosswrapWord(),stringWidth(), andparseOsc8Url()
🤖 Prompt for AI Agents
In `@src/string/immutable/wrap_ansi.zig` around lines 219 - 286, parseSgrCode,
joinRowsWithAnsiPreservation, parseOsc8Url, stringWidth, and wrapWord have three
related bugs: SGR parsing stops at first non-digit, only a single escape_code is
tracked losing concurrent styles, and OSC8 termination only checks BEL not the
ST sequence. Fix parseSgrCode to collect the full semicolon-separated parameter
list (e.g., parse until the final 'm' and return all params/bytes, not just the
first numeric token), change joinRowsWithAnsiPreservation to track active SGR
state as a collection (stack/set/list of codes) and replay the entire
active-code sequence when restoring styles after a line break, and make OSC8
handling consistent by recognizing both BEL (0x07) and the ST terminator (ESC
followed by '\\') in parseOsc8Url, stringWidth, and wrapWord so escapes
terminate correctly and width/wrapping and style restoration work for both BEL-
and ST-terminated OSC8 sequences.
Address review comment: Replace std::vector with WTF::Vector throughout
the wrapAnsi implementation for consistency with WebKit code style.
API changes applied:
- empty() -> isEmpty()
- push_back() -> append()
- data() -> span().data()
- reserve() -> reserveCapacity()
- back() -> last()
- pop_back() -> removeLast()
- resize() -> shrink()
- insert(end(), begin, end) -> appendVector() / append(span{})
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 3
Claude-Permission-Prompts: 2
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/bun.js/bindings/wrapAnsi.cpp`:
- Around line 232-243: The code in wrapWord calls decodeUTF8 when
sizeof(Char)==1, which mis-decodes Latin1 bytes; implement a decodeLatin1(const
Latin1Character*, size_t, size_t&) that returns the byte value as a Unicode
codepoint and sets outLen=1, then change the branch in wrapWord to call
decodeLatin1 when Char is Latin1Character (otherwise keep decodeUTF8),
preserving charLen and passing the resulting codepoint to getVisibleWidth
(respecting options.ambiguousIsNarrow); ensure decodeLatin1 is used only for the
Latin1Character type and does not rely on available length.
♻️ Duplicate comments (5)
src/bun.js/bindings/wrapAnsi.cpp (5)
35-68: Missing continuation byte validation in UTF-8 decoding.This issue was flagged previously and remains unaddressed. The decoding assembles multi-byte sequences without validating that continuation bytes have the correct
10xxxxxxpattern. Malformed UTF-8 input could produce incorrect codepoints instead of the replacement character.
150-204: Incomplete OSC sequence terminator handling intrimLeadingSpaces.This was flagged previously. The escape sequence detection at lines 163-164 only handles BEL (
0x07) andmterminators, but OSC sequences can also be terminated by ST (ESC \or0x9C). This affects multiple functions in the file that use the same pattern.
352-373: SGR parsing limitation: extended color codes not supported.This limitation was noted in previous reviews.
parseSgrCodeonly parses simple numeric codes and doesn't support compound SGR sequences like38;5;n(256-color) or38;2;r;g;b(true color). Extended color styles won't be properly preserved across wrapped lines.This may be acceptable for the initial implementation given the complexity involved.
703-708: Handle negative column values.This was flagged previously and remains unaddressed.
toIntegerOrInfinitycan return negative values or infinity, which when cast tosize_tcauses unsigned wraparound or undefined behavior.Suggested fix
// Get columns size_t columns = 0; if (!columnsValue.isUndefined()) { - columns = static_cast<size_t>(columnsValue.toIntegerOrInfinity(globalObject)); + double colsDouble = columnsValue.toIntegerOrInfinity(globalObject); RETURN_IF_EXCEPTION(scope, {}); + if (colsDouble > 0 && std::isfinite(colsDouble)) + columns = static_cast<size_t>(colsDouble); }
710-735: Consider caching property identifiers for options parsing.This was noted in previous reviews. Creating
Identifier::fromStringfor each option lookup on every call is slightly inefficient. For a frequently-called API, consider using cached identifiers.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/bun.js/bindings/wrapAnsi.cpp
🧰 Additional context used
📓 Path-based instructions (1)
src/bun.js/bindings/**/*.cpp
📄 CodeRabbit inference engine (CLAUDE.md)
src/bun.js/bindings/**/*.cpp: Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Define properties using HashTableValue arrays in C++ JavaScript class bindings
Add iso subspaces for C++ classes with fields in JavaScript class bindings
Cache structures in ZigGlobalObject for JavaScript class bindings
Files:
src/bun.js/bindings/wrapAnsi.cpp
🧠 Learnings (23)
📓 Common learnings
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for C++ classes with fields in JavaScript class bindings
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Create classes in three parts in C++ when there is a public constructor: Foo (JSDestructibleObject), FooPrototype (JSNonFinalObject), and FooConstructor (InternalFunction)
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Create V8 class implementations with .cpp extension following the pattern V8ClassName.cpp that include the header, v8_compatibility_assertions.h, use ASSERT_V8_TYPE_LAYOUT_MATCHES macro, and implement methods using isolate->currentHandleScope()->createLocal<T>() for handle creation
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-20T05:35:57.318Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: src/bun.js/bindings/headers.h:729-731
Timestamp: 2025-09-20T05:35:57.318Z
Learning: symbols.txt in the Bun codebase is specifically for V8 API mangled symbols (without leading underscore), not for general Bun host functions declared with BUN_DECLARE_HOST_FUNCTION. Host functions are handled through different build mechanisms.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Create V8 class headers with .h extension following the pattern V8ClassName.h that include pragma once, v8.h, V8Local.h, V8Isolate.h, and declare classes extending from Data with BUN_EXPORT static methods
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add symbol names with leading underscore and semicolons in braces to src/symbols.dyn for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-10T00:57:09.173Z
Learnt from: franciscop
Repo: oven-sh/bun PR: 24514
File: src/bun.js/api/crypto/PasswordObject.zig:86-101
Timestamp: 2025-11-10T00:57:09.173Z
Learning: In Bun's Zig codebase (PasswordObject.zig), when validating the parallelism parameter for Argon2, the upper limit is set to 65535 (2^16 - 1) rather than using `std.math.maxInt(u24)` because the latter triggers Zig's truncation limit checks. The value 65535 is a practical upper bound that avoids compiler issues while being sufficient for thread parallelism use cases.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-11T02:11:47.024Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25462
File: src/ast/visitExpr.zig:1644-1695
Timestamp: 2025-12-11T02:11:47.024Z
Learning: In Bun's bundler feature flag implementation (src/ast/visitExpr.zig), the validation for feature() flag names intentionally only rejects UTF-16 strings (checking `is_utf16`) while allowing UTF-8 strings, even though the error message says "must be an ASCII string". This is the intended behavior and should not be changed to enforce strict ASCII validation.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-30T21:52:04.707Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-23T06:50:41.142Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25429
File: src/bun.js/bindings/helpers.h:422-422
Timestamp: 2025-12-23T06:50:41.142Z
Learning: In Bun's C++ bindings (src/bun.js/bindings/helpers.h and similar files), when returning an empty JSC::Identifier and a VM is accessible, prefer using `vm.propertyNames->emptyIdentifier` over constructing with `JSC::Identifier(JSC::Identifier::EmptyIdentifierFlag::EmptyIdentifier)`. The cached identifier from the VM's property names table is more efficient and consistent with WebKit upgrade patterns.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-16T00:21:32.179Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-16T00:21:32.179Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JavaScript class bindings
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-03T20:43:06.996Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/test/snapshot.zig:19-19
Timestamp: 2025-11-03T20:43:06.996Z
Learning: In Bun's Zig codebase, when storing JSValue objects in collections like ArrayList, use `jsc.Strong.Optional` (not raw JSValue). When adding values, wrap them with `jsc.Strong.Optional.create(value, globalThis)`. In cleanup code, iterate the collection calling `.deinit()` on each Strong.Optional item before calling `.deinit()` on the ArrayList itself. This pattern automatically handles GC protection. See examples in src/bun.js/test/ScopeFunctions.zig and src/bun.js/node/node_cluster_binding.zig.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use JSC intrinsics (prefixed with `$`) such as `$Array.from()`, `$isCallable()`, and `$newArrayWithSize()` for performance-critical operations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use JSC::WriteBarrier for heap-allocated references in V8 objects and implement visitChildren() for custom heap objects to support garbage collection
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-07T08:20:47.215Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22258
File: src/cli/test_command.zig:1258-1281
Timestamp: 2025-09-07T08:20:47.215Z
Learning: For Bun's test line filtering feature, the parseFileLineArg function should only handle the specific cases of "file:line" and "file:line:col" formats. It should not try to be overly tolerant of other patterns, as components like ":col" or other non-numeric segments could legitimately be part of filenames. The current conservative approach that checks for numeric segments in expected positions is appropriate.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use localToJSValue() to convert V8 handles to JSC values and perform JSC operations within V8 method implementations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
🧬 Code graph analysis (1)
src/bun.js/bindings/wrapAnsi.cpp (1)
src/bun.js/api/BunObject.bind.ts (1)
stringWidth(30-36)
🪛 Cppcheck (2.19.0)
src/bun.js/bindings/wrapAnsi.cpp
[information] 42-42: Include file
(missingInclude)
[information] 52-52: Include file
(missingIncludeSystem)
[information] 59-59: Include file
(missingIncludeSystem)
[error] 66-66: failed to evaluate #if condition, undefined function-like macro invocation
(syntaxError)
🔇 Additional comments (7)
src/bun.js/bindings/wrapAnsi.cpp (7)
1-14: LGTM - Includes and extern declarations are well-organized.The header includes and extern declarations for the Zig visible-width functions are appropriate.
70-90: LGTM - UTF-16 decoding correctly handles surrogate pairs.The surrogate pair detection and codepoint calculation are correct.
286-346: LGTM - Trailing space trimming with ANSI preservation.The logic correctly identifies the last visible content and preserves ANSI codes. The same OSC terminator limitation mentioned earlier applies here as well.
429-505: LGTM - ANSI style preservation across line breaks.The logic correctly closes SGR styles and hyperlinks before newlines and reopens them after, ensuring terminal compatibility. The order of operations is correct.
511-615: LGTM - Line processing logic handles various wrap modes correctly.The implementation properly handles trim, hard wrap, and word wrap modes with appropriate row management.
621-677: LGTM - Main implementation with proper normalization and line processing.The CRLF normalization and per-line processing with ANSI preservation are implemented correctly. Uses
WTF::Vectoras recommended.
739-743: Encoding dispatch needs to account for Latin1 vs UTF-8.When
view->is8Bit()is true, the data is Latin1-encoded. The current implementation routes this throughwrapAnsiImpl<Latin1Character>, but internally calls UTF-8 decoding/width functions. Ensure the Latin1 encoding issues noted earlier are addressed before this dispatch is correct.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
|
This shouldn't be doing UTF-8 decoding. |
JSC 8-bit strings are Latin1-encoded, not UTF-8. The code was incorrectly:
1. Calling Bun__visibleWidthExcludeANSI_utf8 for Latin1 data
2. Using UTF-8 multi-byte decoding on Latin1 characters
Fix by:
- Using Bun__visibleWidthExcludeANSI_latin1 for 8-bit strings
- Handling Latin1 inline (each byte is one char, direct 1:1 mapping to U+0000-U+00FF)
- Removing unused UTF-8 decoding utilities
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
|
ah sorry tests were not successful |
CSI sequences (like cursor movement ESC[1D) end with bytes in 0x40-0x7E
range, not just 'm'. Added proper detection for all CSI terminators
in both wrapWord and trimRowTrailingSpaces functions.
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 1
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
| Vector<Char> normalized; | ||
| normalized.reserveCapacity(input.size()); | ||
|
|
||
| for (size_t i = 0; i < input.size(); ++i) { |
There was a problem hiding this comment.
this should really use WTF::find to find these newlines
There was a problem hiding this comment.
This should also use WTF::find but could be done as a follow-up.
| @@ -0,0 +1,606 @@ | |||
| /// wrap-ansi compatible text wrapping with ANSI escape code preservation. | |||
There was a problem hiding this comment.
Is this file still used?
Jarred-Sumner
left a comment
There was a problem hiding this comment.
- I think probably can delete src/string/immutable/wrap_ansi.zig
- The CodeRabbit comments about SGR parsing and width tracking, and integer validation looks real
The C++ implementation is now used instead.
Negative values and Infinity would cause incorrect behavior due to
unsigned integer wraparound. Now they are treated as 0 (return input
unchanged).
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 2
Claude-Permission-Prompts: 1
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
When a character starts a new line, the visible width counter was reset
to 0 instead of the character's width. This could cause incorrect
wrapping for subsequent characters.
Also restructured the code to avoid double-counting width by moving
the vis += charWidth into the appropriate branch.
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
Per review feedback, use WebKit's utility function instead of manual
iteration for better consistency with codebase patterns.
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
Document that extended color sequences are correctly preserved across
line wraps without close/reopen, matching npm wrap-ansi behavior.
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 0
Claude-Permission-Prompts: 0
Claude-Escapes: 0
Claude-Plan:
<claude-plan>
# wrapAnsi関数の実装計画
## 概要
BunにNPMの`wrap-ansi`ライブラリと互換性のある`Bun.wrapAnsi()`関数を追加する。
## APIシグネチャ
```typescript
Bun.wrapAnsi(string: string, columns: number, options?: WrapAnsiOptions): string
interface WrapAnsiOptions {
hard?: boolean; // default: false - 長い単語を強制分割
wordWrap?: boolean; // default: true - 単語境界で折り返し
trim?: boolean; // default: true - 行の先頭・末尾の空白を削除
ambiguousIsNarrow?: boolean; // default: true - 曖昧幅文字をnarrow(幅1)として扱う
}
```
## 実装アプローチ
`stringWidth`と同様にZig + bindgenシステムを使用。既存のANSI/文字幅計算コードを再利用。
## 変更するファイル
### 1. バインディング定義
**ファイル**: `src/bun.js/api/BunObject.bind.ts`
```typescript
export const WrapAnsiOptions = t.dictionary({
hard: t.boolean.default(false),
wordWrap: t.boolean.default(true),
trim: t.boolean.default(true),
ambiguousIsNarrow: t.boolean.default(true),
});
export const wrapAnsi = fn({
args: {
global: t.globalObject,
str: t.DOMString.default(""),
columns: t.usize,
opts: WrapAnsiOptions.default({}),
},
ret: t.any, // JSValue (String)
});
```
### 2. Zig実装
**ファイル**: `src/bun.js/api/BunObject.zig`
エントリーポイント関数を追加。コア実装を呼び出す。
### 3. コア実装(新規)
**ファイル**: `src/string/immutable/wrap_ansi.zig`
主要な処理:
1. 改行で分割し、各行を処理
2. スペースで単語に分割し、表示幅を計算(ANSI除外)
3. 列幅に基づいて折り返し
4. ANSIエスケープコードの追跡(SGR、OSC 8ハイパーリンク)
5. 行跨ぎでスタイルを閉じて再開
既存コードの再利用:
- `src/string/immutable/visible.zig` - 表示幅計算
- ANSI CSI/OSCパースロジック
### 4. テスト
**ファイル**: `test/js/bun/util/wrapAnsi.test.ts`
NPMライブラリとの比較テスト:
- 基本的な折り返し
- ANSIカラー付き文字列
- hard/wordWrap/trimオプション
- 全角文字、絵文字、サロゲートペア
- ハイパーリンク
### 5. 型定義
**ファイル**: `packages/bun-types/bun.d.ts`
TypeScript型定義を追加。
## 実装ステップ
### Step 1: bindgen定義追加
`BunObject.bind.ts`にオプションと関数シグネチャを追加。
### Step 2: コア実装
`wrap_ansi.zig`に以下を実装:
- `wrapAnsi()` - メイン関数
- `wrapLine()` - 1行の処理
- `wrapWord()` - 長い単語の分割(hardモード)
- ANSIスタイル追跡構造体
- SGRコードの開始→終了マッピング
### Step 3: BunObjectへの統合
`BunObject.zig`にエントリーポイント関数を追加。
### Step 4: テスト作成
NPMとの互換性テストを作成。
### Step 5: 型定義追加
`bun.d.ts`にインターフェースを追加。
## SGRコードマッピング
行跨ぎでスタイルを復元するために必要:
| 開始コード | 終了コード | 説明 |
|-----------|-----------|------|
| 1 | 22 | bold |
| 2 | 22 | dim |
| 3 | 23 | italic |
| 4 | 24 | underline |
| 7 | 27 | inverse |
| 8 | 28 | hidden |
| 9 | 29 | strikethrough |
| 30-37 | 39 | foreground color |
| 40-47 | 49 | background color |
| 90-97 | 39 | bright foreground |
| 100-107 | 49 | bright background |
## 検証方法
```bash
# テスト実行
bun bd test test/js/bun/util/wrapAnsi.test.ts
# システムBunでは失敗することを確認
USE_SYSTEM_BUN=1 bun test test/js/bun/util/wrapAnsi.test.ts
# 手動テスト
bun bd -e "console.log(Bun.wrapAnsi('hello world', 5))"
```
## 注意事項
- 全角文字は幅2としてカウント
- ANSIエスケープは幅0
- Unicode正規化(normalize())を適用
- `\r\n`を`\n`に正規化
</claude-plan>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/bun.js/bindings/wrapAnsi.cpp`:
- Around line 56-70: The Latin1 branch in stringWidth ignores the
ambiguousIsNarrow flag and calls Bun__visibleWidthExcludeANSI_latin1 without
passing ambiguity, causing width mismatches with wrapWord/getVisibleWidth;
update stringWidth to forward ambiguousIsNarrow to the Latin1 width helper (or
replace the Latin1 path with a per-codepoint loop that calls getVisibleWidth for
each U+0000–U+00FF codepoint) and adjust the comment to remove the incorrect
claim that Latin1 has no ambiguous-width characters; changes should touch
stringWidth, the call site to Bun__visibleWidthExcludeANSI_latin1, and the
comment lines referenced.
♻️ Duplicate comments (1)
src/bun.js/bindings/wrapAnsi.cpp (1)
264-271: OSC ST terminators are not recognizedOSC sequences can terminate with ST (ESC
\or 0x9C).isAnsiEscapeTerminatoronly treats BEL as the OSC terminator (Line 268–270), which can leave the parser in escape mode and drop visible text for OSC 8 hyperlinks that use ST. Please handle ST (and the ESC\sequence) in the escape-state logic.🐛 Suggested fix (partial)
- if (isOscSequence) - return c == 0x07; // BEL terminates OSC sequences + if (isOscSequence) + return c == 0x07 || c == 0x9c; // BEL or ST terminates OSC sequencesYou’ll still need to detect the two-byte ESC
\sequence in the loops that track escape state (e.g.,wrapWord,trimLeadingSpaces,trimRowTrailingSpaces).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/bun.js/bindings/wrapAnsi.cpptest/js/bun/util/wrapAnsi.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}
📄 CodeRabbit inference engine (test/CLAUDE.md)
test/**/*.test.{ts,js,jsx,tsx,mjs,cjs}: Usebun bd test <...test file>to run tests with compiled code changes. Do not usebun testas it will not include your changes.
Usebun:testfor files ending in*.test.{ts,js,jsx,tsx,mjs,cjs}. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, usebun bd <file>instead ofbun bd test <file>since they expect exit code 0.
Do not set a timeout on tests. Bun already has timeouts built-in.
Files:
test/js/bun/util/wrapAnsi.test.ts
test/**/*.test.ts
📄 CodeRabbit inference engine (CLAUDE.md)
test/**/*.test.ts: Use Bun's Jest-compatible test runner with proper test fixtures and imports fromharness
Always useport: 0when binding to ports in tests - never hardcode ports or use custom random port functions
UsenormalizeBunSnapshotto normalize snapshot output in tests
Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output as these will never fail in CI
UsetempDirfromharnessto create temporary directories in tests - do not usetmpdirSyncorfs.mkdtempSync
In spawned process tests, use expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0) for more useful error messages
Do not usesetTimeoutin tests - await the condition to be met instead, as you are testing the CONDITION not TIME PASSING
Files:
test/js/bun/util/wrapAnsi.test.ts
src/bun.js/bindings/*.cpp
📄 CodeRabbit inference engine (CLAUDE.md)
When implementing JavaScript classes in C++, create three classes if there's a public constructor: class inheriting from JSDestructibleObject, a Prototype class, and a Constructor class
Files:
src/bun.js/bindings/wrapAnsi.cpp
🧠 Learnings (44)
📓 Common learnings
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add corresponding test cases to test/v8/v8.test.ts using checkSameOutput() function to compare Node.js and Bun output
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-15T03:22:50.711Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T03:22:50.711Z
Learning: Applies to test/**/*.test.ts : Use Bun's Jest-compatible test runner with proper test fixtures and imports from `harness`
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun bd test <...test file>` to run tests with compiled code changes. Do not use `bun test` as it will not include your changes.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Use `bun:test` for files ending in `*.test.{ts,js,jsx,tsx,mjs,cjs}`. For test files without .test extension in test/js/node/test/{parallel,sequential}/*.js, use `bun bd <file>` instead of `bun bd test <file>` since they expect exit code 0.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-19T02:44:46.354Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: packages/bun-otel/context-propagation.test.ts:1-1
Timestamp: 2025-10-19T02:44:46.354Z
Learning: In the Bun repository, standalone packages under packages/ (e.g., bun-vscode, bun-inspector-protocol, bun-plugin-yaml, bun-plugin-svelte, bun-debug-adapter-protocol, bun-otel) co-locate their tests with package source code using *.test.ts files. This follows standard npm/monorepo patterns. The test/ directory hierarchy (test/js/bun/, test/cli/, test/js/node/) is reserved for testing Bun's core runtime APIs and built-in functionality, not standalone packages.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-15T03:22:50.711Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T03:22:50.711Z
Learning: Applies to test/**/*.test.ts : Use `normalizeBunSnapshot` to normalize snapshot output in tests
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-11-14T16:07:01.064Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 24719
File: docs/bundler/executables.mdx:527-560
Timestamp: 2025-11-14T16:07:01.064Z
Learning: In the Bun repository, certain bundler features like compile with code splitting (--compile --splitting) are CLI-only and not supported in the Bun.build() JavaScript API. Tests for CLI-only features use backend: "cli" flag (e.g., test/bundler/bundler_compile_splitting.test.ts). The CompileBuildConfig interface correctly restricts these with splitting?: never;. When documenting CLI-only bundler features, add a note clarifying they're not available via the programmatic API.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-26T01:32:04.844Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 24082
File: test/cli/test/coverage.test.ts:60-112
Timestamp: 2025-10-26T01:32:04.844Z
Learning: In the Bun repository test files (test/cli/test/*.test.ts), when spawning Bun CLI commands with Bun.spawnSync for testing, prefer using stdio: ["inherit", "inherit", "inherit"] to inherit stdio streams rather than piping them.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*.test.{ts,js,jsx,tsx,mjs,cjs} : Do not set a timeout on tests. Bun already has timeouts built-in.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-08T13:48:02.430Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 23373
File: test/js/bun/tarball/extract.test.ts:107-111
Timestamp: 2025-10-08T13:48:02.430Z
Learning: In Bun's test runner, use `expect(async () => { await ... }).toThrow()` to assert async rejections. Unlike Jest/Vitest, Bun does not require `await expect(...).rejects.toThrow()` - the async function wrapper with `.toThrow()` is the correct pattern for async error assertions in Bun tests.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-15T03:22:50.711Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T03:22:50.711Z
Learning: Applies to test/**/*.test.ts : Never write tests that check for 'panic', 'uncaught exception', or similar strings in test output as these will never fail in CI
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-09-30T22:53:19.887Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 23117
File: src/bun.js/test/snapshot.zig:265-276
Timestamp: 2025-09-30T22:53:19.887Z
Learning: In Bun's snapshot testing (src/bun.js/test/snapshot.zig), multiple inline snapshots at the same line and column (same call position) must have identical values. However, multiple inline snapshots on the same line at different columns are allowed to have different values. The check is position-specific (line+col), not line-wide.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-15T03:22:50.711Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T03:22:50.711Z
Learning: Applies to test/**/*.test.ts : In spawned process tests, use expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0) for more useful error messages
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Ensure V8 API tests compare identical C++ code output between Node.js and Bun through the test suite validation process
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-09-20T00:58:38.042Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 22568
File: test/js/valkey/valkey.test.ts:561-564
Timestamp: 2025-09-20T00:58:38.042Z
Learning: For test/js/valkey/valkey.test.ts, do not comment on synchronous throw assertions for async Redis methods (like ctx.redis.set(), ctx.redis.unsubscribe(), etc.) - Bun's Redis client implementation differs from Node.js and can throw synchronously even for async methods. The maintainer has explicitly requested to stop looking at this error pattern.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-18T05:23:24.403Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 23798
File: test/js/bun/telemetry-server.test.ts:91-100
Timestamp: 2025-10-18T05:23:24.403Z
Learning: In the Bun codebase, telemetry tests (test/js/bun/telemetry-*.test.ts) should focus on telemetry API behavior: configure/disable/isEnabled, callback signatures and invocation, request ID correlation, and error handling. HTTP protocol behaviors like status code normalization (e.g., 200 with empty body → 204) should be tested in HTTP server tests (test/js/bun/http/), not in telemetry tests. Keep separation of concerns: telemetry tests verify the telemetry API contract; HTTP tests verify HTTP semantics.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-05T23:04:01.518Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/CLAUDE.md:0-0
Timestamp: 2026-01-05T23:04:01.518Z
Learning: Applies to test/**/*-fixture.ts : Test files that spawn Bun processes should end in `*-fixture.ts` to identify them as test fixtures rather than tests themselves.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2026-01-14T21:08:10.438Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: test/js/node/test/parallel/CLAUDE.md:0-0
Timestamp: 2026-01-14T21:08:10.438Z
Learning: These are Node.js compatibility tests not written by Bun and cannot be modified
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-11-06T00:58:23.965Z
Learnt from: markovejnovic
Repo: oven-sh/bun PR: 24417
File: test/js/bun/spawn/spawn.test.ts:903-918
Timestamp: 2025-11-06T00:58:23.965Z
Learning: In Bun test files, `await using` with spawn() is appropriate for long-running processes that need guaranteed cleanup on scope exit or when explicitly testing disposal behavior. For short-lived processes that exit naturally (e.g., console.log scripts), the pattern `const proc = spawn(...); await proc.exited;` is standard and more common, as evidenced by 24 instances vs 4 `await using` instances in test/js/bun/spawn/spawn.test.ts.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-30T21:52:04.707Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24212
File: src/cli/publish_command.zig:782-788
Timestamp: 2025-10-30T21:52:04.707Z
Learning: In the Bun codebase (oven-sh/bun), `enable_ansi_colors` flags are used to gate both ANSI color codes and Unicode box-drawing characters/emoji. This is the established pattern across the codebase.
Applied to files:
test/js/bun/util/wrapAnsi.test.tssrc/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-20T03:39:41.770Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: test/regression/issue/21830.fixture.ts:14-63
Timestamp: 2025-09-20T03:39:41.770Z
Learning: Bun's test runner supports async describe callbacks, unlike Jest/Vitest where describe callbacks must be synchronous. The syntax `describe("name", async () => { ... })` is valid in Bun.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-09-24T05:48:59.872Z
Learnt from: nektro
Repo: oven-sh/bun PR: 22806
File: scripts/runner.node.mjs:687-689
Timestamp: 2025-09-24T05:48:59.872Z
Learning: In the Bun codebase, the `startGroup` utility function in scripts/runner.node.mjs automatically closes any previously open group when called, so `startGroup("End")` correctly closes the final test group and keeps subsequent output ungrouped.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-10-25T17:20:19.041Z
Learnt from: theshadow27
Repo: oven-sh/bun PR: 24063
File: test/js/bun/telemetry/server-header-injection.test.ts:5-20
Timestamp: 2025-10-25T17:20:19.041Z
Learning: In the Bun telemetry codebase, tests are organized into two distinct layers: (1) Internal API tests in test/js/bun/telemetry/ use numeric InstrumentKind enum values to test Zig↔JS injection points and low-level integration; (2) Public API tests in packages/bun-otel/test/ use string InstrumentKind values ("http", "fetch", etc.) to test the public-facing BunSDK and instrumentation APIs. This separation allows internal tests to use efficient numeric enums for refactoring flexibility while the public API maintains a developer-friendly string-based interface.
Applied to files:
test/js/bun/util/wrapAnsi.test.ts
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Add BUN_EXPORT visibility attribute to all public V8 API functions to ensure proper symbol export across platforms
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : For each new V8 C++ method, add both GCC/Clang and MSVC mangled symbol names to the V8API struct in src/napi/napi.zig using extern fn declarations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:37:47.899Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/AGENTS.md:0-0
Timestamp: 2025-11-24T18:37:47.899Z
Learning: Applies to src/bun.js/bindings/v8/**/<UNKNOWN> : <UNKNOWN>
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-20T05:35:57.318Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 22534
File: src/bun.js/bindings/headers.h:729-731
Timestamp: 2025-09-20T05:35:57.318Z
Learning: symbols.txt in the Bun codebase is specifically for V8 API mangled symbols (without leading underscore), not for general Bun host functions declared with BUN_DECLARE_HOST_FUNCTION. Host functions are handled through different build mechanisms.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add symbol names with leading underscore and semicolons in braces to src/symbols.dyn for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add symbol names without leading underscore to src/symbols.txt for each new V8 API method
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2026-01-15T03:22:50.711Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T03:22:50.711Z
Learning: C++ code for JavaScriptCore bindings should be placed in `src/bun.js/bindings/*.cpp`
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Create V8 class headers with .h extension following the pattern V8ClassName.h that include pragma once, v8.h, V8Local.h, V8Isolate.h, and declare classes extending from Data with BUN_EXPORT static methods
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-01T21:59:54.571Z
Learnt from: taylordotfish
Repo: oven-sh/bun PR: 23169
File: src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h:47-74
Timestamp: 2025-10-01T21:59:54.571Z
Learning: In the new bindings generator (bindgenv2) for `src/bun.js/bindings/webcore/JSDOMConvertEnumeration.h`, the context-aware enumeration conversion overloads intentionally use stricter validation (requiring `value.isString()` without ToString coercion), diverging from Web IDL semantics. This is a design decision documented in comments.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Create V8 class implementations with .cpp extension following the pattern V8ClassName.cpp that include the header, v8_compatibility_assertions.h, use ASSERT_V8_TYPE_LAYOUT_MATCHES macro, and implement methods using isolate->currentHandleScope()->createLocal<T>() for handle creation
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-10-24T10:43:09.398Z
Learnt from: fmguerreiro
Repo: oven-sh/bun PR: 23774
File: src/install/PackageManager/updatePackageJSONAndInstall.zig:548-548
Timestamp: 2025-10-24T10:43:09.398Z
Learning: In Bun's Zig codebase, the `as(usize, intCast(...))` cast pattern triggers a Zig compiler bug that causes compilation to hang indefinitely when used in complex control flow contexts (loops + short-circuit operators + optional unwrapping). Avoid this pattern and use simpler alternatives like just `intCast(...)` if type casting is necessary.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-10T00:57:09.173Z
Learnt from: franciscop
Repo: oven-sh/bun PR: 24514
File: src/bun.js/api/crypto/PasswordObject.zig:86-101
Timestamp: 2025-11-10T00:57:09.173Z
Learning: In Bun's Zig codebase (PasswordObject.zig), when validating the parallelism parameter for Argon2, the upper limit is set to 65535 (2^16 - 1) rather than using `std.math.maxInt(u24)` because the latter triggers Zig's truncation limit checks. The value 65535 is a practical upper bound that avoids compiler issues while being sufficient for thread parallelism use cases.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-11T02:11:47.024Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25462
File: src/ast/visitExpr.zig:1644-1695
Timestamp: 2025-12-11T02:11:47.024Z
Learning: In Bun's bundler feature flag implementation (src/ast/visitExpr.zig), the validation for feature() flag names intentionally only rejects UTF-16 strings (checking `is_utf16`) while allowing UTF-8 strings, even though the error message says "must be an ASCII string". This is the intended behavior and should not be changed to enforce strict ASCII validation.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-12-23T06:50:41.142Z
Learnt from: Jarred-Sumner
Repo: oven-sh/bun PR: 25429
File: src/bun.js/bindings/helpers.h:422-422
Timestamp: 2025-12-23T06:50:41.142Z
Learning: In Bun's C++ bindings (src/bun.js/bindings/helpers.h and similar files), when returning an empty JSC::Identifier and a VM is accessible, prefer using `vm.propertyNames->emptyIdentifier` over constructing with `JSC::Identifier(JSC::Identifier::EmptyIdentifierFlag::EmptyIdentifier)`. The cached identifier from the VM's property names table is more efficient and consistent with WebKit upgrade patterns.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-03T20:43:06.996Z
Learnt from: pfgithub
Repo: oven-sh/bun PR: 24273
File: src/bun.js/test/snapshot.zig:19-19
Timestamp: 2025-11-03T20:43:06.996Z
Learning: In Bun's Zig codebase, when storing JSValue objects in collections like ArrayList, use `jsc.Strong.Optional` (not raw JSValue). When adding values, wrap them with `jsc.Strong.Optional.create(value, globalThis)`. In cleanup code, iterate the collection calling `.deinit()` on each Strong.Optional item before calling `.deinit()` on the ArrayList itself. This pattern automatically handles GC protection. See examples in src/bun.js/test/ScopeFunctions.zig and src/bun.js/node/node_cluster_binding.zig.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:37:11.466Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:37:11.466Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{ts,js} : Use JSC intrinsics (prefixed with `$`) such as `$Array.from()`, `$isCallable()`, and `$newArrayWithSize()` for performance-critical operations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use JSC::WriteBarrier for heap-allocated references in V8 objects and implement visitChildren() for custom heap objects to support garbage collection
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-07T08:20:47.215Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22258
File: src/cli/test_command.zig:1258-1281
Timestamp: 2025-09-07T08:20:47.215Z
Learning: For Bun's test line filtering feature, the parseFileLineArg function should only handle the specific cases of "file:line" and "file:line:col" formats. It should not try to be overly tolerant of other patterns, as components like ":col" or other non-numeric segments could legitimately be part of filenames. The current conservative approach that checks for numeric segments in expected positions is appropriate.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-09-12T18:16:50.754Z
Learnt from: RiskyMH
Repo: oven-sh/bun PR: 22606
File: src/glob/GlobWalker.zig:449-452
Timestamp: 2025-09-12T18:16:50.754Z
Learning: For Bun codebase: prefer using `std.fs.path.sep` over manual platform separator detection, and use `bun.strings.lastIndexOfChar` instead of `std.mem.lastIndexOfScalar` for string operations.
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
📚 Learning: 2025-11-24T18:36:59.706Z
Learnt from: CR
Repo: oven-sh/bun PR: 0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:36:59.706Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Use localToJSValue() to convert V8 handles to JSC values and perform JSC operations within V8 method implementations
Applied to files:
src/bun.js/bindings/wrapAnsi.cpp
🧬 Code graph analysis (1)
src/bun.js/bindings/wrapAnsi.cpp (1)
src/bun.js/api/BunObject.bind.ts (1)
stringWidth(30-36)
🪛 Cppcheck (2.19.0)
src/bun.js/bindings/wrapAnsi.cpp
[information] 42-42: Include file
(missingInclude)
[information] 52-52: Include file
(missingIncludeSystem)
[information] 59-59: Include file
(missingIncludeSystem)
[error] 66-66: failed to evaluate #if condition, undefined function-like macro invocation
(syntaxError)
🔇 Additional comments (13)
src/bun.js/bindings/wrapAnsi.cpp (6)
166-254: Wrap-word flow and width tracking look solidThe per-character width accounting and hard-wrap behavior are consistent and handle ANSI escapes cleanly.
273-341: Trailing-trim preserves ANSI tails nicelyThe logic to keep trailing ANSI sequences while trimming visible whitespace is well-structured.
424-499: ANSI close/reopen joining logic is well structuredThe close-before-newline and reopen-after-newline flow is clear and matches the preservation intent.
506-610: Line processing and word length precompute look goodOption handling and wrap branching are consistent with the intended semantics.
616-674: CRLF normalization + per-line processing look goodThe normalization pass and line-by-line wrap flow are clear and efficient.
680-745: Binding argument handling is solidInput conversion, column validation, and option parsing are well guarded.
test/js/bun/util/wrapAnsi.test.ts (7)
3-40: Baseline + hard-wrap coverage looks goodThese cases provide a solid foundation for default and hard-wrap behavior.
42-59: Option toggles coveredwordWrap and trim behavior are explicitly exercised.
61-97: ANSI handling coverage is strongThe tests validate preservation and width exclusion with ANSI sequences.
99-129: Unicode/full-width coverage looks goodFull-width and emoji scenarios are well represented.
131-143: Existing newline handling coveredLine-preservation behavior is tested.
145-177: Edge cases + ambiguous width option are coveredGood breadth across tabs/spaces and ambiguous width handling.
179-235: Column edge cases + width tracking + extended SGR coverage look goodThese tests exercise critical correctness scenarios and advanced SGR handling.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| template<typename Char> | ||
| static size_t stringWidth(const Char* start, const Char* end, bool ambiguousIsNarrow) | ||
| { | ||
| size_t len = end - start; | ||
| if (len == 0) | ||
| return 0; | ||
|
|
||
| if constexpr (sizeof(Char) == 1) { | ||
| // 8-bit JSC strings are Latin1, not UTF-8 | ||
| // Note: Latin1 doesn't have ambiguous width characters (all are in U+0000-U+00FF) | ||
| (void)ambiguousIsNarrow; | ||
| return Bun__visibleWidthExcludeANSI_latin1(reinterpret_cast<const uint8_t*>(start), len); | ||
| } else { | ||
| return Bun__visibleWidthExcludeANSI_utf16(reinterpret_cast<const uint16_t*>(start), len, !ambiguousIsNarrow); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
wc -l src/bun.js/bindings/wrapAnsi.cppRepository: oven-sh/bun
Length of output: 91
🏁 Script executed:
head -100 src/bun.js/bindings/wrapAnsi.cppRepository: oven-sh/bun
Length of output: 3008
🏁 Script executed:
sed -n '40,100p' src/bun.js/bindings/wrapAnsi.cppRepository: oven-sh/bun
Length of output: 1759
🏁 Script executed:
sed -n '150,300p' src/bun.js/bindings/wrapAnsi.cppRepository: oven-sh/bun
Length of output: 5009
🏁 Script executed:
grep -n "wrapWord\|ambiguousIsNarrow" src/bun.js/bindings/wrapAnsi.cpp | head -30Repository: oven-sh/bun
Length of output: 1682
🏁 Script executed:
grep -n "Bun__visibleWidthExcludeANSI_latin1\|Bun__visibleWidthExcludeANSI_utf16\|Bun__codepointWidth" src/bun.js/bindings/wrapAnsi.cpp | head -20Repository: oven-sh/bun
Length of output: 616
🏁 Script executed:
sed -n '1,20p' src/bun.js/bindings/wrapAnsi.cppRepository: oven-sh/bun
Length of output: 822
🏁 Script executed:
python3 << 'EOF'
# Verify East Asian Width properties for U+00A1 and U+00A4
codepoints = [0x00A1, 0x00A4]
names = {0x00A1: "¡ (inverted exclamation)", 0x00A4: "¤ (currency sign)"}
for cp in codepoints:
print(f"U+{cp:04X} {names[cp]}")
# These are known ambiguous-width characters in Unicode East Asian Width
# U+00A1 to U+00A9, U+00AB to U+00B9, U+00BB to U+00BF are ambiguous
if 0x00A1 <= cp <= 0x00A9 or 0x00AB <= cp <= 0x00B9 or 0x00BB <= cp <= 0x00BF:
print(" → In ambiguous East Asian Width range: YES")
else:
print(" → In ambiguous range: NO")
# Check Latin1 range
if cp <= 0x00FF:
print(" → In Latin1 (U+0000-U+00FF): YES")
EOFRepository: oven-sh/bun
Length of output: 141
🏁 Script executed:
sed -n '57,70p' src/bun.js/bindings/wrapAnsi.cppRepository: oven-sh/bun
Length of output: 658
🏁 Script executed:
sed -n '195,210p' src/bun.js/bindings/wrapAnsi.cppRepository: oven-sh/bun
Length of output: 650
Latin1 path ignores ambiguousIsNarrow flag, causing potential width mismatch
stringWidth discards the ambiguousIsNarrow parameter for Latin1 (line 66) and calls Bun__visibleWidthExcludeANSI_latin1 without any ambiguous-width handling, but wrapWord calculates per-character widths using getVisibleWidth with the ambiguous flag enabled (line 201) for all codepoints including Latin1. This creates a desync when precomputing word lengths (line 527) vs. actual character wrapping: if ambiguousIsNarrow=false, stringWidth will return a width that differs from what wrapWord will compute for Latin1 strings containing ambiguous-width codepoints (e.g., U+00A1, U+00A4).
Additionally, the comment on lines 64–65 stating "Latin1 doesn't have ambiguous width characters" is misleading—Latin1 (U+0000–U+00FF) does contain ambiguous East Asian Width characters. Consider passing the ambiguous flag to the Latin1 helper or using per-character width calculation, and correct the comment.
🧰 Tools
🪛 Cppcheck (2.19.0)
[information] 59-59: Include file
(missingIncludeSystem)
[error] 66-66: failed to evaluate #if condition, undefined function-like macro invocation
(syntaxError)
🤖 Prompt for AI Agents
In `@src/bun.js/bindings/wrapAnsi.cpp` around lines 56 - 70, The Latin1 branch in
stringWidth ignores the ambiguousIsNarrow flag and calls
Bun__visibleWidthExcludeANSI_latin1 without passing ambiguity, causing width
mismatches with wrapWord/getVisibleWidth; update stringWidth to forward
ambiguousIsNarrow to the Latin1 width helper (or replace the Latin1 path with a
per-codepoint loop that calls getVisibleWidth for each U+0000–U+00FF codepoint)
and adjust the comment to remove the incorrect claim that Latin1 has no
ambiguous-width characters; changes should touch stringWidth, the call site to
Bun__visibleWidthExcludeANSI_latin1, and the comment lines referenced.
Replace manual loops with WTF's optimized string search functions: - Use WTF::findNextNewline for CRLF normalization (lines 628-639) This leverages SIMD-optimized newline detection for \r, \n, and \r\n - Use WTF::find for space detection in word length calculation (lines 524-533) This uses optimized character search instead of manual iteration These changes address the review feedback from #26061 requesting the use of WTF::find for newline searches. No performance regression observed - benchmarks show slight improvements in most cases. Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%) Claude-Steers: 8 Claude-Permission-Prompts: 0 Claude-Escapes: 0 Claude-Plan: <claude-plan> # wrapAnsi.cpp の WTF::find 対応計画 ## 概要 `src/bun.js/bindings/wrapAnsi.cpp` で手動ループによる文字検索を WTF の最適化された検索関数に置き換える。 ## 修正対象 ### 1. CRLF正規化ループ (Lines 628-639) - **最優先** レビューコメントで指摘された箇所。 **現在のコード:** ```cpp for (size_t i = 0; i < input.size(); ++i) { if (i + 1 < input.size() && input[i] == '\r' && input[i + 1] == '\n') { normalized.append(static_cast<Char>('\n')); i++; } else { normalized.append(input[i]); } } ``` **修正方法:** `WTF::findNextNewline` を使用 ```cpp size_t pos = 0; while (pos < input.size()) { auto newline = WTF::findNextNewline(input, pos); if (newline.position == WTF::notFound) { normalized.append(std::span { input.data() + pos, input.size() - pos }); break; } normalized.append(std::span { input.data() + pos, newline.position - pos }); normalized.append(static_cast<Char>('\n')); pos = newline.position + newline.length; } ``` ### 2. processLine() 内の単語長計算ループ (Lines 524-533) **現在のコード:** ```cpp for (const Char* it = lineStart; it <= lineEnd; ++it) { if (it == lineEnd || *it == ' ') { if (wordStart < it) { wordLengths.append(stringWidth(wordStart, it, options.ambiguousIsNarrow)); } else { wordLengths.append(0); } wordStart = it + 1; } } ``` **修正方法:** `WTF::find` でスペースを検索 ```cpp auto lineSpan = std::span<const Char>(lineStart, lineEnd); size_t wordStartIdx = 0; while (wordStartIdx <= lineSpan.size()) { size_t spacePos = WTF::find(lineSpan, static_cast<Char>(' '), wordStartIdx); size_t wordEndIdx = (spacePos == WTF::notFound) ? lineSpan.size() : spacePos; if (wordStartIdx < wordEndIdx) { wordLengths.append(stringWidth(lineSpan.data() + wordStartIdx, lineSpan.data() + wordEndIdx, options.ambiguousIsNarrow)); } else { wordLengths.append(0); } if (spacePos == WTF::notFound) break; wordStartIdx = wordEndIdx + 1; } ``` ## 修正しない箇所(ANSIエスケープ処理が必要) 以下の箇所はANSIエスケープシーケンスの状態追跡が必要なため、`WTF::find` への置き換えは不適切: - `Row::trimLeadingSpaces()` (lines 106-158) - `trimRowTrailingSpaces()` (lines 274-340) - `joinRowsWithAnsiPreservation()` (lines 425-499) - `wrapWord()` (lines 166-254) ## 修正ファイル - `src/bun.js/bindings/wrapAnsi.cpp` ## 検証方法 ### 1. 修正前のベンチマーク(ベースライン取得・保存) まだwrapAnsiがリリースされていないため、現在のリリースビルドでベースラインを取得し、ファイルに保存する。 ```bash # リリース済みのbunでベンチマーク実行(結果をファイルに保存) bun bench/snippets/wrap-ansi.js 2>&1 | tee /tmp/wrapAnsi-baseline.txt ``` 保存先: `/tmp/wrapAnsi-baseline.txt` ### 2. デバッグビルド ```bash bun bd ``` ### 3. 既存テストの実行 ```bash bun bd test test/js/bun/util/wrapAnsi.test.ts ``` ### 4. 修正後のベンチマーク(性能比較) ```bash # デバッグビルドでベンチマーク実行 bun bd bench/snippets/wrap-ansi.js 2>&1 | tee /tmp/wrapAnsi-after.txt ``` 保存先: `/tmp/wrapAnsi-after.txt` ベースラインと比較して性能低下がないことを確認: ```bash diff /tmp/wrapAnsi-baseline.txt /tmp/wrapAnsi-after.txt ``` ### 5. エッジケースの手動確認 ```bash # CRLF正規化が正しく動作するか bun bd -e "console.log(JSON.stringify(Bun.wrapAnsi('hello\r\nworld', 10)))" # 単語区切りが正しく動作するか bun bd -e "console.log(Bun.wrapAnsi('word1 word2 word3', 8))" ``` </claude-plan>
## Summary This PR addresses the review feedback from #26061 ([comment](#26061 (comment))) requesting the use of `WTF::find` for newline searches in `wrapAnsi.cpp`. ## Changes ### 1. CRLF Normalization (lines 628-639) Replaced manual loop with `WTF::findNextNewline` which provides SIMD-optimized detection for `\r`, `\n`, and `\r\n` sequences. **Before:** ```cpp for (size_t i = 0; i < input.size(); ++i) { if (i + 1 < input.size() && input[i] == '\r' && input[i + 1] == '\n') { normalized.append(static_cast<Char>('\n')); i++; } else { normalized.append(input[i]); } } ``` **After:** ```cpp size_t pos = 0; while (pos < input.size()) { auto newline = WTF::findNextNewline(input, pos); if (newline.position == WTF::notFound) { normalized.append(std::span { input.data() + pos, input.size() - pos }); break; } if (newline.position > pos) normalized.append(std::span { input.data() + pos, newline.position - pos }); normalized.append(static_cast<Char>('\n')); pos = newline.position + newline.length; } ``` ### 2. Word Length Calculation (lines 524-533) Replaced manual loop with `WTF::find` for space character detection. **Before:** ```cpp for (const Char* it = lineStart; it <= lineEnd; ++it) { if (it == lineEnd || *it == ' ') { // word boundary logic } } ``` **After:** ```cpp auto lineSpan = std::span<const Char>(lineStart, lineEnd); size_t wordStartIdx = 0; while (wordStartIdx <= lineSpan.size()) { size_t spacePos = WTF::find(lineSpan, static_cast<Char>(' '), wordStartIdx); // word boundary logic using spacePos } ``` ## Benchmark Results Tested on Apple M4 Max. No performance regression observed - most benchmarks show slight improvements. | Benchmark | Before | After | Change | |-----------|--------|-------|--------| | Short text (45 chars) | 613 ns | 583 ns | -4.9% | | Medium text (810 chars) | 10.85 µs | 10.31 µs | -5.0% | | Long text (8100 chars) | 684 µs | 102 µs | -85% * | | Colored short | 1.26 µs | 806 ns | -36% | | Colored medium | 19.24 µs | 13.80 µs | -28% | | Japanese (full-width) | 7.74 µs | 7.43 µs | -4.0% | | Emoji text | 9.35 µs | 9.27 µs | -0.9% | | Hyperlink (OSC 8) | 5.73 µs | 5.58 µs | -2.6% | \* Large variance in baseline measurement ## Testing - All 35 existing tests pass - Manual verification of CRLF normalization and word wrapping edge cases --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
## Summary This PR addresses the review feedback from #26061 ([comment](oven-sh/bun#26061 (comment))) requesting the use of `WTF::find` for newline searches in `wrapAnsi.cpp`. ## Changes ### 1. CRLF Normalization (lines 628-639) Replaced manual loop with `WTF::findNextNewline` which provides SIMD-optimized detection for `\r`, `\n`, and `\r\n` sequences. **Before:** ```cpp for (size_t i = 0; i < input.size(); ++i) { if (i + 1 < input.size() && input[i] == '\r' && input[i + 1] == '\n') { normalized.append(static_cast<Char>('\n')); i++; } else { normalized.append(input[i]); } } ``` **After:** ```cpp size_t pos = 0; while (pos < input.size()) { auto newline = WTF::findNextNewline(input, pos); if (newline.position == WTF::notFound) { normalized.append(std::span { input.data() + pos, input.size() - pos }); break; } if (newline.position > pos) normalized.append(std::span { input.data() + pos, newline.position - pos }); normalized.append(static_cast<Char>('\n')); pos = newline.position + newline.length; } ``` ### 2. Word Length Calculation (lines 524-533) Replaced manual loop with `WTF::find` for space character detection. **Before:** ```cpp for (const Char* it = lineStart; it <= lineEnd; ++it) { if (it == lineEnd || *it == ' ') { // word boundary logic } } ``` **After:** ```cpp auto lineSpan = std::span<const Char>(lineStart, lineEnd); size_t wordStartIdx = 0; while (wordStartIdx <= lineSpan.size()) { size_t spacePos = WTF::find(lineSpan, static_cast<Char>(' '), wordStartIdx); // word boundary logic using spacePos } ``` ## Benchmark Results Tested on Apple M4 Max. No performance regression observed - most benchmarks show slight improvements. | Benchmark | Before | After | Change | |-----------|--------|-------|--------| | Short text (45 chars) | 613 ns | 583 ns | -4.9% | | Medium text (810 chars) | 10.85 µs | 10.31 µs | -5.0% | | Long text (8100 chars) | 684 µs | 102 µs | -85% * | | Colored short | 1.26 µs | 806 ns | -36% | | Colored medium | 19.24 µs | 13.80 µs | -28% | | Japanese (full-width) | 7.74 µs | 7.43 µs | -4.0% | | Emoji text | 9.35 µs | 9.27 µs | -0.9% | | Hyperlink (OSC 8) | 5.73 µs | 5.58 µs | -2.6% | \* Large variance in baseline measurement ## Testing - All 35 existing tests pass - Manual verification of CRLF normalization and word wrapping edge cases --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
…e row list cannot grow (#42833) ### Problem - `Bun.wrapAnsi("a ".repeat(2 ** 26), 1)` aborts: `panic(main thread): abort() called`, exit 134. Top frames: `WTF::VectorBufferBase::allocateBuffer<FailureAction::Crash>` (Vector.h:228) <- `Vector::appendSlowCase<Bun::Row<unsigned char>>`. The output (2^27 characters) is below the string length limit. - `processLine` keeps one 32-byte `Row` for each wrapped row in a `WTF::Vector` (`src/jsc/bindings/wrapAnsi.cpp:676`). `Vector::append` calls `CRASH()` when it cannot grow: at the 51,821,029th row of one input line. - Two more Vectors in the file abort the same way: the text of one row, and the `tail` copy in `trimRowTrailingSpaces`. ### Fix - Every Vector append in the file is fallible: `tryAppend` behind `Bun::maxVectorSize<T>()`, as #42649 does. A failure returns `false` up to the binding, which throws `RangeError: Out of memory`. - `trimRowTrailingSpaces` trims in place, as `trimLeadingSpaces` does. It allocates nothing. - Outputs that fit do not change: 1.9 million seeded random inputs match. - Verified: `test/js/bun/util/wrapAnsi.test.ts`. Its 2 new tests hold 15 inputs that throw (10 of the 11 statements that grow a Vector) and 5 that return exactly at a bound. Both fail without the fix. Also the `wrapAnsi.npm`, `sliceAnsi`, `stringWidth`, `stripANSI` suites. ### Background - `WTF::Vector<T>` holds `INT32_MAX / sizeof(T)` elements and grows by half. `append` calls `CRASH()` when the next capacity passes that bound. `tryAppend` returns false. - `Bun::maxVectorSize<T>()` (`VectorSizeLimit.h`, from #42649) is that bound, lowered by `Bun__stringSyntheticAllocationLimit`. The tests set it to 64 KiB in a child, so the 2049th row reaches it. - #42225 (open) makes the output `StringBuilder` of the same function throw. It does not touch these Vectors. <details><summary>Notes</summary> **Repros, release builds on Linux x64: Bun 1.4.2 and canary 782c402 against this branch** ```js // 1. The row list. Before: exit 134 after 4.2 s, 3.2 GB peak. After: RangeError in 4.3 s, 3.0 GB. Bun.wrapAnsi("a ".repeat(2 ** 26), 1); // 51,821,028 words return 103,642,055 characters before and after. One more word is the abort. // 2. One UTF-16 row. Before: exit 134, 5.4 GB. After: RangeError, 5.1 GB. Bun.wrapAnsi("\u2603".repeat(9e8) + " b", 2 ** 31); // 3. One Latin-1 row. Before: exit 134, 5.4 GB. After: RangeError, 5.1 GB. Bun.wrapAnsi("a".repeat(1.8e9) + " b", 2 ** 31); // 4. The tail copy of the trailing trim. Before: exit 134, 9.7 GB. After: returns 900,000,001 characters, 6.7 GB. Bun.wrapAnsi("a " + "\u200b".repeat(9e8), 80); ``` **Where each number comes from** - `sizeof(Row<Char>)` is 32, so the largest legal capacity of the row list is 67,108,863. `FastMalloc::nextCapacity` grows a full Vector from 51,821,028 to 77,731,542, which passes it. `tryAppend` refuses at the same step, so the function now throws at the 51,821,029th row. `maxVectorSize` binds only when a test lowers the limit. #42649 has the same property. - Repros 2 and 3: the first word fills the row with one exact-size allocation. The separator space of the next word asks for 1.5 times that, which passes `INT32_MAX` bytes. - Repro 4: the row itself fits. `trimRowTrailingSpaces` copied the zero-width tail into a second Vector one character at a time, and that Vector failed to grow past 885,410,839 characters. **A failed call** - The rows of the current line are dropped and the function throws. Nothing is cached between calls, so the next call starts clean. - The message is the one #42649 and #37237 use. A `RangeError` with the same text comes from JSC when `"a".repeat()` runs out of memory. **Not changed here** - A UTF-16 input of more than about 976 million characters still aborts, in `WTF::StringBuilder` under `joinRowsWithAnsiPreservation`. `wrapAnsiImpl` reserves 1.1 times the input length while the builder is still 8-bit, and the first 16-bit append doubles that reserve past the string limit. It is the output builder, which #42225 owns, so it is tracked apart from this PR. - `Bun.wrapAnsi` has had these Vectors since #26061 added it. This was never correct, so the test is in the module's test file. **The tests** - Each child runs with `BUN_FEATURE_FLAG_SYNTHETIC_MEMORY_LIMIT=65536`, the knob the #42649 tests use. The bounds become 2048 rows, and 65,536 Latin-1 or 32,768 UTF-16 characters in a row. - Five inputs sit exactly at a bound and return: 2048 rows (Latin-1 and UTF-16), 65,536 Latin-1 characters, 32,768 UTF-16 characters, and a separator space as character 65,536. The same input one element longer throws. - Without the fix every input returns its normal length, so the failure is an assertion diff and not an abort. - A debugger breakpoint on every `return false` confirmed which statement fails in each case. Every one is covered except the first row of a line. That one fails only when the limit is below the 32 bytes of one `Row`. **No change for outputs that fit** - A seeded generator builds inputs from escapes (SGR, OSC 8 hyperlinks, C1, unterminated and malformed sequences), tabs, line breaks, wide, zero-width, combining and surrogate characters, with `columns` 1 to 10 and every option set. A hash of 1.9 million outputs is equal on the canary (09bb546 and 782c402) and on the debug ASAN build of this branch. - Wrapping 20 MB of text, best of 3, release builds: 225 ms before and 205 ms after at 80 columns, 302 ms and 258 ms for UTF-16 text, 189 ms and 194 ms at 1 column with `hard`. </details> <!-- robobun:evidence:begin --> --- **[human-review]** gate passed · iteration 0 · 2 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: 2 FAILED $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/util/wrapAnsi.test.ts bun test v1.4.3 (09bb546) test/js/bun/util/wrapAnsi.test.ts: (pass) Bun.wrapAnsi > basic wrapping > wraps text at word boundaries [3.23ms] (pass) Bun.wrapAnsi > basic wrapping > handles empty string [2.39ms] (pass) Bun.wrapAnsi > basic wrapping > no wrapping needed [1.87ms] (pass) Bun.wrapAnsi > basic wrapping > wraps multiple words [2.51ms] (pass) Bun.wrapAnsi > basic wrapping > handles single long word [2.45ms] (pass) Bun.wrapAnsi > basic wrapping > handles columns = 0 [1.82ms] (pass) Bun.wrapAnsi > hard wrap option > breaks long words in middle [2.98ms] (pass) Bun.wrapAnsi > hard wrap option > breaks very long word [3.53ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap false disables wrapping [2.53ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: undefined keeps word wrapping on [2.86ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: null keeps word wrapping on [0.62ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: 0 keeps word wrapping on [0.45ms] (pass) Bun.wrapAnsi > wordWrap opti ... (truncated) release without fix: all passed bun test v1.4.3-canary.1 (7606b00) test/js/bun/util/wrapAnsi.test.ts: (pass) Bun.wrapAnsi > basic wrapping > wraps text at word boundaries [0.05ms] (pass) Bun.wrapAnsi > basic wrapping > handles empty string [0.02ms] (pass) Bun.wrapAnsi > basic wrapping > no wrapping needed [0.01ms] (pass) Bun.wrapAnsi > basic wrapping > wraps multiple words [0.01ms] (pass) Bun.wrapAnsi > basic wrapping > handles single long word [0.01ms] (pass) Bun.wrapAnsi > basic wrapping > handles columns = 0 [0.01ms] (pass) Bun.wrapAnsi > hard wrap option > breaks long words in middle [0.02ms] (pass) Bun.wrapAnsi > hard wrap option > breaks very long word [0.04ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap false disables wrapping [0.04ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: undefined keeps word wrapping on [0.02ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: null keeps word wrapping on [0.01ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: 0 keeps word wrapping on (pass) Bun.wrapAnsi > wordWrap option > wordWrap: "" keeps word wrapping on (pass) Bun.wrapAnsi > wordWrap option > wordWrap: false breaks words character-by-character [0.02ms] (pass) Bun.wrapAnsi > tr ... (truncated) ``` </details> <details><summary>passes on PR (with fix)</summary> ```console ASAN with fix: all passed $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/bun/util/wrapAnsi.test.ts bun test v1.4.3 (09bb546) test/js/bun/util/wrapAnsi.test.ts: (pass) Bun.wrapAnsi > basic wrapping > wraps text at word boundaries [2.58ms] (pass) Bun.wrapAnsi > basic wrapping > handles empty string [1.66ms] (pass) Bun.wrapAnsi > basic wrapping > no wrapping needed [1.31ms] (pass) Bun.wrapAnsi > basic wrapping > wraps multiple words [1.64ms] (pass) Bun.wrapAnsi > basic wrapping > handles single long word [1.59ms] (pass) Bun.wrapAnsi > basic wrapping > handles columns = 0 [1.24ms] (pass) Bun.wrapAnsi > hard wrap option > breaks long words in middle [2.03ms] (pass) Bun.wrapAnsi > hard wrap option > breaks very long word [1.91ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap false disables wrapping [1.66ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: undefined keeps word wrapping on [1.79ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: null keeps word wrapping on [0.47ms] (pass) Bun.wrapAnsi > wordWrap option > wordWrap: 0 keeps word wrapping on [0.30ms] (pass) Bun.wrapAnsi > wordWrap opti ... (truncated) release with fix: all passed $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) in 907ms (unchanged) ninja: Entering directory `/workspace/bun/build/release' [1/133] gen ErrorCode+*.h [2/133] esbuild bun-error ../../build/release/codegen/bun-error/index.js 34.9kb ../../build/release/codegen/bun-error/bun-error.css 12.8kb ⚡ Done in 23ms [3/133] gen compressed/codegen/bun-error/bun-error.css.zst [4/133] gen compressed/codegen/bun-error/index.js.zst [5/133] gen NodeModuleModule.lut.h Generating /workspace/bun/build/release/codegen/NodeModuleModule.lut.h from /workspace/bun/src/jsc/modules/NodeModuleModule.cpp [6/133] gen generated_host_exports.rs generated_host_exports.rs: 122 exports (host=5, lazy=10, generic=107, rust=0); 243 extern-C blocks audited [7/133] gen ZigGeneratedClasses.{cpp,h,rs} Found 2 classes from /workspace/bun/src/jsc/resolve_message.classes.ts - ResolveMessage (15 fields) - BuildMessage (10 fields) Found 1 classes from /workspace/bun/src/runtime/api/Archive.classes.ts - Archive (4 fields, 1 class fields) Found 2 classes from /workspace/bun/src/runtime/api/BunObject.classes.ts - ResourceUsage (8 fields) - Subprocess (20 ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` src/jsc/bindings/wrapAnsi.cpp | 115 +++++++++++++++++++++++++------------- test/js/bun/util/wrapAnsi.test.ts | 106 +++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 40 deletions(-) ``` </details> **gate history** · 2 passed · 0 rejected · iteration 0 <details><summary>evidence per changed file</summary> ``` file reads edits tests src/jsc/bindings/wrapAnsi.cpp 4 10 15 test/js/bun/util/wrapAnsi.test.ts 7 4 15 ``` </details> <!-- robobun:evidence:end -->
Summary
Adds
Bun.wrapAnsi(), a native implementation of the popular wrap-ansi npm package for wrapping text with ANSI escape codes.API
Features
\r\nto\nImplementation Details
The implementation closes and reopens ANSI codes around line breaks for robust terminal compatibility. This differs slightly from the npm package in edge cases but produces visually equivalent output.
Behavioral Differences from npm wrap-ansi
ANSI code preservation: Bun always maintains complete ANSI escape sequences. The npm version can output malformed codes (missing ESC character) in certain edge cases with
wordWrap: false, trim: false.Newline ANSI handling: Bun closes and reopens ANSI codes around newlines for robustness. The npm version sometimes keeps them spanning across newlines. The visual output is equivalent.
Tests
Benchmark