@sarj/eslint-plugin 16.2.5 → 16.2.6

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
@@ -349,69 +349,34 @@ function commentLineEnd(sourceText, start) {
349
349
  // src/rules/require-explicit-contract-implementation.ts
350
350
  var REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION = {
351
351
  defaultLevel: "warning",
352
- summary: "Require owned classes supplied as typed collaborators to explicitly implement their contract.",
353
- rationale: "Structural assignment checks a fake only at the use site; an implements clause keeps its obligations visible at the class declaration.",
354
- remediation: "Add an implements clause for the actual injected interface, or inherit an abstract class that declares it.",
352
+ summary: "Require nominal inheritance when an injected abstract class is rejected by an instanceof guard.",
353
+ rationale: "A structurally assignable fake can still fail a constructor's nominal runtime guard. Pure structural interfaces do not require an implements clause.",
354
+ remediation: "Use an implementation inheriting the existing abstract class, or remove the nominal guard if the boundary is intentionally structural.",
355
355
  category: "architecture",
356
356
  autofix: "none",
357
357
  limitations: [
358
- "Only class instances whose concrete class and selected constructor parameter resolve through the TypeScript checker are checked.",
359
- "Object literals, casts, any, callback-valued data properties, unresolved heritage, dynamic factories, and third-party classes are excluded."
358
+ "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.",
359
+ "The contract must resolve to an abstract class without decorators, unresolved ancestors, or custom computed static members in its resolved hierarchy.",
360
+ "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."
360
361
  ],
361
362
  examples: [
362
363
  {
363
- id: "structural-fake-at-injection",
364
- title: "A fake reaches a typed collaborator without implementing its interface",
364
+ id: "runtime-nominal-guard-with-structural-fake",
365
+ title: "A structurally compatible fake fails the constructor's runtime guard",
365
366
  outcome: "match",
366
- files: [{ path: "src/service.ts", source: "interface Publisher { publish(): void } class Consumer { constructor(readonly publisher: Publisher) {} } class FakePublisher { publish() {} } new Consumer(new FakePublisher());" }],
367
+ files: [{ path: "src/service.ts", source: "abstract class Publisher { abstract publish(): void } class Consumer { constructor(readonly publisher: Publisher) { if (!(publisher instanceof Publisher)) throw new TypeError('Nominal publisher required'); } } class FakePublisher { publish() {} } new Consumer(new FakePublisher());" }],
367
368
  focusPath: "src/service.ts",
368
369
  expectedCount: 1,
369
370
  public: true
370
371
  },
371
372
  {
372
- id: "explicit-fake-at-injection",
373
- title: "The fake names its actual interface",
373
+ id: "runtime-nominal-guard-with-inherited-fake",
374
+ title: "The fake satisfies the existing nominal runtime contract",
374
375
  outcome: "no-match",
375
- files: [{ path: "src/service.ts", source: "interface Publisher { publish(): void } class Consumer { constructor(readonly publisher: Publisher) {} } class FakePublisher implements Publisher { publish() {} } new Consumer(new FakePublisher());" }],
376
+ files: [{ path: "src/service.ts", source: "abstract class Publisher { abstract publish(): void } class Consumer { constructor(readonly publisher: Publisher) { if (!(publisher instanceof Publisher)) throw new TypeError('Nominal publisher required'); } } class FakePublisher extends Publisher { publish() {} } new Consumer(new FakePublisher());" }],
376
377
  focusPath: "src/service.ts",
377
378
  expectedCount: 0,
378
379
  public: true
379
- },
380
- {
381
- id: "inherited-implementation",
382
- title: "The fake inherits an implementation of the port",
383
- outcome: "no-match",
384
- files: [{ path: "src/service.ts", source: "interface Publisher { publish(): void } class Consumer { constructor(readonly publisher: Publisher) {} } class BasePublisher implements Publisher { publish() {} } class FakePublisher extends BasePublisher {} new Consumer(new FakePublisher());" }],
385
- focusPath: "src/service.ts",
386
- expectedCount: 0,
387
- public: false
388
- },
389
- {
390
- id: "non-contract-data-argument",
391
- title: "A data object is not a service port",
392
- outcome: "no-match",
393
- files: [{ path: "src/service.ts", source: "interface Options { name: string } class Consumer { constructor(readonly options: Options) {} } class FakeOptions { name = 'test' } new Consumer(new FakeOptions());" }],
394
- focusPath: "src/service.ts",
395
- expectedCount: 0,
396
- public: false
397
- },
398
- {
399
- id: "explicit-cast-origin-unknown",
400
- title: "A cast hides the concrete source",
401
- outcome: "no-match",
402
- files: [{ path: "src/service.ts", source: "interface Publisher { publish(): void } class Consumer { constructor(readonly publisher: Publisher) {} } class FakePublisher { publish() {} } new Consumer(new FakePublisher() as Publisher);" }],
403
- focusPath: "src/service.ts",
404
- expectedCount: 0,
405
- public: false
406
- },
407
- {
408
- id: "unrelated-interface",
409
- title: "Implementing another interface does not declare the supplied port",
410
- outcome: "match",
411
- files: [{ path: "src/service.ts", source: "interface Publisher { publish(): void } interface Other { publish(): void } class Consumer { constructor(readonly publisher: Publisher) {} } class FakePublisher implements Other { publish() {} } new Consumer(new FakePublisher());" }],
412
- focusPath: "src/service.ts",
413
- expectedCount: 1,
414
- public: false
415
380
  }
416
381
  ]
417
382
  };
@@ -422,7 +387,7 @@ function behavioralContract(checker, type) {
422
387
  return behavioralContract(checker, substantive[0]);
423
388
  }
424
389
  if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) return null;
425
- const declaration = type.getSymbol()?.declarations?.find((item) => ts.isInterfaceDeclaration(item) || ts.isTypeAliasDeclaration(item) || ts.isClassDeclaration(item) && item.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword));
390
+ const declaration = type.getSymbol()?.declarations?.find((item) => ts.isClassDeclaration(item) && item.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword));
426
391
  if (declaration === void 0) return null;
427
392
  return checker.getPropertiesOfType(type).some((property) => {
428
393
  if (!property.declarations?.some((member) => ts.isMethodSignature(member) || ts.isMethodDeclaration(member))) return false;
@@ -430,43 +395,86 @@ function behavioralContract(checker, type) {
430
395
  return propertyType.getCallSignatures().length > 0;
431
396
  }) ? type : null;
432
397
  }
433
- function ownedClass(type) {
398
+ function ownedClass(checker, expression) {
399
+ if (!ts.isNewExpression(expression)) return null;
400
+ const type = checker.getTypeAtLocation(expression);
434
401
  if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) return null;
435
402
  const declaration = type.getSymbol()?.declarations?.find(ts.isClassDeclaration);
436
- if (declaration === void 0 || declaration.getSourceFile().isDeclarationFile || /(?:^|[/\\])(?:node_modules|vendor|generated)(?:[/\\])/u.test(declaration.getSourceFile().fileName)) return null;
403
+ const constructed = canonicalSymbol(checker, expression.expression)?.declarations?.find(ts.isClassDeclaration);
404
+ if (declaration === void 0 || declaration !== constructed || declaration.getSourceFile().isDeclarationFile || /(?:^|[/\\])(?:node_modules|vendor|generated)(?:[/\\])/u.test(declaration.getSourceFile().fileName)) return null;
437
405
  return declaration;
438
406
  }
