eslint-plugin-zod 3.5.4 → 3.7.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
@@ -17,7 +17,10 @@
17
17
  [issuesBadge]: https://img.shields.io/github/issues/marcalexiei/eslint-plugin-zod.svg?style=for-the-badge
18
18
  [issuesURL]: https://github.com/marcalexiei/eslint-plugin-zod/issues
19
19
 
20
- [ESLint](https://eslint.org) plugin that adds custom linting rules to enforce best practices when using [Zod](https://github.com/colinhacks/zod)
20
+ [ESLint](https://eslint.org) plugin that adds custom linting rules to enforce best practices when using [Zod](https://github.com/colinhacks/zod).
21
+
22
+ It can also work with [Oxlint](https://oxc.rs/docs/guide/usage/linter.html)!\
23
+ Find out more about [Oxlint's `jsPLugins`](https://oxc.rs/docs/guide/usage/linter/js-plugins.html).
21
24
 
22
25
  ## Rules
23
26
 
@@ -52,21 +55,24 @@ This plugin is primarily built for `zod`, so some rules are exclusive to `zod` a
52
55
 
53
56
  ### `zod` exclusive rules
54
57
 
55
- | Name                             | Description | 💼 | 🔧 | 💡 | ❌ |
56
- | :--------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | :-- | :-- | :-- | :-- |
57
- | [array-style](docs/rules/array-style.md) | Enforce consistent Zod array style | ✅ | 🔧 | | |
58
- | [no-number-schema-with-int](docs/rules/no-number-schema-with-int.md) | Disallow usage of `z.number().int()` as it is considered legacy | ✅ | 🔧 | | |
59
- | [no-optional-and-default-together](docs/rules/no-optional-and-default-together.md) | Disallow using both `.optional()` and `.default()` on the same Zod schema | ✅ | 🔧 | | |
60
- | [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 | ✅ | 🔧 | | |
61
- | [no-throw-in-refine](docs/rules/no-throw-in-refine.md) | Disallow throwing errors directly inside Zod refine callbacks | ✅ | | | |
62
- | [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. | ✅ | 🔧 | | |
63
- | [prefer-meta-last](docs/rules/prefer-meta-last.md) | Enforce `.meta()` as last method | ✅ | 🔧 | | |
64
- | [prefer-string-schema-with-trim](docs/rules/prefer-string-schema-with-trim.md) | Enforce `z.string().trim()` to prevent accidental leading/trailing whitespace | ✅ | 🔧 | | |
58
+ | Name                             | Description | 💼 | 🔧 | 💡 | ❌ |
59
+ | :--------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------- | :-- | :-- | :-- | :-- |
60
+ | [array-style](docs/rules/array-style.md) | Enforce consistent Zod array style | ✅ | 🔧 | | |
61
+ | [no-number-schema-with-int](docs/rules/no-number-schema-with-int.md) | Disallow usage of `z.number().int()` as it is considered legacy | ✅ | 🔧 | | |
62
+ | [no-optional-and-default-together](docs/rules/no-optional-and-default-together.md) | Disallow using both `.optional()` and `.default()` on the same Zod schema | ✅ | 🔧 | | |
63
+ | [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 | ✅ | 🔧 | | |
64
+ | [no-throw-in-refine](docs/rules/no-throw-in-refine.md) | Disallow throwing errors directly inside Zod refine callbacks | ✅ | | | |
65
+ | [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 | | | | |
66
+ | [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. | ✅ | 🔧 | | |
67
+ | [prefer-meta-last](docs/rules/prefer-meta-last.md) | Enforce `.meta()` as last method | ✅ | 🔧 | | |
68
+ | [prefer-string-schema-with-trim](docs/rules/prefer-string-schema-with-trim.md) | Enforce `z.string().trim()` to prevent accidental leading/trailing whitespace | ✅ | 🔧 | | |
65
69
 
66
70
  <!-- end auto-generated rules list -->
67
71
 
68
72
  ## Installation
69
73
 
74
+ ### ESLint
75
+
70
76
  Install `eslint` and `eslint-plugin-zod` using your preferred package manager:
71
77
 
72
78
  ```shell
@@ -81,7 +87,7 @@ yarn add --dev eslint eslint-plugin-zod
81
87
  pnpm add --save-dev eslint eslint-plugin-zod
82
88
  ```
83
89
 
84
- ## Configuration
90
+ #### ESLint Configuration
85
91
 
86
92
  1. Import the plugin
87
93
 
@@ -109,6 +115,57 @@ export default defineConfig(
109
115
  );
110
116
  ```
111
117
 
118
+ ### Oxlint
119
+
120
+ Install `oxlint` and `eslint-plugin-zod` using your preferred package manager:
121
+
122
+ ```shell
123
+ npm i --save-dev oxlint eslint-plugin-zod
124
+ ```
125
+
126
+ ```shell
127
+ yarn add --dev oxlint eslint-plugin-zod
128
+ ```
129
+
130
+ ```shell
131
+ pnpm add --save-dev oxlint eslint-plugin-zod
132
+ ```
133
+
134
+ #### Oxlint Configuration
135
+
136
+ 1. Import the plugin
137
+
138
+ ```ts
139
+ import eslintPluginZod from 'eslint-plugin-zod';
140
+ ```
141
+
142
+ 2. Add `eslint-plugin-zod` to the `jsPlugins` key
143
+
144
+ ```ts
145
+ {
146
+ jsPlugins: ['eslint-plugin-zod'],
147
+ // ...
148
+ }
149
+ ```
150
+
151
+ 3. Add `eslintPluginZod.configs.recommended.rules` to your Oxlint config.\
152
+ Alternatively you can specify the rules manually
153
+
154
+ Here’s a minimal example using the flat config format:
155
+
156
+ ```ts
157
+ // oxlint.config.ts
158
+ import eslintPluginZod from 'eslint-plugin-zod';
159
+ import { defineConfig } from 'oxlint';
160
+
161
+ export default defineConfig({
162
+ jsPlugins: ['eslint-plugin-zod'],
163
+ rules: {
164
+ ...eslintPluginZod.configs.recommended.rules,
165
+ },
166
+ });
167
+ ```
168
+
112
169
  ## Zod peer dependency version
113
170
 
114
171
  `eslint-plugin-zod` is designed for projects that use `zod@^4`.
package/dist/index.cjs CHANGED
@@ -9,6 +9,7 @@ const require_no_number_schema_with_int = require("./rules/no-number-schema-with
9
9
  const require_no_optional_and_default_together = require("./rules/no-optional-and-default-together.cjs");
10
10
  const require_no_string_schema_with_uuid = require("./rules/no-string-schema-with-uuid.cjs");
11
11
  const require_no_throw_in_refine = require("./rules/no-throw-in-refine.cjs");
12
+ const require_no_transform_in_record_key = require("./rules/no-transform-in-record-key.cjs");
12
13
  const require_no_unknown_schema = require("./rules/no-unknown-schema.cjs");
13
14
  const require_prefer_enum_over_literal_union = require("./rules/prefer-enum-over-literal-union.cjs");
14
15
  const require_prefer_meta_last = require("./rules/prefer-meta-last.cjs");
@@ -36,6 +37,7 @@ const eslintPluginZod = {
36
37
  "no-string-schema-with-uuid": require_no_string_schema_with_uuid.noStringSchemaWithUuid,
37
38
  "no-optional-and-default-together": require_no_optional_and_default_together.noOptionalAndDefaultTogether,
38
39
  "no-throw-in-refine": require_no_throw_in_refine.noThrowInRefine,
40
+ "no-transform-in-record-key": require_no_transform_in_record_key.noTransformInRecordKey,
39
41
  "no-unknown-schema": require_no_unknown_schema.noUnknownSchema,
40
42
  "prefer-enum-over-literal-union": require_prefer_enum_over_literal_union.preferEnumOverLiteralUnion,
41
43
  "prefer-meta": require_prefer_meta.preferMeta,
package/dist/index.mjs CHANGED
@@ -9,6 +9,7 @@ import { noNumberSchemaWithInt } from "./rules/no-number-schema-with-int.mjs";
9
9
  import { noOptionalAndDefaultTogether } from "./rules/no-optional-and-default-together.mjs";
10
10
  import { noStringSchemaWithUuid } from "./rules/no-string-schema-with-uuid.mjs";
11
11
  import { noThrowInRefine } from "./rules/no-throw-in-refine.mjs";
12
+ import { noTransformInRecordKey } from "./rules/no-transform-in-record-key.mjs";
12
13
  import { noUnknownSchema } from "./rules/no-unknown-schema.mjs";
13
14
  import { preferEnumOverLiteralUnion } from "./rules/prefer-enum-over-literal-union.mjs";
14
15
  import { preferMetaLast } from "./rules/prefer-meta-last.mjs";
@@ -36,6 +37,7 @@ const eslintPluginZod = {
36
37
  "no-string-schema-with-uuid": noStringSchemaWithUuid,
37
38
  "no-optional-and-default-together": noOptionalAndDefaultTogether,
38
39
  "no-throw-in-refine": noThrowInRefine,
40
+ "no-transform-in-record-key": noTransformInRecordKey,
39
41
  "no-unknown-schema": noUnknownSchema,
40
42
  "prefer-enum-over-literal-union": preferEnumOverLiteralUnion,
41
43
  "prefer-meta": preferMeta,
@@ -0,0 +1,54 @@
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-transform-in-record-key.ts
6
+ const { zodImportAllowedSource, trackZodSchemaImports } = require_track_zod_schema_imports.createZodSchemaImportTrack("zod");
7
+ /**
8
+ * Methods that mutate/transform the value and should not be used in z.record() key schemas
9
+ */
10
+ const TRANSFORM_METHODS = [
11
+ "transform",
12
+ "map",
13
+ "trim",
14
+ "toLowerCase",
15
+ "toUpperCase"
16
+ ];
17
+ const noTransformInRecordKey = require_create_plugin_rule.createZodPluginRule({
18
+ name: "no-transform-in-record-key",
19
+ meta: {
20
+ type: "problem",
21
+ docs: {
22
+ zodImportAllowedSource,
23
+ description: "Disallow transforms in z.record() key schemas, which can cause silent key mutations and data loss through key collisions"
24
+ },
25
+ messages: { noTransformInRecordKey: "Transforms in z.record() key schemas cause silent key mutation and potential data loss. Use validators like .min() instead of transforms like .trim() or .toLowerCase()" },
26
+ schema: []
27
+ },
28
+ defaultOptions: [],
29
+ create(context) {
30
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods } = trackZodSchemaImports();
31
+ /**
32
+ * Check if a schema (as an expression) contains any transform methods in its chain
33
+ */
34
+ function hasTransformMethods(schema) {
35
+ if (schema.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) return false;
36
+ if (schema.type === _typescript_eslint_utils.AST_NODE_TYPES.CallExpression) return collectZodChainMethods(schema).some((method) => TRANSFORM_METHODS.includes(method.name));
37
+ return false;
38
+ }
39
+ return {
40
+ ImportDeclaration: importDeclarationListener,
41
+ CallExpression(node) {
42
+ if (detectZodSchemaRootNode(node)?.schemaType !== "record") return;
43
+ const keySchemaArg = node.arguments.at(0);
44
+ if (!keySchemaArg) return;
45
+ if (hasTransformMethods(keySchemaArg)) context.report({
46
+ node: keySchemaArg,
47
+ messageId: "noTransformInRecordKey"
48
+ });
49
+ }
50
+ };
51
+ }
52
+ });
53
+ //#endregion
54
+ exports.noTransformInRecordKey = noTransformInRecordKey;
@@ -0,0 +1,53 @@
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-transform-in-record-key.ts
5
+ const { zodImportAllowedSource, trackZodSchemaImports } = createZodSchemaImportTrack("zod");
6
+ /**
7
+ * Methods that mutate/transform the value and should not be used in z.record() key schemas
8
+ */
9
+ const TRANSFORM_METHODS = [
10
+ "transform",
11
+ "map",
12
+ "trim",
13
+ "toLowerCase",
14
+ "toUpperCase"
15
+ ];
16
+ const noTransformInRecordKey = createZodPluginRule({
17
+ name: "no-transform-in-record-key",
18
+ meta: {
19
+ type: "problem",
20
+ docs: {
21
+ zodImportAllowedSource,
22
+ description: "Disallow transforms in z.record() key schemas, which can cause silent key mutations and data loss through key collisions"
23
+ },
24
+ messages: { noTransformInRecordKey: "Transforms in z.record() key schemas cause silent key mutation and potential data loss. Use validators like .min() instead of transforms like .trim() or .toLowerCase()" },
25
+ schema: []
26
+ },
27
+ defaultOptions: [],
28
+ create(context) {
29
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods } = trackZodSchemaImports();
30
+ /**
31
+ * Check if a schema (as an expression) contains any transform methods in its chain
32
+ */
33
+ function hasTransformMethods(schema) {
34
+ if (schema.type === AST_NODE_TYPES.SpreadElement) return false;
35
+ if (schema.type === AST_NODE_TYPES.CallExpression) return collectZodChainMethods(schema).some((method) => TRANSFORM_METHODS.includes(method.name));
36
+ return false;
37
+ }
38
+ return {
39
+ ImportDeclaration: importDeclarationListener,
40
+ CallExpression(node) {
41
+ if (detectZodSchemaRootNode(node)?.schemaType !== "record") return;
42
+ const keySchemaArg = node.arguments.at(0);
43
+ if (!keySchemaArg) return;
44
+ if (hasTransformMethods(keySchemaArg)) context.report({
45
+ node: keySchemaArg,
46
+ messageId: "noTransformInRecordKey"
47
+ });
48
+ }
49
+ };
50
+ }
51
+ });
52
+ //#endregion
53
+ export { noTransformInRecordKey };
@@ -54,6 +54,11 @@ function trackZodSchemaImports(importAllowedSource) {
54
54
  }
55
55
  /**
56
56
  * Wrapper to avoid duplication of importAllowedSource across rule code
57
+ *
58
+ * This function exposes:
59
+ *
60
+ * - `zodImportAllowedSource` - to be used in rules meta to properly generate documentation
61
+ * - `trackZodSchemaImports` - used inside a rule to process only zod related variables
57
62
  */
58
63
  function createZodSchemaImportTrack(zodImportAllowedSource) {
59
64
  return {
@@ -53,6 +53,11 @@ function trackZodSchemaImports(importAllowedSource) {
53
53
  }
54
54
  /**
55
55
  * Wrapper to avoid duplication of importAllowedSource across rule code
56
+ *
57
+ * This function exposes:
58
+ *
59
+ * - `zodImportAllowedSource` - to be used in rules meta to properly generate documentation
60
+ * - `trackZodSchemaImports` - used inside a rule to process only zod related variables
56
61
  */
57
62
  function createZodSchemaImportTrack(zodImportAllowedSource) {
58
63
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-zod",
3
- "version": "3.5.4",
3
+ "version": "3.7.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": {
@@ -27,6 +27,7 @@
27
27
  "eslint",
28
28
  "eslintplugin",
29
29
  "eslint-plugin",
30
+ "oxlint",
30
31
  "zod"
31
32
  ],
32
33
  "files": [
@@ -45,9 +46,16 @@
45
46
  },
46
47
  "peerDependencies": {
47
48
  "eslint": "^9 || ^10",
49
+ "oxlint": "^1.59.0",
48
50
  "zod": "^4"
49
51
  },
50
52
  "peerDependenciesMeta": {
53
+ "eslint": {
54
+ "optional": true
55
+ },
56
+ "oxlint": {
57
+ "optional": true
58
+ },
51
59
  "zod": {
52
60
  "optional": true
53
61
  }
@@ -83,8 +91,8 @@
83
91
  "lint:js:fix": "eslint . --fix",
84
92
  "lint:docs": "eslint-doc-generator --check",
85
93
  "lint:knip": "knip",
86
- "format": "prettier . --check",
87
- "format:fix": "prettier . --write",
94
+ "format": "prettier . --write",
95
+ "format:check": "prettier . --check",
88
96
  "test": "vitest",
89
97
  "test:dev": "vitest --typecheck=false",
90
98
  "release": "pnpm run build && changeset publish",