eslint-plugin-zod-mini 0.1.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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/dist/index.cjs +62 -0
  5. package/dist/index.d.cts +22 -0
  6. package/dist/index.d.mts +23 -0
  7. package/dist/index.mjs +62 -0
  8. package/dist/meta.cjs +12 -0
  9. package/dist/meta.mjs +9 -0
  10. package/dist/rules/consistent-import-source.cjs +61 -0
  11. package/dist/rules/consistent-import-source.mjs +60 -0
  12. package/dist/rules/consistent-import.cjs +153 -0
  13. package/dist/rules/consistent-import.mjs +152 -0
  14. package/dist/rules/consistent-object-schema-type.cjs +82 -0
  15. package/dist/rules/consistent-object-schema-type.mjs +81 -0
  16. package/dist/rules/consistent-schema-output-type-style.cjs +70 -0
  17. package/dist/rules/consistent-schema-output-type-style.mjs +69 -0
  18. package/dist/rules/consistent-schema-var-name.cjs +76 -0
  19. package/dist/rules/consistent-schema-var-name.mjs +75 -0
  20. package/dist/rules/no-any-schema.cjs +63 -0
  21. package/dist/rules/no-any-schema.mjs +62 -0
  22. package/dist/rules/no-empty-custom-schema.cjs +36 -0
  23. package/dist/rules/no-empty-custom-schema.mjs +36 -0
  24. package/dist/rules/no-unknown-schema.cjs +31 -0
  25. package/dist/rules/no-unknown-schema.mjs +31 -0
  26. package/dist/rules/prefer-meta.cjs +46 -0
  27. package/dist/rules/prefer-meta.mjs +45 -0
  28. package/dist/rules/prefer-namespace-import.cjs +117 -0
  29. package/dist/rules/prefer-namespace-import.mjs +116 -0
  30. package/dist/rules/require-brand-type-parameter.cjs +49 -0
  31. package/dist/rules/require-brand-type-parameter.mjs +49 -0
  32. package/dist/rules/require-error-message.cjs +85 -0
  33. package/dist/rules/require-error-message.mjs +84 -0
  34. package/dist/rules/require-schema-suffix.cjs +69 -0
  35. package/dist/rules/require-schema-suffix.mjs +68 -0
  36. package/dist/rules/schema-error-property-style.cjs +96 -0
  37. package/dist/rules/schema-error-property-style.mjs +94 -0
  38. package/dist/src/index.d.ts +21 -0
  39. package/dist/src/index.js +56 -0
  40. package/dist/src/meta.d.ts +3 -0
  41. package/dist/src/meta.js +8 -0
  42. package/dist/src/rules/consistent-import-source.d.ts +11 -0
  43. package/dist/src/rules/consistent-import-source.js +64 -0
  44. package/dist/src/rules/consistent-import-source.spec.d.ts +1 -0
  45. package/dist/src/rules/consistent-import-source.spec.js +103 -0
  46. package/dist/src/rules/consistent-import.d.ts +12 -0
  47. package/dist/src/rules/consistent-import.js +176 -0
  48. package/dist/src/rules/consistent-import.spec.d.ts +1 -0
  49. package/dist/src/rules/consistent-import.spec.js +194 -0
  50. package/dist/src/rules/consistent-object-schema-type.d.ts +13 -0
  51. package/dist/src/rules/consistent-object-schema-type.js +82 -0
  52. package/dist/src/rules/consistent-object-schema-type.spec.d.ts +1 -0
  53. package/dist/src/rules/consistent-object-schema-type.spec.js +138 -0
  54. package/dist/src/rules/consistent-schema-output-type-style.d.ts +12 -0
  55. package/dist/src/rules/consistent-schema-output-type-style.js +81 -0
  56. package/dist/src/rules/consistent-schema-output-type-style.spec.d.ts +1 -0
  57. package/dist/src/rules/consistent-schema-output-type-style.spec.js +128 -0
  58. package/dist/src/rules/consistent-schema-var-name.d.ts +10 -0
  59. package/dist/src/rules/consistent-schema-var-name.js +86 -0
  60. package/dist/src/rules/consistent-schema-var-name.spec.d.ts +1 -0
  61. package/dist/src/rules/consistent-schema-var-name.spec.js +130 -0
  62. package/dist/src/rules/no-any-schema.d.ts +6 -0
  63. package/dist/src/rules/no-any-schema.js +65 -0
  64. package/dist/src/rules/no-any-schema.spec.d.ts +1 -0
  65. package/dist/src/rules/no-any-schema.spec.js +155 -0
  66. package/dist/src/rules/no-empty-custom-schema.d.ts +5 -0
  67. package/dist/src/rules/no-empty-custom-schema.js +43 -0
  68. package/dist/src/rules/no-empty-custom-schema.spec.d.ts +1 -0
  69. package/dist/src/rules/no-empty-custom-schema.spec.js +97 -0
  70. package/dist/src/rules/no-unknown-schema.d.ts +5 -0
  71. package/dist/src/rules/no-unknown-schema.js +33 -0
  72. package/dist/src/rules/no-unknown-schema.spec.d.ts +1 -0
  73. package/dist/src/rules/no-unknown-schema.spec.js +75 -0
  74. package/dist/src/rules/prefer-meta.d.ts +5 -0
  75. package/dist/src/rules/prefer-meta.js +51 -0
  76. package/dist/src/rules/prefer-meta.spec.d.ts +1 -0
  77. package/dist/src/rules/prefer-meta.spec.js +118 -0
  78. package/dist/src/rules/prefer-namespace-import.d.ts +5 -0
  79. package/dist/src/rules/prefer-namespace-import.js +146 -0
  80. package/dist/src/rules/prefer-namespace-import.spec.d.ts +1 -0
  81. package/dist/src/rules/prefer-namespace-import.spec.js +100 -0
  82. package/dist/src/rules/require-brand-type-parameter.d.ts +6 -0
  83. package/dist/src/rules/require-brand-type-parameter.js +58 -0
  84. package/dist/src/rules/require-brand-type-parameter.spec.d.ts +1 -0
  85. package/dist/src/rules/require-brand-type-parameter.spec.js +157 -0
  86. package/dist/src/rules/require-error-message.d.ts +5 -0
  87. package/dist/src/rules/require-error-message.js +103 -0
  88. package/dist/src/rules/require-error-message.spec.d.ts +1 -0
  89. package/dist/src/rules/require-error-message.spec.js +161 -0
  90. package/dist/src/rules/require-schema-suffix.d.ts +9 -0
  91. package/dist/src/rules/require-schema-suffix.js +79 -0
  92. package/dist/src/rules/require-schema-suffix.spec.d.ts +1 -0
  93. package/dist/src/rules/require-schema-suffix.spec.js +131 -0
  94. package/dist/src/rules/schema-error-property-style.d.ts +10 -0
  95. package/dist/src/rules/schema-error-property-style.js +102 -0
  96. package/dist/src/rules/schema-error-property-style.spec.d.ts +1 -0
  97. package/dist/src/rules/schema-error-property-style.spec.js +134 -0
  98. package/dist/src/utils/create-plugin-rule.d.ts +8 -0
  99. package/dist/src/utils/create-plugin-rule.js +3 -0
  100. package/dist/tests/setup.d.ts +1 -0
  101. package/dist/tests/setup.js +12 -0
  102. package/dist/tsconfig.tsbuildinfo +1 -0
  103. package/dist/tsdown.config.d.ts +2 -0
  104. package/dist/tsdown.config.js +11 -0
  105. package/dist/utils/create-plugin-rule.cjs +6 -0
  106. package/dist/utils/create-plugin-rule.mjs +6 -0
  107. package/dist/vitest.config.d.ts +2 -0
  108. package/dist/vitest.config.js +7 -0
  109. package/package.json +80 -0
