Skip to content

fix(zod-to-valibot): handle z.partial() function form and add z.brand() support#1524

Open
codewithsupra wants to merge 3 commits into
open-circle:mainfrom
codewithsupra:fix/codemod-partial-function-form-and-brand
Open

fix(zod-to-valibot): handle z.partial() function form and add z.brand() support#1524
codewithsupra wants to merge 3 commits into
open-circle:mainfrom
codewithsupra:fix/codemod-partial-function-form-and-brand

Conversation

@codewithsupra

@codewithsupra codewithsupra commented Jun 29, 2026

Copy link
Copy Markdown

Problem

Two bugs in @valibot/zod-to-valibot when migrating Zod v4 code:

1. z.partial(Schema) mangled to v.partial(v) (fixes #1503)

Zod v4 exposes partial as both a method (Schema.partial()) and a standalone function (z.partial(Schema)). The codemod only handled the method form. When it encountered the function form, it incorrectly used the z namespace identifier as the schema argument:

// Input
const xxx = z.partial(Foo).parse("xxx");

// Before (wrong)
const xxx = v.parse(v.partial(v), "xxx");

// After (correct)
const xxx = v.parse(v.partial(Foo), "xxx");

Fix: In transformPartial, detect the function form by checking if inputArgs[0] is not an ObjectExpression (key-selector pattern). If so, treat inputArgs[0] as the schema and inputArgs[1] as the optional key selector.

2. z.string().brand("name") crashes the codemod (fixes #1501)

brand was not listed in ZOD_METHODS, so the codemod treated it as an unknown method and failed to transform the chain:

// Input
const Schema = z.string().brand("StationNumber");

// Before (broken output)
const Schema = v.string()("StationNumber");

// After (correct)
const Schema = v.pipe(v.string(), v.brand("StationNumber"));

Fix: Add brand to ZOD_METHODS and implement transformBrand using addToPipe, matching how Valibot's v.brand() action works.

Changes

  • constants.ts — add 'brand' to ZOD_METHODS
  • methods/brand/brand.ts — new transformBrand using addToPipe
  • methods/index.ts — export brand transform
  • methods/partial/partial.ts — detect and handle function-call form
  • schemas-and-links.ts — import and wire transformBrand into the switch
  • __testfixtures__/brand-schema/ — new test fixture
  • __testfixtures__/object-partial-function-form/ — new test fixture
  • test-setup.test.ts — register both new fixtures

All 73 tests pass.

Summary by CodeRabbit

  • New Features

    • Added support for converting Zod .brand(...) chains to Valibot branding.
    • Expanded partial-schema conversion to support the standalone z.partial(schema, options?) function-call form, including an optional key selector.
  • Bug Fixes

    • Improved correctness for z.partial conversions across chained and standalone usage patterns, including cases where the mask is provided via a variable.
  • Tests

    • Added/updated fixtures and test cases for branded schemas, partial function-call variants, and object partial-mask scenarios.

@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. fix A smaller enhancement or bug fix labels Jun 29, 2026
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ff1d559b-8042-4353-9355-2bebe5fcb299

📥 Commits

Reviewing files that changed from the base of the PR and between fb3972a and d613109.

📒 Files selected for processing (1)
  • codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts

Walkthrough

The PR adds a new brand transform path for Zod .brand(...), wiring it into method dispatch and exporting it through the transformer method registry. It also updates transformPartial to handle z.partial(Schema, keys?) by treating the first argument as the schema and reading the optional selector separately. New fixtures cover branded schemas, function-call partials, and variable-mask partial cases.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main codemod fixes for z.partial() function form and z.brand() support.
Linked Issues check ✅ Passed The changes address both linked issues by preserving the schema in z.partial(Schema) and adding brand transform support.
Out of Scope Changes check ✅ Passed The added fixtures, exports, and transform updates all support the two requested codemod fixes and stay in scope.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.1)
codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts

File contains syntax errors that prevent linting: Line 589: Expected a semicolon or an implicit semicolon after a statement, but found none


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: 5

🤖 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
`@codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts`:
- Around line 4-20: `transformBrand` is a pure exported helper that is missing
the required documentation and side-effect marker. Add JSDoc directly above the
exported `transformBrand` function, and place the `// `@__NO_SIDE_EFFECTS__``
annotation before it so it follows the codemod guidelines for pure factory
helpers. Keep the annotations attached to `transformBrand` so they remain
discoverable even if the implementation shifts.
- Line 2: The relative import in brand.ts should use an explicit .ts extension
to satisfy the repo’s ESM import rule. Update the import for addToPipe from the
local helpers module so it references the .ts file directly, keeping the
existing path structure and symbol name intact.

In
`@codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.ts`:
- Line 1: The barrel re-export in the methods/brand entrypoint uses a
extensionless path, which violates the ESM import rule used throughout the
codemod sources. Update the export in the brand index barrel to use an explicit
.ts re-export path so it matches the rest of the codebase and satisfies the
ESLint import extension requirement.

In `@codemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.ts`:
- Line 2: The barrel export in the methods index is using an extensionless path,
which violates the ESM import rules in this codebase. Update the export in the
index module to reference the explicit TypeScript entry point for brand, such as
./brand/index.ts or the concrete module it re-exports, so the new barrel remains
ESLint-compliant and ESM-safe. Use the existing index barrel and brand module
names to locate the change.

In
`@codemod/zod-to-valibot/src/transform/schemas-and-links/methods/partial/partial.ts`:
- Around line 25-39: The standalone partial transformation in partial.ts is
being detected too broadly by checking inputArgs[0].type instead of the actual
call shape, which causes method-chain cases like Schema.partial(mask) to lose
the schema receiver and emit the wrong output. Update the branch in the partial
transformer to only match true standalone z.partial(...) calls using the
call/method structure around inputArgs and schemaArg, then keep method-chain
forms flowing through the schemaExp path. Add a fixture covering
Foo.partial(mask) to lock in the correct behavior for identifier-based
selectors.
🪄 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: ede4a32e-9bba-48c6-9e39-c5c5e52726cc

📥 Commits

Reviewing files that changed from the base of the PR and between 0dc26ea and 68b15e0.

📒 Files selected for processing (11)
  • codemod/zod-to-valibot/__testfixtures__/brand-schema/input.ts
  • codemod/zod-to-valibot/__testfixtures__/brand-schema/output.ts
  • codemod/zod-to-valibot/__testfixtures__/object-partial-function-form/input.ts
  • codemod/zod-to-valibot/__testfixtures__/object-partial-function-form/output.ts
  • codemod/zod-to-valibot/src/test-setup.test.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/constants.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/partial/partial.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts

Comment thread codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts Outdated
Comment thread codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.ts Outdated
Comment thread codemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.ts Outdated
Comment thread codemod/zod-to-valibot/src/transform/schemas-and-links/methods/partial/partial.ts Outdated

@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.

3 issues found across 11 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="codemod/zod-to-valibot/__testfixtures__/brand-schema/input.ts">

<violation number="1" location="codemod/zod-to-valibot/__testfixtures__/brand-schema/input.ts:3">
P2: Brand test fixture only covers runtime-string-argument brands, missing the common `.brand<"Name">()` and `.brand()` (no-argument) forms. Since `transformBrand` only copies runtime `args` and drops TypeScript type parameters, the dominant Zod brand pattern is untested and its type information would be lost in transformation.</violation>
</file>

<file name="codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts">

<violation number="1" location="codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts:7">
P1: `transformBrand` drops TypeScript generic brand names by only forwarding runtime `args`, producing invalid `v.brand()` for the canonical `z.string().brand<'Name'>()` pattern.</violation>
</file>

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

Re-trigger cubic

export function transformBrand(
valibotIdentifier: string,
schemaExp: j.CallExpression | j.MemberExpression | j.Identifier,
args: j.CallExpression['arguments']

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: transformBrand drops TypeScript generic brand names by only forwarding runtime args, producing invalid v.brand() for the canonical z.string().brand<'Name'>() pattern.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts, line 7:

<comment>`transformBrand` drops TypeScript generic brand names by only forwarding runtime `args`, producing invalid `v.brand()` for the canonical `z.string().brand<'Name'>()` pattern.</comment>

<file context>
@@ -0,0 +1,20 @@
+export function transformBrand(
+  valibotIdentifier: string,
+  schemaExp: j.CallExpression | j.MemberExpression | j.Identifier,
+  args: j.CallExpression['arguments']
+) {
+  return addToPipe(
</file context>

Comment thread codemod/zod-to-valibot/src/transform/schemas-and-links/methods/partial/partial.ts Outdated
@@ -0,0 +1,5 @@
import { z } from "zod";

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: Brand test fixture only covers runtime-string-argument brands, missing the common .brand<"Name">() and .brand() (no-argument) forms. Since transformBrand only copies runtime args and drops TypeScript type parameters, the dominant Zod brand pattern is untested and its type information would be lost in transformation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At codemod/zod-to-valibot/__testfixtures__/brand-schema/input.ts, line 3:

<comment>Brand test fixture only covers runtime-string-argument brands, missing the common `.brand<"Name">()` and `.brand()` (no-argument) forms. Since `transformBrand` only copies runtime `args` and drops TypeScript type parameters, the dominant Zod brand pattern is untested and its type information would be lost in transformation.</comment>

<file context>
@@ -0,0 +1,5 @@
+import { z } from "zod";
+
+const Schema1 = z.string().brand("StationNumber");
+const Schema2 = z.number().brand("UserId");
+const Schema3 = z.string().min(1).brand("NonEmptyString");
</file context>

…e standalone z.partial on isStandaloneCall flag, add variable-mask fixture
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Jun 29, 2026

@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: 1

🧹 Nitpick comments (1)
codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts (1)

14-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a .ts specifier for the new methods import.

The added ./methods import breaks this repo’s TS ESM import rule and is likely to fail lint in this path. As per coding guidelines, **/*.{ts,tsx,js,jsx} files must use ESM imports with .ts extensions.

♻️ Suggested fix
-} from './methods';
+} from './methods/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 `@codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts`
at line 14, The new methods import in schemas-and-links.ts uses a bare ./methods
path, which violates the repo’s TS ESM import convention. Update the import in
the schemas-and-links module to use the .ts specifier for the methods file, and
make sure any related import statements in this transform path follow the same
ESM extension rule.

Source: Coding guidelines

🤖 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 `@codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts`:
- Around line 589-595: The standalone-call check in schemas-and-links.ts is too
broad because `transformedExp === null` also matches linked alias chains like
`Foo.partial(mask)`, causing `toValibotMethodExp` and `transformPartial` to
treat `mask` as the schema argument. Update the `isStandaloneCall` logic in this
recursive link path to detect only the واقعی standalone API shape for `partial`
rather than relying on `transformedExp` alone, and keep the linked call flowing
through the normal method-call branch with the correct schema/mask argument
order.

---

Nitpick comments:
In `@codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts`:
- Line 14: The new methods import in schemas-and-links.ts uses a bare ./methods
path, which violates the repo’s TS ESM import convention. Update the import in
the schemas-and-links module to use the .ts specifier for the methods file, and
make sure any related import statements in this transform path follow the same
ESM extension rule.
🪄 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: 19beaf74-090b-4eae-870a-e7cd12c78487

📥 Commits

Reviewing files that changed from the base of the PR and between 68b15e0 and fb3972a.

📒 Files selected for processing (7)
  • codemod/zod-to-valibot/__testfixtures__/object-partial-variable-mask/input.ts
  • codemod/zod-to-valibot/__testfixtures__/object-partial-variable-mask/output.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/partial/partial.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts
✅ Files skipped from review due to trivial changes (3)
  • codemod/zod-to-valibot/testfixtures/object-partial-variable-mask/output.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.ts
  • codemod/zod-to-valibot/testfixtures/object-partial-variable-mask/input.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/partial/partial.ts

Comment on lines +589 to +595
const isStandaloneCall = transformedExp === null;
transformedExp = toValibotMethodExp(
valibotIdentifier,
propertyName,
transformedExp ?? j.identifier(identifier),
cur.value.arguments
cur.value.arguments,
isStandaloneCall

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

transformedExp === null misclassifies linked .partial(mask) calls as standalone.

During the recursive link pass, alias-based chains like Foo.partial(mask) also arrive here with transformedExp === null. That sets isStandaloneCall=true, and transformPartial then reads mask as the schema argument, producing the wrong Valibot call. Gate the flag on the actual standalone API shape instead of transformedExp alone.

💡 Suggested fix
-        const isStandaloneCall = transformedExp === null;
+        const isStandaloneCall =
+          propertyName === 'partial' &&
+          transformedExp === null &&
+          curSchemaType === null;
📝 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
const isStandaloneCall = transformedExp === null;
transformedExp = toValibotMethodExp(
valibotIdentifier,
propertyName,
transformedExp ?? j.identifier(identifier),
cur.value.arguments
cur.value.arguments,
isStandaloneCall
const isStandaloneCall =
propertyName === 'partial' &&
transformedExp === null &&
curSchemaType === null;
transformedExp = toValibotMethodExp(
valibotIdentifier,
propertyName,
transformedExp ?? j.identifier(identifier),
cur.value.arguments,
isStandaloneCall
🤖 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 `@codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts`
around lines 589 - 595, The standalone-call check in schemas-and-links.ts is too
broad because `transformedExp === null` also matches linked alias chains like
`Foo.partial(mask)`, causing `toValibotMethodExp` and `transformPartial` to
treat `mask` as the schema argument. Update the `isStandaloneCall` logic in this
recursive link path to detect only the واقعی standalone API shape for `partial`
rather than relying on `transformedExp` alone, and keep the linked call flowing
through the normal method-call branch with the correct schema/mask argument
order.

@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 7 files (changes from recent commits).

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="codemod/zod-to-valibot/__testfixtures__/brand-schema/input.ts">

<violation number="1" location="codemod/zod-to-valibot/__testfixtures__/brand-schema/input.ts:3">
P2: Brand test fixture only covers runtime-string-argument brands, missing the common `.brand<"Name">()` and `.brand()` (no-argument) forms. Since `transformBrand` only copies runtime `args` and drops TypeScript type parameters, the dominant Zod brand pattern is untested and its type information would be lost in transformation.</violation>
</file>

<file name="codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts">

<violation number="1" location="codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts:7">
P1: `transformBrand` drops TypeScript generic brand names by only forwarding runtime `args`, producing invalid `v.brand()` for the canonical `z.string().brand<'Name'>()` pattern.</violation>
</file>

<file name="codemod/zod-to-valibot/__testfixtures__/object-partial-variable-mask/input.ts">

<violation number="1" location="codemod/zod-to-valibot/__testfixtures__/object-partial-variable-mask/input.ts:1">
P2: The `object-partial-variable-mask` test fixture is present with input and output files, but it is not registered in `test-setup.test.ts`. As a result, this edge case for `.partial(mask)` with a variable mask is never executed. Add `'object-partial-variable-mask'` to the `defineTests` array in `codemod/zod-to-valibot/src/test-setup.test.ts`.</violation>
</file>

<file name="codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts">

<violation number="1" location="codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts:589">
P1: Standalone-call detection misclassifies linked-schema method chains as function-form calls, causing incorrect argument semantics in transformPartial.</violation>
</file>

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

Re-trigger cubic

Comment thread codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts Outdated
@@ -0,0 +1,8 @@
import { z } from "zod";

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: The object-partial-variable-mask test fixture is present with input and output files, but it is not registered in test-setup.test.ts. As a result, this edge case for .partial(mask) with a variable mask is never executed. Add 'object-partial-variable-mask' to the defineTests array in codemod/zod-to-valibot/src/test-setup.test.ts.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At codemod/zod-to-valibot/__testfixtures__/object-partial-variable-mask/input.ts, line 1:

<comment>The `object-partial-variable-mask` test fixture is present with input and output files, but it is not registered in `test-setup.test.ts`. As a result, this edge case for `.partial(mask)` with a variable mask is never executed. Add `'object-partial-variable-mask'` to the `defineTests` array in `codemod/zod-to-valibot/src/test-setup.test.ts`.</comment>

<file context>
@@ -0,0 +1,8 @@
+import { z } from "zod";
+
+const Foo = z.object({ foo: z.string(), bar: z.number() });
</file context>

…-and-links.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix A smaller enhancement or bug fix size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codemod mangles z.partial Codemod breaks z.brand

1 participant