Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions library/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export * from './toCamelCase/index.ts';
export * from './toDate/index.ts';
export * from './toKebabCase/index.ts';
export * from './toLowerCase/index.ts';
export * from './toCamelCase/index.ts';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Duplicate re-export of toCamelCase/index.ts.

Line 104 already exports ./toCamelCase/index.ts; line 108 duplicates it verbatim. The line-range summary claims this was "a previously missing re-export," but the annotated file shows the export already existed — this is a redundant duplicate, not a new addition, and likely a leftover from the file-organization mixup in toCamelCase.ts (see companion comment there).

🧹 Proposed fix
 export * from './toDate/index.ts';
 export * from './toKebabCase/index.ts';
 export * from './toLowerCase/index.ts';
-export * from './toCamelCase/index.ts';
 export * from './toMaxValue/index.ts';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export * from './toCamelCase/index.ts';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/src/actions/index.ts` at line 108, Remove the redundant duplicate
export of ./toCamelCase/index.ts from the barrel exports in
library/src/actions/index.ts, keeping the existing single re-export unchanged.

export * from './toMaxValue/index.ts';
export * from './toMinValue/index.ts';
export * from './toNumber/index.ts';
Expand Down
2 changes: 1 addition & 1 deletion library/src/actions/toCamelCase/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './toCamelCase.ts';
export * from './toCamelCase.ts';
138 changes: 120 additions & 18 deletions library/src/actions/toCamelCase/toCamelCase.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
import type { BaseTransformation } from '../../types/index.ts';
import { _formatCase } from '../../utils/index.ts';

/** Splits a string into words for case conversion. */
function splitWords(value: string): string[] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Reimplementing case-splitting logic in a local splitWords() helper instead of reusing the shared _formatCase util (still used by the sibling toSnakeCase/toKebabCase/toPascalCase actions) duplicates tested logic and risks behavioral drift between the case-conversion actions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At library/src/actions/toCamelCase/toCamelCase.ts, line 4:

<comment>Reimplementing case-splitting logic in a local splitWords() helper instead of reusing the shared `_formatCase` util (still used by the sibling toSnakeCase/toKebabCase/toPascalCase actions) duplicates tested logic and risks behavioral drift between the case-conversion actions.</comment>

<file context>
@@ -1,36 +1,33 @@
-import { _formatCase } from '../../utils/index.ts';
+
+/** Splits a string into words for case conversion. */
+function splitWords(value: string): string[] {
+  // Split on non-alphanumeric boundaries and existing case/separator breaks.
+  return value
</file context>

// Split on non-alphanumeric boundaries and existing case/separator breaks.
return value
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: splitWords doesn't split on uppercase acronym boundaries (e.g., URLValue → stays as one word). This means inputs with acronym runs like parseURLValue are not split into their constituent words. The old toCamelCase implementation handled this correctly (parseURLValueparseUrlValue), but the new shared splitWords function produces "parseUrlvalue" instead, losing the capital V in Value. This affects all four conversion actions. Consider adding a regex like /([A-Z]+)([A-Z][a-z])/g to split between uppercase runs that transition to a capitalized word.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At library/src/actions/toCamelCase/toCamelCase.ts, line 7:

<comment>`splitWords` doesn't split on uppercase acronym boundaries (e.g., `URLValue` → stays as one word). This means inputs with acronym runs like `parseURLValue` are not split into their constituent words. The old `toCamelCase` implementation handled this correctly (`parseURLValue` → `parseUrlValue`), but the new shared `splitWords` function produces `"parseUrlvalue"` instead, losing the capital V in `Value`. This affects all four conversion actions. Consider adding a regex like `/([A-Z]+)([A-Z][a-z])/g` to split between uppercase runs that transition to a capitalized word.</comment>

<file context>
@@ -1,36 +1,33 @@
+function splitWords(value: string): string[] {
+  // Split on non-alphanumeric boundaries and existing case/separator breaks.
+  return value
+    .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
+    .replace(/[_\-\s]+/g, ' ')
+    .split(' ')
</file context>

.replace(/[_\-\s]+/g, ' ')
.split(' ')
.map((part) => part.trim())
.filter((part) => part.length > 0);
}
Comment on lines +4 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does valibot's toCamelCase/toSnakeCase/toKebabCase/toPascalCase splitWords implementation split acronym boundaries like "parseURLValue" into "parse Url Value"?

💡 Result:

Yes, Valibot's string case transformation actions (toCamelCase, toSnakeCase, toKebabCase, and toPascalCase) do split words based on acronym boundaries [1][2][3][4]. According to the official documentation, these actions treat acronym runs as distinct segments that are normalized to lowercase [2][3][4]. For example, the input parseURLValue is processed as parse, URL, and Value, resulting in outputs like [3][4]: - toCamelCase: parseUrlValue [3] - toSnakeCase: parse_url_value [4] - toKebabCase: parse-url-value [2] - toPascalCase: ParseUrlValue [5] The implementation uses a shared tokenizer utility that identifies word boundaries by underscores (_), hyphens (-), ASCII whitespace, and both case and acronym changes [1][2][3][6]. Digits remain attached to the preceding token (e.g., item2Name becomes item2-name) [2][3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'library/src/actions/toCamelCase/*' 'library/src/actions/*Case*' 'library/src/**/to*Case*' | sed -n '1,120p'

Repository: open-circle/valibot

Length of output: 1328


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the implementation and tests with line numbers.
sed -n '1,220p' library/src/actions/toCamelCase/toCamelCase.ts
printf '\n--- TESTS ---\n'
sed -n '1,240p' library/src/actions/toCamelCase/toCamelCase.test.ts

Repository: open-circle/valibot

Length of output: 6673


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re

def split_words(value: str):
    return [
        part.strip()
        for part in re.sub(r'([a-z0-9])([A-Z])', r'\1 \2', value)
                    .replace('_', ' ')
                    .replace('-', ' ')
                    .split(' ')
        if part.strip()
    ]

def camel(value: str):
    words = split_words(value)
    return ''.join(
        w.lower() if i == 0 else w[:1].upper() + w[1:].lower()
        for i, w in enumerate(words)
    )

for s in ["parseURLValue", "helloWorld", "item2Name", "HELLO_WORLD", "--foo__bar--", "foo\tbar\nbaz"]:
    print(s, "=>", split_words(s), "=>", camel(s))
PY

Repository: open-circle/valibot

Length of output: 441


Split acronym boundaries in splitWords
Add the acronym split before the lowercase→uppercase rule, otherwise parseURLValue becomes parseUrlvalue and the same bug affects toSnakeCase, toKebabCase, and toPascalCase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/src/actions/toCamelCase/toCamelCase.ts` around lines 4 - 12, Update
splitWords to split acronym-to-word boundaries before applying the existing
lowercase-or-digit-to-uppercase replacement, so inputs like parseURLValue
produce separate URL and Value tokens. Preserve the existing separator handling
and ensure the shared tokenization yields correct results for toCamelCase,
toSnakeCase, toKebabCase, and toPascalCase.


