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.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/index.cjs +62 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.mts +23 -0
- package/dist/index.mjs +62 -0
- package/dist/meta.cjs +12 -0
- package/dist/meta.mjs +9 -0
- package/dist/rules/consistent-import-source.cjs +61 -0
- package/dist/rules/consistent-import-source.mjs +60 -0
- package/dist/rules/consistent-import.cjs +153 -0
- package/dist/rules/consistent-import.mjs +152 -0
- package/dist/rules/consistent-object-schema-type.cjs +82 -0
- package/dist/rules/consistent-object-schema-type.mjs +81 -0
- package/dist/rules/consistent-schema-output-type-style.cjs +70 -0
- package/dist/rules/consistent-schema-output-type-style.mjs +69 -0
- package/dist/rules/consistent-schema-var-name.cjs +76 -0
- package/dist/rules/consistent-schema-var-name.mjs +75 -0
- package/dist/rules/no-any-schema.cjs +63 -0
- package/dist/rules/no-any-schema.mjs +62 -0
- package/dist/rules/no-empty-custom-schema.cjs +36 -0
- package/dist/rules/no-empty-custom-schema.mjs +36 -0
- package/dist/rules/no-unknown-schema.cjs +31 -0
- package/dist/rules/no-unknown-schema.mjs +31 -0
- package/dist/rules/prefer-meta.cjs +46 -0
- package/dist/rules/prefer-meta.mjs +45 -0
- package/dist/rules/prefer-namespace-import.cjs +117 -0
- package/dist/rules/prefer-namespace-import.mjs +116 -0
- package/dist/rules/require-brand-type-parameter.cjs +49 -0
- package/dist/rules/require-brand-type-parameter.mjs +49 -0
- package/dist/rules/require-error-message.cjs +85 -0
- package/dist/rules/require-error-message.mjs +84 -0
- package/dist/rules/require-schema-suffix.cjs +69 -0
- package/dist/rules/require-schema-suffix.mjs +68 -0
- package/dist/rules/schema-error-property-style.cjs +96 -0
- package/dist/rules/schema-error-property-style.mjs +94 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +56 -0
- package/dist/src/meta.d.ts +3 -0
- package/dist/src/meta.js +8 -0
- package/dist/src/rules/consistent-import-source.d.ts +11 -0
- package/dist/src/rules/consistent-import-source.js +64 -0
- package/dist/src/rules/consistent-import-source.spec.d.ts +1 -0
- package/dist/src/rules/consistent-import-source.spec.js +103 -0
- package/dist/src/rules/consistent-import.d.ts +12 -0
- package/dist/src/rules/consistent-import.js +176 -0
- package/dist/src/rules/consistent-import.spec.d.ts +1 -0
- package/dist/src/rules/consistent-import.spec.js +194 -0
- package/dist/src/rules/consistent-object-schema-type.d.ts +13 -0
- package/dist/src/rules/consistent-object-schema-type.js +82 -0
- package/dist/src/rules/consistent-object-schema-type.spec.d.ts +1 -0
- package/dist/src/rules/consistent-object-schema-type.spec.js +138 -0
- package/dist/src/rules/consistent-schema-output-type-style.d.ts +12 -0
- package/dist/src/rules/consistent-schema-output-type-style.js +81 -0
- package/dist/src/rules/consistent-schema-output-type-style.spec.d.ts +1 -0
- package/dist/src/rules/consistent-schema-output-type-style.spec.js +128 -0
- package/dist/src/rules/consistent-schema-var-name.d.ts +10 -0
- package/dist/src/rules/consistent-schema-var-name.js +86 -0
- package/dist/src/rules/consistent-schema-var-name.spec.d.ts +1 -0
- package/dist/src/rules/consistent-schema-var-name.spec.js +130 -0
- package/dist/src/rules/no-any-schema.d.ts +6 -0
- package/dist/src/rules/no-any-schema.js +65 -0
- package/dist/src/rules/no-any-schema.spec.d.ts +1 -0
- package/dist/src/rules/no-any-schema.spec.js +155 -0
- package/dist/src/rules/no-empty-custom-schema.d.ts +5 -0
- package/dist/src/rules/no-empty-custom-schema.js +43 -0
- package/dist/src/rules/no-empty-custom-schema.spec.d.ts +1 -0
- package/dist/src/rules/no-empty-custom-schema.spec.js +97 -0
- package/dist/src/rules/no-unknown-schema.d.ts +5 -0
- package/dist/src/rules/no-unknown-schema.js +33 -0
- package/dist/src/rules/no-unknown-schema.spec.d.ts +1 -0
- package/dist/src/rules/no-unknown-schema.spec.js +75 -0
- package/dist/src/rules/prefer-meta.d.ts +5 -0
- package/dist/src/rules/prefer-meta.js +51 -0
- package/dist/src/rules/prefer-meta.spec.d.ts +1 -0
- package/dist/src/rules/prefer-meta.spec.js +118 -0
- package/dist/src/rules/prefer-namespace-import.d.ts +5 -0
- package/dist/src/rules/prefer-namespace-import.js +146 -0
- package/dist/src/rules/prefer-namespace-import.spec.d.ts +1 -0
- package/dist/src/rules/prefer-namespace-import.spec.js +100 -0
- package/dist/src/rules/require-brand-type-parameter.d.ts +6 -0
- package/dist/src/rules/require-brand-type-parameter.js +58 -0
- package/dist/src/rules/require-brand-type-parameter.spec.d.ts +1 -0
- package/dist/src/rules/require-brand-type-parameter.spec.js +157 -0
- package/dist/src/rules/require-error-message.d.ts +5 -0
- package/dist/src/rules/require-error-message.js +103 -0
- package/dist/src/rules/require-error-message.spec.d.ts +1 -0
- package/dist/src/rules/require-error-message.spec.js +161 -0
- package/dist/src/rules/require-schema-suffix.d.ts +9 -0
- package/dist/src/rules/require-schema-suffix.js +79 -0
- package/dist/src/rules/require-schema-suffix.spec.d.ts +1 -0
- package/dist/src/rules/require-schema-suffix.spec.js +131 -0
- package/dist/src/rules/schema-error-property-style.d.ts +10 -0
- package/dist/src/rules/schema-error-property-style.js +102 -0
- package/dist/src/rules/schema-error-property-style.spec.d.ts +1 -0
- package/dist/src/rules/schema-error-property-style.spec.js +134 -0
- package/dist/src/utils/create-plugin-rule.d.ts +8 -0
- package/dist/src/utils/create-plugin-rule.js +3 -0
- package/dist/tests/setup.d.ts +1 -0
- package/dist/tests/setup.js +12 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsdown.config.d.ts +2 -0
- package/dist/tsdown.config.js +11 -0
- package/dist/utils/create-plugin-rule.cjs +6 -0
- package/dist/utils/create-plugin-rule.mjs +6 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +7 -0
- package/package.json +80 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
+
import { consistentImportSource } from './consistent-import-source.js';
|
|
3
|
+
const ruleTester = new RuleTester();
|
|
4
|
+
ruleTester.run(consistentImportSource.name, consistentImportSource, {
|
|
5
|
+
valid: [
|
|
6
|
+
{ code: 'import * as z from "zod/mini"' },
|
|
7
|
+
{ code: 'import { z } from "zod/mini"' },
|
|
8
|
+
{ code: 'import z from "zod/mini"' },
|
|
9
|
+
{
|
|
10
|
+
code: 'import z from "zod/mini"',
|
|
11
|
+
options: [{ sources: ['zod/mini'] }],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
code: 'import z from "zod/v4-mini"',
|
|
15
|
+
options: [{ sources: ['zod/v4-mini'] }],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
code: 'import z from "zod"',
|
|
19
|
+
options: [{ sources: ['zod'] }],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'non-zod import is ignored',
|
|
23
|
+
code: 'import z from "react"',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
invalid: [
|
|
27
|
+
{
|
|
28
|
+
name: 'zod not allowed by default',
|
|
29
|
+
code: 'import * as z from "zod"',
|
|
30
|
+
errors: [
|
|
31
|
+
{
|
|
32
|
+
messageId: 'sourceNotAllowed',
|
|
33
|
+
data: { source: 'zod', sources: '"zod/mini"' },
|
|
34
|
+
suggestions: [
|
|
35
|
+
{
|
|
36
|
+
messageId: 'replaceSource',
|
|
37
|
+
data: { valid: 'zod/mini', invalid: 'zod' },
|
|
38
|
+
output: 'import * as z from "zod/mini"',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'zod/v4 not allowed by default',
|
|
46
|
+
code: 'import z from "zod/v4"',
|
|
47
|
+
errors: [
|
|
48
|
+
{
|
|
49
|
+
messageId: 'sourceNotAllowed',
|
|
50
|
+
data: { source: 'zod/v4', sources: '"zod/mini"' },
|
|
51
|
+
suggestions: [
|
|
52
|
+
{
|
|
53
|
+
messageId: 'replaceSource',
|
|
54
|
+
data: { valid: 'zod/mini', invalid: 'zod/v4' },
|
|
55
|
+
output: 'import z from "zod/mini"',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'should keep quote style',
|
|
63
|
+
code: "import z from 'zod/v4-mini'",
|
|
64
|
+
options: [{ sources: ['zod/mini'] }],
|
|
65
|
+
errors: [
|
|
66
|
+
{
|
|
67
|
+
messageId: 'sourceNotAllowed',
|
|
68
|
+
data: { source: 'zod/v4-mini', sources: '"zod/mini"' },
|
|
69
|
+
suggestions: [
|
|
70
|
+
{
|
|
71
|
+
messageId: 'replaceSource',
|
|
72
|
+
data: { valid: 'zod/mini', invalid: 'zod/v4-mini' },
|
|
73
|
+
output: "import z from 'zod/mini'",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'multiple sources, multiple suggestions',
|
|
81
|
+
code: 'import z from "zod"',
|
|
82
|
+
options: [{ sources: ['zod/mini', 'zod/v4-mini'] }],
|
|
83
|
+
errors: [
|
|
84
|
+
{
|
|
85
|
+
messageId: 'sourceNotAllowed',
|
|
86
|
+
data: { source: 'zod', sources: '"zod/mini", "zod/v4-mini"' },
|
|
87
|
+
suggestions: [
|
|
88
|
+
{
|
|
89
|
+
messageId: 'replaceSource',
|
|
90
|
+
data: { valid: 'zod/mini', invalid: 'zod' },
|
|
91
|
+
output: 'import z from "zod/mini"',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
messageId: 'replaceSource',
|
|
95
|
+
data: { valid: 'zod/v4-mini', invalid: 'zod' },
|
|
96
|
+
output: 'import z from "zod/v4-mini"',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare const IMPORT_SYNTAXES: readonly ["namespace", "named"];
|
|
2
|
+
type ImportSyntax = (typeof IMPORT_SYNTAXES)[number];
|
|
3
|
+
interface Options {
|
|
4
|
+
syntax: ImportSyntax;
|
|
5
|
+
}
|
|
6
|
+
type MessageIds = 'changeImportSyntax' | 'removeDuplicate' | 'convertUsage';
|
|
7
|
+
export declare const consistentImport: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, [Options], {
|
|
8
|
+
zodImportAllowedSource: "all" | "zod" | "zod-mini";
|
|
9
|
+
}, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
const IMPORT_SYNTAXES = ['namespace', 'named'];
|
|
5
|
+
function isGroupFirstImportKindValidForSyntax(group, syntax) {
|
|
6
|
+
const { hasOnlyTypeImports, nodes } = group;
|
|
7
|
+
const [firstImportNode] = nodes;
|
|
8
|
+
const { specifiers, importKind } = firstImportNode;
|
|
9
|
+
if (specifiers.length !== 1) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const [specifier] = specifiers;
|
|
13
|
+
const isValidForSyntax = syntax === 'named'
|
|
14
|
+
? specifier.type === AST_NODE_TYPES.ImportSpecifier &&
|
|
15
|
+
specifier.imported.type === AST_NODE_TYPES.Identifier &&
|
|
16
|
+
specifier.imported.name === 'z'
|
|
17
|
+
: specifier.type === AST_NODE_TYPES.ImportNamespaceSpecifier;
|
|
18
|
+
if (!isValidForSyntax) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (hasOnlyTypeImports) {
|
|
22
|
+
return importKind === 'type';
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function shouldIdentifierBeRenamed(node) {
|
|
27
|
+
if (node.parent.type === AST_NODE_TYPES.ImportSpecifier) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
if (node.parent.type === AST_NODE_TYPES.MemberExpression &&
|
|
31
|
+
node.parent.object.type === AST_NODE_TYPES.Identifier &&
|
|
32
|
+
node.parent.object.name !== node.name) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
function getNamespaceAliasNameFrom(node) {
|
|
38
|
+
if (node.type === AST_NODE_TYPES.ImportDefaultSpecifier ||
|
|
39
|
+
node.type === AST_NODE_TYPES.ImportNamespaceSpecifier) {
|
|
40
|
+
return node.local.name;
|
|
41
|
+
}
|
|
42
|
+
if (node.imported.type === AST_NODE_TYPES.Identifier &&
|
|
43
|
+
node.imported.name === 'z') {
|
|
44
|
+
return node.local.name;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
export const consistentImport = createZodMiniPluginRule({
|
|
49
|
+
name: 'consistent-import',
|
|
50
|
+
meta: {
|
|
51
|
+
type: 'problem',
|
|
52
|
+
docs: {
|
|
53
|
+
zodImportAllowedSource: 'zod-mini',
|
|
54
|
+
description: 'Enforce a consistent import style for Zod Mini',
|
|
55
|
+
},
|
|
56
|
+
fixable: 'code',
|
|
57
|
+
messages: {
|
|
58
|
+
changeImportSyntax: 'Use a {{syntax}} import for Zod Mini.',
|
|
59
|
+
removeDuplicate: 'Remove duplicate Zod Mini import; Zod Mini is already imported.',
|
|
60
|
+
convertUsage: 'Update Zod Mini usage to match the {{syntax}} import syntax.',
|
|
61
|
+
},
|
|
62
|
+
schema: [
|
|
63
|
+
{
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
syntax: {
|
|
67
|
+
description: 'Specifies the import syntax to use for Zod Mini.',
|
|
68
|
+
type: 'string',
|
|
69
|
+
enum: IMPORT_SYNTAXES,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
additionalProperties: false,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
defaultOptions: [{ syntax: 'namespace' }],
|
|
77
|
+
create(context, [options]) {
|
|
78
|
+
const { syntax } = options;
|
|
79
|
+
const { sourceCode } = context;
|
|
80
|
+
const importGroups = {};
|
|
81
|
+
return {
|
|
82
|
+
ImportDeclaration(node) {
|
|
83
|
+
const { source, importKind } = node;
|
|
84
|
+
if (!isZodImportSource(source.value, 'zod-mini')) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!importGroups[source.value]) {
|
|
88
|
+
importGroups[source.value] = {
|
|
89
|
+
hasOnlyTypeImports: true,
|
|
90
|
+
nodes: [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (importGroups[source.value].hasOnlyTypeImports &&
|
|
94
|
+
importKind === 'value') {
|
|
95
|
+
importGroups[source.value].hasOnlyTypeImports = false;
|
|
96
|
+
}
|
|
97
|
+
importGroups[source.value].nodes.push(node);
|
|
98
|
+
},
|
|
99
|
+
'Program:exit': function () {
|
|
100
|
+
let namespaceAliasNameIndex = 0;
|
|
101
|
+
for (const importGroup of Object.values(importGroups)) {
|
|
102
|
+
const { hasOnlyTypeImports, nodes } = importGroup;
|
|
103
|
+
const [firstImportNode, ...othersImportNodes] = nodes;
|
|
104
|
+
const nodesWithVariablesToUpdate = [];
|
|
105
|
+
let namespaceAliasName = null;
|
|
106
|
+
for (const specifier of nodes.flatMap((it) => it.specifiers)) {
|
|
107
|
+
if (!namespaceAliasName) {
|
|
108
|
+
namespaceAliasName = getNamespaceAliasNameFrom(specifier);
|
|
109
|
+
if (namespaceAliasName) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
nodesWithVariablesToUpdate.push(specifier);
|
|
114
|
+
}
|
|
115
|
+
if (!namespaceAliasName) {
|
|
116
|
+
namespaceAliasName = 'z';
|
|
117
|
+
if (namespaceAliasNameIndex > 0) {
|
|
118
|
+
namespaceAliasName = `z${namespaceAliasNameIndex}`;
|
|
119
|
+
namespaceAliasNameIndex += 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const isFirstImportValid = isGroupFirstImportKindValidForSyntax(importGroup, syntax);
|
|
123
|
+
if (!isFirstImportValid) {
|
|
124
|
+
context.report({
|
|
125
|
+
node: firstImportNode,
|
|
126
|
+
messageId: 'changeImportSyntax',
|
|
127
|
+
data: { syntax },
|
|
128
|
+
fix(fixer) {
|
|
129
|
+
const importTypeKeyword = hasOnlyTypeImports ? 'type ' : '';
|
|
130
|
+
let importSpecifier;
|
|
131
|
+
if (syntax === 'named') {
|
|
132
|
+
if (namespaceAliasName === 'z') {
|
|
133
|
+
importSpecifier = '{ z }';
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
importSpecifier = `{ z as ${namespaceAliasName} }`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
importSpecifier = `* as ${namespaceAliasName}`;
|
|
141
|
+
}
|
|
142
|
+
const newImportText = `import ${importTypeKeyword}${importSpecifier} from ${firstImportNode.source.raw};`;
|
|
143
|
+
return fixer.replaceText(firstImportNode, newImportText);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const allVariables = nodesWithVariablesToUpdate.flatMap((it) => sourceCode.getDeclaredVariables(it));
|
|
148
|
+
const allReferences = allVariables.flatMap((it) => it.references);
|
|
149
|
+
for (const ref of allReferences) {
|
|
150
|
+
const { identifier } = ref;
|
|
151
|
+
if (shouldIdentifierBeRenamed(identifier)) {
|
|
152
|
+
context.report({
|
|
153
|
+
node: identifier,
|
|
154
|
+
messageId: 'convertUsage',
|
|
155
|
+
data: { syntax },
|
|
156
|
+
fix(fixer) {
|
|
157
|
+
const newId = `${namespaceAliasName}.${identifier.name}`;
|
|
158
|
+
return fixer.replaceText(identifier, newId);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const extraImport of othersImportNodes) {
|
|
164
|
+
context.report({
|
|
165
|
+
node: extraImport,
|
|
166
|
+
messageId: 'removeDuplicate',
|
|
167
|
+
fix(fixer) {
|
|
168
|
+
return fixer.removeRange(extraImport.range);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
+
import dedent from 'dedent';
|
|
3
|
+
import { consistentImport } from './consistent-import.js';
|
|
4
|
+
const ruleTester = new RuleTester();
|
|
5
|
+
ruleTester.run(`${consistentImport.name} namespace`, consistentImport, {
|
|
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 namespace import',
|
|
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: [
|
|
40
|
+
{ messageId: 'changeImportSyntax', data: { syntax: 'namespace' } },
|
|
41
|
+
],
|
|
42
|
+
output: dedent `
|
|
43
|
+
import * as z from "zod/mini";
|
|
44
|
+
const userSchema = z.string();
|
|
45
|
+
`,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'default import',
|
|
49
|
+
code: 'import z from "zod/mini";',
|
|
50
|
+
errors: [
|
|
51
|
+
{ messageId: 'changeImportSyntax', data: { syntax: 'namespace' } },
|
|
52
|
+
],
|
|
53
|
+
output: 'import * as z from "zod/mini";',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'multiple named imports',
|
|
57
|
+
code: 'import { object, string } from "zod/mini";',
|
|
58
|
+
errors: [
|
|
59
|
+
{ messageId: 'changeImportSyntax', data: { syntax: 'namespace' } },
|
|
60
|
+
],
|
|
61
|
+
output: 'import * as z from "zod/mini";',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'named type import',
|
|
65
|
+
code: 'import type { z } from "zod/mini";',
|
|
66
|
+
errors: [
|
|
67
|
+
{ messageId: 'changeImportSyntax', data: { syntax: 'namespace' } },
|
|
68
|
+
],
|
|
69
|
+
output: 'import type * as z from "zod/mini";',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'zod/v4-mini default import',
|
|
73
|
+
code: 'import z from "zod/v4-mini";',
|
|
74
|
+
errors: [
|
|
75
|
+
{ messageId: 'changeImportSyntax', data: { syntax: 'namespace' } },
|
|
76
|
+
],
|
|
77
|
+
output: 'import * as z from "zod/v4-mini";',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'named imports with type',
|
|
81
|
+
code: dedent `
|
|
82
|
+
import { z } from "zod/mini";
|
|
83
|
+
import type { $ZodType } from "zod/mini";
|
|
84
|
+
const userSchema: $ZodType = z.string();
|
|
85
|
+
`,
|
|
86
|
+
errors: [
|
|
87
|
+
{
|
|
88
|
+
messageId: 'changeImportSyntax',
|
|
89
|
+
data: { syntax: 'namespace' },
|
|
90
|
+
line: 1,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
messageId: 'removeDuplicate',
|
|
94
|
+
data: { syntax: 'namespace' },
|
|
95
|
+
line: 2,
|
|
96
|
+
},
|
|
97
|
+
{ messageId: 'convertUsage', data: { syntax: 'namespace' }, line: 3 },
|
|
98
|
+
],
|
|
99
|
+
output: dedent `
|
|
100
|
+
import * as z from "zod/mini";
|
|
101
|
+
|
|
102
|
+
const userSchema: z.$ZodType = z.string();
|
|
103
|
+
`,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'keeps name from named z import',
|
|
107
|
+
code: dedent `
|
|
108
|
+
import { z as myZod } from "zod/mini";
|
|
109
|
+
const userSchema = myZod.string();
|
|
110
|
+
`,
|
|
111
|
+
errors: [{ messageId: 'changeImportSyntax', line: 1 }],
|
|
112
|
+
output: dedent `
|
|
113
|
+
import * as myZod from "zod/mini";
|
|
114
|
+
const userSchema = myZod.string();
|
|
115
|
+
`,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'two named imports from same module',
|
|
119
|
+
code: dedent `
|
|
120
|
+
import { array, boolean } from 'zod/mini';
|
|
121
|
+
const userSchema = array(boolean())
|
|
122
|
+
`,
|
|
123
|
+
errors: [
|
|
124
|
+
{ messageId: 'changeImportSyntax', line: 1 },
|
|
125
|
+
{ messageId: 'convertUsage', line: 2 },
|
|
126
|
+
{ messageId: 'convertUsage', line: 2 },
|
|
127
|
+
],
|
|
128
|
+
output: dedent `
|
|
129
|
+
import * as z from 'zod/mini';
|
|
130
|
+
const userSchema = z.array(z.boolean())
|
|
131
|
+
`,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
});
|
|
135
|
+
ruleTester.run(`${consistentImport.name} named`, consistentImport, {
|
|
136
|
+
valid: [
|
|
137
|
+
{
|
|
138
|
+
name: 'valid named import',
|
|
139
|
+
options: [{ syntax: 'named' }],
|
|
140
|
+
code: dedent `
|
|
141
|
+
import { z } from 'zod/mini';
|
|
142
|
+
const userSchema = z.string();
|
|
143
|
+
`,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'unrelated imports',
|
|
147
|
+
code: 'import { useState } from "react";',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
invalid: [
|
|
151
|
+
{
|
|
152
|
+
name: 'default import',
|
|
153
|
+
options: [{ syntax: 'named' }],
|
|
154
|
+
code: dedent `
|
|
155
|
+
import z from 'zod/mini';
|
|
156
|
+
const userSchema = z.string();
|
|
157
|
+
`,
|
|
158
|
+
errors: [{ messageId: 'changeImportSyntax', data: { syntax: 'named' } }],
|
|
159
|
+
output: dedent `
|
|
160
|
+
import { z } from 'zod/mini';
|
|
161
|
+
const userSchema = z.string();
|
|
162
|
+
`,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'namespace import',
|
|
166
|
+
options: [{ syntax: 'named' }],
|
|
167
|
+
code: dedent `
|
|
168
|
+
import * as z from 'zod/mini';
|
|
169
|
+
const userSchema = z.string();
|
|
170
|
+
`,
|
|
171
|
+
errors: [{ messageId: 'changeImportSyntax', data: { syntax: 'named' } }],
|
|
172
|
+
output: dedent `
|
|
173
|
+
import { z } from 'zod/mini';
|
|
174
|
+
const userSchema = z.string();
|
|
175
|
+
`,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'individual named imports converted to z',
|
|
179
|
+
options: [{ syntax: 'named' }],
|
|
180
|
+
code: dedent `
|
|
181
|
+
import { string } from 'zod/mini';
|
|
182
|
+
const userSchema = string();
|
|
183
|
+
`,
|
|
184
|
+
errors: [
|
|
185
|
+
{ messageId: 'changeImportSyntax', data: { syntax: 'named' } },
|
|
186
|
+
{ messageId: 'convertUsage', data: { syntax: 'named' } },
|
|
187
|
+
],
|
|
188
|
+
output: dedent `
|
|
189
|
+
import { z } from 'zod/mini';
|
|
190
|
+
const userSchema = z.string();
|
|
191
|
+
`,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
+
declare const ZOD_OBJECT_METHODS: readonly ["object", "looseObject", "strictObject"];
|
|
3
|
+
type ZodObjectMethod = (typeof ZOD_OBJECT_METHODS)[number];
|
|
4
|
+
interface Options {
|
|
5
|
+
allow: Array<ZodObjectMethod>;
|
|
6
|
+
}
|
|
7
|
+
type MessageIds = 'consistentMethod' | 'useMethod';
|
|
8
|
+
export declare const consistentObjectSchemaType: TSESLint.RuleModule<MessageIds, [Options], {
|
|
9
|
+
zodImportAllowedSource: "all" | "zod" | "zod-mini";
|
|
10
|
+
}, TSESLint.RuleListener> & {
|
|
11
|
+
name: string;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
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 ZOD_OBJECT_METHODS = ['object', 'looseObject', 'strictObject'];
|
|
5
|
+
const defaultOptions = { allow: ['object'] };
|
|
6
|
+
const { zodImportAllowedSource, trackZodSchemaImports, } = createZodSchemaImportTrack('zod-mini');
|
|
7
|
+
export const consistentObjectSchemaType = createZodMiniPluginRule({
|
|
8
|
+
name: 'consistent-object-schema-type',
|
|
9
|
+
meta: {
|
|
10
|
+
hasSuggestions: true,
|
|
11
|
+
type: 'suggestion',
|
|
12
|
+
docs: {
|
|
13
|
+
zodImportAllowedSource,
|
|
14
|
+
description: 'Enforce consistent usage of Zod Mini schema methods',
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
consistentMethod: "Inconsistent Zod Mini object schema method '{{actual}}'. Allowed: {{allowedList}}.",
|
|
18
|
+
useMethod: "Replace with '{{expected}}'.",
|
|
19
|
+
},
|
|
20
|
+
schema: [
|
|
21
|
+
{
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
allow: {
|
|
25
|
+
type: 'array',
|
|
26
|
+
description: 'Decides which object methods are allowed',
|
|
27
|
+
items: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
enum: [...ZOD_OBJECT_METHODS],
|
|
30
|
+
},
|
|
31
|
+
minItems: 1,
|
|
32
|
+
uniqueItems: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
defaultOptions: [defaultOptions],
|
|
40
|
+
create(context, [{ allow: allowedList }]) {
|
|
41
|
+
const { importDeclarationListener, detectZodSchemaRootNode, } = trackZodSchemaImports();
|
|
42
|
+
return {
|
|
43
|
+
ImportDeclaration: importDeclarationListener,
|
|
44
|
+
CallExpression(node) {
|
|
45
|
+
const zodSchemaMeta = detectZodSchemaRootNode(node);
|
|
46
|
+
const schemaType = zodSchemaMeta === null || zodSchemaMeta === void 0 ? void 0 : zodSchemaMeta.schemaType;
|
|
47
|
+
if (!schemaType || !ZOD_OBJECT_METHODS.includes(schemaType)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (allowedList.includes(schemaType)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const { callee } = node;
|
|
54
|
+
if (callee.type === AST_NODE_TYPES.Identifier) {
|
|
55
|
+
context.report({
|
|
56
|
+
node,
|
|
57
|
+
messageId: 'consistentMethod',
|
|
58
|
+
data: { actual: schemaType, allowedList: allowedList.join(',') },
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (callee.type === AST_NODE_TYPES.MemberExpression) {
|
|
63
|
+
context.report({
|
|
64
|
+
node,
|
|
65
|
+
messageId: 'consistentMethod',
|
|
66
|
+
data: {
|
|
67
|
+
actual: schemaType,
|
|
68
|
+
allowedList: allowedList.join(','),
|
|
69
|
+
},
|
|
70
|
+
suggest: allowedList.map((it) => ({
|
|
71
|
+
messageId: 'useMethod',
|
|
72
|
+
data: { expected: it },
|
|
73
|
+
fix(fixer) {
|
|
74
|
+
return fixer.replaceText(callee.property, it);
|
|
75
|
+
},
|
|
76
|
+
})),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|