Implement Bun.YAML.stringify (#22183)

### What does this PR do?
This PR adds `Bun.YAML.stringify`. The stringifier will double quote
strings only when necessary (looks for keywords, numbers, or containing
non-printable or escaped characters). Anchors and aliases are detected
by object equality, and anchor name is chosen from property name, array
item, or the root collection.
```js
import { YAML } from "bun"

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

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

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

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

### How did you verify your code works?
Added tests for basic use and edgecases

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- New Features
- Added YAML.stringify to the YAML API, producing YAML from JavaScript
values with quoting, anchors, and indentation support.

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

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

- Chores
- Added a YAML stringify benchmark script covering multiple libraries
and data shapes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Dylan Conway
2025-09-01 01:27:51 +00:00
committed by GitHub
parent 25c61fcd5a
commit fcaff77ed7
20 changed files with 3569 additions and 81 deletions

View File

@@ -0,0 +1,407 @@
import { bench, group, run } from "../runner.mjs";
import jsYaml from "js-yaml";
import yaml from "yaml";
// Small object
const smallObject = {
name: "John Doe",
age: 30,
email: "john@example.com",
active: true,
};
// Medium object with nested structures
const mediumObject = {
company: "Acme Corp",
employees: [
{
name: "John Doe",
age: 30,
position: "Developer",
skills: ["JavaScript", "TypeScript", "Node.js"],
},
{
name: "Jane Smith",
age: 28,
position: "Designer",
skills: ["Figma", "Photoshop", "Illustrator"],
},
{
name: "Bob Johnson",
age: 35,
position: "Manager",
skills: ["Leadership", "Communication", "Planning"],
},
],
settings: {
database: {
host: "localhost",
port: 5432,
name: "mydb",
},
cache: {
enabled: true,
ttl: 3600,
},
},
};
// Large object with complex structures
const largeObject = {
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: "nginx-deployment",
labels: {
app: "nginx",
},
},
spec: {
replicas: 3,
selector: {
matchLabels: {
app: "nginx",
},
},
template: {
metadata: {
labels: {
app: "nginx",
},
},
spec: {
containers: [
{
name: "nginx",
image: "nginx:1.14.2",
ports: [
{
containerPort: 80,
},
],
env: [
{
name: "ENV_VAR_1",
value: "value1",
},
{
name: "ENV_VAR_2",
value: "value2",
},
],
volumeMounts: [
{
name: "config",
mountPath: "/etc/nginx",
},
],
resources: {
limits: {
cpu: "1",
memory: "1Gi",
},
requests: {
cpu: "0.5",
memory: "512Mi",
},
},
},
],
volumes: [
{
name: "config",
configMap: {
name: "nginx-config",
items: [
{
key: "nginx.conf",
path: "nginx.conf",
},
{
key: "mime.types",
path: "mime.types",
},
],
},
},
],
nodeSelector: {
disktype: "ssd",
},
tolerations: [
{
key: "key1",
operator: "Equal",
value: "value1",
effect: "NoSchedule",
},
{
key: "key2",
operator: "Exists",
effect: "NoExecute",
},
],
affinity: {
nodeAffinity: {
requiredDuringSchedulingIgnoredDuringExecution: {
nodeSelectorTerms: [
{
matchExpressions: [
{
key: "kubernetes.io/e2e-az-name",
operator: "In",
values: ["e2e-az1", "e2e-az2"],
},
],
},
],
},
},
podAntiAffinity: {
preferredDuringSchedulingIgnoredDuringExecution: [
{
weight: 100,
podAffinityTerm: {
labelSelector: {
matchExpressions: [
{
key: "app",
operator: "In",
values: ["web-store"],
},
],
},
topologyKey: "kubernetes.io/hostname",
},
},
],
},
},
},
},
},
};
// Object with anchors and references (after resolution)
const objectWithAnchors = {
defaults: {
adapter: "postgresql",
host: "localhost",
port: 5432,
},
development: {
adapter: "postgresql",
host: "localhost",
port: 5432,
database: "dev_db",
},
test: {
adapter: "postgresql",
host: "localhost",
port: 5432,
database: "test_db",
},
production: {
adapter: "postgresql",
host: "prod.example.com",
port: 5432,
database: "prod_db",
},
};
// Array of items
const arrayObject = [
{
id: 1,
name: "Item 1",
price: 10.99,
tags: ["electronics", "gadgets"],
},
{
id: 2,
name: "Item 2",
price: 25.5,
tags: ["books", "education"],
},
{
id: 3,
name: "Item 3",
price: 5.0,
tags: ["food", "snacks"],
},
{
id: 4,
name: "Item 4",
price: 100.0,
tags: ["electronics", "computers"],
},
{
id: 5,
name: "Item 5",
price: 15.75,
tags: ["clothing", "accessories"],
},
];
// Multiline strings
const multilineObject = {
description:
"This is a multiline string\nthat preserves line breaks\nand indentation.\n\nIt can contain multiple paragraphs\nand special characters: !@#$%^&*()\n",
folded: "This is a folded string where line breaks are converted to spaces unless there are\nempty lines like above.",
plain: "This is a plain string",
quoted: 'This is a quoted string with "escapes"',
literal: "This is a literal string with 'quotes'",
};
// Numbers and special values
const numbersObject = {
integer: 42,
negative: -17,
float: 3.14159,
scientific: 0.000123,
infinity: Infinity,
negativeInfinity: -Infinity,
notANumber: NaN,
octal: 493, // 0o755
hex: 255, // 0xFF
binary: 10, // 0b1010
};
// Dates and timestamps
const datesObject = {
date: new Date("2024-01-15"),
datetime: new Date("2024-01-15T10:30:00Z"),
timestamp: new Date("2024-01-15T15:30:00.123456789Z"), // Adjusted for UTC-5
canonical: new Date("2024-01-15T10:30:00.123456789Z"),
};
// Stringify benchmarks
group("stringify small object", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(smallObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(smallObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(smallObject);
});
});
group("stringify medium object", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(mediumObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(mediumObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(mediumObject);
});
});
group("stringify large object", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(largeObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(largeObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(largeObject);
});
});
group("stringify object with anchors", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(objectWithAnchors);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(objectWithAnchors);
});
bench("yaml.stringify", () => {
return yaml.stringify(objectWithAnchors);
});
});
group("stringify array", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(arrayObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(arrayObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(arrayObject);
});
});
group("stringify object with multiline strings", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(multilineObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(multilineObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(multilineObject);
});
});
group("stringify object with numbers", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(numbersObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(numbersObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(numbersObject);
});
});
group("stringify object with dates", () => {
if (typeof Bun !== "undefined" && Bun.YAML) {
bench("Bun.YAML.stringify", () => {
return Bun.YAML.stringify(datesObject);
});
}
bench("js-yaml.dump", () => {
return jsYaml.dump(datesObject);
});
bench("yaml.stringify", () => {
return yaml.stringify(datesObject);
});
});
await run();

View File

@@ -198,6 +198,7 @@ src/bun.js/bindings/ServerRouteList.cpp
src/bun.js/bindings/spawn.cpp src/bun.js/bindings/spawn.cpp
src/bun.js/bindings/SQLClient.cpp src/bun.js/bindings/SQLClient.cpp
src/bun.js/bindings/sqlite/JSSQLStatement.cpp src/bun.js/bindings/sqlite/JSSQLStatement.cpp
src/bun.js/bindings/StringBuilderBinding.cpp
src/bun.js/bindings/stripANSI.cpp src/bun.js/bindings/stripANSI.cpp
src/bun.js/bindings/Strong.cpp src/bun.js/bindings/Strong.cpp
src/bun.js/bindings/TextCodec.cpp src/bun.js/bindings/TextCodec.cpp

View File

@@ -200,6 +200,7 @@ src/bun.js/bindings/sizes.zig
src/bun.js/bindings/SourceProvider.zig src/bun.js/bindings/SourceProvider.zig
src/bun.js/bindings/SourceType.zig src/bun.js/bindings/SourceType.zig
src/bun.js/bindings/static_export.zig src/bun.js/bindings/static_export.zig
src/bun.js/bindings/StringBuilder.zig
src/bun.js/bindings/SystemError.zig src/bun.js/bindings/SystemError.zig
src/bun.js/bindings/TextCodec.zig src/bun.js/bindings/TextCodec.zig
src/bun.js/bindings/URL.zig src/bun.js/bindings/URL.zig

View File

@@ -644,6 +644,38 @@ declare module "bun" {
* ``` * ```
*/ */
export function parse(input: string): unknown; export function parse(input: string): unknown;
/**
* Convert a JavaScript value into a YAML string. Strings are double quoted if they contain keywords, non-printable or
* escaped characters, or if a YAML parser would parse them as numbers. Anchors and aliases are inferred from objects, allowing cycles.
*
* @category Utilities
*
* @param input The JavaScript value to stringify.
* @param replacer Currently not supported.
* @param space A number for how many spaces each level of indentation gets, or a string used as indentation. The number is clamped between 0 and 10, and the first 10 characters of the string are used.
* @returns A string containing the YAML document.
*
* @example
* ```ts
* import { YAML } from "bun";
*
* const input = {
* abc: "def"
* };
* console.log(YAML.stringify(input));
* // # output
* // abc: def
*
* const cycle = {};
* cycle.obj = cycle;
* console.log(YAML.stringify(cycle));
* // # output
* // &root
* // obj:
* // *root
*/
export function stringify(input: unknown, replacer?: undefined | null, space?: string | number): string;
} }
/** /**

View File

@@ -1,5 +1,5 @@
pub fn create(globalThis: *jsc.JSGlobalObject) jsc.JSValue { pub fn create(globalThis: *jsc.JSGlobalObject) jsc.JSValue {
const object = JSValue.createEmptyObject(globalThis, 1); const object = JSValue.createEmptyObject(globalThis, 2);
object.put( object.put(
globalThis, globalThis,
ZigString.static("parse"), ZigString.static("parse"),
@@ -10,10 +10,898 @@ pub fn create(globalThis: *jsc.JSGlobalObject) jsc.JSValue {
parse, parse,
), ),
); );
object.put(
globalThis,
ZigString.static("stringify"),
jsc.createCallback(
globalThis,
ZigString.static("stringify"),
3,
stringify,
),
);
return object; return object;
} }
pub fn stringify(global: *JSGlobalObject, callFrame: *jsc.CallFrame) JSError!JSValue {
const value, const replacer, const space_value = callFrame.argumentsAsArray(3);
value.ensureStillAlive();
if (value.isUndefined() or value.isSymbol() or value.isFunction()) {
return .js_undefined;
}
if (!replacer.isUndefinedOrNull()) {
return global.throw("YAML.stringify does not support the replacer argument", .{});
}
var scope: bun.AllocationScope = .init(bun.default_allocator);
defer scope.deinit();
var stringifier: Stringifier = try .init(scope.allocator(), global, space_value);
defer stringifier.deinit();
stringifier.findAnchorsAndAliases(global, value, .root) catch |err| return switch (err) {
error.OutOfMemory, error.JSError => |js_err| js_err,
error.StackOverflow => global.throwStackOverflow(),
};
stringifier.stringify(global, value) catch |err| return switch (err) {
error.OutOfMemory, error.JSError => |js_err| js_err,
error.StackOverflow => global.throwStackOverflow(),
};
return stringifier.builder.toString(global);
}
const Stringifier = struct {
stack_check: bun.StackCheck,
builder: wtf.StringBuilder,
indent: usize,
known_collections: std.AutoHashMap(JSValue, AnchorAlias),
array_item_counter: usize,
prop_names: bun.StringHashMap(usize),
space: Space,
pub const Space = union(enum) {
minified,
number: u32,
str: String,
pub fn init(global: *JSGlobalObject, space_value: JSValue) JSError!Space {
if (space_value.isNumber()) {
var num = space_value.toInt32();
num = @max(0, @min(num, 10));
if (num == 0) {
return .minified;
}
return .{ .number = @intCast(num) };
}
if (space_value.isString()) {
const str = try space_value.toBunString(global);
if (str.length() == 0) {
str.deref();
return .minified;
}
return .{ .str = str };
}
return .minified;
}
pub fn deinit(this: *const Space) void {
switch (this.*) {
.minified => {},
.number => {},
.str => |str| {
str.deref();
},
}
}
};
const AnchorOrigin = enum {
root,
array_item,
prop_value,
};
const AnchorAlias = struct {
anchored: bool,
used: bool,
name: Name,
pub fn init(origin: ValueOrigin) AnchorAlias {
return .{
.anchored = false,
.used = false,
.name = switch (origin) {
.root => .root,
.array_item => .{ .array_item = 0 },
.prop_value => .{ .prop_value = .{ .prop_name = origin.prop_value, .counter = 0 } },
},
};
}
pub const Name = union(AnchorOrigin) {
// only one root anchor is possible
root,
array_item: usize,
prop_value: struct {
prop_name: String,
// added after the name
counter: usize,
},
};
};
pub fn init(allocator: std.mem.Allocator, global: *JSGlobalObject, space_value: JSValue) JSError!Stringifier {
var prop_names: bun.StringHashMap(usize) = .init(allocator);
// always rename anchors named "root" to avoid collision with
// root anchor/alias
try prop_names.put("root", 0);
return .{
.stack_check = .init(),
.builder = .init(),
.indent = 0,
.known_collections = .init(allocator),
.array_item_counter = 0,
.prop_names = prop_names,
.space = try .init(global, space_value),
};
}
pub fn deinit(this: *Stringifier) void {
this.builder.deinit();
this.known_collections.deinit();
this.prop_names.deinit();
this.space.deinit();
}
const ValueOrigin = union(AnchorOrigin) {
root,
array_item,
prop_value: String,
};
pub fn findAnchorsAndAliases(this: *Stringifier, global: *JSGlobalObject, value: JSValue, origin: ValueOrigin) StringifyError!void {
if (!this.stack_check.isSafeToRecurse()) {
return error.StackOverflow;
}
const unwrapped = try value.unwrapBoxedPrimitive(global);
if (unwrapped.isNull()) {
return;
}
if (unwrapped.isNumber()) {
return;
}
if (unwrapped.isBigInt()) {
return global.throw("YAML.stringify cannot serialize BigInt", .{});
}
if (unwrapped.isBoolean()) {
return;
}
if (unwrapped.isString()) {
return;
}
if (comptime Environment.ci_assert) {
bun.assertWithLocation(unwrapped.isObject(), @src());
}
const object_entry = try this.known_collections.getOrPut(unwrapped);
if (object_entry.found_existing) {
// this will become an alias. increment counters here because
// now the anchor/alias is confirmed used.
if (object_entry.value_ptr.used) {
return;
}
object_entry.value_ptr.used = true;
switch (object_entry.value_ptr.name) {
.root => {
// only one possible
},
.array_item => |*counter| {
counter.* = this.array_item_counter;
this.array_item_counter += 1;
},
.prop_value => |*prop_value| {
const name_entry = try this.prop_names.getOrPut(prop_value.prop_name.byteSlice());
if (name_entry.found_existing) {
name_entry.value_ptr.* += 1;
} else {
name_entry.value_ptr.* = 0;
}
prop_value.counter = name_entry.value_ptr.*;
},
}
return;
}
object_entry.value_ptr.* = .init(origin);
if (unwrapped.isArray()) {
var iter = try unwrapped.arrayIterator(global);
while (try iter.next()) |item| {
if (item.isUndefined() or item.isSymbol() or item.isFunction()) {
continue;
}
try this.findAnchorsAndAliases(global, item, .array_item);
}
return;
}
var iter: jsc.JSPropertyIterator(.{ .skip_empty_name = false, .include_value = true }) = try .init(
global,
try unwrapped.toObject(global),
);
defer iter.deinit();
while (try iter.next()) |prop_name| {
if (iter.value.isUndefined() or iter.value.isSymbol() or iter.value.isFunction()) {
continue;
}
try this.findAnchorsAndAliases(global, iter.value, .{ .prop_value = prop_name });
}
}
const StringifyError = JSError || bun.StackOverflow;
pub fn stringify(this: *Stringifier, global: *JSGlobalObject, value: JSValue) StringifyError!void {
if (!this.stack_check.isSafeToRecurse()) {
return error.StackOverflow;
}
const unwrapped = try value.unwrapBoxedPrimitive(global);
if (unwrapped.isNull()) {
this.builder.append(.latin1, "null");
return;
}
if (unwrapped.isNumber()) {
if (unwrapped.isInt32()) {
this.builder.append(.int, unwrapped.asInt32());
return;
}
const num = unwrapped.asNumber();
if (std.math.isNegativeInf(num)) {
this.builder.append(.latin1, "-.inf");
// } else if (std.math.isPositiveInf(num)) {
// builder.append(.latin1, "+.inf");
} else if (std.math.isInf(num)) {
this.builder.append(.latin1, ".inf");
} else if (std.math.isNan(num)) {
this.builder.append(.latin1, ".nan");
} else if (std.math.isNegativeZero(num)) {
this.builder.append(.latin1, "-0");
} else if (std.math.isPositiveZero(num)) {
this.builder.append(.latin1, "+0");
} else {
this.builder.append(.double, num);
}
return;
}
if (unwrapped.isBigInt()) {
return global.throw("YAML.stringify cannot serialize BigInt", .{});
}
if (unwrapped.isBoolean()) {
if (unwrapped.asBoolean()) {
this.builder.append(.latin1, "true");
} else {
this.builder.append(.latin1, "false");
}
return;
}
if (unwrapped.isString()) {
const value_str = try unwrapped.toBunString(global);
defer value_str.deref();
this.appendString(value_str);
return;
}
if (comptime Environment.ci_assert) {
bun.assertWithLocation(unwrapped.isObject(), @src());
}
const has_anchor: ?*AnchorAlias = has_anchor: {
const anchor = this.known_collections.getPtr(unwrapped) orelse {
break :has_anchor null;
};
if (!anchor.used) {
break :has_anchor null;
}
break :has_anchor anchor;
};
if (has_anchor) |anchor| {
this.builder.append(.lchar, if (anchor.anchored) '*' else '&');
switch (anchor.name) {
.root => {
this.builder.append(.latin1, "root");
},
.array_item => {
this.builder.append(.latin1, "item");
this.builder.append(.usize, anchor.name.array_item);
},
.prop_value => |prop_value| {
if (prop_value.prop_name.length() == 0) {
this.builder.append(.latin1, "value");
this.builder.append(.usize, prop_value.counter);
} else {
this.builder.append(.string, anchor.name.prop_value.prop_name);
if (anchor.name.prop_value.counter != 0) {
this.builder.append(.usize, anchor.name.prop_value.counter);
}
}
},
}
if (anchor.anchored) {
return;
}
switch (this.space) {
.minified => {
this.builder.append(.lchar, ' ');
},
.number, .str => {
this.newline();
},
}
anchor.anchored = true;
}
if (unwrapped.isArray()) {
var iter = try unwrapped.arrayIterator(global);
if (iter.len == 0) {
this.builder.append(.latin1, "[]");
return;
}
switch (this.space) {
.minified => {
this.builder.append(.lchar, '[');
var first = true;
while (try iter.next()) |item| {
if (item.isUndefined() or item.isSymbol() or item.isFunction()) {
continue;
}
if (!first) {
this.builder.append(.lchar, ',');
}
first = false;
try this.stringify(global, item);
}
this.builder.append(.lchar, ']');
},
.number, .str => {
this.builder.ensureUnusedCapacity(iter.len * "- ".len);
var first = true;
while (try iter.next()) |item| {
if (item.isUndefined() or item.isSymbol() or item.isFunction()) {
continue;
}
if (!first) {
this.newline();
}
first = false;
this.builder.append(.latin1, "- ");
// don't need to print a newline here for any value
this.indent += 1;
try this.stringify(global, item);
this.indent -= 1;
}
},
}
return;
}
var iter: jsc.JSPropertyIterator(.{ .skip_empty_name = false, .include_value = true }) = try .init(
global,
try unwrapped.toObject(global),
);
defer iter.deinit();
if (iter.len == 0) {
this.builder.append(.latin1, "{}");
return;
}
switch (this.space) {
.minified => {
this.builder.append(.lchar, '{');
var first = true;
while (try iter.next()) |prop_name| {
if (iter.value.isUndefined() or iter.value.isSymbol() or iter.value.isFunction()) {
continue;
}
if (!first) {
this.builder.append(.lchar, ',');
}
first = false;
this.appendString(prop_name);
this.builder.append(.latin1, ": ");
try this.stringify(global, iter.value);
}
this.builder.append(.lchar, '}');
},
.number, .str => {
this.builder.ensureUnusedCapacity(iter.len * ": ".len);
var first = true;
while (try iter.next()) |prop_name| {
if (iter.value.isUndefined() or iter.value.isSymbol() or iter.value.isFunction()) {
continue;
}
if (!first) {
this.newline();
}
first = false;
this.appendString(prop_name);
this.builder.append(.latin1, ": ");
this.indent += 1;
if (propValueNeedsNewline(iter.value)) {
this.newline();
}
try this.stringify(global, iter.value);
this.indent -= 1;
}
},
}
}
/// Does this object property value need a newline? True for arrays and objects.
fn propValueNeedsNewline(value: JSValue) bool {
return !value.isNumber() and !value.isBoolean() and !value.isNull() and !value.isString();
}
fn newline(this: *Stringifier) void {
const indent_count = this.indent;
switch (this.space) {
.minified => {},
.number => |space_num| {
this.builder.append(.lchar, '\n');
this.builder.ensureUnusedCapacity(indent_count * space_num);
for (0..indent_count * space_num) |_| {
this.builder.append(.lchar, ' ');
}
},
.str => |space_str| {
this.builder.append(.lchar, '\n');
const clamped = if (space_str.length() > 10)
space_str.substringWithLen(0, 10)
else
space_str;
this.builder.ensureUnusedCapacity(indent_count * clamped.length());
for (0..indent_count) |_| {
this.builder.append(.string, clamped);
}
},
}
}
fn appendDoubleQuotedString(this: *Stringifier, str: String) void {
this.builder.append(.lchar, '"');
for (0..str.length()) |i| {
const c = str.charAt(i);
switch (c) {
0x00 => this.builder.append(.latin1, "\\0"),
0x01 => this.builder.append(.latin1, "\\x01"),
0x02 => this.builder.append(.latin1, "\\x02"),
0x03 => this.builder.append(.latin1, "\\x03"),
0x04 => this.builder.append(.latin1, "\\x04"),
0x05 => this.builder.append(.latin1, "\\x05"),
0x06 => this.builder.append(.latin1, "\\x06"),
0x07 => this.builder.append(.latin1, "\\a"), // bell
0x08 => this.builder.append(.latin1, "\\b"), // backspace
0x09 => this.builder.append(.latin1, "\\t"), // tab
0x0a => this.builder.append(.latin1, "\\n"), // line feed
0x0b => this.builder.append(.latin1, "\\v"), // vertical tab
0x0c => this.builder.append(.latin1, "\\f"), // form feed
0x0d => this.builder.append(.latin1, "\\r"), // carriage return
0x0e => this.builder.append(.latin1, "\\x0e"),
0x0f => this.builder.append(.latin1, "\\x0f"),
0x10 => this.builder.append(.latin1, "\\x10"),
0x11 => this.builder.append(.latin1, "\\x11"),
0x12 => this.builder.append(.latin1, "\\x12"),
0x13 => this.builder.append(.latin1, "\\x13"),
0x14 => this.builder.append(.latin1, "\\x14"),
0x15 => this.builder.append(.latin1, "\\x15"),
0x16 => this.builder.append(.latin1, "\\x16"),
0x17 => this.builder.append(.latin1, "\\x17"),
0x18 => this.builder.append(.latin1, "\\x18"),
0x19 => this.builder.append(.latin1, "\\x19"),
0x1a => this.builder.append(.latin1, "\\x1a"),
0x1b => this.builder.append(.latin1, "\\e"), // escape
0x1c => this.builder.append(.latin1, "\\x1c"),
0x1d => this.builder.append(.latin1, "\\x1d"),
0x1e => this.builder.append(.latin1, "\\x1e"),
0x1f => this.builder.append(.latin1, "\\x1f"),
0x22 => this.builder.append(.latin1, "\\\""), // "
0x5c => this.builder.append(.latin1, "\\\\"), // \
0x7f => this.builder.append(.latin1, "\\x7f"), // delete
0x85 => this.builder.append(.latin1, "\\N"), // next line
0xa0 => this.builder.append(.latin1, "\\_"), // non-breaking space
0xa8 => this.builder.append(.latin1, "\\L"), // line separator
0xa9 => this.builder.append(.latin1, "\\P"), // paragraph separator
0x20...0x21,
0x23...0x5b,
0x5d...0x7e,
0x80...0x84,
0x86...0x9f,
0xa1...0xa7,
0xaa...std.math.maxInt(u16),
=> this.builder.append(.uchar, c),
}
}
this.builder.append(.lchar, '"');
}
fn appendString(this: *Stringifier, str: String) void {
if (stringNeedsQuotes(str)) {
this.appendDoubleQuotedString(str);
return;
}
this.builder.append(.string, str);
}
fn stringNeedsQuotes(str: String) bool {
if (str.isEmpty()) {
return true;
}
switch (str.charAt(str.length() - 1)) {
// whitespace characters
' ',
'\t',
'\n',
'\r',
=> return true,
else => {},
}
switch (str.charAt(0)) {
// starting with indicators or whitespace requires quotes
'&',
'*',
'?',
'|',
'-',
'<',
'>',
'!',
'%',
'@',
' ',
'\t',
'\n',
'\r',
'#',
=> return true,
else => {},
}
const keywords = &.{
"true",
"True",
"TRUE",
"false",
"False",
"FALSE",
"yes",
"Yes",
"YES",
"no",
"No",
"NO",
"on",
"On",
"ON",
"off",
"Off",
"OFF",
"n",
"N",
"y",
"Y",
"null",
"Null",
"NULL",
"~",
".inf",
".Inf",
".INF",
".nan",
".NaN",
".NAN",
};
inline for (keywords) |keyword| {
if (str.eqlComptime(keyword)) {
return true;
}
}
var i: usize = 0;
while (i < str.length()) {
switch (str.charAt(i)) {
// flow indicators need to be quoted always
'{',
'}',
'[',
']',
',',
=> return true,
':',
=> {
if (i + 1 < str.length()) {
switch (str.charAt(i + 1)) {
' ',
'\t',
'\n',
'\r',
=> return true,
else => {},
}
}
i += 1;
},
'#',
'`',
'\'',
=> return true,
'-' => {
if (i + 2 < str.length() and str.charAt(i + 1) == '-' and str.charAt(i + 2) == '-') {
if (i + 3 >= str.length()) {
return true;
}
switch (str.charAt(i + 3)) {
' ',
'\t',
'\r',
'\n',
'[',
']',
'{',
'}',
',',
=> return true,
else => {},
}
}
if (i == 0 and stringIsNumber(str, &i)) {
return true;
}
i += 1;
},
'.' => {
if (i + 2 < str.length() and str.charAt(i + 1) == '.' and str.charAt(i + 2) == '.') {
if (i + 3 >= str.length()) {
return true;
}
switch (str.charAt(i + 3)) {
' ',
'\t',
'\r',
'\n',
'[',
']',
'{',
'}',
',',
=> return true,
else => {},
}
}
if (i == 0 and stringIsNumber(str, &i)) {
return true;
}
i += 1;
},
'0'...'9' => {
if (i == 0 and stringIsNumber(str, &i)) {
return true;
}
i += 1;
},
0x00...0x1f,
0x22,
0x7f,
0x85,
0xa0,
0xa8,
0xa9,
=> return true,
else => {
i += 1;
},
}
}
return false;
}
fn stringIsNumber(str: String, offset: *usize) bool {
const start = offset.*;
var i = start;
var @"+" = false;
var @"-" = false;
var e = false;
var dot = false;
var base: enum { dec, hex, oct } = .dec;
next: switch (str.charAt(i)) {
'.' => {
if (dot or base != .dec) {
offset.* = i;
return false;
}
dot = true;
i += 1;
if (i < str.length()) {
continue :next str.charAt(i);
}
return true;
},
'+' => {
if (@"+") {
offset.* = i;
return false;
}
@"+" = true;
i += 1;
if (i < str.length()) {
continue :next str.charAt(i);
}
return true;
},
'-' => {
if (@"-") {
offset.* = i;
return false;
}
@"-" = true;
i += 1;
if (i < str.length()) {
continue :next str.charAt(i);
}
return true;
},
'0' => {
if (i == start) {
if (i + 1 < str.length()) {
const nc = str.charAt(i + 1);
if (nc == 'x' or nc == 'X') {
base = .hex;
} else if (nc == 'o' or nc == 'O') {
base = .oct;
} else {
offset.* = i;
return false;
}
i += 1;
} else {
return true;
}
}
i += 1;
if (i < str.length()) {
continue :next str.charAt(i);
}
return true;
},
'e',
'E',
=> {
if (base == .oct or (e and base == .dec)) {
offset.* = i;
return false;
}
e = true;
i += 1;
if (i < str.length()) {
continue :next str.charAt(i);
}
return true;
},
'a'...'d',
'f',
'A'...'D',
'F',
=> {
if (base != .hex) {
offset.* = i;
return false;
}
i += 1;
if (i < str.length()) {
continue :next str.charAt(i);
}
return true;
},
'1'...'9' => {
i += 1;
if (i < str.length()) {
continue :next str.charAt(i);
}
return true;
},
else => {
offset.* = i;
return false;
},
}
}
};
pub fn parse( pub fn parse(
global: *jsc.JSGlobalObject, global: *jsc.JSGlobalObject,
callFrame: *jsc.CallFrame, callFrame: *jsc.CallFrame,
@@ -23,8 +911,10 @@ pub fn parse(
const input_value = callFrame.argumentsAsArray(1)[0]; const input_value = callFrame.argumentsAsArray(1)[0];
const input_str = try input_value.toBunString(global); const input: jsc.Node.BlobOrStringOrBuffer = try jsc.Node.BlobOrStringOrBuffer.fromJS(global, arena.allocator(), input_value) orelse input: {
const input = input_str.toSlice(arena.allocator()); const str = try input_value.toBunString(global);
break :input .{ .string_or_buffer = .{ .string = str.toSlice(arena.allocator()) } };
};
defer input.deinit(); defer input.deinit();
var log = logger.Log.init(bun.default_allocator); var log = logger.Log.init(bun.default_allocator);
@@ -75,12 +965,18 @@ const ParserCtx = struct {
ctx.result = .zero; ctx.result = .zero;
return; return;
}, },
error.StackOverflow => {
ctx.result = ctx.global.throwStackOverflow() catch .zero;
return;
},
}; };
} }
pub fn toJS(ctx: *ParserCtx, args: *MarkedArgumentBuffer, expr: Expr) JSError!JSValue { const ToJSError = JSError || bun.StackOverflow;
pub fn toJS(ctx: *ParserCtx, args: *MarkedArgumentBuffer, expr: Expr) ToJSError!JSValue {
if (!ctx.stack_check.isSafeToRecurse()) { if (!ctx.stack_check.isSafeToRecurse()) {
return ctx.global.throwStackOverflow(); return error.StackOverflow;
} }
switch (expr.data) { switch (expr.data) {
.e_null => return .null, .e_null => return .null,
@@ -143,7 +1039,9 @@ const ParserCtx = struct {
const std = @import("std"); const std = @import("std");
const bun = @import("bun"); const bun = @import("bun");
const Environment = bun.Environment;
const JSError = bun.JSError; const JSError = bun.JSError;
const String = bun.String;
const default_allocator = bun.default_allocator; const default_allocator = bun.default_allocator;
const logger = bun.logger; const logger = bun.logger;
const YAML = bun.interchange.yaml.YAML; const YAML = bun.interchange.yaml.YAML;
@@ -156,3 +1054,4 @@ const JSGlobalObject = jsc.JSGlobalObject;
const JSValue = jsc.JSValue; const JSValue = jsc.JSValue;
const MarkedArgumentBuffer = jsc.MarkedArgumentBuffer; const MarkedArgumentBuffer = jsc.MarkedArgumentBuffer;
const ZigString = jsc.ZigString; const ZigString = jsc.ZigString;
const wtf = bun.jsc.wtf;

View File

@@ -717,6 +717,21 @@ WTF::String BunString::toWTFString() const
return WTF::String(); return WTF::String();
} }
void BunString::appendToBuilder(WTF::StringBuilder& builder) const
{
if (this->tag == BunStringTag::WTFStringImpl) {
builder.append(this->impl.wtf);
return;
}
if (this->tag == BunStringTag::ZigString || this->tag == BunStringTag::StaticZigString) {
Zig::appendToBuilder(this->impl.zig, builder);
return;
}
// append nothing for BunStringTag::Dead and BunStringTag::Empty
}
WTF::String BunString::toWTFString(ZeroCopyTag) const WTF::String BunString::toWTFString(ZeroCopyTag) const
{ {
if (this->tag == BunStringTag::ZigString) { if (this->tag == BunStringTag::ZigString) {

View File

@@ -1,5 +1,5 @@
// TODO determine size and alignment automatically // TODO determine size and alignment automatically
const size = 56; const size = if (Environment.allow_assert or Environment.enable_asan) 56 else 8;
const alignment = 8; const alignment = 8;
/// Binding for JSC::CatchScope. This should be used rarely, only at translation boundaries between /// Binding for JSC::CatchScope. This should be used rarely, only at translation boundaries between

View File

@@ -2,6 +2,17 @@
using JSC::CatchScope; using JSC::CatchScope;
#if ENABLE(EXCEPTION_SCOPE_VERIFICATION)
#define ExpectedCatchScopeSize 56
#define ExpectedCatchScopeAlignment 8
#else
#define ExpectedCatchScopeSize 8
#define ExpectedCatchScopeAlignment 8
#endif
static_assert(sizeof(CatchScope) == ExpectedCatchScopeSize, "CatchScope.zig assumes CatchScope is 56 bytes");
static_assert(alignof(CatchScope) == ExpectedCatchScopeAlignment, "CatchScope.zig assumes CatchScope is 8-byte aligned");
extern "C" void CatchScope__construct( extern "C" void CatchScope__construct(
void* ptr, void* ptr,
JSC::JSGlobalObject* globalObject, JSC::JSGlobalObject* globalObject,

View File

@@ -130,18 +130,19 @@ static EncodedJSValue getOwnProxyObject(JSPropertyIterator* iter, JSObject* obje
extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValue(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, BunString* propertyName, size_t i) extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValue(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, BunString* propertyName, size_t i)
{ {
const auto& prop = iter->properties->propertyNameVector()[i];
if (iter->isSpecialProxy) [[unlikely]] {
return getOwnProxyObject(iter, object, prop, propertyName);
}
auto& vm = iter->vm; auto& vm = iter->vm;
auto scope = DECLARE_THROW_SCOPE(vm); auto scope = DECLARE_THROW_SCOPE(vm);
const auto& prop = iter->properties->propertyNameVector()[i];
if (iter->isSpecialProxy) [[unlikely]] {
RELEASE_AND_RETURN(scope, getOwnProxyObject(iter, object, prop, propertyName));
}
// This has to be get because we may need to call on prototypes // This has to be get because we may need to call on prototypes
// If we meant for this to only run for own keys, the property name would not be included in the array. // If we meant for this to only run for own keys, the property name would not be included in the array.
PropertySlot slot(object, PropertySlot::InternalMethodType::Get); PropertySlot slot(object, PropertySlot::InternalMethodType::Get);
if (!object->getPropertySlot(globalObject, prop, slot)) { if (!object->getPropertySlot(globalObject, prop, slot)) {
return {}; RELEASE_AND_RETURN(scope, {});
} }
RETURN_IF_EXCEPTION(scope, {}); RETURN_IF_EXCEPTION(scope, {});
@@ -154,13 +155,14 @@ extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValue(JSPropertyIte
extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValueNonObservable(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, BunString* propertyName, size_t i) extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValueNonObservable(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, BunString* propertyName, size_t i)
{ {
const auto& prop = iter->properties->propertyNameVector()[i];
if (iter->isSpecialProxy) [[unlikely]] {
return getOwnProxyObject(iter, object, prop, propertyName);
}
auto& vm = iter->vm; auto& vm = iter->vm;
auto scope = DECLARE_THROW_SCOPE(vm); auto scope = DECLARE_THROW_SCOPE(vm);
const auto& prop = iter->properties->propertyNameVector()[i];
if (iter->isSpecialProxy) [[unlikely]] {
RELEASE_AND_RETURN(scope, getOwnProxyObject(iter, object, prop, propertyName));
}
PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry, vm.ptr()); PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry, vm.ptr());
auto has = object->getNonIndexPropertySlot(globalObject, prop, slot); auto has = object->getNonIndexPropertySlot(globalObject, prop, slot);
RETURN_IF_EXCEPTION(scope, {}); RETURN_IF_EXCEPTION(scope, {});

View File

@@ -1271,6 +1271,17 @@ pub const JSValue = enum(i64) {
return if (this.isObject()) this.uncheckedPtrCast(JSObject) else null; return if (this.isObject()) this.uncheckedPtrCast(JSObject) else null;
} }
/// Unwraps Number, Boolean, String, and BigInt objects to their primitive forms.
pub fn unwrapBoxedPrimitive(this: JSValue, global: *JSGlobalObject) JSError!JSValue {
var scope: CatchScope = undefined;
scope.init(global, @src());
defer scope.deinit();
const result = JSC__JSValue__unwrapBoxedPrimitive(global, this);
try scope.returnIfException();
return result;
}
extern fn JSC__JSValue__unwrapBoxedPrimitive(*JSGlobalObject, JSValue) JSValue;
extern fn JSC__JSValue__getPrototype(this: JSValue, globalObject: *JSGlobalObject) JSValue; extern fn JSC__JSValue__getPrototype(this: JSValue, globalObject: *JSGlobalObject) JSValue;
pub fn getPrototype(this: JSValue, globalObject: *JSGlobalObject) JSValue { pub fn getPrototype(this: JSValue, globalObject: *JSGlobalObject) JSValue {
return JSC__JSValue__getPrototype(this, globalObject); return JSC__JSValue__getPrototype(this, globalObject);

View File

@@ -0,0 +1,91 @@
const StringBuilder = @This();
const size = 24;
const alignment = 8;
bytes: [size]u8 align(alignment),
pub inline fn init() StringBuilder {
var this: StringBuilder = undefined;
StringBuilder__init(&this.bytes);
return this;
}
extern fn StringBuilder__init(*anyopaque) void;
pub fn deinit(this: *StringBuilder) void {
StringBuilder__deinit(&this.bytes);
}
extern fn StringBuilder__deinit(*anyopaque) void;
const Append = enum {
latin1,
utf16,
double,
int,
usize,
string,
lchar,
uchar,
quoted_json_string,
pub fn Type(comptime this: Append) type {
return switch (this) {
.latin1 => []const u8,
.utf16 => []const u16,
.double => f64,
.int => i32,
.usize => usize,
.string => String,
.lchar => u8,
.uchar => u16,
.quoted_json_string => String,
};
}
};
pub fn append(this: *StringBuilder, comptime append_type: Append, value: append_type.Type()) void {
switch (comptime append_type) {
.latin1 => StringBuilder__appendLatin1(&this.bytes, value.ptr, value.len),
.utf16 => StringBuilder__appendUtf16(&this.bytes, value.ptr, value.len),
.double => StringBuilder__appendDouble(&this.bytes, value),
.int => StringBuilder__appendInt(&this.bytes, value),
.usize => StringBuilder__appendUsize(&this.bytes, value),
.string => StringBuilder__appendString(&this.bytes, value),
.lchar => StringBuilder__appendLChar(&this.bytes, value),
.uchar => StringBuilder__appendUChar(&this.bytes, value),
.quoted_json_string => StringBuilder__appendQuotedJsonString(&this.bytes, value),
}
}
extern fn StringBuilder__appendLatin1(*anyopaque, str: [*]const u8, len: usize) void;
extern fn StringBuilder__appendUtf16(*anyopaque, str: [*]const u16, len: usize) void;
extern fn StringBuilder__appendDouble(*anyopaque, num: f64) void;
extern fn StringBuilder__appendInt(*anyopaque, num: i32) void;
extern fn StringBuilder__appendUsize(*anyopaque, num: usize) void;
extern fn StringBuilder__appendString(*anyopaque, str: String) void;
extern fn StringBuilder__appendLChar(*anyopaque, c: u8) void;
extern fn StringBuilder__appendUChar(*anyopaque, c: u16) void;
extern fn StringBuilder__appendQuotedJsonString(*anyopaque, str: String) void;
pub fn toString(this: *StringBuilder, global: *JSGlobalObject) JSError!JSValue {
var scope: jsc.CatchScope = undefined;
scope.init(global, @src());
defer scope.deinit();
const result = StringBuilder__toString(&this.bytes, global);
try scope.returnIfException();
return result;
}
extern fn StringBuilder__toString(*anyopaque, global: *JSGlobalObject) JSValue;
pub fn ensureUnusedCapacity(this: *StringBuilder, additional: usize) void {
StringBuilder__ensureUnusedCapacity(&this.bytes, additional);
}
extern fn StringBuilder__ensureUnusedCapacity(*anyopaque, usize) void;
const bun = @import("bun");
const JSError = bun.JSError;
const String = bun.String;
const jsc = bun.jsc;
const JSGlobalObject = jsc.JSGlobalObject;
const JSValue = jsc.JSValue;

View File

@@ -0,0 +1,81 @@
#include "root.h"
#include "BunString.h"
#include "headers-handwritten.h"
static_assert(sizeof(WTF::StringBuilder) == 24, "StringBuilder.zig assumes WTF::StringBuilder is 24 bytes");
static_assert(alignof(WTF::StringBuilder) == 8, "StringBuilder.zig assumes WTF::StringBuilder is 8-byte aligned");
extern "C" void StringBuilder__init(WTF::StringBuilder* ptr)
{
new (ptr) WTF::StringBuilder(OverflowPolicy::RecordOverflow);
}
extern "C" void StringBuilder__deinit(WTF::StringBuilder* builder)
{
builder->~StringBuilder();
}
extern "C" void StringBuilder__appendLatin1(WTF::StringBuilder* builder, LChar const* ptr, size_t len)
{
builder->append({ ptr, len });
}
extern "C" void StringBuilder__appendUtf16(WTF::StringBuilder* builder, UChar const* ptr, size_t len)
{
builder->append({ ptr, len });
}
extern "C" void StringBuilder__appendDouble(WTF::StringBuilder* builder, double num)
{
builder->append(num);
}
extern "C" void StringBuilder__appendInt(WTF::StringBuilder* builder, int32_t num)
{
builder->append(num);
}
extern "C" void StringBuilder__appendUsize(WTF::StringBuilder* builder, size_t num)
{
builder->append(num);
}
extern "C" void StringBuilder__appendString(WTF::StringBuilder* builder, BunString str)
{
str.appendToBuilder(*builder);
}
extern "C" void StringBuilder__appendLChar(WTF::StringBuilder* builder, LChar c)
{
builder->append(c);
}
extern "C" void StringBuilder__appendUChar(WTF::StringBuilder* builder, UChar c)
{
builder->append(c);
}
extern "C" void StringBuilder__appendQuotedJsonString(WTF::StringBuilder* builder, BunString str)
{
auto string = str.toWTFString(BunString::ZeroCopy);
builder->appendQuotedJSONString(string);
}
extern "C" JSC::EncodedJSValue StringBuilder__toString(WTF::StringBuilder* builder, JSC::JSGlobalObject* globalObject)
{
auto& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (builder->hasOverflowed()) [[unlikely]] {
JSC::throwOutOfMemoryError(globalObject, scope);
return JSC::JSValue::encode({});
}
auto str = builder->toString();
return JSC::JSValue::encode(JSC::jsString(vm, str));
}
extern "C" void StringBuilder__ensureUnusedCapacity(WTF::StringBuilder* builder, size_t additional)
{
builder->reserveCapacity(builder->length() + additional);
}

View File

@@ -36,6 +36,8 @@ pub const WTF = struct {
return buffer[0..@intCast(res)]; return buffer[0..@intCast(res)];
} }
pub const StringBuilder = @import("./StringBuilder.zig");
}; };
const bun = @import("bun"); const bun = @import("bun");

View File

@@ -36,6 +36,7 @@
#include "JavaScriptCore/JSArrayBuffer.h" #include "JavaScriptCore/JSArrayBuffer.h"
#include "JavaScriptCore/JSArrayInlines.h" #include "JavaScriptCore/JSArrayInlines.h"
#include "JavaScriptCore/ErrorInstanceInlines.h" #include "JavaScriptCore/ErrorInstanceInlines.h"
#include "JavaScriptCore/BigIntObject.h"
#include "JavaScriptCore/JSCallbackObject.h" #include "JavaScriptCore/JSCallbackObject.h"
#include "JavaScriptCore/JSClassRef.h" #include "JavaScriptCore/JSClassRef.h"
@@ -2096,6 +2097,28 @@ BunString WebCore__DOMURL__fileSystemPath(WebCore::DOMURL* arg0, int* errorCode)
return BunString { BunStringTag::Dead, nullptr }; return BunString { BunStringTag::Dead, nullptr };
} }
// Taken from unwrapBoxedPrimitive in JSONObject.cpp in WebKit
extern "C" JSC::EncodedJSValue JSC__JSValue__unwrapBoxedPrimitive(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
{
JSValue value = JSValue::decode(encodedValue);
if (!value.isObject()) {
return JSValue::encode(value);
}
JSObject* object = asObject(value);
if (object->inherits<NumberObject>()) {
return JSValue::encode(jsNumber(object->toNumber(globalObject)));
}
if (object->inherits<StringObject>())
return JSValue::encode(object->toString(globalObject));
if (object->inherits<BooleanObject>() || object->inherits<BigIntObject>())
return JSValue::encode(jsCast<JSWrapperObject*>(object)->internalValue());
return JSValue::encode(object);
}
extern "C" JSC::EncodedJSValue ZigString__toJSONObject(const ZigString* strPtr, JSC::JSGlobalObject* globalObject) extern "C" JSC::EncodedJSValue ZigString__toJSONObject(const ZigString* strPtr, JSC::JSGlobalObject* globalObject)
{ {
ASSERT_NO_PENDING_EXCEPTION(globalObject); ASSERT_NO_PENDING_EXCEPTION(globalObject);

View File

@@ -81,6 +81,8 @@ typedef struct BunString {
bool isEmpty() const; bool isEmpty() const;
void appendToBuilder(WTF::StringBuilder& builder) const;
} BunString; } BunString;
typedef struct ZigErrorType { typedef struct ZigErrorType {

View File

@@ -183,6 +183,24 @@ static const WTF::String toStringCopy(ZigString str)
} }
} }
static void appendToBuilder(ZigString str, WTF::StringBuilder& builder)
{
if (str.len == 0 || str.ptr == nullptr) {
return;
}
if (isTaggedUTF8Ptr(str.ptr)) [[unlikely]] {
WTF::String converted = WTF::String::fromUTF8ReplacingInvalidSequences(std::span { untag(str.ptr), str.len });
builder.append(converted);
return;
}
if (isTaggedUTF16Ptr(str.ptr)) {
builder.append({ reinterpret_cast<const char16_t*>(untag(str.ptr)), str.len });
return;
}
builder.append({ untag(str.ptr), str.len });
}
static WTF::String toStringNotConst(ZigString str) { return toString(str); } static WTF::String toStringNotConst(ZigString str) { return toString(str); }
static const JSC::JSString* toJSString(ZigString str, JSC::JSGlobalObject* global) static const JSC::JSString* toJSString(ZigString str, JSC::JSGlobalObject* global)

View File

@@ -3720,7 +3720,7 @@ pub noinline fn throwStackOverflow() StackOverflow!void {
@branchHint(.cold); @branchHint(.cold);
return error.StackOverflow; return error.StackOverflow;
} }
const StackOverflow = error{StackOverflow}; pub const StackOverflow = error{StackOverflow};
pub const S3 = @import("./s3/client.zig"); pub const S3 = @import("./s3/client.zig");

View File

@@ -866,19 +866,8 @@ pub const String = extern struct {
bun.assert(index < this.length()); bun.assert(index < this.length());
} }
return switch (this.tag) { return switch (this.tag) {
.WTFStringImpl => if (this.value.WTFStringImpl.is8Bit()) @intCast(this.value.WTFStringImpl.utf8Slice()[index]) else this.value.WTFStringImpl.utf16Slice()[index], .WTFStringImpl => if (this.value.WTFStringImpl.is8Bit()) this.value.WTFStringImpl.latin1Slice()[index] else this.value.WTFStringImpl.utf16Slice()[index],
.ZigString, .StaticZigString => if (!this.value.ZigString.is16Bit()) @intCast(this.value.ZigString.slice()[index]) else this.value.ZigString.utf16Slice()[index], .ZigString, .StaticZigString => if (!this.value.ZigString.is16Bit()) this.value.ZigString.slice()[index] else this.value.ZigString.utf16Slice()[index],
else => 0,
};
}
pub fn charAtU8(this: String, index: usize) u8 {
if (comptime bun.Environment.allow_assert) {
bun.assert(index < this.length());
}
return switch (this.tag) {
.WTFStringImpl => if (this.value.WTFStringImpl.is8Bit()) this.value.WTFStringImpl.utf8Slice()[index] else @truncate(this.value.WTFStringImpl.utf16Slice()[index]),
.ZigString, .StaticZigString => if (!this.value.ZigString.is16Bit()) this.value.ZigString.slice()[index] else @truncate(this.value.ZigString.utf16SliceAligned()[index]),
else => 0, else => 0,
}; };
} }
@@ -1178,10 +1167,6 @@ pub const SliceWithUnderlyingString = struct {
return this.utf8.slice(); return this.utf8.slice();
} }
pub fn sliceZ(this: SliceWithUnderlyingString) [:0]const u8 {
return this.utf8.sliceZ();
}
pub fn format(self: SliceWithUnderlyingString, comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void { pub fn format(self: SliceWithUnderlyingString, comptime fmt: []const u8, opts: std.fmt.FormatOptions, writer: anytype) !void {
if (self.utf8.len == 0) { if (self.utf8.len == 0) {
try self.underlying.format(fmt, opts, writer); try self.underlying.format(fmt, opts, writer);

View File

@@ -3,3 +3,8 @@ import { expectType } from "./utilities";
expectType(Bun.YAML.parse("")).is<unknown>(); expectType(Bun.YAML.parse("")).is<unknown>();
// @ts-expect-error // @ts-expect-error
expectType(Bun.YAML.parse({})).is<unknown>(); expectType(Bun.YAML.parse({})).is<unknown>();
expectType(Bun.YAML.stringify({ abc: "def"})).is<string>();
// @ts-expect-error
expectType(Bun.YAML.stringify("hi", {})).is<string>();
// @ts-expect-error
expectType(Bun.YAML.stringify("hi", null, 123n)).is<string>();

File diff suppressed because it is too large Load Diff