/**
* To camel case action interface.
*
* @beta
*/
export interface ToCamelCaseAction
extends BaseTransformation<string, string, never> {
/**
* The action type.
*/
readonly type: 'to_camel_case';
/**
* The action reference.
*/
readonly reference: typeof toCamelCase;
}

/**
* Creates a to camel case transformation action.
*
* Words are separated by `_`, `-` and ASCII whitespace, as well as by case
* and acronym boundaries.
*
* Hint: Acronym runs are normalized to lowercase (e.g. `parseURLValue` →
* `parseUrlValue`) and digits stay attached to the preceding token (e.g.
* `item2Name` → `item2Name`).
* Converts the input to camelCase, e.g. `"hello_world"` → `"helloWorld"`,
* `"foo-bar"` → `"fooBar"`. Existing case boundaries (e.g. `fooBar`) are
* preserved and re-joined. See issue #1324.
*
* @returns A to camel case action.
*
* @beta
*/
// @__NO_SIDE_EFFECTS__
export function toCamelCase(): ToCamelCaseAction {
Expand All @@ -40,8 +37,113 @@ export function toCamelCase(): ToCamelCaseAction {
reference: toCamelCase,
async: false,
'~run'(dataset) {
dataset.value = _formatCase(dataset.value, '', false, true);
const words = splitWords(dataset.value);
dataset.value = words
.map((word, i) =>
i === 0
? word.toLowerCase()
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
)
.join('');
return dataset;
},
};
}

/**
* To snake case action interface.
*/
export interface ToSnakeCaseAction
extends BaseTransformation<string, string, never> {
readonly type: 'to_snake_case';
readonly reference: typeof toSnakeCase;
}

/**
* Creates a to snake case transformation action.
*
* Converts the input to snake_case, e.g. `"helloWorld"` → `"hello_world"`,
* `"foo-bar"` → `"foo_bar"`. See issue #1324.
*
* @returns A to snake case action.
*/
// @__NO_SIDE_EFFECTS__
export function toSnakeCase(): ToSnakeCaseAction {
return {
kind: 'transformation',
type: 'to_snake_case',
reference: toSnakeCase,
async: false,
'~run'(dataset) {
const words = splitWords(dataset.value);
dataset.value = words.map((w) => w.toLowerCase()).join('_');
return dataset;
},
};
}

/**
* To kebab case action interface.
*/
export interface ToKebabCaseAction
extends BaseTransformation<string, string, never> {
readonly type: 'to_kebab_case';
readonly reference: typeof toKebabCase;
}

