fallow-type-aware 3.17.0 → 3.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/semantic-identity.mjs +9 -3
- package/src/semantic.mjs +138 -29
package/package.json
CHANGED
|
@@ -237,9 +237,15 @@ export const symbolForDeclaration = (project, declaration) =>
|
|
|
237
237
|
|
|
238
238
|
export const resolveAlias = (checker, symbol) => {
|
|
239
239
|
if (!symbol) return undefined;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
const seen = new Set();
|
|
241
|
+
let current = symbol;
|
|
242
|
+
while ((current.flags & SymbolFlags.Alias) !== 0 && !seen.has(current)) {
|
|
243
|
+
seen.add(current);
|
|
244
|
+
const aliased = checker.getAliasedSymbol(current);
|
|
245
|
+
if (checker.isUnknownSymbol(aliased) || aliased === current) return current;
|
|
246
|
+
current = aliased;
|
|
247
|
+
}
|
|
248
|
+
return current;
|
|
243
249
|
};
|
|
244
250
|
|
|
245
251
|
export const declarationsForSymbol = (project, symbol) =>
|
package/src/semantic.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
isImportTypeNode,
|
|
21
21
|
isNamespaceImport,
|
|
22
22
|
isPropertyAccessExpression,
|
|
23
|
+
isQualifiedName,
|
|
23
24
|
isPrivateIdentifier,
|
|
24
25
|
isSetAccessorDeclaration,
|
|
25
26
|
isStringLiteralLikeNode,
|
|
@@ -431,30 +432,60 @@ const registerSymbolTargets = (root, state, entry) => {
|
|
|
431
432
|
state.exportEntriesByProject.set(project, projectEntries);
|
|
432
433
|
});
|
|
433
434
|
}
|
|
434
|
-
entry.resolved.ownerContexts.forEach(({
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
entry.query.symbol.
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
435
|
+
entry.resolved.ownerContexts.forEach(({ declaration, anchor }) => {
|
|
436
|
+
const namespaces =
|
|
437
|
+
entry.query.symbol.declarationKind === "export"
|
|
438
|
+
? declarationNamespaces(declaration)
|
|
439
|
+
: new Set([entry.query.symbol.namespace]);
|
|
440
|
+
namespaces.forEach((namespace) => addSymbolTarget(root, state, entry, declaration, namespace));
|
|
441
|
+
if (entry.query.symbol.declarationKind === "export" && anchor) {
|
|
442
|
+
declarationNamespaces(anchor).forEach((namespace) =>
|
|
443
|
+
addSymbolTarget(root, state, entry, anchor, namespace),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
443
446
|
});
|
|
444
447
|
state.contractRelationsByQuery.set(entry.query.id, entry.resolved.contractRelations);
|
|
445
448
|
};
|
|
446
449
|
|
|
447
450
|
const matchingSymbolEntries = (project, symbol, namespace, targetsByKey) => {
|
|
451
|
+
const declarations = declarationsForSymbol(project, symbol);
|
|
452
|
+
const matchingLane = declarations.filter((declaration) =>
|
|
453
|
+
declarationNamespaces(declaration).has(namespace),
|
|
454
|
+
);
|
|
455
|
+
if (matchingLane.length > 0) {
|
|
456
|
+
return new Set(
|
|
457
|
+
matchingLane.flatMap(
|
|
458
|
+
(declaration) => targetsByKey.get(stableDeclarationKey(declaration, namespace)) ?? [],
|
|
459
|
+
),
|
|
460
|
+
);
|
|
461
|
+
}
|
|
448
462
|
return new Set(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
463
|
+
declarations.flatMap((declaration) =>
|
|
464
|
+
[...declarationNamespaces(declaration)].flatMap(
|
|
465
|
+
(declaredNamespace) =>
|
|
466
|
+
targetsByKey.get(stableDeclarationKey(declaration, declaredNamespace)) ?? [],
|
|
467
|
+
),
|
|
468
|
+
),
|
|
455
469
|
);
|
|
456
470
|
};
|
|
457
471
|
|
|
472
|
+
const matchingAliasEntries = (project, symbol, namespace, targetsByKey) => {
|
|
473
|
+
const entries = new Set();
|
|
474
|
+
const seen = new Set();
|
|
475
|
+
let current = symbol;
|
|
476
|
+
while (current && !seen.has(current)) {
|
|
477
|
+
seen.add(current);
|
|
478
|
+
matchingSymbolEntries(project, current, namespace, targetsByKey).forEach((entry) =>
|
|
479
|
+
entries.add(entry),
|
|
480
|
+
);
|
|
481
|
+
if ((current.flags & SymbolFlags.Alias) === 0) break;
|
|
482
|
+
const aliased = project.checker.getAliasedSymbol(current);
|
|
483
|
+
if (project.checker.isUnknownSymbol(aliased) || aliased === current) break;
|
|
484
|
+
current = aliased;
|
|
485
|
+
}
|
|
486
|
+
return entries;
|
|
487
|
+
};
|
|
488
|
+
|
|
458
489
|
const isDeclarationReference = (root, state, entry, node) => {
|
|
459
490
|
const locations = state.declarationLocationsByQuery.get(entry.query.id);
|
|
460
491
|
if (locations.has(locationKey(location(root, node)))) return true;
|
|
@@ -488,13 +519,37 @@ const importTypeSpecifier = (node) => {
|
|
|
488
519
|
const isDynamicImportCall = (node) =>
|
|
489
520
|
isCallExpression(node) && node.expression.getText(node.getSourceFile()) === "import";
|
|
490
521
|
|
|
522
|
+
const MODULE_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
523
|
+
|
|
524
|
+
const lexicalModuleIdentityForText = (project, sourceFile, specifierText) => {
|
|
525
|
+
if (!specifierText?.startsWith(".")) return undefined;
|
|
526
|
+
const resolved = path.resolve(path.dirname(sourceFile.fileName), specifierText);
|
|
527
|
+
const extension = path.extname(resolved);
|
|
528
|
+
const stem = extension ? resolved.slice(0, -extension.length) : resolved;
|
|
529
|
+
const candidates = [
|
|
530
|
+
resolved,
|
|
531
|
+
...MODULE_SOURCE_EXTENSIONS.map((candidate) => `${stem}${candidate}`),
|
|
532
|
+
...MODULE_SOURCE_EXTENSIONS.map((candidate) => path.join(resolved, `index${candidate}`)),
|
|
533
|
+
];
|
|
534
|
+
for (const candidate of candidates) {
|
|
535
|
+
const matched = project.program.getSourceFile(candidate);
|
|
536
|
+
if (matched) return sourceFileIdentity(matched);
|
|
537
|
+
if (existsSync(candidate)) return canonicalFileIdentity(candidate);
|
|
538
|
+
}
|
|
539
|
+
return undefined;
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const lexicalModuleIdentity = (project, specifier) =>
|
|
543
|
+
specifier
|
|
544
|
+
? lexicalModuleIdentityForText(project, specifier.getSourceFile(), specifier.text)
|
|
545
|
+
: undefined;
|
|
546
|
+
|
|
491
547
|
const moduleIdentityForSpecifier = (project, specifier) => {
|
|
492
548
|
if (!specifier) return undefined;
|
|
549
|
+
const lexicalIdentity = lexicalModuleIdentity(project, specifier);
|
|
550
|
+
if (lexicalIdentity) return lexicalIdentity;
|
|
493
551
|
const moduleSymbol = project.checker.getSymbolAtLocation(specifier);
|
|
494
|
-
const moduleDeclaration = declarationsForSymbol(
|
|
495
|
-
project,
|
|
496
|
-
resolveAlias(project.checker, moduleSymbol),
|
|
497
|
-
)[0];
|
|
552
|
+
const moduleDeclaration = declarationsForSymbol(project, moduleSymbol)[0];
|
|
498
553
|
return moduleDeclaration ? sourceFileIdentity(moduleDeclaration.getSourceFile()) : undefined;
|
|
499
554
|
};
|
|
500
555
|
|
|
@@ -507,7 +562,9 @@ const namespaceImportDeclaration = (project, node) => {
|
|
|
507
562
|
: undefined
|
|
508
563
|
: isElementAccessExpression(parent) && parent.argumentExpression === node
|
|
509
564
|
? parent.expression
|
|
510
|
-
:
|
|
565
|
+
: isQualifiedName(parent) && parent.right === node
|
|
566
|
+
? parent.left
|
|
567
|
+
: undefined;
|
|
511
568
|
if (!namespace) return undefined;
|
|
512
569
|
if (!isIdentifier(namespace)) return undefined;
|
|
513
570
|
const symbol = project.checker.getSymbolAtLocation(namespace);
|
|
@@ -516,9 +573,28 @@ const namespaceImportDeclaration = (project, node) => {
|
|
|
516
573
|
.find(isNamespaceImport);
|
|
517
574
|
};
|
|
518
575
|
|
|
576
|
+
const localAliasModuleEdgeDeclaration = (project, node) => {
|
|
577
|
+
const symbol = project.checker.getSymbolAtLocation(node);
|
|
578
|
+
return symbol?.declarations
|
|
579
|
+
?.map((declaration) => moduleEdgeDeclaration(declaration.resolve(project)))
|
|
580
|
+
.find(Boolean);
|
|
581
|
+
};
|
|
582
|
+
|
|
519
583
|
const referencedModuleIdentity = (project, node) => {
|
|
584
|
+
const importType = ancestorImportType(node);
|
|
585
|
+
const lexicalImportType = importType
|
|
586
|
+
?.getText(importType.getSourceFile())
|
|
587
|
+
.match(/import\s*\(\s*["']([^"']+)["']/u)?.[1];
|
|
588
|
+
const lexicalIdentity = lexicalModuleIdentityForText(
|
|
589
|
+
project,
|
|
590
|
+
node.getSourceFile(),
|
|
591
|
+
lexicalImportType,
|
|
592
|
+
);
|
|
593
|
+
if (lexicalIdentity) return lexicalIdentity;
|
|
520
594
|
const declaration =
|
|
521
|
-
moduleEdgeDeclaration(node) ??
|
|
595
|
+
moduleEdgeDeclaration(node) ??
|
|
596
|
+
moduleEdgeDeclaration(namespaceImportDeclaration(project, node)) ??
|
|
597
|
+
localAliasModuleEdgeDeclaration(project, node);
|
|
522
598
|
const declarationSpecifier = declaration?.moduleSpecifier;
|
|
523
599
|
const specifier =
|
|
524
600
|
declarationSpecifier && isStringLiteralLikeNode(declarationSpecifier)
|
|
@@ -527,13 +603,22 @@ const referencedModuleIdentity = (project, node) => {
|
|
|
527
603
|
return moduleIdentityForSpecifier(project, specifier);
|
|
528
604
|
};
|
|
529
605
|
|
|
530
|
-
const isExactExportReference = (project, entry, node) =>
|
|
531
|
-
entry.query.symbol.declarationKind !== "export"
|
|
532
|
-
referencedModuleIdentity(project, node)
|
|
533
|
-
|
|
606
|
+
const isExactExportReference = (project, entry, node) => {
|
|
607
|
+
if (entry.query.symbol.declarationKind !== "export") return true;
|
|
608
|
+
const referencedModule = referencedModuleIdentity(project, node);
|
|
609
|
+
if (!referencedModule) return false;
|
|
610
|
+
const queryModule = canonicalFileIdentity(entry.query.symbol.absolutePath);
|
|
611
|
+
return (
|
|
612
|
+
referencedModule === queryModule ||
|
|
613
|
+
sourceFileIdentity(entry.resolved.declaration.getSourceFile()) === queryModule
|
|
614
|
+
);
|
|
615
|
+
};
|
|
534
616
|
|
|
535
617
|
const recordSymbolUse = (root, state, project, entry, node, namespace) => {
|
|
536
618
|
if (isDeclarationReference(root, state, entry, node)) return;
|
|
619
|
+
if (isDeclaration(node.parent) && node.parent.name === node) return;
|
|
620
|
+
const moduleEdge = moduleEdgeDeclaration(node);
|
|
621
|
+
if (moduleEdge) return;
|
|
537
622
|
if (!isExactExportReference(project, entry, node)) return;
|
|
538
623
|
const evidence = state.evidenceByQuery.get(entry.query.id);
|
|
539
624
|
state.totalByQuery.set(entry.query.id, state.totalByQuery.get(entry.query.id) + 1);
|
|
@@ -635,6 +720,26 @@ const referenceNamespaces = (project, node, symbol) => {
|
|
|
635
720
|
return namespaces.size > 0 ? [...namespaces] : [isTypePosition(node) ? "type" : "value"];
|
|
636
721
|
};
|
|
637
722
|
|
|
723
|
+
const surfaceEntriesForNode = (state, project, node, namespace) => {
|
|
724
|
+
if (moduleEdgeDeclaration(node)) return new Set();
|
|
725
|
+
const moduleIdentity = referencedModuleIdentity(project, node);
|
|
726
|
+
if (!moduleIdentity) return new Set();
|
|
727
|
+
const direct = [...(state.exportEntriesByModule.get(moduleIdentity) ?? [])].filter(
|
|
728
|
+
(entry) =>
|
|
729
|
+
entry.query.symbol.exportedName === node.text && entry.query.symbol.namespace === namespace,
|
|
730
|
+
);
|
|
731
|
+
const entries = new Set(direct);
|
|
732
|
+
for (const entry of direct) {
|
|
733
|
+
for (const { declaration } of entry.resolved.ownerContexts) {
|
|
734
|
+
const symbol = resolveAlias(project.checker, symbolForDeclaration(project, declaration));
|
|
735
|
+
matchingSymbolEntries(project, symbol, namespace, state.targetsByKey).forEach((related) =>
|
|
736
|
+
entries.add(related),
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return entries;
|
|
741
|
+
};
|
|
742
|
+
|
|
638
743
|
const scanSymbolUseFile = (root, state, project, sourceFile, includeDefaultImports) => {
|
|
639
744
|
const projectEntries = classMemberEntrySetForProject(state, project);
|
|
640
745
|
const nodes = [];
|
|
@@ -644,7 +749,7 @@ const scanSymbolUseFile = (root, state, project, sourceFile, includeDefaultImpor
|
|
|
644
749
|
recordStringDispatchedMemberAccess(state, projectEntries, node);
|
|
645
750
|
recordComputedMemberAccess(state, project, projectEntries, node);
|
|
646
751
|
}
|
|
647
|
-
if (isIdentifierReference(node,
|
|
752
|
+
if (isIdentifierReference(node, undefined, includeDefaultImports)) {
|
|
648
753
|
nodes.push(node);
|
|
649
754
|
return;
|
|
650
755
|
}
|
|
@@ -652,9 +757,13 @@ const scanSymbolUseFile = (root, state, project, sourceFile, includeDefaultImpor
|
|
|
652
757
|
});
|
|
653
758
|
const symbols = project.checker.getSymbolAtLocation(nodes);
|
|
654
759
|
nodes.forEach((node, index) => {
|
|
655
|
-
const
|
|
760
|
+
const rawSymbol = symbols[index];
|
|
761
|
+
const symbol = resolveAlias(project.checker, rawSymbol);
|
|
656
762
|
referenceNamespaces(project, node, symbol).forEach((namespace) => {
|
|
657
|
-
const entries =
|
|
763
|
+
const entries = new Set([
|
|
764
|
+
...matchingAliasEntries(project, rawSymbol, namespace, state.targetsByKey),
|
|
765
|
+
...surfaceEntriesForNode(state, project, node, namespace),
|
|
766
|
+
]);
|
|
658
767
|
entries.forEach((entry) => recordSymbolUse(root, state, project, entry, node, namespace));
|
|
659
768
|
});
|
|
660
769
|
});
|
|
@@ -956,7 +1065,7 @@ const resolvedOwnerContext = (query, owner) => {
|
|
|
956
1065
|
const anchor = symbolQueryAnchor(owner.project, query);
|
|
957
1066
|
const target = resolvedAnchorTarget(owner.project, anchor, query.symbol);
|
|
958
1067
|
return symbolQueryResolved(owner.project, query.symbol, anchor, target)
|
|
959
|
-
? { ...owner, ...target }
|
|
1068
|
+
? { ...owner, anchor, ...target }
|
|
960
1069
|
: undefined;
|
|
961
1070
|
};
|
|
962
1071
|
|