eslint-plugin-valibot 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 +138 -0
- package/dist/index.cjs +2636 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +154 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.mjs +2588 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +92 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2588 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
name: "eslint-plugin-valibot",
|
|
4
|
+
version: "0.1.0",
|
|
5
|
+
description: "ESLint rules for safer, more maintainable Valibot usage.",
|
|
6
|
+
license: "MIT",
|
|
7
|
+
repository: {
|
|
8
|
+
type: "git",
|
|
9
|
+
url: "git+https://github.com/nimaebra/eslint-plugin-valibot.git"
|
|
10
|
+
},
|
|
11
|
+
homepage: "https://github.com/nimaebra/eslint-plugin-valibot#readme",
|
|
12
|
+
bugs: {
|
|
13
|
+
url: "https://github.com/nimaebra/eslint-plugin-valibot/issues"
|
|
14
|
+
},
|
|
15
|
+
type: "module",
|
|
16
|
+
sideEffects: false,
|
|
17
|
+
main: "./dist/index.cjs",
|
|
18
|
+
module: "./dist/index.mjs",
|
|
19
|
+
types: "./dist/index.d.ts",
|
|
20
|
+
exports: {
|
|
21
|
+
".": {
|
|
22
|
+
types: "./dist/index.d.ts",
|
|
23
|
+
import: "./dist/index.mjs",
|
|
24
|
+
require: "./dist/index.cjs"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
files: [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
keywords: [
|
|
32
|
+
"eslint",
|
|
33
|
+
"eslint-plugin",
|
|
34
|
+
"eslintplugin",
|
|
35
|
+
"valibot",
|
|
36
|
+
"schema",
|
|
37
|
+
"validation",
|
|
38
|
+
"typescript"
|
|
39
|
+
],
|
|
40
|
+
engines: {
|
|
41
|
+
node: "^20.19.0 || ^22.13.0 || >=24.0.0"
|
|
42
|
+
},
|
|
43
|
+
packageManager: "pnpm@10.33.4",
|
|
44
|
+
publishConfig: {
|
|
45
|
+
access: "public"
|
|
46
|
+
},
|
|
47
|
+
scripts: {
|
|
48
|
+
build: "tsup --config tsup.config.ts",
|
|
49
|
+
changeset: "changeset",
|
|
50
|
+
clean: "rimraf dist coverage",
|
|
51
|
+
"configs:check": "tsx scripts/check-configs.ts",
|
|
52
|
+
"create:rule": "tsx scripts/generate-rule.ts",
|
|
53
|
+
"docs:build": "eslint-doc-generator . && tsx scripts/build-configs-doc.ts",
|
|
54
|
+
"docs:check": "eslint-doc-generator --check . && tsx scripts/check-docs.ts",
|
|
55
|
+
format: "prettier . --write",
|
|
56
|
+
"format:check": "prettier . --check",
|
|
57
|
+
lint: "eslint .",
|
|
58
|
+
"lint:fix": "eslint . --fix",
|
|
59
|
+
knip: "knip",
|
|
60
|
+
"pack:dry-run": "pnpm pack --dry-run",
|
|
61
|
+
prepublishOnly: "pnpm check",
|
|
62
|
+
"release:publish": "changeset publish",
|
|
63
|
+
"smoke:pack": "tsx scripts/smoke-pack.ts",
|
|
64
|
+
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
65
|
+
test: "vitest run tests/rules tests/configs",
|
|
66
|
+
"test:integration": "vitest run tests/integration",
|
|
67
|
+
"test:watch": "vitest",
|
|
68
|
+
check: "pnpm lint && pnpm typecheck && pnpm knip && pnpm test && pnpm build && pnpm test:integration && pnpm docs:check && pnpm configs:check",
|
|
69
|
+
"version-packages": "changeset version"
|
|
70
|
+
},
|
|
71
|
+
peerDependencies: {
|
|
72
|
+
eslint: "^9.0.0 || ^10.0.0",
|
|
73
|
+
valibot: "^1.0.0"
|
|
74
|
+
},
|
|
75
|
+
devDependencies: {
|
|
76
|
+
"@changesets/cli": "^2.31.0",
|
|
77
|
+
"@eslint/js": "^10.0.1",
|
|
78
|
+
"@types/node": "^24.12.0",
|
|
79
|
+
"@typescript-eslint/utils": "^8.59.4",
|
|
80
|
+
eslint: "^10.4.0",
|
|
81
|
+
"eslint-doc-generator": "^3.6.0",
|
|
82
|
+
globals: "^17.6.0",
|
|
83
|
+
knip: "^6.14.1",
|
|
84
|
+
prettier: "^3.8.3",
|
|
85
|
+
rimraf: "^6.1.3",
|
|
86
|
+
tsup: "^8.5.1",
|
|
87
|
+
tsx: "^4.22.3",
|
|
88
|
+
typescript: "^6.0.3",
|
|
89
|
+
"typescript-eslint": "^8.59.4",
|
|
90
|
+
vite: "^7.2.4",
|
|
91
|
+
vitest: "^4.1.7"
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/utils/create-rule.ts
|
|
96
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
97
|
+
var DOCS_BASE_URL = "https://github.com/nimaebrazeh/eslint-plugin-valibot/blob/main/docs/rules";
|
|
98
|
+
var createRule = ESLintUtils.RuleCreator(
|
|
99
|
+
(ruleName) => `${DOCS_BASE_URL}/${ruleName}.md`
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// src/rules/consistent-import.ts
|
|
103
|
+
var STYLES = ["namespace", "named"];
|
|
104
|
+
var consistentImport = createRule({
|
|
105
|
+
name: "consistent-import",
|
|
106
|
+
meta: {
|
|
107
|
+
type: "suggestion",
|
|
108
|
+
docs: {
|
|
109
|
+
description: "Enforce a consistent Valibot import style using either namespace or named imports."
|
|
110
|
+
},
|
|
111
|
+
schema: [
|
|
112
|
+
{
|
|
113
|
+
type: "object",
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
properties: {
|
|
116
|
+
namespaceAlias: {
|
|
117
|
+
type: "string",
|
|
118
|
+
minLength: 1
|
|
119
|
+
},
|
|
120
|
+
style: {
|
|
121
|
+
type: "string",
|
|
122
|
+
enum: [...STYLES]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
messages: {
|
|
128
|
+
namespaceAlias: "Use '{{expectedAlias}}' as the namespace alias for Valibot imports.",
|
|
129
|
+
preferNamed: "Use named Valibot imports like `import { object, string } from 'valibot'`.",
|
|
130
|
+
preferNamespace: "Use a namespace Valibot import like `import * as {{namespaceAlias}} from 'valibot'`."
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
defaultOptions: [
|
|
134
|
+
{
|
|
135
|
+
namespaceAlias: "v",
|
|
136
|
+
style: "namespace"
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
create(context, [options]) {
|
|
140
|
+
const resolvedOptions = resolveOptions(options);
|
|
141
|
+
return {
|
|
142
|
+
ImportDeclaration(node) {
|
|
143
|
+
if (node.source.value !== "valibot") {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (resolvedOptions.style === "namespace") {
|
|
147
|
+
verifyNamespaceImport(context, node, resolvedOptions);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
verifyNamedImport(context, node);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
function resolveOptions(options) {
|
|
156
|
+
return {
|
|
157
|
+
namespaceAlias: options?.namespaceAlias ?? "v",
|
|
158
|
+
style: options?.style ?? "namespace"
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function verifyNamespaceImport(context, node, options) {
|
|
162
|
+
const namespaceSpecifier = node.specifiers.find(
|
|
163
|
+
(specifier) => specifier.type === "ImportNamespaceSpecifier"
|
|
164
|
+
);
|
|
165
|
+
const hasOnlyNamespaceSpecifier = node.specifiers.length === 1 && Boolean(namespaceSpecifier);
|
|
166
|
+
if (!namespaceSpecifier || !hasOnlyNamespaceSpecifier) {
|
|
167
|
+
context.report({
|
|
168
|
+
node,
|
|
169
|
+
messageId: "preferNamespace",
|
|
170
|
+
data: {
|
|
171
|
+
namespaceAlias: options.namespaceAlias
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (namespaceSpecifier.local.name !== options.namespaceAlias) {
|
|
177
|
+
context.report({
|
|
178
|
+
node: namespaceSpecifier.local,
|
|
179
|
+
messageId: "namespaceAlias",
|
|
180
|
+
data: {
|
|
181
|
+
expectedAlias: options.namespaceAlias
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function verifyNamedImport(context, node) {
|
|
187
|
+
const hasNamespaceSpecifier = node.specifiers.some(
|
|
188
|
+
(specifier) => specifier.type === "ImportNamespaceSpecifier"
|
|
189
|
+
);
|
|
190
|
+
const hasDefaultSpecifier = node.specifiers.some(
|
|
191
|
+
(specifier) => specifier.type === "ImportDefaultSpecifier"
|
|
192
|
+
);
|
|
193
|
+
const hasNamedSpecifiers = node.specifiers.some(
|
|
194
|
+
(specifier) => specifier.type === "ImportSpecifier"
|
|
195
|
+
);
|
|
196
|
+
if (!hasNamedSpecifiers || hasNamespaceSpecifier || hasDefaultSpecifier) {
|
|
197
|
+
context.report({
|
|
198
|
+
node,
|
|
199
|
+
messageId: "preferNamed"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/utils/collect-valibot-imports.ts
|
|
205
|
+
var VALIBOT_MODULE_NAME = "valibot";
|
|
206
|
+
function createEmptyValibotImports() {
|
|
207
|
+
return {
|
|
208
|
+
namespaces: /* @__PURE__ */ new Set(),
|
|
209
|
+
importedNames: /* @__PURE__ */ new Map()
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function hasValibotImports(imports) {
|
|
213
|
+
return imports.namespaces.size > 0 || imports.importedNames.size > 0;
|
|
214
|
+
}
|
|
215
|
+
function collectValibotImports(program) {
|
|
216
|
+
const imports = createEmptyValibotImports();
|
|
217
|
+
for (const statement of program.body) {
|
|
218
|
+
if (statement.type === "ImportDeclaration" && statement.source.value === VALIBOT_MODULE_NAME) {
|
|
219
|
+
for (const specifier of statement.specifiers) {
|
|
220
|
+
if (specifier.type === "ImportNamespaceSpecifier") {
|
|
221
|
+
imports.namespaces.add(specifier.local.name);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (specifier.type === "ImportSpecifier") {
|
|
225
|
+
const importedName = specifier.imported.type === "Identifier" ? specifier.imported.name : String(specifier.imported.value);
|
|
226
|
+
imports.importedNames.set(specifier.local.name, importedName);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (statement.type !== "VariableDeclaration") {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
for (const declaration of statement.declarations) {
|
|
235
|
+
if (!declaration.init || declaration.init.type !== "CallExpression") {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!isRequireFromValibot(declaration.init)) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (declaration.id.type === "Identifier") {
|
|
242
|
+
imports.namespaces.add(declaration.id.name);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (declaration.id.type !== "ObjectPattern") {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
for (const property of declaration.id.properties) {
|
|
249
|
+
if (property.type !== "Property") {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (property.key.type !== "Identifier") {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (property.value.type !== "Identifier") {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
imports.importedNames.set(property.value.name, property.key.name);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return imports;
|
|
263
|
+
}
|
|
264
|
+
function isRequireFromValibot(node) {
|
|
265
|
+
return node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length === 1 && node.arguments[0]?.type === "Literal" && node.arguments[0].value === VALIBOT_MODULE_NAME;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/utils/is-valibot-call.ts
|
|
269
|
+
function getValibotCallName(node, imports) {
|
|
270
|
+
if (node.callee.type === "Identifier") {
|
|
271
|
+
return imports.importedNames.get(node.callee.name) ?? null;
|
|
272
|
+
}
|
|
273
|
+
if (node.callee.type === "MemberExpression" && !node.callee.computed && node.callee.object.type === "Identifier" && node.callee.property.type === "Identifier" && imports.namespaces.has(node.callee.object.name)) {
|
|
274
|
+
return node.callee.property.name;
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
function isValibotCall(node, imports, functionName) {
|
|
279
|
+
return getValibotCallName(node, imports) === functionName;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/utils/is-schema-expression.ts
|
|
283
|
+
var SCHEMA_CALL_NAMES = /* @__PURE__ */ new Set([
|
|
284
|
+
"any",
|
|
285
|
+
"array",
|
|
286
|
+
"bigint",
|
|
287
|
+
"blob",
|
|
288
|
+
"boolean",
|
|
289
|
+
"custom",
|
|
290
|
+
"date",
|
|
291
|
+
"enum",
|
|
292
|
+
"exactOptional",
|
|
293
|
+
"fallback",
|
|
294
|
+
"file",
|
|
295
|
+
"function",
|
|
296
|
+
"instance",
|
|
297
|
+
"intersect",
|
|
298
|
+
"lazy",
|
|
299
|
+
"literal",
|
|
300
|
+
"looseObject",
|
|
301
|
+
"looseTuple",
|
|
302
|
+
"map",
|
|
303
|
+
"message",
|
|
304
|
+
"nan",
|
|
305
|
+
"never",
|
|
306
|
+
"nonNullable",
|
|
307
|
+
"nonNullish",
|
|
308
|
+
"nonOptional",
|
|
309
|
+
"null",
|
|
310
|
+
"nullable",
|
|
311
|
+
"nullish",
|
|
312
|
+
"number",
|
|
313
|
+
"object",
|
|
314
|
+
"objectWithRest",
|
|
315
|
+
"omit",
|
|
316
|
+
"optional",
|
|
317
|
+
"partial",
|
|
318
|
+
"pick",
|
|
319
|
+
"picklist",
|
|
320
|
+
"pipe",
|
|
321
|
+
"promise",
|
|
322
|
+
"readonly",
|
|
323
|
+
"record",
|
|
324
|
+
"required",
|
|
325
|
+
"set",
|
|
326
|
+
"strictObject",
|
|
327
|
+
"strictTuple",
|
|
328
|
+
"string",
|
|
329
|
+
"symbol",
|
|
330
|
+
"tuple",
|
|
331
|
+
"tupleWithRest",
|
|
332
|
+
"undefined",
|
|
333
|
+
"undefinedable",
|
|
334
|
+
"union",
|
|
335
|
+
"unknown",
|
|
336
|
+
"unwrap",
|
|
337
|
+
"variant",
|
|
338
|
+
"void"
|
|
339
|
+
]);
|
|
340
|
+
function isValibotSchemaExpression(node, imports) {
|
|
341
|
+
return node?.type === "CallExpression" && isValibotSchemaCall(node, imports);
|
|
342
|
+
}
|
|
343
|
+
function isValibotSchemaCall(node, imports) {
|
|
344
|
+
const callName = getValibotCallName(node, imports);
|
|
345
|
+
return callName !== null && SCHEMA_CALL_NAMES.has(callName);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/utils/schema-identifiers.ts
|
|
349
|
+
function getSchemaBindingName(node, imports) {
|
|
350
|
+
if (node.id.type !== "Identifier" || !isValibotSchemaExpression(node.init, imports)) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return node.id.name;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/rules/consistent-schema-convention.ts
|
|
357
|
+
var CONVENTIONS = ["same-name", "suffix"];
|
|
358
|
+
var SCOPES = ["exported", "all"];
|
|
359
|
+
var INFER_TYPE_NAMES = {
|
|
360
|
+
InferInput: "input",
|
|
361
|
+
InferOutput: "output"
|
|
362
|
+
};
|
|
363
|
+
var consistentSchemaConvention = createRule({
|
|
364
|
+
name: "consistent-schema-convention",
|
|
365
|
+
meta: {
|
|
366
|
+
type: "suggestion",
|
|
367
|
+
docs: {
|
|
368
|
+
description: "Enforce a consistent Valibot schema naming convention for exported schemas and inferred types."
|
|
369
|
+
},
|
|
370
|
+
schema: [
|
|
371
|
+
{
|
|
372
|
+
type: "object",
|
|
373
|
+
additionalProperties: false,
|
|
374
|
+
properties: {
|
|
375
|
+
allowDataSuffix: {
|
|
376
|
+
type: "boolean"
|
|
377
|
+
},
|
|
378
|
+
convention: {
|
|
379
|
+
type: "string",
|
|
380
|
+
enum: [...CONVENTIONS]
|
|
381
|
+
},
|
|
382
|
+
dataSuffix: {
|
|
383
|
+
type: "string",
|
|
384
|
+
minLength: 1
|
|
385
|
+
},
|
|
386
|
+
inputSuffix: {
|
|
387
|
+
type: "string",
|
|
388
|
+
minLength: 1
|
|
389
|
+
},
|
|
390
|
+
outputSuffix: {
|
|
391
|
+
type: "string",
|
|
392
|
+
minLength: 1
|
|
393
|
+
},
|
|
394
|
+
schemaSuffix: {
|
|
395
|
+
type: "string",
|
|
396
|
+
minLength: 1
|
|
397
|
+
},
|
|
398
|
+
scope: {
|
|
399
|
+
type: "string",
|
|
400
|
+
enum: [...SCOPES]
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
],
|
|
405
|
+
messages: {
|
|
406
|
+
schemaNameConvention: "Rename this schema binding to '{{expectedName}}' to follow the '{{convention}}' naming convention.",
|
|
407
|
+
typeNameConvention: "Rename this inferred type alias to '{{expectedName}}' to follow the '{{convention}}' naming convention."
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
defaultOptions: [
|
|
411
|
+
{
|
|
412
|
+
allowDataSuffix: true,
|
|
413
|
+
convention: "suffix",
|
|
414
|
+
dataSuffix: "Data",
|
|
415
|
+
inputSuffix: "Input",
|
|
416
|
+
outputSuffix: "Output",
|
|
417
|
+
schemaSuffix: "Schema",
|
|
418
|
+
scope: "exported"
|
|
419
|
+
}
|
|
420
|
+
],
|
|
421
|
+
create(context, [options]) {
|
|
422
|
+
let imports = createEmptyValibotImports();
|
|
423
|
+
const exportedTypeBindings = /* @__PURE__ */ new Set();
|
|
424
|
+
const exportedValueBindings = /* @__PURE__ */ new Set();
|
|
425
|
+
const inferTypeAliases = [];
|
|
426
|
+
const resolvedOptions = resolveOptions2(options);
|
|
427
|
+
const schemaBindings = /* @__PURE__ */ new Map();
|
|
428
|
+
return {
|
|
429
|
+
Program(node) {
|
|
430
|
+
imports = collectValibotImports(node);
|
|
431
|
+
exportedTypeBindings.clear();
|
|
432
|
+
exportedValueBindings.clear();
|
|
433
|
+
inferTypeAliases.length = 0;
|
|
434
|
+
schemaBindings.clear();
|
|
435
|
+
collectExportedBindings(
|
|
436
|
+
node,
|
|
437
|
+
exportedValueBindings,
|
|
438
|
+
exportedTypeBindings
|
|
439
|
+
);
|
|
440
|
+
},
|
|
441
|
+
VariableDeclarator(node) {
|
|
442
|
+
if (!hasValibotImports(imports)) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const bindingName = getSchemaBindingName(node, imports);
|
|
446
|
+
if (!bindingName || !shouldCheckSchemaBinding(
|
|
447
|
+
node,
|
|
448
|
+
resolvedOptions,
|
|
449
|
+
exportedValueBindings
|
|
450
|
+
)) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (node.id.type !== "Identifier") {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
schemaBindings.set(bindingName, {
|
|
457
|
+
name: bindingName,
|
|
458
|
+
node: node.id
|
|
459
|
+
});
|
|
460
|
+
},
|
|
461
|
+
TSTypeAliasDeclaration(node) {
|
|
462
|
+
if (!hasValibotImports(imports)) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const inferTypeAlias = getInferTypeAliasInfo(node, imports);
|
|
466
|
+
if (!inferTypeAlias || !shouldCheckTypeAlias(node, resolvedOptions, exportedTypeBindings)) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
inferTypeAliases.push(inferTypeAlias);
|
|
470
|
+
},
|
|
471
|
+
"Program:exit"() {
|
|
472
|
+
if (!hasValibotImports(imports)) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
for (const binding of schemaBindings.values()) {
|
|
476
|
+
const expectedName = getExpectedSchemaName(
|
|
477
|
+
binding.name,
|
|
478
|
+
resolvedOptions
|
|
479
|
+
);
|
|
480
|
+
if (binding.name === expectedName) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
context.report({
|
|
484
|
+
node: binding.node,
|
|
485
|
+
messageId: "schemaNameConvention",
|
|
486
|
+
data: {
|
|
487
|
+
convention: resolvedOptions.convention,
|
|
488
|
+
expectedName
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
for (const typeAlias of inferTypeAliases) {
|
|
493
|
+
if (!schemaBindings.has(typeAlias.schemaName)) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
const expectedName = getExpectedTypeAliasName(
|
|
497
|
+
typeAlias.schemaName,
|
|
498
|
+
typeAlias.inferKind,
|
|
499
|
+
resolvedOptions
|
|
500
|
+
);
|
|
501
|
+
if (typeAlias.aliasName === expectedName || isAllowedDataAliasName(
|
|
502
|
+
typeAlias.aliasName,
|
|
503
|
+
typeAlias.schemaName,
|
|
504
|
+
resolvedOptions
|
|
505
|
+
)) {
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
context.report({
|
|
509
|
+
node: typeAlias.node,
|
|
510
|
+
messageId: "typeNameConvention",
|
|
511
|
+
data: {
|
|
512
|
+
convention: resolvedOptions.convention,
|
|
513
|
+
expectedName
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
function resolveOptions2(options) {
|
|
522
|
+
return {
|
|
523
|
+
allowDataSuffix: options?.allowDataSuffix ?? true,
|
|
524
|
+
convention: options?.convention ?? "suffix",
|
|
525
|
+
dataSuffix: options?.dataSuffix ?? "Data",
|
|
526
|
+
inputSuffix: options?.inputSuffix ?? "Input",
|
|
527
|
+
outputSuffix: options?.outputSuffix ?? "Output",
|
|
528
|
+
schemaSuffix: options?.schemaSuffix ?? "Schema",
|
|
529
|
+
scope: options?.scope ?? "exported"
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function collectExportedBindings(program, exportedValueBindings, exportedTypeBindings) {
|
|
533
|
+
for (const statement of program.body) {
|
|
534
|
+
if (statement.type === "ExportDefaultDeclaration") {
|
|
535
|
+
if (statement.declaration.type === "Identifier") {
|
|
536
|
+
exportedValueBindings.add(statement.declaration.name);
|
|
537
|
+
}
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (statement.type !== "ExportNamedDeclaration") {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (statement.declaration) {
|
|
544
|
+
collectExportedDeclarationBindings(
|
|
545
|
+
statement.declaration,
|
|
546
|
+
exportedValueBindings,
|
|
547
|
+
exportedTypeBindings
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
for (const specifier of statement.specifiers) {
|
|
551
|
+
if (specifier.local.type !== "Identifier") {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (statement.exportKind === "type" || specifier.exportKind === "type") {
|
|
555
|
+
exportedTypeBindings.add(specifier.local.name);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
exportedValueBindings.add(specifier.local.name);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function collectExportedDeclarationBindings(declaration, exportedValueBindings, exportedTypeBindings) {
|
|
563
|
+
if (!declaration) {
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (declaration.type === "VariableDeclaration") {
|
|
567
|
+
for (const declarator of declaration.declarations) {
|
|
568
|
+
if (declarator.id.type === "Identifier") {
|
|
569
|
+
exportedValueBindings.add(declarator.id.name);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (declaration.type === "TSTypeAliasDeclaration") {
|
|
575
|
+
exportedTypeBindings.add(declaration.id.name);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function shouldCheckSchemaBinding(node, options, exportedValueBindings) {
|
|
579
|
+
return options.scope === "all" || isInlineExportedVariableDeclarator(node) || node.id.type === "Identifier" && exportedValueBindings.has(node.id.name);
|
|
580
|
+
}
|
|
581
|
+
function shouldCheckTypeAlias(node, options, exportedTypeBindings) {
|
|
582
|
+
return options.scope === "all" || isInlineExportedTypeAlias(node) || exportedTypeBindings.has(node.id.name);
|
|
583
|
+
}
|
|
584
|
+
function isInlineExportedVariableDeclarator(node) {
|
|
585
|
+
return node.parent?.parent?.type === "ExportNamedDeclaration";
|
|
586
|
+
}
|
|
587
|
+
function isInlineExportedTypeAlias(node) {
|
|
588
|
+
return node.parent?.type === "ExportNamedDeclaration";
|
|
589
|
+
}
|
|
590
|
+
function getInferTypeAliasInfo(node, imports) {
|
|
591
|
+
if (node.typeAnnotation.type !== "TSTypeReference") {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
const inferKind = getInferTypeKind(node.typeAnnotation, imports);
|
|
595
|
+
const typeParameter = node.typeAnnotation.typeArguments?.params[0];
|
|
596
|
+
const schemaName = typeParameter && getSchemaNameFromTypeQuery(typeParameter);
|
|
597
|
+
if (!inferKind || !schemaName) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
return {
|
|
601
|
+
aliasName: node.id.name,
|
|
602
|
+
inferKind,
|
|
603
|
+
node: node.id,
|
|
604
|
+
schemaName
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function getInferTypeKind(node, imports) {
|
|
608
|
+
if (node.typeName.type === "Identifier") {
|
|
609
|
+
return INFER_TYPE_NAMES[imports.importedNames.get(node.typeName.name) ?? ""] ?? null;
|
|
610
|
+
}
|
|
611
|
+
if (node.typeName.type !== "TSQualifiedName") {
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
return node.typeName.left.type === "Identifier" && imports.namespaces.has(node.typeName.left.name) ? INFER_TYPE_NAMES[node.typeName.right.name] ?? null : null;
|
|
615
|
+
}
|
|
616
|
+
function getSchemaNameFromTypeQuery(node) {
|
|
617
|
+
return node.type === "TSTypeQuery" && node.exprName.type === "Identifier" ? node.exprName.name : null;
|
|
618
|
+
}
|
|
619
|
+
function getExpectedSchemaName(actualName, options) {
|
|
620
|
+
const baseName = getNormalizedBaseName(actualName, options);
|
|
621
|
+
return options.convention === "same-name" ? baseName : `${baseName}${options.schemaSuffix}`;
|
|
622
|
+
}
|
|
623
|
+
function getExpectedTypeAliasName(schemaName, inferKind, options) {
|
|
624
|
+
const baseName = getNormalizedBaseName(schemaName, options);
|
|
625
|
+
if (options.convention === "same-name") {
|
|
626
|
+
return baseName;
|
|
627
|
+
}
|
|
628
|
+
return `${baseName}${inferKind === "input" ? options.inputSuffix : options.outputSuffix}`;
|
|
629
|
+
}
|
|
630
|
+
function isAllowedDataAliasName(aliasName, schemaName, options) {
|
|
631
|
+
return options.convention === "suffix" && options.allowDataSuffix && aliasName === `${getNormalizedBaseName(schemaName, options)}${options.dataSuffix}`;
|
|
632
|
+
}
|
|
633
|
+
function getNormalizedBaseName(value, options) {
|
|
634
|
+
const strippedValue = stripKnownSuffix(value, options);
|
|
635
|
+
return isPascalCaseLike(strippedValue) ? strippedValue : toPascalCase(strippedValue);
|
|
636
|
+
}
|
|
637
|
+
function stripKnownSuffix(value, options) {
|
|
638
|
+
const suffixes = [
|
|
639
|
+
options.schemaSuffix,
|
|
640
|
+
options.inputSuffix,
|
|
641
|
+
options.outputSuffix,
|
|
642
|
+
...options.allowDataSuffix ? [options.dataSuffix] : []
|
|
643
|
+
].sort((left, right) => right.length - left.length);
|
|
644
|
+
for (const suffix of suffixes) {
|
|
645
|
+
if (value.endsWith(suffix) && value.length > suffix.length) {
|
|
646
|
+
return value.slice(0, -suffix.length);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return value;
|
|
650
|
+
}
|
|
651
|
+
function isPascalCaseLike(value) {
|
|
652
|
+
return /^[A-Z][A-Za-z0-9]*$/.test(value);
|
|
653
|
+
}
|
|
654
|
+
function toPascalCase(value) {
|
|
655
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join("");
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/utils/pipe-analysis.ts
|
|
659
|
+
function getPipeActionDescriptors(pipeCall, imports, sourceCode) {
|
|
660
|
+
if (!isValibotCall(pipeCall, imports, "pipe") || pipeCall.arguments.length < 2) {
|
|
661
|
+
return [];
|
|
662
|
+
}
|
|
663
|
+
return pipeCall.arguments.flatMap((argument, actionIndex) => {
|
|
664
|
+
if (actionIndex === 0 || argument.type !== "CallExpression") {
|
|
665
|
+
return [];
|
|
666
|
+
}
|
|
667
|
+
const name = getValibotCallName(argument, imports);
|
|
668
|
+
if (!name) {
|
|
669
|
+
return [];
|
|
670
|
+
}
|
|
671
|
+
return [
|
|
672
|
+
{
|
|
673
|
+
actionIndex,
|
|
674
|
+
fingerprint: sourceCode.getText(argument),
|
|
675
|
+
name,
|
|
676
|
+
node: argument
|
|
677
|
+
}
|
|
678
|
+
];
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/rules/no-duplicate-pipe-actions.ts
|
|
683
|
+
var noDuplicatePipeActions = createRule({
|
|
684
|
+
name: "no-duplicate-pipe-actions",
|
|
685
|
+
meta: {
|
|
686
|
+
type: "suggestion",
|
|
687
|
+
docs: {
|
|
688
|
+
description: "Disallow duplicate Valibot actions inside the same pipe() call."
|
|
689
|
+
},
|
|
690
|
+
fixable: "code",
|
|
691
|
+
schema: [],
|
|
692
|
+
messages: {
|
|
693
|
+
duplicatePipeAction: "This pipe action duplicates an earlier {{actionName}}() call in the same pipe."
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
defaultOptions: [],
|
|
697
|
+
create(context) {
|
|
698
|
+
let imports = createEmptyValibotImports();
|
|
699
|
+
const sourceCode = context.sourceCode;
|
|
700
|
+
return {
|
|
701
|
+
Program(node) {
|
|
702
|
+
imports = collectValibotImports(node);
|
|
703
|
+
},
|
|
704
|
+
CallExpression(node) {
|
|
705
|
+
if (!hasValibotImports(imports) || !isValibotCall(node, imports, "pipe")) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const seenActions = /* @__PURE__ */ new Map();
|
|
709
|
+
for (const action of getPipeActionDescriptors(
|
|
710
|
+
node,
|
|
711
|
+
imports,
|
|
712
|
+
sourceCode
|
|
713
|
+
)) {
|
|
714
|
+
const firstAction = seenActions.get(action.fingerprint);
|
|
715
|
+
if (!firstAction) {
|
|
716
|
+
seenActions.set(action.fingerprint, action.node);
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
context.report({
|
|
720
|
+
node: action.node,
|
|
721
|
+
messageId: "duplicatePipeAction",
|
|
722
|
+
data: {
|
|
723
|
+
actionName: action.name
|
|
724
|
+
},
|
|
725
|
+
fix: canSafelyRemoveDuplicateAction(
|
|
726
|
+
node,
|
|
727
|
+
action.actionIndex,
|
|
728
|
+
sourceCode
|
|
729
|
+
) ? (fixer) => {
|
|
730
|
+
const removalRange = getDuplicateActionRemovalRange(
|
|
731
|
+
node,
|
|
732
|
+
action.actionIndex
|
|
733
|
+
);
|
|
734
|
+
return fixer.removeRange(removalRange);
|
|
735
|
+
} : null
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
function canSafelyRemoveDuplicateAction(pipeCall, actionIndex, sourceCode) {
|
|
743
|
+
const currentAction = pipeCall.arguments[actionIndex];
|
|
744
|
+
if (!currentAction) {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
if (sourceCode.getCommentsBefore(currentAction).length > 0 || sourceCode.getCommentsAfter(currentAction).length > 0) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
function getDuplicateActionRemovalRange(pipeCall, actionIndex) {
|
|
753
|
+
const currentAction = pipeCall.arguments[actionIndex];
|
|
754
|
+
if (!currentAction) {
|
|
755
|
+
return [pipeCall.range[0], pipeCall.range[0]];
|
|
756
|
+
}
|
|
757
|
+
const nextAction = pipeCall.arguments[actionIndex + 1];
|
|
758
|
+
if (nextAction) {
|
|
759
|
+
return [currentAction.range[0], nextAction.range[0]];
|
|
760
|
+
}
|
|
761
|
+
const previousAction = pipeCall.arguments[actionIndex - 1];
|
|
762
|
+
if (!previousAction) {
|
|
763
|
+
return currentAction.range;
|
|
764
|
+
}
|
|
765
|
+
return [previousAction.range[1], currentAction.range[1]];
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/rules/no-recreated-schemas.ts
|
|
769
|
+
var noRecreatedSchemas = createRule({
|
|
770
|
+
name: "no-recreated-schemas",
|
|
771
|
+
meta: {
|
|
772
|
+
type: "suggestion",
|
|
773
|
+
docs: {
|
|
774
|
+
description: "Disallow recreating static Valibot schemas inside function scope."
|
|
775
|
+
},
|
|
776
|
+
schema: [],
|
|
777
|
+
messages: {
|
|
778
|
+
noRecreatedSchema: "This schema is recreated on every call. Hoist it to module scope unless it truly depends on runtime inputs."
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
defaultOptions: [],
|
|
782
|
+
create(context) {
|
|
783
|
+
let imports = createEmptyValibotImports();
|
|
784
|
+
return {
|
|
785
|
+
Program(node) {
|
|
786
|
+
imports = collectValibotImports(node);
|
|
787
|
+
},
|
|
788
|
+
VariableDeclarator(node) {
|
|
789
|
+
if (node.id.type !== "Identifier" || !isStaticSchemaRecreation(node.init, imports)) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
context.report({
|
|
793
|
+
node: node.id,
|
|
794
|
+
messageId: "noRecreatedSchema"
|
|
795
|
+
});
|
|
796
|
+
},
|
|
797
|
+
ReturnStatement(node) {
|
|
798
|
+
if (!isStaticSchemaRecreation(node.argument, imports)) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
context.report({
|
|
802
|
+
node: node.argument,
|
|
803
|
+
messageId: "noRecreatedSchema"
|
|
804
|
+
});
|
|
805
|
+
},
|
|
806
|
+
ArrowFunctionExpression(node) {
|
|
807
|
+
if (node.body.type === "BlockStatement" || !isStaticSchemaRecreation(node.body, imports)) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
context.report({
|
|
811
|
+
node: node.body,
|
|
812
|
+
messageId: "noRecreatedSchema"
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
function isStaticSchemaRecreation(node, imports) {
|
|
819
|
+
return hasValibotImports(imports) && isValibotSchemaExpression(node, imports) && findEnclosingFunction(node) !== null && !containsNestedFunction(node) && !hasDynamicSchemaInputs(node, imports);
|
|
820
|
+
}
|
|
821
|
+
function findEnclosingFunction(node) {
|
|
822
|
+
let current = node.parent;
|
|
823
|
+
while (current) {
|
|
824
|
+
if (isFunctionLike(current)) {
|
|
825
|
+
return current;
|
|
826
|
+
}
|
|
827
|
+
current = current.parent;
|
|
828
|
+
}
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
function containsNestedFunction(node) {
|
|
832
|
+
return traverseNode(
|
|
833
|
+
node,
|
|
834
|
+
(childNode) => childNode !== node && isFunctionLike(childNode)
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
function hasDynamicSchemaInputs(node, imports) {
|
|
838
|
+
return traverseNode(node, (childNode, parentNode) => {
|
|
839
|
+
if (childNode === node || childNode.type !== "Identifier") {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
if (imports.importedNames.has(childNode.name) || imports.namespaces.has(childNode.name)) {
|
|
843
|
+
return false;
|
|
844
|
+
}
|
|
845
|
+
if (parentNode?.type === "MemberExpression" && parentNode.property === childNode && !parentNode.computed) {
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
if (parentNode?.type === "Property" && parentNode.key === childNode && !parentNode.computed) {
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
return true;
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
function isFunctionLike(node) {
|
|
855
|
+
return node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
|
|
856
|
+
}
|
|
857
|
+
function traverseNode(node, predicate, parent) {
|
|
858
|
+
if (predicate(node, parent)) {
|
|
859
|
+
return true;
|
|
860
|
+
}
|
|
861
|
+
for (const [key, value] of Object.entries(node)) {
|
|
862
|
+
if (key === "parent" || value === null || value === void 0) {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (Array.isArray(value)) {
|
|
866
|
+
for (const item of value) {
|
|
867
|
+
if (isNode(item) && traverseNode(item, predicate, node)) {
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (isNode(value) && traverseNode(value, predicate, node)) {
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
function isNode(value) {
|
|
880
|
+
return typeof value === "object" && value !== null && "type" in value;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/rules/no-schema-as-type.ts
|
|
884
|
+
var ALLOWED_INFER_TYPE_NAMES = /* @__PURE__ */ new Set(["InferInput", "InferOutput"]);
|
|
885
|
+
var noSchemaAsType = createRule({
|
|
886
|
+
name: "no-schema-as-type",
|
|
887
|
+
meta: {
|
|
888
|
+
type: "problem",
|
|
889
|
+
docs: {
|
|
890
|
+
description: "Disallow using a Valibot schema value itself as a TypeScript type."
|
|
891
|
+
},
|
|
892
|
+
schema: [],
|
|
893
|
+
messages: {
|
|
894
|
+
noSchemaAsType: "Use InferInput<typeof {{schemaName}}> or InferOutput<typeof {{schemaName}}> instead of typeof {{schemaName}}."
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
defaultOptions: [],
|
|
898
|
+
create(context) {
|
|
899
|
+
let imports = createEmptyValibotImports();
|
|
900
|
+
const schemaBindings = /* @__PURE__ */ new Set();
|
|
901
|
+
return {
|
|
902
|
+
Program(node) {
|
|
903
|
+
imports = collectValibotImports(node);
|
|
904
|
+
},
|
|
905
|
+
VariableDeclarator(node) {
|
|
906
|
+
if (!hasValibotImports(imports)) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
const schemaBindingName = getSchemaBindingName(node, imports);
|
|
910
|
+
if (schemaBindingName) {
|
|
911
|
+
schemaBindings.add(schemaBindingName);
|
|
912
|
+
}
|
|
913
|
+
},
|
|
914
|
+
TSTypeQuery(node) {
|
|
915
|
+
if (!hasValibotImports(imports) || isAllowedSchemaTypeQuery(node, imports)) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const schemaName = getSchemaTypeQueryName(node);
|
|
919
|
+
if (!schemaName || !schemaBindings.has(schemaName)) {
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
context.report({
|
|
923
|
+
node,
|
|
924
|
+
messageId: "noSchemaAsType",
|
|
925
|
+
data: {
|
|
926
|
+
schemaName
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
function getSchemaTypeQueryName(node) {
|
|
934
|
+
return node.exprName.type === "Identifier" ? node.exprName.name : null;
|
|
935
|
+
}
|
|
936
|
+
function isAllowedSchemaTypeQuery(node, imports) {
|
|
937
|
+
const parent = node.parent;
|
|
938
|
+
if (!parent || parent.type !== "TSTypeParameterInstantiation") {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
const typeReference = parent.parent;
|
|
942
|
+
if (!typeReference || typeReference.type !== "TSTypeReference") {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
return isAllowedInferReference(typeReference.typeName, imports);
|
|
946
|
+
}
|
|
947
|
+
function isAllowedInferReference(typeName, imports) {
|
|
948
|
+
if (typeName.type === "Identifier") {
|
|
949
|
+
return ALLOWED_INFER_TYPE_NAMES.has(
|
|
950
|
+
imports.importedNames.get(typeName.name) ?? ""
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
if (typeName.type !== "TSQualifiedName") {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
return typeName.left.type === "Identifier" && imports.namespaces.has(typeName.left.name) && ALLOWED_INFER_TYPE_NAMES.has(typeName.right.name);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/rules/prefer-picklist.ts
|
|
960
|
+
var preferPicklist = createRule({
|
|
961
|
+
name: "prefer-picklist",
|
|
962
|
+
meta: {
|
|
963
|
+
type: "suggestion",
|
|
964
|
+
docs: {
|
|
965
|
+
description: "Prefer picklist() over union() when the union only contains literal string schemas."
|
|
966
|
+
},
|
|
967
|
+
fixable: "code",
|
|
968
|
+
schema: [],
|
|
969
|
+
messages: {
|
|
970
|
+
preferPicklist: "Prefer picklist([...]) instead of union([...]) for literal string options."
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
defaultOptions: [],
|
|
974
|
+
create(context) {
|
|
975
|
+
let imports = createEmptyValibotImports();
|
|
976
|
+
const sourceCode = context.sourceCode;
|
|
977
|
+
return {
|
|
978
|
+
Program(node) {
|
|
979
|
+
imports = collectValibotImports(node);
|
|
980
|
+
},
|
|
981
|
+
CallExpression(node) {
|
|
982
|
+
if (!hasValibotImports(imports) || !isValibotCall(node, imports, "union")) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const optionsArg = node.arguments[0];
|
|
986
|
+
if (optionsArg?.type !== "ArrayExpression") {
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const literalOptionTexts = getLiteralOptionTexts(
|
|
990
|
+
optionsArg,
|
|
991
|
+
imports,
|
|
992
|
+
sourceCode
|
|
993
|
+
);
|
|
994
|
+
if (!literalOptionTexts) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
const preferredCalleeText = getPreferredCalleeText(node, imports);
|
|
998
|
+
context.report({
|
|
999
|
+
node,
|
|
1000
|
+
messageId: "preferPicklist",
|
|
1001
|
+
fix: preferredCalleeText && (node.arguments.length === 1 || node.arguments.length === 2) ? (fixer) => fixer.replaceText(
|
|
1002
|
+
node,
|
|
1003
|
+
buildPicklistCallText(
|
|
1004
|
+
node,
|
|
1005
|
+
preferredCalleeText,
|
|
1006
|
+
literalOptionTexts,
|
|
1007
|
+
sourceCode
|
|
1008
|
+
)
|
|
1009
|
+
) : null
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
function getLiteralOptionTexts(optionsArray, imports, sourceCode) {
|
|
1016
|
+
if (optionsArray.elements.length < 2) {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
const literalTexts = [];
|
|
1020
|
+
for (const element of optionsArray.elements) {
|
|
1021
|
+
if (!element || element.type === "SpreadElement") {
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
if (element.type !== "CallExpression") {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
if (!isValibotCall(element, imports, "literal") || element.arguments.length !== 1) {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
const literalArg = element.arguments[0];
|
|
1031
|
+
if (!literalArg || literalArg.type !== "Literal" || typeof literalArg.value !== "string") {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
literalTexts.push(sourceCode.getText(literalArg));
|
|
1035
|
+
}
|
|
1036
|
+
return literalTexts;
|
|
1037
|
+
}
|
|
1038
|
+
function getPreferredCalleeText(call, imports) {
|
|
1039
|
+
if (call.callee.type === "MemberExpression") {
|
|
1040
|
+
return `${sourceTextForMemberNamespace(call)}.picklist`;
|
|
1041
|
+
}
|
|
1042
|
+
const localPicklistName = getLocalImportName(imports, "picklist");
|
|
1043
|
+
return localPicklistName ?? null;
|
|
1044
|
+
}
|
|
1045
|
+
function sourceTextForMemberNamespace(call) {
|
|
1046
|
+
if (call.callee.type === "MemberExpression" && call.callee.object.type === "Identifier") {
|
|
1047
|
+
return call.callee.object.name;
|
|
1048
|
+
}
|
|
1049
|
+
return "v";
|
|
1050
|
+
}
|
|
1051
|
+
function getLocalImportName(imports, importedName) {
|
|
1052
|
+
for (const [localName, importedValue] of imports.importedNames) {
|
|
1053
|
+
if (importedValue === importedName) {
|
|
1054
|
+
return localName;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return void 0;
|
|
1058
|
+
}
|
|
1059
|
+
function buildPicklistCallText(unionCall, calleeText, literalOptionTexts, sourceCode) {
|
|
1060
|
+
const valuesText = `[${literalOptionTexts.join(", ")}]`;
|
|
1061
|
+
const messageArg = unionCall.arguments[1];
|
|
1062
|
+
if (messageArg) {
|
|
1063
|
+
return `${calleeText}(${valuesText}, ${sourceCode.getText(messageArg)})`;
|
|
1064
|
+
}
|
|
1065
|
+
return `${calleeText}(${valuesText})`;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/rules/prefer-nullable-over-union-null.ts
|
|
1069
|
+
var OBJECT_SCHEMA_NAMES = [
|
|
1070
|
+
"object",
|
|
1071
|
+
"objectAsync",
|
|
1072
|
+
"strictObject",
|
|
1073
|
+
"strictObjectAsync",
|
|
1074
|
+
"looseObject",
|
|
1075
|
+
"looseObjectAsync",
|
|
1076
|
+
"objectWithRest",
|
|
1077
|
+
"objectWithRestAsync"
|
|
1078
|
+
];
|
|
1079
|
+
var preferNullableOverUnionNull = createRule({
|
|
1080
|
+
name: "prefer-nullable-over-union-null",
|
|
1081
|
+
meta: {
|
|
1082
|
+
type: "suggestion",
|
|
1083
|
+
docs: {
|
|
1084
|
+
description: "Prefer nullable() over union([schema, null()]) when they are equivalent."
|
|
1085
|
+
},
|
|
1086
|
+
fixable: "code",
|
|
1087
|
+
schema: [],
|
|
1088
|
+
messages: {
|
|
1089
|
+
preferNullable: "Prefer nullable() instead of union([schema, null()]) when the schema only needs to accept null."
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
defaultOptions: [],
|
|
1093
|
+
create(context) {
|
|
1094
|
+
let imports = createEmptyValibotImports();
|
|
1095
|
+
const sourceCode = context.sourceCode;
|
|
1096
|
+
return {
|
|
1097
|
+
Program(node) {
|
|
1098
|
+
imports = collectValibotImports(node);
|
|
1099
|
+
},
|
|
1100
|
+
CallExpression(node) {
|
|
1101
|
+
if (!hasValibotImports(imports) || !isValibotCall(node, imports, "union")) {
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
const matchedSchema = getMatchedSchema(node, imports);
|
|
1105
|
+
if (!matchedSchema || isWithinObjectSchemaEntry(node, imports)) {
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const preferredCalleeText = getPreferredCalleeText2(node, imports);
|
|
1109
|
+
context.report({
|
|
1110
|
+
node,
|
|
1111
|
+
messageId: "preferNullable",
|
|
1112
|
+
fix: preferredCalleeText ? (fixer) => fixer.replaceText(
|
|
1113
|
+
node,
|
|
1114
|
+
`${preferredCalleeText}(${sourceCode.getText(matchedSchema)})`
|
|
1115
|
+
) : null
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
function getMatchedSchema(call, imports) {
|
|
1122
|
+
if (call.arguments.length !== 1) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
const options = call.arguments[0];
|
|
1126
|
+
if (options?.type !== "ArrayExpression" || options.elements.length !== 2) {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
const [firstOption, secondOption] = options.elements;
|
|
1130
|
+
if (!isSchemaOption(firstOption) || !isSchemaOption(secondOption)) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
if (isNullSchemaCall(firstOption, imports)) {
|
|
1134
|
+
return secondOption;
|
|
1135
|
+
}
|
|
1136
|
+
if (isNullSchemaCall(secondOption, imports)) {
|
|
1137
|
+
return firstOption;
|
|
1138
|
+
}
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
function isSchemaOption(node) {
|
|
1142
|
+
return node !== null && node.type !== "SpreadElement";
|
|
1143
|
+
}
|
|
1144
|
+
function isNullSchemaCall(node, imports) {
|
|
1145
|
+
return node.type === "CallExpression" && node.arguments.length === 0 && isValibotCall(node, imports, "null");
|
|
1146
|
+
}
|
|
1147
|
+
function isWithinObjectSchemaEntry(node, imports) {
|
|
1148
|
+
let current = node;
|
|
1149
|
+
while (current?.parent) {
|
|
1150
|
+
const parent = current.parent;
|
|
1151
|
+
if (parent.type === "Property" && parent.value === current) {
|
|
1152
|
+
const objectLiteral = parent.parent;
|
|
1153
|
+
if (!objectLiteral || objectLiteral.type !== "ObjectExpression") {
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
const schemaCall = objectLiteral.parent;
|
|
1157
|
+
if (!schemaCall || schemaCall.type !== "CallExpression" || schemaCall.arguments[0] !== objectLiteral) {
|
|
1158
|
+
return false;
|
|
1159
|
+
}
|
|
1160
|
+
return OBJECT_SCHEMA_NAMES.some(
|
|
1161
|
+
(schemaName) => isValibotCall(schemaCall, imports, schemaName)
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
current = parent;
|
|
1165
|
+
}
|
|
1166
|
+
return false;
|
|
1167
|
+
}
|
|
1168
|
+
function getPreferredCalleeText2(call, imports) {
|
|
1169
|
+
if (call.callee.type === "MemberExpression") {
|
|
1170
|
+
return `${sourceTextForMemberNamespace2(call)}.nullable`;
|
|
1171
|
+
}
|
|
1172
|
+
const localNullableName = getLocalImportName2(imports, "nullable");
|
|
1173
|
+
return localNullableName ?? null;
|
|
1174
|
+
}
|
|
1175
|
+
function sourceTextForMemberNamespace2(call) {
|
|
1176
|
+
if (call.callee.type === "MemberExpression" && call.callee.object.type === "Identifier") {
|
|
1177
|
+
return call.callee.object.name;
|
|
1178
|
+
}
|
|
1179
|
+
return "v";
|
|
1180
|
+
}
|
|
1181
|
+
function getLocalImportName2(imports, importedName) {
|
|
1182
|
+
for (const [localName, importedValue] of imports.importedNames) {
|
|
1183
|
+
if (importedValue === importedName) {
|
|
1184
|
+
return localName;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return void 0;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// src/rules/prefer-optional-over-union-undefined.ts
|
|
1191
|
+
var OBJECT_SCHEMA_NAMES2 = [
|
|
1192
|
+
"object",
|
|
1193
|
+
"objectAsync",
|
|
1194
|
+
"strictObject",
|
|
1195
|
+
"strictObjectAsync",
|
|
1196
|
+
"looseObject",
|
|
1197
|
+
"looseObjectAsync",
|
|
1198
|
+
"objectWithRest",
|
|
1199
|
+
"objectWithRestAsync"
|
|
1200
|
+
];
|
|
1201
|
+
var preferOptionalOverUnionUndefined = createRule(
|
|
1202
|
+
{
|
|
1203
|
+
name: "prefer-optional-over-union-undefined",
|
|
1204
|
+
meta: {
|
|
1205
|
+
type: "suggestion",
|
|
1206
|
+
docs: {
|
|
1207
|
+
description: "Prefer optional() over union([schema, undefined()]) when they are equivalent."
|
|
1208
|
+
},
|
|
1209
|
+
fixable: "code",
|
|
1210
|
+
schema: [],
|
|
1211
|
+
messages: {
|
|
1212
|
+
preferOptional: "Prefer optional() instead of union([schema, undefined()]) when the schema only needs to accept undefined."
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1215
|
+
defaultOptions: [],
|
|
1216
|
+
create(context) {
|
|
1217
|
+
let imports = createEmptyValibotImports();
|
|
1218
|
+
const sourceCode = context.sourceCode;
|
|
1219
|
+
return {
|
|
1220
|
+
Program(node) {
|
|
1221
|
+
imports = collectValibotImports(node);
|
|
1222
|
+
},
|
|
1223
|
+
CallExpression(node) {
|
|
1224
|
+
if (!hasValibotImports(imports) || !isValibotCall(node, imports, "union")) {
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const matchedSchema = getMatchedSchema2(node, imports);
|
|
1228
|
+
if (!matchedSchema || isWithinObjectSchemaEntry2(node, imports)) {
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
const preferredCalleeText = getPreferredCalleeText3(node, imports);
|
|
1232
|
+
context.report({
|
|
1233
|
+
node,
|
|
1234
|
+
messageId: "preferOptional",
|
|
1235
|
+
fix: preferredCalleeText ? (fixer) => fixer.replaceText(
|
|
1236
|
+
node,
|
|
1237
|
+
`${preferredCalleeText}(${sourceCode.getText(matchedSchema)})`
|
|
1238
|
+
) : null
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
);
|
|
1245
|
+
function getMatchedSchema2(call, imports) {
|
|
1246
|
+
if (call.arguments.length !== 1) {
|
|
1247
|
+
return null;
|
|
1248
|
+
}
|
|
1249
|
+
const options = call.arguments[0];
|
|
1250
|
+
if (options?.type !== "ArrayExpression" || options.elements.length !== 2) {
|
|
1251
|
+
return null;
|
|
1252
|
+
}
|
|
1253
|
+
const [firstOption, secondOption] = options.elements;
|
|
1254
|
+
if (!isSchemaOption2(firstOption) || !isSchemaOption2(secondOption)) {
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
if (isUndefinedSchemaCall(firstOption, imports)) {
|
|
1258
|
+
return secondOption;
|
|
1259
|
+
}
|
|
1260
|
+
if (isUndefinedSchemaCall(secondOption, imports)) {
|
|
1261
|
+
return firstOption;
|
|
1262
|
+
}
|
|
1263
|
+
return null;
|
|
1264
|
+
}
|
|
1265
|
+
function isSchemaOption2(node) {
|
|
1266
|
+
return node !== null && node.type !== "SpreadElement";
|
|
1267
|
+
}
|
|
1268
|
+
function isUndefinedSchemaCall(node, imports) {
|
|
1269
|
+
return node.type === "CallExpression" && node.arguments.length === 0 && isValibotCall(node, imports, "undefined");
|
|
1270
|
+
}
|
|
1271
|
+
function isWithinObjectSchemaEntry2(node, imports) {
|
|
1272
|
+
let current = node;
|
|
1273
|
+
while (current?.parent) {
|
|
1274
|
+
const parent = current.parent;
|
|
1275
|
+
if (parent.type === "Property" && parent.value === current) {
|
|
1276
|
+
const objectLiteral = parent.parent;
|
|
1277
|
+
if (!objectLiteral || objectLiteral.type !== "ObjectExpression") {
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
const schemaCall = objectLiteral.parent;
|
|
1281
|
+
if (!schemaCall || schemaCall.type !== "CallExpression" || schemaCall.arguments[0] !== objectLiteral) {
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1284
|
+
return OBJECT_SCHEMA_NAMES2.some(
|
|
1285
|
+
(schemaName) => isValibotCall(schemaCall, imports, schemaName)
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
current = parent;
|
|
1289
|
+
}
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
function getPreferredCalleeText3(call, imports) {
|
|
1293
|
+
if (call.callee.type === "MemberExpression") {
|
|
1294
|
+
return `${sourceTextForMemberNamespace3(call)}.optional`;
|
|
1295
|
+
}
|
|
1296
|
+
const localOptionalName = getLocalImportName3(imports, "optional");
|
|
1297
|
+
return localOptionalName ?? null;
|
|
1298
|
+
}
|
|
1299
|
+
function sourceTextForMemberNamespace3(call) {
|
|
1300
|
+
if (call.callee.type === "MemberExpression" && call.callee.object.type === "Identifier") {
|
|
1301
|
+
return call.callee.object.name;
|
|
1302
|
+
}
|
|
1303
|
+
return "v";
|
|
1304
|
+
}
|
|
1305
|
+
function getLocalImportName3(imports, importedName) {
|
|
1306
|
+
for (const [localName, importedValue] of imports.importedNames) {
|
|
1307
|
+
if (importedValue === importedName) {
|
|
1308
|
+
return localName;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return void 0;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// src/rules/prefer-variant.ts
|
|
1315
|
+
var OBJECT_SCHEMA_NAMES3 = [
|
|
1316
|
+
"object",
|
|
1317
|
+
"strictObject",
|
|
1318
|
+
"looseObject",
|
|
1319
|
+
"objectWithRest"
|
|
1320
|
+
];
|
|
1321
|
+
var preferVariant = createRule({
|
|
1322
|
+
name: "prefer-variant",
|
|
1323
|
+
meta: {
|
|
1324
|
+
type: "suggestion",
|
|
1325
|
+
docs: {
|
|
1326
|
+
description: "Prefer variant() over union() when object schemas share an obvious discriminant key."
|
|
1327
|
+
},
|
|
1328
|
+
fixable: "code",
|
|
1329
|
+
schema: [],
|
|
1330
|
+
messages: {
|
|
1331
|
+
preferVariant: "Prefer variant('{{key}}', [...]) over union([...]) for discriminated object schemas."
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
defaultOptions: [],
|
|
1335
|
+
create(context) {
|
|
1336
|
+
let imports = createEmptyValibotImports();
|
|
1337
|
+
const sourceCode = context.sourceCode;
|
|
1338
|
+
return {
|
|
1339
|
+
Program(node) {
|
|
1340
|
+
imports = collectValibotImports(node);
|
|
1341
|
+
},
|
|
1342
|
+
CallExpression(node) {
|
|
1343
|
+
if (!hasValibotImports(imports) || !isValibotCall(node, imports, "union")) {
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
const optionsArray = node.arguments[0];
|
|
1347
|
+
if (optionsArray?.type !== "ArrayExpression") {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
const optionCalls = getOptionObjectSchemaCalls(optionsArray, imports);
|
|
1351
|
+
if (!optionCalls) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
const discriminantKey = getCommonDiscriminantKey(optionCalls, imports);
|
|
1355
|
+
if (!discriminantKey) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
const preferredCalleeText = getPreferredCalleeText4(node, imports);
|
|
1359
|
+
context.report({
|
|
1360
|
+
node,
|
|
1361
|
+
messageId: "preferVariant",
|
|
1362
|
+
data: {
|
|
1363
|
+
key: discriminantKey
|
|
1364
|
+
},
|
|
1365
|
+
fix: preferredCalleeText && node.arguments.length === 1 ? (fixer) => fixer.replaceText(
|
|
1366
|
+
node,
|
|
1367
|
+
`${preferredCalleeText}(${toSingleQuotedString(discriminantKey)}, ${sourceCode.getText(optionsArray)})`
|
|
1368
|
+
) : null
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
function getOptionObjectSchemaCalls(optionsArray, imports) {
|
|
1375
|
+
if (optionsArray.elements.length < 2) {
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
const optionCalls = [];
|
|
1379
|
+
for (const element of optionsArray.elements) {
|
|
1380
|
+
if (!element || element.type === "SpreadElement") {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
if (element.type !== "CallExpression") {
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
if (!OBJECT_SCHEMA_NAMES3.some((name) => isValibotCall(element, imports, name))) {
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
optionCalls.push(element);
|
|
1390
|
+
}
|
|
1391
|
+
return optionCalls;
|
|
1392
|
+
}
|
|
1393
|
+
function getCommonDiscriminantKey(objectSchemaCalls, imports) {
|
|
1394
|
+
const literalKeyMaps = objectSchemaCalls.map(
|
|
1395
|
+
(schemaCall) => getLiteralDiscriminantKeyMap(schemaCall, imports)
|
|
1396
|
+
);
|
|
1397
|
+
if (literalKeyMaps.some((map) => map.size === 0)) {
|
|
1398
|
+
return null;
|
|
1399
|
+
}
|
|
1400
|
+
const firstMap = literalKeyMaps[0];
|
|
1401
|
+
for (const [keyName, firstLiteral] of firstMap) {
|
|
1402
|
+
let isCommon = true;
|
|
1403
|
+
const literalValues = /* @__PURE__ */ new Set([firstLiteral]);
|
|
1404
|
+
for (let index = 1; index < literalKeyMaps.length; index += 1) {
|
|
1405
|
+
const literalValue = literalKeyMaps[index].get(keyName);
|
|
1406
|
+
if (!literalValue) {
|
|
1407
|
+
isCommon = false;
|
|
1408
|
+
break;
|
|
1409
|
+
}
|
|
1410
|
+
literalValues.add(literalValue);
|
|
1411
|
+
}
|
|
1412
|
+
if (isCommon && literalValues.size > 1) {
|
|
1413
|
+
return keyName;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
function getLiteralDiscriminantKeyMap(objectSchemaCall, imports) {
|
|
1419
|
+
const entriesArg = objectSchemaCall.arguments[0];
|
|
1420
|
+
if (!entriesArg || entriesArg.type !== "ObjectExpression") {
|
|
1421
|
+
return /* @__PURE__ */ new Map();
|
|
1422
|
+
}
|
|
1423
|
+
const keyMap = /* @__PURE__ */ new Map();
|
|
1424
|
+
for (const property of entriesArg.properties) {
|
|
1425
|
+
if (property.type !== "Property" || property.computed) {
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
const keyName = getPropertyKeyName(property.key);
|
|
1429
|
+
if (!keyName) {
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
const literalValue = getLiteralSchemaValue(property.value, imports);
|
|
1433
|
+
if (!literalValue) {
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
keyMap.set(keyName, literalValue);
|
|
1437
|
+
}
|
|
1438
|
+
return keyMap;
|
|
1439
|
+
}
|
|
1440
|
+
function getPropertyKeyName(key) {
|
|
1441
|
+
if (key.type === "Identifier") {
|
|
1442
|
+
return key.name;
|
|
1443
|
+
}
|
|
1444
|
+
if (key.type === "Literal" && typeof key.value === "string") {
|
|
1445
|
+
return key.value;
|
|
1446
|
+
}
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
function getLiteralSchemaValue(value, imports) {
|
|
1450
|
+
if (value.type !== "CallExpression") {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
if (!isValibotCall(value, imports, "literal") || value.arguments.length !== 1) {
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
const literalArg = value.arguments[0];
|
|
1457
|
+
if (!literalArg || literalArg.type !== "Literal") {
|
|
1458
|
+
return null;
|
|
1459
|
+
}
|
|
1460
|
+
return String(literalArg.value);
|
|
1461
|
+
}
|
|
1462
|
+
function getPreferredCalleeText4(call, imports) {
|
|
1463
|
+
if (call.callee.type === "MemberExpression") {
|
|
1464
|
+
return `${sourceTextForMemberNamespace4(call)}.variant`;
|
|
1465
|
+
}
|
|
1466
|
+
const localVariantName = getLocalImportName4(imports, "variant");
|
|
1467
|
+
return localVariantName ?? null;
|
|
1468
|
+
}
|
|
1469
|
+
function sourceTextForMemberNamespace4(call) {
|
|
1470
|
+
if (call.callee.type === "MemberExpression" && call.callee.object.type === "Identifier") {
|
|
1471
|
+
return call.callee.object.name;
|
|
1472
|
+
}
|
|
1473
|
+
return "v";
|
|
1474
|
+
}
|
|
1475
|
+
function getLocalImportName4(imports, importedName) {
|
|
1476
|
+
for (const [localName, importedValue] of imports.importedNames) {
|
|
1477
|
+
if (importedValue === importedName) {
|
|
1478
|
+
return localName;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return void 0;
|
|
1482
|
+
}
|
|
1483
|
+
function toSingleQuotedString(value) {
|
|
1484
|
+
return `'${value.replaceAll("'", "\\'")}'`;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// src/rules/prefer-nullish.ts
|
|
1488
|
+
var WRAPPER_PAIRS = [
|
|
1489
|
+
["optional", "nullable"],
|
|
1490
|
+
["nullable", "optional"]
|
|
1491
|
+
];
|
|
1492
|
+
var preferNullish = createRule({
|
|
1493
|
+
name: "prefer-nullish",
|
|
1494
|
+
meta: {
|
|
1495
|
+
type: "suggestion",
|
|
1496
|
+
docs: {
|
|
1497
|
+
description: "Prefer nullish() over nested optional() and nullable() wrappers."
|
|
1498
|
+
},
|
|
1499
|
+
fixable: "code",
|
|
1500
|
+
schema: [],
|
|
1501
|
+
messages: {
|
|
1502
|
+
preferNullish: "Prefer nullish() instead of nesting optional() and nullable()."
|
|
1503
|
+
}
|
|
1504
|
+
},
|
|
1505
|
+
defaultOptions: [],
|
|
1506
|
+
create(context) {
|
|
1507
|
+
let imports = createEmptyValibotImports();
|
|
1508
|
+
const sourceCode = context.sourceCode;
|
|
1509
|
+
return {
|
|
1510
|
+
Program(node) {
|
|
1511
|
+
imports = collectValibotImports(node);
|
|
1512
|
+
},
|
|
1513
|
+
CallExpression(node) {
|
|
1514
|
+
if (!hasValibotImports(imports) || node.arguments.length !== 1) {
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
const innerCall = node.arguments[0];
|
|
1518
|
+
if (innerCall?.type !== "CallExpression" || innerCall.arguments.length !== 1) {
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
const innerSchema = innerCall.arguments[0];
|
|
1522
|
+
if (!innerSchema) {
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
const pair = getWrapperPair(node, innerCall, imports);
|
|
1526
|
+
if (!pair) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
const preferredCalleeText = getPreferredCalleeText5(node, imports);
|
|
1530
|
+
context.report({
|
|
1531
|
+
node,
|
|
1532
|
+
messageId: "preferNullish",
|
|
1533
|
+
fix: preferredCalleeText ? (fixer) => fixer.replaceText(
|
|
1534
|
+
node,
|
|
1535
|
+
`${preferredCalleeText}(${sourceCode.getText(innerSchema)})`
|
|
1536
|
+
) : null
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
function getWrapperPair(outerCall, innerCall, imports) {
|
|
1543
|
+
for (const pair of WRAPPER_PAIRS) {
|
|
1544
|
+
if (isValibotCall(outerCall, imports, pair[0]) && isValibotCall(innerCall, imports, pair[1])) {
|
|
1545
|
+
return pair;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
return null;
|
|
1549
|
+
}
|
|
1550
|
+
function getPreferredCalleeText5(call, imports) {
|
|
1551
|
+
if (call.callee.type === "MemberExpression") {
|
|
1552
|
+
return `${sourceTextForMemberNamespace5(call)}.nullish`;
|
|
1553
|
+
}
|
|
1554
|
+
const localNullishName = getLocalImportName5(imports, "nullish");
|
|
1555
|
+
return localNullishName ?? null;
|
|
1556
|
+
}
|
|
1557
|
+
function sourceTextForMemberNamespace5(call) {
|
|
1558
|
+
if (call.callee.type === "MemberExpression" && call.callee.object.type === "Identifier") {
|
|
1559
|
+
return call.callee.object.name;
|
|
1560
|
+
}
|
|
1561
|
+
return "v";
|
|
1562
|
+
}
|
|
1563
|
+
function getLocalImportName5(imports, importedName) {
|
|
1564
|
+
for (const [localName, importedValue] of imports.importedNames) {
|
|
1565
|
+
if (importedValue === importedName) {
|
|
1566
|
+
return localName;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
return void 0;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// src/rules/no-unguarded-parse.ts
|
|
1573
|
+
var GUARDED_PARSER_NAMES = ["parse", "assert"];
|
|
1574
|
+
var noUnguardedParse = createRule({
|
|
1575
|
+
name: "no-unguarded-parse",
|
|
1576
|
+
meta: {
|
|
1577
|
+
type: "problem",
|
|
1578
|
+
docs: {
|
|
1579
|
+
description: "Require Valibot parse() and assert() calls to be wrapped in try/catch."
|
|
1580
|
+
},
|
|
1581
|
+
schema: [],
|
|
1582
|
+
messages: {
|
|
1583
|
+
unguardedParserCall: "Wrap Valibot {{functionName}}() in a try/catch block or switch to safeParse() for recoverable validation."
|
|
1584
|
+
}
|
|
1585
|
+
},
|
|
1586
|
+
defaultOptions: [],
|
|
1587
|
+
create(context) {
|
|
1588
|
+
let imports = createEmptyValibotImports();
|
|
1589
|
+
return {
|
|
1590
|
+
Program(node) {
|
|
1591
|
+
imports = collectValibotImports(node);
|
|
1592
|
+
},
|
|
1593
|
+
CallExpression(node) {
|
|
1594
|
+
if (!hasValibotImports(imports)) {
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
const parserName = getGuardedParserName(node, imports);
|
|
1598
|
+
if (!parserName || isInsideTryBlock(node)) {
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
context.report({
|
|
1602
|
+
node,
|
|
1603
|
+
messageId: "unguardedParserCall",
|
|
1604
|
+
data: {
|
|
1605
|
+
functionName: parserName
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
function getGuardedParserName(call, imports) {
|
|
1613
|
+
for (const functionName of GUARDED_PARSER_NAMES) {
|
|
1614
|
+
if (isValibotCall(call, imports, functionName)) {
|
|
1615
|
+
return functionName;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return null;
|
|
1619
|
+
}
|
|
1620
|
+
function isInsideTryBlock(node) {
|
|
1621
|
+
let current = node;
|
|
1622
|
+
while (current?.parent) {
|
|
1623
|
+
const parent = current.parent;
|
|
1624
|
+
if (parent.type === "TryStatement" && parent.block === current) {
|
|
1625
|
+
return true;
|
|
1626
|
+
}
|
|
1627
|
+
current = parent;
|
|
1628
|
+
}
|
|
1629
|
+
return false;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/rules/no-redundant-schema-wrappers.ts
|
|
1633
|
+
var DUPLICATE_WRAPPER_NAMES = /* @__PURE__ */ new Set([
|
|
1634
|
+
"optional",
|
|
1635
|
+
"nullable",
|
|
1636
|
+
"nullish",
|
|
1637
|
+
"nonOptional",
|
|
1638
|
+
"nonNullable",
|
|
1639
|
+
"nonNullish"
|
|
1640
|
+
]);
|
|
1641
|
+
var noRedundantSchemaWrappers = createRule({
|
|
1642
|
+
name: "no-redundant-schema-wrappers",
|
|
1643
|
+
meta: {
|
|
1644
|
+
type: "problem",
|
|
1645
|
+
docs: {
|
|
1646
|
+
description: "Disallow redundant nested Valibot schema wrappers."
|
|
1647
|
+
},
|
|
1648
|
+
fixable: "code",
|
|
1649
|
+
schema: [],
|
|
1650
|
+
messages: {
|
|
1651
|
+
redundantWrapper: "This Valibot wrapper is applied redundantly. Remove the outer wrapper."
|
|
1652
|
+
}
|
|
1653
|
+
},
|
|
1654
|
+
defaultOptions: [],
|
|
1655
|
+
create(context) {
|
|
1656
|
+
let imports = createEmptyValibotImports();
|
|
1657
|
+
const sourceCode = context.sourceCode;
|
|
1658
|
+
return {
|
|
1659
|
+
Program(node) {
|
|
1660
|
+
imports = collectValibotImports(node);
|
|
1661
|
+
},
|
|
1662
|
+
CallExpression(node) {
|
|
1663
|
+
if (!hasValibotImports(imports)) {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (node.arguments.length !== 1) {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
const innerCall = node.arguments[0];
|
|
1670
|
+
if (innerCall.type !== "CallExpression" || innerCall.arguments.length !== 1) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const duplicatedWrapperName = getDuplicatedWrapperName(
|
|
1674
|
+
node,
|
|
1675
|
+
innerCall,
|
|
1676
|
+
imports
|
|
1677
|
+
);
|
|
1678
|
+
if (!duplicatedWrapperName) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
context.report({
|
|
1682
|
+
node,
|
|
1683
|
+
messageId: "redundantWrapper",
|
|
1684
|
+
fix(fixer) {
|
|
1685
|
+
return fixer.replaceText(node, sourceCode.getText(innerCall));
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
});
|
|
1692
|
+
function getDuplicatedWrapperName(outerCall, innerCall, imports) {
|
|
1693
|
+
for (const wrapperName of DUPLICATE_WRAPPER_NAMES) {
|
|
1694
|
+
if (isValibotCall(outerCall, imports, wrapperName) && isValibotCall(innerCall, imports, wrapperName)) {
|
|
1695
|
+
return wrapperName;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
return null;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// src/rules/no-any-schema.ts
|
|
1702
|
+
var noAnySchema = createRule({
|
|
1703
|
+
name: "no-any-schema",
|
|
1704
|
+
meta: {
|
|
1705
|
+
type: "problem",
|
|
1706
|
+
docs: {
|
|
1707
|
+
description: "Disallow Valibot any() schemas."
|
|
1708
|
+
},
|
|
1709
|
+
schema: [],
|
|
1710
|
+
messages: {
|
|
1711
|
+
avoidAnySchema: "Avoid Valibot any() schemas. Prefer unknown() or a more precise schema."
|
|
1712
|
+
}
|
|
1713
|
+
},
|
|
1714
|
+
defaultOptions: [],
|
|
1715
|
+
create(context) {
|
|
1716
|
+
let imports = createEmptyValibotImports();
|
|
1717
|
+
return {
|
|
1718
|
+
Program(node) {
|
|
1719
|
+
imports = collectValibotImports(node);
|
|
1720
|
+
},
|
|
1721
|
+
CallExpression(node) {
|
|
1722
|
+
if (!hasValibotImports(imports)) {
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (!isValibotCall(node, imports, "any")) {
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
context.report({
|
|
1729
|
+
node,
|
|
1730
|
+
messageId: "avoidAnySchema"
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1737
|
+
// src/rules/no-loose-object.ts
|
|
1738
|
+
var OBJECT_SCHEMA_TYPES = ["object", "looseObject", "strictObject"];
|
|
1739
|
+
var DEFAULT_ALLOWED_OBJECT_SCHEMA_TYPES = ["object", "strictObject"];
|
|
1740
|
+
var noLooseObject = createRule({
|
|
1741
|
+
name: "no-loose-object",
|
|
1742
|
+
meta: {
|
|
1743
|
+
type: "suggestion",
|
|
1744
|
+
docs: {
|
|
1745
|
+
description: "Disallow disallowed Valibot object schema constructors such as looseObject()."
|
|
1746
|
+
},
|
|
1747
|
+
schema: [
|
|
1748
|
+
{
|
|
1749
|
+
type: "object",
|
|
1750
|
+
additionalProperties: false,
|
|
1751
|
+
properties: {
|
|
1752
|
+
allow: {
|
|
1753
|
+
type: "array",
|
|
1754
|
+
items: {
|
|
1755
|
+
type: "string",
|
|
1756
|
+
enum: [...OBJECT_SCHEMA_TYPES]
|
|
1757
|
+
},
|
|
1758
|
+
uniqueItems: true,
|
|
1759
|
+
minItems: 1
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
],
|
|
1764
|
+
messages: {
|
|
1765
|
+
disallowedObjectSchemaType: "Avoid Valibot {{disallowedType}}() here. Allowed object schema constructors: {{allowedTypes}}."
|
|
1766
|
+
}
|
|
1767
|
+
},
|
|
1768
|
+
defaultOptions: [
|
|
1769
|
+
{
|
|
1770
|
+
allow: [...DEFAULT_ALLOWED_OBJECT_SCHEMA_TYPES]
|
|
1771
|
+
}
|
|
1772
|
+
],
|
|
1773
|
+
create(context, [options]) {
|
|
1774
|
+
let imports = createEmptyValibotImports();
|
|
1775
|
+
const allowedTypes = new Set(
|
|
1776
|
+
options.allow ?? DEFAULT_ALLOWED_OBJECT_SCHEMA_TYPES
|
|
1777
|
+
);
|
|
1778
|
+
return {
|
|
1779
|
+
Program(node) {
|
|
1780
|
+
imports = collectValibotImports(node);
|
|
1781
|
+
},
|
|
1782
|
+
CallExpression(node) {
|
|
1783
|
+
if (!hasValibotImports(imports)) {
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
const callName = getValibotCallName(node, imports);
|
|
1787
|
+
if (!isObjectSchemaType(callName) || allowedTypes.has(callName)) {
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
context.report({
|
|
1791
|
+
node,
|
|
1792
|
+
messageId: "disallowedObjectSchemaType",
|
|
1793
|
+
data: {
|
|
1794
|
+
disallowedType: callName,
|
|
1795
|
+
allowedTypes: formatAllowedTypes(options.allow)
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
function isObjectSchemaType(value) {
|
|
1803
|
+
return value !== null && OBJECT_SCHEMA_TYPES.includes(value);
|
|
1804
|
+
}
|
|
1805
|
+
function formatAllowedTypes(allow) {
|
|
1806
|
+
const allowedTypes = allow ?? [...DEFAULT_ALLOWED_OBJECT_SCHEMA_TYPES];
|
|
1807
|
+
return allowedTypes.map((type) => `${type}()`).join(", ");
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// src/rules/no-unknown-schema.ts
|
|
1811
|
+
var noUnknownSchema = createRule({
|
|
1812
|
+
name: "no-unknown-schema",
|
|
1813
|
+
meta: {
|
|
1814
|
+
type: "problem",
|
|
1815
|
+
docs: {
|
|
1816
|
+
description: "Disallow Valibot unknown() schemas."
|
|
1817
|
+
},
|
|
1818
|
+
schema: [],
|
|
1819
|
+
messages: {
|
|
1820
|
+
avoidUnknownSchema: "Avoid Valibot unknown() schemas. Prefer a more precise schema."
|
|
1821
|
+
}
|
|
1822
|
+
},
|
|
1823
|
+
defaultOptions: [],
|
|
1824
|
+
create(context) {
|
|
1825
|
+
let imports = createEmptyValibotImports();
|
|
1826
|
+
return {
|
|
1827
|
+
Program(node) {
|
|
1828
|
+
imports = collectValibotImports(node);
|
|
1829
|
+
},
|
|
1830
|
+
CallExpression(node) {
|
|
1831
|
+
if (!hasValibotImports(imports)) {
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
if (!isValibotCall(node, imports, "unknown")) {
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
context.report({
|
|
1838
|
+
node,
|
|
1839
|
+
messageId: "avoidUnknownSchema"
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
// src/rules/no-instanceof-builtins.ts
|
|
1847
|
+
var BUILTIN_CONSTRUCTORS = /* @__PURE__ */ new Map([
|
|
1848
|
+
["Date", "date"],
|
|
1849
|
+
["Array", "array"],
|
|
1850
|
+
["Function", "function"],
|
|
1851
|
+
["Promise", "promise"],
|
|
1852
|
+
["Map", "map"],
|
|
1853
|
+
["Set", "set"],
|
|
1854
|
+
["String", "string"],
|
|
1855
|
+
["Number", "number"],
|
|
1856
|
+
["Boolean", "boolean"],
|
|
1857
|
+
["Symbol", "symbol"],
|
|
1858
|
+
["BigInt", "bigint"],
|
|
1859
|
+
["Blob", "blob"]
|
|
1860
|
+
]);
|
|
1861
|
+
var noInstanceofBuiltins = createRule({
|
|
1862
|
+
name: "no-instanceof-builtins",
|
|
1863
|
+
meta: {
|
|
1864
|
+
type: "suggestion",
|
|
1865
|
+
docs: {
|
|
1866
|
+
description: "Prefer primitive schema functions over instance(Constructor) for built-in types."
|
|
1867
|
+
},
|
|
1868
|
+
fixable: "code",
|
|
1869
|
+
schema: [],
|
|
1870
|
+
messages: {
|
|
1871
|
+
noInstanceofBuiltins: "Prefer the native Valibot schema {{preferred}}() instead of instance({{constructor}})."
|
|
1872
|
+
}
|
|
1873
|
+
},
|
|
1874
|
+
defaultOptions: [],
|
|
1875
|
+
create(context) {
|
|
1876
|
+
let imports = createEmptyValibotImports();
|
|
1877
|
+
return {
|
|
1878
|
+
Program(node) {
|
|
1879
|
+
imports = collectValibotImports(node);
|
|
1880
|
+
},
|
|
1881
|
+
CallExpression(node) {
|
|
1882
|
+
if (!hasValibotImports(imports)) {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
if (!isValibotCall(node, imports, "instance") || node.arguments.length !== 1) {
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
const arg = node.arguments[0];
|
|
1889
|
+
if (arg.type !== "Identifier") {
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
const constructorName = arg.name;
|
|
1893
|
+
const preferredSchema = BUILTIN_CONSTRUCTORS.get(constructorName);
|
|
1894
|
+
if (!preferredSchema) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
const preferredCalleeText = getPreferredCalleeText6(node, preferredSchema, imports);
|
|
1898
|
+
context.report({
|
|
1899
|
+
node,
|
|
1900
|
+
messageId: "noInstanceofBuiltins",
|
|
1901
|
+
data: {
|
|
1902
|
+
preferred: preferredSchema,
|
|
1903
|
+
constructor: constructorName
|
|
1904
|
+
},
|
|
1905
|
+
fix(fixer) {
|
|
1906
|
+
return fixer.replaceText(node, `${preferredCalleeText}()`);
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
}
|
|
1912
|
+
});
|
|
1913
|
+
function getPreferredCalleeText6(call, targetName, imports) {
|
|
1914
|
+
if (call.callee.type === "MemberExpression") {
|
|
1915
|
+
return `${sourceTextForMemberNamespace6(call)}.${targetName}`;
|
|
1916
|
+
}
|
|
1917
|
+
const localName = getLocalImportName6(imports, targetName);
|
|
1918
|
+
return localName ?? targetName;
|
|
1919
|
+
}
|
|
1920
|
+
function sourceTextForMemberNamespace6(call) {
|
|
1921
|
+
if (call.callee.type === "MemberExpression" && call.callee.object.type === "Identifier") {
|
|
1922
|
+
return call.callee.object.name;
|
|
1923
|
+
}
|
|
1924
|
+
return "v";
|
|
1925
|
+
}
|
|
1926
|
+
function getLocalImportName6(imports, importedName) {
|
|
1927
|
+
for (const [localName, importedValue] of imports.importedNames) {
|
|
1928
|
+
if (importedValue === importedName) {
|
|
1929
|
+
return localName;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
return void 0;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// src/rules/no-empty-pipe.ts
|
|
1936
|
+
var noEmptyPipe = createRule({
|
|
1937
|
+
name: "no-empty-pipe",
|
|
1938
|
+
meta: {
|
|
1939
|
+
type: "suggestion",
|
|
1940
|
+
docs: {
|
|
1941
|
+
description: "Disallow empty pipe() calls or pipe() calls with a single argument."
|
|
1942
|
+
},
|
|
1943
|
+
fixable: "code",
|
|
1944
|
+
schema: [],
|
|
1945
|
+
messages: {
|
|
1946
|
+
emptyPipe: "Avoid calling pipe() with no arguments.",
|
|
1947
|
+
redundantPipe: "Avoid calling pipe() with only a single schema. Remove the pipe() wrapper."
|
|
1948
|
+
}
|
|
1949
|
+
},
|
|
1950
|
+
defaultOptions: [],
|
|
1951
|
+
create(context) {
|
|
1952
|
+
let imports = createEmptyValibotImports();
|
|
1953
|
+
const sourceCode = context.sourceCode;
|
|
1954
|
+
return {
|
|
1955
|
+
Program(node) {
|
|
1956
|
+
imports = collectValibotImports(node);
|
|
1957
|
+
},
|
|
1958
|
+
CallExpression(node) {
|
|
1959
|
+
if (!hasValibotImports(imports)) {
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
if (!isValibotCall(node, imports, "pipe")) {
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
if (node.arguments.length === 0) {
|
|
1966
|
+
context.report({
|
|
1967
|
+
node,
|
|
1968
|
+
messageId: "emptyPipe"
|
|
1969
|
+
});
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
if (node.arguments.length === 1) {
|
|
1973
|
+
const singleArg = node.arguments[0];
|
|
1974
|
+
context.report({
|
|
1975
|
+
node,
|
|
1976
|
+
messageId: "redundantPipe",
|
|
1977
|
+
fix(fixer) {
|
|
1978
|
+
return fixer.replaceText(node, sourceCode.getText(singleArg));
|
|
1979
|
+
}
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
// src/rules/no-transform-in-record-key.ts
|
|
1988
|
+
var EXPLICIT_KEY_MUTATION_ACTION_NAMES = /* @__PURE__ */ new Set([
|
|
1989
|
+
"normalize",
|
|
1990
|
+
"rawTransform",
|
|
1991
|
+
"transform",
|
|
1992
|
+
"trim",
|
|
1993
|
+
"trimEnd",
|
|
1994
|
+
"trimStart"
|
|
1995
|
+
]);
|
|
1996
|
+
var noTransformInRecordKey = createRule({
|
|
1997
|
+
name: "no-transform-in-record-key",
|
|
1998
|
+
meta: {
|
|
1999
|
+
type: "problem",
|
|
2000
|
+
docs: {
|
|
2001
|
+
description: "Disallow transforms in record() key schemas, which can silently mutate keys and cause collisions."
|
|
2002
|
+
},
|
|
2003
|
+
schema: [],
|
|
2004
|
+
messages: {
|
|
2005
|
+
transformInRecordKey: "Avoid using '{{actionName}}()' in a record() key schema. Transformed record keys can change silently and collide during parsing."
|
|
2006
|
+
}
|
|
2007
|
+
},
|
|
2008
|
+
defaultOptions: [],
|
|
2009
|
+
create(context) {
|
|
2010
|
+
let imports = createEmptyValibotImports();
|
|
2011
|
+
const sourceCode = context.sourceCode;
|
|
2012
|
+
return {
|
|
2013
|
+
Program(node) {
|
|
2014
|
+
imports = collectValibotImports(node);
|
|
2015
|
+
},
|
|
2016
|
+
CallExpression(node) {
|
|
2017
|
+
if (!hasValibotImports(imports) || !isValibotCall(node, imports, "record")) {
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
for (const action of getTransformingKeyActions(
|
|
2021
|
+
node.arguments[0],
|
|
2022
|
+
imports,
|
|
2023
|
+
sourceCode
|
|
2024
|
+
)) {
|
|
2025
|
+
context.report({
|
|
2026
|
+
node: action.node,
|
|
2027
|
+
messageId: "transformInRecordKey",
|
|
2028
|
+
data: {
|
|
2029
|
+
actionName: action.name
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
function getTransformingKeyActions(node, imports, sourceCode, visitedNodes = /* @__PURE__ */ new Set()) {
|
|
2038
|
+
if (!node || visitedNodes.has(node)) {
|
|
2039
|
+
return [];
|
|
2040
|
+
}
|
|
2041
|
+
visitedNodes.add(node);
|
|
2042
|
+
if (node.type === "Identifier") {
|
|
2043
|
+
const resolvedExpression = resolveIdentifierExpression(node, sourceCode);
|
|
2044
|
+
return resolvedExpression ? getTransformingKeyActions(
|
|
2045
|
+
resolvedExpression,
|
|
2046
|
+
imports,
|
|
2047
|
+
sourceCode,
|
|
2048
|
+
visitedNodes
|
|
2049
|
+
) : [];
|
|
2050
|
+
}
|
|
2051
|
+
if (node.type !== "CallExpression") {
|
|
2052
|
+
return [];
|
|
2053
|
+
}
|
|
2054
|
+
const transformingActions = isValibotCall(node, imports, "pipe") ? getPipeActionDescriptors(node, imports, sourceCode).filter(
|
|
2055
|
+
(action) => isTransformingKeyActionName(action.name)
|
|
2056
|
+
) : [];
|
|
2057
|
+
return [
|
|
2058
|
+
...transformingActions,
|
|
2059
|
+
...node.arguments.flatMap(
|
|
2060
|
+
(argument) => getTransformingKeyActions(argument, imports, sourceCode, visitedNodes)
|
|
2061
|
+
)
|
|
2062
|
+
];
|
|
2063
|
+
}
|
|
2064
|
+
function resolveIdentifierExpression(node, sourceCode) {
|
|
2065
|
+
let scope = sourceCode.getScope(node);
|
|
2066
|
+
while (scope) {
|
|
2067
|
+
const definition = scope.set.get(node.name)?.defs[0];
|
|
2068
|
+
if (definition?.node.type === "VariableDeclarator") {
|
|
2069
|
+
return definition.node.init ?? null;
|
|
2070
|
+
}
|
|
2071
|
+
scope = scope.upper;
|
|
2072
|
+
}
|
|
2073
|
+
return null;
|
|
2074
|
+
}
|
|
2075
|
+
function isTransformingKeyActionName(name) {
|
|
2076
|
+
return EXPLICIT_KEY_MUTATION_ACTION_NAMES.has(name) || /^to[A-Z]/u.test(name);
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
// src/utils/issue-message-signatures.ts
|
|
2080
|
+
var ISSUE_MESSAGE_PARAMETER_INDEX_ZERO = /* @__PURE__ */ new Set([
|
|
2081
|
+
"base64",
|
|
2082
|
+
"bic",
|
|
2083
|
+
"bigint",
|
|
2084
|
+
"blob",
|
|
2085
|
+
"boolean",
|
|
2086
|
+
"creditCard",
|
|
2087
|
+
"cuid2",
|
|
2088
|
+
"date",
|
|
2089
|
+
"decimal",
|
|
2090
|
+
"digits",
|
|
2091
|
+
"domain",
|
|
2092
|
+
"email",
|
|
2093
|
+
"emoji",
|
|
2094
|
+
"empty",
|
|
2095
|
+
"file",
|
|
2096
|
+
"finite",
|
|
2097
|
+
"function",
|
|
2098
|
+
"hexadecimal",
|
|
2099
|
+
"hexColor",
|
|
2100
|
+
"imei",
|
|
2101
|
+
"integer",
|
|
2102
|
+
"ip",
|
|
2103
|
+
"ipv4",
|
|
2104
|
+
"ipv6",
|
|
2105
|
+
"isbn",
|
|
2106
|
+
"isoDate",
|
|
2107
|
+
"isoDateTime",
|
|
2108
|
+
"isoDateTimeSecond",
|
|
2109
|
+
"isoTime",
|
|
2110
|
+
"isoTimeSecond",
|
|
2111
|
+
"isoTimestamp",
|
|
2112
|
+
"isoWeek",
|
|
2113
|
+
"isrc",
|
|
2114
|
+
"jwsCompact",
|
|
2115
|
+
"mac",
|
|
2116
|
+
"mac48",
|
|
2117
|
+
"mac64",
|
|
2118
|
+
"nan",
|
|
2119
|
+
"nanoid",
|
|
2120
|
+
"never",
|
|
2121
|
+
"nonEmpty",
|
|
2122
|
+
"null",
|
|
2123
|
+
"number",
|
|
2124
|
+
"octal",
|
|
2125
|
+
"promise",
|
|
2126
|
+
"rfcEmail",
|
|
2127
|
+
"safeInteger",
|
|
2128
|
+
"slug",
|
|
2129
|
+
"string",
|
|
2130
|
+
"symbol",
|
|
2131
|
+
"toBigint",
|
|
2132
|
+
"toDate",
|
|
2133
|
+
"toNumber",
|
|
2134
|
+
"toString",
|
|
2135
|
+
"ulid",
|
|
2136
|
+
"undefined",
|
|
2137
|
+
"url",
|
|
2138
|
+
"uuid",
|
|
2139
|
+
"void"
|
|
2140
|
+
]);
|
|
2141
|
+
var ISSUE_MESSAGE_PARAMETER_INDEX_ONE = /* @__PURE__ */ new Set([
|
|
2142
|
+
"array",
|
|
2143
|
+
"arrayAsync",
|
|
2144
|
+
"bytes",
|
|
2145
|
+
"check",
|
|
2146
|
+
"checkAsync",
|
|
2147
|
+
"checkItems",
|
|
2148
|
+
"checkItemsAsync",
|
|
2149
|
+
"custom",
|
|
2150
|
+
"customAsync",
|
|
2151
|
+
"endsWith",
|
|
2152
|
+
"entries",
|
|
2153
|
+
"enum",
|
|
2154
|
+
"everyItem",
|
|
2155
|
+
"excludes",
|
|
2156
|
+
"graphemes",
|
|
2157
|
+
"gtValue",
|
|
2158
|
+
"guard",
|
|
2159
|
+
"hash",
|
|
2160
|
+
"includes",
|
|
2161
|
+
"instance",
|
|
2162
|
+
"intersect",
|
|
2163
|
+
"intersectAsync",
|
|
2164
|
+
"keyof",
|
|
2165
|
+
"length",
|
|
2166
|
+
"literal",
|
|
2167
|
+
"looseObject",
|
|
2168
|
+
"looseObjectAsync",
|
|
2169
|
+
"looseTuple",
|
|
2170
|
+
"looseTupleAsync",
|
|
2171
|
+
"ltValue",
|
|
2172
|
+
"maxBytes",
|
|
2173
|
+
"maxEntries",
|
|
2174
|
+
"maxGraphemes",
|
|
2175
|
+
"maxLength",
|
|
2176
|
+
"maxSize",
|
|
2177
|
+
"maxValue",
|
|
2178
|
+
"mimeType",
|
|
2179
|
+
"minBytes",
|
|
2180
|
+
"minEntries",
|
|
2181
|
+
"minGraphemes",
|
|
2182
|
+
"minLength",
|
|
2183
|
+
"minSize",
|
|
2184
|
+
"minValue",
|
|
2185
|
+
"multipleOf",
|
|
2186
|
+
"nonNullable",
|
|
2187
|
+
"nonNullableAsync",
|
|
2188
|
+
"nonNullish",
|
|
2189
|
+
"nonNullishAsync",
|
|
2190
|
+
"nonOptional",
|
|
2191
|
+
"nonOptionalAsync",
|
|
2192
|
+
"notBytes",
|
|
2193
|
+
"notEntries",
|
|
2194
|
+
"notGraphemes",
|
|
2195
|
+
"notLength",
|
|
2196
|
+
"notSize",
|
|
2197
|
+
"notValue",
|
|
2198
|
+
"notValues",
|
|
2199
|
+
"object",
|
|
2200
|
+
"objectAsync",
|
|
2201
|
+
"parseBoolean",
|
|
2202
|
+
"parseJson",
|
|
2203
|
+
"picklist",
|
|
2204
|
+
"regex",
|
|
2205
|
+
"required",
|
|
2206
|
+
"requiredAsync",
|
|
2207
|
+
"set",
|
|
2208
|
+
"setAsync",
|
|
2209
|
+
"size",
|
|
2210
|
+
"someItem",
|
|
2211
|
+
"startsWith",
|
|
2212
|
+
"strictObject",
|
|
2213
|
+
"strictObjectAsync",
|
|
2214
|
+
"strictTuple",
|
|
2215
|
+
"strictTupleAsync",
|
|
2216
|
+
"stringifyJson",
|
|
2217
|
+
"tuple",
|
|
2218
|
+
"tupleAsync",
|
|
2219
|
+
"union",
|
|
2220
|
+
"unionAsync",
|
|
2221
|
+
"value",
|
|
2222
|
+
"values"
|
|
2223
|
+
]);
|
|
2224
|
+
var ISSUE_MESSAGE_PARAMETER_INDEX_TWO = /* @__PURE__ */ new Set([
|
|
2225
|
+
"map",
|
|
2226
|
+
"mapAsync",
|
|
2227
|
+
"maxWords",
|
|
2228
|
+
"minWords",
|
|
2229
|
+
"notWords",
|
|
2230
|
+
"objectWithRest",
|
|
2231
|
+
"objectWithRestAsync",
|
|
2232
|
+
"partialCheck",
|
|
2233
|
+
"partialCheckAsync",
|
|
2234
|
+
"record",
|
|
2235
|
+
"recordAsync",
|
|
2236
|
+
"tupleWithRest",
|
|
2237
|
+
"tupleWithRestAsync",
|
|
2238
|
+
"variant",
|
|
2239
|
+
"variantAsync",
|
|
2240
|
+
"words"
|
|
2241
|
+
]);
|
|
2242
|
+
function getIssueMessageParameterIndex(name) {
|
|
2243
|
+
if (ISSUE_MESSAGE_PARAMETER_INDEX_ZERO.has(name)) {
|
|
2244
|
+
return 0;
|
|
2245
|
+
}
|
|
2246
|
+
if (ISSUE_MESSAGE_PARAMETER_INDEX_ONE.has(name)) {
|
|
2247
|
+
return 1;
|
|
2248
|
+
}
|
|
2249
|
+
if (ISSUE_MESSAGE_PARAMETER_INDEX_TWO.has(name)) {
|
|
2250
|
+
return 2;
|
|
2251
|
+
}
|
|
2252
|
+
return null;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
// src/rules/require-issue-messages.ts
|
|
2256
|
+
var requireIssueMessages = createRule({
|
|
2257
|
+
name: "require-issue-messages",
|
|
2258
|
+
meta: {
|
|
2259
|
+
type: "suggestion",
|
|
2260
|
+
docs: {
|
|
2261
|
+
description: "Require explicit custom issue messages on Valibot schemas and issue-producing actions."
|
|
2262
|
+
},
|
|
2263
|
+
schema: [],
|
|
2264
|
+
messages: {
|
|
2265
|
+
missingIssueMessage: "Provide a custom issue message for '{{name}}()' using its message argument or a direct message() wrapper."
|
|
2266
|
+
}
|
|
2267
|
+
},
|
|
2268
|
+
defaultOptions: [],
|
|
2269
|
+
create(context) {
|
|
2270
|
+
let imports = createEmptyValibotImports();
|
|
2271
|
+
return {
|
|
2272
|
+
Program(node) {
|
|
2273
|
+
imports = collectValibotImports(node);
|
|
2274
|
+
},
|
|
2275
|
+
CallExpression(node) {
|
|
2276
|
+
if (!hasValibotImports(imports)) {
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
const callName = getValibotCallName(node, imports);
|
|
2280
|
+
if (!callName) {
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
const messageParameterIndex = getIssueMessageParameterIndex(callName);
|
|
2284
|
+
if (messageParameterIndex === null) {
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
if (hasInlineIssueMessage(node, messageParameterIndex) || isWrappedByDirectMessageCall(node, imports)) {
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
context.report({
|
|
2291
|
+
node,
|
|
2292
|
+
messageId: "missingIssueMessage",
|
|
2293
|
+
data: {
|
|
2294
|
+
name: callName
|
|
2295
|
+
}
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
});
|
|
2301
|
+
function hasInlineIssueMessage(node, messageParameterIndex) {
|
|
2302
|
+
const messageArgument = node.arguments[messageParameterIndex];
|
|
2303
|
+
return Boolean(messageArgument) && !isMissingMessageExpression(messageArgument);
|
|
2304
|
+
}
|
|
2305
|
+
function isWrappedByDirectMessageCall(node, imports) {
|
|
2306
|
+
const parent = node.parent;
|
|
2307
|
+
return parent?.type === "CallExpression" && parent.arguments[0] === node && isValibotCall(parent, imports, "message") && Boolean(parent.arguments[1]) && !isMissingMessageExpression(parent.arguments[1]);
|
|
2308
|
+
}
|
|
2309
|
+
function isMissingMessageExpression(node) {
|
|
2310
|
+
return node.type === "Identifier" && node.name === "undefined" || node.type === "Literal" && (node.value === null || node.value === void 0) || node.type === "UnaryExpression" && node.operator === "void";
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// src/rules/registry.ts
|
|
2314
|
+
var ruleRegistry = [
|
|
2315
|
+
{
|
|
2316
|
+
name: "no-any-schema",
|
|
2317
|
+
rule: noAnySchema,
|
|
2318
|
+
configs: {
|
|
2319
|
+
strict: "warn"
|
|
2320
|
+
}
|
|
2321
|
+
},
|
|
2322
|
+
{
|
|
2323
|
+
name: "no-unknown-schema",
|
|
2324
|
+
rule: noUnknownSchema,
|
|
2325
|
+
configs: {
|
|
2326
|
+
strict: "warn"
|
|
2327
|
+
}
|
|
2328
|
+
},
|
|
2329
|
+
{
|
|
2330
|
+
name: "no-unguarded-parse",
|
|
2331
|
+
rule: noUnguardedParse,
|
|
2332
|
+
configs: {
|
|
2333
|
+
recommended: "error",
|
|
2334
|
+
strict: "error"
|
|
2335
|
+
}
|
|
2336
|
+
},
|
|
2337
|
+
{
|
|
2338
|
+
name: "no-redundant-schema-wrappers",
|
|
2339
|
+
rule: noRedundantSchemaWrappers,
|
|
2340
|
+
configs: {
|
|
2341
|
+
recommended: "error",
|
|
2342
|
+
strict: "error"
|
|
2343
|
+
}
|
|
2344
|
+
},
|
|
2345
|
+
{
|
|
2346
|
+
name: "prefer-nullish",
|
|
2347
|
+
rule: preferNullish,
|
|
2348
|
+
configs: {
|
|
2349
|
+
recommended: "warn",
|
|
2350
|
+
strict: "warn"
|
|
2351
|
+
}
|
|
2352
|
+
},
|
|
2353
|
+
{
|
|
2354
|
+
name: "prefer-nullable-over-union-null",
|
|
2355
|
+
rule: preferNullableOverUnionNull,
|
|
2356
|
+
configs: {
|
|
2357
|
+
recommended: "warn",
|
|
2358
|
+
strict: "warn"
|
|
2359
|
+
}
|
|
2360
|
+
},
|
|
2361
|
+
{
|
|
2362
|
+
name: "prefer-optional-over-union-undefined",
|
|
2363
|
+
rule: preferOptionalOverUnionUndefined,
|
|
2364
|
+
configs: {
|
|
2365
|
+
recommended: "warn",
|
|
2366
|
+
strict: "warn"
|
|
2367
|
+
}
|
|
2368
|
+
},
|
|
2369
|
+
{
|
|
2370
|
+
name: "no-duplicate-pipe-actions",
|
|
2371
|
+
rule: noDuplicatePipeActions,
|
|
2372
|
+
configs: {
|
|
2373
|
+
recommended: "warn",
|
|
2374
|
+
strict: "warn"
|
|
2375
|
+
}
|
|
2376
|
+
},
|
|
2377
|
+
{
|
|
2378
|
+
name: "require-issue-messages",
|
|
2379
|
+
rule: requireIssueMessages,
|
|
2380
|
+
configs: {
|
|
2381
|
+
strict: "warn"
|
|
2382
|
+
}
|
|
2383
|
+
},
|
|
2384
|
+
{
|
|
2385
|
+
name: "no-recreated-schemas",
|
|
2386
|
+
rule: noRecreatedSchemas,
|
|
2387
|
+
configs: {
|
|
2388
|
+
strict: "warn"
|
|
2389
|
+
}
|
|
2390
|
+
},
|
|
2391
|
+
{
|
|
2392
|
+
name: "no-schema-as-type",
|
|
2393
|
+
rule: noSchemaAsType,
|
|
2394
|
+
configs: {
|
|
2395
|
+
strict: "error"
|
|
2396
|
+
},
|
|
2397
|
+
typeScriptOnly: true
|
|
2398
|
+
},
|
|
2399
|
+
{
|
|
2400
|
+
name: "consistent-import",
|
|
2401
|
+
rule: consistentImport,
|
|
2402
|
+
configs: {
|
|
2403
|
+
stylistic: "warn"
|
|
2404
|
+
}
|
|
2405
|
+
},
|
|
2406
|
+
{
|
|
2407
|
+
name: "consistent-schema-convention",
|
|
2408
|
+
rule: consistentSchemaConvention,
|
|
2409
|
+
configs: {
|
|
2410
|
+
stylistic: "warn"
|
|
2411
|
+
}
|
|
2412
|
+
},
|
|
2413
|
+
{
|
|
2414
|
+
name: "prefer-variant",
|
|
2415
|
+
rule: preferVariant,
|
|
2416
|
+
configs: {
|
|
2417
|
+
stylistic: "warn"
|
|
2418
|
+
}
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
name: "prefer-picklist",
|
|
2422
|
+
rule: preferPicklist,
|
|
2423
|
+
configs: {
|
|
2424
|
+
stylistic: "warn"
|
|
2425
|
+
}
|
|
2426
|
+
},
|
|
2427
|
+
{
|
|
2428
|
+
name: "no-loose-object",
|
|
2429
|
+
rule: noLooseObject,
|
|
2430
|
+
configs: {
|
|
2431
|
+
strict: "warn"
|
|
2432
|
+
}
|
|
2433
|
+
},
|
|
2434
|
+
{
|
|
2435
|
+
name: "no-instanceof-builtins",
|
|
2436
|
+
rule: noInstanceofBuiltins,
|
|
2437
|
+
configs: {
|
|
2438
|
+
recommended: "error",
|
|
2439
|
+
strict: "error"
|
|
2440
|
+
}
|
|
2441
|
+
},
|
|
2442
|
+
{
|
|
2443
|
+
name: "no-empty-pipe",
|
|
2444
|
+
rule: noEmptyPipe,
|
|
2445
|
+
configs: {
|
|
2446
|
+
recommended: "error",
|
|
2447
|
+
strict: "error"
|
|
2448
|
+
}
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
name: "no-transform-in-record-key",
|
|
2452
|
+
rule: noTransformInRecordKey,
|
|
2453
|
+
configs: {
|
|
2454
|
+
recommended: "error",
|
|
2455
|
+
strict: "error"
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
];
|
|
2459
|
+
var rules = Object.fromEntries(
|
|
2460
|
+
ruleRegistry.map(({ name, rule }) => [name, rule])
|
|
2461
|
+
);
|
|
2462
|
+
function getRulesForConfig(configName) {
|
|
2463
|
+
return Object.fromEntries(
|
|
2464
|
+
ruleRegistry.flatMap(({ name, configs: configs2 }) => {
|
|
2465
|
+
const entry = configs2[configName];
|
|
2466
|
+
return entry ? [[`valibot/${name}`, entry]] : [];
|
|
2467
|
+
})
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
function getRuleNamesForConfig(configName) {
|
|
2471
|
+
return ruleRegistry.filter(({ configs: configs2 }) => configName in configs2).map(({ name }) => name).sort();
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// src/configs/flat/recommended.ts
|
|
2475
|
+
function createRecommendedConfig(plugin2) {
|
|
2476
|
+
return [
|
|
2477
|
+
{
|
|
2478
|
+
name: "valibot/recommended",
|
|
2479
|
+
plugins: {
|
|
2480
|
+
valibot: plugin2
|
|
2481
|
+
},
|
|
2482
|
+
rules: getRulesForConfig("recommended")
|
|
2483
|
+
}
|
|
2484
|
+
];
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
// src/configs/flat/strict.ts
|
|
2488
|
+
function createStrictConfig(plugin2) {
|
|
2489
|
+
return [
|
|
2490
|
+
{
|
|
2491
|
+
name: "valibot/strict",
|
|
2492
|
+
plugins: {
|
|
2493
|
+
valibot: plugin2
|
|
2494
|
+
},
|
|
2495
|
+
rules: getRulesForConfig("strict")
|
|
2496
|
+
}
|
|
2497
|
+
];
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// src/configs/flat/stylistic.ts
|
|
2501
|
+
function createStylisticConfig(plugin2) {
|
|
2502
|
+
return [
|
|
2503
|
+
{
|
|
2504
|
+
name: "valibot/stylistic",
|
|
2505
|
+
plugins: {
|
|
2506
|
+
valibot: plugin2
|
|
2507
|
+
},
|
|
2508
|
+
rules: getRulesForConfig("stylistic")
|
|
2509
|
+
}
|
|
2510
|
+
];
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
// src/configs/flat/index.ts
|
|
2514
|
+
function createFlatConfigs(plugin2) {
|
|
2515
|
+
return {
|
|
2516
|
+
recommended: createRecommendedConfig(plugin2),
|
|
2517
|
+
strict: createStrictConfig(plugin2),
|
|
2518
|
+
stylistic: createStylisticConfig(plugin2)
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
// src/configs/legacy/recommended.ts
|
|
2523
|
+
var recommended = {
|
|
2524
|
+
plugins: ["valibot"],
|
|
2525
|
+
rules: getRulesForConfig("recommended")
|
|
2526
|
+
};
|
|
2527
|
+
|
|
2528
|
+
// src/configs/legacy/strict.ts
|
|
2529
|
+
var strict = {
|
|
2530
|
+
plugins: ["valibot"],
|
|
2531
|
+
rules: getRulesForConfig("strict")
|
|
2532
|
+
};
|
|
2533
|
+
|
|
2534
|
+
// src/configs/legacy/stylistic.ts
|
|
2535
|
+
var stylistic = {
|
|
2536
|
+
plugins: ["valibot"],
|
|
2537
|
+
rules: getRulesForConfig("stylistic")
|
|
2538
|
+
};
|
|
2539
|
+
|
|
2540
|
+
// src/configs/legacy/index.ts
|
|
2541
|
+
var legacyConfigs = {
|
|
2542
|
+
recommended,
|
|
2543
|
+
strict,
|
|
2544
|
+
stylistic
|
|
2545
|
+
};
|
|
2546
|
+
|
|
2547
|
+
// src/plugin.ts
|
|
2548
|
+
var basePlugin = {
|
|
2549
|
+
meta: {
|
|
2550
|
+
name: "eslint-plugin-valibot",
|
|
2551
|
+
version: package_default.version
|
|
2552
|
+
},
|
|
2553
|
+
rules,
|
|
2554
|
+
configs: legacyConfigs
|
|
2555
|
+
};
|
|
2556
|
+
var flatConfigs = createFlatConfigs(basePlugin);
|
|
2557
|
+
var configs = legacyConfigs;
|
|
2558
|
+
var plugin = {
|
|
2559
|
+
...basePlugin,
|
|
2560
|
+
flatConfigs
|
|
2561
|
+
};
|
|
2562
|
+
var plugin_default = plugin;
|
|
2563
|
+
export {
|
|
2564
|
+
configs,
|
|
2565
|
+
consistentImport,
|
|
2566
|
+
consistentSchemaConvention,
|
|
2567
|
+
plugin_default as default,
|
|
2568
|
+
flatConfigs,
|
|
2569
|
+
getRuleNamesForConfig,
|
|
2570
|
+
getRulesForConfig,
|
|
2571
|
+
noAnySchema,
|
|
2572
|
+
noDuplicatePipeActions,
|
|
2573
|
+
noLooseObject,
|
|
2574
|
+
noRecreatedSchemas,
|
|
2575
|
+
noRedundantSchemaWrappers,
|
|
2576
|
+
noSchemaAsType,
|
|
2577
|
+
noTransformInRecordKey,
|
|
2578
|
+
noUnguardedParse,
|
|
2579
|
+
noUnknownSchema,
|
|
2580
|
+
preferNullableOverUnionNull,
|
|
2581
|
+
preferNullish,
|
|
2582
|
+
preferOptionalOverUnionUndefined,
|
|
2583
|
+
preferPicklist,
|
|
2584
|
+
preferVariant,
|
|
2585
|
+
ruleRegistry,
|
|
2586
|
+
rules
|
|
2587
|
+
};
|
|
2588
|
+
//# sourceMappingURL=index.mjs.map
|