Skip to content

Commit ca72ba9

Browse files
Merge pull request #1273 from paoloricciuti/other-codemod-fixes
feat: graceful multiple imports exit + `transform` and `refine`
2 parents c3860df + a2e98bc commit ca72ba9

File tree

16 files changed

+196
-3
lines changed

16 files changed

+196
-3
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { z, ZodAnyType } from "zod";
2+
3+
const Schema1 = z.string();
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* @valibot-migrate: unable to transform imports from Zod to Valibot: Expected exactly one import specifier from "zod" or "zod/v4". */
2+
import { z, ZodAnyType } from "zod";
3+
4+
const Schema1 = z.string();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { z } from "zod";
2+
3+
// Basic refine
4+
const Schema1 = z.number().refine((val) => val < 100, "Must be less then 100");
5+
6+
// Refine with string schema
7+
const Schema2 = z.string().refine((val) => val.length > 0, "Required");
8+
9+
// Refine after validator
10+
const Schema3 = z.number().min(0).refine((val) => val % 2 === 0, "Must be even");
11+
12+
// Multiple refines
13+
const Schema4 = z.string().refine((val) => val.length > 3).refine((val) => val.includes("@"));
14+
15+
// Refine with complex condition
16+
const Schema5 = z.object({ name: z.string() }).refine((data) => data.name !== "admin", {
17+
message: "Cannot use admin"
18+
});
19+
20+
// Refine on linked schema
21+
const BaseSchema = z.string();
22+
const RefinedSchema = BaseSchema.refine((val) => val.trim().length > 0);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as v from "valibot";
2+
3+
// Basic refine
4+
const Schema1 = v.pipe(v.number(), v.check((val) => val < 100, "Must be less then 100"));
5+
6+
// Refine with string schema
7+
const Schema2 = v.pipe(v.string(), v.check((val) => val.length > 0, "Required"));
8+
9+
// Refine after validator
10+
const Schema3 = v.pipe(v.number(), v.minValue(0), v.check((val) => val % 2 === 0, "Must be even"));
11+
12+
// Multiple refines
13+
const Schema4 = v.pipe(
14+
v.string(),
15+
v.check((val) => val.length > 3),
16+
v.check((val) => val.includes("@"))
17+
);
18+
19+
// Refine with complex condition
20+
const Schema5 = v.pipe(
21+
v.object({ name: v.string() }),
22+
v.check((data) => data.name !== "admin", "Cannot use admin")
23+
);
24+
25+
// Refine on linked schema
26+
const BaseSchema = v.string();
27+
const RefinedSchema = v.pipe(BaseSchema, v.check((val) => val.trim().length > 0));
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { z } from "zod";
2+
3+
// Basic transform
4+
const Schema1 = z.number().transform((val) => val * 2);
5+
6+
// Transform with string schema
7+
const Schema2 = z.string().transform((val) => val.toUpperCase());
8+
9+
// Chained transform with validator
10+
const Schema3 = z.number().min(0).transform((val) => val + 1);
11+
12+
// Transform after validator chain
13+
const Schema4 = z.string().email().transform((val) => `Email: ${val}`);
14+
15+
// Multiple transforms
16+
const Schema5 = z.number().transform((val) => val * 2).transform((val) => val + 10);
17+
18+
// Transform with complex function
19+
const Schema6 = z.object({ name: z.string() }).transform((data) => ({
20+
...data,
21+
displayName: data.name.toUpperCase()
22+
}));
23+
24+
// Transform on linked schema
25+
const BaseSchema = z.string();
26+
const TransformedSchema = BaseSchema.transform((val) => val.trim());
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as v from "valibot";
2+
3+
// Basic transform
4+
const Schema1 = v.pipe(v.number(), v.transform((val) => val * 2));
5+
6+
// Transform with string schema
7+
const Schema2 = v.pipe(v.string(), v.transform((val) => val.toUpperCase()));
8+
9+
// Chained transform with validator
10+
const Schema3 = v.pipe(v.number(), v.minValue(0), v.transform((val) => val + 1));
11+
12+
// Transform after validator chain
13+
const Schema4 = v.pipe(v.string(), v.email(), v.transform((val) => `Email: ${val}`));
14+
15+
// Multiple transforms
16+
const Schema5 = v.pipe(v.number(), v.transform((val) => val * 2), v.transform((val) => val + 10));
17+
18+
// Transform with complex function
19+
const Schema6 = v.pipe(v.object({ name: v.string() }), v.transform((data) => ({
20+
...data,
21+
displayName: data.name.toUpperCase()
22+
})));
23+
24+
// Transform on linked schema
25+
const BaseSchema = v.string();
26+
const TransformedSchema = v.pipe(BaseSchema, v.transform((val) => val.trim()));

