Skip to content

feat: support for more String transformation actions#1558

Open
hazrid93 wants to merge 1 commit into
open-circle:mainfrom
hazrid93:hazrid93/31_1324
Open

feat: support for more String transformation actions#1558
hazrid93 wants to merge 1 commit into
open-circle:mainfrom
hazrid93:hazrid93/31_1324

Conversation

@hazrid93

@hazrid93 hazrid93 commented Jul 20, 2026

Copy link
Copy Markdown

Closes #1324.

valibot has toLowerCase/toUpperCase but no actions for the common case conversions (camelCase, snake_case, kebab case, PascalCase) needed when converting between API (snake_case) and frontend (camelCase) naming, building URL slugs, etc. Add four transformation actions: toCamelCase (hello_world to helloWorld), toSnakeCase (helloWorld to hello_world), toKebabCase (helloWorld to hello world), toPascalCase (hello_world to HelloWorld).

Summary by CodeRabbit

  • New Features
    • Added public access to camel case conversion.
    • Added or updated snake case, kebab case, and Pascal case conversion actions.
  • Improvements
    • Improved conversion of strings with mixed casing, separators, and word boundaries.
    • Standardized formatting results across supported case-conversion options for more predictable output.

Closes open-circle#1324.

valibot has toLowerCase/toUpperCase but no actions for the common case conversions (camelCase, snake_case, kebab case, PascalCase) needed when converting between API (snake_case) and frontend (camelCase) naming, building URL slugs, etc. Add four transformation actions: toCamelCase (hello_world to helloWorld), toSnakeCase (helloWorld to hello_world), toKebabCase (helloWorld to hello world), toPascalCase (hello_world to HelloWorld).
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Case transformation actions now use a shared splitWords helper to tokenize strings and rebuild camel, snake, kebab, and Pascal case values. The actions index also re-exports the toCamelCase module, while its nested index continues to re-export the implementation.

Possibly related PRs

  • open-circle/valibot#1457: Adds related string case-conversion actions and updates the same public action export surface.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding more string transformation actions.
Linked Issues check ✅ Passed The PR implements the four requested case-conversion actions: camel, snake, kebab, and Pascal.
Out of Scope Changes check ✅ Passed The changes stay focused on adding and exporting the new string transformation actions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with 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.

Inline comments:
In `@library/src/actions/index.ts`:
- 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.

In `@library/src/actions/toCamelCase/toCamelCase.ts`:
- Around line 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.
- Around line 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.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e1af0518-19c5-4713-ac57-2efec33b86aa

📥 Commits

Reviewing files that changed from the base of the PR and between 90bc2c7 and 8ddf66c.

📒 Files selected for processing (3)
  • library/src/actions/index.ts
  • library/src/actions/toCamelCase/index.ts
  • library/src/actions/toCamelCase/toCamelCase.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

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.

Comment on lines +4 to +12
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(' ')
.map((part) => part.trim())
.filter((part) => part.length > 0);
}

Copy link
Copy Markdown

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.

Comment on lines +53 to +149
/**
* 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;
},
};
} No newline at end of file

Copy link
Copy Markdown

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="library/src/actions/toCamelCase/toCamelCase.ts">

<violation number="1" location="library/src/actions/toCamelCase/toCamelCase.ts:4">
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.</violation>

<violation number="2" location="library/src/actions/toCamelCase/toCamelCase.ts:7">
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 (`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.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

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')

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>

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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for more String transformation actions

1 participant