Compare commits

...

6 Commits

Author SHA1 Message Date
claude[bot]
ad71db75c3 bun run prettier 2025-05-30 19:37:19 +00:00
claude[bot]
bc91844960 fix: URLSearchParams util.inspect iterator and custom inspect implementation
- Fix formatURLSearchParamsIterator to detect iterator type (keys/values vs entries)
- Format keys/values iterators as simple lists, entries as [key, value] pairs  
- Keep "URLSearchParams Iterator" braces instead of changing to "Entries"
- Add ERR_INVALID_THIS error code definition
- Implement URLSearchParams.prototype[util.inspect.custom] method
- Handle depth and breakLength options properly
- Throw ERR_INVALID_THIS when called with invalid context

Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
2025-05-30 19:34:45 +00:00
claude[bot]
73357cce64 bun run prettier 2025-05-30 18:13:16 +00:00
claude[bot]
0c0dfd2272 add: Bun test for URLSearchParams util.inspect behavior
- Tests basic URLSearchParams formatting with key-value pairs
- Tests empty URLSearchParams formatting
- Tests depth and breakLength options
- Tests URLSearchParams iterators (keys, values, entries)
- Tests iterator consumption behavior
- Tests error handling for invalid custom inspect calls
- Tests special characters and unicode handling
- Tests duplicate keys behavior

Co-authored-by: Jarred-Sumner <Jarred-Sumner@users.noreply.github.com>
2025-05-30 18:09:13 +00:00
Jarred-Sumner
2e7d9a6112 bun run prettier 2025-05-30 05:43:16 +00:00
Jarred Sumner
7d47053d46 fix: implement URLSearchParams inspect 2025-05-29 22:41:01 -07:00
3 changed files with 250 additions and 0 deletions

View File

