eslint-plugin-zod 3.8.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,17 +56,22 @@ This plugin is primarily built for `zod`, so some rules are exclusive to `zod` a
56
56
 
57
57
  ### `zod` exclusive rules
58
58
 
59
- | Name                             | Description | 💼 | 🔧 | 💡 | ❌ |
60
- | :--------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :-- | :-- | :-- | :-- |
61
- | [array-style](docs/rules/array-style.md) | Enforce consistent Zod array style | ✅ | 🔧 | | |
62
- | [no-number-schema-with-int](docs/rules/no-number-schema-with-int.md) | Disallow usage of `z.number().int()` as it is considered legacy | ✅ | 🔧 | | |
63
- | [no-optional-and-default-together](docs/rules/no-optional-and-default-together.md) | Disallow using both `.optional()` and `.default()` on the same Zod schema | ✅ | 🔧 | | |
64
- | [no-string-schema-with-uuid](docs/rules/no-string-schema-with-uuid.md) | Disallow usage of `z.string().uuid()` in favor of the dedicated `z.uuid()` schema | ✅ | 🔧 | | |
65
- | [no-throw-in-refine](docs/rules/no-throw-in-refine.md) | Disallow throwing errors directly inside Zod refine callbacks | ✅ | | | |
66
- | [no-transform-in-record-key](docs/rules/no-transform-in-record-key.md) | Disallow transforms in z.record() key schemas, which can cause silent key mutations and data loss through key collisions | | | | |
67
- | [prefer-enum-over-literal-union](docs/rules/prefer-enum-over-literal-union.md) | Prefer `z.enum()` over `z.union()` when all members are string literals. | ✅ | 🔧 | | |
68
- | [prefer-meta-last](docs/rules/prefer-meta-last.md) | Enforce `.meta()` as last method | ✅ | 🔧 | | |
69
- | [prefer-string-schema-with-trim](docs/rules/prefer-string-schema-with-trim.md) | Enforce `z.string().trim()` to prevent accidental leading/trailing whitespace | ✅ | 🔧 | | |
59
+ | Name                             | Description | 💼 | 🔧 | 💡 | ❌ |
60
+ | :--------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- | :-- | :-- | :-- | :-- |
61
+ | [array-style](docs/rules/array-style.md) | Enforce consistent Zod array style | ✅ | 🔧 | | |
62
+ | [no-number-schema-with-finite](docs/rules/no-number-schema-with-finite.md) | Disallow deprecated `z.number().finite()`. In Zod 4+ number schemas do not allow infinite values by default, so it is a no-op. | ✅ | 🔧 | | |
63
+ | [no-number-schema-with-int](docs/rules/no-number-schema-with-int.md) | Disallow usage of `z.number().int()` as it is considered legacy | ✅ | 🔧 | | |
64
+ | [no-number-schema-with-is-finite](docs/rules/no-number-schema-with-is-finite.md) | Disallow using deprecated `isFinite` on a Zod number schema; in v4+ it is always `true`. | ✅ | | | |
65
+ | [no-number-schema-with-is-int](docs/rules/no-number-schema-with-is-int.md) | Disallow using deprecated `isInt` on a Zod number schema; check the `format` property instead. | ✅ | | | |
66
+ | [no-number-schema-with-safe](docs/rules/no-number-schema-with-safe.md) | Disallow deprecated `z.number().safe()`. Use `z.int()`; `.safe()` is now identical to `.int()`. | ✅ | 🔧 | | |
67
+ | [no-number-schema-with-step](docs/rules/no-number-schema-with-step.md) | Disallow deprecated `z.number().step()`. Use `.multipleOf()` instead. | ✅ | 🔧 | | |
68
+ | [no-optional-and-default-together](docs/rules/no-optional-and-default-together.md) | Disallow using both `.optional()` and `.default()` on the same Zod schema | ✅ | 🔧 | | |
69
+ | [no-string-schema-with-uuid](docs/rules/no-string-schema-with-uuid.md) | Disallow usage of `z.string().uuid()` in favor of the dedicated `z.uuid()` schema | ✅ | 🔧 | | |
70
+ | [no-throw-in-refine](docs/rules/no-throw-in-refine.md) | Disallow throwing errors directly inside Zod refine callbacks | ✅ | | | |
71
+ | [no-transform-in-record-key](docs/rules/no-transform-in-record-key.md) | Disallow transforms in z.record() key schemas, which can cause silent key mutations and data loss through key collisions | | | | |
72
+ | [prefer-enum-over-literal-union](docs/rules/prefer-enum-over-literal-union.md) | Prefer `z.enum()` over `z.union()` when all members are string literals. | ✅ | 🔧 | | |
73
+ | [prefer-meta-last](docs/rules/prefer-meta-last.md) | Enforce `.meta()` as last method | ✅ | 🔧 | | |
74
+ | [prefer-string-schema-with-trim](docs/rules/prefer-string-schema-with-trim.md) | Enforce `z.string().trim()` to prevent accidental leading/trailing whitespace | ✅ | 🔧 | | |
70
75
 
