@sarj/eslint-plugin 16.2.4 → 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 +330 -189
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +330 -189
- package/package.json +1 -1
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
|
|
353
|
-
rationale: "
|
|
354
|
-
remediation: "
|
|
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
|
-
"
|
|
359
|
-
"
|
|
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
|
|
364
|
-
title: "A
|
|
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: "
|
|
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: "
|
|
373
|
-
title: "The fake
|
|
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: "
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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}}`
|
|
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.
|
|
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
|
|
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(
|
|
515
|
+
const implementation = ownedClass(checker, tsArgument);
|
|
506
516
|
if (implementation === null) continue;
|
|
507
|
-
const relationship =
|
|
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 =
|
|
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
|
-
|
|
723
|
-
|
|
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 =
|
|
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
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
const
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
|
1192
|
-
remediation: "
|
|
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
|
-
"
|
|
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: "
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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 ||
|
|
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 ||
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
8993
|
-
if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) || node.returnType === void 0 ||
|
|
8994
|
-
report2(node.returnType.typeAnnotation, `${owner?.id.name ?? alias?.id.name ?? "type"}.${
|
|
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
|
|
9006
|
-
if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) ||
|
|
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"}.${
|
|
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
|
|
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) &&
|
|
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 &&
|
|
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 ||
|
|
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
|
|
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) &&
|
|
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 =
|
|
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)) {
|
|
@@ -17622,15 +17734,16 @@ var prefer_nullish_filter_predicate_default = createRule({
|
|
|
17622
17734
|
var import_utils103 = require("@typescript-eslint/utils");
|
|
17623
17735
|
var ts11 = __toESM(require("typescript"), 1);
|
|
17624
17736
|
var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
|
|
17625
|
-
summary: "Prefer explicit `await`
|
|
17626
|
-
rationale: "
|
|
17737
|
+
summary: "Prefer explicit `await` for proven Promise `.then` calls; the default scope checks direct async returns.",
|
|
17738
|
+
rationale: "Promise callback chains mix explicit async control flow with nested callbacks, making sequencing and failure handling harder to read.",
|
|
17627
17739
|
remediation: "Consider awaiting the Promise and returning the transformed value with ordinary async statements. Preserve catch boundaries, callback behavior, and observable scheduling when rewriting manually.",
|
|
17628
17740
|
category: "maintainability",
|
|
17629
17741
|
since: "15.6.3",
|
|
17630
17742
|
limitations: [
|
|
17631
|
-
"
|
|
17632
|
-
"The receiver must be proven Promise-like by TypeScript; an earlier chain can still produce that receiver.
|
|
17633
|
-
"
|
|
17743
|
+
"The default async-return scope checks one directly returned `.then` call with an inline callback. The all-promise-calls scope checks direct calls to built-in Promise/PromiseLike.then, including optional and literal-computed calls, without automatic rewrites.",
|
|
17744
|
+
"The receiver must be proven Promise-like by TypeScript; an earlier chain can still produce that receiver. The default async-return scope excludes rejection handlers and named callbacks. This is not the upstream return-await policy and no scheduling equivalence is promised.",
|
|
17745
|
+
"The default async-return scope excludes direct loader callbacks passed to resolved `React.lazy` and `next/dynamic` imports because returning the module Promise is their framework contract.",
|
|
17746
|
+
"All-promise-calls requires type information and excludes unproven/custom thenables, dynamic property names, property reads, destructuring, and extracted method aliases. Catch and finally remain available. Framework callback exclusions apply only to the default async-return scope."
|
|
17634
17747
|
],
|
|
17635
17748
|
examples: [
|
|
17636
17749
|
{
|
|
@@ -17687,15 +17800,28 @@ function promiseThenReceiver(node) {
|
|
|
17687
17800
|
}
|
|
17688
17801
|
return callee.object;
|
|
17689
17802
|
}
|
|
17803
|
+
function directThenReceiver(node) {
|
|
17804
|
+
const callee = node.callee;
|
|
17805
|
+
if (callee.type !== import_utils103.AST_NODE_TYPES.MemberExpression) return null;
|
|
17806
|
+
const property = callee.property;
|
|
17807
|
+
const isThen = callee.computed ? property.type === import_utils103.AST_NODE_TYPES.Literal && property.value === "then" || property.type === import_utils103.AST_NODE_TYPES.TemplateLiteral && property.expressions.length === 0 && property.quasis[0]?.value.cooked === "then" : property.type === import_utils103.AST_NODE_TYPES.Identifier && property.name === "then";
|
|
17808
|
+
return isThen ? callee.object : null;
|
|
17809
|
+
}
|
|
17690
17810
|
function isProvenPromiseLike(node, services) {
|
|
17691
17811
|
const checker = services.program.getTypeChecker();
|
|
17692
17812
|
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
17693
|
-
const receiverType = checker.getTypeAtLocation(tsNode);
|
|
17694
|
-
|
|
17813
|
+
const receiverType = checker.getNonNullableType(checker.getTypeAtLocation(tsNode));
|
|
17814
|
+
const members = receiverType.isUnion() ? receiverType.types : [receiverType];
|
|
17815
|
+
return members.every((member) => isBuiltInPromiseType(member, services));
|
|
17816
|
+
}
|
|
17817
|
+
function isBuiltInPromiseType(type, services) {
|
|
17818
|
+
if ((type.flags & (ts11.TypeFlags.Any | ts11.TypeFlags.Unknown | ts11.TypeFlags.Never)) !== 0) {
|
|
17695
17819
|
return false;
|
|
17696
17820
|
}
|
|
17697
|
-
const thenSymbol =
|
|
17698
|
-
const
|
|
17821
|
+
const thenSymbol = services.program.getTypeChecker().getPropertyOfType(type, "then");
|
|
17822
|
+
const declarations = thenSymbol?.declarations;
|
|
17823
|
+
if (declarations === void 0 || declarations.length === 0) return false;
|
|
17824
|
+
return declarations.every(
|
|
17699
17825
|
(declaration) => {
|
|
17700
17826
|
let owner = declaration.parent;
|
|
17701
17827
|
while (owner !== void 0 && !ts11.isInterfaceDeclaration(owner)) {
|
|
@@ -17703,8 +17829,7 @@ function isProvenPromiseLike(node, services) {
|
|
|
17703
17829
|
}
|
|
17704
17830
|
return owner !== void 0 && (owner.name.text === "Promise" || owner.name.text === "PromiseLike") && services.program.isSourceFileDefaultLibrary(owner.getSourceFile());
|
|
17705
17831
|
}
|
|
17706
|
-
)
|
|
17707
|
-
return hasBuiltInPromiseDeclaration;
|
|
17832
|
+
);
|
|
17708
17833
|
}
|
|
17709
17834
|
var prefer_await_in_async_return_default = createRule({
|
|
17710
17835
|
name: "prefer-await-in-async-return",
|
|
@@ -17712,15 +17837,20 @@ var prefer_await_in_async_return_default = createRule({
|
|
|
17712
17837
|
meta: {
|
|
17713
17838
|
type: "suggestion",
|
|
17714
17839
|
docs: {
|
|
17715
|
-
description: "Prefer explicit `await`
|
|
17840
|
+
description: "Prefer explicit `await` for proven Promise `.then` calls; the default scope checks direct async returns."
|
|
17716
17841
|
},
|
|
17717
|
-
schema: [
|
|
17842
|
+
schema: [{
|
|
17843
|
+
type: "object",
|
|
17844
|
+
properties: { scope: { type: "string", enum: ["async-return", "all-promise-calls"] } },
|
|
17845
|
+
additionalProperties: false
|
|
17846
|
+
}],
|
|
17718
17847
|
messages: {
|
|
17719
|
-
preferAwait: "This async function directly returns a Promise `.then` transform. Await the Promise, then return the transformed value with explicit async control flow."
|
|
17848
|
+
preferAwait: "This async function directly returns a Promise `.then` transform. Await the Promise, then return the transformed value with explicit async control flow.",
|
|
17849
|
+
preferAwaitCall: "This call uses built-in Promise.then(). Use async/await, preserving rejection handling and observable scheduling when rewriting."
|
|
17720
17850
|
}
|
|
17721
17851
|
},
|
|
17722
|
-
defaultOptions: [],
|
|
17723
|
-
create(context) {
|
|
17852
|
+
defaultOptions: [{ scope: "async-return" }],
|
|
17853
|
+
create(context, [options]) {
|
|
17724
17854
|
let services;
|
|
17725
17855
|
try {
|
|
17726
17856
|
services = import_utils103.ESLintUtils.getParserServices(context);
|
|
@@ -17753,6 +17883,13 @@ var prefer_await_in_async_return_default = createRule({
|
|
|
17753
17883
|
}
|
|
17754
17884
|
},
|
|
17755
17885
|
CallExpression(node) {
|
|
17886
|
+
if (options.scope === "all-promise-calls") {
|
|
17887
|
+
const receiver2 = directThenReceiver(node);
|
|
17888
|
+
if (receiver2 !== null && isProvenPromiseLike(receiver2, services)) {
|
|
17889
|
+
context.report({ node, messageId: "preferAwaitCall" });
|
|
17890
|
+
}
|
|
17891
|
+
return;
|
|
17892
|
+
}
|
|
17756
17893
|
const owner = directAsyncReturnOwner(node);
|
|
17757
17894
|
if (owner === null || isFrameworkLoaderCallback(owner)) return;
|
|
17758
17895
|
const receiver = promiseThenReceiver(node);
|
|
@@ -21365,7 +21502,7 @@ var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
|
|
|
21365
21502
|
}
|
|
21366
21503
|
]
|
|
21367
21504
|
};
|
|
21368
|
-
function
|
|
21505
|
+
function memberName8(node) {
|
|
21369
21506
|
if (!node.computed && node.property.type === import_utils116.AST_NODE_TYPES.Identifier)
|
|
21370
21507
|
return node.property.name;
|
|
21371
21508
|
if (node.computed && node.property.type === import_utils116.AST_NODE_TYPES.Literal && typeof node.property.value === "string")
|
|
@@ -21383,7 +21520,7 @@ function databaseReceiver(node) {
|
|
|
21383
21520
|
if (node.type === import_utils116.AST_NODE_TYPES.Identifier)
|
|
21384
21521
|
return DATABASE_NAMES.test(node.name);
|
|
21385
21522
|
if (node.type !== import_utils116.AST_NODE_TYPES.MemberExpression) return false;
|
|
21386
|
-
const name =
|
|
21523
|
+
const name = memberName8(node);
|
|
21387
21524
|
return name === "DB" || name !== null && DATABASE_NAMES.test(name);
|
|
21388
21525
|
}
|
|
21389
21526
|
function databaseRootMember(node) {
|
|
@@ -21404,7 +21541,7 @@ function databaseRootMember(node) {
|
|
|
21404
21541
|
}
|
|
21405
21542
|
if (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
|
|
21406
21543
|
if (current.object.type === import_utils116.AST_NODE_TYPES.ThisExpression)
|
|
21407
|
-
return
|
|
21544
|
+
return memberName8(current);
|
|
21408
21545
|
current = current.object;
|
|
21409
21546
|
continue;
|
|
21410
21547
|
}
|
|
@@ -21428,7 +21565,7 @@ function expressionHasDatabaseMarker(node) {
|
|
|
21428
21565
|
continue;
|
|
21429
21566
|
}
|
|
21430
21567
|
if (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
|
|
21431
|
-
const name =
|
|
21568
|
+
const name = memberName8(current);
|
|
21432
21569
|
if (name === "DB" || name !== null && DATABASE_NAMES.test(name))
|
|
21433
21570
|
return true;
|
|
21434
21571
|
current = current.object;
|
|
@@ -21450,7 +21587,7 @@ function expressionCallsBuilder(node) {
|
|
|
21450
21587
|
}
|
|
21451
21588
|
if (current.type === import_utils116.AST_NODE_TYPES.CallExpression) {
|
|
21452
21589
|
if (current.callee.type !== import_utils116.AST_NODE_TYPES.MemberExpression) return false;
|
|
21453
|
-
const name =
|
|
21590
|
+
const name = memberName8(current.callee);
|
|
21454
21591
|
if (name !== null && BUILDER_METHODS.has(name)) return true;
|
|
21455
21592
|
current = current.callee.object;
|
|
21456
21593
|
continue;
|
|
@@ -21501,7 +21638,7 @@ function thisRootMember(node) {
|
|
|
21501
21638
|
let current = node;
|
|
21502
21639
|
let root = null;
|
|
21503
21640
|
while (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
|
|
21504
|
-
const name =
|
|
21641
|
+
const name = memberName8(current);
|
|
21505
21642
|
if (name === null) return null;
|
|
21506
21643
|
root = name;
|
|
21507
21644
|
current = current.object;
|
|
@@ -21555,7 +21692,7 @@ var require_sql_access_class_default = createRule({
|
|
|
21555
21692
|
CallExpression(node) {
|
|
21556
21693
|
if (node.callee.type !== import_utils116.AST_NODE_TYPES.MemberExpression)
|
|
21557
21694
|
return;
|
|
21558
|
-
const method =
|
|
21695
|
+
const method = memberName8(node.callee);
|
|
21559
21696
|
if (knownNonDatabase(node.callee.object)) return;
|
|
21560
21697
|
if (method === null || !isDatabaseOperation(method, node.callee.object))
|
|
21561
21698
|
return;
|
|
@@ -23185,10 +23322,11 @@ var import_node_fs2 = require("fs");
|
|
|
23185
23322
|
var import_node_path2 = require("path");
|
|
23186
23323
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]);
|
|
23187
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;
|
|
23188
23326
|
var NEXT_ROUTE = /^(?:src\/)?app\/(?:.*\/)?route\.[jt]s$/u;
|
|
23189
23327
|
function isFrameworkEntrypoint(filename, exportKey) {
|
|
23190
23328
|
const normalized = filename.replaceAll("\\", "/");
|
|
23191
|
-
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;
|
|
23192
23330
|
const owner = packageOwner(normalized);
|
|
23193
23331
|
if (owner === null) return false;
|
|
23194
23332
|
const path = (0, import_node_path2.relative)(owner.root, normalized).replaceAll("\\", "/");
|
|
@@ -23197,9 +23335,12 @@ function isFrameworkEntrypoint(filename, exportKey) {
|
|
|
23197
23335
|
}
|
|
23198
23336
|
function isNextEntrypoint(path, exportKey) {
|
|
23199
23337
|
const privateAppFolder = /^(?:src\/)?app\/(?:.*\/)?_[^/]+\//u.test(path);
|
|
23200
|
-
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;
|
|
23201
23339
|
if (!privateAppFolder && NEXT_ROUTE.test(path) && HTTP_METHODS.has(exportKey)) return true;
|
|
23202
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
|
+
}
|
|
23203
23344
|
const middleware = /^(?:src\/)?(middleware|proxy)\.[jt]s$/u.exec(path);
|
|
23204
23345
|
return middleware !== null && (exportKey === "default" || exportKey === middleware[1]);
|
|
23205
23346
|
}
|
|
@@ -23242,7 +23383,7 @@ var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
|
|
|
23242
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.",
|
|
23243
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.",
|
|
23244
23385
|
"Exported destructuring patterns are excluded rather than undercounted as public exports.",
|
|
23245
|
-
"Next.js and Astro
|
|
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."
|
|
23246
23387
|
],
|
|
23247
23388
|
examples: [
|
|
23248
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 },
|
|
@@ -23808,7 +23949,7 @@ var RULES = {
|
|
|
23808
23949
|
};
|
|
23809
23950
|
var meta = {
|
|
23810
23951
|
name: "@sarj/eslint-plugin",
|
|
23811
|
-
version: "16.2.
|
|
23952
|
+
version: "16.2.6"
|
|
23812
23953
|
};
|
|
23813
23954
|
var APPLICATION_ONLY_RULES = [];
|
|
23814
23955
|
var LIBRARY_IMPORT_POLICY = ["error", {
|