@sarj/eslint-plugin 15.6.0 → 15.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.cjs +322 -17
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +322 -17
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,4 +8,6 @@ Custom ESLint rules for hypermodern TypeScript / React / Next.js projects
|
|
|
8
8
|
npm install --save-dev @sarj/eslint-plugin
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Rules and configuration are documented in the generated rule directory.
|
|
12
|
+
|
|
11
13
|
[Documentation](https://code-standards.sarj.ai/rules/eslint/) · [Source](https://github.com/sarj-ai/standards)
|
package/dist/index.cjs
CHANGED
|
@@ -5648,9 +5648,9 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5648
5648
|
continue;
|
|
5649
5649
|
}
|
|
5650
5650
|
if ((paramTags.length > 0 || returnTags.length > 0) && documentsTypedFunction(sourceCode, comment)) continue;
|
|
5651
|
-
const
|
|
5651
|
+
const nameTokens2 = tokensOf([declaration.name]);
|
|
5652
5652
|
const paramTokens = tokensOf(declaration.params);
|
|
5653
|
-
const known = /* @__PURE__ */ new Set([...
|
|
5653
|
+
const known = /* @__PURE__ */ new Set([...nameTokens2, ...paramTokens]);
|
|
5654
5654
|
let addsNothing = covered(describedText, known);
|
|
5655
5655
|
for (const tag of paramTags) {
|
|
5656
5656
|
const text = tag.text.replace(/^\{[^}]*\}\s*/, "");
|
|
@@ -5665,7 +5665,7 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5665
5665
|
addsNothing = false;
|
|
5666
5666
|
break;
|
|
5667
5667
|
}
|
|
5668
|
-
const own = /* @__PURE__ */ new Set([...splitIdentifier(path.split(".").pop() ?? ""), ...
|
|
5668
|
+
const own = /* @__PURE__ */ new Set([...splitIdentifier(path.split(".").pop() ?? ""), ...nameTokens2]);
|
|
5669
5669
|
if (!covered(match[2] ?? "", own)) {
|
|
5670
5670
|
addsNothing = false;
|
|
5671
5671
|
break;
|
|
@@ -12254,6 +12254,14 @@ var OPTIONAL_MODIFIERS = /* @__PURE__ */ new Set([
|
|
|
12254
12254
|
"nullish"
|
|
12255
12255
|
]);
|
|
12256
12256
|
var NULLABLE_MODIFIERS = /* @__PURE__ */ new Set(["nullable", "nullish"]);
|
|
12257
|
+
var DOMAIN_PRESERVING_MODIFIERS = /* @__PURE__ */ new Set([
|
|
12258
|
+
...SHAPE_PRESERVING_METHODS,
|
|
12259
|
+
...OPTIONAL_MODIFIERS,
|
|
12260
|
+
...NULLABLE_MODIFIERS,
|
|
12261
|
+
"catch",
|
|
12262
|
+
"default",
|
|
12263
|
+
"prefault"
|
|
12264
|
+
]);
|
|
12257
12265
|
var RESHAPING_MODIFIERS = /* @__PURE__ */ new Set([
|
|
12258
12266
|
"transform",
|
|
12259
12267
|
"pipe",
|
|
@@ -12314,6 +12322,107 @@ var LEAF_NODE_TYPES = {
|
|
|
12314
12322
|
discriminatedUnion: [import_utils61.AST_NODE_TYPES.TSUnionType, import_utils61.AST_NODE_TYPES.TSTypeReference],
|
|
12315
12323
|
intersection: [import_utils61.AST_NODE_TYPES.TSIntersectionType, import_utils61.AST_NODE_TYPES.TSTypeReference]
|
|
12316
12324
|
};
|
|
12325
|
+
function primitiveLiteralKey(node) {
|
|
12326
|
+
if (node.type !== import_utils61.AST_NODE_TYPES.Literal) {
|
|
12327
|
+
return null;
|
|
12328
|
+
}
|
|
12329
|
+
if (node.value === null) {
|
|
12330
|
+
return "null";
|
|
12331
|
+
}
|
|
12332
|
+
switch (typeof node.value) {
|
|
12333
|
+
case "string":
|
|
12334
|
+
return `string:${node.value}`;
|
|
12335
|
+
case "number":
|
|
12336
|
+
return `number:${String(node.value)}`;
|
|
12337
|
+
case "boolean":
|
|
12338
|
+
return `boolean:${String(node.value)}`;
|
|
12339
|
+
case "bigint":
|
|
12340
|
+
return `bigint:${String(node.value)}`;
|
|
12341
|
+
default:
|
|
12342
|
+
return null;
|
|
12343
|
+
}
|
|
12344
|
+
}
|
|
12345
|
+
function exactDomain(keys) {
|
|
12346
|
+
if (keys.length === 0 || keys.some((key) => key === null)) {
|
|
12347
|
+
return null;
|
|
12348
|
+
}
|
|
12349
|
+
const domain = new Set(keys);
|
|
12350
|
+
return domain.size === keys.length ? domain : null;
|
|
12351
|
+
}
|
|
12352
|
+
function staticZodDomain(leaf, call) {
|
|
12353
|
+
if (leaf === null || call === null) {
|
|
12354
|
+
return void 0;
|
|
12355
|
+
}
|
|
12356
|
+
if (leaf === "literal") {
|
|
12357
|
+
const [argument] = call.arguments;
|
|
12358
|
+
if (argument === void 0 || argument.type === import_utils61.AST_NODE_TYPES.SpreadElement) {
|
|
12359
|
+
return null;
|
|
12360
|
+
}
|
|
12361
|
+
if (argument.type === import_utils61.AST_NODE_TYPES.ArrayExpression) {
|
|
12362
|
+
return exactDomain(
|
|
12363
|
+
argument.elements.map(
|
|
12364
|
+
(element) => element === null || element.type === import_utils61.AST_NODE_TYPES.SpreadElement ? null : primitiveLiteralKey(element)
|
|
12365
|
+
)
|
|
12366
|
+
);
|
|
12367
|
+
}
|
|
12368
|
+
return exactDomain([primitiveLiteralKey(argument)]);
|
|
12369
|
+
}
|
|
12370
|
+
if (leaf === "enum") {
|
|
12371
|
+
const [argument] = call.arguments;
|
|
12372
|
+
if (argument === void 0 || argument.type === import_utils61.AST_NODE_TYPES.SpreadElement) {
|
|
12373
|
+
return null;
|
|
12374
|
+
}
|
|
12375
|
+
if (argument.type === import_utils61.AST_NODE_TYPES.ArrayExpression) {
|
|
12376
|
+
return exactDomain(
|
|
12377
|
+
argument.elements.map((element) => {
|
|
12378
|
+
if (element === null || element.type === import_utils61.AST_NODE_TYPES.SpreadElement) {
|
|
12379
|
+
return null;
|
|
12380
|
+
}
|
|
12381
|
+
const key = primitiveLiteralKey(element);
|
|
12382
|
+
return key?.startsWith("string:") === true ? key : null;
|
|
12383
|
+
})
|
|
12384
|
+
);
|
|
12385
|
+
}
|
|
12386
|
+
if (argument.type === import_utils61.AST_NODE_TYPES.ObjectExpression) {
|
|
12387
|
+
return exactDomain(
|
|
12388
|
+
argument.properties.map((property) => {
|
|
12389
|
+
if (property.type !== import_utils61.AST_NODE_TYPES.Property || property.computed || property.kind !== "init" || property.method || property.shorthand) {
|
|
12390
|
+
return null;
|
|
12391
|
+
}
|
|
12392
|
+
const key = primitiveLiteralKey(property.value);
|
|
12393
|
+
return key?.startsWith("string:") === true ? key : null;
|
|
12394
|
+
})
|
|
12395
|
+
);
|
|
12396
|
+
}
|
|
12397
|
+
return null;
|
|
12398
|
+
}
|
|
12399
|
+
if (leaf === "nativeEnum" || leaf === "union" || leaf === "discriminatedUnion") {
|
|
12400
|
+
return null;
|
|
12401
|
+
}
|
|
12402
|
+
return void 0;
|
|
12403
|
+
}
|
|
12404
|
+
function sameDomain(left, right) {
|
|
12405
|
+
if (left.size !== right.size) {
|
|
12406
|
+
return false;
|
|
12407
|
+
}
|
|
12408
|
+
for (const value of left) {
|
|
12409
|
+
if (!right.has(value)) {
|
|
12410
|
+
return false;
|
|
12411
|
+
}
|
|
12412
|
+
}
|
|
12413
|
+
return true;
|
|
12414
|
+
}
|
|
12415
|
+
function isExportedDeclaration(node) {
|
|
12416
|
+
return node.parent?.type === import_utils61.AST_NODE_TYPES.ExportNamedDeclaration;
|
|
12417
|
+
}
|
|
12418
|
+
function isModuleLevelConst(node) {
|
|
12419
|
+
const declaration = node.parent;
|
|
12420
|
+
if (declaration.type !== import_utils61.AST_NODE_TYPES.VariableDeclaration || declaration.kind !== "const") {
|
|
12421
|
+
return false;
|
|
12422
|
+
}
|
|
12423
|
+
const container = declaration.parent;
|
|
12424
|
+
return container.type === import_utils61.AST_NODE_TYPES.Program || container.type === import_utils61.AST_NODE_TYPES.ExportNamedDeclaration && container.parent.type === import_utils61.AST_NODE_TYPES.Program;
|
|
12425
|
+
}
|
|
12317
12426
|
function normalizeSchemaName(name) {
|
|
12318
12427
|
return name.replace(/Schema$/i, "").replace(/^Z(?=[A-Z])/, "").toLowerCase();
|
|
12319
12428
|
}
|
|
@@ -12344,7 +12453,18 @@ function unwrapNullish(annotation) {
|
|
|
12344
12453
|
}
|
|
12345
12454
|
return { core: rest.length === 0 ? null : annotation, nullable };
|
|
12346
12455
|
}
|
|
12347
|
-
function leafAgrees(
|
|
12456
|
+
function leafAgrees(field, annotation) {
|
|
12457
|
+
if (field.domain === null) {
|
|
12458
|
+
return false;
|
|
12459
|
+
}
|
|
12460
|
+
if (field.domain !== void 0) {
|
|
12461
|
+
if (annotation === null) {
|
|
12462
|
+
return false;
|
|
12463
|
+
}
|
|
12464
|
+
const annotationDomain = typeLiteralDomain(annotation);
|
|
12465
|
+
return annotationDomain !== null && sameDomain(field.domain, annotationDomain);
|
|
12466
|
+
}
|
|
12467
|
+
const { leaf } = field;
|
|
12348
12468
|
if (leaf === null || annotation === null) {
|
|
12349
12469
|
return null;
|
|
12350
12470
|
}
|
|
@@ -12361,6 +12481,47 @@ function leafAgrees(leaf, annotation) {
|
|
|
12361
12481
|
}
|
|
12362
12482
|
return expected.includes(core.type);
|
|
12363
12483
|
}
|
|
12484
|
+
function typeLiteralDomain(annotation) {
|
|
12485
|
+
const members = annotation.type === import_utils61.AST_NODE_TYPES.TSUnionType ? annotation.types : [annotation];
|
|
12486
|
+
const keys = [];
|
|
12487
|
+
for (const member of members) {
|
|
12488
|
+
if (member.type === import_utils61.AST_NODE_TYPES.TSNullKeyword) {
|
|
12489
|
+
continue;
|
|
12490
|
+
}
|
|
12491
|
+
if (member.type !== import_utils61.AST_NODE_TYPES.TSLiteralType) {
|
|
12492
|
+
return null;
|
|
12493
|
+
}
|
|
12494
|
+
keys.push(primitiveLiteralKey(member.literal));
|
|
12495
|
+
}
|
|
12496
|
+
return exactDomain(keys);
|
|
12497
|
+
}
|
|
12498
|
+
function staticStringUnionDomain(node) {
|
|
12499
|
+
if (node.type !== import_utils61.AST_NODE_TYPES.TSUnionType) {
|
|
12500
|
+
return null;
|
|
12501
|
+
}
|
|
12502
|
+
const keys = node.types.map((member) => {
|
|
12503
|
+
if (member.type !== import_utils61.AST_NODE_TYPES.TSLiteralType) {
|
|
12504
|
+
return null;
|
|
12505
|
+
}
|
|
12506
|
+
const key = primitiveLiteralKey(member.literal);
|
|
12507
|
+
return key?.startsWith("string:") === true ? key : null;
|
|
12508
|
+
});
|
|
12509
|
+
const domain = exactDomain(keys);
|
|
12510
|
+
return domain !== null && domain.size >= 2 ? domain : null;
|
|
12511
|
+
}
|
|
12512
|
+
function nameTokens(name) {
|
|
12513
|
+
return name.replace(/([a-z\d])([A-Z])/gu, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2").split(/[^A-Za-z\d]+/u).filter((token) => token !== "").map((token) => token.toLowerCase());
|
|
12514
|
+
}
|
|
12515
|
+
function schemaNameTokens(name) {
|
|
12516
|
+
const tokens = nameTokens(name);
|
|
12517
|
+
const withoutPrefix = tokens[0] === "z" ? tokens.slice(1) : tokens;
|
|
12518
|
+
return withoutPrefix.at(-1) === "schema" ? withoutPrefix.slice(0, -1) : withoutPrefix;
|
|
12519
|
+
}
|
|
12520
|
+
function endsWithTokens(candidate, suffix) {
|
|
12521
|
+
return suffix.length >= 2 && candidate.length >= suffix.length && suffix.every(
|
|
12522
|
+
(segment, index) => candidate[candidate.length - suffix.length + index] === segment
|
|
12523
|
+
);
|
|
12524
|
+
}
|
|
12364
12525
|
var prefer_zod_infer_default = createRule({
|
|
12365
12526
|
name: "prefer-zod-infer",
|
|
12366
12527
|
documentation: preferZodInferDocumentation,
|
|
@@ -12383,7 +12544,8 @@ var prefer_zod_infer_default = createRule({
|
|
|
12383
12544
|
}
|
|
12384
12545
|
],
|
|
12385
12546
|
messages: {
|
|
12386
|
-
handWrittenTwin: "`{{typeName}}` restates the shape of the Zod schema `{{schemaName}}` declared in this module. Derive it instead \u2014 `type {{typeName}} = z.infer<typeof {{schemaName}}>` \u2014 so a field added to the schema cannot silently leave the type behind."
|
|
12547
|
+
handWrittenTwin: "`{{typeName}}` restates the shape of the Zod schema `{{schemaName}}` declared in this module. Derive it instead \u2014 `type {{typeName}} = z.infer<typeof {{schemaName}}>` \u2014 so a field added to the schema cannot silently leave the type behind.",
|
|
12548
|
+
repeatedEnumUnion: "`{{propertyName}}` repeats the literal domain already named by `{{typeName}}` and `{{schemaName}}` in multiple object types. Reuse `{{typeName}}` so the accepted values have one source of truth."
|
|
12387
12549
|
}
|
|
12388
12550
|
},
|
|
12389
12551
|
defaultOptions: [{}],
|
|
@@ -12397,6 +12559,9 @@ var prefer_zod_infer_default = createRule({
|
|
|
12397
12559
|
}
|
|
12398
12560
|
const zodNamespaces = /* @__PURE__ */ new Set();
|
|
12399
12561
|
const schemas = [];
|
|
12562
|
+
const enumSchemas = [];
|
|
12563
|
+
const inferredAliases = [];
|
|
12564
|
+
const literalUnionOccurrences = [];
|
|
12400
12565
|
const typeDeclarations = [];
|
|
12401
12566
|
const constrainedTypeNames = /* @__PURE__ */ new Set();
|
|
12402
12567
|
const reshapedSchemaNames = /* @__PURE__ */ new Set();
|
|
@@ -12421,10 +12586,21 @@ var prefer_zod_infer_default = createRule({
|
|
|
12421
12586
|
const callee = call.callee;
|
|
12422
12587
|
return callee.type === import_utils61.AST_NODE_TYPES.MemberExpression && callee.property.type === import_utils61.AST_NODE_TYPES.Identifier ? callee.property.name : "";
|
|
12423
12588
|
}
|
|
12589
|
+
function recordZodImport(node) {
|
|
12590
|
+
if (!isZodModule(node.source.value)) {
|
|
12591
|
+
return;
|
|
12592
|
+
}
|
|
12593
|
+
for (const specifier of node.specifiers) {
|
|
12594
|
+
if (specifier.type === import_utils61.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils61.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils61.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils61.AST_NODE_TYPES.Identifier && specifier.imported.name === "z") {
|
|
12595
|
+
zodNamespaces.add(specifier.local.name);
|
|
12596
|
+
}
|
|
12597
|
+
}
|
|
12598
|
+
}
|
|
12424
12599
|
function schemaField(node) {
|
|
12425
12600
|
const modifiers = [];
|
|
12426
12601
|
let current = node;
|
|
12427
12602
|
let leaf = null;
|
|
12603
|
+
let leafCall = null;
|
|
12428
12604
|
while (current.type === import_utils61.AST_NODE_TYPES.CallExpression) {
|
|
12429
12605
|
const callee = current.callee;
|
|
12430
12606
|
if (callee.type !== import_utils61.AST_NODE_TYPES.MemberExpression || callee.computed || callee.property.type !== import_utils61.AST_NODE_TYPES.Identifier) {
|
|
@@ -12433,18 +12609,72 @@ var prefer_zod_infer_default = createRule({
|
|
|
12433
12609
|
const receiver = callee.object;
|
|
12434
12610
|
if (receiver.type === import_utils61.AST_NODE_TYPES.Identifier && zodNamespaces.has(receiver.name)) {
|
|
12435
12611
|
leaf = callee.property.name;
|
|
12612
|
+
leafCall = current;
|
|
12436
12613
|
break;
|
|
12437
12614
|
}
|
|
12438
12615
|
modifiers.push(callee.property.name);
|
|
12439
12616
|
current = receiver;
|
|
12440
12617
|
}
|
|
12441
12618
|
return {
|
|
12619
|
+
domain: modifiers.every(
|
|
12620
|
+
(name) => DOMAIN_PRESERVING_MODIFIERS.has(name)
|
|
12621
|
+
) ? staticZodDomain(leaf, leafCall) : null,
|
|
12442
12622
|
leaf,
|
|
12443
12623
|
optional: modifiers.some((name) => OPTIONAL_MODIFIERS.has(name)),
|
|
12444
12624
|
nullable: modifiers.some((name) => NULLABLE_MODIFIERS.has(name)),
|
|
12445
12625
|
reshaped: modifiers.some((name) => RESHAPING_MODIFIERS.has(name))
|
|
12446
12626
|
};
|
|
12447
12627
|
}
|
|
12628
|
+
function enumSchemaDomain(init) {
|
|
12629
|
+
const chain = zodCallChain(init);
|
|
12630
|
+
if (chain === null || chain.length !== 1) {
|
|
12631
|
+
return null;
|
|
12632
|
+
}
|
|
12633
|
+
const [call] = chain;
|
|
12634
|
+
if (call === void 0 || methodName2(call) !== "enum") {
|
|
12635
|
+
return null;
|
|
12636
|
+
}
|
|
12637
|
+
const domain = staticZodDomain("enum", call);
|
|
12638
|
+
return domain instanceof Set && domain.size >= 2 ? domain : null;
|
|
12639
|
+
}
|
|
12640
|
+
function inferredSchemaName(node) {
|
|
12641
|
+
if (node.type !== import_utils61.AST_NODE_TYPES.TSTypeReference || node.typeName.type !== import_utils61.AST_NODE_TYPES.TSQualifiedName || node.typeName.left.type !== import_utils61.AST_NODE_TYPES.Identifier || !zodNamespaces.has(node.typeName.left.name) || node.typeName.right.name !== "infer") {
|
|
12642
|
+
return null;
|
|
12643
|
+
}
|
|
12644
|
+
const arguments_ = node.typeArguments?.params ?? [];
|
|
12645
|
+
const [argument] = arguments_;
|
|
12646
|
+
return arguments_.length === 1 && argument?.type === import_utils61.AST_NODE_TYPES.TSTypeQuery && argument.exprName.type === import_utils61.AST_NODE_TYPES.Identifier ? argument.exprName.name : null;
|
|
12647
|
+
}
|
|
12648
|
+
function recordLiteralUnions(members, owner, ownerName, exported) {
|
|
12649
|
+
for (const member of members) {
|
|
12650
|
+
if (member.type !== import_utils61.AST_NODE_TYPES.TSPropertySignature || member.computed || member.optional || member.readonly || member.typeAnnotation === void 0) {
|
|
12651
|
+
continue;
|
|
12652
|
+
}
|
|
12653
|
+
const key = member.key;
|
|
12654
|
+
const propertyName3 = key.type === import_utils61.AST_NODE_TYPES.Identifier ? key.name : key.type === import_utils61.AST_NODE_TYPES.Literal && typeof key.value === "string" ? key.value : null;
|
|
12655
|
+
if (propertyName3 === null) {
|
|
12656
|
+
continue;
|
|
12657
|
+
}
|
|
12658
|
+
const propertyTokens = nameTokens(propertyName3);
|
|
12659
|
+
if (propertyTokens.length < 2) {
|
|
12660
|
+
continue;
|
|
12661
|
+
}
|
|
12662
|
+
const annotation = member.typeAnnotation.typeAnnotation;
|
|
12663
|
+
const domain = staticStringUnionDomain(annotation);
|
|
12664
|
+
if (domain === null || annotation.type !== import_utils61.AST_NODE_TYPES.TSUnionType) {
|
|
12665
|
+
continue;
|
|
12666
|
+
}
|
|
12667
|
+
literalUnionOccurrences.push({
|
|
12668
|
+
domain,
|
|
12669
|
+
exported,
|
|
12670
|
+
node: annotation,
|
|
12671
|
+
owner,
|
|
12672
|
+
ownerName,
|
|
12673
|
+
propertyName: propertyName3,
|
|
12674
|
+
propertyTokens
|
|
12675
|
+
});
|
|
12676
|
+
}
|
|
12677
|
+
}
|
|
12448
12678
|
function schemaFields(init) {
|
|
12449
12679
|
const chain = zodCallChain(init);
|
|
12450
12680
|
if (chain === null || chain.length === 0) {
|
|
@@ -12545,7 +12775,7 @@ var prefer_zod_infer_default = createRule({
|
|
|
12545
12775
|
if (member.readonly) {
|
|
12546
12776
|
return false;
|
|
12547
12777
|
}
|
|
12548
|
-
const agrees = leafAgrees(field
|
|
12778
|
+
const agrees = leafAgrees(field, member.annotation);
|
|
12549
12779
|
if (agrees === false) {
|
|
12550
12780
|
return false;
|
|
12551
12781
|
}
|
|
@@ -12556,16 +12786,16 @@ var prefer_zod_infer_default = createRule({
|
|
|
12556
12786
|
return agreements > 0;
|
|
12557
12787
|
}
|
|
12558
12788
|
return {
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
for (const specifier of node.specifiers) {
|
|
12564
|
-
if (specifier.type === import_utils61.AST_NODE_TYPES.ImportNamespaceSpecifier || specifier.type === import_utils61.AST_NODE_TYPES.ImportDefaultSpecifier || specifier.type === import_utils61.AST_NODE_TYPES.ImportSpecifier && specifier.imported.type === import_utils61.AST_NODE_TYPES.Identifier && specifier.imported.name === "z") {
|
|
12565
|
-
zodNamespaces.add(specifier.local.name);
|
|
12789
|
+
Program(node) {
|
|
12790
|
+
for (const statement of node.body) {
|
|
12791
|
+
if (statement.type === import_utils61.AST_NODE_TYPES.ImportDeclaration) {
|
|
12792
|
+
recordZodImport(statement);
|
|
12566
12793
|
}
|
|
12567
12794
|
}
|
|
12568
12795
|
},
|
|
12796
|
+
ImportDeclaration(node) {
|
|
12797
|
+
recordZodImport(node);
|
|
12798
|
+
},
|
|
12569
12799
|
VariableDeclarator(node) {
|
|
12570
12800
|
if (node.id.type !== import_utils61.AST_NODE_TYPES.Identifier || node.init == null) {
|
|
12571
12801
|
return;
|
|
@@ -12574,6 +12804,13 @@ var prefer_zod_infer_default = createRule({
|
|
|
12574
12804
|
if (fields !== null) {
|
|
12575
12805
|
schemas.push({ name: node.id.name, fields });
|
|
12576
12806
|
}
|
|
12807
|
+
if (isModuleLevelConst(node)) {
|
|
12808
|
+
const domain = enumSchemaDomain(node.init);
|
|
12809
|
+
const tokens = schemaNameTokens(node.id.name);
|
|
12810
|
+
if (domain !== null && tokens.length >= 2) {
|
|
12811
|
+
enumSchemas.push({ domain, name: node.id.name, tokens });
|
|
12812
|
+
}
|
|
12813
|
+
}
|
|
12577
12814
|
},
|
|
12578
12815
|
/** Records `XSchema.transform(...)` and equivalent module-level reshaping. */
|
|
12579
12816
|
"MemberExpression[computed=false]"(node) {
|
|
@@ -12600,8 +12837,22 @@ var prefer_zod_infer_default = createRule({
|
|
|
12600
12837
|
if (members !== null) {
|
|
12601
12838
|
typeDeclarations.push({ name: node.id.name, node: node.id, members });
|
|
12602
12839
|
}
|
|
12840
|
+
recordLiteralUnions(
|
|
12841
|
+
node.body.body,
|
|
12842
|
+
node,
|
|
12843
|
+
node.id.name,
|
|
12844
|
+
isExportedDeclaration(node)
|
|
12845
|
+
);
|
|
12603
12846
|
},
|
|
12604
12847
|
TSTypeAliasDeclaration(node) {
|
|
12848
|
+
const schemaName = inferredSchemaName(node.typeAnnotation);
|
|
12849
|
+
if (schemaName !== null) {
|
|
12850
|
+
inferredAliases.push({
|
|
12851
|
+
exported: isExportedDeclaration(node),
|
|
12852
|
+
schemaName,
|
|
12853
|
+
typeName: node.id.name
|
|
12854
|
+
});
|
|
12855
|
+
}
|
|
12605
12856
|
if (node.typeParameters !== void 0 || node.typeAnnotation.type !== import_utils61.AST_NODE_TYPES.TSTypeLiteral) {
|
|
12606
12857
|
return;
|
|
12607
12858
|
}
|
|
@@ -12609,11 +12860,15 @@ var prefer_zod_infer_default = createRule({
|
|
|
12609
12860
|
if (members !== null) {
|
|
12610
12861
|
typeDeclarations.push({ name: node.id.name, node: node.id, members });
|
|
12611
12862
|
}
|
|
12863
|
+
recordLiteralUnions(
|
|
12864
|
+
node.typeAnnotation.members,
|
|
12865
|
+
node,
|
|
12866
|
+
node.id.name,
|
|
12867
|
+
isExportedDeclaration(node)
|
|
12868
|
+
);
|
|
12612
12869
|
},
|
|
12613
12870
|
"Program:exit"() {
|
|
12614
|
-
|
|
12615
|
-
return;
|
|
12616
|
-
}
|
|
12871
|
+
const twinTypeNames = /* @__PURE__ */ new Set();
|
|
12617
12872
|
const byName = /* @__PURE__ */ new Map();
|
|
12618
12873
|
for (const schema of schemas) {
|
|
12619
12874
|
const key = normalizeSchemaName(schema.name);
|
|
@@ -12635,12 +12890,62 @@ var prefer_zod_infer_default = createRule({
|
|
|
12635
12890
|
if (!isTwin(schema.fields, declaration.members)) {
|
|
12636
12891
|
continue;
|
|
12637
12892
|
}
|
|
12893
|
+
twinTypeNames.add(declaration.name);
|
|
12638
12894
|
context.report({
|
|
12639
12895
|
node: declaration.node,
|
|
12640
12896
|
messageId: "handWrittenTwin",
|
|
12641
12897
|
data: { typeName: declaration.name, schemaName: schema.name }
|
|
12642
12898
|
});
|
|
12643
12899
|
}
|
|
12900
|
+
const aliasesBySchema = /* @__PURE__ */ new Map();
|
|
12901
|
+
for (const alias of inferredAliases) {
|
|
12902
|
+
const aliases = aliasesBySchema.get(alias.schemaName) ?? [];
|
|
12903
|
+
aliases.push(alias);
|
|
12904
|
+
aliasesBySchema.set(alias.schemaName, aliases);
|
|
12905
|
+
}
|
|
12906
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12907
|
+
for (const occurrence of literalUnionOccurrences) {
|
|
12908
|
+
const { ownerName } = occurrence;
|
|
12909
|
+
if (twinTypeNames.has(ownerName ?? "") || ownerName !== null && ignorePatterns.some((pattern) => pattern.test(ownerName))) {
|
|
12910
|
+
continue;
|
|
12911
|
+
}
|
|
12912
|
+
const candidates = enumSchemas.filter(
|
|
12913
|
+
(schema2) => sameDomain(schema2.domain, occurrence.domain) && endsWithTokens(schema2.tokens, occurrence.propertyTokens) && (aliasesBySchema.get(schema2.name)?.length ?? 0) === 1
|
|
12914
|
+
);
|
|
12915
|
+
const [schema] = candidates;
|
|
12916
|
+
if (candidates.length !== 1 || schema === void 0) {
|
|
12917
|
+
continue;
|
|
12918
|
+
}
|
|
12919
|
+
const [alias] = aliasesBySchema.get(schema.name) ?? [];
|
|
12920
|
+
if (alias === void 0 || occurrence.exported && !alias.exported) {
|
|
12921
|
+
continue;
|
|
12922
|
+
}
|
|
12923
|
+
const key = `${schema.name}\0${occurrence.propertyName}`;
|
|
12924
|
+
const group = groups.get(key);
|
|
12925
|
+
if (group === void 0) {
|
|
12926
|
+
groups.set(key, { alias, occurrences: [occurrence], schema });
|
|
12927
|
+
} else {
|
|
12928
|
+
group.occurrences.push(occurrence);
|
|
12929
|
+
}
|
|
12930
|
+
}
|
|
12931
|
+
for (const { alias, occurrences, schema } of groups.values()) {
|
|
12932
|
+
if (new Set(occurrences.map(({ ownerName }) => ownerName)).size < 2 || new Set(occurrences.map(({ owner }) => owner)).size < 2) {
|
|
12933
|
+
continue;
|
|
12934
|
+
}
|
|
12935
|
+
const [first] = occurrences;
|
|
12936
|
+
if (first === void 0) {
|
|
12937
|
+
continue;
|
|
12938
|
+
}
|
|
12939
|
+
context.report({
|
|
12940
|
+
node: first.node,
|
|
12941
|
+
messageId: "repeatedEnumUnion",
|
|
12942
|
+
data: {
|
|
12943
|
+
propertyName: first.propertyName,
|
|
12944
|
+
schemaName: schema.name,
|
|
12945
|
+
typeName: alias.typeName
|
|
12946
|
+
}
|
|
12947
|
+
});
|
|
12948
|
+
}
|
|
12644
12949
|
}
|
|
12645
12950
|
};
|
|
12646
12951
|
}
|
|
@@ -14581,7 +14886,7 @@ var rules = {
|
|
|
14581
14886
|
};
|
|
14582
14887
|
var meta = {
|
|
14583
14888
|
name: "@sarj/eslint-plugin",
|
|
14584
|
-
version: "15.6.
|
|
14889
|
+
version: "15.6.2"
|
|
14585
14890
|
};
|
|
14586
14891
|
var applicationOnlyRules = [
|
|
14587
14892
|
"no-restricted-library-load",
|
package/dist/index.d.cts
CHANGED
|
@@ -263,7 +263,7 @@ declare const rules: {
|
|
|
263
263
|
readonly "prefer-zod-infer": DocumentedRule<readonly [{
|
|
264
264
|
ignoreTypeNames?: readonly string[];
|
|
265
265
|
requireIdenticalShape?: boolean;
|
|
266
|
-
}?], "handWrittenTwin">;
|
|
266
|
+
}?], "handWrittenTwin" | "repeatedEnumUnion">;
|
|
267
267
|
readonly "require-assert-never": DocumentedRule<readonly [], "missingAssertNever">;
|
|
268
268
|
readonly "require-fetch-timeout": DocumentedRule<readonly [{
|
|
269
269
|
allowIn?: readonly string[];
|
|
@@ -415,7 +415,7 @@ type FlatPreset = {
|
|
|
415
415
|
declare const plugin: {
|
|
416
416
|
readonly meta: {
|
|
417
417
|
readonly name: "@sarj/eslint-plugin";
|
|
418
|
-
readonly version: "15.6.
|
|
418
|
+
readonly version: "15.6.2";
|
|
419
419
|
};
|
|
420
420
|
readonly rules: {
|
|
421
421
|
readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
|
|
@@ -493,7 +493,7 @@ declare const plugin: {
|
|
|
493
493
|
readonly "prefer-zod-infer": DocumentedRule<readonly [{
|
|
494
494
|
ignoreTypeNames?: readonly string[];
|
|
495
495
|
requireIdenticalShape?: boolean;
|
|
496
|
-
}?], "handWrittenTwin">;
|
|
496
|
+
}?], "handWrittenTwin" | "repeatedEnumUnion">;
|
|
497
497
|
readonly "require-assert-never": DocumentedRule<readonly [], "missingAssertNever">;
|
|
498
498
|
readonly "require-fetch-timeout": DocumentedRule<readonly [{
|
|
499
499
|
allowIn?: readonly string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -263,7 +263,7 @@ declare const rules: {
|
|
|
263
263
|
readonly "prefer-zod-infer": DocumentedRule<readonly [{
|
|
264
264
|
ignoreTypeNames?: readonly string[];
|
|
265
265
|
requireIdenticalShape?: boolean;
|
|
266
|
-
}?], "handWrittenTwin">;
|
|
266
|
+
}?], "handWrittenTwin" | "repeatedEnumUnion">;
|
|
267
267
|
readonly "require-assert-never": DocumentedRule<readonly [], "missingAssertNever">;
|
|
268
268
|
readonly "require-fetch-timeout": DocumentedRule<readonly [{
|
|
269
269
|
allowIn?: readonly string[];
|
|
@@ -415,7 +415,7 @@ type FlatPreset = {
|
|
|
415
415
|
declare const plugin: {
|
|
416
416
|
readonly meta: {
|
|
417
417
|
readonly name: "@sarj/eslint-plugin";
|
|
418
|
-
readonly version: "15.6.
|
|
418
|
+
readonly version: "15.6.2";
|
|
419
419
|
};
|
|
420
420
|
readonly rules: {
|
|
421
421
|
readonly "duplicate-test-body": DocumentedRule<readonly [], "duplicateTestBody">;
|
|
@@ -493,7 +493,7 @@ declare const plugin: {
|
|
|
493
493
|
readonly "prefer-zod-infer": DocumentedRule<readonly [{
|
|
494
494
|
ignoreTypeNames?: readonly string[];
|
|
495
495
|
requireIdenticalShape?: boolean;
|
|
496
|
-
}?], "handWrittenTwin">;
|
|
496
|
+
}?], "handWrittenTwin" | "repeatedEnumUnion">;
|
|
497
497
|
readonly "require-assert-never": DocumentedRule<readonly [], "missingAssertNever">;
|
|
498
498
|
readonly "require-fetch-timeout": DocumentedRule<readonly [{
|
|
499
499
|
allowIn?: readonly string[];
|
package/dist/index.js
CHANGED
|
@@ -5610,9 +5610,9 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5610
5610
|
continue;
|
|
5611
5611
|
}
|
|
5612
5612
|
if ((paramTags.length > 0 || returnTags.length > 0) && documentsTypedFunction(sourceCode, comment)) continue;
|
|
5613
|
-
const
|
|
5613
|
+
const nameTokens2 = tokensOf([declaration.name]);
|
|
5614
5614
|
const paramTokens = tokensOf(declaration.params);
|
|
5615
|
-
const known = /* @__PURE__ */ new Set([...
|
|
5615
|
+
const known = /* @__PURE__ */ new Set([...nameTokens2, ...paramTokens]);
|
|
5616
5616
|
let addsNothing = covered(describedText, known);
|
|
5617
5617
|
for (const tag of paramTags) {
|
|
5618
5618
|
const text = tag.text.replace(/^\{[^}]*\}\s*/, "");
|
|
@@ -5627,7 +5627,7 @@ var no_restated_jsdoc_default = createRule({
|
|
|
5627
5627
|
addsNothing = false;
|
|
5628
5628
|
break;
|
|
5629
5629
|
}
|
|
5630
|
-
const own = /* @__PURE__ */ new Set([...splitIdentifier(path.split(".").pop() ?? ""), ...
|
|
5630
|
+
const own = /* @__PURE__ */ new Set([...splitIdentifier(path.split(".").pop() ?? ""), ...nameTokens2]);
|
|
5631
5631
|
if (!covered(match[2] ?? "", own)) {
|
|
5632
5632
|
addsNothing = false;
|
|
5633
5633
|
break;
|
|
@@ -12223,6 +12223,14 @@ var OPTIONAL_MODIFIERS = /* @__PURE__ */ new Set([
|
|
|
12223
12223
|
"nullish"
|
|
12224
12224
|
]);
|
|
12225
12225
|
var NULLABLE_MODIFIERS = /* @__PURE__ */ new Set(["nullable", "nullish"]);
|
|
12226
|
+
var DOMAIN_PRESERVING_MODIFIERS = /* @__PURE__ */ new Set([
|
|
12227
|
+
...SHAPE_PRESERVING_METHODS,
|
|
12228
|
+
...OPTIONAL_MODIFIERS,
|
|
12229
|
+
...NULLABLE_MODIFIERS,
|
|
12230
|
+
"catch",
|
|
12231
|
+
"default",
|
|
12232
|
+
"prefault"
|
|
12233
|
+
]);
|
|
12226
12234
|
var RESHAPING_MODIFIERS = /* @__PURE__ */ new Set([
|
|
12227
12235
|
"transform",
|
|
12228
12236
|
"pipe",
|
|
@@ -12283,6 +12291,107 @@ var LEAF_NODE_TYPES = {
|
|
|
12283
12291
|
discriminatedUnion: [AST_NODE_TYPES48.TSUnionType, AST_NODE_TYPES48.TSTypeReference],
|
|
12284
12292
|
intersection: [AST_NODE_TYPES48.TSIntersectionType, AST_NODE_TYPES48.TSTypeReference]
|
|
12285
12293
|
};
|
|
12294
|
+
function primitiveLiteralKey(node) {
|
|
12295
|
+
if (node.type !== AST_NODE_TYPES48.Literal) {
|
|
12296
|
+
return null;
|
|
12297
|
+
}
|
|
12298
|
+
if (node.value === null) {
|
|
12299
|
+
return "null";
|
|
12300
|
+
}
|
|
12301
|
+
switch (typeof node.value) {
|
|
12302
|
+
case "string":
|
|
12303
|
+
return `string:${node.value}`;
|
|
12304
|
+
case "number":
|
|
12305
|
+
return `number:${String(node.value)}`;
|
|
12306
|
+
case "boolean":
|
|
12307
|
+
return `boolean:${String(node.value)}`;
|
|
12308
|
+
case "bigint":
|
|
12309
|
+
return `bigint:${String(node.value)}`;
|
|
12310
|
+
default:
|
|
12311
|
+
return null;
|
|
12312
|
+
}
|
|
12313
|
+
}
|
|
12314
|
+
function exactDomain(keys) {
|
|
12315
|
+
if (keys.length === 0 || keys.some((key) => key === null)) {
|
|
12316
|
+
return null;
|
|
12317
|
+
}
|
|
12318
|
+
const domain = new Set(keys);
|
|
12319
|
+
return domain.size === keys.length ? domain : null;
|
|
12320
|
+
}
|
|
12321
|
+
function staticZodDomain(leaf, call) {
|
|
12322
|
+
if (leaf === null || call === null) {
|
|
12323
|
+
return void 0;
|
|
12324
|
+
}
|
|
12325
|
+
if (leaf === "literal") {
|
|
12326
|
+
const [argument] = call.arguments;
|
|
12327
|
+
if (argument === void 0 || argument.type === AST_NODE_TYPES48.SpreadElement) {
|
|
12328
|
+
return null;
|
|
12329
|
+
}
|
|
12330
|
+
if (argument.type === AST_NODE_TYPES48.ArrayExpression) {
|
|
12331
|
+
return exactDomain(
|
|
12332
|
+
argument.elements.map(
|
|
12333
|
+
(element) => element === null || element.type === AST_NODE_TYPES48.SpreadElement ? null : primitiveLiteralKey(element)
|
|
12334
|
+
)
|
|
12335
|
+
);
|
|
12336
|
+
}
|
|
12337
|
+
return exactDomain([primitiveLiteralKey(argument)]);
|
|
12338
|
+
}
|
|
12339
|
+
if (leaf === "enum") {
|
|
12340
|
+
const [argument] = call.arguments;
|
|
12341
|
+
if (argument === void 0 || argument.type === AST_NODE_TYPES48.SpreadElement) {
|
|
12342
|
+
return null;
|
|
12343
|
+
}
|
|
12344
|
+
if (argument.type === AST_NODE_TYPES48.ArrayExpression) {
|
|
12345
|
+
return exactDomain(
|
|
12346
|
+
argument.elements.map((element) => {
|
|
12347
|
+
if (element === null || element.type === AST_NODE_TYPES48.SpreadElement) {
|
|
12348
|
+
return null;
|
|
12349
|
+
}
|
|
12350
|
+
const key = primitiveLiteralKey(element);
|
|
12351
|
+
return key?.startsWith("string:") === true ? key : null;
|
|
12352
|
+
})
|
|
12353
|
+
);
|
|
12354
|
+
}
|
|
12355
|
+
if (argument.type === AST_NODE_TYPES48.ObjectExpression) {
|
|
12356
|
+
return exactDomain(
|
|
12357
|
+
argument.properties.map((property) => {
|
|
12358
|
+
if (property.type !== AST_NODE_TYPES48.Property || property.computed || property.kind !== "init" || property.method || property.shorthand) {
|
|
12359
|
+
return null;
|
|
12360
|
+
}
|
|
12361
|
+
const key = primitiveLiteralKey(property.value);
|
|
12362
|
+
return key?.startsWith("string:") === true ? key : null;
|
|
12363
|
+
})
|
|
12364
|
+
);
|
|
12365
|
+
}
|
|
12366
|
+
return null;
|
|
12367
|
+
}
|
|
12368
|
+
if (leaf === "nativeEnum" || leaf === "union" || leaf === "discriminatedUnion") {
|
|
12369
|
+
return null;
|
|
12370
|
+
}
|
|
12371
|
+
return void 0;
|
|
12372
|
+
}
|
|
12373
|
+
function sameDomain(left, right) {
|
|
12374
|
+
if (left.size !== right.size) {
|
|
12375
|
+
return false;
|
|
12376
|
+
}
|
|
12377
|
+
for (const value of left) {
|
|
12378
|
+
if (!right.has(value)) {
|
|
12379
|
+
return false;
|
|
12380
|
+
}
|
|
12381
|
+
}
|
|
12382
|
+
return true;
|
|
12383
|
+
}
|
|
12384
|
+
function isExportedDeclaration(node) {
|
|
12385
|
+
return node.parent?.type === AST_NODE_TYPES48.ExportNamedDeclaration;
|
|
12386
|
+
}
|
|
12387
|
+
function isModuleLevelConst(node) {
|
|
12388
|
+
const declaration = node.parent;
|
|
12389
|
+
if (declaration.type !== AST_NODE_TYPES48.VariableDeclaration || declaration.kind !== "const") {
|
|
12390
|
+
return false;
|
|
12391
|
+
}
|
|
12392
|
+
const container = declaration.parent;
|
|
12393
|
+
return container.type === AST_NODE_TYPES48.Program || container.type === AST_NODE_TYPES48.ExportNamedDeclaration && container.parent.type === AST_NODE_TYPES48.Program;
|
|
12394
|
+
}
|
|
12286
12395
|
function normalizeSchemaName(name) {
|
|
12287
12396
|
return name.replace(/Schema$/i, "").replace(/^Z(?=[A-Z])/, "").toLowerCase();
|
|
12288
12397
|
}
|
|
@@ -12313,7 +12422,18 @@ function unwrapNullish(annotation) {
|
|
|
12313
12422
|
}
|
|
12314
12423
|
return { core: rest.length === 0 ? null : annotation, nullable };
|
|
12315
12424
|
}
|
|
12316
|
-
function leafAgrees(
|
|
12425
|
+
function leafAgrees(field, annotation) {
|
|
12426
|
+
if (field.domain === null) {
|
|
12427
|
+
return false;
|
|
12428
|
+
}
|
|
12429
|
+
if (field.domain !== void 0) {
|
|
12430
|
+
if (annotation === null) {
|
|
12431
|
+
return false;
|
|
12432
|
+
}
|
|
12433
|
+
const annotationDomain = typeLiteralDomain(annotation);
|
|
12434
|
+
return annotationDomain !== null && sameDomain(field.domain, annotationDomain);
|
|
12435
|
+
}
|
|
12436
|
+
const { leaf } = field;
|
|
12317
12437
|
if (leaf === null || annotation === null) {
|
|
12318
12438
|
return null;
|
|
12319
12439
|
}
|
|
@@ -12330,6 +12450,47 @@ function leafAgrees(leaf, annotation) {
|
|
|
12330
12450
|
}
|
|
12331
12451
|
return expected.includes(core.type);
|
|
12332
12452
|
}
|
|
12453
|
+
function typeLiteralDomain(annotation) {
|
|
12454
|
+
const members = annotation.type === AST_NODE_TYPES48.TSUnionType ? annotation.types : [annotation];
|
|
12455
|
+
const keys = [];
|
|
12456
|
+
for (const member of members) {
|
|
12457
|
+
if (member.type === AST_NODE_TYPES48.TSNullKeyword) {
|
|
12458
|
+
continue;
|
|
12459
|
+
}
|
|
12460
|
+
if (member.type !== AST_NODE_TYPES48.TSLiteralType) {
|
|
12461
|
+
return null;
|
|
12462
|
+
}
|
|
12463
|
+
keys.push(primitiveLiteralKey(member.literal));
|
|
12464
|
+
}
|
|
12465
|
+
return exactDomain(keys);
|
|
12466
|
+
}
|
|
12467
|
+
function staticStringUnionDomain(node) {
|
|
12468
|
+
if (node.type !== AST_NODE_TYPES48.TSUnionType) {
|
|
12469
|
+
return null;
|
|
12470
|
+
}
|
|
12471
|
+
const keys = node.types.map((member) => {
|
|
12472
|
+
if (member.type !== AST_NODE_TYPES48.TSLiteralType) {
|
|
12473
|
+
return null;
|
|
12474
|
+
}
|
|
12475
|
+
const key = primitiveLiteralKey(member.literal);
|
|
12476
|
+
return key?.startsWith("string:") === true ? key : null;
|
|
12477
|
+
});
|
|
12478
|
+
const domain = exactDomain(keys);
|
|
12479
|
+
return domain !== null && domain.size >= 2 ? domain : null;
|
|
12480
|
+
}
|
|
12481
|
+
function nameTokens(name) {
|
|
12482
|
+
return name.replace(/([a-z\d])([A-Z])/gu, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2").split(/[^A-Za-z\d]+/u).filter((token) => token !== "").map((token) => token.toLowerCase());
|
|
12483
|
+
}
|
|
12484
|
+
function schemaNameTokens(name) {
|
|
12485
|
+
const tokens = nameTokens(name);
|
|
12486
|
+
const withoutPrefix = tokens[0] === "z" ? tokens.slice(1) : tokens;
|
|
12487
|
+
return withoutPrefix.at(-1) === "schema" ? withoutPrefix.slice(0, -1) : withoutPrefix;
|
|
12488
|
+
}
|
|
12489
|
+
function endsWithTokens(candidate, suffix) {
|
|
12490
|
+
return suffix.length >= 2 && candidate.length >= suffix.length && suffix.every(
|
|
12491
|
+
(segment, index) => candidate[candidate.length - suffix.length + index] === segment
|
|
12492
|
+
);
|
|
12493
|
+
}
|
|
12333
12494
|
var prefer_zod_infer_default = createRule({
|
|
12334
12495
|
name: "prefer-zod-infer",
|
|
12335
12496
|
documentation: preferZodInferDocumentation,
|
|
@@ -12352,7 +12513,8 @@ var prefer_zod_infer_default = createRule({
|
|
|
12352
12513
|
}
|
|
12353
12514
|
],
|
|
12354
12515
|
messages: {
|
|
12355
|
-
handWrittenTwin: "`{{typeName}}` restates the shape of the Zod schema `{{schemaName}}` declared in this module. Derive it instead \u2014 `type {{typeName}} = z.infer<typeof {{schemaName}}>` \u2014 so a field added to the schema cannot silently leave the type behind."
|
|
12516
|
+
handWrittenTwin: "`{{typeName}}` restates the shape of the Zod schema `{{schemaName}}` declared in this module. Derive it instead \u2014 `type {{typeName}} = z.infer<typeof {{schemaName}}>` \u2014 so a field added to the schema cannot silently leave the type behind.",
|
|
12517
|
+
repeatedEnumUnion: "`{{propertyName}}` repeats the literal domain already named by `{{typeName}}` and `{{schemaName}}` in multiple object types. Reuse `{{typeName}}` so the accepted values have one source of truth."
|
|
12356
12518
|
}
|
|
12357
12519
|
},
|
|
12358
12520
|
defaultOptions: [{}],
|
|
@@ -12366,6 +12528,9 @@ var prefer_zod_infer_default = createRule({
|
|
|
12366
12528
|
}
|
|
12367
12529
|
const zodNamespaces = /* @__PURE__ */ new Set();
|
|
12368
12530
|
const schemas = [];
|
|
12531
|
+
const enumSchemas = [];
|
|
12532
|
+
const inferredAliases = [];
|
|
12533
|
+
const literalUnionOccurrences = [];
|
|
12369
12534
|
const typeDeclarations = [];
|
|
12370
12535
|
const constrainedTypeNames = /* @__PURE__ */ new Set();
|
|
12371
12536
|
const reshapedSchemaNames = /* @__PURE__ */ new Set();
|
|
@@ -12390,10 +12555,21 @@ var prefer_zod_infer_default = createRule({
|
|
|
12390
12555
|
const callee = call.callee;
|
|
12391
12556
|
return callee.type === AST_NODE_TYPES48.MemberExpression && callee.property.type === AST_NODE_TYPES48.Identifier ? callee.property.name : "";
|
|
12392
12557
|
}
|
|
12558
|
+
function recordZodImport(node) {
|
|
12559
|
+
if (!isZodModule(node.source.value)) {
|
|
12560
|
+
return;
|
|
12561
|
+
}
|
|
12562
|
+
for (const specifier of node.specifiers) {
|
|
12563
|
+
if (specifier.type === AST_NODE_TYPES48.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES48.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES48.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES48.Identifier && specifier.imported.name === "z") {
|
|
12564
|
+
zodNamespaces.add(specifier.local.name);
|
|
12565
|
+
}
|
|
12566
|
+
}
|
|
12567
|
+
}
|
|
12393
12568
|
function schemaField(node) {
|
|
12394
12569
|
const modifiers = [];
|
|
12395
12570
|
let current = node;
|
|
12396
12571
|
let leaf = null;
|
|
12572
|
+
let leafCall = null;
|
|
12397
12573
|
while (current.type === AST_NODE_TYPES48.CallExpression) {
|
|
12398
12574
|
const callee = current.callee;
|
|
12399
12575
|
if (callee.type !== AST_NODE_TYPES48.MemberExpression || callee.computed || callee.property.type !== AST_NODE_TYPES48.Identifier) {
|
|
@@ -12402,18 +12578,72 @@ var prefer_zod_infer_default = createRule({
|
|
|
12402
12578
|
const receiver = callee.object;
|
|
12403
12579
|
if (receiver.type === AST_NODE_TYPES48.Identifier && zodNamespaces.has(receiver.name)) {
|
|
12404
12580
|
leaf = callee.property.name;
|
|
12581
|
+
leafCall = current;
|
|
12405
12582
|
break;
|
|
12406
12583
|
}
|
|
12407
12584
|
modifiers.push(callee.property.name);
|
|
12408
12585
|
current = receiver;
|
|
12409
12586
|
}
|
|
12410
12587
|
return {
|
|
12588
|
+
domain: modifiers.every(
|
|
12589
|
+
(name) => DOMAIN_PRESERVING_MODIFIERS.has(name)
|
|
12590
|
+
) ? staticZodDomain(leaf, leafCall) : null,
|
|
12411
12591
|
leaf,
|
|
12412
12592
|
optional: modifiers.some((name) => OPTIONAL_MODIFIERS.has(name)),
|
|
12413
12593
|
nullable: modifiers.some((name) => NULLABLE_MODIFIERS.has(name)),
|
|
12414
12594
|
reshaped: modifiers.some((name) => RESHAPING_MODIFIERS.has(name))
|
|
12415
12595
|
};
|
|
12416
12596
|
}
|
|
12597
|
+
function enumSchemaDomain(init) {
|
|
12598
|
+
const chain = zodCallChain(init);
|
|
12599
|
+
if (chain === null || chain.length !== 1) {
|
|
12600
|
+
return null;
|
|
12601
|
+
}
|
|
12602
|
+
const [call] = chain;
|
|
12603
|
+
if (call === void 0 || methodName2(call) !== "enum") {
|
|
12604
|
+
return null;
|
|
12605
|
+
}
|
|
12606
|
+
const domain = staticZodDomain("enum", call);
|
|
12607
|
+
return domain instanceof Set && domain.size >= 2 ? domain : null;
|
|
12608
|
+
}
|
|
12609
|
+
function inferredSchemaName(node) {
|
|
12610
|
+
if (node.type !== AST_NODE_TYPES48.TSTypeReference || node.typeName.type !== AST_NODE_TYPES48.TSQualifiedName || node.typeName.left.type !== AST_NODE_TYPES48.Identifier || !zodNamespaces.has(node.typeName.left.name) || node.typeName.right.name !== "infer") {
|
|
12611
|
+
return null;
|
|
12612
|
+
}
|
|
12613
|
+
const arguments_ = node.typeArguments?.params ?? [];
|
|
12614
|
+
const [argument] = arguments_;
|
|
12615
|
+
return arguments_.length === 1 && argument?.type === AST_NODE_TYPES48.TSTypeQuery && argument.exprName.type === AST_NODE_TYPES48.Identifier ? argument.exprName.name : null;
|
|
12616
|
+
}
|
|
12617
|
+
function recordLiteralUnions(members, owner, ownerName, exported) {
|
|
12618
|
+
for (const member of members) {
|
|
12619
|
+
if (member.type !== AST_NODE_TYPES48.TSPropertySignature || member.computed || member.optional || member.readonly || member.typeAnnotation === void 0) {
|
|
12620
|
+
continue;
|
|
12621
|
+
}
|
|
12622
|
+
const key = member.key;
|
|
12623
|
+
const propertyName3 = key.type === AST_NODE_TYPES48.Identifier ? key.name : key.type === AST_NODE_TYPES48.Literal && typeof key.value === "string" ? key.value : null;
|
|
12624
|
+
if (propertyName3 === null) {
|
|
12625
|
+
continue;
|
|
12626
|
+
}
|
|
12627
|
+
const propertyTokens = nameTokens(propertyName3);
|
|
12628
|
+
if (propertyTokens.length < 2) {
|
|
12629
|
+
continue;
|
|
12630
|
+
}
|
|
12631
|
+
const annotation = member.typeAnnotation.typeAnnotation;
|
|
12632
|
+
const domain = staticStringUnionDomain(annotation);
|
|
12633
|
+
if (domain === null || annotation.type !== AST_NODE_TYPES48.TSUnionType) {
|
|
12634
|
+
continue;
|
|
12635
|
+
}
|
|
12636
|
+
literalUnionOccurrences.push({
|
|
12637
|
+
domain,
|
|
12638
|
+
exported,
|
|
12639
|
+
node: annotation,
|
|
12640
|
+
owner,
|
|
12641
|
+
ownerName,
|
|
12642
|
+
propertyName: propertyName3,
|
|
12643
|
+
propertyTokens
|
|
12644
|
+
});
|
|
12645
|
+
}
|
|
12646
|
+
}
|
|
12417
12647
|
function schemaFields(init) {
|
|
12418
12648
|
const chain = zodCallChain(init);
|
|
12419
12649
|
if (chain === null || chain.length === 0) {
|
|
@@ -12514,7 +12744,7 @@ var prefer_zod_infer_default = createRule({
|
|
|
12514
12744
|
if (member.readonly) {
|
|
12515
12745
|
return false;
|
|
12516
12746
|
}
|
|
12517
|
-
const agrees = leafAgrees(field
|
|
12747
|
+
const agrees = leafAgrees(field, member.annotation);
|
|
12518
12748
|
if (agrees === false) {
|
|
12519
12749
|
return false;
|
|
12520
12750
|
}
|
|
@@ -12525,16 +12755,16 @@ var prefer_zod_infer_default = createRule({
|
|
|
12525
12755
|
return agreements > 0;
|
|
12526
12756
|
}
|
|
12527
12757
|
return {
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
for (const specifier of node.specifiers) {
|
|
12533
|
-
if (specifier.type === AST_NODE_TYPES48.ImportNamespaceSpecifier || specifier.type === AST_NODE_TYPES48.ImportDefaultSpecifier || specifier.type === AST_NODE_TYPES48.ImportSpecifier && specifier.imported.type === AST_NODE_TYPES48.Identifier && specifier.imported.name === "z") {
|
|
12534
|
-
zodNamespaces.add(specifier.local.name);
|
|
12758
|
+
Program(node) {
|
|
12759
|
+
for (const statement of node.body) {
|
|
12760
|
+
if (statement.type === AST_NODE_TYPES48.ImportDeclaration) {
|
|
12761
|
+
recordZodImport(statement);
|
|
12535
12762
|
}
|
|
12536
12763
|
}
|
|
12537
12764
|
},
|
|
12765
|
+
ImportDeclaration(node) {
|
|
12766
|
+
recordZodImport(node);
|
|
12767
|
+
},
|
|
12538
12768
|
VariableDeclarator(node) {
|
|
12539
12769
|
if (node.id.type !== AST_NODE_TYPES48.Identifier || node.init == null) {
|
|
12540
12770
|
return;
|
|
@@ -12543,6 +12773,13 @@ var prefer_zod_infer_default = createRule({
|
|
|
12543
12773
|
if (fields !== null) {
|
|
12544
12774
|
schemas.push({ name: node.id.name, fields });
|
|
12545
12775
|
}
|
|
12776
|
+
if (isModuleLevelConst(node)) {
|
|
12777
|
+
const domain = enumSchemaDomain(node.init);
|
|
12778
|
+
const tokens = schemaNameTokens(node.id.name);
|
|
12779
|
+
if (domain !== null && tokens.length >= 2) {
|
|
12780
|
+
enumSchemas.push({ domain, name: node.id.name, tokens });
|
|
12781
|
+
}
|
|
12782
|
+
}
|
|
12546
12783
|
},
|
|
12547
12784
|
/** Records `XSchema.transform(...)` and equivalent module-level reshaping. */
|
|
12548
12785
|
"MemberExpression[computed=false]"(node) {
|
|
@@ -12569,8 +12806,22 @@ var prefer_zod_infer_default = createRule({
|
|
|
12569
12806
|
if (members !== null) {
|
|
12570
12807
|
typeDeclarations.push({ name: node.id.name, node: node.id, members });
|
|
12571
12808
|
}
|
|
12809
|
+
recordLiteralUnions(
|
|
12810
|
+
node.body.body,
|
|
12811
|
+
node,
|
|
12812
|
+
node.id.name,
|
|
12813
|
+
isExportedDeclaration(node)
|
|
12814
|
+
);
|
|
12572
12815
|
},
|
|
12573
12816
|
TSTypeAliasDeclaration(node) {
|
|
12817
|
+
const schemaName = inferredSchemaName(node.typeAnnotation);
|
|
12818
|
+
if (schemaName !== null) {
|
|
12819
|
+
inferredAliases.push({
|
|
12820
|
+
exported: isExportedDeclaration(node),
|
|
12821
|
+
schemaName,
|
|
12822
|
+
typeName: node.id.name
|
|
12823
|
+
});
|
|
12824
|
+
}
|
|
12574
12825
|
if (node.typeParameters !== void 0 || node.typeAnnotation.type !== AST_NODE_TYPES48.TSTypeLiteral) {
|
|
12575
12826
|
return;
|
|
12576
12827
|
}
|
|
@@ -12578,11 +12829,15 @@ var prefer_zod_infer_default = createRule({
|
|
|
12578
12829
|
if (members !== null) {
|
|
12579
12830
|
typeDeclarations.push({ name: node.id.name, node: node.id, members });
|
|
12580
12831
|
}
|
|
12832
|
+
recordLiteralUnions(
|
|
12833
|
+
node.typeAnnotation.members,
|
|
12834
|
+
node,
|
|
12835
|
+
node.id.name,
|
|
12836
|
+
isExportedDeclaration(node)
|
|
12837
|
+
);
|
|
12581
12838
|
},
|
|
12582
12839
|
"Program:exit"() {
|
|
12583
|
-
|
|
12584
|
-
return;
|
|
12585
|
-
}
|
|
12840
|
+
const twinTypeNames = /* @__PURE__ */ new Set();
|
|
12586
12841
|
const byName = /* @__PURE__ */ new Map();
|
|
12587
12842
|
for (const schema of schemas) {
|
|
12588
12843
|
const key = normalizeSchemaName(schema.name);
|
|
@@ -12604,12 +12859,62 @@ var prefer_zod_infer_default = createRule({
|
|
|
12604
12859
|
if (!isTwin(schema.fields, declaration.members)) {
|
|
12605
12860
|
continue;
|
|
12606
12861
|
}
|
|
12862
|
+
twinTypeNames.add(declaration.name);
|
|
12607
12863
|
context.report({
|
|
12608
12864
|
node: declaration.node,
|
|
12609
12865
|
messageId: "handWrittenTwin",
|
|
12610
12866
|
data: { typeName: declaration.name, schemaName: schema.name }
|
|
12611
12867
|
});
|
|
12612
12868
|
}
|
|
12869
|
+
const aliasesBySchema = /* @__PURE__ */ new Map();
|
|
12870
|
+
for (const alias of inferredAliases) {
|
|
12871
|
+
const aliases = aliasesBySchema.get(alias.schemaName) ?? [];
|
|
12872
|
+
aliases.push(alias);
|
|
12873
|
+
aliasesBySchema.set(alias.schemaName, aliases);
|
|
12874
|
+
}
|
|
12875
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12876
|
+
for (const occurrence of literalUnionOccurrences) {
|
|
12877
|
+
const { ownerName } = occurrence;
|
|
12878
|
+
if (twinTypeNames.has(ownerName ?? "") || ownerName !== null && ignorePatterns.some((pattern) => pattern.test(ownerName))) {
|
|
12879
|
+
continue;
|
|
12880
|
+
}
|
|
12881
|
+
const candidates = enumSchemas.filter(
|
|
12882
|
+
(schema2) => sameDomain(schema2.domain, occurrence.domain) && endsWithTokens(schema2.tokens, occurrence.propertyTokens) && (aliasesBySchema.get(schema2.name)?.length ?? 0) === 1
|
|
12883
|
+
);
|
|
12884
|
+
const [schema] = candidates;
|
|
12885
|
+
if (candidates.length !== 1 || schema === void 0) {
|
|
12886
|
+
continue;
|
|
12887
|
+
}
|
|
12888
|
+
const [alias] = aliasesBySchema.get(schema.name) ?? [];
|
|
12889
|
+
if (alias === void 0 || occurrence.exported && !alias.exported) {
|
|
12890
|
+
continue;
|
|
12891
|
+
}
|
|
12892
|
+
const key = `${schema.name}\0${occurrence.propertyName}`;
|
|
12893
|
+
const group = groups.get(key);
|
|
12894
|
+
if (group === void 0) {
|
|
12895
|
+
groups.set(key, { alias, occurrences: [occurrence], schema });
|
|
12896
|
+
} else {
|
|
12897
|
+
group.occurrences.push(occurrence);
|
|
12898
|
+
}
|
|
12899
|
+
}
|
|
12900
|
+
for (const { alias, occurrences, schema } of groups.values()) {
|
|
12901
|
+
if (new Set(occurrences.map(({ ownerName }) => ownerName)).size < 2 || new Set(occurrences.map(({ owner }) => owner)).size < 2) {
|
|
12902
|
+
continue;
|
|
12903
|
+
}
|
|
12904
|
+
const [first] = occurrences;
|
|
12905
|
+
if (first === void 0) {
|
|
12906
|
+
continue;
|
|
12907
|
+
}
|
|
12908
|
+
context.report({
|
|
12909
|
+
node: first.node,
|
|
12910
|
+
messageId: "repeatedEnumUnion",
|
|
12911
|
+
data: {
|
|
12912
|
+
propertyName: first.propertyName,
|
|
12913
|
+
schemaName: schema.name,
|
|
12914
|
+
typeName: alias.typeName
|
|
12915
|
+
}
|
|
12916
|
+
});
|
|
12917
|
+
}
|
|
12613
12918
|
}
|
|
12614
12919
|
};
|
|
12615
12920
|
}
|
|
@@ -14559,7 +14864,7 @@ var rules = {
|
|
|
14559
14864
|
};
|
|
14560
14865
|
var meta = {
|
|
14561
14866
|
name: "@sarj/eslint-plugin",
|
|
14562
|
-
version: "15.6.
|
|
14867
|
+
version: "15.6.2"
|
|
14563
14868
|
};
|
|
14564
14869
|
var applicationOnlyRules = [
|
|
14565
14870
|
"no-restricted-library-load",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sarj/eslint-plugin",
|
|
3
|
-
"version": "15.6.
|
|
3
|
+
"version": "15.6.2",
|
|
4
4
|
"packageManager": "npm@11.19.0",
|
|
5
5
|
"description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"build": "tsup",
|
|
37
37
|
"dogfood": "npm run build && eslint --config eslint.dogfood.config.mjs src tests eslint.config.mjs eslint.dogfood.config.mjs tsup.config.ts vitest.config.ts --max-warnings 0 && node -e \"import('./dist/index.js').then(({default:p}) => console.log('dogfood: '+Object.keys(p.rules).length+' TypeScript rules, 0 diagnostics'))\"",
|
|
38
38
|
"lint": "eslint . --max-warnings 0",
|
|
39
|
-
"prepack": "npm run verify-package",
|
|
39
|
+
"prepack": "npm run build && npm run verify-package",
|
|
40
40
|
"test": "vitest run",
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"verify-package": "node -e \"const fs=require('node:fs'),p=require('./package.json'),walk=x=>typeof x==='string'?[x]:x&&typeof x==='object'?Object.values(x).flatMap(walk):[];for(const file of new Set([p.main,p.module,p.types,...walk(p.exports)].filter(Boolean).map(x=>x.startsWith('./')?x.slice(2):x))){if(!file.startsWith('dist/'))throw new Error('export must live under dist: '+file);const stat=fs.statSync(file);if(!stat.isFile()||stat.size===0)throw new Error('missing or empty export: '+file)}\""
|