@sarj/eslint-plugin 16.6.0 → 16.8.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/dist/index.cjs CHANGED
@@ -52,7 +52,7 @@ module.exports = __toCommonJS(src_exports);
52
52
  // package.json
53
53
  var package_default = {
54
54
  name: "@sarj/eslint-plugin",
55
- version: "16.6.0",
55
+ version: "16.8.0",
56
56
  packageManager: "npm@12.1.0",
57
57
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
58
58
  type: "module",
@@ -470,8 +470,9 @@ function commentLineEnd(sourceText, start) {
470
470
  }
471
471
 
472
472
  // src/rules/require-explicit-contract-implementation.ts
473
+ var ESCAPED_CLASSES = /* @__PURE__ */ new WeakMap();
473
474
  var REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION = {
474
- defaultLevel: "warning",
475
+ defaultLevel: "error",
475
476
  summary: "Require nominal inheritance when an injected abstract class is rejected by an instanceof guard.",
476
477
  rationale: "A structurally assignable fake can still fail a constructor's nominal runtime guard. Pure structural interfaces do not require an implements clause.",
477
478
  remediation: "Use an implementation inheriting the existing abstract class, or remove the nominal guard if the boundary is intentionally structural.",
@@ -479,8 +480,8 @@ var REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION = {
479
480
  autofix: "none",
480
481
  limitations: [
481
482
  "Requires direct construction of both owned consumer and collaborator classes, with a first-statement consumer constructor guard rejecting the exact parameter with if (!(parameter instanceof Contract)) throw.",
482
- "The contract must resolve to an abstract class without decorators, unresolved ancestors, or custom computed static members in its resolved hierarchy.",
483
- "Structural interfaces, non-rejecting branches, reassignment before the guard, casts, unresolved heritage, dynamic factories, explicit implementation constructors or initializers, modules referencing prototype-mutator members or assigning prototypes, and third-party classes are excluded."
483
+ "The contract must resolve to an abstract class without decorators, static blocks, unresolved ancestors, or custom computed static members in its resolved hierarchy. Escaped class constructors are checked across every owned module in the typed program.",
484
+ "Structural interfaces, non-rejecting branches, reassignment before the guard, casts, unresolved heritage, dynamic factories, implementation static blocks, explicit implementation constructors or initializers, involved modules referencing prototype-mutator members or assigning prototypes, escaped class constructors, and third-party classes are excluded."
484
485
  ],
485
486
  examples: [
486
487
  {
@@ -503,6 +504,115 @@ var REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION = {
503
504
  }
504
505
  ]
505
506
  };
507
+ function hasPrototypeMutation(source) {
508
+ function visit(node) {
509
+ const member = memberName(node);
510
+ if (member === "setPrototypeOf" || member === "__proto__" || member === "hasInstance") return true;
511
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && memberName(node.left) === "prototype") return true;
512
+ return ts.forEachChild(node, visit);
513
+ }
514
+ return visit(source) === true;
515
+ }
516
+ function memberName(node) {
517
+ if (ts.isPropertyAccessExpression(node)) return node.name.text;
518
+ if (ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression)) return node.argumentExpression.text;
519
+ return null;
520
+ }
521
+ function ownedSource(source) {
522
+ return !source.isDeclarationFile && !/(?:^|[/\\])(?:node_modules|vendor|vendored|generated|dist)(?:[/\\])/u.test(source.fileName) && !isGeneratedFile(source.fileName, source.text);
523
+ }
524
+ function escapedClasses(program, checker) {
525
+ const cached = ESCAPED_CLASSES.get(program);
526
+ if (cached !== void 0) return cached;
527
+ const escaped = /* @__PURE__ */ new Set();
528
+ const namespaceValues = /* @__PURE__ */ new Map();
529
+ function visit(node) {
530
+ if (ts.isTypeNode(node) || ts.isImportDeclaration(node)) return;
531
+ if (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
532
+ recordEscapedReference(checker, node, escaped, namespaceValues);
533
+ }
534
+ ts.forEachChild(node, visit);
535
+ }
536
+ for (const source of program.getSourceFiles()) if (ownedSource(source)) visit(source);
537
+ ESCAPED_CLASSES.set(program, escaped);
538
+ return escaped;
539
+ }
540
+ function recordEscapedReference(checker, node, escaped, namespaceValues) {
541
+ const symbol = canonicalSymbol(checker, node);
542
+ if (symbol !== void 0) {
543
+ let values = namespaceValues.get(symbol);
544
+ if (values === void 0) {
545
+ values = ownedClassValues(checker, symbol);
546
+ namespaceValues.set(symbol, values);
547
+ }
548
+ if (!namespacePrefix(symbol, node) && !safeClassReference(node)) for (const value of values) escaped.add(value);
549
+ }
550
+ if (ts.isElementAccessExpression(node) && !safeClassReference(node)) {
551
+ const receiver = canonicalSymbol(checker, node.expression) ?? checker.getTypeAtLocation(node.expression).getSymbol();
552
+ if (receiver !== void 0) for (const value of ownedClassValues(checker, receiver)) escaped.add(value);
553
+ }
554
+ }
555
+ function ownedClassValues(checker, original, seen = /* @__PURE__ */ new Set()) {
556
+ const symbol = original.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(original) : original;
557
+ const values = /* @__PURE__ */ new Set();
558
+ if (seen.has(symbol)) return values;
559
+ seen.add(symbol);
560
+ if (symbol.declarations?.some((declaration) => ts.isClassDeclaration(declaration) && ownedSource(declaration.getSourceFile()))) values.add(symbol);
561
+ if (symbol.declarations?.some((declaration) => (ts.isSourceFile(declaration) || ts.isModuleDeclaration(declaration)) && ownedSource(declaration.getSourceFile()))) {
562
+ for (const exported of checker.getExportsOfModule(symbol)) {
563
+ for (const value of ownedClassValues(checker, exported, seen)) values.add(value);
564
+ }
565
+ }
566
+ return values;
567
+ }
568
+ function safeClassReference(node) {
569
+ let reference = ts.isPropertyAccessExpression(node.parent) && node.parent.name === node ? node.parent : node;
570
+ while (ts.isParenthesizedExpression(reference.parent)) reference = reference.parent;
571
+ const parent = reference.parent;
572
+ return ts.isClassDeclaration(parent) && parent.name === reference || ts.isNewExpression(parent) && parent.expression === reference || ts.isExpressionWithTypeArguments(parent) && parent.expression === reference || ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword && parent.right === reference;
573
+ }
574
+ function namespacePrefix(symbol, node) {
575
+ const parent = node.parent;
576
+ const memberCall = (ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent)) && ts.isCallExpression(parent.parent) && parent.parent.expression === parent;
577
+ return !symbol.declarations?.some(ts.isClassDeclaration) && (ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent)) && parent.expression === node && !memberCall;
578
+ }
579
+ function closedNominalBoundary(program, checker, call, consumer, missing) {
580
+ const { implementation, contractSymbol } = missing;
581
+ const implementationSymbol = implementation.name === void 0 ? void 0 : canonicalSymbol(checker, implementation.name);
582
+ const consumerSymbol = consumer.name === void 0 ? void 0 : canonicalSymbol(checker, consumer.name);
583
+ if (implementationSymbol === void 0 || consumerSymbol === void 0) return false;
584
+ const contract = contractSymbol.declarations?.find(ts.isClassDeclaration);
585
+ if (contract === void 0) return false;
586
+ const targets = /* @__PURE__ */ new Set([consumerSymbol, ...classHierarchySymbols(checker, contract), ...classHierarchySymbols(checker, implementation)]);
587
+ const sources = /* @__PURE__ */ new Set([call.getSourceFile(), consumer.getSourceFile(), implementation.getSourceFile(), ...contractSymbol.declarations?.map((item) => item.getSourceFile()) ?? []]);
588
+ return ![...sources].some(hasPrototypeMutation) && ![...targets].some((target) => escapedClasses(program, checker).has(target));
589
+ }
590
+ function classHierarchySymbols(checker, declaration, seen = /* @__PURE__ */ new Set()) {
591
+ const symbol = declaration.name === void 0 ? void 0 : canonicalSymbol(checker, declaration.name);
592
+ if (symbol === void 0 || seen.has(symbol)) return seen;
593
+ seen.add(symbol);
594
+ for (const clause of declaration.heritageClauses ?? []) {
595
+ if (clause.token !== ts.SyntaxKind.ExtendsKeyword) continue;
596
+ for (const parent of clause.types) {
597
+ const ancestor = canonicalSymbol(checker, parent.expression)?.declarations?.find(ts.isClassDeclaration);
598
+ if (ancestor !== void 0) classHierarchySymbols(checker, ancestor, seen);
599
+ }
600
+ }
601
+ return seen;
602
+ }
603
+ function missingImplementation(checker, signature, parameter, call, argument) {
604
+ const parameterType = checker.getTypeOfSymbolAtLocation(parameter, call);
605
+ const contractType = behavioralContract(checker, parameterType);
606
+ if (contractType === null) return null;
607
+ const contractSymbol = contractType.getSymbol();
608
+ if (contractSymbol === void 0 || !hasNominalRejectionGuard(checker, signature, parameter, contractSymbol)) return null;
609
+ const actualType = checker.getTypeAtLocation(argument);
610
+ if (!checker.isTypeAssignableTo(actualType, contractType)) return null;
611
+ const implementation = ownedClass(checker, argument);
612
+ if (implementation === null) return null;
613
+ if (nominallyInherits(checker, implementation, contractSymbol, /* @__PURE__ */ new Set()) !== false) return null;
614
+ return { implementation, contractSymbol };
615
+ }
506
616
  function behavioralContract(checker, type) {
507
617
  if (type.isUnion()) {
508
618
  const substantive = type.types.filter((member) => (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) === 0);
@@ -527,23 +637,9 @@ function ownedClass(checker, expression) {
527
637
  if (declaration === void 0 || declaration !== constructed || declaration.getSourceFile().isDeclarationFile || /(?:^|[/\\])(?:node_modules|vendor|generated)(?:[/\\])/u.test(declaration.getSourceFile().fileName)) return null;
528
638
  return declaration;
529
639
  }
530
- function memberName(node) {
531
- if (ts.isPropertyAccessExpression(node)) return node.name.text;
532
- if (ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression)) return node.argumentExpression.text;
533
- return null;
534
- }
535
- function hasPrototypeMutation(source) {
536
- function visit(node) {
537
- const member = memberName(node);
538
- if (member === "setPrototypeOf" || member === "__proto__" || member === "hasInstance") return true;
539
- if (ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && memberName(node.left) === "prototype") return true;
540
- return ts.forEachChild(node, visit);
541
- }
542
- return visit(source) === true;
543
- }
544
640
  function nominallyInherits(checker, declaration, target, seen) {
545
641
  if (ts.canHaveDecorators(declaration) && ts.getDecorators(declaration)?.length) return null;
546
- if (hasPrototypeMutation(declaration.getSourceFile()) || declaration.members.some((member) => ts.isConstructorDeclaration(member) || ts.isPropertyDeclaration(member) && member.initializer !== void 0)) return null;
642
+ if (hasPrototypeMutation(declaration.getSourceFile()) || declaration.members.some((member) => ts.isConstructorDeclaration(member) || ts.isClassStaticBlockDeclaration(member) || ts.isPropertyDeclaration(member) && member.initializer !== void 0)) return null;
547
643
  const symbol = declaration.name === void 0 ? void 0 : canonicalSymbol(checker, declaration.name);
548
644
  if (symbol !== void 0) {
549
645
  if (seen.has(symbol)) return null;
@@ -558,10 +654,6 @@ function nominallyInherits(checker, declaration, target, seen) {
558
654
  const inherited = parentSymbol.declarations?.find(ts.isClassDeclaration);
559
655
  return inherited === void 0 ? null : nominallyInherits(checker, inherited, target, new Set(seen));
560
656
  }
561
- function canonicalSymbol(checker, node) {
562
- const symbol = checker.getSymbolAtLocation(node);
563
- return symbol === void 0 ? void 0 : (symbol.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
564
- }
565
657
  function hasNominalRejectionGuard(checker, signature, parameter, contract) {
566
658
  const declaration = signature.getDeclaration();
567
659
  if (declaration === void 0 || !ts.isConstructorDeclaration(declaration)) return false;
@@ -586,7 +678,7 @@ function alwaysThrows(statement) {
586
678
  return ts.isThrowStatement(statement) || ts.isBlock(statement) && statement.statements.length === 1 && ts.isThrowStatement(statement.statements[0]);
587
679
  }
588
680
  function hasOrdinaryInstanceCheck(checker, declaration, seen) {
589
- if (seen.has(declaration) || ts.getDecorators(declaration)?.length || hasPrototypeMutation(declaration.getSourceFile())) return false;
681
+ if (seen.has(declaration) || ts.getDecorators(declaration)?.length || hasPrototypeMutation(declaration.getSourceFile()) || declaration.members.some(ts.isClassStaticBlockDeclaration)) return false;
590
682
  seen.add(declaration);
591
683
  if (declaration.members.some((member) => member.name !== void 0 && ts.isComputedPropertyName(member.name) && ts.canHaveModifiers(member) && ts.getModifiers(member)?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword))) return false;
592
684
  for (const clause of declaration.heritageClauses ?? []) {
@@ -598,6 +690,10 @@ function hasOrdinaryInstanceCheck(checker, declaration, seen) {
598
690
  }
599
691
  return true;
600
692
  }
693
+ function canonicalSymbol(checker, node) {
694
+ const symbol = checker.getSymbolAtLocation(node);
695
+ return symbol === void 0 ? void 0 : (symbol.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
696
+ }
601
697
  var require_explicit_contract_implementation_default = createRule({
602
698
  name: "require-explicit-contract-implementation",
603
699
  documentation: REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION,
@@ -627,22 +723,13 @@ var require_explicit_contract_implementation_default = createRule({
627
723
  if (argument.type !== import_utils2.AST_NODE_TYPES.NewExpression) continue;
628
724
  const parameter = parameters[index];
629
725
  if (parameter === void 0) continue;
630
- const parameterType = checker.getTypeOfSymbolAtLocation(parameter, tsNode);
631
- const contractType = behavioralContract(checker, parameterType);
632
- if (contractType === null) continue;
633
- const contractSymbol = contractType.getSymbol();
634
- if (contractSymbol === void 0 || !hasNominalRejectionGuard(checker, signature, parameter, contractSymbol)) continue;
635
726
  const tsArgument = services.esTreeNodeToTSNodeMap.get(argument);
636
- const actualType = checker.getTypeAtLocation(tsArgument);
637
- if (!checker.isTypeAssignableTo(actualType, contractType)) continue;
638
- const implementation = ownedClass(checker, tsArgument);
639
- if (implementation === null) continue;
640
- const relationship = nominallyInherits(checker, implementation, contractSymbol, /* @__PURE__ */ new Set());
641
- if (relationship !== false) continue;
727
+ const missing = missingImplementation(checker, signature, parameter, tsNode, tsArgument);
728
+ if (missing === null || !closedNominalBoundary(services.program, checker, tsNode, consumer, missing)) continue;
642
729
  context.report({
643
730
  node: argument,
644
731
  messageId: "declareActualContract",
645
- data: { implementation: implementation.name?.text ?? "class", contract: contractSymbol.getName() }
732
+ data: { implementation: missing.implementation.name?.text ?? "class", contract: missing.contractSymbol.getName() }
646
733
  });
647
734
  }
648
735
  }
@@ -1482,7 +1569,7 @@ function abstractContractOperations(node, functionAliases) {
1482
1569
 
1483
1570
  // src/rules/require-explicit-service-contract.ts
1484
1571
  var REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION = {
1485
- defaultLevel: "warning",
1572
+ defaultLevel: "error",
1486
1573
  summary: "Require explicit contracts for classes that invoke retained collaborators.",
1487
1574
  rationale: "A one-operation service can orchestrate a collaborator without declaring the contract that callers consume and implementations and fakes satisfy.",
1488
1575
  remediation: "Explicitly implement a focused interface or extend an abstract class covering the service operations. Export the contract only when its consumers need it.",
@@ -7469,7 +7556,7 @@ function branchTerminates(branch) {
7469
7556
  var import_utils42 = require("@typescript-eslint/utils");
7470
7557
  var import_typescript5 = __toESM(require("typescript"), 1);
7471
7558
  var NO_JSON_STRINGIFY_OBJECT_EQUALITY_DOCUMENTATION = {
7472
- defaultLevel: "warning",
7559
+ defaultLevel: "error",
7473
7560
  summary: "Do not use JSON serialization as structural object equality.",
7474
7561
  rationale: "JSON text equality depends on property insertion order and serialization behavior, so semantically equal objects can compare unequal and distinct values can collapse together.",
7475
7562
  remediation: "Compare an explicit domain projection structurally, or use a reviewed canonical serializer when JSON semantics are required.",
@@ -12690,7 +12777,7 @@ var TEST_MODULES = /* @__PURE__ */ new Set(["vitest", "@jest/globals", "bun:test
12690
12777
  var TEST_NAMES2 = /* @__PURE__ */ new Set(["it", "test"]);
12691
12778
  var MODIFIERS = /* @__PURE__ */ new Set(["only", "skip", "concurrent", "fails", "failing", "todo"]);
12692
12779
  var NO_DUPLICATE_TEST_CASE_DOCUMENTATION = {
12693
- defaultLevel: "warning",
12780
+ defaultLevel: "error",
12694
12781
  summary: "Report repeated literal rows in framework test.each or it.each tables.",
12695
12782
  rationale: "Identical rows repeat the same inputs and expected values without exposing a distinct case. Accidental duplicates consume test time and hide a missing boundary vector.",
12696
12783
  remediation: "Remove the accidental duplicate or replace it with the intended distinct boundary. Preserve intentional repeated or stateful scenarios with a local explanation; do not delete their coverage automatically.",
@@ -17549,7 +17636,7 @@ var OPERATIONAL_ROLES = /* @__PURE__ */ new Set([
17549
17636
  ]);
17550
17637
  var MAX_ALIAS_DEPTH = 16;
17551
17638
  var PREFER_NOMINAL_ID_TYPES_DOCUMENTATION = {
17552
- defaultLevel: "warning",
17639
+ defaultLevel: "error",
17553
17640
  summary: "Distinguish identifier roles sharing primitive types at callable boundaries.",
17554
17641
  rationale: "Two identifier roles represented by the same primitive accept each other's values, so swapped arguments survive type checking. Transparent aliases do not prevent this.",
17555
17642
  remediation: "Introduce distinct branded identifier types at a validated domain boundary and use them in the callable contract. Keep wire representations primitive until validation; suppress a required external signature locally with a reason.",
@@ -17714,14 +17801,14 @@ var prefer_named_callback_domain_default = createRule({
17714
17801
  // src/rules/prefer-named-complex-return-type.ts
17715
17802
  var import_utils101 = require("@typescript-eslint/utils");
17716
17803
  var PREFER_NAMED_COMPLEX_RETURN_TYPE_DOCUMENTATION = {
17717
- defaultLevel: "warning",
17804
+ defaultLevel: "error",
17718
17805
  summary: "Prefer a named contract for structurally complex function return types.",
17719
17806
  rationale: "A large inline return annotation hides a reusable domain concept and makes signatures difficult to scan.",
17720
17807
  remediation: "Name the complex nested shape while preserving its generic wrappers and type parameters; reference the named contract from the return annotation.",
17721
17808
  category: "maintainability",
17722
17809
  limitations: [
17723
17810
  "Only explicit object types with at least three members and unions with at least three object variants are reported.",
17724
- "Any single-argument generic wrapper is traversed to find nested shapes, not assumed semantically transparent. Inferred return types are outside this rule; extraction is manual and must preserve locally bound type parameters."
17811
+ "Any single-argument generic wrapper is traversed to find nested shapes, not assumed semantically transparent. Inferred return types are outside this rule; extraction is manual and must preserve locally bound type parameters with generic named contracts. Types depending on polymorphic this or a local value query are excluded."
17725
17812
  ],
17726
17813
  examples: [
17727
17814
  { id: "named-result", title: "Name a multi-state result", outcome: "no-match", files: [{ path: "src/queue.ts", source: "type ClaimResult = { state: 'idle' } | { state: 'waiting'; retryAt: number } | { state: 'claimed'; id: string }; export function claim(): ClaimResult { return { state: 'idle' }; }" }], focusPath: "src/queue.ts", expectedCount: 0, public: true },
@@ -17737,10 +17824,13 @@ function unwrap4(node) {
17737
17824
  }
17738
17825
  function report(context, node) {
17739
17826
  const annotation = node.returnType?.typeAnnotation;
17740
- if (annotation !== void 0 && isComplex(annotation)) {
17827
+ if (annotation !== void 0 && isComplex(annotation) && !hasValueDependency(annotation)) {
17741
17828
  context.report({ node: annotation, messageId: "nameComplexReturnType" });
17742
17829
  }
17743
17830
  }
17831
+ function hasValueDependency(node) {
17832
+ return node.type === import_utils101.AST_NODE_TYPES.TSThisType || node.type === import_utils101.AST_NODE_TYPES.TSTypeQuery || forEachOwnAstChild(node, hasValueDependency);
17833
+ }
17744
17834
  function isComplex(node) {
17745
17835
  const type = unwrap4(node);
17746
17836
  if (type.type === import_utils101.AST_NODE_TYPES.TSTypeLiteral) return type.members.length >= 3;
@@ -19388,20 +19478,20 @@ var prefer_shared_zod_enum_default = createRule({
19388
19478
  // src/rules/prefer-switch-for-repeated-equality.ts
19389
19479
  var import_utils110 = require("@typescript-eslint/utils");
19390
19480
  var PREFER_SWITCH_FOR_REPEATED_EQUALITY_DOCUMENTATION = {
19391
- defaultLevel: "warning",
19481
+ defaultLevel: "error",
19392
19482
  summary: "Prefer switch over long if/else-if chains that compare one value for strict equality.",
19393
19483
  rationale: "A switch makes finite dispatch cases visually uniform and easier to extend without duplicating the discriminant.",
19394
19484
  remediation: "Replace three or more strict-equality branches over the same discriminant with a switch; keep if statements for ranges, guards, and heterogeneous predicates.",
19395
19485
  category: "maintainability",
19396
19486
  limitations: [
19397
19487
  "Only direct if/else-if chains with at least three strict-equality tests are reported.",
19398
- "The discriminant must be a bare identifier; calls, getters, indexed reads and other repeatedly evaluated expressions are excluded. Review case-value effects, selector mutation, branch scoping, and break/continue targets when converting manually; this is not an equivalence proof.",
19399
- "Case values may be literals, enum-like member references, or upper-case named constants; dynamic expressions are excluded.",
19488
+ "The discriminant must be a resolved lexical variable or parameter; unresolved globals, calls, getters, and indexed reads are excluded. Preserve branch scopes and break/continue targets when converting manually; no autofix is offered.",
19489
+ "Case values must be primitive literals. Member references and named constants are excluded because evaluating them can observe getters or mutate the selector. Generated sources are excluded.",
19400
19490
  "The rule deliberately ignores compound predicates, loose equality, ranges, and chains that compare different discriminants."
19401
19491
  ],
19402
19492
  examples: [
19403
19493
  { id: "switch-dispatch", title: "Make finite dispatch explicit", outcome: "no-match", files: [{ path: "src/render.ts", source: "switch (kind) { case 'a': return a(); case 'b': return b(); case 'c': return c(); default: return fallback(); }" }], focusPath: "src/render.ts", expectedCount: 0, public: true },
19404
- { id: "repeated-equality", title: "Avoid repeating the discriminant", outcome: "match", files: [{ path: "src/render.ts", source: "if (kind === 'a') return a(); else if (kind === 'b') return b(); else if (kind === 'c') return c();" }], focusPath: "src/render.ts", expectedCount: 1, public: true },
19494
+ { id: "repeated-equality", title: "Avoid repeating the discriminant", outcome: "match", files: [{ path: "src/render.ts", source: "function render(kind: string) { if (kind === 'a') return a(); else if (kind === 'b') return b(); else if (kind === 'c') return c(); }" }], focusPath: "src/render.ts", expectedCount: 1, public: true },
19405
19495
  { id: "escaped-middle", scenarioId: "native-discriminant", title: "Use native identifier identity", outcome: "match", files: [{ path: "src/dispatch.ts", source: `function probe(kind){if(kind==='a')return 1;else if(\\u006bind==='b')return 2;else if(kind==='c')return 3;return 0;}
19406
19496
  console.log(JSON.stringify(["a","b","c","d"].map(probe)));` }], focusPath: "src/dispatch.ts", expectedCount: 1, public: true },
19407
19497
  { id: "different-discriminants", scenarioId: "native-discriminant", title: "Use native identifier identity", outcome: "no-match", files: [{ path: "src/dispatch.ts", source: `function probe(kind){const other="other";if(kind==='a')return 1;else if(other==='b')return 2;else if(kind==='c')return 3;return 0;}
@@ -19417,10 +19507,7 @@ function discriminantName(test) {
19417
19507
  return discriminant.type === import_utils110.AST_NODE_TYPES.Identifier ? discriminant.name : null;
19418
19508
  }
19419
19509
  function isCaseValue(node) {
19420
- if (node.type === import_utils110.AST_NODE_TYPES.Literal) return true;
19421
- if (node.type === import_utils110.AST_NODE_TYPES.Identifier) return /^[A-Z][A-Z0-9_]*$/u.test(node.name);
19422
- if (node.type !== import_utils110.AST_NODE_TYPES.MemberExpression || node.computed) return false;
19423
- return node.property.type === import_utils110.AST_NODE_TYPES.Identifier && (node.object.type === import_utils110.AST_NODE_TYPES.Identifier || isCaseValue(node.object));
19510
+ return node.type === import_utils110.AST_NODE_TYPES.Literal && !("regex" in node);
19424
19511
  }
19425
19512
  var prefer_switch_for_repeated_equality_default = createRule({
19426
19513
  name: "prefer-switch-for-repeated-equality",
@@ -19435,11 +19522,16 @@ var prefer_switch_for_repeated_equality_default = createRule({
19435
19522
  },
19436
19523
  defaultOptions: [],
19437
19524
  create(context) {
19525
+ if (isGeneratedFile(context.filename, context.sourceCode.text)) return {};
19438
19526
  return {
19439
19527
  IfStatement(node) {
19440
19528
  if (node.parent.type === import_utils110.AST_NODE_TYPES.IfStatement && node.parent.alternate === node) return;
19441
19529
  const first = discriminantName(node.test);
19442
19530
  if (first === null) return;
19531
+ const binding = import_utils110.ASTUtils.findVariable(context.sourceCode.getScope(node), first);
19532
+ if (binding === null || binding.defs.length !== 1 || binding.scope.type === "global") return;
19533
+ const [definition] = binding.defs;
19534
+ if (definition?.type !== "Parameter" && definition?.type !== "Variable") return;
19443
19535
  let count = 1;
19444
19536
  let current = node.alternate;
19445
19537
  while (current?.type === import_utils110.AST_NODE_TYPES.IfStatement) {
@@ -21446,17 +21538,17 @@ var UNRESOLVED_TYPE_NODES = /* @__PURE__ */ new Set([
21446
21538
  import_utils116.AST_NODE_TYPES.TSUnknownKeyword
21447
21539
  ]);
21448
21540
  var NAMED_CHILD_SCHEMA_TYPE_DOCUMENTATION = {
21449
- defaultLevel: "warning",
21541
+ defaultLevel: "error",
21450
21542
  summary: "Reference a named Zod record child's complete schema type instead of repeating its explicit annotation.",
21451
21543
  rationale: "Repeating a child schema's generic contract creates a second maintained declaration even though the record already uses that child at runtime.",
21452
- remediation: "Replace only the matching record type argument with `typeof ChildSchema`; retain the outer annotation required by isolatedDeclarations and every runtime schema operation.",
21544
+ remediation: "Replace only the matching record type argument with `typeof ChildSchema`, or share one explicit named schema type alias between child and record to retain an independently named public contract. Keep the outer annotation required by isolatedDeclarations and every runtime schema operation.",
21453
21545
  category: "maintainability",
21454
21546
  autofix: "none",
21455
21547
  limitations: [
21456
21548
  "Only direct module-level const z.record calls with two arguments, a ZodRecord annotation, and an earlier local const child with a complete explicit Zod type are inspected.",
21457
21549
  "The entire generic child annotation must match token for token; input/output, defaults, transforms, readonly modifiers and brands are never compared by output shape alone.",
21458
21550
  "Bare leaf types, broad ZodType constraints, type queries, imported or unqualified type references, annotation cycles, comments on the repeated argument, mutable bindings and generated files are excluded.",
21459
- "Import aliases and namespace imports resolve through scope bindings. Independent public contract pins require review; this warning does not remove annotations or change runtime validation."
21551
+ "Import aliases and namespace imports resolve through scope bindings. Independent public contract pins can use one shared named type alias or an exact reasoned suppression; runtime validation and outer annotations are preserved."
21460
21552
  ],
21461
21553
  examples: [
21462
21554
  { id: "named-child", title: "Reference the named child contract", outcome: "no-match", files: [{ path: "src/record.ts", source: 'import { z } from "zod"; const KeySchema: z.ZodEnum<{ a: "a"; b: "b" }> = z.enum(["a", "b"]); export const RecordSchema: z.ZodRecord<typeof KeySchema, z.ZodString> = z.record(KeySchema, z.string());' }], focusPath: "src/record.ts", expectedCount: 0, public: true },
@@ -21475,7 +21567,7 @@ var named_child_schema_type_default = createRule({
21475
21567
  type: "suggestion",
21476
21568
  docs: { description: NAMED_CHILD_SCHEMA_TYPE_DOCUMENTATION.summary },
21477
21569
  schema: [],
21478
- messages: { namedChildType: "Use `typeof {{name}}` for this exact child contract; keep the outer schema annotation and runtime operations." }
21570
+ messages: { namedChildType: "Use `typeof {{name}}` or one shared named type alias for this exact child contract; keep the outer schema annotation and runtime operations." }
21479
21571
  },
21480
21572
  defaultOptions: [],
21481
21573
  create(context) {
@@ -22190,15 +22282,15 @@ var require_fetch_timeout_default = createRule({
22190
22282
  // src/rules/require-interface-for-exported-class.ts
22191
22283
  var import_utils120 = require("@typescript-eslint/utils");
22192
22284
  var REQUIRE_INTERFACE_FOR_EXPORTED_CLASS_DOCUMENTATION = {
22193
- defaultLevel: "warning",
22285
+ defaultLevel: "error",
22194
22286
  summary: "Require exported concrete classes with public behavior to declare a contract.",
22195
22287
  rationale: "An explicit contract names the intended public capability separately from implementation details. TypeScript already supports structural compatibility; this is an architecture policy, not a prerequisite for substitution.",
22196
22288
  remediation: "Declare a focused interface and add an implements clause, or inherit from an intentional base contract.",
22197
22289
  category: "architecture",
22198
22290
  limitations: [
22199
22291
  "JavaScript files are excluded because implements is TypeScript syntax; imported framework base contracts still require manual policy review.",
22200
- "An exported injected service may also receive require-port-for-service: its service-boundary error and this general exported-contract warning intentionally enforce distinct policy scopes.",
22201
- "The warning-stage rule checks module-level class declarations and direct class-expression values exported directly, through local export specifiers, or through a default identifier; re-exports and expressions wrapped in other calls require review.",
22292
+ "An exported injected service may also receive require-port-for-service: the two diagnostics intentionally enforce distinct service-boundary and general exported-contract policy scopes.",
22293
+ "The rule checks module-level class declarations and direct class-expression values exported directly, through local export specifiers, or through a default identifier; re-exports and expressions wrapped in other calls require review.",
22202
22294
  "An extends clause satisfies the contract only when its target is a locally declared abstract class; imported base-class contracts require an explicit implements clause.",
22203
22295
  "Static factories and data-only classes without public instance methods are outside the contract requirement."
22204
22296
  ],
@@ -24875,30 +24967,21 @@ var LIBRARY_IMPORT_POLICY = ["error", {
24875
24967
  }];
24876
24968
  var ADVISORY_RULES = [
24877
24969
  "@sarj/excessive-commentary",
24878
- "@sarj/named-child-schema-type",
24879
24970
  "@sarj/no-bespoke-api-case-conversion",
24880
- "@sarj/no-duplicate-test-case",
24881
- "@sarj/no-json-stringify-object-equality",
24882
24971
  "@sarj/no-restated-comment",
24883
24972
  "@sarj/no-restated-jsdoc",
24884
24973
  "@sarj/prefer-millisecond-control-duration-schema",
24885
24974
  "@sarj/prefer-module-level-refined-schema",
24886
24975
  "@sarj/prefer-multi-value-zod-literal",
24887
- "@sarj/prefer-named-complex-return-type",
24888
24976
  "@sarj/prefer-node-crypto-hash",
24889
24977
  "@sarj/prefer-node-fs-promises",
24890
- "@sarj/prefer-nominal-id-types",
24891
24978
  "@sarj/prefer-shared-zod-enum",
24892
- "@sarj/prefer-switch-for-repeated-equality",
24893
24979
  "@sarj/prefer-whole-object-assertion",
24894
24980
  "@sarj/require-camelcase-properties",
24895
- "@sarj/require-explicit-contract-implementation",
24896
- "@sarj/require-explicit-service-contract",
24897
- "@sarj/require-interface-for-exported-class",
24898
24981
  "@sarj/require-sql-access-class"
24899
24982
  ];
24900
24983
  var RECOMMENDED_RULES = {
24901
- "@sarj/named-child-schema-type": "warn",
24984
+ "@sarj/named-child-schema-type": "error",
24902
24985
  "@sarj/no-conditional-empty-object-spread": "error",
24903
24986
  "@sarj/prefer-typed-reflection": "error",
24904
24987
  "@sarj/no-broad-return-type": "error",
@@ -24936,7 +25019,7 @@ var RECOMMENDED_RULES = {
24936
25019
  "@sarj/no-bare-return-from-test-catch": "error",
24937
25020
  "@sarj/no-bespoke-api-case-conversion": "warn",
24938
25021
  "@sarj/no-detached-global-fetch": "error",
24939
- "@sarj/no-json-stringify-object-equality": "warn",
25022
+ "@sarj/no-json-stringify-object-equality": "error",
24940
25023
  "@sarj/no-dangerously-allow-svg": "error",
24941
25024
  "@sarj/no-duplicate-lifecycle-refresh-listeners": "error",
24942
25025
  "@sarj/no-long-comment": "error",
@@ -24958,7 +25041,7 @@ var RECOMMENDED_RULES = {
24958
25041
  "@sarj/no-sleep-in-test-body": "error",
24959
25042
  "@sarj/no-string-concat-in-loop": "error",
24960
25043
  "@sarj/no-tautological-expect": "error",
24961
- "@sarj/no-duplicate-test-case": "warn",
25044
+ "@sarj/no-duplicate-test-case": "error",
24962
25045
  "@sarj/no-typed-doc-sections": "error",
24963
25046
  "@sarj/no-trailing-value-narration": "error",
24964
25047
  "@sarj/no-declaration-comment-wall": "error",
@@ -24981,8 +25064,8 @@ var RECOMMENDED_RULES = {
24981
25064
  "@sarj/prefer-module-level-refined-schema": "warn",
24982
25065
  "@sarj/prefer-multi-value-zod-literal": ["warn", { zodMajorVersion: 4 }],
24983
25066
  "@sarj/prefer-named-callback-domain": "error",
24984
- "@sarj/prefer-nominal-id-types": "warn",
24985
- "@sarj/prefer-named-complex-return-type": "warn",
25067
+ "@sarj/prefer-nominal-id-types": "error",
25068
+ "@sarj/prefer-named-complex-return-type": "error",
24986
25069
  "@sarj/prefer-node-crypto-hash": "warn",
24987
25070
  "@sarj/prefer-node-fs-promises": "warn",
24988
25071
  "@sarj/prefer-non-nullable-collection": "error",
@@ -24990,7 +25073,7 @@ var RECOMMENDED_RULES = {
24990
25073
  "@sarj/prefer-await-in-async-return": "error",
24991
25074
  "@sarj/prefer-schema-for-api-payload": "error",
24992
25075
  "@sarj/prefer-shared-zod-enum": "warn",
24993
- "@sarj/prefer-switch-for-repeated-equality": "warn",
25076
+ "@sarj/prefer-switch-for-repeated-equality": "error",
24994
25077
  "@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
24995
25078
  "@sarj/prefer-server-actions": "error",
24996
25079
  "@sarj/prefer-whole-object-assertion": "warn",
@@ -24999,9 +25082,9 @@ var RECOMMENDED_RULES = {
24999
25082
  "@sarj/prefer-zod-parse-output-type": "error",
25000
25083
  "@sarj/require-assert-never": "error",
25001
25084
  "@sarj/require-fetch-timeout": "error",
25002
- "@sarj/require-explicit-contract-implementation": "warn",
25003
- "@sarj/require-explicit-service-contract": "warn",
25004
- "@sarj/require-interface-for-exported-class": "warn",
25085
+ "@sarj/require-explicit-contract-implementation": "error",
25086
+ "@sarj/require-explicit-service-contract": "error",
25087
+ "@sarj/require-interface-for-exported-class": "error",
25005
25088
  "@sarj/require-port-for-service": "error",
25006
25089
  "@sarj/require-sql-access-class": "warn",
25007
25090
  "@sarj/require-static-next-matcher": "error",
@@ -25016,7 +25099,7 @@ var RECOMMENDED_RULES = {
25016
25099
  "@sarj/require-pascal-case-zod-schema-name": "error"
25017
25100
  };
25018
25101
  var STRICT_RULES = {
25019
- "@sarj/named-child-schema-type": "warn",
25102
+ "@sarj/named-child-schema-type": "error",
25020
25103
  "@sarj/no-conditional-empty-object-spread": "error",
25021
25104
  "@sarj/prefer-typed-reflection": "error",
25022
25105
  "@sarj/no-broad-return-type": "error",
@@ -25055,7 +25138,7 @@ var STRICT_RULES = {
25055
25138
  "@sarj/no-bare-return-from-test-catch": "error",
25056
25139
  "@sarj/no-bespoke-api-case-conversion": "warn",
25057
25140
  "@sarj/no-detached-global-fetch": "error",
25058
- "@sarj/no-json-stringify-object-equality": "warn",
25141
+ "@sarj/no-json-stringify-object-equality": "error",
25059
25142
  "@sarj/no-dangerously-allow-svg": "error",
25060
25143
  "@sarj/no-duplicate-lifecycle-refresh-listeners": "error",
25061
25144
  "@sarj/no-long-comment": "error",
@@ -25080,7 +25163,7 @@ var STRICT_RULES = {
25080
25163
  "@sarj/no-storage-in-stateless-modules": "error",
25081
25164
  "@sarj/no-string-concat-in-loop": "error",
25082
25165
  "@sarj/no-tautological-expect": "error",
25083
- "@sarj/no-duplicate-test-case": "warn",
25166
+ "@sarj/no-duplicate-test-case": "error",
25084
25167
  "@sarj/no-typed-doc-sections": "error",
25085
25168
  "@sarj/no-trailing-value-narration": "error",
25086
25169
  "@sarj/no-declaration-comment-wall": "error",
@@ -25103,8 +25186,8 @@ var STRICT_RULES = {
25103
25186
  "@sarj/prefer-module-level-refined-schema": "warn",
25104
25187
  "@sarj/prefer-multi-value-zod-literal": ["warn", { zodMajorVersion: 4 }],
25105
25188
  "@sarj/prefer-named-callback-domain": "error",
25106
- "@sarj/prefer-nominal-id-types": "warn",
25107
- "@sarj/prefer-named-complex-return-type": "warn",
25189
+ "@sarj/prefer-nominal-id-types": "error",
25190
+ "@sarj/prefer-named-complex-return-type": "error",
25108
25191
  "@sarj/prefer-node-crypto-hash": "warn",
25109
25192
  "@sarj/prefer-node-fs-promises": "warn",
25110
25193
  "@sarj/prefer-non-nullable-collection": "error",
@@ -25112,7 +25195,7 @@ var STRICT_RULES = {
25112
25195
  "@sarj/prefer-await-in-async-return": "error",
25113
25196
  "@sarj/prefer-schema-for-api-payload": "error",
25114
25197
  "@sarj/prefer-shared-zod-enum": "warn",
25115
- "@sarj/prefer-switch-for-repeated-equality": "warn",
25198
+ "@sarj/prefer-switch-for-repeated-equality": "error",
25116
25199
  "@sarj/prefer-semantic-colors": ["error", { requireSemanticTokens: true }],
25117
25200
  "@sarj/prefer-server-actions": "error",
25118
25201
  "@sarj/prefer-whole-object-assertion": "warn",
@@ -25121,9 +25204,9 @@ var STRICT_RULES = {
25121
25204
  "@sarj/prefer-zod-parse-output-type": "error",
25122
25205
  "@sarj/require-assert-never": "error",
25123
25206
  "@sarj/require-fetch-timeout": "error",
25124
- "@sarj/require-explicit-contract-implementation": "warn",
25125
- "@sarj/require-explicit-service-contract": "warn",
25126
- "@sarj/require-interface-for-exported-class": "warn",
25207
+ "@sarj/require-explicit-contract-implementation": "error",
25208
+ "@sarj/require-explicit-service-contract": "error",
25209
+ "@sarj/require-interface-for-exported-class": "error",
25127
25210
  "@sarj/require-port-for-service": "error",
25128
25211
  "@sarj/require-sql-access-class": "warn",
25129
25212
  "@sarj/require-static-next-matcher": "error",