@cplieger/deadset-ts 5.0.0 → 5.1.1
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/README.md +1 -1
- package/dist/src/analysis.js +145 -39
- package/dist/src/configuration-files.js +108 -4
- package/dist/src/conformance-record.js +3 -9
- package/dist/src/contextual-uses.js +247 -0
- package/dist/src/convention-rows.js +6 -0
- package/dist/src/conventions.js +123 -14
- package/dist/src/dependencies.js +8 -2
- package/dist/src/destructuring.js +6 -6
- package/dist/src/entry-points.js +8 -5
- package/dist/src/exempt-classes.js +6 -1
- package/dist/src/exempt.js +35 -16
- package/dist/src/file-facts.js +23 -20
- package/dist/src/findings/completion.js +27 -3
- package/dist/src/findings/interfaces.js +6 -2
- package/dist/src/findings/reads-and-writes.js +53 -9
- package/dist/src/findings/unused-declarations.js +40 -1
- package/dist/src/generated-file.js +24 -0
- package/dist/src/graph.js +1 -1
- package/dist/src/interface-satisfaction.js +2 -68
- package/dist/src/inventory.js +17 -4
- package/dist/src/kinds.js +4 -3
- package/dist/src/manifest.js +29 -4
- package/dist/src/outside-tests.js +139 -0
- package/dist/src/overrides.js +104 -0
- package/dist/src/paths.js +17 -0
- package/dist/src/query.js +15 -0
- package/dist/src/references.js +229 -11
- package/dist/src/resolve.js +12 -11
- package/dist/src/roots.js +121 -37
- package/dist/src/session.js +4 -3
- package/dist/src/stores.js +3 -0
- package/dist/src/template-field.js +60 -3
- package/dist/src/value-flow.js +74 -0
- package/dist/src/version.js +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +194 -49
- package/src/configuration-files.ts +137 -2
- package/src/conformance-record.ts +3 -10
- package/src/contextual-uses.ts +358 -0
- package/src/convention-rows.ts +8 -0
- package/src/conventions.ts +161 -13
- package/src/dependencies.ts +14 -1
- package/src/destructuring.ts +16 -6
- package/src/entry-points.ts +16 -3
- package/src/exempt-classes.ts +6 -1
- package/src/exempt.ts +67 -16
- package/src/file-facts.ts +26 -20
- package/src/findings/completion.ts +31 -3
- package/src/findings/interfaces.ts +9 -2
- package/src/findings/reads-and-writes.ts +70 -9
- package/src/findings/unused-declarations.ts +51 -1
- package/src/generated-file.ts +27 -0
- package/src/graph.ts +4 -4
- package/src/interface-satisfaction.ts +1 -92
- package/src/inventory.ts +19 -4
- package/src/kinds.ts +5 -3
- package/src/manifest.ts +40 -4
- package/src/outside-tests.ts +191 -0
- package/src/overrides.ts +136 -0
- package/src/paths.ts +18 -0
- package/src/query.ts +26 -0
- package/src/references.ts +294 -22
- package/src/resolve.ts +14 -13
- package/src/roots.ts +152 -44
- package/src/session.ts +4 -2
- package/src/stores.ts +9 -0
- package/src/template-field.ts +65 -3
- package/src/value-flow.ts +104 -0
- package/src/version.ts +1 -1
package/src/references.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isArrayLiteralExpression,
|
|
3
|
+
isAsExpression,
|
|
3
4
|
isBinaryExpression,
|
|
4
5
|
isCallExpression,
|
|
5
6
|
isDecorator,
|
|
@@ -32,14 +33,17 @@ import {
|
|
|
32
33
|
} from "@typescript/native/unstable/ast";
|
|
33
34
|
import type { Symbol as TSSymbol } from "@typescript/native/unstable/sync";
|
|
34
35
|
import { aliasChains, type ChainTarget } from "./alias-chain.ts";
|
|
36
|
+
import { contextualUses, type FlowAt, type LiteralAt } from "./contextual-uses.ts";
|
|
35
37
|
import { destructuring, propertyReadsOf, type PropertyRead } from "./destructuring.ts";
|
|
36
38
|
import { globExpression } from "./glob.ts";
|
|
37
39
|
import { declarationsByName, nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
38
|
-
import {
|
|
40
|
+
import { overrideReferences } from "./overrides.ts";
|
|
41
|
+
import { dirnamePath, queriedModulePath, relativePath, resolvePath } from "./paths.ts";
|
|
39
42
|
import { byPosition, isComponentFile, renderPosition, type Position } from "./position.ts";
|
|
40
|
-
import { DEFAULT_BATCH_CAP, UNANSWERED, type Answer } from "./query.ts";
|
|
43
|
+
import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED, type Answer } from "./query.ts";
|
|
41
44
|
import type { ProjectView } from "./session.ts";
|
|
42
45
|
import { typeQueryAliases } from "./type-query-alias.ts";
|
|
46
|
+
import { handedOn, valuesOf } from "./value-flow.ts";
|
|
43
47
|
|
|
44
48
|
/**
|
|
45
49
|
* Every use one project's own files make of the project's own declarations.
|
|
@@ -68,15 +72,23 @@ import { typeQueryAliases } from "./type-query-alias.ts";
|
|
|
68
72
|
export type Use = "read" | "write" | "evaluation" | "decorator";
|
|
69
73
|
|
|
70
74
|
/**
|
|
71
|
-
* Which path answered for one reference: `batch`,
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* `by-name`, every declaration
|
|
75
|
+
* Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`,
|
|
76
|
+
* the per-node lookup for its residue; `shorthand`, the value `{ a }` reads; `alias`, a step
|
|
77
|
+
* along an import or export chain; `syntax`, the tree alone (a decorator's target, a
|
|
78
|
+
* component's markup); `destructured`, the property a pattern names; `contextual`, the member
|
|
79
|
+
* a literal property writes or a value's position reads; `override`, the override a base
|
|
80
|
+
* member's call dispatches to; `by-name`, every declaration so spelled, where the checker failed.
|
|
77
81
|
*/
|
|
78
82
|
export type Resolution =
|
|
79
|
-
|
|
83
|
+
| "batch"
|
|
84
|
+
| "resolved-symbol"
|
|
85
|
+
| "shorthand"
|
|
86
|
+
| "alias"
|
|
87
|
+
| "syntax"
|
|
88
|
+
| "destructured"
|
|
89
|
+
| "contextual"
|
|
90
|
+
| "override"
|
|
91
|
+
| "by-name";
|
|
80
92
|
|
|
81
93
|
/** One use of one declaration by one declaration. */
|
|
82
94
|
export interface Reference {
|
|
@@ -102,6 +114,14 @@ export interface Reference {
|
|
|
102
114
|
* the consumer's root.
|
|
103
115
|
*/
|
|
104
116
|
readonly consumer?: string;
|
|
117
|
+
/** Made by a target file no project of the run holds, so it enters as a consumer's reference does. */
|
|
118
|
+
readonly unheld?: true;
|
|
119
|
+
/**
|
|
120
|
+
* On a write, the local names of the writing file's import bindings that only the
|
|
121
|
+
* values its file writes into the same declaration use, which deleting the
|
|
122
|
+
* declaration with its writes deletes too.
|
|
123
|
+
*/
|
|
124
|
+
readonly alone?: readonly string[];
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
/** The consumer one reference pass reads the files of, beside the target it names. */
|
|
@@ -148,6 +168,13 @@ export interface ReferenceCost {
|
|
|
148
168
|
* pattern destructures, and each step a nested assignment target takes into its value.
|
|
149
169
|
*/
|
|
150
170
|
readonly patternLookups: number;
|
|
171
|
+
/**
|
|
172
|
+
* Batched lookups for the contextual uses: the contextual types of the object literals
|
|
173
|
+
* and of the values that carry a member, and the values' types, each per capped run.
|
|
174
|
+
*/
|
|
175
|
+
readonly contextualBatches: number;
|
|
176
|
+
/** Per-type lookups for the contextual uses: each distinct type's constituents, properties and symbol. */
|
|
177
|
+
readonly contextualLookups: number;
|
|
151
178
|
}
|
|
152
179
|
|
|
153
180
|
/** One project's references, in position order, and what reading them cost. */
|
|
@@ -280,7 +307,11 @@ function markWrite(target: Node, writes: Set<number>): void {
|
|
|
280
307
|
return;
|
|
281
308
|
}
|
|
282
309
|
if (isElementAccessExpression(held)) {
|
|
283
|
-
|
|
310
|
+
// A store through a member's value lands in storage every holder of the value
|
|
311
|
+
// reads, so it reads the member; a variable only ever stored into stays written.
|
|
312
|
+
if (!isPropertyAccessExpression(unparenthesized(held.expression))) {
|
|
313
|
+
markWrite(held.expression, writes);
|
|
314
|
+
}
|
|
284
315
|
return;
|
|
285
316
|
}
|
|
286
317
|
if (isArrayLiteralExpression(held)) {
|
|
@@ -407,6 +438,8 @@ interface Read extends PropertyRead {
|
|
|
407
438
|
/** Everything one file's walk found to resolve, and the decorators it needs no resolution for. */
|
|
408
439
|
interface FileSites {
|
|
409
440
|
readonly uses: readonly Site[];
|
|
441
|
+
readonly literals: readonly LiteralAt[];
|
|
442
|
+
readonly flows: readonly FlowAt[];
|
|
410
443
|
readonly reads: readonly Read[];
|
|
411
444
|
readonly links: readonly Link[];
|
|
412
445
|
readonly evaluations: readonly Evaluation[];
|
|
@@ -447,6 +480,24 @@ function evaluatedBy(node: Node): Node | undefined {
|
|
|
447
480
|
return undefined;
|
|
448
481
|
}
|
|
449
482
|
|
|
483
|
+
/**
|
|
484
|
+
* The own file a module specifier with a query evaluates ({@link queriedModulePath}): the
|
|
485
|
+
* checker resolves the whole specifier to whatever module declaration matches it.
|
|
486
|
+
*/
|
|
487
|
+
function queriedFile(
|
|
488
|
+
file: SourceFile,
|
|
489
|
+
specifier: Node,
|
|
490
|
+
fileIdByName: ReadonlyMap<string, string>,
|
|
491
|
+
): string | undefined {
|
|
492
|
+
if (!isStringLiteral(specifier) && !isNoSubstitutionTemplateLiteral(specifier)) {
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
const path = queriedModulePath(specifier.text);
|
|
496
|
+
return path === undefined
|
|
497
|
+
? undefined
|
|
498
|
+
: fileIdByName.get(resolvePath(dirnamePath(file.fileName), path));
|
|
499
|
+
}
|
|
500
|
+
|
|
450
501
|
/**
|
|
451
502
|
* The re-exports one node writes, if it is an export declaration. A specifier and a
|
|
452
503
|
* namespace export are declarations whose names are where they are written, so no use is
|
|
@@ -491,6 +542,30 @@ function linksOf(
|
|
|
491
542
|
return found;
|
|
492
543
|
}
|
|
493
544
|
|
|
545
|
+
/**
|
|
546
|
+
* Whether one object literal is a destructuring target, written on the left of an
|
|
547
|
+
* assignment or as a `for` loop's initializer, through the literals and spreads it nests in.
|
|
548
|
+
*/
|
|
549
|
+
function isAssignmentPattern(node: Node): boolean {
|
|
550
|
+
let child = node;
|
|
551
|
+
let parent = node.parent;
|
|
552
|
+
while (
|
|
553
|
+
isParenthesizedExpression(parent) ||
|
|
554
|
+
isArrayLiteralExpression(parent) ||
|
|
555
|
+
isObjectLiteralExpression(parent) ||
|
|
556
|
+
isSpreadElement(parent) ||
|
|
557
|
+
isSpreadAssignment(parent) ||
|
|
558
|
+
(isPropertyAssignment(parent) && parent.initializer === child)
|
|
559
|
+
) {
|
|
560
|
+
child = parent;
|
|
561
|
+
parent = parent.parent;
|
|
562
|
+
}
|
|
563
|
+
if (isBinaryExpression(parent)) {
|
|
564
|
+
return parent.operatorToken.kind === SyntaxKind.EqualsToken && parent.left === child;
|
|
565
|
+
}
|
|
566
|
+
return (isForInStatement(parent) || isForOfStatement(parent)) && parent.initializer === child;
|
|
567
|
+
}
|
|
568
|
+
|
|
494
569
|
/**
|
|
495
570
|
* Every name node one file holds that is a use rather than a declaration, in source
|
|
496
571
|
* order, each with its enclosing declaration and its use, and every re-export the file
|
|
@@ -510,6 +585,8 @@ function sitesOf(
|
|
|
510
585
|
const links: Link[] = [];
|
|
511
586
|
const evaluations: Evaluation[] = [];
|
|
512
587
|
const decorated: Decorated[] = [];
|
|
588
|
+
const literals: LiteralAt[] = [];
|
|
589
|
+
const flows: FlowAt[] = [];
|
|
513
590
|
const writes = new Set<number>();
|
|
514
591
|
const alsoRead = new Set<number>();
|
|
515
592
|
const declared = new Set<number>();
|
|
@@ -552,6 +629,13 @@ function sitesOf(
|
|
|
552
629
|
}
|
|
553
630
|
markNames(node);
|
|
554
631
|
markStores(node, writes, alsoRead);
|
|
632
|
+
if (isObjectLiteralExpression(node) && !isAssignmentPattern(node)) {
|
|
633
|
+
literals.push({ literal: node, from: enclosing });
|
|
634
|
+
}
|
|
635
|
+
// An assertion needs only comparability, so no member of its operand is read by it.
|
|
636
|
+
for (const written of isAsExpression(node) ? [] : handedOn(node)) {
|
|
637
|
+
flows.push(...valuesOf(written).map((value) => ({ value, from: enclosing })));
|
|
638
|
+
}
|
|
555
639
|
for (const read of propertyReadsOf(node)) {
|
|
556
640
|
reads.push({ ...read, from: enclosing });
|
|
557
641
|
}
|
|
@@ -575,9 +659,18 @@ function sitesOf(
|
|
|
575
659
|
};
|
|
576
660
|
|
|
577
661
|
file.forEachChild(visit);
|
|
578
|
-
return { uses: found, reads, links, evaluations, decorated };
|
|
662
|
+
return { uses: found, literals, flows, reads, links, evaluations, decorated };
|
|
579
663
|
}
|
|
580
664
|
|
|
665
|
+
/** The declaration kinds of a property an object literal declares. */
|
|
666
|
+
const LITERAL_PROPERTY_KINDS: ReadonlySet<SyntaxKind> = new Set([
|
|
667
|
+
SyntaxKind.PropertyAssignment,
|
|
668
|
+
SyntaxKind.ShorthandPropertyAssignment,
|
|
669
|
+
SyntaxKind.MethodDeclaration,
|
|
670
|
+
SyntaxKind.GetAccessor,
|
|
671
|
+
SyntaxKind.SetAccessor,
|
|
672
|
+
]);
|
|
673
|
+
|
|
581
674
|
/** The rule a file the analysis classified as test-support code is counted under. */
|
|
582
675
|
const TEST_SUPPORT_RULE = "test-support";
|
|
583
676
|
|
|
@@ -691,6 +784,70 @@ function importBindings(file: SourceFile): readonly { node: Node; whole: boolean
|
|
|
691
784
|
return found;
|
|
692
785
|
}
|
|
693
786
|
|
|
787
|
+
/** The value one assignment stores through the name it writes, where it is a plain assignment. */
|
|
788
|
+
function storedValue(name: Node): Node | undefined {
|
|
789
|
+
let target: Node = name;
|
|
790
|
+
if (isPropertyAccessExpression(name.parent) && name.parent.name === name) {
|
|
791
|
+
target = name.parent;
|
|
792
|
+
}
|
|
793
|
+
while (isParenthesizedExpression(target.parent)) {
|
|
794
|
+
target = target.parent;
|
|
795
|
+
}
|
|
796
|
+
const parent = target.parent;
|
|
797
|
+
return isBinaryExpression(parent) &&
|
|
798
|
+
parent.operatorToken.kind === SyntaxKind.EqualsToken &&
|
|
799
|
+
parent.left === target
|
|
800
|
+
? parent.right
|
|
801
|
+
: undefined;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* The import bindings of one file that only the values written into one declaration
|
|
806
|
+
* use, for each write into it. A binding is named by an identifier that spells its
|
|
807
|
+
* local name and is not the member a property access names.
|
|
808
|
+
*/
|
|
809
|
+
function aloneIn(
|
|
810
|
+
file: SourceFile,
|
|
811
|
+
uses: readonly Site[],
|
|
812
|
+
writes: readonly { index: number; value: Node }[],
|
|
813
|
+
found: readonly Reference[],
|
|
814
|
+
): readonly { index: number; names: readonly string[] }[] {
|
|
815
|
+
const bindings = new Set(importBindings(file).map((binding) => binding.node.getText()));
|
|
816
|
+
if (bindings.size === 0) {
|
|
817
|
+
return [];
|
|
818
|
+
}
|
|
819
|
+
const occurrences = new Map<string, Node[]>();
|
|
820
|
+
for (const site of uses) {
|
|
821
|
+
const text = site.node.getText();
|
|
822
|
+
const parent = site.node.parent;
|
|
823
|
+
if (!bindings.has(text) || (isPropertyAccessExpression(parent) && parent.name === site.node)) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
if (!occurrences.get(text)?.includes(site.node)) {
|
|
827
|
+
occurrences.set(text, [...(occurrences.get(text) ?? []), site.node]);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const inside = (node: Node, value: Node): boolean =>
|
|
831
|
+
node.pos >= value.pos && node.end <= value.end;
|
|
832
|
+
const result: { index: number; names: readonly string[] }[] = [];
|
|
833
|
+
for (const write of writes) {
|
|
834
|
+
const to = found[write.index]?.to;
|
|
835
|
+
const sameTarget = writes.filter((one) => found[one.index]?.to === to);
|
|
836
|
+
const names = [...occurrences]
|
|
837
|
+
.filter(
|
|
838
|
+
([, nodes]) =>
|
|
839
|
+
nodes.some((node) => inside(node, write.value)) &&
|
|
840
|
+
nodes.every((node) => sameTarget.some((one) => inside(node, one.value))),
|
|
841
|
+
)
|
|
842
|
+
.map(([name]) => name)
|
|
843
|
+
.sort(compare);
|
|
844
|
+
if (names.length > 0) {
|
|
845
|
+
result.push({ index: write.index, names });
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return result;
|
|
849
|
+
}
|
|
850
|
+
|
|
694
851
|
/** Two strings ordered bytewise, which is the order every set of a run is read in. */
|
|
695
852
|
function compare(a: string, b: string): number {
|
|
696
853
|
if (a === b) {
|
|
@@ -727,7 +884,8 @@ export function references<Brand>(
|
|
|
727
884
|
const found: Reference[] = [];
|
|
728
885
|
const tests: string[] = [];
|
|
729
886
|
const chains = aliasChains(project, held);
|
|
730
|
-
const
|
|
887
|
+
const tables = propertyTables(project.queries);
|
|
888
|
+
const destructured = destructuring(project, cap, tables);
|
|
731
889
|
let batched = 0;
|
|
732
890
|
let fileBatches = 0;
|
|
733
891
|
let residueFallbacks = 0;
|
|
@@ -745,6 +903,7 @@ export function references<Brand>(
|
|
|
745
903
|
relativePath(consumer.root, file.fileName) !== undefined &&
|
|
746
904
|
relativePath(targetRoot, file.fileName) === undefined,
|
|
747
905
|
);
|
|
906
|
+
const ownPaths = project.ownPaths();
|
|
748
907
|
const fileIds = new Set(
|
|
749
908
|
held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id),
|
|
750
909
|
);
|
|
@@ -859,19 +1018,102 @@ export function references<Brand>(
|
|
|
859
1018
|
return project.resolvedSymbolAt(project.handle(site.node));
|
|
860
1019
|
};
|
|
861
1020
|
|
|
862
|
-
|
|
1021
|
+
const walked = files.map((file) => {
|
|
1022
|
+
const fileId = declaredAt(file) ?? "";
|
|
1023
|
+
return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
|
|
1024
|
+
});
|
|
1025
|
+
const fileIdByName = new Map(
|
|
1026
|
+
walked.filter((one) => fileIds.has(one.fileId)).map((one) => [one.file.fileName, one.fileId]),
|
|
1027
|
+
);
|
|
1028
|
+
const contextual = contextualUses(
|
|
1029
|
+
project,
|
|
1030
|
+
held,
|
|
1031
|
+
chains,
|
|
1032
|
+
walked.flatMap((one) => one.literals),
|
|
1033
|
+
walked.flatMap((one) => one.flows),
|
|
1034
|
+
cap,
|
|
1035
|
+
tables,
|
|
1036
|
+
);
|
|
1037
|
+
const writtenBy = new Map<string, readonly string[]>();
|
|
1038
|
+
for (const write of contextual.writes) {
|
|
1039
|
+
const property = write.at.parent;
|
|
1040
|
+
writtenBy.set(nodeKey(property.getSourceFile(), property), write.ids);
|
|
1041
|
+
}
|
|
1042
|
+
const forwardedCache = new Map<number, readonly ChainTarget[]>();
|
|
1043
|
+
/**
|
|
1044
|
+
* The members a literal property one symbol declares writes: a use of the property
|
|
1045
|
+
* reaches them, because the literal's type carries the data the member describes.
|
|
1046
|
+
*/
|
|
1047
|
+
const forwarded = (symbol: TSSymbol): readonly ChainTarget[] => {
|
|
1048
|
+
const known = forwardedCache.get(symbol.id);
|
|
1049
|
+
if (known !== undefined) {
|
|
1050
|
+
return known;
|
|
1051
|
+
}
|
|
1052
|
+
const found: ChainTarget[] = [];
|
|
1053
|
+
if (writtenBy.size > 0) {
|
|
1054
|
+
for (const handle of symbol.declarations) {
|
|
1055
|
+
if (!LITERAL_PROPERTY_KINDS.has(handle.kind) || !ownPaths.has(handle.path)) {
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
const node = project.declarationAt(handle)?.node;
|
|
1059
|
+
const ids =
|
|
1060
|
+
node === undefined ? undefined : writtenBy.get(nodeKey(node.getSourceFile(), node));
|
|
1061
|
+
for (const id of ids ?? []) {
|
|
1062
|
+
if (!found.some((target) => target.id === id)) {
|
|
1063
|
+
found.push({ id, stepped: true, guessed: false });
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
forwardedCache.set(symbol.id, found);
|
|
1069
|
+
return found;
|
|
1070
|
+
};
|
|
1071
|
+
/** One resolved symbol's declarations along its chain, and the members its literal properties write. */
|
|
1072
|
+
const targetsOf = (symbol: TSSymbol): readonly ChainTarget[] => {
|
|
1073
|
+
const through = forwarded(symbol);
|
|
1074
|
+
return through.length === 0 ? chains.chainOf(symbol) : [...chains.chainOf(symbol), ...through];
|
|
1075
|
+
};
|
|
1076
|
+
const testFile = new Map<SourceFile, boolean>();
|
|
1077
|
+
for (const { file } of walked) {
|
|
863
1078
|
const path = renderPosition(file, root, 0).path;
|
|
864
1079
|
const test = patterns.some((pattern) => pattern.test(path));
|
|
1080
|
+
testFile.set(file, test);
|
|
865
1081
|
if (test) {
|
|
866
1082
|
tests.push(path);
|
|
867
1083
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1084
|
+
}
|
|
1085
|
+
/** Each write reference's index in `found`, by its file, with the value it stores. */
|
|
1086
|
+
const valued = new Map<SourceFile, { index: number; value: Node }[]>();
|
|
1087
|
+
const holdsValue = (file: SourceFile, value: Node | undefined): void => {
|
|
1088
|
+
if (value !== undefined) {
|
|
1089
|
+
valued.set(file, [...(valued.get(file) ?? []), { index: found.length - 1, value }]);
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
for (const [use, sites] of [
|
|
1093
|
+
["write", contextual.writes],
|
|
1094
|
+
["read", contextual.reads],
|
|
1095
|
+
] as const) {
|
|
1096
|
+
for (const site of sites) {
|
|
1097
|
+
const file = site.at.getSourceFile();
|
|
1098
|
+
const position = renderPosition(file, root, site.at.getStart());
|
|
1099
|
+
for (const id of site.ids) {
|
|
1100
|
+
found.push({
|
|
1101
|
+
from: site.from,
|
|
1102
|
+
to: id,
|
|
1103
|
+
position,
|
|
1104
|
+
use,
|
|
1105
|
+
resolution: "contextual",
|
|
1106
|
+
test: testFile.get(file) === true,
|
|
1107
|
+
});
|
|
1108
|
+
if (use === "write") {
|
|
1109
|
+
holdsValue(file, site.at.parent);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
for (const { file, fileId, uses, reads, links, evaluations, decorated } of walked) {
|
|
1116
|
+
const test = testFile.get(file) === true;
|
|
875
1117
|
// A component file's markup may use any binding its blocks declare at the top level.
|
|
876
1118
|
const markup = isComponentFile(file);
|
|
877
1119
|
const bindings = markup ? importBindings(file) : [];
|
|
@@ -943,6 +1185,17 @@ export function references<Brand>(
|
|
|
943
1185
|
}
|
|
944
1186
|
|
|
945
1187
|
for (const evaluation of evaluations) {
|
|
1188
|
+
const queried = queriedFile(file, evaluation.node, fileIdByName);
|
|
1189
|
+
if (queried !== undefined) {
|
|
1190
|
+
found.push({
|
|
1191
|
+
from: evaluation.from,
|
|
1192
|
+
to: queried,
|
|
1193
|
+
position: renderPosition(file, root, evaluation.node.getStart()),
|
|
1194
|
+
use: "evaluation",
|
|
1195
|
+
resolution: "syntax",
|
|
1196
|
+
test,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
946
1199
|
const module = answered.get(evaluation.node);
|
|
947
1200
|
if (module === undefined) {
|
|
948
1201
|
continue;
|
|
@@ -1022,7 +1275,7 @@ export function references<Brand>(
|
|
|
1022
1275
|
stepped: false,
|
|
1023
1276
|
guessed: true,
|
|
1024
1277
|
}))
|
|
1025
|
-
:
|
|
1278
|
+
: targetsOf(symbol);
|
|
1026
1279
|
for (const target of targets) {
|
|
1027
1280
|
found.push({
|
|
1028
1281
|
from: read.from,
|
|
@@ -1052,7 +1305,7 @@ export function references<Brand>(
|
|
|
1052
1305
|
const targets = throughAliases(
|
|
1053
1306
|
symbol === UNANSWERED
|
|
1054
1307
|
? named(site.node).map((id) => ({ id, stepped: false, guessed: true }))
|
|
1055
|
-
:
|
|
1308
|
+
: targetsOf(symbol),
|
|
1056
1309
|
);
|
|
1057
1310
|
for (const target of targets) {
|
|
1058
1311
|
found.push({
|
|
@@ -1063,10 +1316,27 @@ export function references<Brand>(
|
|
|
1063
1316
|
resolution: target.guessed ? "by-name" : target.stepped ? "alias" : direct,
|
|
1064
1317
|
test,
|
|
1065
1318
|
});
|
|
1319
|
+
if (site.use === "write") {
|
|
1320
|
+
holdsValue(file, storedValue(site.node));
|
|
1321
|
+
}
|
|
1066
1322
|
}
|
|
1067
1323
|
}
|
|
1068
1324
|
}
|
|
1069
1325
|
|
|
1326
|
+
const usesOf = new Map(walked.map((one) => [one.file, one.uses]));
|
|
1327
|
+
for (const [file, writes] of valued) {
|
|
1328
|
+
const uses = usesOf.get(file) ?? [];
|
|
1329
|
+
for (const { index, names } of aloneIn(file, uses, writes, found)) {
|
|
1330
|
+
const reference = found[index];
|
|
1331
|
+
if (reference !== undefined) {
|
|
1332
|
+
found[index] = { ...reference, alone: names };
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
if (consumer === undefined) {
|
|
1338
|
+
found.push(...overrideReferences(project, held, chains, new Set(tests)));
|
|
1339
|
+
}
|
|
1070
1340
|
found.sort(
|
|
1071
1341
|
(a, b) =>
|
|
1072
1342
|
byPosition(a.position, b.position) ||
|
|
@@ -1090,6 +1360,8 @@ export function references<Brand>(
|
|
|
1090
1360
|
aliasSteps: chains.steps,
|
|
1091
1361
|
patternBatches: destructured.cost.patternBatches,
|
|
1092
1362
|
patternLookups: destructured.cost.patternLookups,
|
|
1363
|
+
contextualBatches: contextual.batches,
|
|
1364
|
+
contextualLookups: contextual.lookups,
|
|
1093
1365
|
},
|
|
1094
1366
|
};
|
|
1095
1367
|
}
|
package/src/resolve.ts
CHANGED
|
@@ -262,18 +262,19 @@ function readStrings<T extends string>(
|
|
|
262
262
|
);
|
|
263
263
|
}
|
|
264
264
|
const seen = new Set<string>();
|
|
265
|
-
for (const entry of value as unknown[]) {
|
|
265
|
+
for (const [index, entry] of (value as unknown[]).entries()) {
|
|
266
|
+
const at = `${path}[${String(index)}]`;
|
|
266
267
|
if (typeof entry !== "string") {
|
|
267
|
-
throw malformed(label,
|
|
268
|
+
throw malformed(label, at, "is not a string");
|
|
268
269
|
}
|
|
269
270
|
if (entry === "") {
|
|
270
|
-
throw malformed(label,
|
|
271
|
+
throw malformed(label, at, "is empty");
|
|
271
272
|
}
|
|
272
273
|
if (seen.has(entry)) {
|
|
273
|
-
throw malformed(label,
|
|
274
|
+
throw malformed(label, at, `names ${JSON.stringify(entry)} again`);
|
|
274
275
|
}
|
|
275
276
|
if (allowed !== undefined && !(allowed as readonly string[]).includes(entry)) {
|
|
276
|
-
throw malformed(label,
|
|
277
|
+
throw malformed(label, at, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
|
|
277
278
|
}
|
|
278
279
|
seen.add(entry);
|
|
279
280
|
}
|
|
@@ -288,12 +289,12 @@ function readComponentExtensions(
|
|
|
288
289
|
label: string,
|
|
289
290
|
): readonly string[] | undefined {
|
|
290
291
|
const extensions = readStrings(ts, "component_extensions", "ts.component_extensions", label, 0);
|
|
291
|
-
const wrong = extensions?.
|
|
292
|
-
if (wrong !==
|
|
292
|
+
const wrong = extensions?.findIndex((one) => !COMPONENT_EXTENSION.test(one)) ?? -1;
|
|
293
|
+
if (extensions !== undefined && wrong !== -1) {
|
|
293
294
|
throw malformed(
|
|
294
295
|
label,
|
|
295
|
-
|
|
296
|
-
`${JSON.stringify(wrong)} is not a full stop followed by letters and digits`,
|
|
296
|
+
`ts.component_extensions[${String(wrong)}]`,
|
|
297
|
+
`${JSON.stringify(extensions[wrong])} is not a full stop followed by letters and digits`,
|
|
297
298
|
);
|
|
298
299
|
}
|
|
299
300
|
return extensions;
|
|
@@ -307,12 +308,12 @@ function readDisabledConventions(
|
|
|
307
308
|
label: string,
|
|
308
309
|
): readonly string[] | undefined {
|
|
309
310
|
const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
|
|
310
|
-
const unknown = names?.
|
|
311
|
-
if (unknown !==
|
|
311
|
+
const unknown = names?.findIndex((one) => !CONVENTION_NAMES.has(one)) ?? -1;
|
|
312
|
+
if (names !== undefined && unknown !== -1) {
|
|
312
313
|
throw malformed(
|
|
313
314
|
label,
|
|
314
|
-
|
|
315
|
-
`${JSON.stringify(unknown)} names no convention row this analyzer carries`,
|
|
315
|
+
`ts.disabled_conventions[${String(unknown)}]`,
|
|
316
|
+
`${JSON.stringify(names[unknown])} names no convention row this analyzer carries`,
|
|
316
317
|
);
|
|
317
318
|
}
|
|
318
319
|
return names;
|