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 +11 -10
- package/dist/index.cjs +2 -0
- package/dist/index.mjs +2 -0
- package/dist/rules/no-transform-in-record-key.cjs +54 -0
- package/dist/rules/no-transform-in-record-key.mjs +53 -0
- package/dist/utils/track-zod-schema-imports.cjs +5 -0
- package/dist/utils/track-zod-schema-imports.mjs +5 -0
- package/package.json +3 -3
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
|
-
| [
|
|
63
|
-
| [prefer-
|
|
64
|
-
| [prefer-
|
|
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.
|
|
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 . --
|
|
87
|
-
"format:
|
|
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",
|