@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.js
CHANGED
|
@@ -298,69 +298,34 @@ function commentLineEnd(sourceText, start) {
|
|
|
298
298
|
// src/rules/require-explicit-contract-implementation.ts
|
|
299
299
|
var REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION = {
|
|
300
300
|
defaultLevel: "warning",
|
|
301
|
-
summary: "Require
|
|
302
|
-
rationale: "
|
|
303
|
-
remediation: "
|
|
301
|
+
summary: "Require nominal inheritance when an injected abstract class is rejected by an instanceof guard.",
|
|
302
|
+
rationale: "A structurally assignable fake can still fail a constructor's nominal runtime guard. Pure structural interfaces do not require an implements clause.",
|
|
303
|
+
remediation: "Use an implementation inheriting the existing abstract class, or remove the nominal guard if the boundary is intentionally structural.",
|
|
304
304
|
category: "architecture",
|
|
305
305
|
autofix: "none",
|
|
306
306
|
limitations: [
|
|
307
|
-
"
|
|
308
|
-
"
|
|
307
|
+
"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.",
|
|
308
|
+
"The contract must resolve to an abstract class without decorators, unresolved ancestors, or custom computed static members in its resolved hierarchy.",
|
|
309
|
+
"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."
|
|
309
310
|
],
|
|
310
311
|
examples: [
|
|
311
312
|
{
|
|
312
|
-
id: "structural-fake
|
|
313
|
-
title: "A
|
|
313
|
+
id: "runtime-nominal-guard-with-structural-fake",
|
|
314
|
+
title: "A structurally compatible fake fails the constructor's runtime guard",
|
|
314
315
|
outcome: "match",
|
|
315
|
-
files: [{ path: "src/service.ts", source: "
|
|
316
|
+
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());" }],
|
|
316
317
|
focusPath: "src/service.ts",
|
|
317
318
|
expectedCount: 1,
|
|
318
319
|
public: true
|
|
319
320
|
},
|
|
320
321
|
{
|
|
321
|
-
id: "
|
|
322
|
-
title: "The fake
|
|
322
|
+
id: "runtime-nominal-guard-with-inherited-fake",
|
|
323
|
+
title: "The fake satisfies the existing nominal runtime contract",
|
|
323
324
|
outcome: "no-match",
|
|
324
|
-
files: [{ path: "src/service.ts", source: "
|
|
325
|
+
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());" }],
|
|
325
326
|
focusPath: "src/service.ts",
|
|
326
327
|
expectedCount: 0,
|
|
327
328
|
public: true
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
id: "inherited-implementation",
|
|
331
|
-
title: "The fake inherits an implementation of the port",
|
|
332
|
-
outcome: "no-match",
|
|
333
|
-
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());" }],
|
|
334
|
-
focusPath: "src/service.ts",
|
|
335
|
-
expectedCount: 0,
|
|
336
|
-
public: false
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
id: "non-contract-data-argument",
|
|
340
|
-
title: "A data object is not a service port",
|
|
341
|
-
outcome: "no-match",
|
|
342
|
-
files: [{ path: "src/service.ts", source: "interface Options { name: string } class Consumer { constructor(readonly options: Options) {} } class FakeOptions { name = 'test' } new Consumer(new FakeOptions());" }],
|
|
343
|
-
focusPath: "src/service.ts",
|
|
344
|
-
expectedCount: 0,
|
|
345
|
-
public: false
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
id: "explicit-cast-origin-unknown",
|
|
349
|
-
title: "A cast hides the concrete source",
|
|
350
|
-
outcome: "no-match",
|
|
351
|
-
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);" }],
|
|
352
|
-
focusPath: "src/service.ts",
|
|
353
|
-
expectedCount: 0,
|
|
354
|
-
public: false
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
id: "unrelated-interface",
|
|
358
|
-
title: "Implementing another interface does not declare the supplied port",
|
|
359
|
-
outcome: "match",
|
|
360
|
-
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());" }],
|
|
361
|
-
focusPath: "src/service.ts",
|
|
362
|
-
expectedCount: 1,
|
|
363
|
-
public: false
|
|
364
329
|
}
|
|
365
330
|
]
|
|
366
331
|
};
|
|
@@ -371,7 +336,7 @@ function behavioralContract(checker, type) {
|
|
|
371
336
|
return behavioralContract(checker, substantive[0]);
|
|
372
337
|
}
|
|
373
338
|
if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) return null;
|
|
374
|
-
const declaration = type.getSymbol()?.declarations?.find((item) => ts.
|
|
339
|
+
const declaration = type.getSymbol()?.declarations?.find((item) => ts.isClassDeclaration(item) && item.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AbstractKeyword));
|
|
375
340
|
if (declaration === void 0) return null;
|
|
376
341
|
return checker.getPropertiesOfType(type).some((property) => {
|
|
377
342
|
if (!property.declarations?.some((member) => ts.isMethodSignature(member) || ts.isMethodDeclaration(member))) return false;
|
|
@@ -379,43 +344,86 @@ function behavioralContract(checker, type) {
|
|
|
379
344
|
return propertyType.getCallSignatures().length > 0;
|
|
380
345
|
}) ? type : null;
|
|
381
346
|
}
|
|
382
|
-
function ownedClass(
|
|
347
|
+
function ownedClass(checker, expression) {
|
|
348
|
+
if (!ts.isNewExpression(expression)) return null;
|
|
349
|
+
const type = checker.getTypeAtLocation(expression);
|
|
383
350
|
if ((type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0) return null;
|
|
384
351
|
const declaration = type.getSymbol()?.declarations?.find(ts.isClassDeclaration);
|
|
385
|
-
|
|
352
|
+
const constructed = canonicalSymbol(checker, expression.expression)?.declarations?.find(ts.isClassDeclaration);
|
|
353
|
+
if (declaration === void 0 || declaration !== constructed || declaration.getSourceFile().isDeclarationFile || /(?:^|[/\\])(?:node_modules|vendor|generated)(?:[/\\])/u.test(declaration.getSourceFile().fileName)) return null;
|
|
386
354
|
return declaration;
|
|
387
355
|
}
|
|
388
|
-
function
|
|
356
|
+
function memberName(node) {
|
|
357
|
+
if (ts.isPropertyAccessExpression(node)) return node.name.text;
|
|
358
|
+
if (ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression)) return node.argumentExpression.text;
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
function hasPrototypeMutation(source) {
|
|
362
|
+
function visit(node) {
|
|
363
|
+
const member = memberName(node);
|
|
364
|
+
if (member === "setPrototypeOf" || member === "__proto__" || member === "hasInstance") return true;
|
|
365
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && memberName(node.left) === "prototype") return true;
|
|
366
|
+
return ts.forEachChild(node, visit);
|
|
367
|
+
}
|
|
368
|
+
return visit(source) === true;
|
|
369
|
+
}
|
|
370
|
+
function nominallyInherits(checker, declaration, target, seen) {
|
|
371
|
+
if (ts.canHaveDecorators(declaration) && ts.getDecorators(declaration)?.length) return null;
|
|
372
|
+
if (hasPrototypeMutation(declaration.getSourceFile()) || declaration.members.some((member) => ts.isConstructorDeclaration(member) || ts.isPropertyDeclaration(member) && member.initializer !== void 0)) return null;
|
|
389
373
|
const symbol = declaration.name === void 0 ? void 0 : canonicalSymbol(checker, declaration.name);
|
|
390
374
|
if (symbol !== void 0) {
|
|
391
375
|
if (seen.has(symbol)) return null;
|
|
392
376
|
seen.add(symbol);
|
|
393
377
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (parentSymbol === target) return true;
|
|
403
|
-
const inherited = parentSymbol.declarations?.find((item) => ts.isClassDeclaration(item) || ts.isInterfaceDeclaration(item));
|
|
404
|
-
if (inherited === void 0) {
|
|
405
|
-
unknown = true;
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
const relationship = explicitlyImplements(checker, inherited, target, new Set(seen));
|
|
409
|
-
if (relationship === true) return true;
|
|
410
|
-
unknown ||= relationship === null;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
return unknown ? null : false;
|
|
378
|
+
const parents = declaration.heritageClauses?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword)?.types;
|
|
379
|
+
if (parents === void 0) return false;
|
|
380
|
+
if (parents.length !== 1) return null;
|
|
381
|
+
const parentSymbol = canonicalSymbol(checker, parents[0].expression);
|
|
382
|
+
if (parentSymbol === void 0) return null;
|
|
383
|
+
if (parentSymbol === target) return true;
|
|
384
|
+
const inherited = parentSymbol.declarations?.find(ts.isClassDeclaration);
|
|
385
|
+
return inherited === void 0 ? null : nominallyInherits(checker, inherited, target, new Set(seen));
|
|
414
386
|
}
|
|
415
387
|
function canonicalSymbol(checker, node) {
|
|
416
388
|
const symbol = checker.getSymbolAtLocation(node);
|
|
417
389
|
return symbol === void 0 ? void 0 : (symbol.flags & ts.SymbolFlags.Alias) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
|
|
418
390
|
}
|
|
391
|
+
function hasNominalRejectionGuard(checker, signature, parameter, contract) {
|
|
392
|
+
const declaration = signature.getDeclaration();
|
|
393
|
+
if (declaration === void 0 || !ts.isConstructorDeclaration(declaration)) return false;
|
|
394
|
+
if (ts.canHaveDecorators(declaration.parent) && ts.getDecorators(declaration.parent)?.length) return false;
|
|
395
|
+
if (hasPrototypeMutation(declaration.getSourceFile())) return false;
|
|
396
|
+
const first = declaration.body?.statements[0];
|
|
397
|
+
if (first === void 0 || !ts.isIfStatement(first) || !alwaysThrows(first.thenStatement)) return false;
|
|
398
|
+
const condition = unparenthesized(first.expression);
|
|
399
|
+
if (!ts.isPrefixUnaryExpression(condition) || condition.operator !== ts.SyntaxKind.ExclamationToken) return false;
|
|
400
|
+
const comparison = unparenthesized(condition.operand);
|
|
401
|
+
if (!ts.isBinaryExpression(comparison) || comparison.operatorToken.kind !== ts.SyntaxKind.InstanceOfKeyword) return false;
|
|
402
|
+
if (canonicalSymbol(checker, comparison.left) !== parameter || canonicalSymbol(checker, comparison.right) !== contract) return false;
|
|
403
|
+
const target = contract.declarations?.find(ts.isClassDeclaration);
|
|
404
|
+
return target !== void 0 && hasOrdinaryInstanceCheck(checker, target, /* @__PURE__ */ new Set());
|
|
405
|
+
}
|
|
406
|
+
function unparenthesized(expression) {
|
|
407
|
+
let current = expression;
|
|
408
|
+
while (ts.isParenthesizedExpression(current)) current = current.expression;
|
|
409
|
+
return current;
|
|
410
|
+
}
|
|
411
|
+
function alwaysThrows(statement) {
|
|
412
|
+
return ts.isThrowStatement(statement) || ts.isBlock(statement) && statement.statements.length === 1 && ts.isThrowStatement(statement.statements[0]);
|
|
413
|
+
}
|
|
414
|
+
function hasOrdinaryInstanceCheck(checker, declaration, seen) {
|
|
415
|
+
if (seen.has(declaration) || ts.getDecorators(declaration)?.length || hasPrototypeMutation(declaration.getSourceFile())) return false;
|
|
416
|
+
seen.add(declaration);
|
|
417
|
+
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;
|
|
418
|
+
for (const clause of declaration.heritageClauses ?? []) {
|
|
419
|
+
if (clause.token !== ts.SyntaxKind.ExtendsKeyword) continue;
|
|
420
|
+
for (const parent of clause.types) {
|
|
421
|
+
const ancestor = canonicalSymbol(checker, parent.expression)?.declarations?.find(ts.isClassDeclaration);
|
|
422
|
+
if (ancestor === void 0 || !hasOrdinaryInstanceCheck(checker, ancestor, seen)) return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
419
427
|
var require_explicit_contract_implementation_default = createRule({
|
|
420
428
|
name: "require-explicit-contract-implementation",
|
|
421
429
|
documentation: REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION,
|
|
@@ -424,7 +432,7 @@ var require_explicit_contract_implementation_default = createRule({
|
|
|
424
432
|
docs: { description: REQUIRE_EXPLICIT_CONTRACT_IMPLEMENTATION_DOCUMENTATION.summary },
|
|
425
433
|
schema: [],
|
|
426
434
|
messages: {
|
|
427
|
-
declareActualContract: "`{{implementation}}`
|
|
435
|
+
declareActualContract: "`{{implementation}}` structurally matches `{{contract}}` but fails the rejecting instanceof guard; use nominal inheritance or make the boundary structural."
|
|
428
436
|
}
|
|
429
437
|
},
|
|
430
438
|
defaultOptions: [],
|
|
@@ -435,25 +443,27 @@ var require_explicit_contract_implementation_default = createRule({
|
|
|
435
443
|
const checker = services.program.getTypeChecker();
|
|
436
444
|
function inspect(node) {
|
|
437
445
|
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
438
|
-
if (!ts.
|
|
446
|
+
if (!ts.isNewExpression(tsNode)) return;
|
|
447
|
+
const consumer = canonicalSymbol(checker, tsNode.expression)?.declarations?.find(ts.isClassDeclaration);
|
|
448
|
+
if (consumer === void 0) return;
|
|
439
449
|
const signature = checker.getResolvedSignature(tsNode);
|
|
440
|
-
if (signature === void 0) return;
|
|
450
|
+
if (signature === void 0 || signature.getDeclaration()?.parent !== consumer) return;
|
|
441
451
|
const parameters = signature.getParameters();
|
|
442
452
|
for (const [index, argument] of node.arguments.entries()) {
|
|
443
|
-
if (argument.type
|
|
453
|
+
if (argument.type !== AST_NODE_TYPES.NewExpression) continue;
|
|
444
454
|
const parameter = parameters[index];
|
|
445
455
|
if (parameter === void 0) continue;
|
|
446
456
|
const parameterType = checker.getTypeOfSymbolAtLocation(parameter, tsNode);
|
|
447
457
|
const contractType = behavioralContract(checker, parameterType);
|
|
448
458
|
if (contractType === null) continue;
|
|
449
459
|
const contractSymbol = contractType.getSymbol();
|
|
450
|
-
if (contractSymbol === void 0) continue;
|
|
460
|
+
if (contractSymbol === void 0 || !hasNominalRejectionGuard(checker, signature, parameter, contractSymbol)) continue;
|
|
451
461
|
const tsArgument = services.esTreeNodeToTSNodeMap.get(argument);
|
|
452
462
|
const actualType = checker.getTypeAtLocation(tsArgument);
|
|
453
463
|
if (!checker.isTypeAssignableTo(actualType, contractType)) continue;
|
|
454
|
-
const implementation = ownedClass(
|
|
464
|
+
const implementation = ownedClass(checker, tsArgument);
|
|
455
465
|
if (implementation === null) continue;
|
|
456
|
-
const relationship =
|
|
466
|
+
const relationship = nominallyInherits(checker, implementation, contractSymbol, /* @__PURE__ */ new Set());
|
|
457
467
|
if (relationship !== false) continue;
|
|
458
468
|
context.report({
|
|
459
469
|
node: argument,
|
|
@@ -463,7 +473,6 @@ var require_explicit_contract_implementation_default = createRule({
|
|
|
463
473
|
}
|
|
464
474
|
}
|
|
465
475
|
return {
|
|
466
|
-
CallExpression: inspect,
|
|
467
476
|
NewExpression: inspect
|
|
468
477
|
};
|
|
469
478
|
}
|
|
@@ -581,7 +590,7 @@ var propertySignatureTypes = (members) => {
|
|
|
581
590
|
};
|
|
582
591
|
var fileTypeIndex = (program) => {
|
|
583
592
|
const objects = /* @__PURE__ */ new Map();
|
|
584
|
-
const functionAliases =
|
|
593
|
+
const functionAliases = localFunctionAliases(program);
|
|
585
594
|
function collectDeclaredType(statement) {
|
|
586
595
|
const declaration = statement.type === AST_NODE_TYPES2.ExportNamedDeclaration ? statement.declaration : statement;
|
|
587
596
|
if (declaration?.type === AST_NODE_TYPES2.TSInterfaceDeclaration) {
|
|
@@ -590,10 +599,6 @@ var fileTypeIndex = (program) => {
|
|
|
590
599
|
}
|
|
591
600
|
if (declaration?.type !== AST_NODE_TYPES2.TSTypeAliasDeclaration) return;
|
|
592
601
|
const aliased = declaration.typeAnnotation;
|
|
593
|
-
if (aliased.type === AST_NODE_TYPES2.TSFunctionType || aliased.type === AST_NODE_TYPES2.TSConstructorType) {
|
|
594
|
-
functionAliases.add(declaration.id.name);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
602
|
const literals = aliased.type === AST_NODE_TYPES2.TSTypeLiteral ? [aliased] : aliased.type === AST_NODE_TYPES2.TSIntersectionType ? aliased.types.filter((part) => part.type === AST_NODE_TYPES2.TSTypeLiteral) : [];
|
|
598
603
|
if (literals.length === 0) return;
|
|
599
604
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -609,6 +614,28 @@ var fileTypeIndex = (program) => {
|
|
|
609
614
|
}
|
|
610
615
|
return { objects, functionAliases };
|
|
611
616
|
};
|
|
617
|
+
function localFunctionAliases(program) {
|
|
618
|
+
const names = /* @__PURE__ */ new Set();
|
|
619
|
+
const parents = /* @__PURE__ */ new Map();
|
|
620
|
+
for (const statement of program.body) {
|
|
621
|
+
const declaration = statement.type === AST_NODE_TYPES2.ExportNamedDeclaration ? statement.declaration : statement;
|
|
622
|
+
if (declaration?.type !== AST_NODE_TYPES2.TSTypeAliasDeclaration) continue;
|
|
623
|
+
const annotation = declaration.typeAnnotation;
|
|
624
|
+
if (annotation.type === AST_NODE_TYPES2.TSFunctionType || annotation.type === AST_NODE_TYPES2.TSConstructorType) names.add(declaration.id.name);
|
|
625
|
+
if (annotation.type === AST_NODE_TYPES2.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES2.Identifier) parents.set(declaration.id.name, annotation.typeName.name);
|
|
626
|
+
}
|
|
627
|
+
for (let pass = 0; pass < parents.size; pass += 1) {
|
|
628
|
+
let changed = false;
|
|
629
|
+
for (const [name, parent] of parents) {
|
|
630
|
+
if (names.has(parent) && !names.has(name)) {
|
|
631
|
+
names.add(name);
|
|
632
|
+
changed = true;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (!changed) break;
|
|
636
|
+
}
|
|
637
|
+
return names;
|
|
638
|
+
}
|
|
612
639
|
var typeParameterNames = (...declarations) => {
|
|
613
640
|
const names = /* @__PURE__ */ new Set();
|
|
614
641
|
for (const declaration of declarations) {
|
|
@@ -620,12 +647,15 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
620
647
|
const body2 = ctor.value.body;
|
|
621
648
|
const storedFieldsFrom = /* @__PURE__ */ new Map();
|
|
622
649
|
const storedMemberFieldsFrom = /* @__PURE__ */ new Map();
|
|
650
|
+
const constructedFieldNames = /* @__PURE__ */ new Set();
|
|
651
|
+
const localBindings2 = /* @__PURE__ */ new Set();
|
|
623
652
|
let constructedFields = 0;
|
|
624
653
|
if (body2 !== null && body2 !== void 0) scanStoredFields(body2);
|
|
625
654
|
function scanStoredFields(body3) {
|
|
626
655
|
const pending = [...body3.body];
|
|
627
656
|
function recordStoredField(current) {
|
|
628
657
|
if (current.type === AST_NODE_TYPES2.ArrowFunctionExpression || current.type === AST_NODE_TYPES2.FunctionExpression || current.type === AST_NODE_TYPES2.FunctionDeclaration || current.type === AST_NODE_TYPES2.ClassExpression || current.type === AST_NODE_TYPES2.ClassDeclaration) return;
|
|
658
|
+
if (current.type === AST_NODE_TYPES2.VariableDeclarator && current.id.type === AST_NODE_TYPES2.Identifier) localBindings2.add(current.id.name);
|
|
629
659
|
const expression = current.type === AST_NODE_TYPES2.ExpressionStatement ? current.expression : null;
|
|
630
660
|
const storedField = expression?.type === AST_NODE_TYPES2.AssignmentExpression && expression.left.type === AST_NODE_TYPES2.MemberExpression && expression.left.object.type === AST_NODE_TYPES2.ThisExpression ? staticMemberName(expression.left) : null;
|
|
631
661
|
if (expression?.type !== AST_NODE_TYPES2.AssignmentExpression || !STORAGE_ASSIGNMENT_OPERATORS.has(expression.operator) || expression.left.type !== AST_NODE_TYPES2.MemberExpression || expression.left.object.type !== AST_NODE_TYPES2.ThisExpression || storedField === null) {
|
|
@@ -636,6 +666,7 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
636
666
|
while (source.type === AST_NODE_TYPES2.TSNonNullExpression || source.type === AST_NODE_TYPES2.TSAsExpression || source.type === AST_NODE_TYPES2.TSSatisfiesExpression || source.type === AST_NODE_TYPES2.TSTypeAssertion) source = source.expression;
|
|
637
667
|
if (source.type === AST_NODE_TYPES2.NewExpression) {
|
|
638
668
|
constructedFields += 1;
|
|
669
|
+
constructedFieldNames.add(storedField);
|
|
639
670
|
} else if (source.type === AST_NODE_TYPES2.Identifier) {
|
|
640
671
|
const fields = storedFieldsFrom.get(source.name) ?? /* @__PURE__ */ new Set();
|
|
641
672
|
fields.add(storedField);
|
|
@@ -668,15 +699,19 @@ var readConstructor = (ctor, declared, typeParameters) => {
|
|
|
668
699
|
const collaborators = [];
|
|
669
700
|
function collectCollaborators(parameter) {
|
|
670
701
|
for (const reference of parameterCollaborators(parameter, declared, storedMemberFieldsFrom)) {
|
|
671
|
-
|
|
672
|
-
|
|
702
|
+
if (localBindings2.has(reference.name)) continue;
|
|
703
|
+
let fields = reference.fields;
|
|
704
|
+
if (parameter.type === AST_NODE_TYPES2.TSParameterProperty) fields = [reference.name];
|
|
705
|
+
else if (fields.length === 0) fields = [...storedFieldsFrom.get(reference.name) ?? []];
|
|
706
|
+
const injectedFields = fields.filter((field) => !constructedFieldNames.has(field));
|
|
707
|
+
if (injectedFields.length === 0) continue;
|
|
673
708
|
if (CONFIGISH_TYPE_RE.test(reference.typeName)) continue;
|
|
674
709
|
if (CONFIGISH_NAME_RE.test(reference.name)) continue;
|
|
675
710
|
if (typeParameters.has(reference.typeName)) continue;
|
|
676
711
|
if (BUILTIN_CONTAINER_TYPE_RE.test(reference.typeName)) continue;
|
|
677
712
|
if (VALUE_TYPE_RE.test(reference.typeName)) continue;
|
|
678
713
|
if (declared().functionAliases.has(reference.typeName)) continue;
|
|
679
|
-
collaborators.push({ ...reference, fields });
|
|
714
|
+
collaborators.push({ ...reference, fields: injectedFields });
|
|
680
715
|
}
|
|
681
716
|
}
|
|
682
717
|
for (const parameter of ctor.value.params) {
|
|
@@ -895,11 +930,7 @@ function declaredMemberName(member) {
|
|
|
895
930
|
function localInterfaceSurfaces(program) {
|
|
896
931
|
const interfaces = /* @__PURE__ */ new Map();
|
|
897
932
|
const parents = /* @__PURE__ */ new Map();
|
|
898
|
-
const functionAliases =
|
|
899
|
-
for (const statement of program.body) {
|
|
900
|
-
const declaration = statement.type === AST_NODE_TYPES2.ExportNamedDeclaration ? statement.declaration : statement;
|
|
901
|
-
if (declaration?.type === AST_NODE_TYPES2.TSTypeAliasDeclaration && (declaration.typeAnnotation.type === AST_NODE_TYPES2.TSFunctionType || declaration.typeAnnotation.type === AST_NODE_TYPES2.TSConstructorType)) functionAliases.add(declaration.id.name);
|
|
902
|
-
}
|
|
933
|
+
const functionAliases = localFunctionAliases(program);
|
|
903
934
|
function collectInterfaceSurface(statement) {
|
|
904
935
|
const declaration = statement.type === AST_NODE_TYPES2.ExportNamedDeclaration ? statement.declaration : statement;
|
|
905
936
|
if (declaration?.type === AST_NODE_TYPES2.TSTypeAliasDeclaration) {
|
|
@@ -911,6 +942,10 @@ function localInterfaceSurfaces(program) {
|
|
|
911
942
|
inherited.push(part.typeName.name);
|
|
912
943
|
continue;
|
|
913
944
|
}
|
|
945
|
+
if (part.type === AST_NODE_TYPES2.TSTypeReference) {
|
|
946
|
+
inherited.push("*");
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
914
949
|
if (part.type !== AST_NODE_TYPES2.TSTypeLiteral) continue;
|
|
915
950
|
addCallableMembers(part.members, callables2);
|
|
916
951
|
}
|
|
@@ -1074,30 +1109,18 @@ function createServiceOperationResolver(program) {
|
|
|
1074
1109
|
if (retained.size === 0) return [];
|
|
1075
1110
|
const callers = /* @__PURE__ */ new Map();
|
|
1076
1111
|
const active = /* @__PURE__ */ new Set();
|
|
1077
|
-
for (const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
if (current.type === AST_NODE_TYPES2.CallExpression) {
|
|
1088
|
-
if (retained.has(invokedInstanceField(current) ?? "")) active.add(name);
|
|
1089
|
-
if (current.callee.type === AST_NODE_TYPES2.MemberExpression && current.callee.object.type === AST_NODE_TYPES2.ThisExpression) {
|
|
1090
|
-
const callee = staticMemberName(current.callee);
|
|
1091
|
-
if (callee !== null) {
|
|
1092
|
-
const linked = callers.get(callee) ?? /* @__PURE__ */ new Set();
|
|
1093
|
-
linked.add(name);
|
|
1094
|
-
callers.set(callee, linked);
|
|
1095
|
-
}
|
|
1112
|
+
for (const [name, body2] of serviceOperationBodies(node)) {
|
|
1113
|
+
forEachServiceCall(body2, (call) => {
|
|
1114
|
+
if (retained.has(invokedInstanceField(call) ?? "")) active.add(name);
|
|
1115
|
+
if (call.callee.type === AST_NODE_TYPES2.MemberExpression && call.callee.object.type === AST_NODE_TYPES2.ThisExpression) {
|
|
1116
|
+
const callee = staticMemberName(call.callee);
|
|
1117
|
+
if (callee !== null) {
|
|
1118
|
+
const linked = callers.get(callee) ?? /* @__PURE__ */ new Set();
|
|
1119
|
+
linked.add(name);
|
|
1120
|
+
callers.set(callee, linked);
|
|
1096
1121
|
}
|
|
1097
1122
|
}
|
|
1098
|
-
|
|
1099
|
-
};
|
|
1100
|
-
for (const statement of member.value.body.body) visit(statement);
|
|
1123
|
+
});
|
|
1101
1124
|
}
|
|
1102
1125
|
const pending = [...active];
|
|
1103
1126
|
while (pending.length > 0) {
|
|
@@ -1112,38 +1135,127 @@ function createServiceOperationResolver(program) {
|
|
|
1112
1135
|
return publicMethodNames(node.body, declared().functionAliases).filter((name) => active.has(name));
|
|
1113
1136
|
};
|
|
1114
1137
|
}
|
|
1138
|
+
function serviceOperationBodies(node) {
|
|
1139
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
1140
|
+
for (const member of node.body.body) {
|
|
1141
|
+
if (member.type !== AST_NODE_TYPES2.MethodDefinition && member.type !== AST_NODE_TYPES2.PropertyDefinition) continue;
|
|
1142
|
+
if (member.static || member.decorators.length > 0) continue;
|
|
1143
|
+
if (member.type === AST_NODE_TYPES2.MethodDefinition && member.kind !== "method") continue;
|
|
1144
|
+
if (member.value?.type !== AST_NODE_TYPES2.FunctionExpression && member.value?.type !== AST_NODE_TYPES2.ArrowFunctionExpression) continue;
|
|
1145
|
+
const name = declaredMemberName(member);
|
|
1146
|
+
if (name !== null && member.value.body !== null) bodies.set(name, member.value.body);
|
|
1147
|
+
}
|
|
1148
|
+
return bodies;
|
|
1149
|
+
}
|
|
1150
|
+
function forEachServiceCall(node, onCall) {
|
|
1151
|
+
if (node.type === AST_NODE_TYPES2.FunctionDeclaration || node.type === AST_NODE_TYPES2.FunctionExpression || node.type === AST_NODE_TYPES2.ArrowFunctionExpression || node.type === AST_NODE_TYPES2.ClassDeclaration || node.type === AST_NODE_TYPES2.ClassExpression) return;
|
|
1152
|
+
if (node.type === AST_NODE_TYPES2.BlockStatement) {
|
|
1153
|
+
for (const statement of node.body) {
|
|
1154
|
+
forEachServiceCall(statement, onCall);
|
|
1155
|
+
if (terminatesOperation(statement)) break;
|
|
1156
|
+
}
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const branch = literalExecutionBranch(node);
|
|
1160
|
+
if (branch !== null) {
|
|
1161
|
+
forEachServiceCall(branch, onCall);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (node.type === AST_NODE_TYPES2.CallExpression) onCall(node);
|
|
1165
|
+
forEachOwnAstChild(node, (child) => {
|
|
1166
|
+
forEachServiceCall(child, onCall);
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
function literalExecutionBranch(node) {
|
|
1170
|
+
if (node.type === AST_NODE_TYPES2.WhileStatement && node.test.type === AST_NODE_TYPES2.Literal && node.test.value === false) return node.test;
|
|
1171
|
+
if ((node.type === AST_NODE_TYPES2.IfStatement || node.type === AST_NODE_TYPES2.ConditionalExpression) && node.test.type === AST_NODE_TYPES2.Literal && typeof node.test.value === "boolean") {
|
|
1172
|
+
return node.test.value ? node.consequent : node.alternate ?? node.test;
|
|
1173
|
+
}
|
|
1174
|
+
if (node.type !== AST_NODE_TYPES2.LogicalExpression || node.left.type !== AST_NODE_TYPES2.Literal || typeof node.left.value !== "boolean") return null;
|
|
1175
|
+
if (node.operator === "&&") return node.left.value ? node.right : node.left;
|
|
1176
|
+
if (node.operator === "||") return node.left.value ? node.left : node.right;
|
|
1177
|
+
return node.left;
|
|
1178
|
+
}
|
|
1179
|
+
function terminatesOperation(node) {
|
|
1180
|
+
if (node.type === AST_NODE_TYPES2.ReturnStatement || node.type === AST_NODE_TYPES2.ThrowStatement) return true;
|
|
1181
|
+
if (node.type === AST_NODE_TYPES2.BlockStatement) return node.body.some(terminatesOperation);
|
|
1182
|
+
if (node.type !== AST_NODE_TYPES2.IfStatement) return false;
|
|
1183
|
+
const branch = literalExecutionBranch(node);
|
|
1184
|
+
if (branch !== null) return terminatesOperation(branch);
|
|
1185
|
+
return node.alternate !== null && terminatesOperation(node.consequent) && terminatesOperation(node.alternate);
|
|
1186
|
+
}
|
|
1115
1187
|
function hasDeclaredServiceContract(node, operations, program) {
|
|
1116
|
-
const classes = localClassAbstractness(program);
|
|
1117
1188
|
const interfaces = localInterfaceSurfaces(program);
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
const
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1189
|
+
const classes = localClassDeclarations(program);
|
|
1190
|
+
const functionAliases = fileTypeIndex(program).functionAliases;
|
|
1191
|
+
const covered2 = /* @__PURE__ */ new Set();
|
|
1192
|
+
const pending = [node];
|
|
1193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1194
|
+
function addImplementedContracts(current) {
|
|
1195
|
+
for (const implementation of current.implements) {
|
|
1196
|
+
if (implementation.expression.type !== AST_NODE_TYPES2.Identifier) return false;
|
|
1197
|
+
const name = implementation.expression.name;
|
|
1198
|
+
const surface = interfaces.get(name);
|
|
1199
|
+
const base = classes.get(name);
|
|
1200
|
+
if (surface === void 0 && base === void 0) return false;
|
|
1201
|
+
if (surface?.has("*")) return false;
|
|
1202
|
+
for (const method of surface ?? []) covered2.add(method);
|
|
1203
|
+
if (base?.abstract) pending.push(base);
|
|
1204
|
+
}
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
while (pending.length > 0) {
|
|
1208
|
+
const current = pending.pop();
|
|
1209
|
+
if (current === void 0 || seen.has(current)) continue;
|
|
1210
|
+
seen.add(current);
|
|
1211
|
+
if (current.decorators.length > 0) return null;
|
|
1212
|
+
if (current.abstract) {
|
|
1213
|
+
for (const method of abstractContractOperations(current, functionAliases)) covered2.add(method);
|
|
1214
|
+
}
|
|
1215
|
+
if (!addImplementedContracts(current)) return null;
|
|
1216
|
+
if (current.superClass === null) continue;
|
|
1217
|
+
if (current.superClass.type !== AST_NODE_TYPES2.Identifier) return null;
|
|
1218
|
+
const base = classes.get(current.superClass.name);
|
|
1219
|
+
if (base === void 0) return null;
|
|
1220
|
+
pending.push(base);
|
|
1221
|
+
}
|
|
1222
|
+
return operations.every((name) => covered2.has(name));
|
|
1223
|
+
}
|
|
1224
|
+
function localClassDeclarations(program) {
|
|
1225
|
+
const classes = /* @__PURE__ */ new Map();
|
|
1226
|
+
for (const statement of program.body) {
|
|
1227
|
+
const declaration = statement.type === AST_NODE_TYPES2.ExportNamedDeclaration || statement.type === AST_NODE_TYPES2.ExportDefaultDeclaration ? statement.declaration : statement;
|
|
1228
|
+
if (declaration?.type === AST_NODE_TYPES2.ClassDeclaration && declaration.id !== null) classes.set(declaration.id.name, declaration);
|
|
1229
|
+
}
|
|
1230
|
+
return classes;
|
|
1231
|
+
}
|
|
1232
|
+
function abstractContractOperations(node, functionAliases) {
|
|
1233
|
+
const methods = new Set(publicMethodNames(node.body, functionAliases));
|
|
1234
|
+
for (const member of node.body.body) {
|
|
1235
|
+
if (member.type !== AST_NODE_TYPES2.TSAbstractMethodDefinition && member.type !== AST_NODE_TYPES2.TSAbstractPropertyDefinition) continue;
|
|
1236
|
+
if (member.static || member.accessibility === "private" || member.accessibility === "protected") continue;
|
|
1237
|
+
if (member.type === AST_NODE_TYPES2.TSAbstractPropertyDefinition) {
|
|
1238
|
+
const annotation = member.typeAnnotation?.typeAnnotation;
|
|
1239
|
+
if (annotation?.type !== AST_NODE_TYPES2.TSFunctionType && !(annotation?.type === AST_NODE_TYPES2.TSTypeReference && annotation.typeName.type === AST_NODE_TYPES2.Identifier && functionAliases.has(annotation.typeName.name))) continue;
|
|
1240
|
+
}
|
|
1241
|
+
const name = declaredMemberName(member);
|
|
1242
|
+
if (name !== null) methods.add(name);
|
|
1243
|
+
}
|
|
1244
|
+
return methods;
|
|
1134
1245
|
}
|
|
1135
1246
|
|
|
1136
1247
|
// src/rules/require-explicit-service-contract.ts
|
|
1137
1248
|
var REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION = {
|
|
1138
1249
|
defaultLevel: "warning",
|
|
1139
1250
|
summary: "Require explicit contracts for classes that invoke retained collaborators.",
|
|
1140
|
-
rationale: "A one-operation service can orchestrate a collaborator without declaring the contract that
|
|
1141
|
-
remediation: "
|
|
1251
|
+
rationale: "A one-operation service can orchestrate a collaborator without declaring the contract that callers consume and implementations and fakes satisfy.",
|
|
1252
|
+
remediation: "Explicitly implement a focused interface or extend an abstract class covering the service operations. Export the contract only when its consumers need it.",
|
|
1142
1253
|
category: "architecture",
|
|
1143
1254
|
autofix: "none",
|
|
1144
1255
|
limitations: [
|
|
1145
1256
|
"The rule checks service, store, and provider class declarations whose constructor stores a typed behavioral collaborator and whose public method reaches that collaborator.",
|
|
1146
|
-
"
|
|
1257
|
+
"Existing local interfaces, type aliases, and inherited contracts can cover the operations; the compiler owns signature compatibility.",
|
|
1258
|
+
"Imported or dynamic base classes with unavailable declarations and decorated classes or operations are unresolved and do not produce a finding.",
|
|
1147
1259
|
"Generated files and JavaScript without implements syntax are excluded."
|
|
1148
1260
|
],
|
|
1149
1261
|
examples: [
|
|
@@ -1165,7 +1277,7 @@ var REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION = {
|
|
|
1165
1277
|
outcome: "no-match",
|
|
1166
1278
|
files: [{
|
|
1167
1279
|
path: "src/service.ts",
|
|
1168
|
-
source: "
|
|
1280
|
+
source: "interface Runner { run(): void } class OrchestratorService implements Runner { constructor(private readonly worker: Worker) {} run(): void { this.worker.run(); } }"
|
|
1169
1281
|
}],
|
|
1170
1282
|
focusPath: "src/service.ts",
|
|
1171
1283
|
expectedCount: 0,
|
|
@@ -1181,7 +1293,7 @@ var require_explicit_service_contract_default = createRule({
|
|
|
1181
1293
|
docs: { description: REQUIRE_EXPLICIT_SERVICE_CONTRACT_DOCUMENTATION.summary },
|
|
1182
1294
|
schema: [],
|
|
1183
1295
|
messages: {
|
|
1184
|
-
requireExplicitServiceContract: "`{{name}}` invokes a retained collaborator from {{operations}} without
|
|
1296
|
+
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."
|
|
1185
1297
|
}
|
|
1186
1298
|
},
|
|
1187
1299
|
defaultOptions: [],
|
|
@@ -1192,7 +1304,7 @@ var require_explicit_service_contract_default = createRule({
|
|
|
1192
1304
|
const isExportedServiceClass = createExportedServiceClassResolver(program);
|
|
1193
1305
|
return {
|
|
1194
1306
|
ClassDeclaration(node) {
|
|
1195
|
-
if (node.id === null || node.abstract || !isServiceRole(node.id.name, context.filename)) return;
|
|
1307
|
+
if (node.id === null || node.abstract || node.decorators.length > 0 || !isServiceRole(node.id.name, context.filename)) return;
|
|
1196
1308
|
if (isExportedServiceClass(node) && !isTestFile(context.filename) && !isStoryFile(context.filename) && !isScriptFile(context.filename)) return;
|
|
1197
1309
|
const operations = operationsFor(node);
|
|
1198
1310
|
if (operations.length === 0 || hasDeclaredServiceContract(node, operations, program) !== false) return;
|
|
@@ -7031,7 +7143,7 @@ var RESHAPING_METHODS = /* @__PURE__ */ new Set([
|
|
|
7031
7143
|
function importedName2(specifier) {
|
|
7032
7144
|
return specifier.imported.type === AST_NODE_TYPES34.Identifier ? specifier.imported.name : typeof specifier.imported.value === "string" ? specifier.imported.value : null;
|
|
7033
7145
|
}
|
|
7034
|
-
function
|
|
7146
|
+
function memberName2(node) {
|
|
7035
7147
|
if (!node.computed && node.property.type === AST_NODE_TYPES34.Identifier) {
|
|
7036
7148
|
return node.property.name;
|
|
7037
7149
|
}
|
|
@@ -7116,7 +7228,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7116
7228
|
if (callee.type !== AST_NODE_TYPES34.MemberExpression || callee.object.type !== AST_NODE_TYPES34.CallExpression) {
|
|
7117
7229
|
return null;
|
|
7118
7230
|
}
|
|
7119
|
-
const method =
|
|
7231
|
+
const method = memberName2(callee);
|
|
7120
7232
|
if (method === null) return null;
|
|
7121
7233
|
calls.push({ method, node: current });
|
|
7122
7234
|
current = callee.object;
|
|
@@ -7130,7 +7242,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7130
7242
|
if (callee.type !== AST_NODE_TYPES34.MemberExpression || callee.object.type !== AST_NODE_TYPES34.Identifier || !namespaces.has(callee.object.name) || !resolvesToTrackedImport(callee.object)) {
|
|
7131
7243
|
return null;
|
|
7132
7244
|
}
|
|
7133
|
-
const name =
|
|
7245
|
+
const name = memberName2(callee);
|
|
7134
7246
|
return name !== null && KINDS.has(name) ? name : null;
|
|
7135
7247
|
}
|
|
7136
7248
|
function isInsideReshapingCall(node) {
|
|
@@ -7139,7 +7251,7 @@ var no_impossible_zod_literal_bounds_default = createRule({
|
|
|
7139
7251
|
while (parent !== void 0 && parent.type !== AST_NODE_TYPES34.Program) {
|
|
7140
7252
|
if (parent.type === AST_NODE_TYPES34.CallExpression) {
|
|
7141
7253
|
const callee = parent.callee;
|
|
7142
|
-
if (callee.type === AST_NODE_TYPES34.MemberExpression && RESHAPING_METHODS.has(
|
|
7254
|
+
if (callee.type === AST_NODE_TYPES34.MemberExpression && RESHAPING_METHODS.has(memberName2(callee) ?? "") && (callee.object.type === AST_NODE_TYPES34.CallExpression || callee.object.type === AST_NODE_TYPES34.Identifier && namespaces.has(callee.object.name) && resolvesToTrackedImport(callee.object))) {
|
|
7143
7255
|
return true;
|
|
7144
7256
|
}
|
|
7145
7257
|
if (callee.type === AST_NODE_TYPES34.Identifier && preprocessors.has(callee.name) && resolvesToTrackedImport(callee)) {
|
|
@@ -7405,7 +7517,7 @@ var NO_REDUNDANT_OPTIONAL_ARRAY_DEFAULT_DOCUMENTATION = {
|
|
|
7405
7517
|
function importedName3(specifier) {
|
|
7406
7518
|
return specifier.imported.type === AST_NODE_TYPES35.Identifier ? specifier.imported.name : typeof specifier.imported.value === "string" ? specifier.imported.value : null;
|
|
7407
7519
|
}
|
|
7408
|
-
function
|
|
7520
|
+
function memberName3(node) {
|
|
7409
7521
|
if (!node.computed && node.property.type === AST_NODE_TYPES35.Identifier) {
|
|
7410
7522
|
return node.property.name;
|
|
7411
7523
|
}
|
|
@@ -7457,7 +7569,7 @@ var no_redundant_optional_array_default_default = createRule({
|
|
|
7457
7569
|
return arrayConstructors.has(callee.name) && resolvesToTrackedImport(callee);
|
|
7458
7570
|
}
|
|
7459
7571
|
if (callee.type !== AST_NODE_TYPES35.MemberExpression) return false;
|
|
7460
|
-
const method =
|
|
7572
|
+
const method = memberName3(callee);
|
|
7461
7573
|
if (method === "array" && (callee.object.type === AST_NODE_TYPES35.Identifier ? namespaces.has(callee.object.name) && resolvesToTrackedImport(callee.object) : isZodSchemaExpression(callee.object))) {
|
|
7462
7574
|
return true;
|
|
7463
7575
|
}
|
|
@@ -7498,12 +7610,12 @@ var no_redundant_optional_array_default_default = createRule({
|
|
|
7498
7610
|
},
|
|
7499
7611
|
CallExpression(node) {
|
|
7500
7612
|
const defaultCallee = node.callee;
|
|
7501
|
-
if (defaultCallee.type !== AST_NODE_TYPES35.MemberExpression ||
|
|
7613
|
+
if (defaultCallee.type !== AST_NODE_TYPES35.MemberExpression || memberName3(defaultCallee) !== "default" || defaultCallee.object.type !== AST_NODE_TYPES35.CallExpression) {
|
|
7502
7614
|
return;
|
|
7503
7615
|
}
|
|
7504
7616
|
const optionalCall = defaultCallee.object;
|
|
7505
7617
|
const optionalCallee = optionalCall.callee;
|
|
7506
|
-
if (optionalCallee.type !== AST_NODE_TYPES35.MemberExpression ||
|
|
7618
|
+
if (optionalCallee.type !== AST_NODE_TYPES35.MemberExpression || memberName3(optionalCallee) !== "optional" || optionalCall.arguments.length !== 0 || !isArraySchemaExpression(optionalCallee.object) || sourceCode.getCommentsInside(optionalCall).length > 0) {
|
|
7507
7619
|
return;
|
|
7508
7620
|
}
|
|
7509
7621
|
context.report({
|
|
@@ -7527,12 +7639,12 @@ import * as ts8 from "typescript";
|
|
|
7527
7639
|
import {
|
|
7528
7640
|
AST_NODE_TYPES as AST_NODE_TYPES36
|
|
7529
7641
|
} from "@typescript-eslint/utils";
|
|
7530
|
-
function
|
|
7642
|
+
function memberName4(member) {
|
|
7531
7643
|
return !member.computed && member.key.type === AST_NODE_TYPES36.Identifier ? member.key.name : null;
|
|
7532
7644
|
}
|
|
7533
7645
|
function convertibleMemberName(member) {
|
|
7534
7646
|
if (member.type !== AST_NODE_TYPES36.MethodDefinition && member.type !== AST_NODE_TYPES36.PropertyDefinition && member.type !== AST_NODE_TYPES36.AccessorProperty) return null;
|
|
7535
|
-
return
|
|
7647
|
+
return memberName4(member);
|
|
7536
7648
|
}
|
|
7537
7649
|
|
|
7538
7650
|
// src/rules/interface-contract-members-private.ts
|
|
@@ -8134,7 +8246,7 @@ var LOWER_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
|
8134
8246
|
function propertyName2(node) {
|
|
8135
8247
|
return node.type === AST_NODE_TYPES40.Identifier ? node.name : null;
|
|
8136
8248
|
}
|
|
8137
|
-
function
|
|
8249
|
+
function memberName5(node) {
|
|
8138
8250
|
let current = node;
|
|
8139
8251
|
while (current.type === AST_NODE_TYPES40.TSAsExpression || current.type === AST_NODE_TYPES40.TSNonNullExpression || current.type === AST_NODE_TYPES40.TSTypeAssertion) {
|
|
8140
8252
|
current = current.expression;
|
|
@@ -8198,7 +8310,7 @@ var no_bespoke_api_case_conversion_default = createRule({
|
|
|
8198
8310
|
Property(node) {
|
|
8199
8311
|
if (node.computed || node.method || node.shorthand) return;
|
|
8200
8312
|
const key = propertyName2(node.key);
|
|
8201
|
-
const value =
|
|
8313
|
+
const value = memberName5(node.value);
|
|
8202
8314
|
if (key === null || value === null || !isDirectCaseTranslation(key, value)) return;
|
|
8203
8315
|
if (!hasApiReceiver(node.value)) return;
|
|
8204
8316
|
const wireName = SNAKE_CASE_RE2.test(key) ? key : value;
|
|
@@ -8971,9 +9083,9 @@ var no_positional_tuple_return_default = createRule({
|
|
|
8971
9083
|
TSMethodSignature(node) {
|
|
8972
9084
|
const owner = owningInterface(node);
|
|
8973
9085
|
const alias = owningTypeAlias(node);
|
|
8974
|
-
const
|
|
8975
|
-
if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) || node.returnType === void 0 ||
|
|
8976
|
-
report2(node.returnType.typeAnnotation, `${owner?.id.name ?? alias?.id.name ?? "type"}.${
|
|
9086
|
+
const memberName9 = staticMemberName4(node.key);
|
|
9087
|
+
if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) || node.returnType === void 0 || memberName9 === null) return;
|
|
9088
|
+
report2(node.returnType.typeAnnotation, `${owner?.id.name ?? alias?.id.name ?? "type"}.${memberName9}`);
|
|
8977
9089
|
},
|
|
8978
9090
|
TSTypeAliasDeclaration(node) {
|
|
8979
9091
|
if (!typeExports.has(node.id.name)) return;
|
|
@@ -8984,11 +9096,11 @@ var no_positional_tuple_return_default = createRule({
|
|
|
8984
9096
|
const owner = owningInterface(node);
|
|
8985
9097
|
const alias = owningTypeAlias(node);
|
|
8986
9098
|
const annotation = node.typeAnnotation?.typeAnnotation;
|
|
8987
|
-
const
|
|
8988
|
-
if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) ||
|
|
9099
|
+
const memberName9 = staticMemberName4(node.key);
|
|
9100
|
+
if ((owner === null || !isExportedInterface(owner, typeExports)) && (alias === null || !typeExports.has(alias.id.name)) || memberName9 === null || annotation === void 0) return;
|
|
8989
9101
|
const returnType = callableReturnType(annotation, aliases);
|
|
8990
9102
|
if (returnType !== null) {
|
|
8991
|
-
report2(returnType, `${owner?.id.name ?? alias?.id.name ?? "type"}.${
|
|
9103
|
+
report2(returnType, `${owner?.id.name ?? alias?.id.name ?? "type"}.${memberName9}`);
|
|
8992
9104
|
}
|
|
8993
9105
|
},
|
|
8994
9106
|
PropertyDefinition(node) {
|
|
@@ -16975,7 +17087,7 @@ var PREFER_NODE_CRYPTO_HASH_DOCUMENTATION = {
|
|
|
16975
17087
|
{ 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 }
|
|
16976
17088
|
]
|
|
16977
17089
|
};
|
|
16978
|
-
function
|
|
17090
|
+
function memberName6(node) {
|
|
16979
17091
|
if (!node.computed && node.property.type === AST_NODE_TYPES82.Identifier) return node.property.name;
|
|
16980
17092
|
if (node.computed && node.property.type === AST_NODE_TYPES82.Literal && typeof node.property.value === "string") {
|
|
16981
17093
|
return node.property.value;
|
|
@@ -16991,7 +17103,7 @@ function isCryptoLoader(node, resolve2) {
|
|
|
16991
17103
|
if (node.callee.type === AST_NODE_TYPES82.Identifier) {
|
|
16992
17104
|
return node.callee.name === "require" && isUnshadowedBuiltinIdentifier(node.callee, resolve2);
|
|
16993
17105
|
}
|
|
16994
|
-
return node.callee.type === AST_NODE_TYPES82.MemberExpression && node.callee.object.type === AST_NODE_TYPES82.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object, resolve2) &&
|
|
17106
|
+
return node.callee.type === AST_NODE_TYPES82.MemberExpression && node.callee.object.type === AST_NODE_TYPES82.Identifier && node.callee.object.name === "process" && isUnshadowedBuiltinIdentifier(node.callee.object, resolve2) && memberName6(node.callee) === "getBuiltinModule";
|
|
16995
17107
|
}
|
|
16996
17108
|
function isCryptoSpecifier(node) {
|
|
16997
17109
|
return node.type === AST_NODE_TYPES82.Literal && (node.value === "crypto" || node.value === "node:crypto");
|
|
@@ -17073,14 +17185,14 @@ function importedName6(node) {
|
|
|
17073
17185
|
return node.type === AST_NODE_TYPES82.Identifier ? node.name : node.value;
|
|
17074
17186
|
}
|
|
17075
17187
|
function isMemberCall(node, name) {
|
|
17076
|
-
return node.callee.type === AST_NODE_TYPES82.MemberExpression &&
|
|
17188
|
+
return node.callee.type === AST_NODE_TYPES82.MemberExpression && memberName6(node.callee) === name;
|
|
17077
17189
|
}
|
|
17078
17190
|
function isCreateHashCall(node, directBindings, namespaceBindings, resolve2) {
|
|
17079
17191
|
if (node.callee.type === AST_NODE_TYPES82.Identifier) {
|
|
17080
17192
|
const variable2 = resolve2(node.callee);
|
|
17081
17193
|
return variable2 !== null && directBindings.has(variable2);
|
|
17082
17194
|
}
|
|
17083
|
-
if (node.callee.type !== AST_NODE_TYPES82.MemberExpression ||
|
|
17195
|
+
if (node.callee.type !== AST_NODE_TYPES82.MemberExpression || memberName6(node.callee) !== "createHash") {
|
|
17084
17196
|
return false;
|
|
17085
17197
|
}
|
|
17086
17198
|
if (isCryptoLoader(node.callee.object, resolve2)) return true;
|
|
@@ -17108,7 +17220,7 @@ var PREFER_NODE_FS_PROMISES_DOCUMENTATION = {
|
|
|
17108
17220
|
{ 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 }
|
|
17109
17221
|
]
|
|
17110
17222
|
};
|
|
17111
|
-
function
|
|
17223
|
+
function memberName7(node) {
|
|
17112
17224
|
if (!node.computed && node.property.type === AST_NODE_TYPES83.Identifier) return node.property.name;
|
|
17113
17225
|
if (node.computed && node.property.type === AST_NODE_TYPES83.Literal && typeof node.property.value === "string") {
|
|
17114
17226
|
return node.property.value;
|
|
@@ -17125,7 +17237,7 @@ function isFsLoader(node, isGlobal) {
|
|
|
17125
17237
|
const [argument] = expression.arguments;
|
|
17126
17238
|
if (argument === void 0 || argument.type === AST_NODE_TYPES83.SpreadElement || !isFsSpecifier(argument)) return false;
|
|
17127
17239
|
if (expression.callee.type === AST_NODE_TYPES83.Identifier) return expression.callee.name === "require" && isGlobal(expression.callee);
|
|
17128
|
-
return expression.callee.type === AST_NODE_TYPES83.MemberExpression && expression.callee.object.type === AST_NODE_TYPES83.Identifier && expression.callee.object.name === "process" && isGlobal(expression.callee.object) &&
|
|
17240
|
+
return expression.callee.type === AST_NODE_TYPES83.MemberExpression && expression.callee.object.type === AST_NODE_TYPES83.Identifier && expression.callee.object.name === "process" && isGlobal(expression.callee.object) && memberName7(expression.callee) === "getBuiltinModule";
|
|
17129
17241
|
}
|
|
17130
17242
|
function isFsSpecifier(node) {
|
|
17131
17243
|
return node.type === AST_NODE_TYPES83.Literal && (node.value === "node:fs" || node.value === "fs");
|
|
@@ -17206,7 +17318,7 @@ var prefer_node_fs_promises_default = createRule({
|
|
|
17206
17318
|
}
|
|
17207
17319
|
},
|
|
17208
17320
|
MemberExpression(node) {
|
|
17209
|
-
const name =
|
|
17321
|
+
const name = memberName7(node);
|
|
17210
17322
|
if (name?.endsWith("Sync") !== true) return;
|
|
17211
17323
|
const object = unwrapAwait2(node.object);
|
|
17212
17324
|
if (object.type === AST_NODE_TYPES83.Identifier && isNamespace(object) || isFsLoader(object, isGlobal)) {
|
|
@@ -17631,15 +17743,16 @@ import {
|
|
|
17631
17743
|
} from "@typescript-eslint/utils";
|
|
17632
17744
|
import * as ts11 from "typescript";
|
|
17633
17745
|
var PREFER_AWAIT_IN_ASYNC_RETURN_DOCUMENTATION = {
|
|
17634
|
-
summary: "Prefer explicit `await`
|
|
17635
|
-
rationale: "
|
|
17746
|
+
summary: "Prefer explicit `await` for proven Promise `.then` calls; the default scope checks direct async returns.",
|
|
17747
|
+
rationale: "Promise callback chains mix explicit async control flow with nested callbacks, making sequencing and failure handling harder to read.",
|
|
17636
17748
|
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.",
|
|
17637
17749
|
category: "maintainability",
|
|
17638
17750
|
since: "15.6.3",
|
|
17639
17751
|
limitations: [
|
|
17640
|
-
"
|
|
17641
|
-
"The receiver must be proven Promise-like by TypeScript; an earlier chain can still produce that receiver.
|
|
17642
|
-
"
|
|
17752
|
+
"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.",
|
|
17753
|
+
"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.",
|
|
17754
|
+
"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.",
|
|
17755
|
+
"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."
|
|
17643
17756
|
],
|
|
17644
17757
|
examples: [
|
|
17645
17758
|
{
|
|
@@ -17696,15 +17809,28 @@ function promiseThenReceiver(node) {
|
|
|
17696
17809
|
}
|
|
17697
17810
|
return callee.object;
|
|
17698
17811
|
}
|
|
17812
|
+
function directThenReceiver(node) {
|
|
17813
|
+
const callee = node.callee;
|
|
17814
|
+
if (callee.type !== AST_NODE_TYPES86.MemberExpression) return null;
|
|
17815
|
+
const property = callee.property;
|
|
17816
|
+
const isThen = callee.computed ? property.type === AST_NODE_TYPES86.Literal && property.value === "then" || property.type === AST_NODE_TYPES86.TemplateLiteral && property.expressions.length === 0 && property.quasis[0]?.value.cooked === "then" : property.type === AST_NODE_TYPES86.Identifier && property.name === "then";
|
|
17817
|
+
return isThen ? callee.object : null;
|
|
17818
|
+
}
|
|
17699
17819
|
function isProvenPromiseLike(node, services) {
|
|
17700
17820
|
const checker = services.program.getTypeChecker();
|
|
17701
17821
|
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
17702
|
-
const receiverType = checker.getTypeAtLocation(tsNode);
|
|
17703
|
-
|
|
17822
|
+
const receiverType = checker.getNonNullableType(checker.getTypeAtLocation(tsNode));
|
|
17823
|
+
const members = receiverType.isUnion() ? receiverType.types : [receiverType];
|
|
17824
|
+
return members.every((member) => isBuiltInPromiseType(member, services));
|
|
17825
|
+
}
|
|
17826
|
+
function isBuiltInPromiseType(type, services) {
|
|
17827
|
+
if ((type.flags & (ts11.TypeFlags.Any | ts11.TypeFlags.Unknown | ts11.TypeFlags.Never)) !== 0) {
|
|
17704
17828
|
return false;
|
|
17705
17829
|
}
|
|
17706
|
-
const thenSymbol =
|
|
17707
|
-
const
|
|
17830
|
+
const thenSymbol = services.program.getTypeChecker().getPropertyOfType(type, "then");
|
|
17831
|
+
const declarations = thenSymbol?.declarations;
|
|
17832
|
+
if (declarations === void 0 || declarations.length === 0) return false;
|
|
17833
|
+
return declarations.every(
|
|
17708
17834
|
(declaration) => {
|
|
17709
17835
|
let owner = declaration.parent;
|
|
17710
17836
|
while (owner !== void 0 && !ts11.isInterfaceDeclaration(owner)) {
|
|
@@ -17712,8 +17838,7 @@ function isProvenPromiseLike(node, services) {
|
|
|
17712
17838
|
}
|
|
17713
17839
|
return owner !== void 0 && (owner.name.text === "Promise" || owner.name.text === "PromiseLike") && services.program.isSourceFileDefaultLibrary(owner.getSourceFile());
|
|
17714
17840
|
}
|
|
17715
|
-
)
|
|
17716
|
-
return hasBuiltInPromiseDeclaration;
|
|
17841
|
+
);
|
|
17717
17842
|
}
|
|
17718
17843
|
var prefer_await_in_async_return_default = createRule({
|
|
17719
17844
|
name: "prefer-await-in-async-return",
|
|
@@ -17721,15 +17846,20 @@ var prefer_await_in_async_return_default = createRule({
|
|
|
17721
17846
|
meta: {
|
|
17722
17847
|
type: "suggestion",
|
|
17723
17848
|
docs: {
|
|
17724
|
-
description: "Prefer explicit `await`
|
|
17849
|
+
description: "Prefer explicit `await` for proven Promise `.then` calls; the default scope checks direct async returns."
|
|
17725
17850
|
},
|
|
17726
|
-
schema: [
|
|
17851
|
+
schema: [{
|
|
17852
|
+
type: "object",
|
|
17853
|
+
properties: { scope: { type: "string", enum: ["async-return", "all-promise-calls"] } },
|
|
17854
|
+
additionalProperties: false
|
|
17855
|
+
}],
|
|
17727
17856
|
messages: {
|
|
17728
|
-
preferAwait: "This async function directly returns a Promise `.then` transform. Await the Promise, then return the transformed value with explicit async control flow."
|
|
17857
|
+
preferAwait: "This async function directly returns a Promise `.then` transform. Await the Promise, then return the transformed value with explicit async control flow.",
|
|
17858
|
+
preferAwaitCall: "This call uses built-in Promise.then(). Use async/await, preserving rejection handling and observable scheduling when rewriting."
|
|
17729
17859
|
}
|
|
17730
17860
|
},
|
|
17731
|
-
defaultOptions: [],
|
|
17732
|
-
create(context) {
|
|
17861
|
+
defaultOptions: [{ scope: "async-return" }],
|
|
17862
|
+
create(context, [options]) {
|
|
17733
17863
|
let services;
|
|
17734
17864
|
try {
|
|
17735
17865
|
services = ESLintUtils13.getParserServices(context);
|
|
@@ -17762,6 +17892,13 @@ var prefer_await_in_async_return_default = createRule({
|
|
|
17762
17892
|
}
|
|
17763
17893
|
},
|
|
17764
17894
|
CallExpression(node) {
|
|
17895
|
+
if (options.scope === "all-promise-calls") {
|
|
17896
|
+
const receiver2 = directThenReceiver(node);
|
|
17897
|
+
if (receiver2 !== null && isProvenPromiseLike(receiver2, services)) {
|
|
17898
|
+
context.report({ node, messageId: "preferAwaitCall" });
|
|
17899
|
+
}
|
|
17900
|
+
return;
|
|
17901
|
+
}
|
|
17765
17902
|
const owner = directAsyncReturnOwner(node);
|
|
17766
17903
|
if (owner === null || isFrameworkLoaderCallback(owner)) return;
|
|
17767
17904
|
const receiver = promiseThenReceiver(node);
|
|
@@ -21380,7 +21517,7 @@ var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
|
|
|
21380
21517
|
}
|
|
21381
21518
|
]
|
|
21382
21519
|
};
|
|
21383
|
-
function
|
|
21520
|
+
function memberName8(node) {
|
|
21384
21521
|
if (!node.computed && node.property.type === AST_NODE_TYPES98.Identifier)
|
|
21385
21522
|
return node.property.name;
|
|
21386
21523
|
if (node.computed && node.property.type === AST_NODE_TYPES98.Literal && typeof node.property.value === "string")
|
|
@@ -21398,7 +21535,7 @@ function databaseReceiver(node) {
|
|
|
21398
21535
|
if (node.type === AST_NODE_TYPES98.Identifier)
|
|
21399
21536
|
return DATABASE_NAMES.test(node.name);
|
|
21400
21537
|
if (node.type !== AST_NODE_TYPES98.MemberExpression) return false;
|
|
21401
|
-
const name =
|
|
21538
|
+
const name = memberName8(node);
|
|
21402
21539
|
return name === "DB" || name !== null && DATABASE_NAMES.test(name);
|
|
21403
21540
|
}
|
|
21404
21541
|
function databaseRootMember(node) {
|
|
@@ -21419,7 +21556,7 @@ function databaseRootMember(node) {
|
|
|
21419
21556
|
}
|
|
21420
21557
|
if (current.type === AST_NODE_TYPES98.MemberExpression) {
|
|
21421
21558
|
if (current.object.type === AST_NODE_TYPES98.ThisExpression)
|
|
21422
|
-
return
|
|
21559
|
+
return memberName8(current);
|
|
21423
21560
|
current = current.object;
|
|
21424
21561
|
continue;
|
|
21425
21562
|
}
|
|
@@ -21443,7 +21580,7 @@ function expressionHasDatabaseMarker(node) {
|
|
|
21443
21580
|
continue;
|
|
21444
21581
|
}
|
|
21445
21582
|
if (current.type === AST_NODE_TYPES98.MemberExpression) {
|
|
21446
|
-
const name =
|
|
21583
|
+
const name = memberName8(current);
|
|
21447
21584
|
if (name === "DB" || name !== null && DATABASE_NAMES.test(name))
|
|
21448
21585
|
return true;
|
|
21449
21586
|
current = current.object;
|
|
@@ -21465,7 +21602,7 @@ function expressionCallsBuilder(node) {
|
|
|
21465
21602
|
}
|
|
21466
21603
|
if (current.type === AST_NODE_TYPES98.CallExpression) {
|
|
21467
21604
|
if (current.callee.type !== AST_NODE_TYPES98.MemberExpression) return false;
|
|
21468
|
-
const name =
|
|
21605
|
+
const name = memberName8(current.callee);
|
|
21469
21606
|
if (name !== null && BUILDER_METHODS.has(name)) return true;
|
|
21470
21607
|
current = current.callee.object;
|
|
21471
21608
|
continue;
|
|
@@ -21516,7 +21653,7 @@ function thisRootMember(node) {
|
|
|
21516
21653
|
let current = node;
|
|
21517
21654
|
let root = null;
|
|
21518
21655
|
while (current.type === AST_NODE_TYPES98.MemberExpression) {
|
|
21519
|
-
const name =
|
|
21656
|
+
const name = memberName8(current);
|
|
21520
21657
|
if (name === null) return null;
|
|
21521
21658
|
root = name;
|
|
21522
21659
|
current = current.object;
|
|
@@ -21570,7 +21707,7 @@ var require_sql_access_class_default = createRule({
|
|
|
21570
21707
|
CallExpression(node) {
|
|
21571
21708
|
if (node.callee.type !== AST_NODE_TYPES98.MemberExpression)
|
|
21572
21709
|
return;
|
|
21573
|
-
const method =
|
|
21710
|
+
const method = memberName8(node.callee);
|
|
21574
21711
|
if (knownNonDatabase(node.callee.object)) return;
|
|
21575
21712
|
if (method === null || !isDatabaseOperation(method, node.callee.object))
|
|
21576
21713
|
return;
|
|
@@ -23203,10 +23340,11 @@ import { readFileSync as readFileSync3 } from "fs";
|
|
|
23203
23340
|
import { dirname as dirname3, join as join3, relative as relative2 } from "path";
|
|
23204
23341
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]);
|
|
23205
23342
|
var NEXT_COMPONENT = /^(?:src\/)?app\/(?:.*\/)?(?:page|layout|loading|not-found|template|error|global-error|default|forbidden|unauthorized)\.[jt]sx?$/u;
|
|
23343
|
+
var NEXT_METADATA = /^(?:src\/)?app\/(?:.*\/)?(?:(?:opengraph-image|twitter-image|icon|apple-icon)\.[jt]sx?|(?:robots|sitemap|manifest)\.[jt]s)$/u;
|
|
23206
23344
|
var NEXT_ROUTE = /^(?:src\/)?app\/(?:.*\/)?route\.[jt]s$/u;
|
|
23207
23345
|
function isFrameworkEntrypoint(filename, exportKey) {
|
|
23208
23346
|
const normalized = filename.replaceAll("\\", "/");
|
|
23209
|
-
if (!/(?:^|\/)(?:app|pages)\/|(?:^|\/)(?:middleware|proxy)\.[jt]s$/u.test(normalized)) return false;
|
|
23347
|
+
if (!/(?:^|\/)(?:app|pages)\/|(?:^|\/)(?:middleware|proxy|instrumentation)\.[jt]s$/u.test(normalized)) return false;
|
|
23210
23348
|
const owner = packageOwner(normalized);
|
|
23211
23349
|
if (owner === null) return false;
|
|
23212
23350
|
const path = relative2(owner.root, normalized).replaceAll("\\", "/");
|
|
@@ -23215,9 +23353,12 @@ function isFrameworkEntrypoint(filename, exportKey) {
|
|
|
23215
23353
|
}
|
|
23216
23354
|
function isNextEntrypoint(path, exportKey) {
|
|
23217
23355
|
const privateAppFolder = /^(?:src\/)?app\/(?:.*\/)?_[^/]+\//u.test(path);
|
|
23218
|
-
if (!privateAppFolder && NEXT_COMPONENT.test(path) && exportKey === "default") return true;
|
|
23356
|
+
if (!privateAppFolder && (NEXT_COMPONENT.test(path) || NEXT_METADATA.test(path)) && exportKey === "default") return true;
|
|
23219
23357
|
if (!privateAppFolder && NEXT_ROUTE.test(path) && HTTP_METHODS.has(exportKey)) return true;
|
|
23220
23358
|
if (/^(?:src\/)?pages\/.*\.[jt]sx?$/u.test(path) && exportKey === "default") return true;
|
|
23359
|
+
if (/^(?:src\/)?instrumentation\.[jt]s$/u.test(path)) {
|
|
23360
|
+
return exportKey === "register" || exportKey === "onRequestError";
|
|
23361
|
+
}
|
|
23221
23362
|
const middleware = /^(?:src\/)?(middleware|proxy)\.[jt]s$/u.exec(path);
|
|
23222
23363
|
return middleware !== null && (exportKey === "default" || exportKey === middleware[1]);
|
|
23223
23364
|
}
|
|
@@ -23260,7 +23401,7 @@ var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
|
|
|
23260
23401
|
"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.",
|
|
23261
23402
|
"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.",
|
|
23262
23403
|
"Exported destructuring patterns are excluded rather than undercounted as public exports.",
|
|
23263
|
-
"Next.js and Astro
|
|
23404
|
+
"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."
|
|
23264
23405
|
],
|
|
23265
23406
|
examples: [
|
|
23266
23407
|
{ 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 },
|
|
@@ -23829,7 +23970,7 @@ var RULES = {
|
|
|
23829
23970
|
};
|
|
23830
23971
|
var meta = {
|
|
23831
23972
|
name: "@sarj/eslint-plugin",
|
|
23832
|
-
version: "16.2.
|
|
23973
|
+
version: "16.2.6"
|
|
23833
23974
|
};
|
|
23834
23975
|
var APPLICATION_ONLY_RULES = [];
|
|
23835
23976
|
var LIBRARY_IMPORT_POLICY = ["error", {
|