mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 23:18:47 +00:00
Compare commits
2 Commits
bun-v1.3.3
...
claude/yam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d24858da6d | ||
|
|
18896d297a |
@@ -92,6 +92,7 @@ src/bun.js/bindings/JSWrappingFunction.cpp
|
||||
src/bun.js/bindings/JSX509Certificate.cpp
|
||||
src/bun.js/bindings/JSX509CertificateConstructor.cpp
|
||||
src/bun.js/bindings/JSX509CertificatePrototype.cpp
|
||||
src/bun.js/bindings/JSYAML.cpp
|
||||
src/bun.js/bindings/linux_perf_tracing.cpp
|
||||
src/bun.js/bindings/MarkingConstraint.cpp
|
||||
src/bun.js/bindings/ModuleLoader.cpp
|
||||
|
||||
@@ -86,6 +86,8 @@ static JSValue BunObject_getter_wrap_ArrayBufferSink(VM& vm, JSObject* bunObject
|
||||
|
||||
static JSValue constructCookieObject(VM& vm, JSObject* bunObject);
|
||||
static JSValue constructCookieMapObject(VM& vm, JSObject* bunObject);
|
||||
extern JSValue constructYAMLObject(VM& vm, JSObject* bunObject);
|
||||
static JSValue constructYAMLObjectWrapper(VM& vm, JSObject* bunObject);
|
||||
|
||||
static JSValue constructEnvObject(VM& vm, JSObject* object)
|
||||
{
|
||||
@@ -710,6 +712,7 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
SHA512 BunObject_getter_wrap_SHA512 DontDelete|PropertyCallback
|
||||
SHA512_256 BunObject_getter_wrap_SHA512_256 DontDelete|PropertyCallback
|
||||
TOML BunObject_getter_wrap_TOML DontDelete|PropertyCallback
|
||||
YAML constructYAMLObjectWrapper DontDelete|PropertyCallback
|
||||
Transpiler BunObject_getter_wrap_Transpiler DontDelete|PropertyCallback
|
||||
embeddedFiles BunObject_getter_wrap_embeddedFiles DontDelete|PropertyCallback
|
||||
S3Client BunObject_getter_wrap_S3Client DontDelete|PropertyCallback
|
||||
@@ -867,6 +870,13 @@ static JSValue constructCookieMapObject(VM& vm, JSObject* bunObject)
|
||||
return WebCore::JSCookieMap::getConstructor(vm, zigGlobalObject);
|
||||
}
|
||||
|
||||
extern JSValue constructYAMLObject(VM& vm, JSObject* bunObject);
|
||||
|
||||
static JSValue constructYAMLObjectWrapper(VM& vm, JSObject* bunObject)
|
||||
{
|
||||
return constructYAMLObject(vm, bunObject);
|
||||
}
|
||||
|
||||
JSC::JSObject* createBunObject(VM& vm, JSObject* globalObject)
|
||||
{
|
||||
return JSBunObject::create(vm, jsCast<Zig::GlobalObject*>(globalObject));
|
||||
|
||||
416
src/bun.js/bindings/JSYAML.cpp
Normal file
416
src/bun.js/bindings/JSYAML.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <JavaScriptCore/JSArray.h>
|
||||
#include <JavaScriptCore/DateInstance.h>
|
||||
#include <JavaScriptCore/PropertyNameArray.h>
|
||||
#include <wtf/text/StringBuilder.h>
|
||||
#include <wtf/HashMap.h>
|
||||
#include <wtf/HashSet.h>
|
||||
#include "ZigGlobalObject.h"
|
||||
#include "helpers.h"
|
||||
#include "wtf-bindings.h"
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
namespace Bun {
|
||||
|
||||
static String escapeYAMLString(const String& str)
|
||||
{
|
||||
// Check if string needs quoting
|
||||
bool needsQuotes = false;
|
||||
|
||||
// YAML reserved words and numeric strings
|
||||
if (str == "true"_s || str == "false"_s || str == "null"_s || str == "~"_s ||
|
||||
str == "yes"_s || str == "no"_s || str == "on"_s || str == "off"_s) {
|
||||
needsQuotes = true;
|
||||
}
|
||||
|
||||
// Check if string looks like a number
|
||||
if (!needsQuotes && !str.isEmpty()) {
|
||||
bool isNumeric = true;
|
||||
bool hasDot = false;
|
||||
for (unsigned i = 0; i < str.length(); i++) {
|
||||
auto ch = str[i];
|
||||
if (i == 0 && (ch == '-' || ch == '+')) {
|
||||
continue; // Allow leading sign
|
||||
}
|
||||
if (ch == '.' && !hasDot) {
|
||||
hasDot = true;
|
||||
continue;
|
||||
}
|
||||
if (ch < '0' || ch > '9') {
|
||||
isNumeric = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isNumeric) {
|
||||
needsQuotes = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for leading/trailing whitespace or internal spaces
|
||||
if (!str.isEmpty()) {
|
||||
if (str[0] == ' ' || str[str.length()-1] == ' ') {
|
||||
needsQuotes = true;
|
||||
}
|
||||
// Check for internal spaces when used as keys (this is a simplified check)
|
||||
for (unsigned i = 0; i < str.length(); i++) {
|
||||
if (str[i] == ' ') {
|
||||
needsQuotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for special characters
|
||||
for (unsigned i = 0; i < str.length(); i++) {
|
||||
auto ch = str[i];
|
||||
if (ch == '"' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' ||
|
||||
ch == ':' || ch == '[' || ch == ']' || ch == '{' || ch == '}' ||
|
||||
ch == '#' || ch == '&' || ch == '*' || ch == '!' || ch == '|' ||
|
||||
ch == '>' || ch == '\'' || ch == '%' || ch == '@' || ch == '`') {
|
||||
needsQuotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsQuotes) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// Escape and quote the string
|
||||
StringBuilder result;
|
||||
result.append('"');
|
||||
|
||||
for (unsigned i = 0; i < str.length(); i++) {
|
||||
auto ch = str[i];
|
||||
switch (ch) {
|
||||
case '"':
|
||||
result.append("\\\""_s);
|
||||
break;
|
||||
case '\\':
|
||||
result.append("\\\\"_s);
|
||||
break;
|
||||
case '\n':
|
||||
result.append("\\n"_s);
|
||||
break;
|
||||
case '\r':
|
||||
result.append("\\r"_s);
|
||||
break;
|
||||
case '\t':
|
||||
result.append("\\t"_s);
|
||||
break;
|
||||
default:
|
||||
result.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
result.append('"');
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// Forward declarations
|
||||
static String serializeYAMLValue(JSGlobalObject* globalObject, JSValue value, unsigned indent, HashMap<JSObject*, unsigned>& objectMap, unsigned& anchorCounter, HashSet<JSObject*>& visitedForCircular);
|
||||
|
||||
// Pre-pass to detect circular references
|
||||
static void detectCircularReferences(JSGlobalObject* globalObject, JSValue value, HashSet<JSObject*>& visiting, HashSet<JSObject*>& circular)
|
||||
{
|
||||
if (!value.isObject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSObject* object = value.getObject();
|
||||
|
||||
if (visiting.contains(object)) {
|
||||
circular.add(object);
|
||||
return;
|
||||
}
|
||||
|
||||
if (circular.contains(object)) {
|
||||
return;
|
||||
}
|
||||
|
||||
visiting.add(object);
|
||||
|
||||
if (value.inherits<JSArray>()) {
|
||||
JSArray* array = jsCast<JSArray*>(object);
|
||||
auto length = array->length();
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
JSValue element = array->getIndex(globalObject, i);
|
||||
detectCircularReferences(globalObject, element, visiting, circular);
|
||||
}
|
||||
} else {
|
||||
auto& vm = globalObject->vm();
|
||||
PropertyNameArray propertyNames(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
|
||||
object->getOwnNonIndexPropertyNames(globalObject, propertyNames, DontEnumPropertiesMode::Exclude);
|
||||
|
||||
for (auto& propertyName : propertyNames) {
|
||||
JSValue propValue = object->get(globalObject, propertyName);
|
||||
detectCircularReferences(globalObject, propValue, visiting, circular);
|
||||
}
|
||||
}
|
||||
|
||||
visiting.remove(object);
|
||||
}
|
||||
|
||||
static String serializeYAMLArray(JSGlobalObject* globalObject, JSArray* array, unsigned indent, HashMap<JSObject*, unsigned>& objectMap, unsigned& anchorCounter, HashSet<JSObject*>& visitedForCircular)
|
||||
{
|
||||
auto length = array->length();
|
||||
|
||||
if (length == 0) {
|
||||
return String("[]"_s);
|
||||
}
|
||||
|
||||
StringBuilder result;
|
||||
StringBuilder indentBuilder;
|
||||
for (unsigned i = 0; i < indent; i++) {
|
||||
indentBuilder.append(' ');
|
||||
}
|
||||
String indentStr = indentBuilder.toString();
|
||||
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
if (i > 0) {
|
||||
result.append('\n');
|
||||
}
|
||||
result.append(indentStr);
|
||||
result.append("- "_s);
|
||||
|
||||
JSValue element = array->getIndex(globalObject, i);
|
||||
String serializedElement = serializeYAMLValue(globalObject, element, indent + 2, objectMap, anchorCounter, visitedForCircular);
|
||||
|
||||
if (element.inherits<JSArray>() && !serializedElement.startsWith("*"_s)) {
|
||||
// For nested arrays, we want: "- - first_element\n - second_element\n ..."
|
||||
// The serializedElement comes with indentation, we need to restructure it
|
||||
auto lines = serializedElement.split('\n');
|
||||
if (lines.size() > 0) {
|
||||
// First line should become "- first_element" (removing leading spaces and first dash)
|
||||
String firstLine = lines[0];
|
||||
unsigned trimStart = 0;
|
||||
while (trimStart < firstLine.length() && firstLine[trimStart] == ' ') {
|
||||
trimStart++;
|
||||
}
|
||||
// Should now be at "- element", we want just "- element"
|
||||
result.append(firstLine.substring(trimStart));
|
||||
|
||||
// Subsequent lines should be indented to align under the first element
|
||||
for (size_t lineIdx = 1; lineIdx < lines.size(); lineIdx++) {
|
||||
result.append('\n');
|
||||
result.append(indentStr);
|
||||
result.append(" "_s); // Align with the content after "- "
|
||||
|
||||
String line = lines[lineIdx];
|
||||
// Remove the original indentation
|
||||
unsigned lineTrimStart = 0;
|
||||
while (lineTrimStart < line.length() && line[lineTrimStart] == ' ') {
|
||||
lineTrimStart++;
|
||||
}
|
||||
result.append(line.substring(lineTrimStart));
|
||||
}
|
||||
}
|
||||
} else if (element.isObject() && !serializedElement.startsWith("*"_s)) {
|
||||
// Objects should have their first property on the same line as "- "
|
||||
// and subsequent properties indented to align with the first
|
||||
auto lines = serializedElement.split('\n');
|
||||
if (lines.size() > 0) {
|
||||
// First line should be after "- " with no extra indentation
|
||||
String firstLine = lines[0];
|
||||
// Remove leading whitespace since we already have "- "
|
||||
unsigned trimStart = 0;
|
||||
while (trimStart < firstLine.length() && firstLine[trimStart] == ' ') {
|
||||
trimStart++;
|
||||
}
|
||||
result.append(firstLine.substring(trimStart));
|
||||
|
||||
// Subsequent lines should be indented to align with the start of the first property
|
||||
for (size_t lineIdx = 1; lineIdx < lines.size(); lineIdx++) {
|
||||
result.append('\n');
|
||||
result.append(indentStr);
|
||||
result.append(" "_s); // Align with the content after "- "
|
||||
|
||||
String line = lines[lineIdx];
|
||||
// Remove the original indentation since we're adding our own
|
||||
unsigned lineTrimStart = 0;
|
||||
while (lineTrimStart < line.length() && line[lineTrimStart] == ' ') {
|
||||
lineTrimStart++;
|
||||
}
|
||||
result.append(line.substring(lineTrimStart));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.append(serializedElement);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
static String serializeYAMLObject(JSGlobalObject* globalObject, JSObject* object, unsigned indent, HashMap<JSObject*, unsigned>& objectMap, unsigned& anchorCounter, HashSet<JSObject*>& visitedForCircular)
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
|
||||
PropertyNameArray propertyNames(vm, PropertyNameMode::Strings, PrivateSymbolMode::Exclude);
|
||||
object->getOwnNonIndexPropertyNames(globalObject, propertyNames, DontEnumPropertiesMode::Exclude);
|
||||
|
||||
if (propertyNames.size() == 0) {
|
||||
return String("{}"_s);
|
||||
}
|
||||
|
||||
StringBuilder result;
|
||||
StringBuilder indentBuilder;
|
||||
for (unsigned i = 0; i < indent; i++) {
|
||||
indentBuilder.append(' ');
|
||||
}
|
||||
String indentStr = indentBuilder.toString();
|
||||
bool first = true;
|
||||
|
||||
for (auto& propertyName : propertyNames) {
|
||||
if (!first) {
|
||||
result.append('\n');
|
||||
}
|
||||
first = false;
|
||||
|
||||
result.append(indentStr);
|
||||
String keyStr = propertyName.string();
|
||||
result.append(escapeYAMLString(keyStr));
|
||||
result.append(": "_s);
|
||||
|
||||
JSValue value = object->get(globalObject, propertyName);
|
||||
String serializedValue = serializeYAMLValue(globalObject, value, indent + 2, objectMap, anchorCounter, visitedForCircular);
|
||||
|
||||
if (value.isObject() && (value.inherits<JSArray>() || value.inherits<JSObject>())) {
|
||||
if (serializedValue.startsWith("*"_s)) {
|
||||
// For aliases, keep them on the same line
|
||||
result.append(serializedValue);
|
||||
} else {
|
||||
result.append('\n');
|
||||
result.append(serializedValue);
|
||||
}
|
||||
} else {
|
||||
result.append(serializedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
static String serializeYAMLValue(JSGlobalObject* globalObject, JSValue value, unsigned indent, HashMap<JSObject*, unsigned>& objectMap, unsigned& anchorCounter, HashSet<JSObject*>& visitedForCircular)
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
|
||||
if (value.isNull()) {
|
||||
return String("null"_s);
|
||||
}
|
||||
|
||||
if (value.isUndefined()) {
|
||||
return String("null"_s); // YAML doesn't have undefined, use null
|
||||
}
|
||||
|
||||
if (value.isBoolean()) {
|
||||
return String(value.asBoolean() ? "true"_s : "false"_s);
|
||||
}
|
||||
|
||||
if (value.isNumber()) {
|
||||
double num = value.asNumber();
|
||||
if (std::isnan(num)) {
|
||||
return String(".nan"_s);
|
||||
}
|
||||
if (std::isinf(num)) {
|
||||
return num > 0 ? String(".inf"_s) : String("-.inf"_s);
|
||||
}
|
||||
return String::number(num);
|
||||
}
|
||||
|
||||
if (value.isString()) {
|
||||
return escapeYAMLString(value.toWTFString(globalObject));
|
||||
}
|
||||
|
||||
if (value.inherits<DateInstance>()) {
|
||||
auto* dateInstance = jsCast<DateInstance*>(value);
|
||||
double timeValue = dateInstance->internalNumber();
|
||||
if (std::isnan(timeValue)) {
|
||||
return String("null"_s);
|
||||
}
|
||||
char buffer[64];
|
||||
size_t length = toISOString(vm, timeValue, buffer);
|
||||
return String::fromUTF8(std::span<const char>(buffer, length));
|
||||
}
|
||||
|
||||
if (value.isObject()) {
|
||||
JSObject* object = value.getObject();
|
||||
|
||||
// Check for circular references
|
||||
auto it = objectMap.find(object);
|
||||
if (it != objectMap.end()) {
|
||||
// Already seen this object, use alias
|
||||
StringBuilder alias;
|
||||
alias.append("*anchor"_s);
|
||||
alias.append(String::number(it->value));
|
||||
return alias.toString();
|
||||
}
|
||||
|
||||
// Only add anchors for objects that are actually circular
|
||||
if (visitedForCircular.contains(object)) {
|
||||
objectMap.set(object, ++anchorCounter);
|
||||
|
||||
StringBuilder anchor;
|
||||
anchor.append("&anchor"_s);
|
||||
anchor.append(String::number(anchorCounter));
|
||||
anchor.append(' ');
|
||||
|
||||
if (value.inherits<JSArray>()) {
|
||||
anchor.append(serializeYAMLArray(globalObject, jsCast<JSArray*>(object), indent, objectMap, anchorCounter, visitedForCircular));
|
||||
} else {
|
||||
anchor.append(serializeYAMLObject(globalObject, object, indent, objectMap, anchorCounter, visitedForCircular));
|
||||
}
|
||||
|
||||
return anchor.toString();
|
||||
} else {
|
||||
if (value.inherits<JSArray>()) {
|
||||
return serializeYAMLArray(globalObject, jsCast<JSArray*>(object), indent, objectMap, anchorCounter, visitedForCircular);
|
||||
} else {
|
||||
return serializeYAMLObject(globalObject, object, indent, objectMap, anchorCounter, visitedForCircular);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return String("null"_s);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(yamlStringify, (JSGlobalObject* globalObject, CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
if (callFrame->argumentCount() < 1) {
|
||||
throwTypeError(globalObject, scope, "YAML.stringify requires at least 1 argument"_s);
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSValue value = callFrame->uncheckedArgument(0);
|
||||
|
||||
// First pass: detect circular references
|
||||
HashSet<JSObject*> visiting;
|
||||
HashSet<JSObject*> circular;
|
||||
detectCircularReferences(globalObject, value, visiting, circular);
|
||||
|
||||
// Second pass: serialize with circular reference handling
|
||||
HashMap<JSObject*, unsigned> objectMap;
|
||||
unsigned anchorCounter = 0;
|
||||
|
||||
String result = serializeYAMLValue(globalObject, value, 0, objectMap, anchorCounter, circular);
|
||||
RETURN_IF_EXCEPTION(scope, encodedJSValue());
|
||||
|
||||
return JSValue::encode(jsString(vm, result));
|
||||
}
|
||||
|
||||
JSValue constructYAMLObject(VM& vm, JSObject* bunObject)
|
||||
{
|
||||
JSGlobalObject* globalObject = bunObject->globalObject();
|
||||
JSObject* yamlObject = constructEmptyObject(globalObject);
|
||||
|
||||
yamlObject->putDirectNativeFunction(vm, globalObject, Identifier::fromString(vm, "stringify"_s), 1, yamlStringify, ImplementationVisibility::Public, NoIntrinsic, PropertyAttribute::DontDelete | 0);
|
||||
|
||||
return yamlObject;
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
56
test/js/bun/util/yaml-circular.test.ts
Normal file
56
test/js/bun/util/yaml-circular.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("YAML.stringify does not add anchors for non-circular objects", () => {
|
||||
const obj = { name: "test", value: 42 };
|
||||
const arr = [obj, obj]; // Same object referenced twice, but not circular
|
||||
|
||||
const result = Bun.YAML.stringify(arr);
|
||||
|
||||
// Should not contain anchor/alias syntax since it's not truly circular
|
||||
expect(result).not.toContain("&anchor");
|
||||
expect(result).not.toContain("*anchor");
|
||||
|
||||
// Should just serialize normally (may duplicate the object)
|
||||
expect(result).toContain("name: test");
|
||||
expect(result).toContain("value: 42");
|
||||
});
|
||||
|
||||
test("YAML.stringify handles true circular references", () => {
|
||||
const obj: any = { name: "test" };
|
||||
obj.self = obj; // True circular reference
|
||||
|
||||
const result = Bun.YAML.stringify(obj);
|
||||
|
||||
// Should contain anchor/alias syntax for circular reference
|
||||
expect(result).toContain("&anchor");
|
||||
expect(result).toContain("*anchor");
|
||||
expect(result).toContain("name: test");
|
||||
});
|
||||
|
||||
test("YAML.stringify handles circular array references", () => {
|
||||
const arr: any = [1, 2];
|
||||
arr.push(arr); // Circular array reference
|
||||
|
||||
const result = Bun.YAML.stringify(arr);
|
||||
|
||||
// Should contain anchor/alias syntax for circular reference
|
||||
expect(result).toContain("&anchor");
|
||||
expect(result).toContain("*anchor");
|
||||
expect(result).toContain("- 1");
|
||||
expect(result).toContain("- 2");
|
||||
});
|
||||
|
||||
test("YAML.stringify handles complex circular structures", () => {
|
||||
const a: any = { name: "a" };
|
||||
const b: any = { name: "b" };
|
||||
a.ref = b;
|
||||
b.ref = a; // Circular reference between two objects
|
||||
|
||||
const result = Bun.YAML.stringify({ root: a });
|
||||
|
||||
// Should handle the circular reference properly
|
||||
expect(result).toContain("&anchor");
|
||||
expect(result).toContain("*anchor");
|
||||
expect(result).toContain("name: a");
|
||||
expect(result).toContain("name: b");
|
||||
});
|
||||
10
test/js/bun/util/yaml-simple.test.ts
Normal file
10
test/js/bun/util/yaml-simple.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("YAML object exists", () => {
|
||||
expect(Bun.YAML).toBeDefined();
|
||||
});
|
||||
|
||||
test("YAML.stringify exists", () => {
|
||||
expect(Bun.YAML.stringify).toBeDefined();
|
||||
expect(typeof Bun.YAML.stringify).toBe("function");
|
||||
});
|
||||
207
test/js/bun/util/yaml.test.ts
Normal file
207
test/js/bun/util/yaml.test.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("Bun.YAML exists", () => {
|
||||
expect(Bun.YAML).toBeDefined();
|
||||
expect(typeof Bun.YAML).toBe("object");
|
||||
});
|
||||
|
||||
test("Bun.YAML.stringify exists", () => {
|
||||
expect(Bun.YAML.stringify).toBeDefined();
|
||||
expect(typeof Bun.YAML.stringify).toBe("function");
|
||||
});
|
||||
|
||||
test("YAML.stringify basic values", () => {
|
||||
expect(Bun.YAML.stringify(null)).toBe("null");
|
||||
expect(Bun.YAML.stringify(undefined)).toBe("null");
|
||||
expect(Bun.YAML.stringify(true)).toBe("true");
|
||||
expect(Bun.YAML.stringify(false)).toBe("false");
|
||||
expect(Bun.YAML.stringify(42)).toBe("42");
|
||||
expect(Bun.YAML.stringify(3.14)).toBe("3.14");
|
||||
expect(Bun.YAML.stringify("hello")).toBe("hello");
|
||||
});
|
||||
|
||||
test("YAML.stringify strings requiring quotes", () => {
|
||||
expect(Bun.YAML.stringify("true")).toBe('"true"');
|
||||
expect(Bun.YAML.stringify("false")).toBe('"false"');
|
||||
expect(Bun.YAML.stringify("null")).toBe('"null"');
|
||||
expect(Bun.YAML.stringify("123")).toBe('"123"');
|
||||
expect(Bun.YAML.stringify("hello: world")).toBe('"hello: world"');
|
||||
expect(Bun.YAML.stringify("- item")).toBe('"- item"');
|
||||
expect(Bun.YAML.stringify(" leading space")).toBe('" leading space"');
|
||||
expect(Bun.YAML.stringify("trailing space ")).toBe('"trailing space "');
|
||||
});
|
||||
|
||||
test("YAML.stringify string escaping", () => {
|
||||
expect(Bun.YAML.stringify('hello "world"')).toBe('"hello \\"world\\""');
|
||||
expect(Bun.YAML.stringify("hello\\world")).toBe('"hello\\\\world"');
|
||||
expect(Bun.YAML.stringify("hello\\nworld")).toBe('"hello\\\\nworld"');
|
||||
expect(Bun.YAML.stringify("hello\nworld")).toBe('"hello\\nworld"');
|
||||
expect(Bun.YAML.stringify("hello\tworld")).toBe('"hello\\tworld"');
|
||||
expect(Bun.YAML.stringify("hello\rworld")).toBe('"hello\\rworld"');
|
||||
});
|
||||
|
||||
test("YAML.stringify special numbers", () => {
|
||||
expect(Bun.YAML.stringify(NaN)).toBe(".nan");
|
||||
expect(Bun.YAML.stringify(Infinity)).toBe(".inf");
|
||||
expect(Bun.YAML.stringify(-Infinity)).toBe("-.inf");
|
||||
});
|
||||
|
||||
test("YAML.stringify empty array", () => {
|
||||
expect(Bun.YAML.stringify([])).toBe("[]");
|
||||
});
|
||||
|
||||
test("YAML.stringify simple array", () => {
|
||||
const result = Bun.YAML.stringify([1, 2, 3]);
|
||||
const expected = "- 1\n- 2\n- 3";
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
test("YAML.stringify nested array", () => {
|
||||
const result = Bun.YAML.stringify([1, [2, 3], 4]);
|
||||
const expected = "- 1\n- - 2\n - 3\n- 4";
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
test("YAML.stringify empty object", () => {
|
||||
expect(Bun.YAML.stringify({})).toBe("{}");
|
||||
});
|
||||
|
||||
test("YAML.stringify simple object", () => {
|
||||
const result = Bun.YAML.stringify({ a: 1, b: 2 });
|
||||
// Objects may have different property order, so check both possibilities
|
||||
expect(result === "a: 1\nb: 2" || result === "b: 2\na: 1").toBe(true);
|
||||
});
|
||||
|
||||
test("YAML.stringify nested object", () => {
|
||||
const obj = {
|
||||
name: "test",
|
||||
nested: {
|
||||
value: 42
|
||||
}
|
||||
};
|
||||
const result = Bun.YAML.stringify(obj);
|
||||
|
||||
// Check that it contains the expected structure
|
||||
expect(result).toContain("name: test");
|
||||
expect(result).toContain("nested:");
|
||||
expect(result).toContain(" value: 42");
|
||||
});
|
||||
|
||||
test("YAML.stringify array with objects", () => {
|
||||
const arr = [
|
||||
{ name: "Alice", age: 30 },
|
||||
{ name: "Bob", age: 25 }
|
||||
];
|
||||
const result = Bun.YAML.stringify(arr);
|
||||
|
||||
expect(result).toContain("- name: Alice");
|
||||
expect(result).toContain(" age: 30");
|
||||
expect(result).toContain("- name: Bob");
|
||||
expect(result).toContain(" age: 25");
|
||||
});
|
||||
|
||||
test("YAML.stringify Date objects", () => {
|
||||
const date = new Date("2023-01-01T00:00:00.000Z");
|
||||
const result = Bun.YAML.stringify(date);
|
||||
expect(result).toBe("2023-01-01T00:00:00.000Z");
|
||||
});
|
||||
|
||||
test("YAML.stringify invalid Date", () => {
|
||||
const invalidDate = new Date("invalid");
|
||||
const result = Bun.YAML.stringify(invalidDate);
|
||||
expect(result).toBe("null");
|
||||
});
|
||||
|
||||
test("YAML.stringify circular reference", () => {
|
||||
const obj: any = { name: "test" };
|
||||
obj.self = obj;
|
||||
|
||||
const result = Bun.YAML.stringify(obj);
|
||||
|
||||
// Should contain anchor/alias syntax for circular reference
|
||||
expect(result).toContain("*anchor");
|
||||
});
|
||||
|
||||
test("YAML.stringify circular reference in array", () => {
|
||||
const arr: any = [1, 2];
|
||||
arr.push(arr);
|
||||
|
||||
const result = Bun.YAML.stringify(arr);
|
||||
|
||||
// Should contain anchor/alias syntax for circular reference
|
||||
expect(result).toContain("*anchor");
|
||||
});
|
||||
|
||||
test("YAML.stringify complex nested structure", () => {
|
||||
const complex = {
|
||||
users: [
|
||||
{ name: "Alice", active: true, scores: [85, 92, 78] },
|
||||
{ name: "Bob", active: false, scores: [90, 88, 95] }
|
||||
],
|
||||
config: {
|
||||
version: "1.0",
|
||||
settings: {
|
||||
debug: true,
|
||||
timeout: 5000
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const result = Bun.YAML.stringify(complex);
|
||||
|
||||
// Verify basic structure is present
|
||||
expect(result).toContain("users:");
|
||||
expect(result).toContain("- name: Alice");
|
||||
expect(result).toContain("active: true");
|
||||
expect(result).toContain("scores:");
|
||||
expect(result).toContain("- 85");
|
||||
expect(result).toContain("config:");
|
||||
expect(result).toContain("version: \"1.0\"");
|
||||
expect(result).toContain("settings:");
|
||||
expect(result).toContain("debug: true");
|
||||
expect(result).toContain("timeout: 5000");
|
||||
});
|
||||
|
||||
test("YAML.stringify with null and undefined values", () => {
|
||||
const obj = {
|
||||
nullValue: null,
|
||||
undefinedValue: undefined,
|
||||
normalValue: "test"
|
||||
};
|
||||
|
||||
const result = Bun.YAML.stringify(obj);
|
||||
|
||||
expect(result).toContain("nullValue: null");
|
||||
expect(result).toContain("undefinedValue: null");
|
||||
expect(result).toContain("normalValue: test");
|
||||
});
|
||||
|
||||
test("YAML.stringify preserves array order", () => {
|
||||
const arr = ["first", "second", "third"];
|
||||
const result = Bun.YAML.stringify(arr);
|
||||
const lines = result.split('\n');
|
||||
|
||||
expect(lines[0]).toBe("- first");
|
||||
expect(lines[1]).toBe("- second");
|
||||
expect(lines[2]).toBe("- third");
|
||||
});
|
||||
|
||||
test("YAML.stringify handles special YAML characters in keys", () => {
|
||||
const obj = {
|
||||
"key:with:colons": "value1",
|
||||
"key-with-dashes": "value2",
|
||||
"key with spaces": "value3",
|
||||
"key[with]brackets": "value4"
|
||||
};
|
||||
|
||||
const result = Bun.YAML.stringify(obj);
|
||||
|
||||
expect(result).toContain('"key:with:colons": value1');
|
||||
expect(result).toContain('"key with spaces": value3');
|
||||
expect(result).toContain('"key[with]brackets": value4');
|
||||
});
|
||||
|
||||
test("YAML.stringify error handling", () => {
|
||||
// Test with no arguments
|
||||
expect(() => Bun.YAML.stringify()).toThrow();
|
||||
});
|
||||
Reference in New Issue
Block a user