codemod/zod-to-valibot/src/test-setup.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defineTests(transform, [
2626
'intersection-schema',
2727
'literal-schema',
2828
'map-schema',
29+
'multiple-imports-from-zod',
2930
'named-import',
3031
'named-import-with-alias',
3132
'named-import-with-specific-alias',
@@ -55,6 +56,7 @@ defineTests(transform, [
5556
'parsing',
5657
'readonly',
5758
'record-schema',
59+
'refine',
5860
'schema-chain',
5961
'schema-options',
6062
'set-schema',
@@ -63,6 +65,7 @@ defineTests(transform, [
6365
'specific-namespace-import',
6466
'string-validation-methods',
6567
'symbol-schema',
68+
'transform',
6669
'tuple-schema',
6770
'type-inference',
6871
'undefined-schema',

codemod/zod-to-valibot/src/transform/imports/imports.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import j, { type Collection } from 'jscodeshift';
33
type TransformImportsReturn =
44
| {
55
conclusion: 'skip' | 'unsuccessful';
6+
cause: string;
67
valibotIdentifier?: undefined;
78
}
89
| { conclusion: 'successful'; valibotIdentifier: string };
@@ -20,13 +21,15 @@ export function transformImports(
2021
if (importNodes.length !== 1) {
2122
return {
2223
conclusion: importNodes.length === 0 ? 'skip' : 'unsuccessful',
24+
cause: 'Expected exactly one import statement from "zod" or "zod/v4".',
2325
};
2426
}
2527
// Check the number of specifiers is exactly one
2628
const importSpecifiers = importNodes[0].specifiers;
2729
if (importSpecifiers?.length !== 1) {
2830
return {
2931
conclusion: 'unsuccessful',
32+
cause: 'Expected exactly one import specifier from "zod" or "zod/v4".',
3033
};
3134
}
3235
// Obtain the identifier
@@ -35,6 +38,7 @@ export function transformImports(
3538
if (typeof zodIdentifier !== 'string') {
3639
return {
3740
conclusion: 'unsuccessful',
41+
cause: 'Expected the import specifier to have a local name.',
3842
};
3943
}
4044
const isZodIdentifierZ = zodIdentifier === 'z';

codemod/zod-to-valibot/src/transform/index.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,20 @@ const transform: Transform = (fileInfo, api) => {
99
// ------------ Imports ------------
1010
const transformImportsResult = transformImports(root);
1111
if (transformImportsResult.conclusion !== 'successful') {
12-
return transformImportsResult.conclusion === 'skip'
13-
? undefined
14-
: root.toSource();
12+
if (transformImportsResult.conclusion === 'unsuccessful') {
13+
const node = root.get().node;
14+
// Add comment indicating unsuccessful transformation
15+
node.comments ??= [];
16+
node.comments.unshift(
17+
j.commentBlock(
18+
` @valibot-migrate: unable to transform imports from Zod to Valibot: ${transformImportsResult.cause} `,
19+
true,
20+
false
21+
)
22+
);
23+
return root.toSource();
24+
}
25+
return undefined;
1526
}
1627
const valibotIdentifier = transformImportsResult.valibotIdentifier;
1728

codemod/zod-to-valibot/src/transform/schemas-and-links/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,15 @@ export const ZOD_METHODS = [
153153
'partial',
154154
'passthrough',
155155
'pick',
156+
'refine',
156157
'required',
157158
'rest',
158159
'safeParse',
159160
'safeParseAsync',
160161
'strict',
161162
'strip',
162163
'spa',
164+
'transform',
163165
'unwrap',
164166
] as const;
165167

0 commit comments

Comments
 (0)