@@ -345,6 +345,49 @@ const codes = {}; // exported from errors.js
return error;
};
}
{
// Add ERR_INVALID_THIS error code
const messages = new SafeMap();
const sym = "ERR_INVALID_THIS";
messages.set(sym, type => {
return `Value of "this" must be of type ${type}`;
});
codes[sym] = function NodeError(...args) {
const limit = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
const error = new TypeError();
Error.stackTraceLimit = limit; // Reset the limit and setting the name property.
const msg = messages.get(sym);
assert(typeof msg === "function");
assert(
msg.length <= args.length, // Default options do not count.
`Code: ${sym}; The provided arguments length (${args.length}) does not match the required ones (${msg.length}).`,
);
const message = msg.$apply(error, args);
ObjectDefineProperty(error, "message", { value: message, enumerable: false, writable: true, configurable: true });
ObjectDefineProperty(error, "toString", {
value() {
return `${this.name} [${sym}]: ${this.message}`;
},
enumerable: false,
writable: true,
configurable: true,
});
// addCodeToName + captureLargerStackTrace
let err = error;
const userStackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = Infinity;
ErrorCaptureStackTrace(err);
Error.stackTraceLimit = userStackTraceLimit; // Reset the limit
err.name = `${TypeError.name} [${sym}]`; // Add the error code to the name to include it in the stack trace.
err.stack; // Access the stack to generate the error message including the error code from the name.
delete err.name; // Reset the name to the actual name.
error.code = sym;
return error;
};
}
/**
* @param {unknown} value
* @param {string} name
@@ -362,6 +405,14 @@ function isURL(value) {
return typeof value.href === "string" && value instanceof URL;
}
function isURLSearchParams(value) {
return value !== null && typeof value === "object" && value[Symbol.toStringTag] === "URLSearchParams";
}
function isURLSearchParamsIterator(value) {
return value !== null && typeof value === "object" && value[Symbol.toStringTag] === "URLSearchParams Iterator";
}
const builtInObjects = new SafeSet(
ArrayPrototypeFilter(
ObjectGetOwnPropertyNames(globalThis),
@@ -1300,6 +1351,13 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
: FunctionPrototypeBind(formatMap, null, MapPrototypeEntries(value));
if (size === 0 && keys.length === 0 && protoProps === undefined) return `${prefix}{}`;
braces = [`${prefix}{`, "}"];
} else if (isURLSearchParams(value)) {
const size = value.size;
const prefix = getPrefix(constructor, tag, "URLSearchParams");
keys = getKeys(value, ctx.showHidden);
formatter = FunctionPrototypeBind(formatMap, null, value);
if (size === 0 && keys.length === 0 && protoProps === undefined) return `${prefix}{}`;
braces = [`${prefix}{`, "}"];
} else if (isTypedArray(value)) {
keys = getOwnNonIndexProperties(value, filter);
let bound = value;
@@ -1327,6 +1385,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) {
braces = getIteratorBraces("Set", tag);
// Add braces to the formatter parameters.
formatter = FunctionPrototypeBind(formatIterator, null, braces);
} else if (isURLSearchParamsIterator(value)) {
keys = getKeys(value, ctx.showHidden);
braces = getIteratorBraces("URLSearchParams", tag);
formatter = FunctionPrototypeBind(formatURLSearchParamsIterator, null, braces);
} else {
noIterator = true;
}
@@ -2279,6 +2341,32 @@ function formatIterator(braces, ctx, value, recurseTimes) {
return formatSetIterInner(ctx, recurseTimes, entries, kIterator);
}
function formatURLSearchParamsIterator(braces, ctx, value, recurseTimes) {
const items = [];
let isEntries = false;
// Consume the iterator to determine the format
for (const item of value) {
items.push(item);
// Check if the first item is an array (entries iterator)
if (items.length === 1) {
isEntries = Array.isArray(item) && item.length === 2;
}
}
if (isEntries) {
// Entries iterator: convert to flat array for formatMapIterInner
const entries = [];
for (const [key, val] of items) {
entries.push(key, val);
}
return formatMapIterInner(ctx, recurseTimes, entries, kMapEntries);
} else {
// Keys or values iterator: format as simple list
return formatSetIterInner(ctx, recurseTimes, items, kIterator);
}
}
function formatPromise(ctx, value, recurseTimes) {
let output;
const { 0: state, 1: result } = getPromiseDetails(value);
@@ -2746,6 +2834,38 @@ function internalGetConstructorName(val) {
return m ? m[1] : "Object";
}
// Add custom inspect method to URLSearchParams
if (typeof URLSearchParams !== "undefined") {
URLSearchParams.prototype[customInspectSymbol] = function urlSearchParamsCustomInspect(depth, options, inspect) {
if (this == null || typeof this.forEach !== "function") {
throw codes.ERR_INVALID_THIS("URLSearchParams");
}
if (depth != null && depth < 0) {
return "[Object]";
}
const entries = [];
this.forEach((value, key) => {
entries.push(`'${key}' => '${value}'`);
});
if (entries.length === 0) {
return "URLSearchParams {}";
}
const inner = entries.join(", ");
// Handle breakLength for multiline formatting
if (options && options.breakLength != null && inner.length > options.breakLength) {
const formattedEntries = entries.join(",\n ");
return `URLSearchParams {\n ${formattedEntries}\n}`;
}
return `URLSearchParams { ${inner} }`;
};
}
export default {
inspect,
format,

View File

@@ -0,0 +1,37 @@
'use strict';
require('../common');
const assert = require('assert');
const util = require('util');
const sp = new URLSearchParams('?a=a&b=b&b=c');
assert.strictEqual(util.inspect(sp),
"URLSearchParams { 'a' => 'a', 'b' => 'b', 'b' => 'c' }");
assert.strictEqual(util.inspect(sp, { depth: -1 }), '[Object]');
assert.strictEqual(
util.inspect(sp, { breakLength: 1 }),
"URLSearchParams {\n 'a' => 'a',\n 'b' => 'b',\n 'b' => 'c' }"
);
assert.strictEqual(util.inspect(sp.keys()),
"URLSearchParams Iterator { 'a', 'b', 'b' }");
assert.strictEqual(util.inspect(sp.values()),
"URLSearchParams Iterator { 'a', 'b', 'c' }");
assert.strictEqual(util.inspect(sp.keys(), { breakLength: 1 }),
"URLSearchParams Iterator {\n 'a',\n 'b',\n 'b' }");
assert.throws(() => sp[util.inspect.custom].call(), {
code: 'ERR_INVALID_THIS',
});
const iterator = sp.entries();
assert.strictEqual(util.inspect(iterator),
"URLSearchParams Iterator { [ 'a', 'a' ], [ 'b', 'b' ], " +
"[ 'b', 'c' ] }");
iterator.next();
assert.strictEqual(util.inspect(iterator),
"URLSearchParams Iterator { [ 'b', 'b' ], [ 'b', 'c' ] }");
iterator.next();
iterator.next();
assert.strictEqual(util.inspect(iterator),
'URLSearchParams Iterator { }');
const emptySp = new URLSearchParams();
assert.strictEqual(util.inspect(emptySp), 'URLSearchParams {}');

View File

@@ -0,0 +1,93 @@
import { describe, expect, it } from "bun:test";
import util from "util";
describe("util.inspect URLSearchParams", () => {
it("should format URLSearchParams with key-value pairs", () => {
const sp = new URLSearchParams("?a=a&b=b&b=c");
expect(util.inspect(sp)).toBe("URLSearchParams { 'a' => 'a', 'b' => 'b', 'b' => 'c' }");
});
it("should format empty URLSearchParams", () => {
const emptySp = new URLSearchParams();
expect(util.inspect(emptySp)).toBe("URLSearchParams {}");
});
it("should respect depth option", () => {
const sp = new URLSearchParams("?a=a&b=b&b=c");
expect(util.inspect(sp, { depth: -1 })).toBe("[Object]");
});
it("should respect breakLength option for multiline formatting", () => {
const sp = new URLSearchParams("?a=a&b=b&b=c");
expect(util.inspect(sp, { breakLength: 1 })).toBe(
"URLSearchParams {\n 'a' => 'a',\n 'b' => 'b',\n 'b' => 'c'\n}",
);
});
it("should format URLSearchParams keys iterator", () => {
const sp = new URLSearchParams("?a=a&b=b&b=c");
expect(util.inspect(sp.keys())).toBe("URLSearchParams Iterator { 'a', 'b', 'b' }");
});
it("should format URLSearchParams values iterator", () => {
const sp = new URLSearchParams("?a=a&b=b&b=c");
expect(util.inspect(sp.values())).toBe("URLSearchParams Iterator { 'a', 'b', 'c' }");
});
it("should format URLSearchParams keys iterator with breakLength", () => {
const sp = new URLSearchParams("?a=a&b=b&b=c");
expect(util.inspect(sp.keys(), { breakLength: 1 })).toBe("URLSearchParams Iterator {\n 'a',\n 'b',\n 'b'\n}");
});
it("should format URLSearchParams entries iterator", () => {
const sp = new URLSearchParams("?a=a&b=b&b=c");
const iterator = sp.entries();
expect(util.inspect(iterator)).toBe("URLSearchParams Iterator { [ 'a', 'a' ], [ 'b', 'b' ], [ 'b', 'c' ] }");
});
it("should format URLSearchParams entries iterator after consuming entries", () => {
const sp = new URLSearchParams("?a=a&b=b&b=c");
const iterator = sp.entries();
iterator.next(); // consume first entry
expect(util.inspect(iterator)).toBe("URLSearchParams Iterator { [ 'b', 'b' ], [ 'b', 'c' ] }");
iterator.next(); // consume second entry
iterator.next(); // consume third entry
expect(util.inspect(iterator)).toBe("URLSearchParams Iterator { }");
});
it("should throw error when custom inspect is called incorrectly", () => {
const sp = new URLSearchParams("?a=a&b=b");
expect(() => sp[util.inspect.custom].call()).toThrow({
code: "ERR_INVALID_THIS",
});
});
it("should handle URLSearchParams with special characters", () => {
const sp = new URLSearchParams("?key%20with%20spaces=value%20with%20spaces&special=!@%23$%25");
const result = util.inspect(sp);
expect(result).toContain("URLSearchParams");
expect(result).toContain("'key with spaces' => 'value with spaces'");
expect(result).toContain("'special' => '!@#$%'");
});
it("should handle URLSearchParams with unicode characters", () => {
const sp = new URLSearchParams("?emoji=😀&unicode=🌟");
const result = util.inspect(sp);
expect(result).toContain("URLSearchParams");
expect(result).toContain("'emoji' => '😀'");
expect(result).toContain("'unicode' => '🌟'");
});
it("should handle URLSearchParams with duplicate keys", () => {
const sp = new URLSearchParams();
sp.append("key", "value1");
sp.append("key", "value2");
sp.append("key", "value3");
const result = util.inspect(sp);
expect(result).toContain("URLSearchParams");
expect(result).toContain("'key' => 'value1'");
expect(result).toContain("'key' => 'value2'");
expect(result).toContain("'key' => 'value3'");
});
});