439
- function explicitlyImplements(checker, declaration, target, seen) {
407
+ function memberName(node) {
408
+ if (ts.isPropertyAccessExpression(node)) return node.name.text;
409
+ if (ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression)) return node.argumentExpression.text;
410
+ return null;
411
+ }
412
+ function hasPrototypeMutation(source) {
413
+ function visit(node) {
414
+ const member = memberName(node);
415
+ if (member === "setPrototypeOf" || member === "__proto__" || member === "hasInstance") return true;
416
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && memberName(node.left) === "prototype") return true;
417
+ return ts.forEachChild(node, visit);
418
+ }
419
+ return visit(source) === true;
420
+ }
421
+ function nominallyInherits(checker, declaration, target, seen) {
422
+ if (ts.canHaveDecorators(declaration) && ts.getDecorators(declaration)?.length) return null;
423
+ if (hasPrototypeMutation(declaration.getSourceFile()) || declaration.members.some((member) => ts.isConstructorDeclaration(member) || ts.isPropertyDeclaration(member) && member.initializer !== void 0)) return null;
440
424
  const symbol = declaration.name === void 0 ? void 0 : canonicalSymbol(checker, declaration.name);
441
425
  if (symbol !== void 0) {
442
426
  if (seen.has(symbol)) return null;
443
427
  seen.add(symbol);
444
428
  }
445
- let unknown = false;
446
- for (const clause of declaration.heritageClauses ?? []) {
447
- for (const parent of clause.types) {
448
- const parentSymbol = canonicalSymbol(checker, parent.expression);
449
- if (parentSymbol === void 0) {
450
- unknown = true;
451
- continue;
452
- }
453
- if (parentSymbol === target) return true;
454
- const inherited = parentSymbol.declarations?.find((item) => ts.isClassDeclaration(item) || ts.isInterfaceDeclaration(item));
455
- if (inherited === void 0) {
456
- unknown = true;
457
- continue;
458
- }
459
- const relationship = explicitlyImplements(checker, inherited, target, new Set(seen));
460
- if (relationship === true) return true;
461
- unknown ||= relationship === null;
462
- }
463
- }
464
- return unknown ? null : false;
429
+ const parents = declaration.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword)?.types;
430
+ if (parents === void 0) return false;
431
+ if (parents.length !== 1) return null;
432
+ const parentSymbol = canonicalSymbol(checker, parents[0].expression);
433
+ if (parentSymbol === void 0) return null;
434
+ if (parentSymbol === target) return true;
435
+ const inherited = parentSymbol.declarations?.find(ts.isClassDeclaration);
436
+ return inherited === void 0 ? null : nominallyInherits(checker, inherited, target, new Set(seen));
465
437
  }
