@sarj/eslint-plugin 16.2.5 → 16.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +386 -199
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +386 -199
- 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) {
|
|
@@ -14728,7 +14840,8 @@ var PREFER_IMMUTABLE_MODULE_CONSTANT_DOCUMENTATION = {
|
|
|
14728
14840
|
remediation: "Expose literals with `as const` or a readonly type, and expose Set or Map values through ReadonlySet or ReadonlyMap.",
|
|
14729
14841
|
category: "correctness",
|
|
14730
14842
|
limitations: [
|
|
14731
|
-
"Generated, test and JavaScript files are skipped. Private constants with observed direct or alias mutation are excluded; exported mutable collections remain advisory candidates. Reassigned aliases are conservatively followed, not flow-proven."
|
|
14843
|
+
"Generated, test and JavaScript files are skipped. Private constants with observed direct or alias mutation are excluded; exported mutable collections remain advisory candidates. Reassigned aliases are conservatively followed, not flow-proven.",
|
|
14844
|
+
"Local type aliases are resolved before recognizing standard readonly types; imported or otherwise unknown type declarations are not inferred from their names. Readonly types have no runtime enforcement."
|
|
14732
14845
|
],
|
|
14733
14846
|
examples: [
|
|
14734
14847
|
{
|
|
@@ -14748,6 +14861,26 @@ var PREFER_IMMUTABLE_MODULE_CONSTANT_DOCUMENTATION = {
|
|
|
14748
14861
|
focusPath: "src/constants.ts",
|
|
14749
14862
|
expectedCount: 1,
|
|
14750
14863
|
public: true
|
|
14864
|
+
},
|
|
14865
|
+
{
|
|
14866
|
+
id: "shadowed-readonly-alias",
|
|
14867
|
+
scenarioId: "type-binding",
|
|
14868
|
+
title: "A type named Readonly still exposes mutable properties",
|
|
14869
|
+
outcome: "match",
|
|
14870
|
+
files: [{ path: "src/constants.ts", source: "type Readonly = { voice: boolean };\nexport const TOOL_SUPPORT: Readonly = { voice: true };" }],
|
|
14871
|
+
focusPath: "src/constants.ts",
|
|
14872
|
+
expectedCount: 1,
|
|
14873
|
+
public: true
|
|
14874
|
+
},
|
|
14875
|
+
{
|
|
14876
|
+
id: "resolved-readonly-alias",
|
|
14877
|
+
scenarioId: "type-binding",
|
|
14878
|
+
title: "A local alias resolves to readonly properties",
|
|
14879
|
+
outcome: "no-match",
|
|
14880
|
+
files: [{ path: "src/constants.ts", source: "type ToolSupport = Readonly<{ voice: boolean }>;\nexport const TOOL_SUPPORT: ToolSupport = { voice: true };" }],
|
|
14881
|
+
focusPath: "src/constants.ts",
|
|
14882
|
+
expectedCount: 0,
|
|
14883
|
+
public: true
|
|
14751
14884
|
}
|
|
14752
14885
|
]
|
|
14753
14886
|
};
|
|
@@ -14802,39 +14935,84 @@ function collectionKind(node, isUnshadowedGlobal3) {
|
|
|
14802
14935
|
}
|
|
14803
14936
|
return null;
|
|
14804
14937
|
}
|
|
14805
|
-
function declaredReadonlyType(node, kind,
|
|
14938
|
+
function declaredReadonlyType(node, kind, resolveLocalType, isUnshadowedType) {
|
|
14806
14939
|
const annotation = node.id.type === import_utils89.AST_NODE_TYPES.Identifier ? node.id.typeAnnotation : void 0;
|
|
14807
|
-
if (annotation !== void 0 &&
|
|
14940
|
+
if (annotation !== void 0 && readonlyTypeState(annotation.typeAnnotation, kind, resolveLocalType, isUnshadowedType) === "readonly") {
|
|
14808
14941
|
return true;
|
|
14809
14942
|
}
|
|
14810
|
-
return node.init?.type === import_utils89.AST_NODE_TYPES.TSAsExpression &&
|
|
14943
|
+
return node.init?.type === import_utils89.AST_NODE_TYPES.TSAsExpression && readonlyTypeState(node.init.typeAnnotation, kind, resolveLocalType, isUnshadowedType) === "readonly";
|
|
14811
14944
|
}
|
|
14812
|
-
function
|
|
14813
|
-
if (
|
|
14814
|
-
if (node.type !== import_utils89.AST_NODE_TYPES.TSTypeReference || node.typeName.type !== import_utils89.AST_NODE_TYPES.Identifier)
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14945
|
+
function readonlyTypeState(node, kind, resolveLocalType, isUnshadowedType, arguments_ = /* @__PURE__ */ new Map(), seen = /* @__PURE__ */ new Set()) {
|
|
14946
|
+
if (seen.has(node)) return "unknown";
|
|
14947
|
+
if (node.type !== import_utils89.AST_NODE_TYPES.TSTypeReference || node.typeName.type !== import_utils89.AST_NODE_TYPES.Identifier) {
|
|
14948
|
+
return directReadonlyState(node, kind, isUnshadowedType);
|
|
14949
|
+
}
|
|
14950
|
+
const declaration = resolveLocalType(node.typeName);
|
|
14951
|
+
if (declaration === void 0) return directReadonlyState(node, kind, isUnshadowedType);
|
|
14952
|
+
if (declaration.type === import_utils89.AST_NODE_TYPES.TSTypeParameter) {
|
|
14953
|
+
const argument = arguments_.get(declaration);
|
|
14954
|
+
return argument === void 0 ? "unknown" : readonlyTypeState(
|
|
14955
|
+
argument.node,
|
|
14956
|
+
kind,
|
|
14957
|
+
resolveLocalType,
|
|
14958
|
+
isUnshadowedType,
|
|
14959
|
+
argument.arguments,
|
|
14960
|
+
argument.seen
|
|
14961
|
+
);
|
|
14962
|
+
}
|
|
14963
|
+
const expanded = /* @__PURE__ */ new Set([...seen, node]);
|
|
14964
|
+
return readonlyTypeState(
|
|
14965
|
+
declaration.typeAnnotation,
|
|
14966
|
+
kind,
|
|
14967
|
+
resolveLocalType,
|
|
14968
|
+
isUnshadowedType,
|
|
14969
|
+
bindTypeArguments(declaration, node, arguments_, expanded),
|
|
14970
|
+
expanded
|
|
14971
|
+
);
|
|
14972
|
+
}
|
|
14973
|
+
function bindTypeArguments(declaration, reference, arguments_, seen) {
|
|
14974
|
+
const bindings = new Map(arguments_);
|
|
14975
|
+
for (const [index, parameter] of (declaration.typeParameters?.params ?? []).entries()) {
|
|
14976
|
+
const argument = reference.typeArguments?.params[index];
|
|
14977
|
+
const node = argument ?? parameter.default;
|
|
14978
|
+
if (node !== void 0) {
|
|
14979
|
+
bindings.set(parameter, { node, arguments: argument === void 0 ? new Map(bindings) : arguments_, seen });
|
|
14980
|
+
}
|
|
14981
|
+
}
|
|
14982
|
+
return bindings;
|
|
14819
14983
|
}
|
|
14820
|
-
function
|
|
14984
|
+
function directReadonlyState(node, kind, isUnshadowedType) {
|
|
14821
14985
|
if (node.type === import_utils89.AST_NODE_TYPES.TSTypeOperator && node.operator === "readonly") {
|
|
14822
|
-
return
|
|
14986
|
+
return "readonly";
|
|
14823
14987
|
}
|
|
14824
|
-
if (node.type
|
|
14825
|
-
return
|
|
14988
|
+
if (node.type === import_utils89.AST_NODE_TYPES.TSTypeLiteral) {
|
|
14989
|
+
if (node.members.length === 0) return "unknown";
|
|
14990
|
+
const readonly2 = kind === "literal" && node.members.length > 0 && node.members.every(
|
|
14991
|
+
(member) => (member.type === import_utils89.AST_NODE_TYPES.TSPropertySignature || member.type === import_utils89.AST_NODE_TYPES.TSIndexSignature) && member.readonly
|
|
14992
|
+
);
|
|
14993
|
+
return readonly2 ? "readonly" : "mutable";
|
|
14994
|
+
}
|
|
14995
|
+
if (node.type === import_utils89.AST_NODE_TYPES.TSArrayType || node.type === import_utils89.AST_NODE_TYPES.TSTupleType) return "mutable";
|
|
14996
|
+
if (node.type !== import_utils89.AST_NODE_TYPES.TSTypeReference || node.typeName.type !== import_utils89.AST_NODE_TYPES.Identifier || !isUnshadowedType(node.typeName)) {
|
|
14997
|
+
return "unknown";
|
|
14826
14998
|
}
|
|
14827
14999
|
if (node.typeName.name === "Readonly") {
|
|
14828
|
-
return kind === "literal";
|
|
15000
|
+
return kind === "literal" ? "readonly" : "mutable";
|
|
14829
15001
|
}
|
|
14830
|
-
|
|
15002
|
+
if (["Array", "Map", "Set"].includes(node.typeName.name)) return "mutable";
|
|
15003
|
+
if (!["ReadonlyArray", "ReadonlyMap", "ReadonlySet"].includes(node.typeName.name)) return "unknown";
|
|
15004
|
+
const readonly = kind === "literal" ? node.typeName.name === "ReadonlyArray" : node.typeName.name === `Readonly${kind}`;
|
|
15005
|
+
return readonly ? "readonly" : "mutable";
|
|
14831
15006
|
}
|
|
14832
|
-
function hasUnknownExplicitType(node,
|
|
14833
|
-
const annotation = node.id.type === import_utils89.AST_NODE_TYPES.Identifier ? node.id.typeAnnotation?.typeAnnotation : void 0;
|
|
15007
|
+
function hasUnknownExplicitType(node, kind, resolveLocalType, isUnshadowedType) {
|
|
15008
|
+
const annotation = (node.id.type === import_utils89.AST_NODE_TYPES.Identifier ? node.id.typeAnnotation?.typeAnnotation : void 0) ?? (node.init?.type === import_utils89.AST_NODE_TYPES.TSAsExpression ? node.init.typeAnnotation : void 0);
|
|
14834
15009
|
if (annotation === void 0) return false;
|
|
14835
15010
|
if (annotation.type === import_utils89.AST_NODE_TYPES.TSArrayType || annotation.type === import_utils89.AST_NODE_TYPES.TSTypeOperator) return false;
|
|
14836
15011
|
if (annotation.type !== import_utils89.AST_NODE_TYPES.TSTypeReference || annotation.typeName.type !== import_utils89.AST_NODE_TYPES.Identifier) return true;
|
|
14837
|
-
|
|
15012
|
+
if (resolveLocalType(annotation.typeName) !== void 0) {
|
|
15013
|
+
return readonlyTypeState(annotation, kind, resolveLocalType, isUnshadowedType) === "unknown";
|
|
15014
|
+
}
|
|
15015
|
+
return resolveLocalType(annotation.typeName) === void 0 && (!isUnshadowedType(annotation.typeName) || !["Array", "Map", "Readonly", "ReadonlyArray", "ReadonlyMap", "ReadonlySet", "Set"].includes(annotation.typeName.name));
|
|
14838
15016
|
}
|
|
14839
15017
|
function referenceMutates(identifier, isUnshadowedGlobal3) {
|
|
14840
15018
|
let member = identifier.parent;
|
|
@@ -14877,11 +15055,20 @@ var prefer_immutable_module_constant_default = createRule({
|
|
|
14877
15055
|
const variable = import_utils89.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
14878
15056
|
return variable === null || variable.defs.length === 0;
|
|
14879
15057
|
};
|
|
15058
|
+
const isUnshadowedType = (identifier) => {
|
|
15059
|
+
const variable = import_utils89.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
15060
|
+
return variable === null || !variable.defs.some((definition) => definition.isTypeDefinition);
|
|
15061
|
+
};
|
|
15062
|
+
const resolveLocalType = (identifier) => {
|
|
15063
|
+
const variable = import_utils89.ASTUtils.findVariable(sourceCode.getScope(identifier), identifier.name);
|
|
15064
|
+
const definitions = variable?.defs.filter((definition) => definition.isTypeDefinition) ?? [];
|
|
15065
|
+
const declaration = definitions.length === 1 ? definitions[0]?.node : void 0;
|
|
15066
|
+
return declaration?.type === import_utils89.AST_NODE_TYPES.TSTypeAliasDeclaration || declaration?.type === import_utils89.AST_NODE_TYPES.TSTypeParameter ? declaration : void 0;
|
|
15067
|
+
};
|
|
14880
15068
|
if (JAVASCRIPT_FILE_RE.test(context.filename) || isTestFile(context.filename) || isGeneratedFile(context.filename, sourceCode.getText())) {
|
|
14881
15069
|
return {};
|
|
14882
15070
|
}
|
|
14883
15071
|
const exportedNames2 = /* @__PURE__ */ new Set();
|
|
14884
|
-
const typeAliases2 = /* @__PURE__ */ new Map();
|
|
14885
15072
|
const mutatesThroughAlias = (root) => {
|
|
14886
15073
|
const pending = [root];
|
|
14887
15074
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -14906,10 +15093,6 @@ var prefer_immutable_module_constant_default = createRule({
|
|
|
14906
15093
|
return {
|
|
14907
15094
|
Program(node) {
|
|
14908
15095
|
function collectModuleBindings(statement) {
|
|
14909
|
-
const declaration = statement.type === import_utils89.AST_NODE_TYPES.ExportNamedDeclaration ? statement.declaration : statement;
|
|
14910
|
-
if (declaration?.type === import_utils89.AST_NODE_TYPES.TSTypeAliasDeclaration) {
|
|
14911
|
-
typeAliases2.set(declaration.id.name, declaration.typeAnnotation);
|
|
14912
|
-
}
|
|
14913
15096
|
if (statement.type === import_utils89.AST_NODE_TYPES.ExportNamedDeclaration) {
|
|
14914
15097
|
if (statement.source !== null || statement.exportKind === "type") return;
|
|
14915
15098
|
for (const specifier of statement.specifiers) {
|
|
@@ -14942,7 +15125,7 @@ var prefer_immutable_module_constant_default = createRule({
|
|
|
14942
15125
|
return;
|
|
14943
15126
|
}
|
|
14944
15127
|
const kind = collectionKind(node.init, isUnshadowedGlobal3);
|
|
14945
|
-
if (kind === null || declaredReadonlyType(node, kind,
|
|
15128
|
+
if (kind === null || declaredReadonlyType(node, kind, resolveLocalType, isUnshadowedType) || hasUnknownExplicitType(node, kind, resolveLocalType, isUnshadowedType)) {
|
|
14946
15129
|
return;
|
|
14947
15130
|
}
|
|
14948
15131
|
const variable = sourceCode.getDeclaredVariables(node)[0];
|
|
@@ -16974,7 +17157,7 @@ var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
|
|
|
16974
17157
|
{ 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
17158
|
]
|
|
16976
17159
|
};
|
|
16977
|
-
function
|
|
17160
|
+
function memberName6(node) {
|
|
16978
17161
|
if (!node.computed && node.property.type === import_utils99.AST_NODE_TYPES.Identifier) return node.property.name;
|
|
16979
17162
|
if (node.computed && node.property.type === import_utils99.AST_NODE_TYPES.Literal && typeof node.property.value === "string") {
|
|
16980
17163
|
return node.property.value;
|
|
@@ -16990,7 +17173,7 @@ function isCryptoLoader(node, resolve2) {
|
|
|
16990
17173
|
if (node.callee.type === import_utils99.AST_NODE_TYPES.Identifier) {
|
|
16991
17174
|
return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee, resolve2);
|
|
16992
17175
|
}
|
|
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) &&
|
|
17176
|
+
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
17177
|
}
|
|
16995
17178
|
function isCryptoSpecifier(node) {
|
|
16996
17179
|
return node.type === import_utils99.AST_NODE_TYPES.Literal && (node.value === "crypto" || node.value === "node:crypto");
|
|
@@ -17072,14 +17255,14 @@ function importedName6(node) {
|
|
|
17072
17255
|
return node.type === import_utils99.AST_NODE_TYPES.Identifier ? node.name : node.value;
|
|
17073
17256
|
}
|
|
17074
17257
|
function isMemberCall(node, name) {
|
|
17075
|
-
return node.callee.type === import_utils99.AST_NODE_TYPES.MemberExpression &&
|
|
17258
|
+
return node.callee.type === import_utils99.AST_NODE_TYPES.MemberExpression && memberName6(node.callee) === name;
|
|
17076
17259
|
}
|
|
17077
17260
|
function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
|
|
17078
17261
|
if (node.callee.type === import_utils99.AST_NODE_TYPES.Identifier) {
|
|
17079
17262
|
const variable2 = resolve2(node.callee);
|
|
17080
17263
|
return variable2 !== null && directBindings.has(variable2);
|
|
17081
17264
|
}
|
|
17082
|
-
if (node.callee.type !== import_utils99.AST_NODE_TYPES.MemberExpression ||
|
|
17265
|
+
if (node.callee.type !== import_utils99.AST_NODE_TYPES.MemberExpression || memberName6(node.callee) !== "createHash") {
|
|
17083
17266
|
return false;
|
|
17084
17267
|
}
|
|
17085
17268
|
if (isCryptoLoader(node.callee.object, resolve2)) return true;
|
|
@@ -17107,7 +17290,7 @@ var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
|
|
|
17107
17290
|
{ 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
17291
|
]
|
|
17109
17292
|
};
|
|
17110
|
-
function
|
|
17293
|
+
function memberName7(node) {
|
|
17111
17294
|
if (!node.computed && node.property.type === import_utils100.AST_NODE_TYPES.Identifier) return node.property.name;
|
|
17112
17295
|
if (node.computed && node.property.type === import_utils100.AST_NODE_TYPES.Literal && typeof node.property.value === "string") {
|
|
17113
17296
|
return node.property.value;
|
|
@@ -17124,7 +17307,7 @@ function isFsLoader(node, isGlobal) {
|
|
|
17124
17307
|
const [argument] = expression.arguments;
|
|
17125
17308
|
if (argument === void 0 || argument.type === import_utils100.AST_NODE_TYPES.SpreadElement || !isFsSpecifier(argument)) return false;
|
|
17126
17309
|
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) &&
|
|
17310
|
+
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
17311
|
}
|
|
17129
17312
|
function isFsSpecifier(node) {
|
|
17130
17313
|
return node.type === import_utils100.AST_NODE_TYPES.Literal && (node.value === "node:fs" || node.value === "fs");
|
|
@@ -17205,7 +17388,7 @@ var prefer_node_fs_promises_default = createRule({
|
|
|
17205
17388
|
}
|
|
17206
17389
|
},
|
|
17207
17390
|
MemberExpression(node) {
|
|
17208
|
-
const name =
|
|
17391
|
+
const name = memberName7(node);
|
|
17209
17392
|
if (name?.endsWith("Sync") !== true) return;
|
|
17210
17393
|
const object = unwrapAwait2(node.object);
|
|
17211
17394
|
if (object.type === import_utils100.AST_NODE_TYPES.Identifier && isNamespace(object) || isFsLoader(object, isGlobal)) {
|
|
@@ -21390,7 +21573,7 @@ var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
|
|
|
21390
21573
|
}
|
|
21391
21574
|
]
|
|
21392
21575
|
};
|
|
21393
|
-
function
|
|
21576
|
+
function memberName8(node) {
|
|
21394
21577
|
if (!node.computed && node.property.type === import_utils116.AST_NODE_TYPES.Identifier)
|
|
21395
21578
|
return node.property.name;
|
|
21396
21579
|
if (node.computed && node.property.type === import_utils116.AST_NODE_TYPES.Literal && typeof node.property.value === "string")
|
|
@@ -21408,7 +21591,7 @@ function databaseReceiver(node) {
|
|
|
21408
21591
|
if (node.type === import_utils116.AST_NODE_TYPES.Identifier)
|
|
21409
21592
|
return DATABASE_NAMES.test(node.name);
|
|
21410
21593
|
if (node.type !== import_utils116.AST_NODE_TYPES.MemberExpression) return false;
|
|
21411
|
-
const name =
|
|
21594
|
+
const name = memberName8(node);
|
|
21412
21595
|
return name === "DB" || name !== null && DATABASE_NAMES.test(name);
|
|
21413
21596
|
}
|
|
21414
21597
|
function databaseRootMember(node) {
|
|
@@ -21429,7 +21612,7 @@ function databaseRootMember(node) {
|
|
|
21429
21612
|
}
|
|
21430
21613
|
if (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
|
|
21431
21614
|
if (current.object.type === import_utils116.AST_NODE_TYPES.ThisExpression)
|
|
21432
|
-
return
|
|
21615
|
+
return memberName8(current);
|
|
21433
21616
|
current = current.object;
|
|
21434
21617
|
continue;
|
|
21435
21618
|
}
|
|
@@ -21453,7 +21636,7 @@ function expressionHasDatabaseMarker(node) {
|
|
|
21453
21636
|
continue;
|
|
21454
21637
|
}
|
|
21455
21638
|
if (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
|
|
21456
|
-
const name =
|
|
21639
|
+
const name = memberName8(current);
|
|
21457
21640
|
if (name === "DB" || name !== null && DATABASE_NAMES.test(name))
|
|
21458
21641
|
return true;
|
|
21459
21642
|
current = current.object;
|
|
@@ -21475,7 +21658,7 @@ function expressionCallsBuilder(node) {
|
|
|
21475
21658
|
}
|
|
21476
21659
|
if (current.type === import_utils116.AST_NODE_TYPES.CallExpression) {
|
|
21477
21660
|
if (current.callee.type !== import_utils116.AST_NODE_TYPES.MemberExpression) return false;
|
|
21478
|
-
const name =
|
|
21661
|
+
const name = memberName8(current.callee);
|
|
21479
21662
|
if (name !== null && BUILDER_METHODS.has(name)) return true;
|
|
21480
21663
|
current = current.callee.object;
|
|
21481
21664
|
continue;
|
|
@@ -21526,7 +21709,7 @@ function thisRootMember(node) {
|
|
|
21526
21709
|
let current = node;
|
|
21527
21710
|
let root = null;
|
|
21528
21711
|
while (current.type === import_utils116.AST_NODE_TYPES.MemberExpression) {
|
|
21529
|
-
const name =
|
|
21712
|
+
const name = memberName8(current);
|
|
21530
21713
|
if (name === null) return null;
|
|
21531
21714
|
root = name;
|
|
21532
21715
|
current = current.object;
|
|
@@ -21580,7 +21763,7 @@ var require_sql_access_class_default = createRule({
|
|
|
21580
21763
|
CallExpression(node) {
|
|
21581
21764
|
if (node.callee.type !== import_utils116.AST_NODE_TYPES.MemberExpression)
|
|
21582
21765
|
return;
|
|
21583
|
-
const method =
|
|
21766
|
+
const method = memberName8(node.callee);
|
|
21584
21767
|
if (knownNonDatabase(node.callee.object)) return;
|
|
21585
21768
|
if (method === null || !isDatabaseOperation(method, node.callee.object))
|
|
21586
21769
|
return;
|
|
@@ -23210,10 +23393,11 @@ var import_node_fs2 = require("fs");
|
|
|
23210
23393
|
var import_node_path2 = require("path");
|
|
23211
23394
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]);
|
|
23212
23395
|
var NEXT_COMPONENT = /^(?:src\/)?app\/(?:.*\/)?(?:page|layout|loading|not-found|template|error|global-error|default|forbidden|unauthorized)\.[jt]sx?$/u;
|
|
23396
|
+
var NEXT_METADATA = /^(?:src\/)?app\/(?:.*\/)?(?:(?:opengraph-image|twitter-image|icon|apple-icon)\.[jt]sx?|(?:robots|sitemap|manifest)\.[jt]s)$/u;
|
|
23213
23397
|
var NEXT_ROUTE = /^(?:src\/)?app\/(?:.*\/)?route\.[jt]s$/u;
|
|
23214
23398
|
function isFrameworkEntrypoint(filename, exportKey) {
|
|
23215
23399
|
const normalized = filename.replaceAll("\\", "/");
|
|
23216
|
-
if (!/(?:^|\/)(?:app|pages)\/|(?:^|\/)(?:middleware|proxy)\.[jt]s$/u.test(normalized)) return false;
|
|
23400
|
+
if (!/(?:^|\/)(?:app|pages)\/|(?:^|\/)(?:middleware|proxy|instrumentation)\.[jt]s$/u.test(normalized)) return false;
|
|
23217
23401
|
const owner = packageOwner(normalized);
|
|
23218
23402
|
if (owner === null) return false;
|
|
23219
23403
|
const path = (0, import_node_path2.relative)(owner.root, normalized).replaceAll("\\", "/");
|
|
@@ -23222,9 +23406,12 @@ function isFrameworkEntrypoint(filename, exportKey) {
|
|
|
23222
23406
|
}
|
|
23223
23407
|
function isNextEntrypoint(path, exportKey) {
|
|
23224
23408
|
const privateAppFolder = /^(?:src\/)?app\/(?:.*\/)?_[^/]+\//u.test(path);
|
|
23225
|
-
if (!privateAppFolder && NEXT_COMPONENT.test(path) && exportKey === "default") return true;
|
|
23409
|
+
if (!privateAppFolder && (NEXT_COMPONENT.test(path) || NEXT_METADATA.test(path)) && exportKey === "default") return true;
|
|
23226
23410
|
if (!privateAppFolder && NEXT_ROUTE.test(path) && HTTP_METHODS.has(exportKey)) return true;
|
|
23227
23411
|
if (/^(?:src\/)?pages\/.*\.[jt]sx?$/u.test(path) && exportKey === "default") return true;
|
|
23412
|
+
if (/^(?:src\/)?instrumentation\.[jt]s$/u.test(path)) {
|
|
23413
|
+
return exportKey === "register" || exportKey === "onRequestError";
|
|
23414
|
+
}
|
|
23228
23415
|
const middleware = /^(?:src\/)?(middleware|proxy)\.[jt]s$/u.exec(path);
|
|
23229
23416
|
return middleware !== null && (exportKey === "default" || exportKey === middleware[1]);
|
|
23230
23417
|
}
|
|
@@ -23267,7 +23454,7 @@ var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
|
|
|
23267
23454
|
"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
23455
|
"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
23456
|
"Exported destructuring patterns are excluded rather than undercounted as public exports.",
|
|
23270
|
-
"Next.js and Astro
|
|
23457
|
+
"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
23458
|
],
|
|
23272
23459
|
examples: [
|
|
23273
23460
|
{ 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 +24020,7 @@ var RULES = {
|
|
|
23833
24020
|
};
|
|
23834
24021
|
var meta = {
|
|
23835
24022
|
name: "@sarj/eslint-plugin",
|
|
23836
|
-
version: "16.
|
|
24023
|
+
version: "16.3.0"
|
|
23837
24024
|
};
|
|
23838
24025
|
var APPLICATION_ONLY_RULES = [];
|
|
23839
24026
|
var LIBRARY_IMPORT_POLICY = ["error", {
|