eslint-plugin-zod 3.5.4 → 3.6.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
@@ -52,16 +52,17 @@ This plugin is primarily built for `zod`, so some rules are exclusive to `zod` a
52
52
 
53
53
  ### `zod` exclusive rules
54
54
 
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 | ✅ | 🔧 | | |
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
+ | [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 | | | | |
63
+ | [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. | ✅ | 🔧 | | |
64
+ | [prefer-meta-last](docs/rules/prefer-meta-last.md) | Enforce `.meta()` as last method | ✅ | 🔧 | | |
65
+ | [prefer-string-schema-with-trim](docs/rules/prefer-string-schema-with-trim.md) | Enforce `z.string().trim()` to prevent accidental leading/trailing whitespace | ✅ | 🔧 | | |
65
66
 
66
67
  <!-- end auto-generated rules list -->
67
68
 
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.6.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": {
@@ -83,8 +83,8 @@
83
83
  "lint:js:fix": "eslint . --fix",
84
84
  "lint:docs": "eslint-doc-generator --check",
85
85
  "lint:knip": "knip",
86
- "format": "prettier . --check",
87
- "format:fix": "prettier . --write",
86
+ "format": "prettier . --write",
87
+ "format:check": "prettier . --check",
88
88
  "test": "vitest",
89
89
  "test:dev": "vitest --typecheck=false",
90
90
  "release": "pnpm run build && changeset publish",