eslint-plugin-zod-utils 1.0.2 → 1.0.3
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 +21 -0
- package/dist/index.cjs +122 -7
- package/dist/index.js +122 -7
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -102,6 +102,27 @@ app.post("/users", {
|
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
The rule understands `import { z } from "zod"`, `import * as zod from "zod"`, aliased `z` imports, and direct named schema factories such as `import { object, string } from "zod"`.
|
|
105
|
+
It also understands default imports such as `import z from "zod"`.
|
|
106
|
+
|
|
107
|
+
When type information is available through `@typescript-eslint/parser`, the rule also reports repeated derived schema creation from imported Zod schemas:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { UserSchema } from "./schemas";
|
|
111
|
+
|
|
112
|
+
function getPublicSchema() {
|
|
113
|
+
return UserSchema.pick({ id: true });
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { BaseSchema, TenantIdSchema } from "./schemas";
|
|
119
|
+
|
|
120
|
+
function getTenantSchema() {
|
|
121
|
+
return BaseSchema.extend({ tenantId: TenantIdSchema });
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This type-aware detection does not use schema-name conventions. If type information is unavailable, imported schema roots are not inferred from names such as `UserSchema` or `UserZodSchema`. The rule also does not assume a global `z` identifier refers to Zod.
|
|
105
126
|
|
|
106
127
|
## Development
|
|
107
128
|
|
package/dist/index.cjs
CHANGED
|
@@ -88,6 +88,37 @@ var ZOD_FACTORY_IMPORTS = /* @__PURE__ */ new Set([
|
|
|
88
88
|
"xid"
|
|
89
89
|
]);
|
|
90
90
|
var ZOD_NAMESPACE_IMPORTS = /* @__PURE__ */ new Set(["z", "coerce", "iso"]);
|
|
91
|
+
var ZOD_EXECUTION_METHODS = /* @__PURE__ */ new Set([
|
|
92
|
+
"parse",
|
|
93
|
+
"parseAsync",
|
|
94
|
+
"safeParse",
|
|
95
|
+
"safeParseAsync"
|
|
96
|
+
]);
|
|
97
|
+
var ZOD_SCHEMA_COMBINATOR_METHODS = /* @__PURE__ */ new Set([
|
|
98
|
+
"and",
|
|
99
|
+
"array",
|
|
100
|
+
"brand",
|
|
101
|
+
"catchall",
|
|
102
|
+
"deepPartial",
|
|
103
|
+
"describe",
|
|
104
|
+
"extend",
|
|
105
|
+
"merge",
|
|
106
|
+
"nullable",
|
|
107
|
+
"nullish",
|
|
108
|
+
"omit",
|
|
109
|
+
"optional",
|
|
110
|
+
"or",
|
|
111
|
+
"partial",
|
|
112
|
+
"passthrough",
|
|
113
|
+
"pick",
|
|
114
|
+
"readonly",
|
|
115
|
+
"refine",
|
|
116
|
+
"required",
|
|
117
|
+
"strict",
|
|
118
|
+
"strip",
|
|
119
|
+
"superRefine",
|
|
120
|
+
"transform"
|
|
121
|
+
]);
|
|
91
122
|
function findVariable(scope, name) {
|
|
92
123
|
let currentScope = scope;
|
|
93
124
|
while (currentScope) {
|
|
@@ -121,8 +152,33 @@ function getRootIdentifier(node) {
|
|
|
121
152
|
return null;
|
|
122
153
|
}
|
|
123
154
|
}
|
|
155
|
+
function getRootMethodName(node) {
|
|
156
|
+
let current = node;
|
|
157
|
+
let rootMethodName = null;
|
|
158
|
+
while (true) {
|
|
159
|
+
if (current.type === "CallExpression") {
|
|
160
|
+
current = current.callee;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (current.type === "MemberExpression") {
|
|
164
|
+
if (current.object.type === "Identifier" && !current.computed && current.property.type === "Identifier") {
|
|
165
|
+
rootMethodName = current.property.name;
|
|
166
|
+
}
|
|
167
|
+
current = current.object;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
return rootMethodName;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function getCallMethodName(node) {
|
|
174
|
+
const { callee } = node;
|
|
175
|
+
if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
|
|
176
|
+
return callee.property.name;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
124
180
|
function isZodImportSpecifier(node) {
|
|
125
|
-
if (node.type !== "ImportNamespaceSpecifier" && node.type !== "ImportSpecifier") {
|
|
181
|
+
if (node.type !== "ImportDefaultSpecifier" && node.type !== "ImportNamespaceSpecifier" && node.type !== "ImportSpecifier") {
|
|
126
182
|
return false;
|
|
127
183
|
}
|
|
128
184
|
const declaration = node.parent;
|
|
@@ -132,7 +188,7 @@ function isZodImportSpecifier(node) {
|
|
|
132
188
|
if (declaration.source.value !== "zod") {
|
|
133
189
|
return false;
|
|
134
190
|
}
|
|
135
|
-
if (node.type === "ImportNamespaceSpecifier") {
|
|
191
|
+
if (node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier") {
|
|
136
192
|
return true;
|
|
137
193
|
}
|
|
138
194
|
if (node.importKind === "type" || declaration.importKind === "type") {
|
|
@@ -141,6 +197,39 @@ function isZodImportSpecifier(node) {
|
|
|
141
197
|
const importedName = node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
|
|
142
198
|
return ZOD_NAMESPACE_IMPORTS.has(importedName) || ZOD_FACTORY_IMPORTS.has(importedName);
|
|
143
199
|
}
|
|
200
|
+
function hasFullTypeInformation(services) {
|
|
201
|
+
return services.program !== null;
|
|
202
|
+
}
|
|
203
|
+
function isZodDeclarationFile(fileName) {
|
|
204
|
+
return /(?:^|[/\\])node_modules[/\\]zod[/\\]/u.test(fileName);
|
|
205
|
+
}
|
|
206
|
+
function isZodSchemaType(type, services, seen = /* @__PURE__ */ new Set()) {
|
|
207
|
+
if (!hasFullTypeInformation(services)) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
if (seen.has(type)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
seen.add(type);
|
|
214
|
+
const symbols = [type.getSymbol(), type.aliasSymbol];
|
|
215
|
+
if (symbols.some(
|
|
216
|
+
(symbol) => symbol?.getDeclarations()?.some(
|
|
217
|
+
(declaration) => isZodDeclarationFile(declaration.getSourceFile().fileName)
|
|
218
|
+
)
|
|
219
|
+
)) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
if (type.isUnionOrIntersection()) {
|
|
223
|
+
return type.types.some((subType) => isZodSchemaType(subType, services, seen));
|
|
224
|
+
}
|
|
225
|
+
if (type.getBaseTypes()?.some((baseType) => isZodSchemaType(baseType, services, seen))) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
const parseSymbol = type.getProperty("parse");
|
|
229
|
+
return parseSymbol?.getDeclarations()?.some(
|
|
230
|
+
(declaration) => isZodDeclarationFile(declaration.getSourceFile().fileName)
|
|
231
|
+
) ?? false;
|
|
232
|
+
}
|
|
144
233
|
var noInlineZodSchema = import_utils.ESLintUtils.RuleCreator(
|
|
145
234
|
(ruleName2) => `https://www.npmjs.com/package/eslint-plugin-zod-utils#${ruleName2}`
|
|
146
235
|
)({
|
|
@@ -158,7 +247,15 @@ var noInlineZodSchema = import_utils.ESLintUtils.RuleCreator(
|
|
|
158
247
|
defaultOptions: [],
|
|
159
248
|
create(context) {
|
|
160
249
|
const sourceCode = context.sourceCode;
|
|
250
|
+
const parserServices = import_utils.ESLintUtils.getParserServices(context, true);
|
|
251
|
+
function isZodExecutionCall(node) {
|
|
252
|
+
const methodName = getCallMethodName(node);
|
|
253
|
+
return methodName !== null && ZOD_EXECUTION_METHODS.has(methodName);
|
|
254
|
+
}
|
|
161
255
|
function isZodSchemaCall(node) {
|
|
256
|
+
if (isZodExecutionCall(node)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
162
259
|
const root = getRootIdentifier(node.callee);
|
|
163
260
|
if (!root) {
|
|
164
261
|
return false;
|
|
@@ -167,10 +264,28 @@ var noInlineZodSchema = import_utils.ESLintUtils.RuleCreator(
|
|
|
167
264
|
const variable = findVariable(scope, root.name);
|
|
168
265
|
return variable?.defs.some((definition) => isZodImportSpecifier(definition.node)) ?? false;
|
|
169
266
|
}
|
|
170
|
-
function
|
|
267
|
+
function isTypedZodSchemaCombinatorCall(node) {
|
|
268
|
+
const rootMethodName = getRootMethodName(node);
|
|
269
|
+
if (rootMethodName === null || !ZOD_SCHEMA_COMBINATOR_METHODS.has(rootMethodName)) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
const root = getRootIdentifier(node.callee);
|
|
273
|
+
if (!root) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (!hasFullTypeInformation(parserServices)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
const type = parserServices.getTypeAtLocation(root);
|
|
280
|
+
return isZodSchemaType(type, parserServices);
|
|
281
|
+
}
|
|
282
|
+
function isSchemaCreationCall(node) {
|
|
283
|
+
return isZodSchemaCall(node) || isTypedZodSchemaCombinatorCall(node);
|
|
284
|
+
}
|
|
285
|
+
function hasSchemaCreationCallAncestor(node) {
|
|
171
286
|
let current = node.parent;
|
|
172
287
|
while (current) {
|
|
173
|
-
if (current.type === "CallExpression" &&
|
|
288
|
+
if (current.type === "CallExpression" && isSchemaCreationCall(current)) {
|
|
174
289
|
return true;
|
|
175
290
|
}
|
|
176
291
|
current = current.parent;
|
|
@@ -192,10 +307,10 @@ var noInlineZodSchema = import_utils.ESLintUtils.RuleCreator(
|
|
|
192
307
|
}
|
|
193
308
|
return {
|
|
194
309
|
CallExpression(node) {
|
|
195
|
-
if (!
|
|
310
|
+
if (!isSchemaCreationCall(node)) {
|
|
196
311
|
return;
|
|
197
312
|
}
|
|
198
|
-
if (
|
|
313
|
+
if (hasSchemaCreationCallAncestor(node)) {
|
|
199
314
|
return;
|
|
200
315
|
}
|
|
201
316
|
if (!isInsideRepeatedExecutionPath(node)) {
|
|
@@ -218,7 +333,7 @@ var rules = {
|
|
|
218
333
|
var plugin = {
|
|
219
334
|
meta: {
|
|
220
335
|
name: "eslint-plugin-zod-utils",
|
|
221
|
-
version: "
|
|
336
|
+
version: "1.0.3"
|
|
222
337
|
},
|
|
223
338
|
rules,
|
|
224
339
|
configs: {}
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,37 @@ var ZOD_FACTORY_IMPORTS = /* @__PURE__ */ new Set([
|
|
|
60
60
|
"xid"
|
|
61
61
|
]);
|
|
62
62
|
var ZOD_NAMESPACE_IMPORTS = /* @__PURE__ */ new Set(["z", "coerce", "iso"]);
|
|
63
|
+
var ZOD_EXECUTION_METHODS = /* @__PURE__ */ new Set([
|
|
64
|
+
"parse",
|
|
65
|
+
"parseAsync",
|
|
66
|
+
"safeParse",
|
|
67
|
+
"safeParseAsync"
|
|
68
|
+
]);
|
|
69
|
+
var ZOD_SCHEMA_COMBINATOR_METHODS = /* @__PURE__ */ new Set([
|
|
70
|
+
"and",
|
|
71
|
+
"array",
|
|
72
|
+
"brand",
|
|
73
|
+
"catchall",
|
|
74
|
+
"deepPartial",
|
|
75
|
+
"describe",
|
|
76
|
+
"extend",
|
|
77
|
+
"merge",
|
|
78
|
+
"nullable",
|
|
79
|
+
"nullish",
|
|
80
|
+
"omit",
|
|
81
|
+
"optional",
|
|
82
|
+
"or",
|
|
83
|
+
"partial",
|
|
84
|
+
"passthrough",
|
|
85
|
+
"pick",
|
|
86
|
+
"readonly",
|
|
87
|
+
"refine",
|
|
88
|
+
"required",
|
|
89
|
+
"strict",
|
|
90
|
+
"strip",
|
|
91
|
+
"superRefine",
|
|
92
|
+
"transform"
|
|
93
|
+
]);
|
|
63
94
|
function findVariable(scope, name) {
|
|
64
95
|
let currentScope = scope;
|
|
65
96
|
while (currentScope) {
|
|
@@ -93,8 +124,33 @@ function getRootIdentifier(node) {
|
|
|
93
124
|
return null;
|
|
94
125
|
}
|
|
95
126
|
}
|
|
127
|
+
function getRootMethodName(node) {
|
|
128
|
+
let current = node;
|
|
129
|
+
let rootMethodName = null;
|
|
130
|
+
while (true) {
|
|
131
|
+
if (current.type === "CallExpression") {
|
|
132
|
+
current = current.callee;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (current.type === "MemberExpression") {
|
|
136
|
+
if (current.object.type === "Identifier" && !current.computed && current.property.type === "Identifier") {
|
|
137
|
+
rootMethodName = current.property.name;
|
|
138
|
+
}
|
|
139
|
+
current = current.object;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
return rootMethodName;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function getCallMethodName(node) {
|
|
146
|
+
const { callee } = node;
|
|
147
|
+
if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
|
|
148
|
+
return callee.property.name;
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
96
152
|
function isZodImportSpecifier(node) {
|
|
97
|
-
if (node.type !== "ImportNamespaceSpecifier" && node.type !== "ImportSpecifier") {
|
|
153
|
+
if (node.type !== "ImportDefaultSpecifier" && node.type !== "ImportNamespaceSpecifier" && node.type !== "ImportSpecifier") {
|
|
98
154
|
return false;
|
|
99
155
|
}
|
|
100
156
|
const declaration = node.parent;
|
|
@@ -104,7 +160,7 @@ function isZodImportSpecifier(node) {
|
|
|
104
160
|
if (declaration.source.value !== "zod") {
|
|
105
161
|
return false;
|
|
106
162
|
}
|
|
107
|
-
if (node.type === "ImportNamespaceSpecifier") {
|
|
163
|
+
if (node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier") {
|
|
108
164
|
return true;
|
|
109
165
|
}
|
|
110
166
|
if (node.importKind === "type" || declaration.importKind === "type") {
|
|
@@ -113,6 +169,39 @@ function isZodImportSpecifier(node) {
|
|
|
113
169
|
const importedName = node.imported.type === "Identifier" ? node.imported.name : node.imported.value;
|
|
114
170
|
return ZOD_NAMESPACE_IMPORTS.has(importedName) || ZOD_FACTORY_IMPORTS.has(importedName);
|
|
115
171
|
}
|
|
172
|
+
function hasFullTypeInformation(services) {
|
|
173
|
+
return services.program !== null;
|
|
174
|
+
}
|
|
175
|
+
function isZodDeclarationFile(fileName) {
|
|
176
|
+
return /(?:^|[/\\])node_modules[/\\]zod[/\\]/u.test(fileName);
|
|
177
|
+
}
|
|
178
|
+
function isZodSchemaType(type, services, seen = /* @__PURE__ */ new Set()) {
|
|
179
|
+
if (!hasFullTypeInformation(services)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (seen.has(type)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
seen.add(type);
|
|
186
|
+
const symbols = [type.getSymbol(), type.aliasSymbol];
|
|
187
|
+
if (symbols.some(
|
|
188
|
+
(symbol) => symbol?.getDeclarations()?.some(
|
|
189
|
+
(declaration) => isZodDeclarationFile(declaration.getSourceFile().fileName)
|
|
190
|
+
)
|
|
191
|
+
)) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
if (type.isUnionOrIntersection()) {
|
|
195
|
+
return type.types.some((subType) => isZodSchemaType(subType, services, seen));
|
|
196
|
+
}
|
|
197
|
+
if (type.getBaseTypes()?.some((baseType) => isZodSchemaType(baseType, services, seen))) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
const parseSymbol = type.getProperty("parse");
|
|
201
|
+
return parseSymbol?.getDeclarations()?.some(
|
|
202
|
+
(declaration) => isZodDeclarationFile(declaration.getSourceFile().fileName)
|
|
203
|
+
) ?? false;
|
|
204
|
+
}
|
|
116
205
|
var noInlineZodSchema = ESLintUtils.RuleCreator(
|
|
117
206
|
(ruleName2) => `https://www.npmjs.com/package/eslint-plugin-zod-utils#${ruleName2}`
|
|
118
207
|
)({
|
|
@@ -130,7 +219,15 @@ var noInlineZodSchema = ESLintUtils.RuleCreator(
|
|
|
130
219
|
defaultOptions: [],
|
|
131
220
|
create(context) {
|
|
132
221
|
const sourceCode = context.sourceCode;
|
|
222
|
+
const parserServices = ESLintUtils.getParserServices(context, true);
|
|
223
|
+
function isZodExecutionCall(node) {
|
|
224
|
+
const methodName = getCallMethodName(node);
|
|
225
|
+
return methodName !== null && ZOD_EXECUTION_METHODS.has(methodName);
|
|
226
|
+
}
|
|
133
227
|
function isZodSchemaCall(node) {
|
|
228
|
+
if (isZodExecutionCall(node)) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
134
231
|
const root = getRootIdentifier(node.callee);
|
|
135
232
|
if (!root) {
|
|
136
233
|
return false;
|
|
@@ -139,10 +236,28 @@ var noInlineZodSchema = ESLintUtils.RuleCreator(
|
|
|
139
236
|
const variable = findVariable(scope, root.name);
|
|
140
237
|
return variable?.defs.some((definition) => isZodImportSpecifier(definition.node)) ?? false;
|
|
141
238
|
}
|
|
142
|
-
function
|
|
239
|
+
function isTypedZodSchemaCombinatorCall(node) {
|
|
240
|
+
const rootMethodName = getRootMethodName(node);
|
|
241
|
+
if (rootMethodName === null || !ZOD_SCHEMA_COMBINATOR_METHODS.has(rootMethodName)) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
const root = getRootIdentifier(node.callee);
|
|
245
|
+
if (!root) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (!hasFullTypeInformation(parserServices)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const type = parserServices.getTypeAtLocation(root);
|
|
252
|
+
return isZodSchemaType(type, parserServices);
|
|
253
|
+
}
|
|
254
|
+
function isSchemaCreationCall(node) {
|
|
255
|
+
return isZodSchemaCall(node) || isTypedZodSchemaCombinatorCall(node);
|
|
256
|
+
}
|
|
257
|
+
function hasSchemaCreationCallAncestor(node) {
|
|
143
258
|
let current = node.parent;
|
|
144
259
|
while (current) {
|
|
145
|
-
if (current.type === "CallExpression" &&
|
|
260
|
+
if (current.type === "CallExpression" && isSchemaCreationCall(current)) {
|
|
146
261
|
return true;
|
|
147
262
|
}
|
|
148
263
|
current = current.parent;
|
|
@@ -164,10 +279,10 @@ var noInlineZodSchema = ESLintUtils.RuleCreator(
|
|
|
164
279
|
}
|
|
165
280
|
return {
|
|
166
281
|
CallExpression(node) {
|
|
167
|
-
if (!
|
|
282
|
+
if (!isSchemaCreationCall(node)) {
|
|
168
283
|
return;
|
|
169
284
|
}
|
|
170
|
-
if (
|
|
285
|
+
if (hasSchemaCreationCallAncestor(node)) {
|
|
171
286
|
return;
|
|
172
287
|
}
|
|
173
288
|
if (!isInsideRepeatedExecutionPath(node)) {
|
|
@@ -190,7 +305,7 @@ var rules = {
|
|
|
190
305
|
var plugin = {
|
|
191
306
|
meta: {
|
|
192
307
|
name: "eslint-plugin-zod-utils",
|
|
193
|
-
version: "
|
|
308
|
+
version: "1.0.3"
|
|
194
309
|
},
|
|
195
310
|
rules,
|
|
196
311
|
configs: {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-zod-utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "ESLint utilities for safer Zod schema usage.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"tsup": "^8.5.1",
|
|
47
47
|
"typescript": "^6.0.3",
|
|
48
48
|
"typescript-eslint": "^8.60.1",
|
|
49
|
-
"vitest": "^4.1.8"
|
|
49
|
+
"vitest": "^4.1.8",
|
|
50
|
+
"zod": "^4.4.3"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
53
|
"@typescript-eslint/utils": "^8.60.1"
|