@@ -0,0 +1,146 @@
1
+ import { isZodImportSource } from '@eslint-zod/utils';
2
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
+ import { createZodMiniPluginRule } from '../utils/create-plugin-rule.js';
4
+ function isGroupFirstImportTypeNamespace(group) {
5
+ const { hasOnlyTypeImports, nodes: [firstImportNode], } = group;
6
+ const { specifiers } = firstImportNode;
7
+ if (specifiers.length === 1 &&
8
+ specifiers[0].type === AST_NODE_TYPES.ImportNamespaceSpecifier) {
9
+ if (hasOnlyTypeImports) {
10
+ if (firstImportNode.importKind === 'type') {
11
+ return true;
12
+ }
13
+ }
14
+ else {
15
+ return true;
16
+ }
17
+ }
18
+ return false;
19
+ }
20
+ function shouldIdentifierBeRenamed(node) {
21
+ if (node.parent.type === AST_NODE_TYPES.ImportSpecifier) {
22
+ return false;
23
+ }
24
+ if (node.parent.type === AST_NODE_TYPES.MemberExpression &&
25
+ node.parent.object.type === AST_NODE_TYPES.Identifier &&
26
+ node.parent.object.name !== node.name) {
27
+ return false;
28
+ }
29
+ return true;
30
+ }
31
+ function getNamespaceAliasNameFrom(node) {
32
+ if (node.type === AST_NODE_TYPES.ImportDefaultSpecifier ||
33
+ node.type === AST_NODE_TYPES.ImportNamespaceSpecifier) {
34
+ return node.local.name;
35
+ }
36
+ if (node.imported.type === AST_NODE_TYPES.Identifier &&
37
+ node.imported.name === 'z') {
38
+ return node.local.name;
39
+ }
40
+ return null;
41
+ }
42
+ export const preferNamespaceImport = createZodMiniPluginRule({
43
+ name: 'prefer-namespace-import',
44
+ meta: {
45
+ type: 'suggestion',
46
+ deprecated: {
47
+ message: "Use `zod-mini/consistent-import` with `{ syntax: 'namespace' }`",
48
+ },
49
+ docs: {
50
+ zodImportAllowedSource: 'zod-mini',
51
+ description: "Enforce importing zod/mini as a namespace import (`import * as z from 'zod/mini'`)",
52
+ },
53
+ fixable: 'code',
54
+ messages: {
55
+ useNamespace: 'Import Zod Mini with a namespace import',
56
+ removeDuplicate: 'Zod Mini is already imported via namespace import',
57
+ convertUsage: 'Convert to namespace usage',
58
+ },
59
+ schema: [],
60
+ },
61
+ defaultOptions: [],
62
+ create(context) {
63
+ const { sourceCode } = context;
64
+ const importGroups = {};
65
+ return {
66
+ ImportDeclaration(node) {
67
+ const { source, importKind } = node;
68
+ if (!isZodImportSource(source.value, 'zod-mini')) {
69
+ return;
70
+ }
71
+ if (!importGroups[source.value]) {
72
+ importGroups[source.value] = {
73
+ hasOnlyTypeImports: true,
74
+ nodes: [],
75
+ };
76
+ }
77
+ if (importGroups[source.value].hasOnlyTypeImports &&
78
+ importKind === 'value') {
79
+ importGroups[source.value].hasOnlyTypeImports = false;
80
+ }
81
+ importGroups[source.value].nodes.push(node);
82
+ },
83
+ 'Program:exit': function () {
84
+ let namespaceAliasNameIndex = 0;
85
+ for (const importGroup of Object.values(importGroups)) {
86
+ const { hasOnlyTypeImports, nodes } = importGroup;
87
+ const [firstImportNode, ...othersImportNodes] = nodes;
88
+ const nodesWithVariablesToUpdate = [];
89
+ let namespaceAliasName = null;
90
+ for (const specifier of nodes.flatMap((it) => it.specifiers)) {
91
+ if (!namespaceAliasName) {
92
+ namespaceAliasName = getNamespaceAliasNameFrom(specifier);
93
+ if (namespaceAliasName) {
94
+ continue;
95
+ }
96
+ }
97
+ nodesWithVariablesToUpdate.push(specifier);
98
+ }
99
+ if (!namespaceAliasName) {
100
+ namespaceAliasName = 'z';
101
+ if (namespaceAliasNameIndex > 0) {
102
+ namespaceAliasName = `z${namespaceAliasNameIndex}`;
103
+ namespaceAliasNameIndex += 1;
104
+ }
105
+ }
106
+ const isFirstImportValid = isGroupFirstImportTypeNamespace(importGroup);
107
+ if (!isFirstImportValid) {
108
+ context.report({
109
+ node: firstImportNode,
110
+ messageId: 'useNamespace',
111
+ fix(fixer) {
112
+ const importTypeKeyword = hasOnlyTypeImports ? 'type ' : '';
113
+ const newImportText = `import ${importTypeKeyword}* as ${namespaceAliasName} from ${firstImportNode.source.raw};`;
114
+ return fixer.replaceText(firstImportNode, newImportText);
115
+ },
116
+ });
117
+ }
118
+ const allVariables = nodesWithVariablesToUpdate.flatMap((it) => sourceCode.getDeclaredVariables(it));
119
+ const allReferences = allVariables.flatMap((it) => it.references);
120
+ for (const ref of allReferences) {
121
+ const { identifier } = ref;
122
+ if (shouldIdentifierBeRenamed(identifier)) {
123
+ context.report({
124
+ node: identifier,
125
+ messageId: 'convertUsage',
126
+ fix(fixer) {
127
+ const newId = `${namespaceAliasName}.${identifier.name}`;
128
+ return fixer.replaceText(identifier, newId);
129
+ },
130
+ });
131
+ }
132
+ }
133
+ for (const extraImport of othersImportNodes) {
134
+ context.report({
135
+ node: extraImport,
136
+ messageId: 'removeDuplicate',
137
+ fix(fixer) {
138
+ return fixer.removeRange(extraImport.range);
139
+ },
140
+ });
141
+ }
142
+ }
143
+ },
144
+ };
145
+ },
146
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,100 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+ import dedent from 'dedent';
3
+ import { preferNamespaceImport } from './prefer-namespace-import.js';
4
+ const ruleTester = new RuleTester();
5
+ ruleTester.run(preferNamespaceImport.name, preferNamespaceImport, {
6
+ valid: [
7
+ {
8
+ name: 'namespace import',
9
+ code: 'import * as z from "zod/mini";',
10
+ },
11
+ {
12
+ name: 'aliased namespace import',
13
+ code: 'import * as myZod from "zod/mini";',
14
+ },
15
+ {
16
+ name: 'type namespace import',
17
+ code: 'import type * as myZod from "zod/mini";',
18
+ },
19
+ {
20
+ name: 'unrelated imports',
21
+ code: 'import { useState } from "react";',
22
+ },
23
+ {
24
+ name: 'zod/v4-mini is valid',
25
+ code: 'import * as z from "zod/v4-mini";',
26
+ },
27
+ {
28
+ name: 'not triggered on zod import',
29
+ code: 'import { z } from "zod";',
30
+ },
31
+ ],
32
+ invalid: [
33
+ {
34
+ name: 'named import',
35
+ code: dedent `
36
+ import { z } from "zod/mini";
37
+ const userSchema = z.string();
38
+ `,
39
+ errors: [{ messageId: 'useNamespace' }],
40
+ output: dedent `
41
+ import * as z from "zod/mini";
42
+ const userSchema = z.string();
43
+ `,
44
+ },
45
+ {
46
+ name: 'default import',
47
+ code: 'import z from "zod/mini";',
48
+ errors: [{ messageId: 'useNamespace' }],
49
+ output: 'import * as z from "zod/mini";',
50
+ },
51
+ {
52
+ name: 'multiple named imports',
53
+ code: 'import { object, string } from "zod/mini";',
54
+ errors: [{ messageId: 'useNamespace' }],
55
+ output: 'import * as z from "zod/mini";',
56
+ },
57
+ {
58
+ name: 'named type import',
59
+ code: 'import type { z } from "zod/mini";',
60
+ errors: [{ messageId: 'useNamespace' }],
61
+ output: 'import type * as z from "zod/mini";',
62
+ },
63
+ {
64
+ name: 'zod/v4-mini default import',
65
+ code: 'import z from "zod/v4-mini";',
66
+ errors: [{ messageId: 'useNamespace' }],
67
+ output: 'import * as z from "zod/v4-mini";',
68
+ },
69
+ {
70
+ name: 'named imports (one value and one type)',
71
+ code: dedent `
72
+ import { z } from "zod/mini";
73
+ import type { $ZodType } from "zod/mini";
74
+ const userSchema: $ZodType = z.string();
75
+ `,
76
+ errors: [
77
+ { messageId: 'useNamespace', line: 1 },
78
+ { messageId: 'removeDuplicate', line: 2 },
79
+ { messageId: 'convertUsage', line: 3 },
80
+ ],
81
+ output: dedent `
82
+ import * as z from "zod/mini";
83
+
84
+ const userSchema: z.$ZodType = z.string();
85
+ `,
86
+ },
87
+ {
88
+ name: 'keeps name from named z import',
89
+ code: dedent `
90
+ import { z as myZod } from "zod/mini";
91
+ const userSchema = myZod.string();
92
+ `,
93
+ errors: [{ messageId: 'useNamespace', line: 1 }],
94
+ output: dedent `
95
+ import * as myZod from "zod/mini";
96
+ const userSchema = myZod.string();
97
+ `,
98
+ },
99
+ ],
100
+ });
@@ -0,0 +1,6 @@
1
+ import type { TSESLint } from '@typescript-eslint/utils';
2
+ export declare const requireBrandTypeParameter: TSESLint.RuleModule<"missingTypeParameter" | "removeBrandFunction", [], {
3
+ zodImportAllowedSource: "all" | "zod" | "zod-mini";
4
+ }, TSESLint.RuleListener> & {
5
+ name: string;
6
+ };
@@ -0,0 +1,58 @@
1
+ import { createZodSchemaImportTrack } from '@eslint-zod/utils';
2
+ import { createZodMiniPluginRule } from '../utils/create-plugin-rule.js';
3
+ const { zodImportAllowedSource, trackZodSchemaImports, } = createZodSchemaImportTrack('zod-mini');
4
+ export const requireBrandTypeParameter = createZodMiniPluginRule({
5
+ name: 'require-brand-type-parameter',
6
+ meta: {
7
+ hasSuggestions: true,
8
+ type: 'problem',
9
+ docs: {
10
+ zodImportAllowedSource,
11
+ description: 'Require type parameter on `.brand()` functions',
12
+ },
13
+ messages: {
14
+ missingTypeParameter: 'Type parameter is required when using `.brand()`',
15
+ removeBrandFunction: 'Brand is a static-only construct. If not parameter is required consider removal',
16
+ },
17
+ schema: [],
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods, } = trackZodSchemaImports();
22
+ return {
23
+ ImportDeclaration: importDeclarationListener,
24
+ CallExpression(node) {
25
+ const zodSchemaMeta = detectZodSchemaRootNode(node);
26
+ if (!zodSchemaMeta) {
27
+ return;
28
+ }
29
+ const methods = collectZodChainMethods(zodSchemaMeta.node);
30
+ const brandMethod = methods.find((it) => it.name === 'brand');
31
+ if (!brandMethod) {
32
+ return;
33
+ }
34
+ const brandNode = brandMethod.node;
35
+ const { typeArguments } = brandNode;
36
+ if (typeArguments && typeArguments.params.length > 0) {
37
+ return;
38
+ }
39
+ const brandCalleeNode = brandNode.callee;
40
+ context.report({
41
+ messageId: 'missingTypeParameter',
42
+ node: brandCalleeNode.property,
43
+ suggest: [
44
+ {
45
+ messageId: 'removeBrandFunction',
46
+ fix(fixer) {
47
+ return fixer.removeRange([
48
+ brandCalleeNode.object.range[1],
49
+ brandNode.range[1],
50
+ ]);
51
+ },
52
+ },
53
+ ],
54
+ });
55
+ },
56
+ };
57
+ },
58
+ });
@@ -0,0 +1,157 @@
1
+ import { RuleTester } from '@typescript-eslint/rule-tester';
2
+ import dedent from 'dedent';
3
+ import { requireBrandTypeParameter } from './require-brand-type-parameter.js';
4
+ const ruleTester = new RuleTester();
5
+ ruleTester.run(requireBrandTypeParameter.name, requireBrandTypeParameter, {
6
+ valid: [
7
+ {
8
+ name: 'namespace with type parameter',
9
+ code: dedent `
10
+ import * as z from 'zod/mini'
11
+ z.string().brand<"id">();
12
+ `,
13
+ },
14
+ {
15
+ name: 'named import with type parameter',
16
+ code: dedent `
17
+ import { string } from 'zod/mini'
18
+ string().brand<"id">();
19
+ `,
20
+ },
21
+ {
22
+ name: 'named z import with type parameter',
23
+ code: dedent `
24
+ import { z } from 'zod/mini'
25
+ z.string().brand<"id">();
26
+ `,
27
+ },
28
+ {
29
+ name: 'no error on other brand function',
30
+ code: dedent `
31
+ import * as z from 'zod/mini'
32
+ another.brand();
33
+ `,
34
+ },
35
+ {
36
+ name: 'not triggered on zod import',
37
+ code: dedent `
38
+ import * as z from 'zod';
39
+ z.string().brand();
40
+ `,
41
+ },
42
+ {
43
+ name: 'complex chain',
44
+ code: dedent `
45
+ import * as z from 'zod/mini'
46
+ z.string().check(z.min(1), z.max(10)).brand<"email">();
47
+ `,
48
+ },
49
+ ],
50
+ invalid: [
51
+ {
52
+ name: 'namespace import',
53
+ code: dedent `
54
+ import * as z from 'zod/mini';
55
+ z.string().brand();
56
+ `,
57
+ errors: [
58
+ {
59
+ messageId: 'missingTypeParameter',
60
+ suggestions: [
61
+ {
62
+ messageId: 'removeBrandFunction',
63
+ output: dedent `
64
+ import * as z from 'zod/mini';
65
+ z.string();
66
+ `,
67
+ },
68
+ ],
69
+ },
70
+ ],
71
+ },
72
+ {
73
+ name: 'named import',
74
+ code: dedent `
75
+ import { string } from 'zod/mini';
76
+ string().brand();
77
+ `,
78
+ errors: [
79
+ {
80
+ messageId: 'missingTypeParameter',
81
+ suggestions: [
82
+ {
83
+ messageId: 'removeBrandFunction',
84
+ output: dedent `
85
+ import { string } from 'zod/mini';
86
+ string();
87
+ `,
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ },
93
+ {
94
+ name: 'named z import',
95
+ code: dedent `
96
+ import { z } from 'zod/mini';
97
+ z.string().brand();
98
+ `,
99
+ errors: [
100
+ {
101
+ messageId: 'missingTypeParameter',
102
+ suggestions: [
103
+ {
104
+ messageId: 'removeBrandFunction',
105
+ output: dedent `
106
+ import { z } from 'zod/mini';
107
+ z.string();
108
+ `,
109
+ },
110
+ ],
111
+ },
112
+ ],
113
+ },
114
+ {
115
+ name: 'zod/v4-mini import',
116
+ code: dedent `
117
+ import * as z from 'zod/v4-mini';
118
+ z.string().brand();
119
+ `,
120
+ errors: [
121
+ {
122
+ messageId: 'missingTypeParameter',
123
+ suggestions: [
124
+ {
125
+ messageId: 'removeBrandFunction',
126
+ output: dedent `
127
+ import * as z from 'zod/v4-mini';
128
+ z.string();
129
+ `,
130
+ },
131
+ ],
132
+ },
133
+ ],
134
+ },
135
+ {
136
+ name: 'complex chain without type parameter',
137
+ code: dedent `
138
+ import * as z from 'zod/mini';
139
+ z.string().check(z.min(1)).brand()
140
+ `,
141
+ errors: [
142
+ {
143
+ messageId: 'missingTypeParameter',
144
+ suggestions: [
145
+ {
146
+ messageId: 'removeBrandFunction',
147
+ output: dedent `
148
+ import * as z from 'zod/mini';
149
+ z.string().check(z.min(1))
150
+ `,
151
+ },
152
+ ],
153
+ },
154
+ ],
155
+ },
156
+ ],
157
+ });
@@ -0,0 +1,5 @@
1
+ export declare const requireErrorMessage: import("@typescript-eslint/utils/ts-eslint").RuleModule<"requireErrorMessage" | "preferError" | "removeMessage", [], {
2
+ zodImportAllowedSource: "all" | "zod" | "zod-mini";
3
+ }, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
4
+ name: string;
5
+ };
@@ -0,0 +1,103 @@
1
+ import { createZodSchemaImportTrack } from '@eslint-zod/utils';
2
+ import { AST_NODE_TYPES } from '@typescript-eslint/utils';
3
+ import { createZodMiniPluginRule } from '../utils/create-plugin-rule.js';
4
+ const { zodImportAllowedSource, trackZodSchemaImports, } = createZodSchemaImportTrack('zod-mini');
5
+ export const requireErrorMessage = createZodMiniPluginRule({
6
+ name: 'require-error-message',
7
+ meta: {
8
+ type: 'suggestion',
9
+ fixable: 'code',
10
+ docs: {
11
+ zodImportAllowedSource,
12
+ description: 'Enforce that custom refinements include an error message',
13
+ },
14
+ messages: {
15
+ requireErrorMessage: 'Custom refinements must include an error message',
16
+ preferError: 'Use the "error" property instead of the deprecated "message" property',
17
+ removeMessage: 'The "message" property is deprecated; use "error"',
18
+ },
19
+ schema: [],
20
+ },
21
+ defaultOptions: [],
22
+ create(context) {
23
+ const { importDeclarationListener, detectZodSchemaRootNode, collectZodChainMethods, } = trackZodSchemaImports();
24
+ return {
25
+ ImportDeclaration: importDeclarationListener,
26
+ CallExpression(node) {
27
+ const zodSchemaMeta = detectZodSchemaRootNode(node);
28
+ if (!zodSchemaMeta) {
29
+ return;
30
+ }
31
+ const refines = collectZodChainMethods(node).filter((it) => it.name === 'refine' || it.name === 'custom');
32
+ if (refines.length === 0) {
33
+ return;
34
+ }
35
+ for (const refine of refines) {
36
+ const refineNode = refine.node;
37
+ if (refineNode.arguments.length < 2) {
38
+ context.report({
39
+ messageId: 'requireErrorMessage',
40
+ node: refineNode,
41
+ });
42
+ continue;
43
+ }
44
+ const [, params] = refineNode.arguments;
45
+ if (params.type === AST_NODE_TYPES.Literal) {
46
+ continue;
47
+ }
48
+ if (params.type !== AST_NODE_TYPES.ObjectExpression) {
49
+ continue;
50
+ }
51
+ let errorPropertyNode;
52
+ let messagePropertyNode;
53
+ for (const property of params.properties) {
54
+ if (property.type === AST_NODE_TYPES.Property &&
55
+ property.key.type === AST_NODE_TYPES.Identifier) {
56
+ if (property.key.name === 'error') {
57
+ errorPropertyNode = property;
58
+ }
59
+ if (property.key.name === 'message') {
60
+ messagePropertyNode = property;
61
+ }
62
+ if (errorPropertyNode && messagePropertyNode) {
63
+ break;
64
+ }
65
+ }
66
+ }
67
+ if (errorPropertyNode && messagePropertyNode) {
68
+ context.report({
69
+ messageId: 'removeMessage',
70
+ node: messagePropertyNode,
71
+ fix(fixer) {
72
+ const { sourceCode } = context;
73
+ const nextToken = sourceCode.getTokenAfter(messagePropertyNode);
74
+ let [, end] = messagePropertyNode.range;
75
+ if ((nextToken === null || nextToken === void 0 ? void 0 : nextToken.value) === ',') {
76
+ end = nextToken.range[1];
77
+ }
78
+ return fixer.removeRange([messagePropertyNode.range[0], end]);
79
+ },
80
+ });
81
+ continue;
82
+ }
83
+ if (messagePropertyNode && !errorPropertyNode) {
84
+ context.report({
85
+ messageId: 'preferError',
86
+ node: params,
87
+ fix(fixer) {
88
+ return fixer.replaceTextRange(messagePropertyNode.key.range, 'error');
89
+ },
90
+ });
91
+ continue;
92
+ }
93
+ if (!errorPropertyNode) {
94
+ context.report({
95
+ messageId: 'requireErrorMessage',
96
+ node: params,
97
+ });
98
+ }
99
+ }
100
+ },
101
+ };
102
+ },
103
+ });
@@ -0,0 +1 @@
1
+ export {};