/**
* Creates a to kebab case transformation action.
*
* Converts the input to kebab-case, e.g. `"helloWorld"` → `"hello-world"`,
* `"foo_bar"` → `"foo-bar"`. See issue #1324.
*
* @returns A to kebab case action.
*/
// @__NO_SIDE_EFFECTS__
export function toKebabCase(): ToKebabCaseAction {
return {
kind: 'transformation',
type: 'to_kebab_case',
reference: toKebabCase,
async: false,
'~run'(dataset) {
const words = splitWords(dataset.value);
dataset.value = words.map((w) => w.toLowerCase()).join('-');
return dataset;
},
};
}

/**
* To pascal case action interface.
*/
export interface ToPascalCaseAction
extends BaseTransformation<string, string, never> {
readonly type: 'to_pascal_case';
readonly reference: typeof toPascalCase;
}

/**
* Creates a to pascal case transformation action.
*
* Converts the input to PascalCase, e.g. `"hello_world"` → `"HelloWorld"`,
* `"foo-bar"` → `"FooBar"`. See issue #1324.
*
* @returns A to pascal case action.
*/
// @__NO_SIDE_EFFECTS__
export function toPascalCase(): ToPascalCaseAction {
return {
kind: 'transformation',
type: 'to_pascal_case',
reference: toPascalCase,
async: false,
'~run'(dataset) {
const words = splitWords(dataset.value);
dataset.value = words
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
.join('');
return dataset;
},
};
}
Comment on lines +53 to +149

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether dedicated action files for toSnakeCase/toKebabCase/toPascalCase already exist
fd -a 'toSnakeCase.ts|toKebabCase.ts|toPascalCase.ts' library/src/actions
echo "---"
cat -n library/src/actions/toSnakeCase/toSnakeCase.ts 2>/dev/null
echo "---"
cat -n library/src/actions/toKebabCase/toKebabCase.ts 2>/dev/null
echo "---"
cat -n library/src/actions/toPascalCase/toPascalCase.ts 2>/dev/null

Repository: open-circle/valibot

Length of output: 4741


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- toCamelCase.ts ---\n'
cat -n library/src/actions/toCamelCase/toCamelCase.ts

printf '\n--- toCamelCase/index.ts ---\n'
cat -n library/src/actions/toCamelCase/index.ts

printf '\n--- actions/index.ts ---\n'
cat -n library/src/actions/index.ts

printf '\n--- exports referencing toSnakeCase/toKebabCase/toPascalCase ---\n'
rg -n "toSnakeCase|toKebabCase|toPascalCase" library/src/actions -g '!**/*.d.ts'

Repository: open-circle/valibot

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- library/src/actions/toCamelCase/index.ts ---\n'
cat -n library/src/actions/toCamelCase/index.ts

printf '\n--- library/src/actions/index.ts ---\n'
cat -n library/src/actions/index.ts

printf '\n--- library/src/actions/toSnakeCase/index.ts ---\n'
cat -n library/src/actions/toSnakeCase/index.ts

printf '\n--- library/src/actions/toKebabCase/index.ts ---\n'
cat -n library/src/actions/toKebabCase/index.ts

printf '\n--- library/src/actions/toPascalCase/index.ts ---\n'
cat -n library/src/actions/toPascalCase/index.ts

Repository: open-circle/valibot

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- library/src/actions/toCamelCase/toCamelCase.ts ---'
cat -n library/src/actions/toCamelCase/toCamelCase.ts

echo
echo '--- library/src/actions/toCamelCase/index.ts ---'
cat -n library/src/actions/toCamelCase/index.ts

echo
echo '--- library/src/actions/index.ts ---'
cat -n library/src/actions/index.ts

echo
echo '--- library/src/actions/toSnakeCase/index.ts ---'
cat -n library/src/actions/toSnakeCase/index.ts

echo
echo '--- library/src/actions/toKebabCase/index.ts ---'
cat -n library/src/actions/toKebabCase/index.ts

echo
echo '--- library/src/actions/toPascalCase/index.ts ---'
cat -n library/src/actions/toPascalCase/index.ts

Repository: open-circle/valibot

Length of output: 11039


Remove the duplicate case actions from library/src/actions/toCamelCase/toCamelCase.ts

toSnakeCase, toKebabCase, and toPascalCase already have dedicated modules and are re-exported from library/src/actions/index.ts; keeping them here makes those names ambiguous in the aggregate barrel and drops them from the public export surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/src/actions/toCamelCase/toCamelCase.ts` around lines 53 - 149, Remove
the ToSnakeCaseAction, toSnakeCase, ToKebabCaseAction, toKebabCase,
ToPascalCaseAction, and toPascalCase declarations from this module, leaving the
toCamelCase implementation intact. Rely on their dedicated modules and existing
exports from the actions index so each case action has a single public
definition.

Source: Coding guidelines