71
76
  <!-- end auto-generated rules list -->
72
77
 
package/dist/index.cjs CHANGED
@@ -6,7 +6,12 @@ const require_consistent_object_schema_type = require("./rules/consistent-object
6
6
  const require_consistent_schema_output_type_style = require("./rules/consistent-schema-output-type-style.cjs");
7
7
  const require_no_any_schema = require("./rules/no-any-schema.cjs");
8
8
  const require_no_empty_custom_schema = require("./rules/no-empty-custom-schema.cjs");
9
+ const require_no_number_schema_with_finite = require("./rules/no-number-schema-with-finite.cjs");
9
10
  const require_no_number_schema_with_int = require("./rules/no-number-schema-with-int.cjs");
11
+ const require_no_number_schema_with_is_finite = require("./rules/no-number-schema-with-is-finite.cjs");
12
+ const require_no_number_schema_with_is_int = require("./rules/no-number-schema-with-is-int.cjs");
13
+ const require_no_number_schema_with_safe = require("./rules/no-number-schema-with-safe.cjs");
14
+ const require_no_number_schema_with_step = require("./rules/no-number-schema-with-step.cjs");
10
15
  const require_no_optional_and_default_together = require("./rules/no-optional-and-default-together.cjs");
11
16
  const require_no_string_schema_with_uuid = require("./rules/no-string-schema-with-uuid.cjs");
12
17
  const require_no_throw_in_refine = require("./rules/no-throw-in-refine.cjs");
@@ -35,7 +40,12 @@ const eslintPluginZod = {
35
40
  "consistent-schema-output-type-style": require_consistent_schema_output_type_style.consistentSchemaOutputTypeStyle,
36
41
  "no-any-schema": require_no_any_schema.noAnySchema,
37
42
  "no-empty-custom-schema": require_no_empty_custom_schema.noEmptyCustomSchema,
43
+ "no-number-schema-with-finite": require_no_number_schema_with_finite.noNumberSchemaWithFinite,
38
44
  "no-number-schema-with-int": require_no_number_schema_with_int.noNumberSchemaWithInt,
45
+ "no-number-schema-with-is-finite": require_no_number_schema_with_is_finite.noNumberSchemaWithIsFinite,
46
+ "no-number-schema-with-is-int": require_no_number_schema_with_is_int.noNumberSchemaWithIsInt,
47
+ "no-number-schema-with-safe": require_no_number_schema_with_safe.noNumberSchemaWithSafe,
48
+ "no-number-schema-with-step": require_no_number_schema_with_step.noNumberSchemaWithStep,
39
49
  "no-string-schema-with-uuid": require_no_string_schema_with_uuid.noStringSchemaWithUuid,
40
50
  "no-optional-and-default-together": require_no_optional_and_default_together.noOptionalAndDefaultTogether,
41
51
  "no-throw-in-refine": require_no_throw_in_refine.noThrowInRefine,
@@ -64,7 +74,12 @@ const recommendedConfig = {
64
74
  "zod/consistent-import": "error",
65
75
  "zod/no-any-schema": "error",
66
76
  "zod/no-empty-custom-schema": "error",
77
+ "zod/no-number-schema-with-finite": "error",
67
78
  "zod/no-number-schema-with-int": "error",
79
+ "zod/no-number-schema-with-is-finite": "error",
80
+ "zod/no-number-schema-with-is-int": "error",
81
+ "zod/no-number-schema-with-safe": "error",
82
+ "zod/no-number-schema-with-step": "error",
68
83
  "zod/no-string-schema-with-uuid": "error",
69
84
  "zod/no-optional-and-default-together": "error",
70
85
  "zod/no-throw-in-refine": "error",
package/dist/index.mjs CHANGED
@@ -6,7 +6,12 @@ import { consistentObjectSchemaType } from "./rules/consistent-object-schema-typ
6
6
  import { consistentSchemaOutputTypeStyle } from "./rules/consistent-schema-output-type-style.mjs";
7
7
  import { noAnySchema } from "./rules/no-any-schema.mjs";
8
8
  import { noEmptyCustomSchema } from "./rules/no-empty-custom-schema.mjs";
9
+ import { noNumberSchemaWithFinite } from "./rules/no-number-schema-with-finite.mjs";
9
10
  import { noNumberSchemaWithInt } from "./rules/no-number-schema-with-int.mjs";
11
+ import { noNumberSchemaWithIsFinite } from "./rules/no-number-schema-with-is-finite.mjs";
12
+ import { noNumberSchemaWithIsInt } from "./rules/no-number-schema-with-is-int.mjs";
13
+ import { noNumberSchemaWithSafe } from "./rules/no-number-schema-with-safe.mjs";
14
+ import { noNumberSchemaWithStep } from "./rules/no-number-schema-with-step.mjs";
10
15
  import { noOptionalAndDefaultTogether } from "./rules/no-optional-and-default-together.mjs";
11
16
  import { noStringSchemaWithUuid } from "./rules/no-string-schema-with-uuid.mjs";
12
17
  import { noThrowInRefine } from "./rules/no-throw-in-refine.mjs";
@@ -35,7 +40,12 @@ const eslintPluginZod = {
35
40
  "consistent-schema-output-type-style": consistentSchemaOutputTypeStyle,
36
41
  "no-any-schema": noAnySchema,
37
42
  "no-empty-custom-schema": noEmptyCustomSchema,
43
+ "no-number-schema-with-finite": noNumberSchemaWithFinite,
38
44
  "no-number-schema-with-int": noNumberSchemaWithInt,
45
+ "no-number-schema-with-is-finite": noNumberSchemaWithIsFinite,
46
+ "no-number-schema-with-is-int": noNumberSchemaWithIsInt,
47
+ "no-number-schema-with-safe": noNumberSchemaWithSafe,
48
+ "no-number-schema-with-step": noNumberSchemaWithStep,
39
49
  "no-string-schema-with-uuid": noStringSchemaWithUuid,
40
50
  "no-optional-and-default-together": noOptionalAndDefaultTogether,
41
51
  "no-throw-in-refine": noThrowInRefine,
@@ -64,7 +74,12 @@ const recommendedConfig = {
64
74
  "zod/consistent-import": "error",
65
75
  "zod/no-any-schema": "error",
66
76
  "zod/no-empty-custom-schema": "error",
77
+ "zod/no-number-schema-with-finite": "error",
67
78
  "zod/no-number-schema-with-int": "error",
79
+ "zod/no-number-schema-with-is-finite": "error",
80
+ "zod/no-number-schema-with-is-int": "error",
81
+ "zod/no-number-schema-with-safe": "error",
82
+ "zod/no-number-schema-with-step": "error",
68
83
  "zod/no-string-schema-with-uuid": "error",
69
84
  "zod/no-optional-and-default-together": "error",
70
85
  "zod/no-throw-in-refine": "error",
@@ -0,0 +1,44 @@
1
+ const require_create_plugin_rule = require("../utils/create-plugin-rule.cjs");
2
+ const require_track_zod_schema_imports = require("../utils/track-zod-schema-imports.cjs");
3
+ const require_build_zod_chain_remove_method_fix = require("../utils/build-zod-chain-remove-method-fix.cjs");
4
+ //#region src/rules/no-number-schema-with-finite.ts
5
+ const { zodImportAllowedSource, trackZodSchemaImports } = require_track_zod_schema_imports.createZodSchemaImportTrack("zod");
6
+ const noNumberSchemaWithFinite = require_create_plugin_rule.createZodPluginRule({
7
+ name: "no-number-schema-with-finite",
8
+ meta: {
9
+ fixable: "code",
10
+ type: "problem",
11
+ docs: {
12
+ zodImportAllowedSource,
13
+ description: "Disallow deprecated `z.number().finite()`. In Zod 4+ number schemas do not allow infinite values by default, so it is a no-op."
14
+ },
15
+ messages: { removeFinite: "`.finite()` is deprecated. In Zod 4+ `z.number()` does not allow infinite values by default. Remove this call." },
16
+ schema: []
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods } = trackZodSchemaImports();
21
+ return {
22
+ ImportDeclaration: importDeclarationListener,
23
+ CallExpression(node) {
24
+ if (detectZodSchemaRootNode(node)?.schemaType !== "number") return;
25
+ const methods = collectZodChainMethods(node);
26
+ const finiteIndex = methods.findIndex((m) => m.name === "finite" && m.node === node);
27
+ if (finiteIndex === -1) return;
28
+ context.report({
29
+ node,
30
+ messageId: "removeFinite",
31
+ fix(fixer) {
32
+ return require_build_zod_chain_remove_method_fix.buildZodChainRemoveMethodFix({
33
+ fixer,
34
+ methods,
35
+ removeIndex: finiteIndex
36
+ });
37
+ }
38
+ });
39
+ }
40
+ };
41
+ }
42
+ });
43
+ //#endregion
44
+ exports.noNumberSchemaWithFinite = noNumberSchemaWithFinite;
@@ -0,0 +1,44 @@
1
+ import { createZodPluginRule } from "../utils/create-plugin-rule.mjs";
2
+ import { createZodSchemaImportTrack } from "../utils/track-zod-schema-imports.mjs";
3
+ import { buildZodChainRemoveMethodFix } from "../utils/build-zod-chain-remove-method-fix.mjs";
4
+ //#region src/rules/no-number-schema-with-finite.ts
5
+ const { zodImportAllowedSource, trackZodSchemaImports } = createZodSchemaImportTrack("zod");
6
+ const noNumberSchemaWithFinite = createZodPluginRule({
7
+ name: "no-number-schema-with-finite",
8
+ meta: {
9
+ fixable: "code",
10
+ type: "problem",
11
+ docs: {
12
+ zodImportAllowedSource,
13
+ description: "Disallow deprecated `z.number().finite()`. In Zod 4+ number schemas do not allow infinite values by default, so it is a no-op."
14
+ },
15
+ messages: { removeFinite: "`.finite()` is deprecated. In Zod 4+ `z.number()` does not allow infinite values by default. Remove this call." },
16
+ schema: []
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods } = trackZodSchemaImports();
21
+ return {
22
+ ImportDeclaration: importDeclarationListener,
23
+ CallExpression(node) {
24
+ if (detectZodSchemaRootNode(node)?.schemaType !== "number") return;
25
+ const methods = collectZodChainMethods(node);
26
+ const finiteIndex = methods.findIndex((m) => m.name === "finite" && m.node === node);
27
+ if (finiteIndex === -1) return;
28
+ context.report({
29
+ node,
30
+ messageId: "removeFinite",
31
+ fix(fixer) {
32
+ return buildZodChainRemoveMethodFix({
33
+ fixer,
34
+ methods,
35
+ removeIndex: finiteIndex
36
+ });
37
+ }
38
+ });
39
+ }
40
+ };
41
+ }
42
+ });
43
+ //#endregion
44
+ export { noNumberSchemaWithFinite };
@@ -0,0 +1,38 @@
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_create_plugin_rule = require("../utils/create-plugin-rule.cjs");
3
+ const require_track_zod_schema_imports = require("../utils/track-zod-schema-imports.cjs");
4
+ let _typescript_eslint_utils = require("@typescript-eslint/utils");
5
+ //#region src/rules/no-number-schema-with-is-finite.ts
6
+ const { zodImportAllowedSource, trackZodSchemaImports } = require_track_zod_schema_imports.createZodSchemaImportTrack("zod");
7
+ const noNumberSchemaWithIsFinite = require_create_plugin_rule.createZodPluginRule({
8
+ name: "no-number-schema-with-is-finite",
9
+ meta: {
10
+ type: "problem",
11
+ docs: {
12
+ zodImportAllowedSource,
13
+ description: "Disallow using deprecated `isFinite` on a Zod number schema; in v4+ it is always `true`."
14
+ },
15
+ messages: { deprecated: "`isFinite` is deprecated. Number schemas no longer accept infinite values, so this is always `true` for `z.number()`." },
16
+ schema: []
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const { importDeclarationListener, isZodNumberSchemaCallExpression } = trackZodSchemaImports();
21
+ return {
22
+ ImportDeclaration: importDeclarationListener,
23
+ MemberExpression(node) {
24
+ if (node.computed) return;
25
+ if (node.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
26
+ if (node.property.name !== "isFinite") return;
27
+ if (node.object.type !== _typescript_eslint_utils.AST_NODE_TYPES.CallExpression) return;
28
+ if (!isZodNumberSchemaCallExpression(node.object)) return;
29
+ context.report({
30
+ node,
31
+ messageId: "deprecated"
32
+ });
33
+ }
34
+ };
35
+ }
36
+ });
37
+ //#endregion
38
+ exports.noNumberSchemaWithIsFinite = noNumberSchemaWithIsFinite;
@@ -0,0 +1,37 @@
1
+ import { createZodPluginRule } from "../utils/create-plugin-rule.mjs";
2
+ import { createZodSchemaImportTrack } from "../utils/track-zod-schema-imports.mjs";
3
+ import { AST_NODE_TYPES } from "@typescript-eslint/utils";
4
+ //#region src/rules/no-number-schema-with-is-finite.ts
5
+ const { zodImportAllowedSource, trackZodSchemaImports } = createZodSchemaImportTrack("zod");
6
+ const noNumberSchemaWithIsFinite = createZodPluginRule({
7
+ name: "no-number-schema-with-is-finite",
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ zodImportAllowedSource,
12
+ description: "Disallow using deprecated `isFinite` on a Zod number schema; in v4+ it is always `true`."
13
+ },
14
+ messages: { deprecated: "`isFinite` is deprecated. Number schemas no longer accept infinite values, so this is always `true` for `z.number()`." },
15
+ schema: []
16
+ },
17
+ defaultOptions: [],
18
+ create(context) {
19
+ const { importDeclarationListener, isZodNumberSchemaCallExpression } = trackZodSchemaImports();
20
+ return {
21
+ ImportDeclaration: importDeclarationListener,
22
+ MemberExpression(node) {
23
+ if (node.computed) return;
24
+ if (node.property.type !== AST_NODE_TYPES.Identifier) return;
25
+ if (node.property.name !== "isFinite") return;
26
+ if (node.object.type !== AST_NODE_TYPES.CallExpression) return;
27
+ if (!isZodNumberSchemaCallExpression(node.object)) return;
28
+ context.report({
29
+ node,
30
+ messageId: "deprecated"
31
+ });
32
+ }
33
+ };
34
+ }
35
+ });
36
+ //#endregion
37
+ export { noNumberSchemaWithIsFinite };
@@ -0,0 +1,38 @@
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_create_plugin_rule = require("../utils/create-plugin-rule.cjs");
3
+ const require_track_zod_schema_imports = require("../utils/track-zod-schema-imports.cjs");
4
+ let _typescript_eslint_utils = require("@typescript-eslint/utils");
5
+ //#region src/rules/no-number-schema-with-is-int.ts
6
+ const { zodImportAllowedSource, trackZodSchemaImports } = require_track_zod_schema_imports.createZodSchemaImportTrack("zod");
7
+ const noNumberSchemaWithIsInt = require_create_plugin_rule.createZodPluginRule({
8
+ name: "no-number-schema-with-is-int",
9
+ meta: {
10
+ type: "problem",
11
+ docs: {
12
+ zodImportAllowedSource,
13
+ description: "Disallow using deprecated `isInt` on a Zod number schema; check the `format` property instead."
14
+ },
15
+ messages: { useFormat: "`isInt` is deprecated. Check the `format` property on the number schema instead (or compare to `\"int\"` or `\"float\"`)." },
16
+ schema: []
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const { importDeclarationListener, isZodNumberSchemaCallExpression } = trackZodSchemaImports();
21
+ return {
22
+ ImportDeclaration: importDeclarationListener,
23
+ MemberExpression(node) {
24
+ if (node.computed) return;
25
+ if (node.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
26
+ if (node.property.name !== "isInt") return;
27
+ if (node.object.type !== _typescript_eslint_utils.AST_NODE_TYPES.CallExpression) return;
28
+ if (!isZodNumberSchemaCallExpression(node.object)) return;
29
+ context.report({
30
+ node,
31
+ messageId: "useFormat"
32
+ });
33
+ }
34
+ };
35
+ }
36
+ });
37
+ //#endregion
38
+ exports.noNumberSchemaWithIsInt = noNumberSchemaWithIsInt;
@@ -0,0 +1,37 @@
1
+ import { createZodPluginRule } from "../utils/create-plugin-rule.mjs";
2
+ import { createZodSchemaImportTrack } from "../utils/track-zod-schema-imports.mjs";
3
+ import { AST_NODE_TYPES } from "@typescript-eslint/utils";
4
+ //#region src/rules/no-number-schema-with-is-int.ts
5
+ const { zodImportAllowedSource, trackZodSchemaImports } = createZodSchemaImportTrack("zod");
6
+ const noNumberSchemaWithIsInt = createZodPluginRule({
7
+ name: "no-number-schema-with-is-int",
8
+ meta: {
9
+ type: "problem",
10
+ docs: {
11
+ zodImportAllowedSource,
12
+ description: "Disallow using deprecated `isInt` on a Zod number schema; check the `format` property instead."
13
+ },
14
+ messages: { useFormat: "`isInt` is deprecated. Check the `format` property on the number schema instead (or compare to `\"int\"` or `\"float\"`)." },
15
+ schema: []
16
+ },
17
+ defaultOptions: [],
18
+ create(context) {
19
+ const { importDeclarationListener, isZodNumberSchemaCallExpression } = trackZodSchemaImports();
20
+ return {
21
+ ImportDeclaration: importDeclarationListener,
22
+ MemberExpression(node) {
23
+ if (node.computed) return;
24
+ if (node.property.type !== AST_NODE_TYPES.Identifier) return;
25
+ if (node.property.name !== "isInt") return;
26
+ if (node.object.type !== AST_NODE_TYPES.CallExpression) return;
27
+ if (!isZodNumberSchemaCallExpression(node.object)) return;
28
+ context.report({
29
+ node,
30
+ messageId: "useFormat"
31
+ });
32
+ }
33
+ };
34
+ }
35
+ });
36
+ //#endregion
37
+ export { noNumberSchemaWithIsInt };
@@ -0,0 +1,57 @@
1
+ const require_create_plugin_rule = require("../utils/create-plugin-rule.cjs");
2
+ const require_track_zod_schema_imports = require("../utils/track-zod-schema-imports.cjs");
3
+ const require_build_zod_chain_replacement_fix = require("../utils/build-zod-chain-replacement-fix.cjs");
4
+ //#region src/rules/no-number-schema-with-safe.ts
5
+ const { zodImportAllowedSource, trackZodSchemaImports } = require_track_zod_schema_imports.createZodSchemaImportTrack("zod");
6
+ const noNumberSchemaWithSafe = require_create_plugin_rule.createZodPluginRule({
7
+ name: "no-number-schema-with-safe",
8
+ meta: {
9
+ fixable: "code",
10
+ type: "problem",
11
+ docs: {
12
+ zodImportAllowedSource,
13
+ description: "Disallow deprecated `z.number().safe()`. Use `z.int()`; `.safe()` is now identical to `.int()`."
14
+ },
15
+ messages: { useInt: "`.safe()` is deprecated; it is identical to `.int()`. Use `z.int()` (or the equivalent) instead of chaining `.safe()` on `z.number()`." },
16
+ schema: []
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const { sourceCode } = context;
21
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods } = trackZodSchemaImports();
22
+ return {
23
+ ImportDeclaration: importDeclarationListener,
24
+ CallExpression(node) {
25
+ const zodSchemaMeta = detectZodSchemaRootNode(node);
26
+ if (zodSchemaMeta?.schemaType !== "number") return;
27
+ const methods = collectZodChainMethods(node);
28
+ const safeIndex = methods.findIndex((m) => m.name === "safe" && m.node === node);
29
+ if (safeIndex === -1) return;
30
+ const numberIndex = methods.findIndex((m) => m.name === "number");
31
+ if (zodSchemaMeta.schemaDecl === "named") {
32
+ context.report({
33
+ node,
34
+ messageId: "useInt"
35
+ });
36
+ return;
37
+ }
38
+ context.report({
39
+ node,
40
+ messageId: "useInt",
41
+ fix(fixer) {
42
+ return require_build_zod_chain_replacement_fix.buildZodChainReplacementFix({
43
+ sourceCode,
44
+ fixer,
45
+ methods,
46
+ fromIndex: numberIndex,
47
+ toIndex: safeIndex,
48
+ toMethodName: "int"
49
+ });
50
+ }
51
+ });
52
+ }
53
+ };
54
+ }
55
+ });
56
+ //#endregion
57
+ exports.noNumberSchemaWithSafe = noNumberSchemaWithSafe;
@@ -0,0 +1,57 @@
1
+ import { createZodPluginRule } from "../utils/create-plugin-rule.mjs";
2
+ import { createZodSchemaImportTrack } from "../utils/track-zod-schema-imports.mjs";
3
+ import { buildZodChainReplacementFix } from "../utils/build-zod-chain-replacement-fix.mjs";
4
+ //#region src/rules/no-number-schema-with-safe.ts
5
+ const { zodImportAllowedSource, trackZodSchemaImports } = createZodSchemaImportTrack("zod");
6
+ const noNumberSchemaWithSafe = createZodPluginRule({
7
+ name: "no-number-schema-with-safe",
8
+ meta: {
9
+ fixable: "code",
10
+ type: "problem",
11
+ docs: {
12
+ zodImportAllowedSource,
13
+ description: "Disallow deprecated `z.number().safe()`. Use `z.int()`; `.safe()` is now identical to `.int()`."
14
+ },
15
+ messages: { useInt: "`.safe()` is deprecated; it is identical to `.int()`. Use `z.int()` (or the equivalent) instead of chaining `.safe()` on `z.number()`." },
16
+ schema: []
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const { sourceCode } = context;
21
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods } = trackZodSchemaImports();
22
+ return {
23
+ ImportDeclaration: importDeclarationListener,
24
+ CallExpression(node) {
25
+ const zodSchemaMeta = detectZodSchemaRootNode(node);
26
+ if (zodSchemaMeta?.schemaType !== "number") return;
27
+ const methods = collectZodChainMethods(node);
28
+ const safeIndex = methods.findIndex((m) => m.name === "safe" && m.node === node);
29
+ if (safeIndex === -1) return;
30
+ const numberIndex = methods.findIndex((m) => m.name === "number");
31
+ if (zodSchemaMeta.schemaDecl === "named") {
32
+ context.report({
33
+ node,
34
+ messageId: "useInt"
35
+ });
36
+ return;
37
+ }
38
+ context.report({
39
+ node,
40
+ messageId: "useInt",
41
+ fix(fixer) {
42
+ return buildZodChainReplacementFix({
43
+ sourceCode,
44
+ fixer,
45
+ methods,
46
+ fromIndex: numberIndex,
47
+ toIndex: safeIndex,
48
+ toMethodName: "int"
49
+ });
50
+ }
51
+ });
52
+ }
53
+ };
54
+ }
55
+ });
56
+ //#endregion
57
+ export { noNumberSchemaWithSafe };
@@ -0,0 +1,43 @@
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_create_plugin_rule = require("../utils/create-plugin-rule.cjs");
3
+ const require_track_zod_schema_imports = require("../utils/track-zod-schema-imports.cjs");
4
+ let _typescript_eslint_utils = require("@typescript-eslint/utils");
5
+ //#region src/rules/no-number-schema-with-step.ts
6
+ const { zodImportAllowedSource, trackZodSchemaImports } = require_track_zod_schema_imports.createZodSchemaImportTrack("zod");
7
+ const noNumberSchemaWithStep = require_create_plugin_rule.createZodPluginRule({
8
+ name: "no-number-schema-with-step",
9
+ meta: {
10
+ fixable: "code",
11
+ type: "problem",
12
+ docs: {
13
+ zodImportAllowedSource,
14
+ description: "Disallow deprecated `z.number().step()`. Use `.multipleOf()` instead."
15
+ },
16
+ messages: { useMultipleOf: "`.step()` is deprecated. Use `.multipleOf()` with the same argument instead." },
17
+ schema: []
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods } = trackZodSchemaImports();
22
+ return {
23
+ ImportDeclaration: importDeclarationListener,
24
+ CallExpression(node) {
25
+ if (detectZodSchemaRootNode(node)?.schemaType !== "number") return;
26
+ if (collectZodChainMethods(node).findIndex((m) => m.name === "step" && m.node === node) === -1) return;
27
+ const { callee } = node;
28
+ if (callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.MemberExpression) return;
29
+ if (callee.property.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier) return;
30
+ const { property } = callee;
31
+ context.report({
32
+ node,
33
+ messageId: "useMultipleOf",
34
+ fix(fixer) {
35
+ return fixer.replaceText(property, "multipleOf");
36
+ }
37
+ });
38
+ }
39
+ };
40
+ }
41
+ });
42
+ //#endregion
43
+ exports.noNumberSchemaWithStep = noNumberSchemaWithStep;
@@ -0,0 +1,42 @@
1
+ import { createZodPluginRule } from "../utils/create-plugin-rule.mjs";
2
+ import { createZodSchemaImportTrack } from "../utils/track-zod-schema-imports.mjs";
3
+ import { AST_NODE_TYPES } from "@typescript-eslint/utils";
4
+ //#region src/rules/no-number-schema-with-step.ts
5
+ const { zodImportAllowedSource, trackZodSchemaImports } = createZodSchemaImportTrack("zod");
6
+ const noNumberSchemaWithStep = createZodPluginRule({
7
+ name: "no-number-schema-with-step",
8
+ meta: {
9
+ fixable: "code",
10
+ type: "problem",
11
+ docs: {
12
+ zodImportAllowedSource,
13
+ description: "Disallow deprecated `z.number().step()`. Use `.multipleOf()` instead."
14
+ },
15
+ messages: { useMultipleOf: "`.step()` is deprecated. Use `.multipleOf()` with the same argument instead." },
16
+ schema: []
17
+ },
18
+ defaultOptions: [],
19
+ create(context) {
20
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods } = trackZodSchemaImports();
21
+ return {
22
+ ImportDeclaration: importDeclarationListener,
23
+ CallExpression(node) {
24
+ if (detectZodSchemaRootNode(node)?.schemaType !== "number") return;
25
+ if (collectZodChainMethods(node).findIndex((m) => m.name === "step" && m.node === node) === -1) return;
26
+ const { callee } = node;
27
+ if (callee.type !== AST_NODE_TYPES.MemberExpression) return;
28
+ if (callee.property.type !== AST_NODE_TYPES.Identifier) return;
29
+ const { property } = callee;
30
+ context.report({
31
+ node,
32
+ messageId: "useMultipleOf",
33
+ fix(fixer) {
34
+ return fixer.replaceText(property, "multipleOf");
35
+ }
36
+ });
37
+ }
38
+ };
39
+ }
40
+ });
41
+ //#endregion
42
+ export { noNumberSchemaWithStep };
@@ -0,0 +1,15 @@
1
+ //#region src/utils/build-zod-chain-remove-method-fix.ts
2
+ /**
3
+ * Remove one call from a zod method chain, e.g. `z.number().min(0).finite()` →
4
+ * `z.number().min(0)`.
5
+ */
6
+ function buildZodChainRemoveMethodFix(opts) {
7
+ const { fixer, methods, removeIndex } = opts;
8
+ if (removeIndex < 1) return null;
9
+ const prev = methods[removeIndex - 1]?.node;
10
+ const toRemove = methods[removeIndex]?.node;
11
+ if (!prev?.range || !toRemove?.range) return null;
12
+ return fixer.removeRange([prev.range[1], toRemove.range[1]]);
13
+ }
14
+ //#endregion
15
+ exports.buildZodChainRemoveMethodFix = buildZodChainRemoveMethodFix;
@@ -0,0 +1,15 @@
1
+ //#region src/utils/build-zod-chain-remove-method-fix.ts
2
+ /**
3
+ * Remove one call from a zod method chain, e.g. `z.number().min(0).finite()` →
4
+ * `z.number().min(0)`.
5
+ */
6
+ function buildZodChainRemoveMethodFix(opts) {
7
+ const { fixer, methods, removeIndex } = opts;
8
+ if (removeIndex < 1) return null;
9
+ const prev = methods[removeIndex - 1]?.node;
10
+ const toRemove = methods[removeIndex]?.node;
11
+ if (!prev?.range || !toRemove?.range) return null;
12
+ return fixer.removeRange([prev.range[1], toRemove.range[1]]);
13
+ }
14
+ //#endregion
15
+ export { buildZodChainRemoveMethodFix };
@@ -71,6 +71,16 @@ function parseZodCallExpression(call, zodNamespaces, zodNamedImports) {
71
71
  };
