mirror of
https://github.com/oven-sh/bun
synced 2026-02-04 07:58:54 +00:00
Compare commits
4 Commits
claude/rep
...
claude/imp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
830e829401 | ||
|
|
5917373293 | ||
|
|
f52a0a4b53 | ||
|
|
d7cf65eb0e |
@@ -44,6 +44,17 @@ pub const BunObject = struct {
|
||||
pub const zstdDecompressSync = toJSCallback(JSZstd.decompressSync);
|
||||
pub const zstdCompress = toJSCallback(JSZstd.compress);
|
||||
pub const zstdDecompress = toJSCallback(JSZstd.decompress);
|
||||
|
||||
// Case conversion functions
|
||||
pub const camelCase = toJSCallback(CaseConvert.jsCamelCase);
|
||||
pub const pascalCase = toJSCallback(CaseConvert.jsPascalCase);
|
||||
pub const snakeCase = toJSCallback(CaseConvert.jsSnakeCase);
|
||||
pub const kebabCase = toJSCallback(CaseConvert.jsKebabCase);
|
||||
pub const screamingSnakeCase = toJSCallback(CaseConvert.jsScreamingSnakeCase);
|
||||
pub const constantCase = toJSCallback(CaseConvert.jsConstantCase); // Alias for screamingSnakeCase
|
||||
pub const dotCase = toJSCallback(CaseConvert.jsDotCase);
|
||||
pub const capitalCase = toJSCallback(CaseConvert.jsCapitalCase);
|
||||
pub const trainCase = toJSCallback(CaseConvert.jsTrainCase);
|
||||
|
||||
// --- Callbacks ---
|
||||
|
||||
@@ -180,6 +191,17 @@ pub const BunObject = struct {
|
||||
@export(&BunObject.zstdDecompressSync, .{ .name = callbackName("zstdDecompressSync") });
|
||||
@export(&BunObject.zstdCompress, .{ .name = callbackName("zstdCompress") });
|
||||
@export(&BunObject.zstdDecompress, .{ .name = callbackName("zstdDecompress") });
|
||||
|
||||
// Case conversion exports
|
||||
@export(&BunObject.camelCase, .{ .name = callbackName("camelCase") });
|
||||
@export(&BunObject.pascalCase, .{ .name = callbackName("pascalCase") });
|
||||
@export(&BunObject.snakeCase, .{ .name = callbackName("snakeCase") });
|
||||
@export(&BunObject.kebabCase, .{ .name = callbackName("kebabCase") });
|
||||
@export(&BunObject.screamingSnakeCase, .{ .name = callbackName("screamingSnakeCase") });
|
||||
@export(&BunObject.constantCase, .{ .name = callbackName("constantCase") });
|
||||
@export(&BunObject.dotCase, .{ .name = callbackName("dotCase") });
|
||||
@export(&BunObject.capitalCase, .{ .name = callbackName("capitalCase") });
|
||||
@export(&BunObject.trainCase, .{ .name = callbackName("trainCase") });
|
||||
// --- Callbacks ---
|
||||
|
||||
// --- LazyProperty initializers ---
|
||||
@@ -2070,6 +2092,7 @@ pub fn createBunStdout(globalThis: *jsc.JSGlobalObject) callconv(.C) jsc.JSValue
|
||||
}
|
||||
|
||||
const Braces = @import("../../shell/braces.zig");
|
||||
const CaseConvert = @import("./CaseConvert.zig");
|
||||
const Which = @import("../../which.zig");
|
||||
const options = @import("../../options.zig");
|
||||
const std = @import("std");
|
||||
|
||||
470
src/bun.js/api/CaseConvert.zig
Normal file
470
src/bun.js/api/CaseConvert.zig
Normal file
@@ -0,0 +1,470 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("bun");
|
||||
const jsc = bun.jsc;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
/// Check if a Unicode codepoint is a letter
|
||||
fn isLetter(codepoint: u21) bool {
|
||||
// Basic Latin letters
|
||||
if ((codepoint >= 'A' and codepoint <= 'Z') or (codepoint >= 'a' and codepoint <= 'z')) {
|
||||
return true;
|
||||
}
|
||||
// Extended Latin and other alphabetic ranges
|
||||
// This covers most common accented characters
|
||||
if ((codepoint >= 0xC0 and codepoint <= 0xFF and codepoint != 0xD7 and codepoint != 0xF7) or // Latin-1 Supplement letters
|
||||
(codepoint >= 0x100 and codepoint <= 0x17F) or // Latin Extended-A
|
||||
(codepoint >= 0x180 and codepoint <= 0x24F) or // Latin Extended-B and IPA
|
||||
(codepoint >= 0x1E00 and codepoint <= 0x1EFF)) // Latin Extended Additional
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check if a Unicode codepoint is uppercase
|
||||
fn isUpper(codepoint: u21) bool {
|
||||
// ASCII uppercase
|
||||
if (codepoint >= 'A' and codepoint <= 'Z') {
|
||||
return true;
|
||||
}
|
||||
// Latin-1 Supplement uppercase (À-Þ, except ×)
|
||||
if (codepoint >= 0xC0 and codepoint <= 0xDE and codepoint != 0xD7) {
|
||||
return true;
|
||||
}
|
||||
// Simple heuristic for other Latin uppercase: even positions in extended ranges
|
||||
// This is not perfect but covers many common cases
|
||||
if ((codepoint >= 0x100 and codepoint <= 0x17F) or
|
||||
(codepoint >= 0x1E00 and codepoint <= 0x1EFF))
|
||||
{
|
||||
return (codepoint & 1) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check if a Unicode codepoint is lowercase
|
||||
fn isLower(codepoint: u21) bool {
|
||||
// ASCII lowercase
|
||||
if (codepoint >= 'a' and codepoint <= 'z') {
|
||||
return true;
|
||||
}
|
||||
// Latin-1 Supplement lowercase (ß-ÿ, except ÷)
|
||||
if (codepoint >= 0xDF and codepoint <= 0xFF and codepoint != 0xF7) {
|
||||
return true;
|
||||
}
|
||||
// Simple heuristic for other Latin lowercase: odd positions in extended ranges
|
||||
if ((codepoint >= 0x100 and codepoint <= 0x17F) or
|
||||
(codepoint >= 0x1E00 and codepoint <= 0x1EFF))
|
||||
{
|
||||
return (codepoint & 1) == 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Convert a Unicode codepoint to uppercase
|
||||
fn toUpperCodepoint(codepoint: u21) u21 {
|
||||
// ASCII
|
||||
if (codepoint >= 'a' and codepoint <= 'z') {
|
||||
return codepoint - 32;
|
||||
}
|
||||
// Latin-1 Supplement
|
||||
if (codepoint >= 0xE0 and codepoint <= 0xFE and codepoint != 0xF7) {
|
||||
return codepoint - 32;
|
||||
}
|
||||
// Latin Extended: odd to even (simplified)
|
||||
if ((codepoint >= 0x101 and codepoint <= 0x17F) or
|
||||
(codepoint >= 0x1E01 and codepoint <= 0x1EFF))
|
||||
{
|
||||
if ((codepoint & 1) == 1) {
|
||||
return codepoint - 1;
|
||||
}
|
||||
}
|
||||
return codepoint;
|
||||
}
|
||||
|
||||
/// Convert a Unicode codepoint to lowercase
|
||||
fn toLowerCodepoint(codepoint: u21) u21 {
|
||||
// ASCII
|
||||
if (codepoint >= 'A' and codepoint <= 'Z') {
|
||||
return codepoint + 32;
|
||||
}
|
||||
// Latin-1 Supplement
|
||||
if (codepoint >= 0xC0 and codepoint <= 0xDE and codepoint != 0xD7) {
|
||||
return codepoint + 32;
|
||||
}
|
||||
// Latin Extended: even to odd (simplified)
|
||||
if ((codepoint >= 0x100 and codepoint <= 0x17E) or
|
||||
(codepoint >= 0x1E00 and codepoint <= 0x1EFE))
|
||||
{
|
||||
if ((codepoint & 1) == 0) {
|
||||
return codepoint + 1;
|
||||
}
|
||||
}
|
||||
return codepoint;
|
||||
}
|
||||
|
||||
/// Check if a codepoint is alphanumeric
|
||||
fn isAlphanumeric(codepoint: u21) bool {
|
||||
return isLetter(codepoint) or (codepoint >= '0' and codepoint <= '9');
|
||||
}
|
||||
|
||||
/// Check if a codepoint is a digit
|
||||
fn isDigit(codepoint: u21) bool {
|
||||
return codepoint >= '0' and codepoint <= '9';
|
||||
}
|
||||
|
||||
/// Represents a word extracted from the input
|
||||
const Word = struct {
|
||||
bytes: []const u8,
|
||||
|
||||
/// Write the word to output with specified case transformation
|
||||
fn writeTo(self: Word, writer: anytype, comptime transform: enum { lower, upper, capital }) !void {
|
||||
var iter = std.unicode.Utf8Iterator{ .bytes = self.bytes, .i = 0 };
|
||||
var first = true;
|
||||
|
||||
while (iter.nextCodepoint()) |codepoint| {
|
||||
const transformed = switch (transform) {
|
||||
.lower => toLowerCodepoint(codepoint),
|
||||
.upper => toUpperCodepoint(codepoint),
|
||||
.capital => if (first) toUpperCodepoint(codepoint) else toLowerCodepoint(codepoint),
|
||||
};
|
||||
first = false;
|
||||
|
||||
var buf: [4]u8 = undefined;
|
||||
const len = std.unicode.utf8Encode(transformed, &buf) catch {
|
||||
// If encoding fails, just write original codepoint
|
||||
const orig_len = std.unicode.utf8Encode(codepoint, &buf) catch 1;
|
||||
try writer.writeAll(buf[0..orig_len]);
|
||||
continue;
|
||||
};
|
||||
try writer.writeAll(buf[0..len]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Split a UTF-8 string into words based on various delimiters and case changes
|
||||
fn splitIntoWords(allocator: std.mem.Allocator, input: []const u8) !std.ArrayList(Word) {
|
||||
var words = std.ArrayList(Word).init(allocator);
|
||||
errdefer words.deinit();
|
||||
|
||||
if (input.len == 0) return words;
|
||||
|
||||
var iter = std.unicode.Utf8Iterator{ .bytes = input, .i = 0 };
|
||||
var word_start: usize = 0;
|
||||
var prev_codepoint: ?u21 = null;
|
||||
var prev_was_lower = false;
|
||||
var prev_was_upper = false;
|
||||
var prev_was_digit = false;
|
||||
|
||||
while (iter.i < input.len) {
|
||||
const start_pos = iter.i;
|
||||
const codepoint = iter.nextCodepoint() orelse break;
|
||||
|
||||
const is_alnum = isAlphanumeric(codepoint);
|
||||
const is_digit = isDigit(codepoint);
|
||||
const is_lower = isLower(codepoint);
|
||||
const is_upper = isUpper(codepoint);
|
||||
|
||||
// Handle word boundaries
|
||||
if (!is_alnum) {
|
||||
// Non-alphanumeric character - end current word
|
||||
if (start_pos > word_start) {
|
||||
try words.append(Word{ .bytes = input[word_start..start_pos] });
|
||||
}
|
||||
word_start = iter.i;
|
||||
prev_codepoint = null;
|
||||
prev_was_lower = false;
|
||||
prev_was_upper = false;
|
||||
prev_was_digit = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for transitions that should split words
|
||||
if (prev_codepoint) |_| {
|
||||
var should_split = false;
|
||||
|
||||
// Split on digit to uppercase letter transition (test123Case -> test123, Case)
|
||||
if (prev_was_digit and is_upper and !is_digit) {
|
||||
should_split = true;
|
||||
}
|
||||
// Split on lowercase to uppercase transition (camelCase -> camel, Case)
|
||||
else if (prev_was_lower and is_upper and !is_digit) {
|
||||
should_split = true;
|
||||
}
|
||||
// Split on uppercase sequence ending (XMLParser -> XML, Parser)
|
||||
else if (prev_was_upper and is_upper and !is_digit) {
|
||||
// Look ahead to see if next is lowercase
|
||||
const saved_i = iter.i;
|
||||
if (iter.nextCodepoint()) |next_cp| {
|
||||
if (isLower(next_cp)) {
|
||||
should_split = true;
|
||||
}
|
||||
}
|
||||
iter.i = saved_i;
|
||||
}
|
||||
|
||||
if (should_split) {
|
||||
if (start_pos > word_start) {
|
||||
try words.append(Word{ .bytes = input[word_start..start_pos] });
|
||||
}
|
||||
word_start = start_pos;
|
||||
}
|
||||
}
|
||||
|
||||
prev_codepoint = codepoint;
|
||||
prev_was_lower = is_lower;
|
||||
prev_was_upper = is_upper;
|
||||
prev_was_digit = is_digit;
|
||||
}
|
||||
|
||||
// Add the last word if any
|
||||
if (word_start < input.len) {
|
||||
try words.append(Word{ .bytes = input[word_start..] });
|
||||
}
|
||||
|
||||
return words;
|
||||
}
|
||||
|
||||
/// Convert string to camelCase: "two words" -> "twoWords"
|
||||
pub fn camelCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.bytes.len == 0) continue;
|
||||
|
||||
if (idx == 0) {
|
||||
try word.writeTo(result.writer(), .lower);
|
||||
} else {
|
||||
try word.writeTo(result.writer(), .capital);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to PascalCase: "two words" -> "TwoWords"
|
||||
pub fn pascalCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items) |word| {
|
||||
if (word.bytes.len == 0) continue;
|
||||
try word.writeTo(result.writer(), .capital);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to snake_case: "two words" -> "two_words"
|
||||
pub fn snakeCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.bytes.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('_');
|
||||
}
|
||||
try word.writeTo(result.writer(), .lower);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to kebab-case: "two words" -> "two-words"
|
||||
pub fn kebabCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.bytes.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('-');
|
||||
}
|
||||
try word.writeTo(result.writer(), .lower);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to SCREAMING_SNAKE_CASE: "two words" -> "TWO_WORDS"
|
||||
pub fn screamingSnakeCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.bytes.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('_');
|
||||
}
|
||||
try word.writeTo(result.writer(), .upper);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Alias for screamingSnakeCase for compatibility
|
||||
pub const constantCase = screamingSnakeCase;
|
||||
|
||||
/// Convert string to dot.case: "two words" -> "two.words"
|
||||
pub fn dotCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.bytes.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('.');
|
||||
}
|
||||
try word.writeTo(result.writer(), .lower);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to Capital Case: "two words" -> "Two Words"
|
||||
pub fn capitalCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.bytes.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append(' ');
|
||||
}
|
||||
try word.writeTo(result.writer(), .capital);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Convert string to Train-Case: "two words" -> "Two-Words"
|
||||
pub fn trainCase(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
|
||||
const words = try splitIntoWords(allocator, input);
|
||||
defer words.deinit();
|
||||
|
||||
if (words.items.len == 0) return try allocator.alloc(u8, 0);
|
||||
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
for (words.items, 0..) |word, idx| {
|
||||
if (word.bytes.len == 0) continue;
|
||||
|
||||
if (idx > 0) {
|
||||
try result.append('-');
|
||||
}
|
||||
try word.writeTo(result.writer(), .capital);
|
||||
}
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
/// Generic case conversion function that handles string extraction and conversion
|
||||
fn convertCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame, comptime converter: fn (std.mem.Allocator, []const u8) anyerror![]u8) bun.JSError!JSValue {
|
||||
const arguments = callFrame.arguments_old(1);
|
||||
if (arguments.len < 1) {
|
||||
return globalThis.throw("expected 1 argument, got 0", .{});
|
||||
}
|
||||
|
||||
const input_value = arguments.ptr[0];
|
||||
|
||||
// Convert to string
|
||||
const bunstr = try input_value.toBunString(globalThis);
|
||||
if (globalThis.hasException()) return .zero;
|
||||
defer bunstr.deref();
|
||||
|
||||
// Get UTF8 bytes
|
||||
const allocator = bun.default_allocator;
|
||||
const utf8_slice = bunstr.toUTF8(allocator);
|
||||
defer utf8_slice.deinit();
|
||||
|
||||
// Apply the conversion
|
||||
const result_bytes = converter(allocator, utf8_slice.slice()) catch |err| {
|
||||
if (err == error.OutOfMemory) {
|
||||
return globalThis.throwOutOfMemory();
|
||||
}
|
||||
return globalThis.throw("case conversion failed", .{});
|
||||
};
|
||||
defer allocator.free(result_bytes);
|
||||
|
||||
// Create a new string from the result
|
||||
var result_str = bun.String.cloneUTF8(result_bytes);
|
||||
return result_str.transferToJS(globalThis);
|
||||
}
|
||||
|
||||
// JavaScript-exposed functions
|
||||
pub fn jsCamelCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, camelCase);
|
||||
}
|
||||
|
||||
pub fn jsPascalCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, pascalCase);
|
||||
}
|
||||
|
||||
pub fn jsSnakeCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, snakeCase);
|
||||
}
|
||||
|
||||
pub fn jsKebabCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, kebabCase);
|
||||
}
|
||||
|
||||
pub fn jsScreamingSnakeCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, screamingSnakeCase);
|
||||
}
|
||||
|
||||
// Alias for compatibility
|
||||
pub const jsConstantCase = jsScreamingSnakeCase;
|
||||
|
||||
pub fn jsDotCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, dotCase);
|
||||
}
|
||||
|
||||
pub fn jsCapitalCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, capitalCase);
|
||||
}
|
||||
|
||||
pub fn jsTrainCase(globalThis: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!JSValue {
|
||||
return convertCase(globalThis, callFrame, trainCase);
|
||||
}
|
||||
@@ -75,6 +75,15 @@
|
||||
macro(zstdDecompressSync) \
|
||||
macro(zstdCompress) \
|
||||
macro(zstdDecompress) \
|
||||
macro(camelCase) \
|
||||
macro(pascalCase) \
|
||||
macro(snakeCase) \
|
||||
macro(kebabCase) \
|
||||
macro(screamingSnakeCase) \
|
||||
macro(constantCase) \
|
||||
macro(dotCase) \
|
||||
macro(capitalCase) \
|
||||
macro(trainCase) \
|
||||
|
||||
#define DECLARE_ZIG_BUN_OBJECT_CALLBACK(name) BUN_DECLARE_HOST_FUNCTION(BunObject_callback_##name);
|
||||
FOR_EACH_CALLBACK(DECLARE_ZIG_BUN_OBJECT_CALLBACK);
|
||||
|
||||
@@ -807,6 +807,15 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
zstdDecompressSync BunObject_callback_zstdDecompressSync DontDelete|Function 1
|
||||
zstdCompress BunObject_callback_zstdCompress DontDelete|Function 1
|
||||
zstdDecompress BunObject_callback_zstdDecompress DontDelete|Function 1
|
||||
camelCase BunObject_callback_camelCase DontDelete|Function 1
|
||||
pascalCase BunObject_callback_pascalCase DontDelete|Function 1
|
||||
snakeCase BunObject_callback_snakeCase DontDelete|Function 1
|
||||
kebabCase BunObject_callback_kebabCase DontDelete|Function 1
|
||||
screamingSnakeCase BunObject_callback_screamingSnakeCase DontDelete|Function 1
|
||||
constantCase BunObject_callback_constantCase DontDelete|Function 1
|
||||
dotCase BunObject_callback_dotCase DontDelete|Function 1
|
||||
capitalCase BunObject_callback_capitalCase DontDelete|Function 1
|
||||
trainCase BunObject_callback_trainCase DontDelete|Function 1
|
||||
@end
|
||||
*/
|
||||
|
||||
|
||||
503
test/js/bun/case-convert-compat.test.ts
Normal file
503
test/js/bun/case-convert-compat.test.ts
Normal file
@@ -0,0 +1,503 @@
|
||||
import { test, expect, describe } from "bun:test";
|
||||
|
||||
// Test cases ported from https://github.com/blakeembrey/change-case
|
||||
// to ensure maximum compatibility
|
||||
|
||||
describe("change-case compatibility tests", () => {
|
||||
// Basic test cases from change-case library
|
||||
const testCases = [
|
||||
// Empty string
|
||||
{
|
||||
input: "",
|
||||
expected: {
|
||||
camelCase: "",
|
||||
pascalCase: "",
|
||||
snakeCase: "",
|
||||
kebabCase: "",
|
||||
screamingSnakeCase: "",
|
||||
constantCase: "", // alias
|
||||
dotCase: "",
|
||||
capitalCase: "",
|
||||
trainCase: "",
|
||||
},
|
||||
},
|
||||
// Single word
|
||||
{
|
||||
input: "test",
|
||||
expected: {
|
||||
camelCase: "test",
|
||||
pascalCase: "Test",
|
||||
snakeCase: "test",
|
||||
kebabCase: "test",
|
||||
screamingSnakeCase: "TEST",
|
||||
constantCase: "TEST",
|
||||
dotCase: "test",
|
||||
capitalCase: "Test",
|
||||
trainCase: "Test",
|
||||
},
|
||||
},
|
||||
// Two words
|
||||
{
|
||||
input: "test string",
|
||||
expected: {
|
||||
camelCase: "testString",
|
||||
pascalCase: "TestString",
|
||||
snakeCase: "test_string",
|
||||
kebabCase: "test-string",
|
||||
screamingSnakeCase: "TEST_STRING",
|
||||
constantCase: "TEST_STRING",
|
||||
dotCase: "test.string",
|
||||
capitalCase: "Test String",
|
||||
trainCase: "Test-String",
|
||||
},
|
||||
},
|
||||
// Capitalized words
|
||||
{
|
||||
input: "Test String",
|
||||
expected: {
|
||||
camelCase: "testString",
|
||||
pascalCase: "TestString",
|
||||
snakeCase: "test_string",
|
||||
kebabCase: "test-string",
|
||||
screamingSnakeCase: "TEST_STRING",
|
||||
constantCase: "TEST_STRING",
|
||||
dotCase: "test.string",
|
||||
capitalCase: "Test String",
|
||||
trainCase: "Test-String",
|
||||
},
|
||||
},
|
||||
// Version with V and number
|
||||
{
|
||||
input: "TestV2",
|
||||
expected: {
|
||||
camelCase: "testV2",
|
||||
pascalCase: "TestV2",
|
||||
snakeCase: "test_v2",
|
||||
kebabCase: "test-v2",
|
||||
screamingSnakeCase: "TEST_V2",
|
||||
constantCase: "TEST_V2",
|
||||
dotCase: "test.v2",
|
||||
capitalCase: "Test V2",
|
||||
trainCase: "Test-V2",
|
||||
},
|
||||
},
|
||||
// Leading/trailing underscores
|
||||
{
|
||||
input: "_foo_bar_",
|
||||
expected: {
|
||||
camelCase: "fooBar",
|
||||
pascalCase: "FooBar",
|
||||
snakeCase: "foo_bar",
|
||||
kebabCase: "foo-bar",
|
||||
screamingSnakeCase: "FOO_BAR",
|
||||
constantCase: "FOO_BAR",
|
||||
dotCase: "foo.bar",
|
||||
capitalCase: "Foo Bar",
|
||||
trainCase: "Foo-Bar",
|
||||
},
|
||||
},
|
||||
// ALL CAPS
|
||||
{
|
||||
input: "ALL CAPS",
|
||||
expected: {
|
||||
camelCase: "allCaps",
|
||||
pascalCase: "AllCaps",
|
||||
snakeCase: "all_caps",
|
||||
kebabCase: "all-caps",
|
||||
screamingSnakeCase: "ALL_CAPS",
|
||||
constantCase: "ALL_CAPS",
|
||||
dotCase: "all.caps",
|
||||
capitalCase: "All Caps",
|
||||
trainCase: "All-Caps",
|
||||
},
|
||||
},
|
||||
// camelCase input
|
||||
{
|
||||
input: "camelCase",
|
||||
expected: {
|
||||
camelCase: "camelCase",
|
||||
pascalCase: "CamelCase",
|
||||
snakeCase: "camel_case",
|
||||
kebabCase: "camel-case",
|
||||
screamingSnakeCase: "CAMEL_CASE",
|
||||
constantCase: "CAMEL_CASE",
|
||||
dotCase: "camel.case",
|
||||
capitalCase: "Camel Case",
|
||||
trainCase: "Camel-Case",
|
||||
},
|
||||
},
|
||||
// PascalCase input
|
||||
{
|
||||
input: "PascalCase",
|
||||
expected: {
|
||||
camelCase: "pascalCase",
|
||||
pascalCase: "PascalCase",
|
||||
snakeCase: "pascal_case",
|
||||
kebabCase: "pascal-case",
|
||||
screamingSnakeCase: "PASCAL_CASE",
|
||||
constantCase: "PASCAL_CASE",
|
||||
dotCase: "pascal.case",
|
||||
capitalCase: "Pascal Case",
|
||||
trainCase: "Pascal-Case",
|
||||
},
|
||||
},
|
||||
// snake_case input
|
||||
{
|
||||
input: "snake_case",
|
||||
expected: {
|
||||
camelCase: "snakeCase",
|
||||
pascalCase: "SnakeCase",
|
||||
snakeCase: "snake_case",
|
||||
kebabCase: "snake-case",
|
||||
screamingSnakeCase: "SNAKE_CASE",
|
||||
constantCase: "SNAKE_CASE",
|
||||
dotCase: "snake.case",
|
||||
capitalCase: "Snake Case",
|
||||
trainCase: "Snake-Case",
|
||||
},
|
||||
},
|
||||
// kebab-case input
|
||||
{
|
||||
input: "kebab-case",
|
||||
expected: {
|
||||
camelCase: "kebabCase",
|
||||
pascalCase: "KebabCase",
|
||||
snakeCase: "kebab_case",
|
||||
kebabCase: "kebab-case",
|
||||
screamingSnakeCase: "KEBAB_CASE",
|
||||
constantCase: "KEBAB_CASE",
|
||||
dotCase: "kebab.case",
|
||||
capitalCase: "Kebab Case",
|
||||
trainCase: "Kebab-Case",
|
||||
},
|
||||
},
|
||||
// CONSTANT_CASE input
|
||||
{
|
||||
input: "CONSTANT_CASE",
|
||||
expected: {
|
||||
camelCase: "constantCase",
|
||||
pascalCase: "ConstantCase",
|
||||
snakeCase: "constant_case",
|
||||
kebabCase: "constant-case",
|
||||
screamingSnakeCase: "CONSTANT_CASE",
|
||||
constantCase: "CONSTANT_CASE",
|
||||
dotCase: "constant.case",
|
||||
capitalCase: "Constant Case",
|
||||
trainCase: "Constant-Case",
|
||||
},
|
||||
},
|
||||
// dot.case input
|
||||
{
|
||||
input: "dot.case",
|
||||
expected: {
|
||||
camelCase: "dotCase",
|
||||
pascalCase: "DotCase",
|
||||
snakeCase: "dot_case",
|
||||
kebabCase: "dot-case",
|
||||
screamingSnakeCase: "DOT_CASE",
|
||||
constantCase: "DOT_CASE",
|
||||
dotCase: "dot.case",
|
||||
capitalCase: "Dot Case",
|
||||
trainCase: "Dot-Case",
|
||||
},
|
||||
},
|
||||
// path/case input
|
||||
{
|
||||
input: "path/case",
|
||||
expected: {
|
||||
camelCase: "pathCase",
|
||||
pascalCase: "PathCase",
|
||||
snakeCase: "path_case",
|
||||
kebabCase: "path-case",
|
||||
screamingSnakeCase: "PATH_CASE",
|
||||
constantCase: "PATH_CASE",
|
||||
dotCase: "path.case",
|
||||
capitalCase: "Path Case",
|
||||
trainCase: "Path-Case",
|
||||
},
|
||||
},
|
||||
// Mixed separators
|
||||
{
|
||||
input: "mixed_string-case.dot/path",
|
||||
expected: {
|
||||
camelCase: "mixedStringCaseDotPath",
|
||||
pascalCase: "MixedStringCaseDotPath",
|
||||
snakeCase: "mixed_string_case_dot_path",
|
||||
kebabCase: "mixed-string-case-dot-path",
|
||||
screamingSnakeCase: "MIXED_STRING_CASE_DOT_PATH",
|
||||
constantCase: "MIXED_STRING_CASE_DOT_PATH",
|
||||
dotCase: "mixed.string.case.dot.path",
|
||||
capitalCase: "Mixed String Case Dot Path",
|
||||
trainCase: "Mixed-String-Case-Dot-Path",
|
||||
},
|
||||
},
|
||||
// Consecutive uppercase (acronyms)
|
||||
{
|
||||
input: "XMLHttpRequest",
|
||||
expected: {
|
||||
camelCase: "xmlHttpRequest",
|
||||
pascalCase: "XmlHttpRequest",
|
||||
snakeCase: "xml_http_request",
|
||||
kebabCase: "xml-http-request",
|
||||
screamingSnakeCase: "XML_HTTP_REQUEST",
|
||||
constantCase: "XML_HTTP_REQUEST",
|
||||
dotCase: "xml.http.request",
|
||||
capitalCase: "Xml Http Request",
|
||||
trainCase: "Xml-Http-Request",
|
||||
},
|
||||
},
|
||||
// Numbers (basic)
|
||||
{
|
||||
input: "foo2bar",
|
||||
expected: {
|
||||
camelCase: "foo2bar",
|
||||
pascalCase: "Foo2bar",
|
||||
snakeCase: "foo2bar",
|
||||
kebabCase: "foo2bar",
|
||||
screamingSnakeCase: "FOO2BAR",
|
||||
constantCase: "FOO2BAR",
|
||||
dotCase: "foo2bar",
|
||||
capitalCase: "Foo2bar",
|
||||
trainCase: "Foo2bar",
|
||||
},
|
||||
},
|
||||
// Multiple spaces
|
||||
{
|
||||
input: "multiple spaces",
|
||||
expected: {
|
||||
camelCase: "multipleSpaces",
|
||||
pascalCase: "MultipleSpaces",
|
||||
snakeCase: "multiple_spaces",
|
||||
kebabCase: "multiple-spaces",
|
||||
screamingSnakeCase: "MULTIPLE_SPACES",
|
||||
constantCase: "MULTIPLE_SPACES",
|
||||
dotCase: "multiple.spaces",
|
||||
capitalCase: "Multiple Spaces",
|
||||
trainCase: "Multiple-Spaces",
|
||||
},
|
||||
},
|
||||
// Special characters
|
||||
{
|
||||
input: "special@#$characters",
|
||||
expected: {
|
||||
camelCase: "specialCharacters",
|
||||
pascalCase: "SpecialCharacters",
|
||||
snakeCase: "special_characters",
|
||||
kebabCase: "special-characters",
|
||||
screamingSnakeCase: "SPECIAL_CHARACTERS",
|
||||
constantCase: "SPECIAL_CHARACTERS",
|
||||
dotCase: "special.characters",
|
||||
capitalCase: "Special Characters",
|
||||
trainCase: "Special-Characters",
|
||||
},
|
||||
},
|
||||
// Unicode with accented characters
|
||||
{
|
||||
input: "café_münchen",
|
||||
expected: {
|
||||
camelCase: "caféMünchen",
|
||||
pascalCase: "CaféMünchen",
|
||||
snakeCase: "café_münchen",
|
||||
kebabCase: "café-münchen",
|
||||
screamingSnakeCase: "CAFÉ_MÜNCHEN",
|
||||
constantCase: "CAFÉ_MÜNCHEN",
|
||||
dotCase: "café.münchen",
|
||||
capitalCase: "Café München",
|
||||
trainCase: "Café-München",
|
||||
},
|
||||
},
|
||||
// Single letter
|
||||
{
|
||||
input: "a",
|
||||
expected: {
|
||||
camelCase: "a",
|
||||
pascalCase: "A",
|
||||
snakeCase: "a",
|
||||
kebabCase: "a",
|
||||
screamingSnakeCase: "A",
|
||||
constantCase: "A",
|
||||
dotCase: "a",
|
||||
capitalCase: "A",
|
||||
trainCase: "A",
|
||||
},
|
||||
},
|
||||
// Two letters
|
||||
{
|
||||
input: "aB",
|
||||
expected: {
|
||||
camelCase: "aB",
|
||||
pascalCase: "AB",
|
||||
snakeCase: "a_b",
|
||||
kebabCase: "a-b",
|
||||
screamingSnakeCase: "A_B",
|
||||
constantCase: "A_B",
|
||||
dotCase: "a.b",
|
||||
capitalCase: "A B",
|
||||
trainCase: "A-B",
|
||||
},
|
||||
},
|
||||
// Tabs and newlines
|
||||
{
|
||||
input: "tabs\tand\nnewlines",
|
||||
expected: {
|
||||
camelCase: "tabsAndNewlines",
|
||||
pascalCase: "TabsAndNewlines",
|
||||
snakeCase: "tabs_and_newlines",
|
||||
kebabCase: "tabs-and-newlines",
|
||||
screamingSnakeCase: "TABS_AND_NEWLINES",
|
||||
constantCase: "TABS_AND_NEWLINES",
|
||||
dotCase: "tabs.and.newlines",
|
||||
capitalCase: "Tabs And Newlines",
|
||||
trainCase: "Tabs-And-Newlines",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Run all test cases
|
||||
for (const { input, expected } of testCases) {
|
||||
describe(`input: "${input}"`, () => {
|
||||
test("camelCase", () => {
|
||||
expect(Bun.camelCase(input)).toBe(expected.camelCase);
|
||||
});
|
||||
|
||||
test("pascalCase", () => {
|
||||
expect(Bun.pascalCase(input)).toBe(expected.pascalCase);
|
||||
});
|
||||
|
||||
test("snakeCase", () => {
|
||||
expect(Bun.snakeCase(input)).toBe(expected.snakeCase);
|
||||
});
|
||||
|
||||
test("kebabCase", () => {
|
||||
expect(Bun.kebabCase(input)).toBe(expected.kebabCase);
|
||||
});
|
||||
|
||||
test("screamingSnakeCase", () => {
|
||||
expect(Bun.screamingSnakeCase(input)).toBe(expected.screamingSnakeCase);
|
||||
});
|
||||
|
||||
test("constantCase (alias)", () => {
|
||||
expect(Bun.constantCase(input)).toBe(expected.constantCase);
|
||||
});
|
||||
|
||||
test("dotCase", () => {
|
||||
expect(Bun.dotCase(input)).toBe(expected.dotCase);
|
||||
});
|
||||
|
||||
test("capitalCase", () => {
|
||||
expect(Bun.capitalCase(input)).toBe(expected.capitalCase);
|
||||
});
|
||||
|
||||
test("trainCase", () => {
|
||||
expect(Bun.trainCase(input)).toBe(expected.trainCase);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Edge cases and special scenarios
|
||||
describe("edge cases", () => {
|
||||
test("null input", () => {
|
||||
expect(Bun.camelCase(null)).toBe("null");
|
||||
expect(Bun.pascalCase(null)).toBe("Null");
|
||||
expect(Bun.snakeCase(null)).toBe("null");
|
||||
});
|
||||
|
||||
test("undefined input", () => {
|
||||
expect(Bun.camelCase(undefined)).toBe("undefined");
|
||||
expect(Bun.pascalCase(undefined)).toBe("Undefined");
|
||||
expect(Bun.snakeCase(undefined)).toBe("undefined");
|
||||
});
|
||||
|
||||
test("number input", () => {
|
||||
expect(Bun.camelCase(123)).toBe("123");
|
||||
expect(Bun.pascalCase(123)).toBe("123");
|
||||
expect(Bun.snakeCase(123)).toBe("123");
|
||||
});
|
||||
|
||||
test("boolean input", () => {
|
||||
expect(Bun.camelCase(true)).toBe("true");
|
||||
expect(Bun.pascalCase(false)).toBe("False");
|
||||
expect(Bun.snakeCase(true)).toBe("true");
|
||||
});
|
||||
|
||||
test("very long string", () => {
|
||||
const longString = "this is a very long string with many words".repeat(10);
|
||||
const result = Bun.camelCase(longString);
|
||||
expect(result).toContain("thisIsAVeryLongString");
|
||||
expect(result.length).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
test("only special characters", () => {
|
||||
expect(Bun.camelCase("!@#$%^&*()")).toBe("");
|
||||
expect(Bun.snakeCase("!@#$%^&*()")).toBe("");
|
||||
expect(Bun.kebabCase("!@#$%^&*()")).toBe("");
|
||||
});
|
||||
|
||||
test("only numbers", () => {
|
||||
expect(Bun.camelCase("123456")).toBe("123456");
|
||||
expect(Bun.pascalCase("123456")).toBe("123456");
|
||||
expect(Bun.snakeCase("123456")).toBe("123456");
|
||||
});
|
||||
|
||||
test("mixed numbers and letters", () => {
|
||||
expect(Bun.camelCase("123abc456def")).toBe("123abc456def");
|
||||
expect(Bun.snakeCase("123abc456def")).toBe("123abc456def");
|
||||
});
|
||||
});
|
||||
|
||||
// Test for proper acronym handling
|
||||
describe("acronym handling", () => {
|
||||
test("XMLHttpRequest", () => {
|
||||
expect(Bun.camelCase("XMLHttpRequest")).toBe("xmlHttpRequest");
|
||||
expect(Bun.snakeCase("XMLHttpRequest")).toBe("xml_http_request");
|
||||
});
|
||||
|
||||
test("IOError", () => {
|
||||
expect(Bun.camelCase("IOError")).toBe("ioError");
|
||||
expect(Bun.snakeCase("IOError")).toBe("io_error");
|
||||
});
|
||||
|
||||
test("HTTPSConnection", () => {
|
||||
expect(Bun.camelCase("HTTPSConnection")).toBe("httpsConnection");
|
||||
expect(Bun.snakeCase("HTTPSConnection")).toBe("https_connection");
|
||||
});
|
||||
|
||||
test("APIKey", () => {
|
||||
expect(Bun.camelCase("APIKey")).toBe("apiKey");
|
||||
expect(Bun.snakeCase("APIKey")).toBe("api_key");
|
||||
});
|
||||
});
|
||||
|
||||
// Test for version strings (important for change-case compatibility)
|
||||
describe("version strings", () => {
|
||||
test("version 1.2.10", () => {
|
||||
const input = "version 1.2.10";
|
||||
// Note: change-case with separateNumbers option would split these differently
|
||||
// Our implementation keeps numbers together unless there's a case change
|
||||
expect(Bun.camelCase(input)).toBe("version1210");
|
||||
expect(Bun.snakeCase(input)).toBe("version_1_2_10");
|
||||
});
|
||||
|
||||
test("v1.0.0", () => {
|
||||
expect(Bun.camelCase("v1.0.0")).toBe("v100");
|
||||
expect(Bun.snakeCase("v1.0.0")).toBe("v1_0_0");
|
||||
});
|
||||
});
|
||||
|
||||
// Consistency checks
|
||||
describe("consistency", () => {
|
||||
test("idempotency - applying same conversion twice yields same result", () => {
|
||||
const testString = "test_string";
|
||||
const once = Bun.snakeCase(testString);
|
||||
const twice = Bun.snakeCase(once);
|
||||
expect(once).toBe(twice);
|
||||
});
|
||||
|
||||
test("round-trip conversions maintain structure", () => {
|
||||
const original = "testString";
|
||||
const snake = Bun.snakeCase(original);
|
||||
const camel = Bun.camelCase(snake);
|
||||
expect(camel).toBe(original);
|
||||
});
|
||||
});
|
||||
});
|
||||
178
test/js/bun/case-convert.test.ts
Normal file
178
test/js/bun/case-convert.test.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { test, expect } from "bun:test";
|
||||
|
||||
test("Bun.camelCase", () => {
|
||||
expect(Bun.camelCase("two words")).toBe("twoWords");
|
||||
expect(Bun.camelCase("hello world")).toBe("helloWorld");
|
||||
expect(Bun.camelCase("HELLO_WORLD")).toBe("helloWorld");
|
||||
expect(Bun.camelCase("kebab-case")).toBe("kebabCase");
|
||||
expect(Bun.camelCase("snake_case")).toBe("snakeCase");
|
||||
expect(Bun.camelCase("PascalCase")).toBe("pascalCase");
|
||||
expect(Bun.camelCase("multiple spaces")).toBe("multipleSpaces");
|
||||
expect(Bun.camelCase("123-numbers-456")).toBe("123Numbers456");
|
||||
expect(Bun.camelCase("")).toBe("");
|
||||
expect(Bun.camelCase("alreadyCamelCase")).toBe("alreadyCamelCase");
|
||||
expect(Bun.camelCase("XML-Parser")).toBe("xmlParser");
|
||||
expect(Bun.camelCase("XMLParser")).toBe("xmlParser");
|
||||
});
|
||||
|
||||
test("Bun.pascalCase", () => {
|
||||
expect(Bun.pascalCase("two words")).toBe("TwoWords");
|
||||
expect(Bun.pascalCase("hello world")).toBe("HelloWorld");
|
||||
expect(Bun.pascalCase("HELLO_WORLD")).toBe("HelloWorld");
|
||||
expect(Bun.pascalCase("kebab-case")).toBe("KebabCase");
|
||||
expect(Bun.pascalCase("snake_case")).toBe("SnakeCase");
|
||||
expect(Bun.pascalCase("camelCase")).toBe("CamelCase");
|
||||
expect(Bun.pascalCase("multiple spaces")).toBe("MultipleSpaces");
|
||||
expect(Bun.pascalCase("123-numbers-456")).toBe("123Numbers456");
|
||||
expect(Bun.pascalCase("")).toBe("");
|
||||
expect(Bun.pascalCase("AlreadyPascalCase")).toBe("AlreadyPascalCase");
|
||||
expect(Bun.pascalCase("xml-parser")).toBe("XmlParser");
|
||||
expect(Bun.pascalCase("XMLParser")).toBe("XmlParser");
|
||||
});
|
||||
|
||||
test("Bun.snakeCase", () => {
|
||||
expect(Bun.snakeCase("two words")).toBe("two_words");
|
||||
expect(Bun.snakeCase("hello world")).toBe("hello_world");
|
||||
expect(Bun.snakeCase("HELLO_WORLD")).toBe("hello_world");
|
||||
expect(Bun.snakeCase("kebab-case")).toBe("kebab_case");
|
||||
expect(Bun.snakeCase("camelCase")).toBe("camel_case");
|
||||
expect(Bun.snakeCase("PascalCase")).toBe("pascal_case");
|
||||
expect(Bun.snakeCase("multiple spaces")).toBe("multiple_spaces");
|
||||
expect(Bun.snakeCase("123-numbers-456")).toBe("123_numbers_456");
|
||||
expect(Bun.snakeCase("")).toBe("");
|
||||
expect(Bun.snakeCase("already_snake_case")).toBe("already_snake_case");
|
||||
expect(Bun.snakeCase("XMLParser")).toBe("xml_parser");
|
||||
});
|
||||
|
||||
test("Bun.kebabCase", () => {
|
||||
expect(Bun.kebabCase("two words")).toBe("two-words");
|
||||
expect(Bun.kebabCase("hello world")).toBe("hello-world");
|
||||
expect(Bun.kebabCase("HELLO_WORLD")).toBe("hello-world");
|
||||
expect(Bun.kebabCase("snake_case")).toBe("snake-case");
|
||||
expect(Bun.kebabCase("camelCase")).toBe("camel-case");
|
||||
expect(Bun.kebabCase("PascalCase")).toBe("pascal-case");
|
||||
expect(Bun.kebabCase("multiple spaces")).toBe("multiple-spaces");
|
||||
expect(Bun.kebabCase("123-numbers-456")).toBe("123-numbers-456");
|
||||
expect(Bun.kebabCase("")).toBe("");
|
||||
expect(Bun.kebabCase("already-kebab-case")).toBe("already-kebab-case");
|
||||
expect(Bun.kebabCase("XMLParser")).toBe("xml-parser");
|
||||
});
|
||||
|
||||
test("Bun.screamingSnakeCase", () => {
|
||||
expect(Bun.screamingSnakeCase("two words")).toBe("TWO_WORDS");
|
||||
expect(Bun.screamingSnakeCase("hello world")).toBe("HELLO_WORLD");
|
||||
expect(Bun.screamingSnakeCase("hello_world")).toBe("HELLO_WORLD");
|
||||
expect(Bun.screamingSnakeCase("kebab-case")).toBe("KEBAB_CASE");
|
||||
expect(Bun.screamingSnakeCase("camelCase")).toBe("CAMEL_CASE");
|
||||
expect(Bun.screamingSnakeCase("PascalCase")).toBe("PASCAL_CASE");
|
||||
expect(Bun.screamingSnakeCase("multiple spaces")).toBe("MULTIPLE_SPACES");
|
||||
expect(Bun.screamingSnakeCase("123-numbers-456")).toBe("123_NUMBERS_456");
|
||||
expect(Bun.screamingSnakeCase("")).toBe("");
|
||||
expect(Bun.screamingSnakeCase("ALREADY_CONSTANT_CASE")).toBe("ALREADY_CONSTANT_CASE");
|
||||
expect(Bun.screamingSnakeCase("XMLParser")).toBe("XML_PARSER");
|
||||
});
|
||||
|
||||
test("Bun.constantCase (alias for screamingSnakeCase)", () => {
|
||||
// constantCase should work as an alias for backward compatibility
|
||||
expect(Bun.constantCase("two words")).toBe("TWO_WORDS");
|
||||
expect(Bun.constantCase("camelCase")).toBe("CAMEL_CASE");
|
||||
// Both should produce the same output
|
||||
const testCases = ["hello world", "camelCase", "PascalCase", "snake_case", "kebab-case"];
|
||||
for (const testCase of testCases) {
|
||||
expect(Bun.constantCase(testCase)).toBe(Bun.screamingSnakeCase(testCase));
|
||||
}
|
||||
});
|
||||
|
||||
test("Bun.dotCase", () => {
|
||||
expect(Bun.dotCase("two words")).toBe("two.words");
|
||||
expect(Bun.dotCase("hello world")).toBe("hello.world");
|
||||
expect(Bun.dotCase("HELLO_WORLD")).toBe("hello.world");
|
||||
expect(Bun.dotCase("kebab-case")).toBe("kebab.case");
|
||||
expect(Bun.dotCase("camelCase")).toBe("camel.case");
|
||||
expect(Bun.dotCase("PascalCase")).toBe("pascal.case");
|
||||
expect(Bun.dotCase("multiple spaces")).toBe("multiple.spaces");
|
||||
expect(Bun.dotCase("123-numbers-456")).toBe("123.numbers.456");
|
||||
expect(Bun.dotCase("")).toBe("");
|
||||
expect(Bun.dotCase("already.dot.case")).toBe("already.dot.case");
|
||||
expect(Bun.dotCase("XMLParser")).toBe("xml.parser");
|
||||
});
|
||||
|
||||
test("Bun.capitalCase", () => {
|
||||
expect(Bun.capitalCase("two words")).toBe("Two Words");
|
||||
expect(Bun.capitalCase("hello world")).toBe("Hello World");
|
||||
expect(Bun.capitalCase("HELLO_WORLD")).toBe("Hello World");
|
||||
expect(Bun.capitalCase("kebab-case")).toBe("Kebab Case");
|
||||
expect(Bun.capitalCase("camelCase")).toBe("Camel Case");
|
||||
expect(Bun.capitalCase("PascalCase")).toBe("Pascal Case");
|
||||
expect(Bun.capitalCase("multiple spaces")).toBe("Multiple Spaces");
|
||||
expect(Bun.capitalCase("123-numbers-456")).toBe("123 Numbers 456");
|
||||
expect(Bun.capitalCase("")).toBe("");
|
||||
expect(Bun.capitalCase("already Capital Case")).toBe("Already Capital Case");
|
||||
expect(Bun.capitalCase("XMLParser")).toBe("Xml Parser");
|
||||
});
|
||||
|
||||
test("Bun.trainCase", () => {
|
||||
expect(Bun.trainCase("two words")).toBe("Two-Words");
|
||||
expect(Bun.trainCase("hello world")).toBe("Hello-World");
|
||||
expect(Bun.trainCase("HELLO_WORLD")).toBe("Hello-World");
|
||||
expect(Bun.trainCase("kebab-case")).toBe("Kebab-Case");
|
||||
expect(Bun.trainCase("camelCase")).toBe("Camel-Case");
|
||||
expect(Bun.trainCase("PascalCase")).toBe("Pascal-Case");
|
||||
expect(Bun.trainCase("multiple spaces")).toBe("Multiple-Spaces");
|
||||
expect(Bun.trainCase("123-numbers-456")).toBe("123-Numbers-456");
|
||||
expect(Bun.trainCase("")).toBe("");
|
||||
expect(Bun.trainCase("Already-Train-Case")).toBe("Already-Train-Case");
|
||||
expect(Bun.trainCase("XMLParser")).toBe("Xml-Parser");
|
||||
});
|
||||
|
||||
test("case conversion with special characters", () => {
|
||||
const input = "hello@world#test!";
|
||||
expect(Bun.camelCase(input)).toBe("helloWorldTest");
|
||||
expect(Bun.pascalCase(input)).toBe("HelloWorldTest");
|
||||
expect(Bun.snakeCase(input)).toBe("hello_world_test");
|
||||
expect(Bun.kebabCase(input)).toBe("hello-world-test");
|
||||
expect(Bun.constantCase(input)).toBe("HELLO_WORLD_TEST");
|
||||
expect(Bun.dotCase(input)).toBe("hello.world.test");
|
||||
expect(Bun.capitalCase(input)).toBe("Hello World Test");
|
||||
expect(Bun.trainCase(input)).toBe("Hello-World-Test");
|
||||
});
|
||||
|
||||
test("case conversion with numbers", () => {
|
||||
// Numbers stay with adjacent letters unless there's a case change
|
||||
const input = "test123case456";
|
||||
expect(Bun.camelCase(input)).toBe("test123case456");
|
||||
expect(Bun.pascalCase(input)).toBe("Test123case456");
|
||||
expect(Bun.snakeCase(input)).toBe("test123case456");
|
||||
expect(Bun.kebabCase(input)).toBe("test123case456");
|
||||
expect(Bun.constantCase(input)).toBe("TEST123CASE456");
|
||||
expect(Bun.dotCase(input)).toBe("test123case456");
|
||||
expect(Bun.capitalCase(input)).toBe("Test123case456");
|
||||
expect(Bun.trainCase(input)).toBe("Test123case456");
|
||||
|
||||
// When there's a case change after numbers, it splits
|
||||
const input2 = "test123Case456";
|
||||
expect(Bun.camelCase(input2)).toBe("test123Case456");
|
||||
expect(Bun.snakeCase(input2)).toBe("test123_case456");
|
||||
expect(Bun.kebabCase(input2)).toBe("test123-case456");
|
||||
});
|
||||
|
||||
test("case conversion with non-strings", () => {
|
||||
// Should convert to string first
|
||||
expect(Bun.camelCase(123)).toBe("123");
|
||||
expect(Bun.camelCase(true)).toBe("true");
|
||||
expect(Bun.camelCase(null)).toBe("null");
|
||||
expect(Bun.camelCase(undefined)).toBe("undefined");
|
||||
});
|
||||
|
||||
test("case conversion error handling", () => {
|
||||
// Should throw when no arguments provided
|
||||
expect(() => (Bun as any).camelCase()).toThrow();
|
||||
expect(() => (Bun as any).pascalCase()).toThrow();
|
||||
expect(() => (Bun as any).snakeCase()).toThrow();
|
||||
expect(() => (Bun as any).kebabCase()).toThrow();
|
||||
expect(() => (Bun as any).screamingSnakeCase()).toThrow();
|
||||
expect(() => (Bun as any).constantCase()).toThrow(); // Should still work as alias
|
||||
expect(() => (Bun as any).dotCase()).toThrow();
|
||||
expect(() => (Bun as any).capitalCase()).toThrow();
|
||||
expect(() => (Bun as any).trainCase()).toThrow();
|
||||
});
|
||||
Reference in New Issue
Block a user