Skip to content

fix(to-valibot): support z.brand() in codemod (#1501)#1542

Open
xianjianlf2 wants to merge 1 commit into
open-circle:mainfrom
xianjianlf2:fix/codemod-brand-1501
Open

fix(to-valibot): support z.brand() in codemod (#1501)#1542
xianjianlf2 wants to merge 1 commit into
open-circle:mainfrom
xianjianlf2:fix/codemod-brand-1501

Conversation

@xianjianlf2

@xianjianlf2 xianjianlf2 commented Jul 16, 2026

Copy link
Copy Markdown

Overview

The @valibot/zod-to-valibot codemod had no handler for Zod's .brand() method. Because the method went unrecognized, it was left in place as a call on the transformed schema, producing invalid output like v.string()("Name") (a schema object called as a function).

Fixes #1501

Fix

Added a brand method handler that maps schema.brand(name) into a pipe action, mirroring how the existing transform method is handled. It uses the shared addToPipe helper so the brand is appended to any existing pipe:

z.string().brand("StationNumber")   ->   v.pipe(v.string(), v.brand("StationNumber"))

The handler is wired into ZOD_METHODS (constants), methods/index.ts, and the schemas-and-links dispatcher.

Tests

Added a brand test fixture (__testfixtures__/brand/) covering a basic brand, a brand after a validator chain, a brand on a number schema, and a brand on a linked schema, and registered it in test-setup.test.ts. The full codemod suite passes (72/72).

Summary by CodeRabbit

  • New Features
    • Added support for converting Zod branded schemas to equivalent Valibot branded schemas.
    • Branding is preserved across strings, UUID validators, numeric minimum constraints, and existing schema pipelines.
  • Tests
    • Added coverage for multiple branded schema patterns to verify accurate conversions.

The codemod did not recognize Zod's .brand() method, so
z.string().brand("X") was emitted as the broken v.string()("X").
Add a brand method handler that maps it into a pipe action,
producing v.pipe(v.string(), v.brand("X")), matching how the
existing transform method is handled.

Fixes open-circle#1501
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. fix A smaller enhancement or bug fix labels Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The Zod-to-Valibot codemod now recognizes the brand method and transforms it into a Valibot brand pipeline step. The implementation is registered through the method exports and dispatch logic. New fixtures cover branding plain strings, UUID chains, constrained numbers, and derived schemas, and the test setup includes the new brand scenario.

🚥 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 support for z.brand() in the zod-to-valibot codemod.
Linked Issues check ✅ Passed The changes implement the requested .brand() codemod fix and produce the expected Valibot pipe output for branded schemas.
Out of Scope Changes check ✅ Passed The added transformer, wiring, and fixtures are all directly related to supporting .brand() in the codemod.
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
`@codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts`:
- Around line 4-8: Add JSDoc immediately above the exported transformBrand
function describing its purpose, and add the // `@__NO_SIDE_EFFECTS__` annotation
required for this pure factory transformation. Keep the function signature and
implementation unchanged.
- Around line 1-2: Update the relative ESM specifiers in brand.ts,
brand/index.ts, and methods/index.ts to include the .ts extension: use
../../helpers.ts, ./brand.ts, and ./brand.ts respectively. Leave the jscodeshift
package import unchanged.
- Around line 4-20: Update transformBrand to handle Zod’s type-only brand<...>()
form instead of forwarding only runtime args: preserve the brand name by
converting the type argument to the required Valibot name, or explicitly reject
unsupported type-only branding. Add a fixture covering
z.string().brand<"UserId">() and verify the resulting transform behavior.
🪄 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: 2ecf73f0-45e6-4781-ac57-4211df4fe6eb

📥 Commits

Reviewing files that changed from the base of the PR and between 32247b3 and de44991.

📒 Files selected for processing (8)
  • codemod/zod-to-valibot/__testfixtures__/brand/input.ts
  • codemod/zod-to-valibot/__testfixtures__/brand/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/schemas-and-links.ts

Comment on lines +1 to +2
import j from 'jscodeshift';
import { addToPipe } from '../../helpers';

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 | 🟠 Major | ⚡ Quick win

Use .ts extensions on the new relative imports and exports.

As per coding guidelines, ESM relative specifiers in these TypeScript files must include .ts; keep the package import unchanged.

  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts#L1-L2: change ../../helpers to ../../helpers.ts.
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.ts#L1: change ./brand to ./brand.ts.
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.ts#L2: change ./brand to ./brand.ts.
📍 Affects 3 files
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts#L1-L2 (this comment)
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.ts#L1-L1
  • codemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.ts#L2-L2
🤖 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/methods/brand/brand.ts`
around lines 1 - 2, Update the relative ESM specifiers in brand.ts,
brand/index.ts, and methods/index.ts to include the .ts extension: use
../../helpers.ts, ./brand.ts, and ./brand.ts respectively. Leave the jscodeshift
package import unchanged.

Source: Coding guidelines

Comment on lines +4 to +8
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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add the required JSDoc and purity annotation.

This exported function is under codemod/** but has neither JSDoc nor // @NO_SIDE_EFFECTS``. As per coding guidelines, exported functions require JSDoc and pure factory functions require the annotation.

🤖 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/methods/brand/brand.ts`
around lines 4 - 8, Add JSDoc immediately above the exported transformBrand
function describing its purpose, and add the // `@__NO_SIDE_EFFECTS__` annotation
required for this pure factory transformation. Keep the function signature and
implementation unchanged.

Source: Coding guidelines

Comment on lines +4 to +20
export function transformBrand(
valibotIdentifier: string,
schemaExp: j.CallExpression | j.MemberExpression | j.Identifier,
args: j.CallExpression['arguments']
) {
return addToPipe(
valibotIdentifier,
schemaExp,
j.callExpression(
j.memberExpression(
j.identifier(valibotIdentifier),
j.identifier('brand')
),
args
)
);
}

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- brand transformer ---\n'
sed -n '1,220p' codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts

printf '\n--- nearby tests/fixtures ---\n'
fd -a 'brand' codemod/zod-to-valibot -t f
fd -a 'brand' codemod/zod-to-valibot -t f | while read -r f; do
  echo "\n### $f"
  sed -n '1,220p' "$f"
done

printf '\n--- search for brand() usage and docs ---\n'
rg -n "brand<|brand\(" codemod/zod-to-valibot packages library -g '!**/dist/**' -g '!**/build/**'

Repository: open-circle/valibot

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- brand transformer ---'
sed -n '1,220p' codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/brand.ts

echo
echo '--- nearby tests/fixtures ---'
fd -a 'brand' codemod/zod-to-valibot -t f
fd -a 'brand' codemod/zod-to-valibot -t f | while read -r f; do
  echo
  echo "### $f"
  sed -n '1,240p' "$f"
done

echo
echo '--- search for brand usage and valibot brand API ---'
rg -n "brand<|brand\(" codemod/zod-to-valibot packages library -g '!**/dist/**' -g '!**/build/**'

Repository: open-circle/valibot

Length of output: 2735


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- search for brand type-parameter handling ---'
rg -n "typeParameters|typeArguments|TSLiteralType|brand" codemod/zod-to-valibot/src codemod/zod-to-valibot/__testfixtures__ -g '!**/dist/**' -g '!**/build/**'

echo
echo '--- parser/transform entrypoints around brand ---'
fd -a 'brand' codemod/zod-to-valibot/src -t f
fd -a 'schema' codemod/zod-to-valibot/src -t f

Repository: open-circle/valibot

Length of output: 3646


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- schemas-and-links brand dispatch ---'
sed -n '350,430p' codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts

echo
echo '--- surrounding helper signatures ---'
sed -n '1,120p' codemod/zod-to-valibot/src/transform/schemas-and-links/methods/index.ts
sed -n '1,120p' codemod/zod-to-valibot/src/transform/schemas-and-links/methods/brand/index.ts

echo
echo '--- pure factory / JSDoc examples in codemod ---'
rg -n "// `@__NO_SIDE_EFFECTS__`|export function" codemod/zod-to-valibot/src -g '!**/dist/**' -g '!**/build/**'

Repository: open-circle/valibot

Length of output: 16913


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Valibot brand signature ---'
sed -n '1,120p' library/src/actions/brand/brand.ts

echo
echo '--- brand test coverage ---'
sed -n '1,120p' library/src/actions/brand/brand.test.ts
sed -n '1,120p' library/src/actions/brand/brand.test-d.ts

Repository: open-circle/valibot

Length of output: 3363


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- schema-and-links method parsing ---'
sed -n '120,240p' codemod/zod-to-valibot/src/transform/schemas-and-links/schemas-and-links.ts

echo
echo '--- brand test fixtures, if any type-only case exists elsewhere ---'
rg -n "brand<|\.brand\(" codemod/zod-to-valibot/__testfixtures__ -g '!**/dist/**' -g '!**/build/**'

Repository: open-circle/valibot

Length of output: 5188


🌐 Web query:

Zod brand type parameter brand<T>() Valibot brand requires name argument API docs

💡 Result:

In Valibot, the brand function is a transformation action used to brand the output type of a schema with a specific name [1][2]. Regarding your query about the API requirements: yes, the brand function requires a name argument [2][3]. Its signature is defined as: brand<TInput, TName extends BrandName>(name: TName): BrandAction<TInput, TName> The name argument (of type TName) is mandatory and is used to uniquely identify the brand at the type level [2][3]. This branding ensures that data can only be considered valid if it has been validated by a schema containing that specific brand [1]. Unlike some other libraries where branding might be purely a type-level construct, Valibot's brand acts as a runtime transformation action [2][3].

Citations:


🌐 Web query:

Zod docs brand type-only brand<"UserId">() syntax

💡 Result:

In Zod, the.brand<"Name"> syntax is a static-only construct used to create nominal types by "stamping" a Zod schema [1][2][3]. It does not affect the runtime behavior of.parse or.safeParse; its primary purpose is to distinguish between types that share the same underlying structure (e.g., distinguishing a UserId from a regular string) at the TypeScript type level [1][4][3]. Usage Syntax: You apply the brand to a Zod schema using the.brand method, passing the brand name as a string literal generic [3][5]. const UserIdSchema = z.string.brand<"UserId">; type UserId = z.infer; // string & z.$brand<"UserId"> In newer versions (Zod 4.x), you can optionally specify the direction of the brand to control whether it applies to the input, output, or both [3][5]: z.string.brand<"UserId", "in">; // Brand applies to input z.string.brand<"UserId", "out">; // Brand applies to output z.string.brand<"UserId", "inout">; // Brand applies to both (default) Key Characteristics: - Type-Only: Branding exists purely at the TypeScript type level [1][6]. Zod does not use these brands during runtime validation [4][7]. - Persistence: Brands are generally preserved through standard Zod operations, but they can be lost when using.transform or.pipe because these operations create new types that TypeScript may not automatically associate with the original brand [7][6]. If a brand is lost through these operations, you must manually re-apply it [7][6]. - Nominal Typing: By using branded types, you prevent accidental interchangeability of types. For example, a function expecting a UserId will reject a plain string or a string branded as a different type, even if both are structurally strings [3][5].

Citations:


Handle Zod's type-only brand<...>() form here.
transformBrand only forwards runtime args, so z.string().brand<"UserId">() drops the brand name and becomes v.brand(), but Valibot requires a name. Carry the type argument through or reject this form explicitly, and add a fixture for it.

🤖 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/methods/brand/brand.ts`
around lines 4 - 20, Update transformBrand to handle Zod’s type-only
brand<...>() form instead of forwarding only runtime args: preserve the brand
name by converting the type argument to the required Valibot name, or explicitly
reject unsupported type-only branding. Add a fixture covering
z.string().brand<"UserId">() and verify the resulting transform behavior.

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

1 issue found across 8 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/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:17">
P1: When Zod's `.brand()` is called with only a type argument (e.g., `z.string().brand<"UserId">()`), the `args` array will be empty. This produces `v.brand()` in the output, but Valibot's `brand` action requires a `name` parameter. The type-argument-only form is common in Zod codebases. Consider extracting the type argument from the AST node and using it as the brand name, or emitting a warning/comment when no runtime argument is provided.</violation>
</file>

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

Re-trigger cubic

j.identifier(valibotIdentifier),
j.identifier('brand')
),
args

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: When Zod's .brand() is called with only a type argument (e.g., z.string().brand<"UserId">()), the args array will be empty. This produces v.brand() in the output, but Valibot's brand action requires a name parameter. The type-argument-only form is common in Zod codebases. Consider extracting the type argument from the AST node and using it as the brand name, or emitting a warning/comment when no runtime argument is provided.

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

<comment>When Zod's `.brand()` is called with only a type argument (e.g., `z.string().brand<"UserId">()`), the `args` array will be empty. This produces `v.brand()` in the output, but Valibot's `brand` action requires a `name` parameter. The type-argument-only form is common in Zod codebases. Consider extracting the type argument from the AST node and using it as the brand name, or emitting a warning/comment when no runtime argument is provided.</comment>

<file context>
@@ -0,0 +1,20 @@
+        j.identifier(valibotIdentifier),
+        j.identifier('brand')
+      ),
+      args
+    )
+  );
</file context>

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:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codemod breaks z.brand

1 participant