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

@@ -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)
{
const auto& prop = iter->properties->propertyNameVector()[i];
if (iter->isSpecialProxy) [[unlikely]] {
return getOwnProxyObject(iter, object, prop, propertyName);
}
auto& vm = iter->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
// 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);
if (!object->getPropertySlot(globalObject, prop, slot)) {
return {};
RELEASE_AND_RETURN(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)
{
const auto& prop = iter->properties->propertyNameVector()[i];
if (iter->isSpecialProxy) [[unlikely]] {
return getOwnProxyObject(iter, object, prop, propertyName);
}
auto& vm = iter->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());
auto has = object->getNonIndexPropertySlot(globalObject, prop, slot);
RETURN_IF_EXCEPTION(scope, {});