466
438
  function canonicalSymbol(checker, node) {
467
439
  const symbol = checker.getSymbolAtLocation(node);
468
440
  return symbol === void 0 ? void 0 : (symbol.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
469
441
  }
442
+ function hasNominalRejectionGuard(checker, signature, parameter, contract) {
443
+ const declaration = signature.getDeclaration();
444
+ if (declaration === void 0 || !ts.isConstructorDeclaration(declaration)) return false;
445
+ if (ts.canHaveDecorators(declaration.parent) && ts.getDecorators(declaration.parent)?.length) return false;
446
+ if (hasPrototypeMutation(declaration.getSourceFile())) return false;
447
+ const first = declaration.body?.statements[0];
448
+ if (first === void 0 || !ts.isIfStatement(first) || !alwaysThrows(first.thenStatement)) return false;
449
+ const condition = unparenthesized(first.expression);
450
+ if (!ts.isPrefixUnaryExpression(condition) || condition.operator !== ts.SyntaxKind.ExclamationToken) return false;
451
+ const comparison = unparenthesized(condition.operand);
452
+ if (!ts.isBinaryExpression(comparison) || comparison.operatorToken.kind !== ts.SyntaxKind.InstanceOfKeyword) return false;
453
+ if (canonicalSymbol(checker, comparison.left) !== parameter || canonicalSymbol(checker, comparison.right) !== contract) return false;
454
+ const target = contract.declarations?.find(ts.isClassDeclaration);
455
+ return target !== void 0 && hasOrdinaryInstanceCheck(checker, target, /* @__PURE__ */ new Set());
456
+ }
457
+ function unparenthesized(expression) {
458
+ let current = expression;
459
+ while (ts.isParenthesizedExpression(current)) current = current.expression;
460
+ return current;
461
+ }
462
+ function alwaysThrows(statement) {
463
+ return ts.isThrowStatement(statement) || ts.isBlock(statement) && statement.statements.length === 1 && ts.isThrowStatement(statement.statements[0]);
464
+ }
465
+ function hasOrdinaryInstanceCheck(checker, declaration, seen) {
466
+ if (seen.has(declaration) || ts.getDecorators(declaration)?.length || hasPrototypeMutation(declaration.getSourceFile())) return false;
467
+ seen.add(declaration);
468
+ 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;
469
+ for (const clause of declaration.heritageClauses ?? []) {
470
+ if (clause.token !== ts.SyntaxKind.ExtendsKeyword) continue;
471
+ for (const parent of clause.types) {
472
+ const ancestor = canonicalSymbol(checker, parent.expression)?.declarations?.find(ts.isClassDeclaration);
473
+ if (ancestor === void 0 || !hasOrdinaryInstanceCheck(checker, ancestor, seen)) return false;
474
+ }
475
+ }
476
+ return true;
477
+ }
470
478
  var require_explicit_contract_implementation_default = createRule({
471
479
  name: "require-explicit-contract-implementation",
472
480
  documentation: REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION,
@@ -475,7 +483,7 @@ var require_explicit_contract_implementation_default = createRule({
475
483
  docs: { description: REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION.summary },
476
484
  schema: [],
477
485
  messages: {
478
- declareActualContract: "`{{implementation}}` is supplied as `{{contract}}` without explicitly implementing that contract."
486
+ declareActualContract: "`{{implementation}}` structurally matches `{{contract}}` but fails the rejecting instanceof guard; use nominal inheritance or make the boundary structural."
479
487
  }
480
488
  },
481
489
  defaultOptions: [],
@@ -486,25 +494,27 @@ var require_explicit_contract_implementation_default = createRule({
486
494
  const checker = services.program.getTypeChecker();
487
495
  function inspect(node) {
488
496
  const tsNode = services.esTreeNodeToTSNodeMap.get(node);
489
- if (!ts.isCallExpression(tsNode) && !ts.isNewExpression(tsNode)) return;
497
+ if (!ts.isNewExpression(tsNode)) return;
498
+ const consumer = canonicalSymbol(checker, tsNode.expression)?.declarations?.find(ts.isClassDeclaration);
499
+ if (consumer === void 0) return;
490
500
  const signature = checker.getResolvedSignature(tsNode);
491
- if (signature === void 0) return;
501
+ if (signature === void 0 || signature.getDeclaration()?.parent !== consumer) return;
492
502
  const parameters = signature.getParameters();
493
503
  for (const [index, argument] of node.arguments.entries()) {
494
- if (argument.type === import_utils2.AST_NODE_TYPES.SpreadElement || argument.type === import_utils2.AST_NODE_TYPES.TSAsExpression || argument.type === import_utils2.AST_NODE_TYPES.TSTypeAssertion || argument.type === import_utils2.AST_NODE_TYPES.TSSatisfiesExpression) continue;
504
+ if (argument.type !== import_utils2.AST_NODE_TYPES.NewExpression) continue;
495
505
  const parameter = parameters[index];
496
506
  if (parameter === void 0) continue;
497
507
  const parameterType = checker.getTypeOfSymbolAtLocation(parameter, tsNode);
498
508
  const contractType = behavioralContract(checker, parameterType);
499
509
  if (contractType === null) continue;
500
510
  const contractSymbol = contractType.getSymbol();
501
- if (contractSymbol === void 0) continue;
511
+ if (contractSymbol === void 0 || !hasNominalRejectionGuard(checker, signature, parameter, contractSymbol)) continue;
502
512
  const tsArgument = services.esTreeNodeToTSNodeMap.get(argument);
503
513
  const actualType = checker.getTypeAtLocation(tsArgument);
504
514
  if (!checker.isTypeAssignableTo(actualType, contractType)) continue;
505
- const implementation = ownedClass(actualType);
515
+ const implementation = ownedClass(checker, tsArgument);
506
516
  if (implementation === null) continue;
507
- const relationship = explicitlyImplements(checker, implementation, contractSymbol, /* @__PURE__ */ new Set());
517
+ const relationship = nominallyInherits(checker, implementation, contractSymbol, /* @__PURE__ */ new Set());
508
518
  if (relationship !== false) continue;
509
519
  context.report({
510
520
  node: argument,
@@ -514,7 +524,6 @@ var require_explicit_contract_implementation_default = createRule({
514
524
  }
515
525
  }
516
526
  return {
517
- CallExpression: inspect,
518
527
  NewExpression: inspect
519
528
  };
520
529
  }
@@ -632,7 +641,7 @@ var propertySignatureTypes = (members) => {
632
641
  };
633
642
  var fileTypeIndex = (program) => {
634
643
  const objects = /* @__PURE__ */ new Map();
635
- const functionAliases = /* @__PURE__ */ new Set();
644
+ const functionAliases = localFunctionAliases(program);
636
645
  function collectDeclaredType(statement) {
637
646
  const declaration = statement.type === import_utils3.AST_NODE_TYPES.ExportNamedDeclaration ? statement.declaration : statement;
638
647
  if (declaration?.type === import_utils3.AST_NODE_TYPES.TSInterfaceDeclaration) {
@@ -641,10 +650,6 @@ var fileTypeIndex = (program) => {
641
650
  }
642
651
  if (declaration?.type !== import_utils3.AST_NODE_TYPES.TSTypeAliasDeclaration) return;
643
652
  const aliased = declaration.typeAnnotation;
644
- if (aliased.type === import_utils3.AST_NODE_TYPES.TSFunctionType || aliased.type === import_utils3.AST_NODE_TYPES.TSConstructorType) {
645
- functionAliases.add(declaration.id.name);
646
- return;
647
- }
648
653
  const literals = aliased.type === import_utils3.AST_NODE_TYPES.TSTypeLiteral ? [aliased] : aliased.type === import_utils3.AST_NODE_TYPES.TSIntersectionType ? aliased.types.filter((part) => part.type === import_utils3.AST_NODE_TYPES.TSTypeLiteral) : [];
649
654
  if (literals.length === 0) return;
650
655
  const merged = /* @__PURE__ */ new Map();
@@ -660,6 +665,28 @@ var fileTypeIndex = (program) => {
660
665
  }
661
666
  return { objects, functionAliases };
662
667
  };
668
+ function localFunctionAliases(program) {
669
+ const names = /* @__PURE__ */ new Set();
670
+ const parents = /* @__PURE__ */ new Map();
671
+ for (const statement of program.body) {
672
+ const declaration = statement.type === import_utils3.AST_NODE_TYPES.ExportNamedDeclaration ? statement.declaration : statement;
673
+ if (declaration?.type !== import_utils3.AST_NODE_TYPES.TSTypeAliasDeclaration) continue;
674
+ const annotation = declaration.typeAnnotation;
675
+ if (annotation.type === import_utils3.AST_NODE_TYPES.TSFunctionType || annotation.type === import_utils3.AST_NODE_TYPES.TSConstructorType) names.add(declaration.id.name);
676
+ if (annotation.type === import_utils3.AST_NODE_TYPES.TSTypeReference && annotation.typeName.type === import_utils3.AST_NODE_TYPES.Identifier) parents.set(declaration.id.name, annotation.typeName.name);
677
+ }
678
+ for (let pass = 0; pass < parents.size; pass += 1) {
679
+ let changed = false;
680
+ for (const [name, parent] of parents) {
681
+ if (names.has(parent) && !names.has(name)) {
682
+ names.add(name);
683
+ changed = true;
684
+ }
685
+ }
686
+ if (!changed) break;
687
+ }
688
+ return names;
689
+ }
663
690
  var typeParameterNames = (...declarations) => {
664
691
  const names = /* @__PURE__ */ new Set();
665
692
  for (const declaration of declarations) {
@@ -671,12 +698,15 @@ var readConstructor = (ctor, declared, typeParameters) => {
671
698
  const body2 = ctor.value.body;
672
699
  const storedFieldsFrom = /* @__PURE__ */ new Map();
673
700
  const storedMemberFieldsFrom = /* @__PURE__ */ new Map();
701
+ const constructedFieldNames = /* @__PURE__ */ new Set();
702
+ const localBindings2 = /* @__PURE__ */ new Set();
674
703
  let constructedFields = 0;
675
704
  if (body2 !== null && body2 !== void 0) scanStoredFields(body2);
676
705
  function scanStoredFields(body3) {
677
706
  const pending = [...body3.body];
678
707
  function recordStoredField(current) {
679
708
  if (current.type === import_utils3.AST_NODE_TYPES.ArrowFunctionExpression || current.type === import_utils3.AST_NODE_TYPES.FunctionExpression || current.type === import_utils3.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils3.AST_NODE_TYPES.ClassExpression || current.type === import_utils3.AST_NODE_TYPES.ClassDeclaration) return;
709
+ if (current.type === import_utils3.AST_NODE_TYPES.VariableDeclarator && current.id.type === import_utils3.AST_NODE_TYPES.Identifier) localBindings2.add(current.id.name);
680
710
  const expression = current.type === import_utils3.AST_NODE_TYPES.ExpressionStatement ? current.expression : null;
681
711
  const storedField = expression?.type === import_utils3.AST_NODE_TYPES.AssignmentExpression && expression.left.type === import_utils3.AST_NODE_TYPES.MemberExpression && expression.left.object.type === import_utils3.AST_NODE_TYPES.ThisExpression ? staticMemberName(expression.left) : null;
682
712
  if (expression?.type !== import_utils3.AST_NODE_TYPES.AssignmentExpression || !STORAGE_ASSIGNMENT_OPERATORS.has(expression.operator) || expression.left.type !== import_utils3.AST_NODE_TYPES.MemberExpression || expression.left.object.type !== import_utils3.AST_NODE_TYPES.ThisExpression || storedField === null) {
@@ -687,6 +717,7 @@ var readConstructor = (ctor, declared, typeParameters) => {
687
717
  while (source.type === import_utils3.AST_NODE_TYPES.TSNonNullExpression || source.type === import_utils3.AST_NODE_TYPES.TSAsExpression || source.type === import_utils3.AST_NODE_TYPES.TSSatisfiesExpression || source.type === import_utils3.AST_NODE_TYPES.TSTypeAssertion) source = source.expression;
688
718
  if (source.type === import_utils3.AST_NODE_TYPES.NewExpression) {
689
719
  constructedFields += 1;
720
+ constructedFieldNames.add(storedField);
690
721
  } else if (source.type === import_utils3.AST_NODE_TYPES.Identifier) {
691
722
  const fields = storedFieldsFrom.get(source.name) ?? /* @__PURE__ */ new Set();
692
723
  fields.add(storedField);
@@ -719,15 +750,19 @@ var readConstructor = (ctor, declared, typeParameters) => {
719
750
  const collaborators = [];
720
751
  function collectCollaborators(parameter) {
721
752
  for (const reference of parameterCollaborators(parameter, declared, storedMemberFieldsFrom)) {
722
- const fields = parameter.type === import_utils3.AST_NODE_TYPES.TSParameterProperty ? [reference.name] : reference.fields.length > 0 ? reference.fields : [...storedFieldsFrom.get(reference.name) ?? []];
723
- if (fields.length === 0) continue;
753
+ if (localBindings2.has(reference.name)) continue;
754
+ let fields = reference.fields;
755
+ if (parameter.type === import_utils3.AST_NODE_TYPES.TSParameterProperty) fields = [reference.name];
756
+ else if (fields.length === 0) fields = [...storedFieldsFrom.get(reference.name) ?? []];
757
+ const injectedFields = fields.filter((field) => !constructedFieldNames.has(field));
758
+ if (injectedFields.length === 0) continue;
724
759
  if (CONFIGISH_TYPE_RE.test(reference.typeName)) continue;
725
760
  if (CONFIGISH_NAME_RE.test(reference.name)) continue;
726
761
  if (typeParameters.has(reference.typeName)) continue;
727
762
  if (BUILTIN_CONTAINER_TYPE_RE.test(reference.typeName)) continue;
728
763
  if (VALUE_TYPE_RE.test(reference.typeName)) continue;
729
764
  if (declared().functionAliases.has(reference.typeName)) continue;
730
- collaborators.push({ ...reference, fields });
765
+ collaborators.push({ ...reference, fields: injectedFields });
731
766
  }
732
767
  }
733
768
  for (const parameter of ctor.value.params) {
@@ -946,11 +981,7 @@ function declaredMemberName(member) {
946
981
  function localInterfaceSurfaces(program) {
947
982
  const interfaces = /* @__PURE__ */ new Map();
948
983
  const parents = /* @__PURE__ */ new Map();
949
- const functionAliases = /* @__PURE__ */ new Set();
950
- for (const statement of program.body) {
951
- const declaration = statement.type === import_utils3.AST_NODE_TYPES.ExportNamedDeclaration ? statement.declaration : statement;
952
- if (declaration?.type === import_utils3.AST_NODE_TYPES.TSTypeAliasDeclaration && (declaration.typeAnnotation.type === import_utils3.AST_NODE_TYPES.TSFunctionType || declaration.typeAnnotation.type === import_utils3.AST_NODE_TYPES.TSConstructorType)) functionAliases.add(declaration.id.name);
953
- }
984
+ const functionAliases = localFunctionAliases(program);
954
985
  function collectInterfaceSurface(statement) {
955
986
  const declaration = statement.type === import_utils3.AST_NODE_TYPES.ExportNamedDeclaration ? statement.declaration : statement;
956
987
  if (declaration?.type === import_utils3.AST_NODE_TYPES.TSTypeAliasDeclaration) {
@@ -962,6 +993,10 @@ function localInterfaceSurfaces(program) {
962
993
  inherited.push(part.typeName.name);
963
994
  continue;
964
995
  }
996
+ if (part.type === import_utils3.AST_NODE_TYPES.TSTypeReference) {
997
+ inherited.push("*");
998
+ continue;
999
+ }
965
1000
  if (part.type !== import_utils3.AST_NODE_TYPES.TSTypeLiteral) continue;
966
1001
  addCallableMembers(part.members, callables2);
967
1002
  }
@@ -1125,30 +1160,18 @@ function createServiceOperationResolver(program) {
1125
1160
  if (retained.size === 0) return [];
1126
1161
  const callers = /* @__PURE__ */ new Map();
1127
1162
  const active = /* @__PURE__ */ new Set();
1128
- for (const member of node.body.body) {
1129
- if (member.type !== import_utils3.AST_NODE_TYPES.MethodDefinition || member.kind !== "method" || member.value.body === null) continue;
1130
- const name = declaredMemberName(member);
1131
- if (name === null) continue;
1132
- const visit = (current) => {
1133
- if (current.type === import_utils3.AST_NODE_TYPES.FunctionDeclaration || current.type === import_utils3.AST_NODE_TYPES.FunctionExpression || current.type === import_utils3.AST_NODE_TYPES.ClassDeclaration || current.type === import_utils3.AST_NODE_TYPES.ClassExpression) return;
1134
- if (current.type === import_utils3.AST_NODE_TYPES.IfStatement && current.test.type === import_utils3.AST_NODE_TYPES.Literal && typeof current.test.value === "boolean") {
1135
- visit(current.test.value ? current.consequent : current.alternate ?? current.test);
1136
- return;
1137
- }
1138
- if (current.type === import_utils3.AST_NODE_TYPES.CallExpression) {
1139
- if (retained.has(invokedInstanceField(current) ?? "")) active.add(name);
1140
- if (current.callee.type === import_utils3.AST_NODE_TYPES.MemberExpression && current.callee.object.type === import_utils3.AST_NODE_TYPES.ThisExpression) {
1141
- const callee = staticMemberName(current.callee);
1142
- if (callee !== null) {
1143
- const linked = callers.get(callee) ?? /* @__PURE__ */ new Set();
1144
- linked.add(name);
1145
- callers.set(callee, linked);
1146
- }
1163
+ for (const [name, body2] of serviceOperationBodies(node)) {
1164
+ forEachServiceCall(body2, (call) => {
1165
+ if (retained.has(invokedInstanceField(call) ?? "")) active.add(name);
1166
+ if (call.callee.type === import_utils3.AST_NODE_TYPES.MemberExpression && call.callee.object.type === import_utils3.AST_NODE_TYPES.ThisExpression) {
1167
+ const callee = staticMemberName(call.callee);
1168
+ if (callee !== null) {
1169
+ const linked = callers.get(callee) ?? /* @__PURE__ */ new Set();
1170
+ linked.add(name);
1171
+ callers.set(callee, linked);
1147
1172
  }
1148
1173
  }
1149
- forEachOwnAstChild(current, visit);
1150
- };
1151
- for (const statement of member.value.body.body) visit(statement);
1174
+ });
1152
1175
  }
1153
1176
  const pending = [...active];
1154
1177
  while (pending.length > 0) {
@@ -1163,38 +1186,127 @@ function createServiceOperationResolver(program) {
1163
1186
  return publicMethodNames(node.body, declared().functionAliases).filter((name) => active.has(name));
1164
1187
  };
1165
1188
  }
1189
+ function serviceOperationBodies(node) {
1190
+ const bodies = /* @__PURE__ */ new Map();
1191
+ for (const member of node.body.body) {
1192
+ if (member.type !== import_utils3.AST_NODE_TYPES.MethodDefinition && member.type !== import_utils3.AST_NODE_TYPES.PropertyDefinition) continue;
1193
+ if (member.static || member.decorators.length > 0) continue;
1194
+ if (member.type === import_utils3.AST_NODE_TYPES.MethodDefinition && member.kind !== "method") continue;
1195
+ if (member.value?.type !== import_utils3.AST_NODE_TYPES.FunctionExpression && member.value?.type !== import_utils3.AST_NODE_TYPES.ArrowFunctionExpression) continue;
1196
+ const name = declaredMemberName(member);
1197
+ if (name !== null && member.value.body !== null) bodies.set(name, member.value.body);
1198
+ }
1199
+ return bodies;
1200
+ }
1201
+ function forEachServiceCall(node, onCall) {
1202
+ if (node.type === import_utils3.AST_NODE_TYPES.FunctionDeclaration || node.type === import_utils3.AST_NODE_TYPES.FunctionExpression || node.type === import_utils3.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils3.AST_NODE_TYPES.ClassDeclaration || node.type === import_utils3.AST_NODE_TYPES.ClassExpression) return;
1203
+ if (node.type === import_utils3.AST_NODE_TYPES.BlockStatement) {
1204
+ for (const statement of node.body) {
1205
+ forEachServiceCall(statement, onCall);
1206
+ if (terminatesOperation(statement)) break;
1207
+ }
1208
+ return;
1209
+ }
1210
+ const branch = literalExecutionBranch(node);
1211
+ if (branch !== null) {
1212
+ forEachServiceCall(branch, onCall);
1213
+ return;
1214
+ }
1215
+ if (node.type === import_utils3.AST_NODE_TYPES.CallExpression) onCall(node);
1216
+ forEachOwnAstChild(node, (child) => {
1217
+ forEachServiceCall(child, onCall);
1218
+ });
1219
+ }
1220
+ function literalExecutionBranch(node) {
1221
+ if (node.type === import_utils3.AST_NODE_TYPES.WhileStatement && node.test.type === import_utils3.AST_NODE_TYPES.Literal && node.test.value === false) return node.test;
1222
+ if ((node.type === import_utils3.AST_NODE_TYPES.IfStatement || node.type === import_utils3.AST_NODE_TYPES.ConditionalExpression) && node.test.type === import_utils3.AST_NODE_TYPES.Literal && typeof node.test.value === "boolean") {
1223
+ return node.test.value ? node.consequent : node.alternate ?? node.test;
1224
+ }
1225
+ if (node.type !== import_utils3.AST_NODE_TYPES.LogicalExpression || node.left.type !== import_utils3.AST_NODE_TYPES.Literal || typeof node.left.value !== "boolean") return null;
1226
+ if (node.operator === "&&") return node.left.value ? node.right : node.left;
1227
+ if (node.operator === "||") return node.left.value ? node.left : node.right;
1228
+ return node.left;
1229
+ }
1230
+ function terminatesOperation(node) {
1231
+ if (node.type === import_utils3.AST_NODE_TYPES.ReturnStatement || node.type === import_utils3.AST_NODE_TYPES.ThrowStatement) return true;
1232
+ if (node.type === import_utils3.AST_NODE_TYPES.BlockStatement) return node.body.some(terminatesOperation);
1233
+ if (node.type !== import_utils3.AST_NODE_TYPES.IfStatement) return false;
1234
+ const branch = literalExecutionBranch(node);
1235
+ if (branch !== null) return terminatesOperation(branch);
1236
+ return node.alternate !== null && terminatesOperation(node.consequent) && terminatesOperation(node.alternate);
1237
+ }
1166
1238
  function hasDeclaredServiceContract(node, operations, program) {
1167
- const classes = localClassAbstractness(program);
1168
1239
  const interfaces = localInterfaceSurfaces(program);
1169
- if (node.implements.length > 0) return implementedSurfaceCovers(node, operations, classes, interfaces);
1170
- if (node.superClass === null) return false;
1171
- if (node.superClass.type !== import_utils3.AST_NODE_TYPES.Identifier) return null;
1172
- const baseName = node.superClass.name;
1173
- const isAbstract = classes.get(baseName);
1174
- if (isAbstract === void 0) return null;
1175
- if (!isAbstract) return false;
1176
- const base = program.body.flatMap((statement) => {
1177
- const declaration = statement.type === import_utils3.AST_NODE_TYPES.ExportNamedDeclaration ? statement.declaration : statement;
1178
- return declaration?.type === import_utils3.AST_NODE_TYPES.ClassDeclaration && declaration.id?.name === baseName ? [declaration] : [];
1179
- })[0];
1180
- if (base === void 0) return null;
1181
- const abstractOperations = new Set(base.body.body.flatMap(
1182
- (member) => member.type === import_utils3.AST_NODE_TYPES.TSAbstractMethodDefinition && member.accessibility !== "private" && member.accessibility !== "protected" && !member.static ? [declaredMemberName(member)] : []
1183
- ));
1184
- return operations.every((name) => abstractOperations.has(name) || publicMethodNames(base.body, /* @__PURE__ */ new Set()).includes(name));
1240
+ const classes = localClassDeclarations(program);
1241
+ const functionAliases = fileTypeIndex(program).functionAliases;
1242
+ const covered2 = /* @__PURE__ */ new Set();
1243
+ const pending = [node];
1244
+ const seen = /* @__PURE__ */ new Set();
1245
+ function addImplementedContracts(current) {
1246
+ for (const implementation of current.implements) {
1247
+ if (implementation.expression.type !== import_utils3.AST_NODE_TYPES.Identifier) return false;
1248
+ const name = implementation.expression.name;
1249
+ const surface = interfaces.get(name);
1250
+ const base = classes.get(name);
1251
+ if (surface === void 0 && base === void 0) return false;
1252
+ if (surface?.has("*")) return false;
1253
+ for (const method of surface ?? []) covered2.add(method);
1254
+ if (base?.abstract) pending.push(base);
1255
+ }
1256
+ return true;
1257
+ }
1258
+ while (pending.length > 0) {
1259
+ const current = pending.pop();
1260
+ if (current === void 0 || seen.has(current)) continue;
1261
+ seen.add(current);
1262
+ if (current.decorators.length > 0) return null;
1263
+ if (current.abstract) {
1264
+ for (const method of abstractContractOperations(current, functionAliases)) covered2.add(method);
1265
+ }
1266
+ if (!addImplementedContracts(current)) return null;
1267
+ if (current.superClass === null) continue;
1268
+ if (current.superClass.type !== import_utils3.AST_NODE_TYPES.Identifier) return null;
1269
+ const base = classes.get(current.superClass.name);
1270
+ if (base === void 0) return null;
1271
+ pending.push(base);
1272
+ }
1273
+ return operations.every((name) => covered2.has(name));
1274
+ }
1275
+ function localClassDeclarations(program) {
1276
+ const classes = /* @__PURE__ */ new Map();
1277
+ for (const statement of program.body) {
1278
+ const declaration = statement.type === import_utils3.AST_NODE_TYPES.ExportNamedDeclaration || statement.type === import_utils3.AST_NODE_TYPES.ExportDefaultDeclaration ? statement.declaration : statement;
1279
+ if (declaration?.type === import_utils3.AST_NODE_TYPES.ClassDeclaration && declaration.id !== null) classes.set(declaration.id.name, declaration);
1280
+ }
1281
+ return classes;
1282
+ }
1283
+ function abstractContractOperations(node, functionAliases) {
1284
+ const methods = new Set(publicMethodNames(node.body, functionAliases));
1285
+ for (const member of node.body.body) {
1286
+ if (member.type !== import_utils3.AST_NODE_TYPES.TSAbstractMethodDefinition && member.type !== import_utils3.AST_NODE_TYPES.TSAbstractPropertyDefinition) continue;
1287
+ if (member.static || member.accessibility === "private" || member.accessibility === "protected") continue;
1288
+ if (member.type === import_utils3.AST_NODE_TYPES.TSAbstractPropertyDefinition) {
1289
+ const annotation = member.typeAnnotation?.typeAnnotation;
1290
+ if (annotation?.type !== import_utils3.AST_NODE_TYPES.TSFunctionType && !(annotation?.type === import_utils3.AST_NODE_TYPES.TSTypeReference && annotation.typeName.type === import_utils3.AST_NODE_TYPES.Identifier && functionAliases.has(annotation.typeName.name))) continue;
1291
+ }
1292
+ const name = declaredMemberName(member);
1293
+ if (name !== null) methods.add(name);
1294
+ }
1295
+ return methods;
1185
1296
  }
1186
1297
 
1187
1298
  // src/rules/require-explicit-service-contract.ts
1188
1299
  var REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION = {
1189
1300
  defaultLevel: "warning",
1190
1301
  summary: "Require explicit contracts for classes that invoke retained collaborators.",
1191
- rationale: "A one-operation service can orchestrate a collaborator without declaring the contract that its callers and fakes must implement.",
1192
- remediation: "Export a focused interface or abstract class and explicitly implement or extend it.",
1302
+ rationale: "A one-operation service can orchestrate a collaborator without declaring the contract that callers consume and implementations and fakes satisfy.",
1303
+ remediation: "Explicitly implement a focused interface or extend an abstract class covering the service operations. Export the contract only when its consumers need it.",
1193
1304
  category: "architecture",
1194
1305
  autofix: "none",
1195
1306
  limitations: [
1196
1307
  "The rule checks service, store, and provider class declarations whose constructor stores a typed behavioral collaborator and whose public method reaches that collaborator.",
1197
- "Imported or dynamic base classes with unavailable declarations are unresolved and do not produce a finding.",
1308
+ "Existing local interfaces, type aliases, and inherited contracts can cover the operations; the compiler owns signature compatibility.",
1309
+ "Imported or dynamic base classes with unavailable declarations and decorated classes or operations are unresolved and do not produce a finding.",
1198
1310
  "Generated files and JavaScript without implements syntax are excluded."
1199
1311
  ],
1200
1312
  examples: [
@@ -1216,7 +1328,7 @@ var REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION = {
1216
1328
  outcome: "no-match",
1217
1329
  files: [{
1218
1330
  path: "src/service.ts",
1219
- source: "export interface Runner { run(): void } class OrchestratorService implements Runner { constructor(private readonly worker: Worker) {} run(): void { this.worker.run(); } }"
1331
+ source: "interface Runner { run(): void } class OrchestratorService implements Runner { constructor(private readonly worker: Worker) {} run(): void { this.worker.run(); } }"
1220
1332
  }],
1221
1333
  focusPath: "src/service.ts",
1222
1334
  expectedCount: 0,
@@ -1232,7 +1344,7 @@ var require_explicit_service_contract_default = createRule({
1232
1344
  docs: { description: REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION.summary },
1233
1345
  schema: [],
1234
1346
  messages: {
1235
- requireExplicitServiceContract: "`{{name}}` invokes a retained collaborator from {{operations}} without explicitly implementing a public contract for those operations."
1347
+ requireExplicitServiceContract: "`{{name}}` invokes a retained collaborator from {{operations}} without a declared interface or abstract-class contract covering those operations. Implement a focused contract; it can remain local."
1236
1348
  }
1237
1349
  },
1238
1350
  defaultOptions: [],
@@ -1243,7 +1355,7 @@ var require_explicit_service_contract_default = createRule({
1243
1355
  const isExportedServiceClass = createExportedServiceClassResolver(program);
1244
1356
  return {
1245
1357
  ClassDeclaration(node) {
1246
- if (node.id === null || node.abstract || !isServiceRole(node.id.name, context.filename)) return;
1358
+ if (node.id === null || node.abstract || node.decorators.length > 0 || !isServiceRole(node.id.name, context.filename)) return;
1247
1359
  if (isExportedServiceClass(node) && !isTestFile(context.filename) && !isStoryFile(context.filename) && !isScriptFile(context.filename)) return;
1248
1360
  const operations = operationsFor(node);
1249
1361
  if (operations.length === 0 || hasDeclaredServiceContract(node, operations, program) !== false) return;
@@ -7058,7 +7170,7 @@ var RESHAPING_METHODS = /* @__PURE__ */ new Set([
7058
7170
  function importedName2(specifier) {
7059
7171
  return specifier.imported.type === import_utils40.AST_NODE_TYPES.Identifier ? specifier.imported.name : typeof specifier.imported.value === "string" ? specifier.imported.value : null;
7060
7172
  }
7061
- function memberName(node) {
7173
+ function memberName2(node) {
7062
7174
  if (!node.computed && node.property.type === import_utils40.AST_NODE_TYPES.Identifier) {
7063
7175
  return node.property.name;
7064
7176
  }
@@ -7143,7 +7255,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
7143
7255
  if (callee.type !== import_utils40.AST_NODE_TYPES.MemberExpression || callee.object.type !== import_utils40.AST_NODE_TYPES.CallExpression) {
7144
7256
  return null;
7145
7257
  }
7146
- const method = memberName(callee);
7258
+ const method = memberName2(callee);
7147
7259
  if (method === null) return null;
7148
7260
  calls.push({ method, node: current });
7149
7261
  current = callee.object;
@@ -7157,7 +7269,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
7157
7269
  if (callee.type !== import_utils40.AST_NODE_TYPES.MemberExpression || callee.object.type !== import_utils40.AST_NODE_TYPES.Identifier || !namespaces.has(callee.object.name) || !resolvesToTrackedImport(callee.object)) {
7158
7270
  return null;
7159
7271
  }
7160
- const name = memberName(callee);
7272
+ const name = memberName2(callee);
7161
7273
  return name !== null && KINDS.has(name) ? name : null;
7162
7274
  }
7163
7275
  function isInsideReshapingCall(node) {
@@ -7166,7 +7278,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
7166
7278
  while (parent !== void 0 && parent.type !== import_utils40.AST_NODE_TYPES.Program) {
7167
7279
  if (parent.type === import_utils40.AST_NODE_TYPES.CallExpression) {
7168
7280
  const callee = parent.callee;
7169
- if (callee.type === import_utils40.AST_NODE_TYPES.MemberExpression && RESHAPING_METHODS.has(memberName(callee) ?? "") && (callee.object.type === import_utils40.AST_NODE_TYPES.CallExpression || callee.object.type === import_utils40.AST_NODE_TYPES.Identifier && namespaces.has(callee.object.name) && resolvesToTrackedImport(callee.object))) {
7281
+ if (callee.type === import_utils40.AST_NODE_TYPES.MemberExpression && RESHAPING_METHODS.has(memberName2(callee) ?? "") && (callee.object.type === import_utils40.AST_NODE_TYPES.CallExpression || callee.object.type === import_utils40.AST_NODE_TYPES.Identifier && namespaces.has(callee.object.name) && resolvesToTrackedImport(callee.object))) {
7170
7282
  return true;
7171
7283
  }
7172
7284
  if (callee.type === import_utils40.AST_NODE_TYPES.Identifier && preprocessors.has(callee.name) && resolvesToTrackedImport(callee)) {
@@ -7428,7 +7540,7 @@ var NO_REDUNDANT_OPTIONAL_ARRAY_DEFAULT_DOCUMENTATION = {
7428
7540
  function importedName3(specifier) {
7429
7541
  return specifier.imported.type === import_utils42.AST_NODE_TYPES.Identifier ? specifier.imported.name : typeof specifier.imported.value === "string" ? specifier.imported.value : null;
7430
7542
  }
7431
- function memberName2(node) {
7543
+ function memberName3(node) {
7432
7544
  if (!node.computed && node.property.type === import_utils42.AST_NODE_TYPES.Identifier) {
7433
7545
  return node.property.name;
7434
7546
  }
@@ -7480,7 +7592,7 @@ var no_redundant_optional_array_default_default = createRule({
7480
7592
  return arrayConstructors.has(callee.name) && resolvesToTrackedImport(callee);
7481
7593
  }
7482
7594
  if (callee.type !== import_utils42.AST_NODE_TYPES.MemberExpression) return false;
7483
- const method = memberName2(callee);
7595
+ const method = memberName3(callee);
7484
7596
  if (method === "array" && (callee.object.type === import_utils42.AST_NODE_TYPES.Identifier ? namespaces.has(callee.object.name) && resolvesToTrackedImport(callee.object) : isZodSchemaExpression(callee.object))) {
7485
7597
  return true;
7486
7598
  }
@@ -7521,12 +7633,12 @@ var no_redundant_optional_array_default_default = createRule({
7521
7633
  },
7522
7634
  CallExpression(node) {
7523
7635
  const defaultCallee = node.callee;
7524
- if (defaultCallee.type !== import_utils42.AST_NODE_TYPES.MemberExpression || memberName2(defaultCallee) !== "default" || defaultCallee.object.type !== import_utils42.AST_NODE_TYPES.CallExpression) {
7636
+ if (defaultCallee.type !== import_utils42.AST_NODE_TYPES.MemberExpression || memberName3(defaultCallee) !== "default" || defaultCallee.object.type !== import_utils42.AST_NODE_TYPES.CallExpression) {
7525
7637
  return;
7526
7638
  }
7527
7639
  const optionalCall = defaultCallee.object;
7528
7640
  const optionalCallee = optionalCall.callee;
7529
- if (optionalCallee.type !== import_utils42.AST_NODE_TYPES.MemberExpression || memberName2(optionalCallee) !== "optional" || optionalCall.arguments.length !== 0 || !isArraySchemaExpression(optionalCallee.object) || sourceCode.getCommentsInside(optionalCall).length > 0) {
7641
+ if (optionalCallee.type !== import_utils42.AST_NODE_TYPES.MemberExpression || memberName3(optionalCallee) !== "optional" || optionalCall.arguments.length !== 0 || !isArraySchemaExpression(optionalCallee.object) || sourceCode.getCommentsInside(optionalCall).length > 0) {
7530
7642
  return;
7531
7643
  }
7532
7644
  context.report({
@@ -7545,12 +7657,12 @@ var ts8 = __toESM(require("typescript"), 1);
7545
7657
 
7546
7658
  // src/rules/_class-private.ts
7547
7659
  var import_utils43 = require("@typescript-eslint/utils");
7548
- function memberName3(member) {
7660
+ function memberName4(member) {
7549
7661
  return !member.computed && member.key.type === import_utils43.AST_NODE_TYPES.Identifier ? member.key.name : null;
7550
7662
  }
7551
7663
  function convertibleMemberName(member) {
7552
7664
  if (member.type !== import_utils43.AST_NODE_TYPES.MethodDefinition && member.type !== import_utils43.AST_NODE_TYPES.PropertyDefinition && member.type !== import_utils43.AST_NODE_TYPES.AccessorProperty) return null;
7553
- return memberName3(member);
7665
+ return memberName4(member);
7554
7666
  }
7555
7667
 
7556
7668
  // src/rules/interface-contract-members-private.ts
@@ -8152,7 +8264,7 @@ var LOWER_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
8152
8264
  function propertyName2(node) {
8153
8265
  return node.type === import_utils48.AST_NODE_TYPES.Identifier ? node.name : null;
8154
8266
  }
8155
- function memberName4(node) {
8267
+ function memberName5(node) {
8156
8268
  let current = node;
8157
8269
  while (current.type === import_utils48.AST_NODE_TYPES.TSAsExpression || current.type === import_utils48.AST_NODE_TYPES.TSNonNullExpression || current.type === import_utils48.AST_NODE_TYPES.TSTypeAssertion) {
8158
8270
  current = current.expression;
@@ -8216,7 +8328,7 @@ var no_bespoke_api_case_conversion_default = createRule({
8216
8328
  Property(node) {
8217
8329
  if (node.computed || node.method || node.shorthand) return;
8218
8330
  const key = propertyName2(node.key);
8219
- const value = memberName4(node.value);
8331
+ const value = memberName5(node.value);
8220
8332
  if (key === null || value === null || !isDirectCaseTranslation(key, value)) return;
8221
8333
  if (!hasApiReceiver(node.value)) return;
8222
8334
  const wireName = SNAKE_CASE_RE2.test(key) ? key : value;
@@ -8989,9 +9101,9 @@ var no_positional_tuple_return_default = createRule({
8989
9101
  TSMethodSignature(node) {
8990
9102
  const owner = owningInterface(node);
8991
9103
  const alias = owningTypeAlias(node);
8992
- const memberName8 = staticMemberName4(node.key);
8993
- if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) || node.returnType === void 0 || memberName8 === null) return;
8994
- report2(node.returnType.typeAnnotation, `${owner?.id.name ?? alias?.id.name ?? "type"}.${memberName8}`);
9104
+ const memberName9 = staticMemberName4(node.key);
9105
+ if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) || node.returnType === void 0 || memberName9 === null) return;
9106
+ report2(node.returnType.typeAnnotation, `${owner?.id.name ?? alias?.id.name ?? "type"}.${memberName9}`);
8995
9107
  },
8996
9108
  TSTypeAliasDeclaration(node) {
8997
9109
  if (!typeExports.has(node.id.name)) return;
@@ -9002,11 +9114,11 @@ var no_positional_tuple_return_default = createRule({
9002
9114
  const owner = owningInterface(node);
9003
9115
  const alias = owningTypeAlias(node);
9004
9116
  const annotation = node.typeAnnotation?.typeAnnotation;
9005
- const memberName8 = staticMemberName4(node.key);
9006
- if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) || memberName8 === null || annotation === void 0) return;
9117
+ const memberName9 = staticMemberName4(node.key);
9118
+ if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) || memberName9 === null || annotation === void 0) return;
9007
9119
  const returnType = callableReturnType(annotation, aliases);
9008
9120
  if (returnType !== null) {
9009
- report2(returnType, `${owner?.id.name ?? alias?.id.name ?? "type"}.${memberName8}`);
9121
+ report2(returnType, `${owner?.id.name ?? alias?.id.name ?? "type"}.${memberName9}`);
9010
9122
  }
9011
9123
  },
9012
9124
  PropertyDefinition(node) {
@@ -16974,7 +17086,7 @@ var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
16974
17086
  { id: "mutable-one-shot-chain", title: "Avoid mutable state for one value", outcome: "match", files: [{ path: "case.ts", source: "import { createHash } from 'node:crypto'; export const digest = createHash('sha256').update('value').digest('hex');" }], focusPath: "case.ts", expectedCount: 1, public: true }
16975
17087
  ]
16976
17088
  };
16977
- function memberName5(node) {
17089
+ function memberName6(node) {
16978
17090
  if (!node.computed && node.property.type === import_utils99.AST_NODE_TYPES.Identifier) return node.property.name;
16979
17091
  if (node.computed && node.property.type === import_utils99.AST_NODE_TYPES.Literal && typeof node.property.value === "string") {
16980
17092
  return node.property.value;
@@ -16990,7 +17102,7 @@ function isCryptoLoader(node, resolve2) {
16990
17102
  if (node.callee.type === import_utils99.AST_NODE_TYPES.Identifier) {
16991
17103
  return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee, resolve2);
16992
17104
  }
16993
- return node.callee.type === import_utils99.AST_NODE_TYPES.MemberExpression && node.callee.object.type === import_utils99.AST_NODE_TYPES.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object, resolve2) && memberName5(node.callee) === "getBuiltinModule";
17105
+ return node.callee.type === import_utils99.AST_NODE_TYPES.MemberExpression && node.callee.object.type === import_utils99.AST_NODE_TYPES.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object, resolve2) && memberName6(node.callee) === "getBuiltinModule";
16994
17106
  }
16995
17107
  function isCryptoSpecifier(node) {
16996
17108
  return node.type === import_utils99.AST_NODE_TYPES.Literal && (node.value === "crypto" || node.value === "node:crypto");
@@ -17072,14 +17184,14 @@ function importedName6(node) {
17072
17184
  return node.type === import_utils99.AST_NODE_TYPES.Identifier ? node.name : node.value;
17073
17185
  }
17074
17186
  function isMemberCall(node, name) {
17075
- return node.callee.type === import_utils99.AST_NODE_TYPES.MemberExpression && memberName5(node.callee) === name;
17187
+ return node.callee.type === import_utils99.AST_NODE_TYPES.MemberExpression && memberName6(node.callee) === name;
17076
17188
  }
17077
17189
  function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
17078
17190
  if (node.callee.type === import_utils99.AST_NODE_TYPES.Identifier) {
17079
17191
  const variable2 = resolve2(node.callee);
17080
17192
  return variable2 !== null && directBindings.has(variable2);
17081
17193
  }
17082
- if (node.callee.type !== import_utils99.AST_NODE_TYPES.MemberExpression || memberName5(node.callee) !== "createHash") {
17194
+ if (node.callee.type !== import_utils99.AST_NODE_TYPES.MemberExpression || memberName6(node.callee) !== "createHash") {
17083
17195
  return false;
17084
17196
  }
17085
17197
  if (isCryptoLoader(node.callee.object, resolve2)) return true;
@@ -17107,7 +17219,7 @@ var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
17107
17219
  { id: "sync-read", title: "Do not block on a synchronous read", outcome: "match", files: [{ path: "src/store.ts", source: "import { readFileSync } from 'node:fs'; export function load(path: string) { return readFileSync(path, 'utf8'); }" }], focusPath: "src/store.ts", expectedCount: 1, public: true }
17108
17220
  ]
17109
17221
  };
17110
- function memberName6(node) {
17222
+ function memberName7(node) {
17111
17223
  if (!node.computed && node.property.type === import_utils100.AST_NODE_TYPES.Identifier) return node.property.name;
17112
17224
  if (node.computed && node.property.type === import_utils100.AST_NODE_TYPES.Literal && typeof node.property.value === "string") {
17113
17225
  return node.property.value;
@@ -17124,7 +17236,7 @@ function isFsLoader(node, isGlobal) {
17124
17236
  const [argument] = expression.arguments;
17125
17237
  if (argument === void 0 || argument.type === import_utils100.AST_NODE_TYPES.SpreadElement || !isFsSpecifier(argument)) return false;
17126
17238
  if (expression.callee.type === import_utils100.AST_NODE_TYPES.Identifier) return expression.callee.name === "require" && isGlobal(expression.callee);
17127
- return expression.callee.type === import_utils100.AST_NODE_TYPES.MemberExpression && expression.callee.object.type === import_utils100.AST_NODE_TYPES.Identifier && expression.callee.object.name === "process" && isGlobal(expression.callee.object) && memberName6(expression.callee) === "getBuiltinModule";
17239
+ return expression.callee.type === import_utils100.AST_NODE_TYPES.MemberExpression && expression.callee.object.type === import_utils100.AST_NODE_TYPES.Identifier && expression.callee.object.name === "process" && isGlobal(expression.callee.object) && memberName7(expression.callee) === "getBuiltinModule";
17128
17240
  }
17129
17241
  function isFsSpecifier(node) {
17130
17242
  return node.type === import_utils100.AST_NODE_TYPES.Literal && (node.value === "node:fs" || node.value === "fs");
@@ -17205,7 +17317,7 @@ var prefer_node_fs_promises_default = createRule({
17205
17317
  }
17206
17318
  },
17207
17319
  MemberExpression(node) {
17208
- const name = memberName6(node);
17320
+ const name = memberName7(node);
17209
17321
  if (name?.endsWith("Sync") !== true) return;
17210
17322
  const object = unwrapAwait2(node.object);
17211
17323
  if (object.type === import_utils100.AST_NODE_TYPES.Identifier && isNamespace(object) || isFsLoader(object, isGlobal)) {
@@ -21390,7 +21502,7 @@ var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
21390
21502
  }
21391
21503
  ]
21392
21504
  };
21393
- function memberName7(node) {
21505
+ function memberName8(node) {
21394
21506
  if (!node.computed && node.property.type === import_utils116.AST_NODE_TYPES.Identifier)
21395
21507
  return node.property.name;
21396
21508
  if (node.computed && node.property.type === import_utils116.AST_NODE_TYPES.Literal && typeof node.property.value === "string")
@@ -21408,7 +21520,7 @@ function databaseReceiver(node) {
21408
21520
  if (node.type === import_utils116.AST_NODE_TYPES.Identifier)
21409
21521
  return DATABASE_NAMES.test(node.name);
21410
21522
  if (node.type !== import_utils116.AST_NODE_TYPES.MemberExpression) return false;
21411
- const name = memberName7(node);
21523
+ const name = memberName8(node);
21412
21524
  return name === "DB" || name !== null && DATABASE_NAMES.test(name);
21413
21525
  }
21414
21526
  function databaseRootMember(node) {
@@ -21429,7 +21541,7 @@ function databaseRootMember(node) {
21429
21541
  }
21430
21542
  if (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
21431
21543
  if (current.object.type === import_utils116.AST_NODE_TYPES.ThisExpression)
21432
- return memberName7(current);
21544
+ return memberName8(current);
21433
21545
  current = current.object;
21434
21546
  continue;
21435
21547
  }
@@ -21453,7 +21565,7 @@ function expressionHasDatabaseMarker(node) {
21453
21565
  continue;
21454
21566
  }
21455
21567
  if (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
21456
- const name = memberName7(current);
21568
+ const name = memberName8(current);
21457
21569
  if (name === "DB" || name !== null && DATABASE_NAMES.test(name))
21458
21570
  return true;
21459
21571
  current = current.object;
@@ -21475,7 +21587,7 @@ function expressionCallsBuilder(node) {
21475
21587
  }
21476
21588
  if (current.type === import_utils116.AST_NODE_TYPES.CallExpression) {
21477
21589
  if (current.callee.type !== import_utils116.AST_NODE_TYPES.MemberExpression) return false;
21478
- const name = memberName7(current.callee);
21590
+ const name = memberName8(current.callee);
21479
21591
  if (name !== null && BUILDER_METHODS.has(name)) return true;
21480
21592
  current = current.callee.object;
21481
21593
  continue;
@@ -21526,7 +21638,7 @@ function thisRootMember(node) {
21526
21638
  let current = node;
21527
21639
  let root = null;
21528
21640
  while (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
21529
- const name = memberName7(current);
21641
+ const name = memberName8(current);
21530
21642
  if (name === null) return null;
21531
21643
  root = name;
21532
21644
  current = current.object;
@@ -21580,7 +21692,7 @@ var require_sql_access_class_default = createRule({
21580
21692
  CallExpression(node) {
21581
21693
  if (node.callee.type !== import_utils116.AST_NODE_TYPES.MemberExpression)
21582
21694
  return;
21583
- const method = memberName7(node.callee);
21695
+ const method = memberName8(node.callee);
21584
21696
  if (knownNonDatabase(node.callee.object)) return;
21585
21697
  if (method === null || !isDatabaseOperation(method, node.callee.object))
21586
21698
  return;
@@ -23210,10 +23322,11 @@ var import_node_fs2 = require("fs");
23210
23322
  var import_node_path2 = require("path");
23211
23323
  var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]);
23212
23324
  var NEXT_COMPONENT = /^(?:src\/)?app\/(?:.*\/)?(?:page|layout|loading|not-found|template|error|global-error|default|forbidden|unauthorized)\.[jt]sx?$/u;
23325
+ var NEXT_METADATA = /^(?:src\/)?app\/(?:.*\/)?(?:(?:opengraph-image|twitter-image|icon|apple-icon)\.[jt]sx?|(?:robots|sitemap|manifest)\.[jt]s)$/u;
23213
23326
  var NEXT_ROUTE = /^(?:src\/)?app\/(?:.*\/)?route\.[jt]s$/u;
23214
23327
  function isFrameworkEntrypoint(filename, exportKey) {
23215
23328
  const normalized = filename.replaceAll("\\", "/");
23216
- if (!/(?:^|\/)(?:app|pages)\/|(?:^|\/)(?:middleware|proxy)\.[jt]s$/u.test(normalized)) return false;
23329
+ if (!/(?:^|\/)(?:app|pages)\/|(?:^|\/)(?:middleware|proxy|instrumentation)\.[jt]s$/u.test(normalized)) return false;
23217
23330
  const owner = packageOwner(normalized);
23218
23331
  if (owner === null) return false;
23219
23332
  const path = (0, import_node_path2.relative)(owner.root, normalized).replaceAll("\\", "/");
@@ -23222,9 +23335,12 @@ function isFrameworkEntrypoint(filename, exportKey) {
23222
23335
  }
23223
23336
  function isNextEntrypoint(path, exportKey) {
23224
23337
  const privateAppFolder = /^(?:src\/)?app\/(?:.*\/)?_[^/]+\//u.test(path);
23225
- if (!privateAppFolder && NEXT_COMPONENT.test(path) && exportKey === "default") return true;
23338
+ if (!privateAppFolder && (NEXT_COMPONENT.test(path) || NEXT_METADATA.test(path)) && exportKey === "default") return true;
23226
23339
  if (!privateAppFolder && NEXT_ROUTE.test(path) && HTTP_METHODS.has(exportKey)) return true;
23227
23340
  if (/^(?:src\/)?pages\/.*\.[jt]sx?$/u.test(path) && exportKey === "default") return true;
23341
+ if (/^(?:src\/)?instrumentation\.[jt]s$/u.test(path)) {
23342
+ return exportKey === "register" || exportKey === "onRequestError";
23343
+ }
23228
23344
  const middleware = /^(?:src\/)?(middleware|proxy)\.[jt]s$/u.exec(path);
23229
23345
  return middleware !== null && (exportKey === "default" || exportKey === middleware[1]);
23230
23346
  }
@@ -23267,7 +23383,7 @@ var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
23267
23383
  "Framework entrypoints, generic stems covered by no-generic-single-export-module, tests, generated files, anonymous defaults, CommonJS, unresolved re-exports, and emission-dependent exports are excluded.",
23268
23384
  "The rule compares the primary filename stem and preserves a single private underscore prefix and conventional suffixes such as .server or .worker. Supporting types do not exempt a sole runtime export; public runtime aliases count as distinct exports.",
23269
23385
  "Exported destructuring patterns are excluded rather than undercounted as public exports.",
23270
- "Next.js and Astro routing exemptions require the nearest package.json to declare the framework, a conventional entrypoint path, and its expected runtime export. Custom routing roots and pageExtensions are not inferred."
23386
+ "Next.js metadata routes, instrumentation, and other Next.js and Astro entrypoint exemptions require the nearest package.json to declare the framework, a conventional entrypoint path, and its expected runtime export. Custom routing roots and pageExtensions are not inferred."
23271
23387
  ],
23272
23388
  examples: [
23273
23389
  { id: "matching-class", title: "Match a class and module", outcome: "no-match", files: [{ path: "src/artifact-store.ts", source: "export class ArtifactStore {}" }], focusPath: "src/artifact-store.ts", expectedCount: 0, public: true },
@@ -23833,7 +23949,7 @@ var RULES = {
23833
23949
  };
23834
23950
  var meta = {
23835
23951
  name: "@sarj/eslint-plugin",
23836
- version: "16.2.5"
23952
+ version: "16.2.6"
23837
23953
  };
23838
23954
  var APPLICATION_ONLY_RULES = [];
23839
23955
  var LIBRARY_IMPORT_POLICY = ["error", {