72
72
  return null;
73
73
  }
74
+ /**
75
+ * True when `node` is a zod number schema call chain (e.g. `z.number().min(1)`) or `number().min(1)`.
76
+ * Used for member access like `z.number().isInt` where the call is not the outermost expression
77
+ * in the file (so {@link detectZodSchemaRootNode} does not apply).
78
+ */
79
+ function isZodNumberSchemaCallExpression(node, zodNamespaces, zodNamedImports) {
80
+ if (node.type !== _typescript_eslint_utils.AST_NODE_TYPES.CallExpression) return false;
81
+ const parsed = parseZodCallExpression(node, zodNamespaces, zodNamedImports);
82
+ return parsed !== null && parsed.schemaType === "number";
83
+ }
74
84
  /** Examine an expression (argument) for zod schema CallExpressions.
75
85
  * Supports:
76
86
  * - direct CallExpression (string(), z.string(), etc)
@@ -100,3 +110,4 @@ function detectZodSchemaRootNode(node, zodNamespaces, zodNamedImports) {
100
110
  }
101
111
  //#endregion
102
112
  exports.detectZodSchemaRootNode = detectZodSchemaRootNode;
113
+ exports.isZodNumberSchemaCallExpression = isZodNumberSchemaCallExpression;
@@ -70,6 +70,16 @@ function parseZodCallExpression(call, zodNamespaces, zodNamedImports) {
70
70
  };
71
71
  return null;
72
72
  }
73
+ /**
74
+ * True when `node` is a zod number schema call chain (e.g. `z.number().min(1)`) or `number().min(1)`.
75
+ * Used for member access like `z.number().isInt` where the call is not the outermost expression
76
+ * in the file (so {@link detectZodSchemaRootNode} does not apply).
77
+ */
78
+ function isZodNumberSchemaCallExpression(node, zodNamespaces, zodNamedImports) {
79
+ if (node.type !== AST_NODE_TYPES.CallExpression) return false;
80
+ const parsed = parseZodCallExpression(node, zodNamespaces, zodNamedImports);
81
+ return parsed !== null && parsed.schemaType === "number";
82
+ }
73
83
  /** Examine an expression (argument) for zod schema CallExpressions.
74
84
  * Supports:
75
85
  * - direct CallExpression (string(), z.string(), etc)
@@ -98,4 +108,4 @@ function detectZodSchemaRootNode(node, zodNamespaces, zodNamedImports) {
98
108
  };
99
109
  }
100
110
  //#endregion
101
- export { detectZodSchemaRootNode };
111
+ export { detectZodSchemaRootNode, isZodNumberSchemaCallExpression };
@@ -58,7 +58,8 @@ function trackZodSchemaImports(importAllowedSource) {
58
58
  getNamedImportOriginal: (localName) => zodNamedImports.get(localName),
59
59
  getNamedImportLocal: (originalName) => zodNamedImportsByOriginal.get(originalName),
60
60
  detectZodSchemaRootNode: (node) => require_detect_zod_schema_root_node.detectZodSchemaRootNode(node, zodNamespaces, zodNamedImports),
61
- collectZodChainMethods
61
+ collectZodChainMethods,
62
+ isZodNumberSchemaCallExpression: (node) => require_detect_zod_schema_root_node.isZodNumberSchemaCallExpression(node, zodNamespaces, zodNamedImports)
62
63
  };
63
64
  }
64
65
  /**
@@ -1,4 +1,4 @@
1
- import { detectZodSchemaRootNode } from "./detect-zod-schema-root-node.mjs";
1
+ import { detectZodSchemaRootNode, isZodNumberSchemaCallExpression } from "./detect-zod-schema-root-node.mjs";
2
2
  import { isZodImportSource } from "./is-zod-import-source.mjs";
3
3
  import { AST_NODE_TYPES } from "@typescript-eslint/utils";
4
4
  //#region src/utils/track-zod-schema-imports.ts
@@ -57,7 +57,8 @@ function trackZodSchemaImports(importAllowedSource) {
57
57
  getNamedImportOriginal: (localName) => zodNamedImports.get(localName),
58
58
  getNamedImportLocal: (originalName) => zodNamedImportsByOriginal.get(originalName),
59
59
  detectZodSchemaRootNode: (node) => detectZodSchemaRootNode(node, zodNamespaces, zodNamedImports),
60
- collectZodChainMethods
60
+ collectZodChainMethods,
61
+ isZodNumberSchemaCallExpression: (node) => isZodNumberSchemaCallExpression(node, zodNamespaces, zodNamedImports)
61
62
  };
62
63
  }
63
64
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-zod",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "type": "module",
5
5
  "description": "ESLint plugin that adds custom linting rules to enforce best practices when using Zod",
6
6
  "engines": {