mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
### 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>
195 lines
6.5 KiB
C++
195 lines
6.5 KiB
C++
#include "root.h"
|
|
|
|
#include "BunClientData.h"
|
|
#include "ZigGlobalObject.h"
|
|
#include "JavaScriptCore/JSType.h"
|
|
#include "JavaScriptCore/EnumerationMode.h"
|
|
#include "JavaScriptCore/ExceptionScope.h"
|
|
#include "JavaScriptCore/ThrowScope.h"
|
|
#include "JavaScriptCore/JSCJSValue.h"
|
|
#include "JavaScriptCore/JSGlobalObject.h"
|
|
#include "JavaScriptCore/PropertyNameArray.h"
|
|
#include "wtf/Assertions.h"
|
|
#include "wtf/FastMalloc.h"
|
|
#include "headers-handwritten.h"
|
|
#include "ObjectBindings.h"
|
|
|
|
namespace Bun {
|
|
using namespace JSC;
|
|
|
|
class JSPropertyIterator {
|
|
public:
|
|
JSPropertyIterator(JSC::VM& m_vm, RefPtr<JSC::PropertyNameArrayData> m_properties)
|
|
: vm(m_vm)
|
|
, properties(m_properties)
|
|
{
|
|
}
|
|
|
|
RefPtr<JSC::PropertyNameArrayData> properties;
|
|
Ref<JSC::VM> vm;
|
|
bool isSpecialProxy = false;
|
|
static JSPropertyIterator* create(JSC::VM& vm, RefPtr<JSC::PropertyNameArrayData> data)
|
|
{
|
|
return new JSPropertyIterator(vm, data);
|
|
}
|
|
|
|
WTF_DEPRECATED_MAKE_FAST_ALLOCATED(JSPropertyIterator);
|
|
};
|
|
|
|
extern "C" JSPropertyIterator* Bun__JSPropertyIterator__create(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue encodedValue, size_t* count, bool own_properties_only, bool only_non_index_properties)
|
|
{
|
|
auto& vm = JSC::getVM(globalObject);
|
|
JSC::JSValue value = JSValue::decode(encodedValue);
|
|
JSC::JSObject* object = value.getObject();
|
|
ASSERT(object);
|
|
ASSERT(count);
|
|
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
JSC::PropertyNameArray array(vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Exclude);
|
|
|
|
if (object->hasNonReifiedStaticProperties()) [[unlikely]] {
|
|
object->reifyAllStaticProperties(globalObject);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
}
|
|
|
|
#if OS(WINDOWS)
|
|
if (object->type() == JSC::ProxyObjectType) [[unlikely]] {
|
|
// Check if we're actually iterating through the JSEnvironmentVariableMap's proxy.
|
|
auto* zigGlobal = defaultGlobalObject(globalObject);
|
|
if (zigGlobal->m_processEnvObject.isInitialized()) {
|
|
if (object == zigGlobal->m_processEnvObject.get(zigGlobal)) {
|
|
object->methodTable()->getOwnPropertyNames(
|
|
object,
|
|
globalObject,
|
|
array,
|
|
DontEnumPropertiesMode::Exclude);
|
|
RETURN_IF_EXCEPTION(scope, nullptr);
|
|
|
|
*count = array.size();
|
|
if (array.size() == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto* iter = JSPropertyIterator::create(vm, array.releaseData());
|
|
iter->isSpecialProxy = true;
|
|
return iter;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (own_properties_only) {
|
|
if (only_non_index_properties) {
|
|
object->getOwnNonIndexPropertyNames(globalObject, array, DontEnumPropertiesMode::Exclude);
|
|
} else {
|
|
object->methodTable()->getOwnPropertyNames(object, globalObject, array, DontEnumPropertiesMode::Exclude);
|
|
}
|
|
} else {
|
|
object->getPropertyNames(globalObject, array, DontEnumPropertiesMode::Exclude);
|
|
}
|
|
RETURN_IF_EXCEPTION(scope, nullptr);
|
|
|
|
*count = array.size();
|
|
if (array.size() == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
return JSPropertyIterator::create(vm, array.releaseData());
|
|
}
|
|
|
|
extern "C" size_t Bun__JSPropertyIterator__getLongestPropertyName(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object)
|
|
{
|
|
size_t longest = 0;
|
|
for (const auto& prop : iter->properties->propertyNameVector()) {
|
|
if (prop.length() > longest) {
|
|
longest = prop.length();
|
|
}
|
|
}
|
|
|
|
return longest;
|
|
}
|
|
|
|
static EncodedJSValue getOwnProxyObject(JSPropertyIterator* iter, JSObject* object, const JSC::Identifier& prop, BunString* propertyName)
|
|
{
|
|
auto& vm = iter->vm;
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
PropertySlot slot(object, PropertySlot::InternalMethodType::GetOwnProperty, nullptr);
|
|
auto* globalObject = object->globalObject();
|
|
if (!object->methodTable()->getOwnPropertySlot(object, globalObject, prop, slot)) {
|
|
return {};
|
|
}
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
JSValue result = slot.getValue(globalObject, prop);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
*propertyName = Bun::toString(prop.impl());
|
|
return JSValue::encode(result);
|
|
}
|
|
|
|
extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValue(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, BunString* propertyName, size_t i)
|
|
{
|
|
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)) {
|
|
RELEASE_AND_RETURN(scope, {});
|
|
}
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
JSValue result = slot.getValue(globalObject, prop);
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
*propertyName = Bun::toString(prop.impl());
|
|
return JSValue::encode(result);
|
|
}
|
|
|
|
extern "C" EncodedJSValue Bun__JSPropertyIterator__getNameAndValueNonObservable(JSPropertyIterator* iter, JSC::JSGlobalObject* globalObject, JSC::JSObject* object, BunString* propertyName, size_t i)
|
|
{
|
|
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, {});
|
|
if (!has) {
|
|
return {};
|
|
}
|
|
if (slot.isAccessor() || slot.isCustom()) {
|
|
return {};
|
|
}
|
|
|
|
JSValue result = slot.getPureResult();
|
|
RETURN_IF_EXCEPTION(scope, {});
|
|
|
|
*propertyName = Bun::toString(prop.impl());
|
|
return JSValue::encode(result);
|
|
}
|
|
|
|
extern "C" void Bun__JSPropertyIterator__getName(JSPropertyIterator* iter, BunString* propertyName, size_t i)
|
|
{
|
|
const auto& prop = iter->properties->propertyNameVector()[i];
|
|
*propertyName = Bun::toString(prop.impl());
|
|
}
|
|
|
|
extern "C" void Bun__JSPropertyIterator__deinit(JSPropertyIterator* iter)
|
|
{
|
|
delete iter;
|
|
}
|
|
|
|
}
|