fix(zod-to-valibot): handle z.partial() function form and add z.brand() support#1524
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThe PR adds a new 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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.tsFile 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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
codemod/zod-to-valibot/__testfixtures__/brand-schema/input.tscodemod/zod-to-valibot/__testfixtures__/brand-schema/output.tscodemod/zod-to-valibot/__testfixtures__/object-partial-function-form/input.tscodemod/zod-to-valibot/__testfixtures__/object-partial-function-form/output.tscodemod/zod-to-valibot/src/test-setup.test.tscodemod/zod-to-valibot/src/transform/schemas-and-links/constants.tscodemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.tscodemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.tscodemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.tscodemod/zod-to-valibot/src/transform/schemas-and-links/methods/partial/partial.tscodemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts
There was a problem hiding this comment.
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'] |
There was a problem hiding this comment.
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>
| @@ -0,0 +1,5 @@ | |||
| import { z } from "zod"; | |||
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 winUse a
.tsspecifier for the new methods import.The added
./methodsimport 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.tsextensions.♻️ 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
📒 Files selected for processing (7)
codemod/zod-to-valibot/__testfixtures__/object-partial-variable-mask/input.tscodemod/zod-to-valibot/__testfixtures__/object-partial-variable-mask/output.tscodemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.tscodemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.tscodemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.tscodemod/zod-to-valibot/src/transform/schemas-and-links/methods/partial/partial.tscodemod/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
| const isStandaloneCall = transformedExp === null; | ||
| transformedExp = toValibotMethodExp( | ||
| valibotIdentifier, | ||
| propertyName, | ||
| transformedExp ?? j.identifier(identifier), | ||
| cur.value.arguments | ||
| cur.value.arguments, | ||
| isStandaloneCall |
There was a problem hiding this comment.
🎯 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.
| 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.
There was a problem hiding this comment.
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
| @@ -0,0 +1,8 @@ | |||
| import { z } from "zod"; | |||
There was a problem hiding this comment.
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>
Problem
Two bugs in
@valibot/zod-to-valibotwhen migrating Zod v4 code:1.
z.partial(Schema)mangled tov.partial(v)(fixes #1503)Zod v4 exposes
partialas 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 theznamespace identifier as the schema argument:Fix: In
transformPartial, detect the function form by checking ifinputArgs[0]is not anObjectExpression(key-selector pattern). If so, treatinputArgs[0]as the schema andinputArgs[1]as the optional key selector.2.
z.string().brand("name")crashes the codemod (fixes #1501)brandwas not listed inZOD_METHODS, so the codemod treated it as an unknown method and failed to transform the chain:Fix: Add
brandtoZOD_METHODSand implementtransformBrandusingaddToPipe, matching how Valibot'sv.brand()action works.Changes
constants.ts— add'brand'toZOD_METHODSmethods/brand/brand.ts— newtransformBrandusingaddToPipemethods/index.ts— export brand transformmethods/partial/partial.ts— detect and handle function-call formschemas-and-links.ts— import and wiretransformBrandinto the switch__testfixtures__/brand-schema/— new test fixture__testfixtures__/object-partial-function-form/— new test fixturetest-setup.test.ts— register both new fixturesAll 73 tests pass.
Summary by CodeRabbit
New Features
.brand(...)chains to Valibot branding.z.partial(schema, options?)function-call form, including an optional key selector.Bug Fixes
z.partialconversions across chained and standalone usage patterns, including cases where the mask is provided via a variable.Tests