fallow-type-aware 3.34.1 → 3.36.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 +10 -1
- package/src/backend-preflight.mjs +33 -4
- package/src/backend-selection.mjs +18 -0
- package/src/backend.mjs +169 -0
- package/src/computed-member-access.mjs +45 -0
- package/src/generated-protocol.mjs +12 -0
- package/src/mutant-typecheck.mjs +13 -10
- package/src/namespace-uses.mjs +499 -0
- package/src/project-package.mjs +37 -0
- package/src/project-state.mjs +12 -10
- package/src/protocol.mjs +7 -16
- package/src/reference-names.mjs +84 -0
- package/src/semantic-identity.mjs +7 -5
- package/src/semantic.mjs +94 -197
- package/src/shorthand-references.mjs +243 -0
- package/src/symbol-impact.mjs +3 -6
- package/src/symbol-targets.mjs +74 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ast } from "./backend.mjs";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
isExportDeclaration,
|
|
5
|
+
isImportDeclaration,
|
|
6
|
+
isImportEqualsDeclaration,
|
|
7
|
+
isModuleBlock,
|
|
8
|
+
isModuleDeclaration,
|
|
9
|
+
isNamedExports,
|
|
10
|
+
isNamespaceExportDeclaration,
|
|
11
|
+
isNamespaceImport,
|
|
12
|
+
} = ast;
|
|
13
|
+
|
|
14
|
+
const JAVASCRIPT_FILE = /\.[cm]?jsx?$/iu;
|
|
15
|
+
|
|
16
|
+
// In JavaScript, require bindings and export assignments also create aliases.
|
|
17
|
+
// The scan keeps every identifier of these files.
|
|
18
|
+
export const isJavaScriptFile = (sourceFile) => JAVASCRIPT_FILE.test(sourceFile.fileName);
|
|
19
|
+
|
|
20
|
+
const importNames = ({ importClause }) => {
|
|
21
|
+
const bindings = importClause?.namedBindings;
|
|
22
|
+
if (!bindings) return [importClause?.name];
|
|
23
|
+
if (isNamespaceImport(bindings)) return [importClause.name, bindings.name];
|
|
24
|
+
return [importClause.name, ...bindings.elements.map((element) => element.name)];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const exportNames = ({ exportClause }) => {
|
|
28
|
+
if (!exportClause) return [];
|
|
29
|
+
if (isNamedExports(exportClause)) return exportClause.elements.map((element) => element.name);
|
|
30
|
+
return [exportClause.name];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Returns the statements of a `namespace` or `declare module` body, also when nested. */
|
|
34
|
+
export const moduleBodyStatements = (body) => {
|
|
35
|
+
if (body && isModuleDeclaration(body)) return moduleBodyStatements(body.body);
|
|
36
|
+
return body && isModuleBlock(body) ? body.statements : [];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const statementAliasNames = (statement) => {
|
|
40
|
+
if (isImportDeclaration(statement)) return importNames(statement);
|
|
41
|
+
if (isImportEqualsDeclaration(statement) || isNamespaceExportDeclaration(statement)) {
|
|
42
|
+
return [statement.name];
|
|
43
|
+
}
|
|
44
|
+
if (isExportDeclaration(statement)) return exportNames(statement);
|
|
45
|
+
if (isModuleDeclaration(statement)) {
|
|
46
|
+
return moduleBodyStatements(statement.body).flatMap(statementAliasNames);
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Default-library and external-library files sit below node_modules. The
|
|
52
|
+
// path check skips them before the source file and its metadata, which each
|
|
53
|
+
// cost a checker round trip.
|
|
54
|
+
export const NODE_MODULES_SEGMENT = /[\\/]node_modules[\\/]/u;
|
|
55
|
+
|
|
56
|
+
const isOwnSourceFile = (project, sourceFile) =>
|
|
57
|
+
Boolean(sourceFile) &&
|
|
58
|
+
!project.program.isSourceFileDefaultLibrary(sourceFile) &&
|
|
59
|
+
!project.program.isSourceFileFromExternalLibrary(sourceFile);
|
|
60
|
+
|
|
61
|
+
/** Returns the program files outside node_modules and the default libraries. */
|
|
62
|
+
export const ownSourceFiles = (project) =>
|
|
63
|
+
project.program
|
|
64
|
+
.getSourceFileNames()
|
|
65
|
+
.filter((fileName) => !NODE_MODULES_SEGMENT.test(fileName))
|
|
66
|
+
.map((fileName) => project.program.getSourceFile(fileName))
|
|
67
|
+
.filter((sourceFile) => isOwnSourceFile(project, sourceFile));
|
|
68
|
+
|
|
69
|
+
const programAliasNames = (project) =>
|
|
70
|
+
ownSourceFiles(project)
|
|
71
|
+
.flatMap((sourceFile) => sourceFile.statements.flatMap(statementAliasNames))
|
|
72
|
+
.map((name) => name?.text)
|
|
73
|
+
.filter((text) => typeof text === "string");
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns the identifier names that can reach a symbol-use target.
|
|
77
|
+
*
|
|
78
|
+
* An identifier resolves to a declaration with its own name, or to an alias
|
|
79
|
+
* that an import or export declares under that name. So the scan needs only
|
|
80
|
+
* the target names and the names that the alias declarations of the project
|
|
81
|
+
* files declare, project declaration files included.
|
|
82
|
+
*/
|
|
83
|
+
export const referenceNames = (projects, targetNames) =>
|
|
84
|
+
new Set([...targetNames, "default", ...projects.flatMap(programAliasNames)]);
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { sync, ast } from "./backend.mjs";
|
|
4
|
+
|
|
5
|
+
import { canonicalFileIdentity } from "./file-identity.mjs";
|
|
6
|
+
|
|
7
|
+
const { SymbolFlags } = sync;
|
|
8
|
+
const {
|
|
5
9
|
isClassDeclaration,
|
|
6
10
|
isClassExpression,
|
|
7
11
|
isEnumDeclaration,
|
|
@@ -19,9 +23,7 @@ import {
|
|
|
19
23
|
isSourceFile,
|
|
20
24
|
isTypeAliasDeclaration,
|
|
21
25
|
isVariableDeclaration,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
import { canonicalFileIdentity } from "./file-identity.mjs";
|
|
26
|
+
} = ast;
|
|
25
27
|
|
|
26
28
|
const DECLARATION_KIND_RULES = [
|
|
27
29
|
[[isClassDeclaration, isClassExpression], "class"],
|
package/src/semantic.mjs
CHANGED
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
isCallExpression,
|
|
8
|
-
isClassDeclaration,
|
|
9
|
-
isClassExpression,
|
|
10
|
-
isDecorator,
|
|
11
|
-
isElementAccessExpression,
|
|
12
|
-
isExportDeclaration,
|
|
13
|
-
isExportSpecifier,
|
|
14
|
-
isFunctionBody,
|
|
15
|
-
isGetAccessorDeclaration,
|
|
16
|
-
isIdentifier,
|
|
17
|
-
isImportDeclaration,
|
|
18
|
-
isImportClause,
|
|
19
|
-
isImportSpecifier,
|
|
20
|
-
isImportTypeNode,
|
|
21
|
-
isNamespaceImport,
|
|
22
|
-
isPropertyAccessExpression,
|
|
23
|
-
isQualifiedName,
|
|
24
|
-
isPrivateIdentifier,
|
|
25
|
-
isSetAccessorDeclaration,
|
|
26
|
-
isStringLiteralLikeNode,
|
|
27
|
-
isTypeNode,
|
|
28
|
-
isTypeQueryNode,
|
|
29
|
-
} from "typescript/unstable/ast/is";
|
|
5
|
+
import { ast, createApi as createBackendApi, heritageTypeName, sync } from "./backend.mjs";
|
|
30
6
|
|
|
31
7
|
import { canonicalFileIdentity } from "./file-identity.mjs";
|
|
8
|
+
import { packagePathTargets, projectPackage } from "./project-package.mjs";
|
|
32
9
|
import { projectResult, projectState } from "./project-state.mjs";
|
|
33
10
|
import {
|
|
34
11
|
declarationName,
|
|
@@ -51,8 +28,50 @@ import {
|
|
|
51
28
|
symbolForDeclaration,
|
|
52
29
|
} from "./semantic-identity.mjs";
|
|
53
30
|
import { analyzeSymbolImpact } from "./symbol-impact.mjs";
|
|
31
|
+
import { isJavaScriptFile, referenceNames } from "./reference-names.mjs";
|
|
32
|
+
import { recordComputedMemberAccess } from "./computed-member-access.mjs";
|
|
33
|
+
import {
|
|
34
|
+
namespaceSourceIdentity,
|
|
35
|
+
recordDynamicImportUncertainty,
|
|
36
|
+
recordNamespaceUse,
|
|
37
|
+
} from "./namespace-uses.mjs";
|
|
38
|
+
import {
|
|
39
|
+
isLiteralMemberName,
|
|
40
|
+
qualifierExpression,
|
|
41
|
+
referenceValueSymbol,
|
|
42
|
+
shorthandReferenceSymbols,
|
|
43
|
+
} from "./shorthand-references.mjs";
|
|
44
|
+
import {
|
|
45
|
+
matchingAliasEntries,
|
|
46
|
+
matchingSymbolEntries,
|
|
47
|
+
recordSymbolUncertainty,
|
|
48
|
+
targetName,
|
|
49
|
+
} from "./symbol-targets.mjs";
|
|
54
50
|
import { analyzeTypeCoupling } from "./type-coupling.mjs";
|
|
55
51
|
|
|
52
|
+
const { SignatureKind, SymbolFlags } = sync;
|
|
53
|
+
const {
|
|
54
|
+
isCallExpression,
|
|
55
|
+
isClassDeclaration,
|
|
56
|
+
isClassExpression,
|
|
57
|
+
isDecorator,
|
|
58
|
+
isExportDeclaration,
|
|
59
|
+
isExportSpecifier,
|
|
60
|
+
isFunctionBody,
|
|
61
|
+
isGetAccessorDeclaration,
|
|
62
|
+
isIdentifier,
|
|
63
|
+
isImportDeclaration,
|
|
64
|
+
isImportSpecifier,
|
|
65
|
+
isImportTypeNode,
|
|
66
|
+
isNamespaceImport,
|
|
67
|
+
isPropertyAccessExpression,
|
|
68
|
+
isPrivateIdentifier,
|
|
69
|
+
isSetAccessorDeclaration,
|
|
70
|
+
isStringLiteralLikeNode,
|
|
71
|
+
isTypeNode,
|
|
72
|
+
isTypeQueryNode,
|
|
73
|
+
} = ast;
|
|
74
|
+
|
|
56
75
|
const ATTACHED_COMMENT_PATTERN = /\/\/|\/\*/u;
|
|
57
76
|
const MAX_SYMBOL_USE_EVIDENCE = 1;
|
|
58
77
|
const DECLARATION_KINDS = new Set([
|
|
@@ -352,23 +371,15 @@ const semanticUse = (root, node, namespace) => {
|
|
|
352
371
|
};
|
|
353
372
|
};
|
|
354
373
|
|
|
355
|
-
const isDefaultImportReference = (node) => isImportClause(node.parent) && node.parent.name === node;
|
|
356
|
-
|
|
357
374
|
const candidateNameMatches = (candidateNames, name) => !candidateNames || candidateNames.has(name);
|
|
358
375
|
|
|
359
|
-
const isIdentifierReference = (node,
|
|
360
|
-
|
|
361
|
-
if (candidateNameMatches(candidateNames, node.text)) return true;
|
|
362
|
-
return includeDefaultImports && isDefaultImportReference(node);
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
const isElementNameNode = (node) =>
|
|
366
|
-
isStringLiteralLikeNode(node) &&
|
|
367
|
-
isElementAccessExpression(node.parent) &&
|
|
368
|
-
node.parent.argumentExpression === node;
|
|
376
|
+
const isIdentifierReference = (node, names) =>
|
|
377
|
+
isIdentifier(node) && candidateNameMatches(names, node.text);
|
|
369
378
|
|
|
370
379
|
const isElementReference = (node, candidateNames) =>
|
|
371
|
-
|
|
380
|
+
isStringLiteralLikeNode(node) &&
|
|
381
|
+
candidateNameMatches(candidateNames, node.text) &&
|
|
382
|
+
isLiteralMemberName(node);
|
|
372
383
|
|
|
373
384
|
const entriesByName = (entries) => {
|
|
374
385
|
const byName = new Map();
|
|
@@ -396,8 +407,11 @@ const symbolUseBatchState = (resolvedQueries, evidenceLimit) => {
|
|
|
396
407
|
uncertaintiesByQuery: new Map(resolvedQueries.map(({ query }) => [query.id, new Set()])),
|
|
397
408
|
declarationLocationsByQuery: new Map(resolvedQueries.map(({ query }) => [query.id, new Set()])),
|
|
398
409
|
targetsByKey: new Map(),
|
|
410
|
+
targetNames: new Set(),
|
|
411
|
+
aliasEntriesByProject: new Map(),
|
|
399
412
|
exportEntriesByModule: new Map(),
|
|
400
413
|
exportEntriesByProject: new Map(),
|
|
414
|
+
exportedNames: new Set(),
|
|
401
415
|
candidateNames: new Set(),
|
|
402
416
|
classMemberEntries,
|
|
403
417
|
classMemberEntriesByName: entriesByName(classMemberEntries),
|
|
@@ -412,6 +426,7 @@ const addSymbolTarget = (root, state, entry, declaration, namespace) => {
|
|
|
412
426
|
state.declarationLocationsByQuery
|
|
413
427
|
.get(entry.query.id)
|
|
414
428
|
.add(locationKey(location(root, declaration.name ?? declaration)));
|
|
429
|
+
state.targetNames.add(targetName(declaration));
|
|
415
430
|
const key = stableDeclarationKey(declaration, namespace);
|
|
416
431
|
const targets = state.targetsByKey.get(key) ?? [];
|
|
417
432
|
targets.push(entry);
|
|
@@ -422,6 +437,7 @@ const registerSymbolTargets = (root, state, entry) => {
|
|
|
422
437
|
state.candidateNames.add(entry.query.symbol.localName);
|
|
423
438
|
state.candidateNames.add(entry.query.symbol.exportedName);
|
|
424
439
|
if (entry.query.symbol.declarationKind === "export") {
|
|
440
|
+
state.exportedNames.add(entry.query.symbol.exportedName);
|
|
425
441
|
const moduleIdentity = canonicalFileIdentity(entry.query.symbol.absolutePath);
|
|
426
442
|
const entries = state.exportEntriesByModule.get(moduleIdentity) ?? new Set();
|
|
427
443
|
entries.add(entry);
|
|
@@ -447,45 +463,6 @@ const registerSymbolTargets = (root, state, entry) => {
|
|
|
447
463
|
state.contractRelationsByQuery.set(entry.query.id, entry.resolved.contractRelations);
|
|
448
464
|
};
|
|
449
465
|
|
|
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
|
-
}
|
|
462
|
-
return new Set(
|
|
463
|
-
declarations.flatMap((declaration) =>
|
|
464
|
-
[...declarationNamespaces(declaration)].flatMap(
|
|
465
|
-
(declaredNamespace) =>
|
|
466
|
-
targetsByKey.get(stableDeclarationKey(declaration, declaredNamespace)) ?? [],
|
|
467
|
-
),
|
|
468
|
-
),
|
|
469
|
-
);
|
|
470
|
-
};
|
|
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
|
-
|
|
489
466
|
const isDeclarationReference = (root, state, entry, node) => {
|
|
490
467
|
const locations = state.declarationLocationsByQuery.get(entry.query.id);
|
|
491
468
|
if (locations.has(locationKey(location(root, node)))) return true;
|
|
@@ -516,9 +493,6 @@ const importTypeSpecifier = (node) => {
|
|
|
516
493
|
return literal && isStringLiteralLikeNode(literal) ? literal : undefined;
|
|
517
494
|
};
|
|
518
495
|
|
|
519
|
-
const isDynamicImportCall = (node) =>
|
|
520
|
-
isCallExpression(node) && node.expression.getText(node.getSourceFile()) === "import";
|
|
521
|
-
|
|
522
496
|
const MODULE_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
523
497
|
|
|
524
498
|
const lexicalModuleIdentityForText = (project, sourceFile, specifierText) => {
|
|
@@ -554,19 +528,8 @@ const moduleIdentityForSpecifier = (project, specifier) => {
|
|
|
554
528
|
};
|
|
555
529
|
|
|
556
530
|
const namespaceImportDeclaration = (project, node) => {
|
|
557
|
-
const
|
|
558
|
-
if (!
|
|
559
|
-
const namespace = isPropertyAccessExpression(parent)
|
|
560
|
-
? parent.name === node
|
|
561
|
-
? parent.expression
|
|
562
|
-
: undefined
|
|
563
|
-
: isElementAccessExpression(parent) && parent.argumentExpression === node
|
|
564
|
-
? parent.expression
|
|
565
|
-
: isQualifiedName(parent) && parent.right === node
|
|
566
|
-
? parent.left
|
|
567
|
-
: undefined;
|
|
568
|
-
if (!namespace) return undefined;
|
|
569
|
-
if (!isIdentifier(namespace)) return undefined;
|
|
531
|
+
const namespace = qualifierExpression(node);
|
|
532
|
+
if (!namespace || !isIdentifier(namespace)) return undefined;
|
|
570
533
|
const symbol = project.checker.getSymbolAtLocation(namespace);
|
|
571
534
|
return symbol?.declarations
|
|
572
535
|
?.map((declaration) => declaration.resolve(project))
|
|
@@ -574,7 +537,7 @@ const namespaceImportDeclaration = (project, node) => {
|
|
|
574
537
|
};
|
|
575
538
|
|
|
576
539
|
const localAliasModuleEdgeDeclaration = (project, node) => {
|
|
577
|
-
const symbol = project
|
|
540
|
+
const symbol = referenceValueSymbol(project, node);
|
|
578
541
|
return symbol?.declarations
|
|
579
542
|
?.map((declaration) => moduleEdgeDeclaration(declaration.resolve(project)))
|
|
580
543
|
.find(Boolean);
|
|
@@ -605,7 +568,8 @@ const referencedModuleIdentity = (project, node) => {
|
|
|
605
568
|
|
|
606
569
|
const isExactExportReference = (project, entry, node) => {
|
|
607
570
|
if (entry.query.symbol.declarationKind !== "export") return true;
|
|
608
|
-
const referencedModule =
|
|
571
|
+
const referencedModule =
|
|
572
|
+
referencedModuleIdentity(project, node) ?? namespaceSourceIdentity(project, node);
|
|
609
573
|
if (!referencedModule) return false;
|
|
610
574
|
const queryModule = canonicalFileIdentity(entry.query.symbol.absolutePath);
|
|
611
575
|
return (
|
|
@@ -650,78 +614,37 @@ const classMemberEntrySetForProject = (state, project) => {
|
|
|
650
614
|
return entries;
|
|
651
615
|
};
|
|
652
616
|
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
};
|
|
656
|
-
|
|
657
|
-
const isExactElementReference = (node) => isElementNameNode(node) && typeof node.text === "string";
|
|
617
|
+
const isExactElementReference = (node) =>
|
|
618
|
+
isLiteralMemberName(node) && typeof node.text === "string";
|
|
658
619
|
|
|
659
|
-
const
|
|
660
|
-
if (!isStringLiteralLikeNode(node) || isExactElementReference(node)) return;
|
|
620
|
+
const recordNameUncertainty = (state, projectEntries, node) =>
|
|
661
621
|
(state.classMemberEntriesByName.get(node.text) ?? [])
|
|
662
622
|
.filter((entry) => projectEntries.has(entry))
|
|
663
623
|
.forEach((entry) => recordSymbolUncertainty(state, entry, "dynamic-member-access"));
|
|
664
|
-
};
|
|
665
|
-
|
|
666
|
-
const recordComputedMemberAccess = (state, project, entries, node) => {
|
|
667
|
-
if (
|
|
668
|
-
entries.size === 0 ||
|
|
669
|
-
state.projectsWithUnknownDynamicAccess.has(project) ||
|
|
670
|
-
!isElementAccessExpression(node) ||
|
|
671
|
-
node.argumentExpression === undefined ||
|
|
672
|
-
isStringLiteralLikeNode(node.argumentExpression)
|
|
673
|
-
) {
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
const receiverType = project.checker.getTypeAtLocation(node.expression);
|
|
677
|
-
const receiverProperties = project.checker.getPropertiesOfType(receiverType);
|
|
678
|
-
if (receiverProperties.length === 0) {
|
|
679
|
-
entries.forEach((entry) => recordSymbolUncertainty(state, entry, "dynamic-member-access"));
|
|
680
|
-
state.projectsWithUnknownDynamicAccess.add(project);
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
const matched = new Set(
|
|
684
|
-
receiverProperties
|
|
685
|
-
.filter((property) => state.candidateNames.has(property.name))
|
|
686
|
-
.flatMap((property) => [
|
|
687
|
-
...matchingSymbolEntries(project, property, "value", state.targetsByKey),
|
|
688
|
-
]),
|
|
689
|
-
);
|
|
690
|
-
matched.forEach((entry) => recordSymbolUncertainty(state, entry, "dynamic-member-access"));
|
|
691
|
-
};
|
|
692
624
|
|
|
693
|
-
const
|
|
694
|
-
if (!
|
|
695
|
-
|
|
696
|
-
if (!specifier || !isStringLiteralLikeNode(specifier)) {
|
|
697
|
-
(state.exportEntriesByProject.get(project) ?? []).forEach((entry) =>
|
|
698
|
-
recordSymbolUncertainty(state, entry, "dynamic-behavior"),
|
|
699
|
-
);
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
const moduleIdentity = moduleIdentityForSpecifier(project, specifier);
|
|
703
|
-
if (!moduleIdentity) return;
|
|
704
|
-
(state.exportEntriesByModule.get(moduleIdentity) ?? []).forEach((entry) =>
|
|
705
|
-
recordSymbolUncertainty(state, entry, "dynamic-behavior"),
|
|
706
|
-
);
|
|
625
|
+
const recordStringDispatchedMemberAccess = (state, projectEntries, node) => {
|
|
626
|
+
if (!isStringLiteralLikeNode(node) || isExactElementReference(node)) return;
|
|
627
|
+
recordNameUncertainty(state, projectEntries, node);
|
|
707
628
|
};
|
|
708
629
|
|
|
709
|
-
const referenceNamespaces = (project, node,
|
|
630
|
+
const referenceNamespaces = (project, node, rawSymbol) => {
|
|
710
631
|
const alias = aliasNode(node);
|
|
711
632
|
if (!alias || !isExportSpecifier(alias)) {
|
|
712
633
|
return [isTypePosition(node) ? "type" : "value"];
|
|
713
634
|
}
|
|
714
635
|
if (alias.isTypeOnly || alias.parent?.parent?.isTypeOnly) return ["type"];
|
|
715
636
|
const namespaces = new Set(
|
|
716
|
-
declarationsForSymbol(project,
|
|
717
|
-
...declarationNamespaces(declaration),
|
|
718
|
-
|
|
637
|
+
declarationsForSymbol(project, resolveAlias(project.checker, rawSymbol)).flatMap(
|
|
638
|
+
(declaration) => [...declarationNamespaces(declaration)],
|
|
639
|
+
),
|
|
719
640
|
);
|
|
720
641
|
return namespaces.size > 0 ? [...namespaces] : [isTypePosition(node) ? "type" : "value"];
|
|
721
642
|
};
|
|
722
643
|
|
|
723
644
|
const surfaceEntriesForNode = (state, project, node, namespace) => {
|
|
724
|
-
|
|
645
|
+
// Resolving the referenced module costs checker round trips, so skip every
|
|
646
|
+
// identifier that cannot name an export query.
|
|
647
|
+
if (!state.exportedNames.has(node.text) || moduleEdgeDeclaration(node)) return new Set();
|
|
725
648
|
const moduleIdentity = referencedModuleIdentity(project, node);
|
|
726
649
|
if (!moduleIdentity) return new Set();
|
|
727
650
|
const direct = [...(state.exportEntriesByModule.get(moduleIdentity) ?? [])].filter(
|
|
@@ -732,7 +655,7 @@ const surfaceEntriesForNode = (state, project, node, namespace) => {
|
|
|
732
655
|
for (const entry of direct) {
|
|
733
656
|
for (const { declaration } of entry.resolved.ownerContexts) {
|
|
734
657
|
const symbol = resolveAlias(project.checker, symbolForDeclaration(project, declaration));
|
|
735
|
-
matchingSymbolEntries(project, symbol, namespace, state
|
|
658
|
+
matchingSymbolEntries(project, symbol, namespace, state).forEach((related) =>
|
|
736
659
|
entries.add(related),
|
|
737
660
|
);
|
|
738
661
|
}
|
|
@@ -740,28 +663,32 @@ const surfaceEntriesForNode = (state, project, node, namespace) => {
|
|
|
740
663
|
return entries;
|
|
741
664
|
};
|
|
742
665
|
|
|
743
|
-
const scanSymbolUseFile = (root, state, project, sourceFile,
|
|
666
|
+
const scanSymbolUseFile = (root, state, project, sourceFile, names) => {
|
|
744
667
|
const projectEntries = classMemberEntrySetForProject(state, project);
|
|
745
668
|
const nodes = [];
|
|
746
669
|
visit(sourceFile, (node) => {
|
|
747
|
-
recordDynamicImportUncertainty(state, project, node);
|
|
670
|
+
recordDynamicImportUncertainty(state, project, node, moduleIdentityForSpecifier);
|
|
748
671
|
if (projectEntries.size > 0) {
|
|
749
672
|
recordStringDispatchedMemberAccess(state, projectEntries, node);
|
|
750
673
|
recordComputedMemberAccess(state, project, projectEntries, node);
|
|
751
674
|
}
|
|
752
|
-
if (isIdentifierReference(node, undefined
|
|
675
|
+
if (isIdentifierReference(node, isJavaScriptFile(sourceFile) ? undefined : names)) {
|
|
753
676
|
nodes.push(node);
|
|
754
677
|
return;
|
|
755
678
|
}
|
|
756
679
|
if (isElementReference(node, state.candidateNames)) nodes.push(node);
|
|
757
680
|
});
|
|
758
681
|
const symbols = project.checker.getSymbolAtLocation(nodes);
|
|
682
|
+
const references = shorthandReferenceSymbols(project, nodes, symbols, state);
|
|
683
|
+
references.unresolved.forEach((node) => recordNameUncertainty(state, projectEntries, node));
|
|
759
684
|
nodes.forEach((node, index) => {
|
|
760
685
|
const rawSymbol = symbols[index];
|
|
761
|
-
|
|
762
|
-
referenceNamespaces(project, node,
|
|
686
|
+
recordNamespaceUse(state, project, node, rawSymbol);
|
|
687
|
+
referenceNamespaces(project, node, rawSymbol).forEach((namespace) => {
|
|
763
688
|
const entries = new Set([
|
|
764
|
-
...
|
|
689
|
+
...references.symbols[index].flatMap((symbol) => [
|
|
690
|
+
...matchingAliasEntries(project, symbol, namespace, state),
|
|
691
|
+
]),
|
|
765
692
|
...surfaceEntriesForNode(state, project, node, namespace),
|
|
766
693
|
]);
|
|
767
694
|
entries.forEach((entry) => recordSymbolUse(root, state, project, entry, node, namespace));
|
|
@@ -769,7 +696,7 @@ const scanSymbolUseFile = (root, state, project, sourceFile, includeDefaultImpor
|
|
|
769
696
|
});
|
|
770
697
|
};
|
|
771
698
|
|
|
772
|
-
const scanSymbolUseProjects = (root, state, projects,
|
|
699
|
+
const scanSymbolUseProjects = (root, state, projects, names) => {
|
|
773
700
|
const scannedFiles = new Set();
|
|
774
701
|
let sourceScanCount = 0;
|
|
775
702
|
for (const project of projects) {
|
|
@@ -778,7 +705,7 @@ const scanSymbolUseProjects = (root, state, projects, includeDefaultImports) =>
|
|
|
778
705
|
if (scannedFiles.has(fileIdentity)) continue;
|
|
779
706
|
scannedFiles.add(fileIdentity);
|
|
780
707
|
sourceScanCount += 1;
|
|
781
|
-
scanSymbolUseFile(root, state, project, sourceFile,
|
|
708
|
+
scanSymbolUseFile(root, state, project, sourceFile, names);
|
|
782
709
|
}
|
|
783
710
|
}
|
|
784
711
|
return sourceScanCount;
|
|
@@ -787,10 +714,8 @@ const scanSymbolUseProjects = (root, state, projects, includeDefaultImports) =>
|
|
|
787
714
|
const batchSymbolUseEvidence = (root, resolvedQueries, scanProjects, evidenceLimit) => {
|
|
788
715
|
const state = symbolUseBatchState(resolvedQueries, evidenceLimit);
|
|
789
716
|
resolvedQueries.forEach((entry) => registerSymbolTargets(root, state, entry));
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
);
|
|
793
|
-
const sourceScanCount = scanSymbolUseProjects(root, state, scanProjects, includeDefaultImports);
|
|
717
|
+
const names = referenceNames(scanProjects, [...state.candidateNames, ...state.targetNames]);
|
|
718
|
+
const sourceScanCount = scanSymbolUseProjects(root, state, scanProjects, names);
|
|
794
719
|
return {
|
|
795
720
|
evidenceByQuery: state.evidenceByQuery,
|
|
796
721
|
totalByQuery: state.totalByQuery,
|
|
@@ -1013,7 +938,7 @@ const frameworkContractRelationsFor = (root, project, declaration, contracts, me
|
|
|
1013
938
|
: "extends";
|
|
1014
939
|
for (const heritageType of clause.types ?? []) {
|
|
1015
940
|
const rawSymbol =
|
|
1016
|
-
project.checker.getSymbolAtLocation(heritageType
|
|
941
|
+
project.checker.getSymbolAtLocation(heritageTypeName(heritageType)) ??
|
|
1017
942
|
project.checker.getTypeAtLocation(heritageType).aliasSymbol ??
|
|
1018
943
|
project.checker.getTypeAtLocation(heritageType).symbol;
|
|
1019
944
|
const symbol = resolveAlias(project.checker, rawSymbol);
|
|
@@ -1294,31 +1219,6 @@ const wildcardPattern = (target) => {
|
|
|
1294
1219
|
return new RegExp(`^${escaped}$`, "u");
|
|
1295
1220
|
};
|
|
1296
1221
|
|
|
1297
|
-
const pathWithin = (root, candidate) =>
|
|
1298
|
-
candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
|
1299
|
-
|
|
1300
|
-
const packageAt = (directory) => {
|
|
1301
|
-
const packageFile = path.join(directory, "package.json");
|
|
1302
|
-
if (!existsSync(packageFile)) return undefined;
|
|
1303
|
-
try {
|
|
1304
|
-
return { root: directory, json: JSON.parse(readFileSync(packageFile, "utf8")) };
|
|
1305
|
-
} catch {
|
|
1306
|
-
return { root: directory, json: {} };
|
|
1307
|
-
}
|
|
1308
|
-
};
|
|
1309
|
-
|
|
1310
|
-
const projectPackage = (root, project) => {
|
|
1311
|
-
const normalizedRoot = path.resolve(root);
|
|
1312
|
-
let current = path.dirname(project.configFileName);
|
|
1313
|
-
while (pathWithin(normalizedRoot, current)) {
|
|
1314
|
-
const found = packageAt(current);
|
|
1315
|
-
if (found) return found;
|
|
1316
|
-
if (current === normalizedRoot) break;
|
|
1317
|
-
current = path.dirname(current);
|
|
1318
|
-
}
|
|
1319
|
-
return { root: normalizedRoot, json: {} };
|
|
1320
|
-
};
|
|
1321
|
-
|
|
1322
1222
|
const ancestorRoots = (root, start) => {
|
|
1323
1223
|
const normalizedRoot = path.resolve(root);
|
|
1324
1224
|
const roots = [];
|
|
@@ -1386,12 +1286,6 @@ const addPathEntry = (entries, sourceFiles, roots, target) => {
|
|
|
1386
1286
|
);
|
|
1387
1287
|
};
|
|
1388
1288
|
|
|
1389
|
-
const packagePathTargets = (project, packageJson) => {
|
|
1390
|
-
const packageName = packageJson.name;
|
|
1391
|
-
if (typeof packageName !== "string") return [];
|
|
1392
|
-
return project.compilerOptions.paths?.[packageName] ?? [];
|
|
1393
|
-
};
|
|
1394
|
-
|
|
1395
1289
|
const discoverEntryPoints = (root, project, requested) => {
|
|
1396
1290
|
const sourceFiles = sourceFileIndex(project);
|
|
1397
1291
|
if (requested.length > 0) return requestedEntryPoints(requested, sourceFiles);
|
|
@@ -2205,7 +2099,7 @@ const executeDisposableSemanticAnalysis = (input) => {
|
|
|
2205
2099
|
|
|
2206
2100
|
export const analyzeSemanticQueries = (
|
|
2207
2101
|
{ root, projects, queries, evidenceLimit },
|
|
2208
|
-
{ createApi = (cwd) =>
|
|
2102
|
+
{ createApi = (cwd) => createBackendApi({ cwd }) } = {},
|
|
2209
2103
|
) => {
|
|
2210
2104
|
if (queries.length === 0) return emptyRequestedAnalysis();
|
|
2211
2105
|
const setupStartedAt = performance.now();
|
|
@@ -2224,7 +2118,10 @@ export const analyzeSemanticQueries = (
|
|
|
2224
2118
|
}
|
|
2225
2119
|
};
|
|
2226
2120
|
|
|
2227
|
-
export const createSemanticSession = (
|
|
2121
|
+
export const createSemanticSession = (
|
|
2122
|
+
root,
|
|
2123
|
+
{ createApi = (cwd) => createBackendApi({ cwd }) } = {},
|
|
2124
|
+
) => {
|
|
2228
2125
|
const api = createApi(root);
|
|
2229
2126
|
const state = {
|
|
2230
2127
|
openFiles: new Set(),
|