@sarj/eslint-plugin 16.2.5 → 16.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +289 -173
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +289 -173
- 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)) {
|
|
@@ -21405,7 +21517,7 @@ var REQUIRE_SQL_ACCESS_CLASS_DOCUMENTATION = {
|
|
|
21405
21517
|
}
|
|
21406
21518
|
]
|
|
21407
21519
|
};
|
|
21408
|
-
function
|
|
21520
|
+
function memberName8(node) {
|
|
21409
21521
|
if (!node.computed && node.property.type === AST_NODE_TYPES98.Identifier)
|
|
21410
21522
|
return node.property.name;
|
|
21411
21523
|
if (node.computed && node.property.type === AST_NODE_TYPES98.Literal && typeof node.property.value === "string")
|
|
@@ -21423,7 +21535,7 @@ function databaseReceiver(node) {
|
|
|
21423
21535
|
if (node.type === AST_NODE_TYPES98.Identifier)
|
|
21424
21536
|
return DATABASE_NAMES.test(node.name);
|
|
21425
21537
|
if (node.type !== AST_NODE_TYPES98.MemberExpression) return false;
|
|
21426
|
-
const name =
|
|
21538
|
+
const name = memberName8(node);
|
|
21427
21539
|
return name === "DB" || name !== null && DATABASE_NAMES.test(name);
|
|
21428
21540
|
}
|
|
21429
21541
|
function databaseRootMember(node) {
|
|
@@ -21444,7 +21556,7 @@ function databaseRootMember(node) {
|
|
|
21444
21556
|
}
|
|
21445
21557
|
if (current.type === AST_NODE_TYPES98.MemberExpression) {
|
|
21446
21558
|
if (current.object.type === AST_NODE_TYPES98.ThisExpression)
|
|
21447
|
-
return
|
|
21559
|
+
return memberName8(current);
|
|
21448
21560
|
current = current.object;
|
|
21449
21561
|
continue;
|
|
21450
21562
|
}
|
|
@@ -21468,7 +21580,7 @@ function expressionHasDatabaseMarker(node) {
|
|
|
21468
21580
|
continue;
|
|
21469
21581
|
}
|
|
21470
21582
|
if (current.type === AST_NODE_TYPES98.MemberExpression) {
|
|
21471
|
-
const name =
|
|
21583
|
+
const name = memberName8(current);
|
|
21472
21584
|
if (name === "DB" || name !== null && DATABASE_NAMES.test(name))
|
|
21473
21585
|
return true;
|
|
21474
21586
|
current = current.object;
|
|
@@ -21490,7 +21602,7 @@ function expressionCallsBuilder(node) {
|
|
|
21490
21602
|
}
|
|
21491
21603
|
if (current.type === AST_NODE_TYPES98.CallExpression) {
|
|
21492
21604
|
if (current.callee.type !== AST_NODE_TYPES98.MemberExpression) return false;
|
|
21493
|
-
const name =
|
|
21605
|
+
const name = memberName8(current.callee);
|
|
21494
21606
|
if (name !== null && BUILDER_METHODS.has(name)) return true;
|
|
21495
21607
|
current = current.callee.object;
|
|
21496
21608
|
continue;
|
|
@@ -21541,7 +21653,7 @@ function thisRootMember(node) {
|
|
|
21541
21653
|
let current = node;
|
|
21542
21654
|
let root = null;
|
|
21543
21655
|
while (current.type === AST_NODE_TYPES98.MemberExpression) {
|
|
21544
|
-
const name =
|
|
21656
|
+
const name = memberName8(current);
|
|
21545
21657
|
if (name === null) return null;
|
|
21546
21658
|
root = name;
|
|
21547
21659
|
current = current.object;
|
|
@@ -21595,7 +21707,7 @@ var require_sql_access_class_default = createRule({
|
|
|
21595
21707
|
CallExpression(node) {
|
|
21596
21708
|
if (node.callee.type !== AST_NODE_TYPES98.MemberExpression)
|
|
21597
21709
|
return;
|
|
21598
|
-
const method =
|
|
21710
|
+
const method = memberName8(node.callee);
|
|
21599
21711
|
if (knownNonDatabase(node.callee.object)) return;
|
|
21600
21712
|
if (method === null || !isDatabaseOperation(method, node.callee.object))
|
|
21601
21713
|
return;
|
|
@@ -23228,10 +23340,11 @@ import { readFileSync as readFileSync3 } from "fs";
|
|
|
23228
23340
|
import { dirname as dirname3, join as join3, relative as relative2 } from "path";
|
|
23229
23341
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]);
|
|
23230
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;
|
|
23231
23344
|
var NEXT_ROUTE = /^(?:src\/)?app\/(?:.*\/)?route\.[jt]s$/u;
|
|
23232
23345
|
function isFrameworkEntrypoint(filename, exportKey) {
|
|
23233
23346
|
const normalized = filename.replaceAll("\\", "/");
|
|
23234
|
-
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;
|
|
23235
23348
|
const owner = packageOwner(normalized);
|
|
23236
23349
|
if (owner === null) return false;
|
|
23237
23350
|
const path = relative2(owner.root, normalized).replaceAll("\\", "/");
|
|
@@ -23240,9 +23353,12 @@ function isFrameworkEntrypoint(filename, exportKey) {
|
|
|
23240
23353
|
}
|
|
23241
23354
|
function isNextEntrypoint(path, exportKey) {
|
|
23242
23355
|
const privateAppFolder = /^(?:src\/)?app\/(?:.*\/)?_[^/]+\//u.test(path);
|
|
23243
|
-
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;
|
|
23244
23357
|
if (!privateAppFolder && NEXT_ROUTE.test(path) && HTTP_METHODS.has(exportKey)) return true;
|
|
23245
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
|
+
}
|
|
23246
23362
|
const middleware = /^(?:src\/)?(middleware|proxy)\.[jt]s$/u.exec(path);
|
|
23247
23363
|
return middleware !== null && (exportKey === "default" || exportKey === middleware[1]);
|
|
23248
23364
|
}
|
|
@@ -23285,7 +23401,7 @@ var SOLE_EXPORT_MATCHES_FILENAME_DOCUMENTATION = {
|
|
|
23285
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.",
|
|
23286
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.",
|
|
23287
23403
|
"Exported destructuring patterns are excluded rather than undercounted as public exports.",
|
|
23288
|
-
"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."
|
|
23289
23405
|
],
|
|
23290
23406
|
examples: [
|
|
23291
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 },
|
|
@@ -23854,7 +23970,7 @@ var RULES = {
|
|
|
23854
23970
|
};
|
|
23855
23971
|
var meta = {
|
|
23856
23972
|
name: "@sarj/eslint-plugin",
|
|
23857
|
-
version: "16.2.
|
|
23973
|
+
version: "16.2.6"
|
|
23858
23974
|
};
|
|
23859
23975
|
var APPLICATION_ONLY_RULES = [];
|
|
23860
23976
|
var LIBRARY_IMPORT_POLICY = ["error", {
|