@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.3
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/src/analysis.js +30 -6
- package/dist/src/augmentations.js +105 -12
- package/dist/src/build-scripts.js +437 -0
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/convention-rows.js +62 -2
- package/dist/src/conventions.js +15 -0
- package/dist/src/dependencies.js +89 -0
- package/dist/src/entry-points.js +9 -18
- package/dist/src/findings/completion.js +122 -49
- package/dist/src/findings/intra-function.js +4 -2
- package/dist/src/findings/reads-and-writes.js +7 -6
- package/dist/src/findings/unused-declarations.js +13 -5
- package/dist/src/findings/visibility-narrowing.js +8 -3
- package/dist/src/graph.js +5 -3
- package/dist/src/intra-function-parts.js +45 -17
- package/dist/src/kinds.js +8 -8
- package/dist/src/manifest.js +18 -0
- package/dist/src/matrix.js +76 -4
- package/dist/src/references.js +34 -3
- package/dist/src/report.js +23 -4
- package/dist/src/reporters/sarif.js +12 -8
- package/dist/src/roots.js +138 -11
- package/dist/src/serialization-contract.js +258 -8
- package/dist/src/suppress.js +1 -0
- package/dist/src/sweep.js +7 -9
- package/dist/src/test-globals.js +234 -0
- package/dist/src/test-support.js +8 -11
- package/dist/src/type-errors.js +11 -3
- package/dist/src/unseen-loads.js +107 -0
- package/dist/src/verbs/analyze.js +24 -2
- package/dist/src/yaml-document.js +355 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +48 -8
- package/src/augmentations.ts +134 -16
- package/src/build-scripts.ts +564 -0
- package/src/conformance-record.ts +1 -1
- package/src/convention-rows.ts +72 -2
- package/src/conventions.ts +16 -0
- package/src/dependencies.ts +96 -0
- package/src/entry-points.ts +10 -25
- package/src/file-facts.ts +3 -0
- package/src/finding.ts +5 -0
- package/src/findings/completion.ts +153 -56
- package/src/findings/intra-function.ts +4 -2
- package/src/findings/reads-and-writes.ts +7 -6
- package/src/findings/unused-declarations.ts +17 -5
- package/src/findings/visibility-narrowing.ts +9 -3
- package/src/graph.ts +5 -3
- package/src/intra-function-parts.ts +55 -16
- package/src/kinds.ts +8 -8
- package/src/manifest.ts +21 -3
- package/src/matrix.ts +122 -4
- package/src/references.ts +38 -2
- package/src/report.ts +56 -16
- package/src/reporters/sarif.ts +16 -8
- package/src/roots.ts +162 -12
- package/src/serialization-contract.ts +309 -7
- package/src/suppress.ts +1 -0
- package/src/sweep.ts +21 -9
- package/src/test-globals.ts +275 -0
- package/src/test-support.ts +17 -13
- package/src/type-errors.ts +14 -3
- package/src/unseen-loads.ts +145 -0
- package/src/verbs/analyze.ts +28 -2
- package/src/yaml-document.ts +377 -0
package/src/analysis.ts
CHANGED
|
@@ -107,6 +107,7 @@ import { references, testFileRulesOf, type Reference, type TestFileRule } from "
|
|
|
107
107
|
import {
|
|
108
108
|
roots,
|
|
109
109
|
unmatchedEverywhere,
|
|
110
|
+
type Root,
|
|
110
111
|
type RootKind,
|
|
111
112
|
type RootOptions,
|
|
112
113
|
type Roots,
|
|
@@ -114,7 +115,10 @@ import {
|
|
|
114
115
|
import type { Scope } from "./scope.ts";
|
|
115
116
|
import { serializationContract } from "./serialization-contract.ts";
|
|
116
117
|
import { outsideTestReferences, testFilesAt, type OutsideTest } from "./outside-tests.ts";
|
|
118
|
+
import { mergedGlobalInterfaces } from "./augmentations.ts";
|
|
119
|
+
import { unseenLoadsOf, type UnseenLoad } from "./unseen-loads.ts";
|
|
117
120
|
import { joinedTestFiles, withJoinedTests } from "./test-joins.ts";
|
|
121
|
+
import { GLOBALS_RUNNER, namesTestGlobals, withTestGlobals } from "./test-globals.ts";
|
|
118
122
|
import { supportReferences, testSupportFiles, type TestEvidence } from "./test-support.ts";
|
|
119
123
|
import {
|
|
120
124
|
diagnosticsOf,
|
|
@@ -428,11 +432,17 @@ function readProjects<Answer>(
|
|
|
428
432
|
const joins = discovered.derived
|
|
429
433
|
? joinedTestFiles(engine, host, absoluteRoot, discovered.configFiles, testPatterns)
|
|
430
434
|
: new Map<string, readonly string[]>();
|
|
431
|
-
const programs =
|
|
435
|
+
const programs = withTestGlobals(
|
|
432
436
|
engine,
|
|
433
437
|
host,
|
|
434
|
-
|
|
435
|
-
|
|
438
|
+
parse,
|
|
439
|
+
discovered.configFiles,
|
|
440
|
+
withBaseUrlPaths(
|
|
441
|
+
engine,
|
|
442
|
+
host,
|
|
443
|
+
[...discovered.configFiles, ...consumerOf.keys()],
|
|
444
|
+
workspace?.programs ?? new Map(),
|
|
445
|
+
),
|
|
436
446
|
);
|
|
437
447
|
const session = runSession(
|
|
438
448
|
engine,
|
|
@@ -804,6 +814,8 @@ interface ReadProject<Extra> {
|
|
|
804
814
|
readonly componentWarnings: readonly ComponentWarning[];
|
|
805
815
|
/** Per own file below the target root, the module specifiers its imports write. */
|
|
806
816
|
readonly imports: ReadonlyMap<string, readonly string[]>;
|
|
817
|
+
/** The global augmentations a declaration outside the project names. */
|
|
818
|
+
readonly augmented: readonly Root[];
|
|
807
819
|
readonly extra: Extra;
|
|
808
820
|
}
|
|
809
821
|
|
|
@@ -940,6 +952,9 @@ function readRun<Extra>(
|
|
|
940
952
|
disabled,
|
|
941
953
|
);
|
|
942
954
|
const components = project.ownSourceFiles().filter(isComponentFile);
|
|
955
|
+
const injected = project.program
|
|
956
|
+
.getSourceFileNames()
|
|
957
|
+
.some((name) => name.endsWith(`/node_modules/${GLOBALS_RUNNER}/globals.d.ts`));
|
|
943
958
|
const pathOf = (file: (typeof components)[number]): string =>
|
|
944
959
|
relativePath(targetRoot, file.fileName) ?? file.fileName;
|
|
945
960
|
return {
|
|
@@ -948,12 +963,27 @@ function readRun<Extra>(
|
|
|
948
963
|
testFiles: resolved.testFilePaths,
|
|
949
964
|
evidence,
|
|
950
965
|
componentFiles: components.map(pathOf),
|
|
966
|
+
augmented: mergedGlobalInterfaces(
|
|
967
|
+
project,
|
|
968
|
+
projectRead.held,
|
|
969
|
+
sourceFilesOf(project, targetRoot),
|
|
970
|
+
).map((id): Root => ({ id, kind: "global-augmentation", source: "" })),
|
|
951
971
|
imports: new Map(
|
|
952
972
|
project.ownSourceFiles().flatMap((file) => {
|
|
953
973
|
const path = relativePath(targetRoot, file.fileName);
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
974
|
+
if (path === undefined) {
|
|
975
|
+
return [];
|
|
976
|
+
}
|
|
977
|
+
const specifiers = file.imports.filter(isStringLiteralLikeNode).map((one) => one.text);
|
|
978
|
+
// A file calling the globals a program declares is written for their runner.
|
|
979
|
+
return [
|
|
980
|
+
[
|
|
981
|
+
path,
|
|
982
|
+
injected && namesTestGlobals(project, file)
|
|
983
|
+
? [...specifiers, GLOBALS_RUNNER]
|
|
984
|
+
: specifiers,
|
|
985
|
+
],
|
|
986
|
+
];
|
|
957
987
|
}),
|
|
958
988
|
),
|
|
959
989
|
componentWarnings: components.flatMap((file) =>
|
|
@@ -977,7 +1007,7 @@ function readRun<Extra>(
|
|
|
977
1007
|
}));
|
|
978
1008
|
// Test-support code is code only test code imports in any configuration of the run: a
|
|
979
1009
|
// configuration that holds no test file says nothing about what the tests import.
|
|
980
|
-
const support = testSupportFiles(
|
|
1010
|
+
const { support, testUsed } = testSupportFiles(
|
|
981
1011
|
withOutside.flatMap((one) => one.projectRead.held.symbols),
|
|
982
1012
|
withOutside.flatMap((one) => one.references),
|
|
983
1013
|
withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability),
|
|
@@ -999,13 +1029,17 @@ function readRun<Extra>(
|
|
|
999
1029
|
const made = supportReferences([...one.references, ...(satisfied?.references ?? [])], support);
|
|
1000
1030
|
const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
|
|
1001
1031
|
const supportFiles = [...support].filter((path) => held.has(path)).sort();
|
|
1032
|
+
const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort();
|
|
1002
1033
|
const configured: Configured = {
|
|
1003
1034
|
configuration: projectRead.configuration,
|
|
1004
1035
|
symbols: projectRead.held.symbols,
|
|
1005
1036
|
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
1006
|
-
|
|
1037
|
+
// The augmentations are rooted after test support is read: an outside declaration
|
|
1038
|
+
// naming one calls nothing of its file.
|
|
1039
|
+
roots: [...projectRead.rooted.liveUnderReachability, ...one.augmented],
|
|
1007
1040
|
testFiles: [...one.testFiles, ...supportFiles],
|
|
1008
1041
|
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
1042
|
+
...(testUsedFiles.length === 0 ? {} : { testUsedFiles }),
|
|
1009
1043
|
...(projectRead.held.ambient.size === 0 ? {} : { ambient: projectRead.held.ambient }),
|
|
1010
1044
|
...(projectRead.referenceOnly.length === 0
|
|
1011
1045
|
? {}
|
|
@@ -1244,6 +1278,7 @@ interface EmitterExtra {
|
|
|
1244
1278
|
readonly directives: readonly InlineDirective[];
|
|
1245
1279
|
readonly declarationsNamed: readonly boolean[];
|
|
1246
1280
|
readonly parts: ProjectParts;
|
|
1281
|
+
readonly unseenLoads: readonly UnseenLoad[];
|
|
1247
1282
|
}
|
|
1248
1283
|
|
|
1249
1284
|
/** Reads a run's projects with every per-project fact an emitter reads. */
|
|
@@ -1279,6 +1314,7 @@ export function readEmitterRun(
|
|
|
1279
1314
|
),
|
|
1280
1315
|
declarationsNamed: declarationsNamed(project, read.held, configuredDeclarations(config)),
|
|
1281
1316
|
parts: projectParts(project, read.held, references, targetRoot),
|
|
1317
|
+
unseenLoads: unseenLoadsOf(project, read.configuration, targetRoot, testFiles),
|
|
1282
1318
|
}),
|
|
1283
1319
|
);
|
|
1284
1320
|
// Whether a declaration carries the marker decides its code, so one whose marker went
|
|
@@ -1384,6 +1420,7 @@ export function emitterInputOver(
|
|
|
1384
1420
|
heldByInclusion: new Set(projects.flatMap((one) => one.extra.heldByInclusion)),
|
|
1385
1421
|
generated: new Set(projects.flatMap((one) => one.extra.generated)),
|
|
1386
1422
|
inOperands: new Set(projects.flatMap((one) => one.extra.inOperands)),
|
|
1423
|
+
unseenLoads: projects.flatMap((one) => one.extra.unseenLoads),
|
|
1387
1424
|
},
|
|
1388
1425
|
boundary: {
|
|
1389
1426
|
consumers: {
|
|
@@ -1474,6 +1511,7 @@ function readAnalysis(
|
|
|
1474
1511
|
librariesUnfound: read.librariesUnfound,
|
|
1475
1512
|
conventionsApplied: read.conventionsApplied,
|
|
1476
1513
|
unmappedEntries: unmappedEverywhere(read.projects),
|
|
1514
|
+
unseenLoads: read.projects.flatMap((one) => one.extra.unseenLoads),
|
|
1477
1515
|
},
|
|
1478
1516
|
configured: read.projects.map((one) => one.configured),
|
|
1479
1517
|
symbols,
|
|
@@ -1572,6 +1610,8 @@ interface RunFacts {
|
|
|
1572
1610
|
readonly conventionsApplied: readonly AppliedConvention[];
|
|
1573
1611
|
/** The manifest entries no project of the run reads back to a file, each once. */
|
|
1574
1612
|
readonly unmappedEntries: readonly ManifestEntry[];
|
|
1613
|
+
/** The loads of production files whose target the run cannot resolve, per configuration. */
|
|
1614
|
+
readonly unseenLoads: readonly UnseenLoad[];
|
|
1575
1615
|
}
|
|
1576
1616
|
|
|
1577
1617
|
/** One configuration whose component files a run cannot read. */
|
package/src/augmentations.ts
CHANGED
|
@@ -7,9 +7,18 @@
|
|
|
7
7
|
* as `import.meta` or `process.env`, so a use of its member is a use of it.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
isInterfaceDeclaration,
|
|
12
|
+
isModuleBlock,
|
|
13
|
+
isModuleDeclaration,
|
|
14
|
+
type Node,
|
|
15
|
+
} from "@typescript/native/ast";
|
|
16
|
+
import { nodeKey, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
11
17
|
import { referenceKey } from "./matrix.ts";
|
|
12
18
|
import type { Reference } from "./references.ts";
|
|
19
|
+
import type { ProjectView } from "./session.ts";
|
|
20
|
+
import type { SourceFiles } from "./source-files.ts";
|
|
21
|
+
import type { NodeHandle } from "@typescript/native/sync";
|
|
13
22
|
|
|
14
23
|
/** Whether one declaration is a `declare global` block: a module-level namespace named `global`. */
|
|
15
24
|
function isGlobalBlock(
|
|
@@ -26,25 +35,24 @@ function isGlobalBlock(
|
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
/**
|
|
29
|
-
*
|
|
30
|
-
* file in `scripts
|
|
31
|
-
*
|
|
32
|
-
* files that are no module, by identifier.
|
|
38
|
+
* Per declaration, the interfaces and namespaces between it and the augmentation block or
|
|
39
|
+
* the file in `scripts` that holds it, with the block itself, or none where no such block
|
|
40
|
+
* or file holds it; and whether that holder is the global scope rather than a module
|
|
41
|
+
* declaration. `scripts` holds the files that are no module, by identifier.
|
|
33
42
|
*/
|
|
34
|
-
|
|
43
|
+
function holdersOf(
|
|
35
44
|
held: Inventory,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
): readonly Reference[] {
|
|
45
|
+
scripts: ReadonlySet<string>,
|
|
46
|
+
): ((id: string) => { readonly ids: readonly string[]; readonly global: boolean }) | undefined {
|
|
39
47
|
const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
|
|
40
48
|
const globals = new Set(
|
|
41
49
|
held.symbols.filter((one) => isGlobalBlock(one, held, files)).map((one) => one.id),
|
|
42
50
|
);
|
|
43
51
|
if (globals.size === 0 && held.modules.size === 0 && scripts.size === 0) {
|
|
44
|
-
return
|
|
52
|
+
return undefined;
|
|
45
53
|
}
|
|
46
54
|
const byId = new Map(held.symbols.map((one) => [one.id, one]));
|
|
47
|
-
|
|
55
|
+
return (id) => {
|
|
48
56
|
const ids: string[] = [];
|
|
49
57
|
for (
|
|
50
58
|
let at = byId.get(byId.get(id)?.parent ?? "");
|
|
@@ -52,29 +60,139 @@ export function augmentationReferences(
|
|
|
52
60
|
at = byId.get(at.parent)
|
|
53
61
|
) {
|
|
54
62
|
if (globals.has(at.id) || held.modules.has(at.id)) {
|
|
55
|
-
return [...ids, at.id];
|
|
63
|
+
return { ids: [...ids, at.id], global: globals.has(at.id) };
|
|
56
64
|
}
|
|
57
65
|
if (scripts.has(at.id)) {
|
|
58
|
-
return ids;
|
|
66
|
+
return { ids, global: true };
|
|
59
67
|
}
|
|
60
68
|
if (at.kind === "interface" || at.kind === "namespace") {
|
|
61
69
|
ids.push(at.id);
|
|
62
70
|
}
|
|
63
71
|
}
|
|
72
|
+
return { ids: [], global: false };
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The ambient interfaces of the global scope among `interfaces`: each one a declaration
|
|
78
|
+
* file or a `declare` holds that a `declare global` block, or the top level of a file in
|
|
79
|
+
* `scripts`, declares, directly or inside a namespace there.
|
|
80
|
+
*/
|
|
81
|
+
export function ambientGlobalInterfaces(
|
|
82
|
+
held: Inventory,
|
|
83
|
+
interfaces: readonly string[],
|
|
84
|
+
scripts: ReadonlySet<string>,
|
|
85
|
+
): readonly string[] {
|
|
86
|
+
const holders = holdersOf(held, scripts);
|
|
87
|
+
return holders === undefined
|
|
88
|
+
? []
|
|
89
|
+
: interfaces.filter((id) => held.ambient.has(id) && holders(id).global);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Each ambient global interface of the project's own files that merges into an interface
|
|
94
|
+
* declared outside them, with every interface, namespace and block between it and the
|
|
95
|
+
* global scope: the outside declaration names it wherever a value is typed by it. Where
|
|
96
|
+
* every outside declaration is a package's, not the compiler's library, and declares no
|
|
97
|
+
* member, each member it declares as well: such an interface is a slot the package reads
|
|
98
|
+
* by type, as `keyof` does, in declarations the analysis does not walk.
|
|
99
|
+
*/
|
|
100
|
+
export function mergedGlobalInterfaces<Brand>(
|
|
101
|
+
project: ProjectView<Brand>,
|
|
102
|
+
held: Inventory,
|
|
103
|
+
files: SourceFiles,
|
|
104
|
+
): readonly string[] {
|
|
105
|
+
const scripts = new Set<string>();
|
|
106
|
+
const names = new Map<string, Node>();
|
|
107
|
+
for (const file of files.byName.values()) {
|
|
108
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
109
|
+
if (fileId !== undefined && file.externalModuleIndicator === undefined) {
|
|
110
|
+
scripts.add(fileId);
|
|
111
|
+
}
|
|
112
|
+
const visit = (node: Node): void => {
|
|
113
|
+
if (isInterfaceDeclaration(node)) {
|
|
114
|
+
const id = held.declarations.get(nodeKey(file, node));
|
|
115
|
+
if (id !== undefined) {
|
|
116
|
+
names.set(id, node.name);
|
|
117
|
+
}
|
|
118
|
+
} else if (isModuleDeclaration(node) || isModuleBlock(node)) {
|
|
119
|
+
node.forEachChild(visit);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
file.forEachChild(visit);
|
|
123
|
+
}
|
|
124
|
+
const global = new Set(ambientGlobalInterfaces(held, [...names.keys()], scripts));
|
|
125
|
+
const asked = [...names].filter(([id]) => global.has(id));
|
|
126
|
+
if (asked.length === 0) {
|
|
64
127
|
return [];
|
|
128
|
+
}
|
|
129
|
+
const answers = project.symbolsAt(asked.map(([, name]) => project.handle(name)));
|
|
130
|
+
const holders = holdersOf(held, scripts);
|
|
131
|
+
const empty = (handle: NodeHandle): boolean => {
|
|
132
|
+
if (project.program.getSourceFileMetadata(handle.path)?.isDefaultLibrary === true) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const node = project.declarationAt(handle)?.node;
|
|
136
|
+
return node !== undefined && isInterfaceDeclaration(node) && node.members.length === 0;
|
|
65
137
|
};
|
|
138
|
+
return [
|
|
139
|
+
...new Set(
|
|
140
|
+
asked.flatMap(([id], at) => {
|
|
141
|
+
const symbol = answers[at];
|
|
142
|
+
if (typeof symbol !== "object") {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
const outside = symbol.declarations.filter((handle) => !files.byName.has(handle.path));
|
|
146
|
+
if (outside.length === 0) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const members = outside.every(empty)
|
|
150
|
+
? held.symbols.filter((one) => one.parent === id).map((one) => one.id)
|
|
151
|
+
: [];
|
|
152
|
+
return [id, ...(holders?.(id).ids ?? []), ...members];
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* For each reference to a declaration an augmentation block, or the global scope of a
|
|
160
|
+
* file in `scripts` (the files that are no module), holds, a read at the same place of
|
|
161
|
+
* every interface and namespace between it and the block or the file, and of the block
|
|
162
|
+
* itself. A read through an interface of `merged`, an ambient global interface merging
|
|
163
|
+
* into a language or library one, is a production read whoever writes it: it types a
|
|
164
|
+
* value the runtime provides, so a test reading a member needs it as code does.
|
|
165
|
+
*/
|
|
166
|
+
export function augmentationReferences(
|
|
167
|
+
held: Inventory,
|
|
168
|
+
found: readonly Reference[],
|
|
169
|
+
scripts: ReadonlySet<string> = new Set(),
|
|
170
|
+
merged: ReadonlySet<string> = new Set(),
|
|
171
|
+
): readonly Reference[] {
|
|
172
|
+
const holders = holdersOf(held, scripts);
|
|
173
|
+
if (holders === undefined) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
const fileOf = new Map(
|
|
177
|
+
held.symbols.filter((one) => one.kind === "file").map((one) => [one.position.path, one.id]),
|
|
178
|
+
);
|
|
66
179
|
// Two uses at one place of declarations one block holds read the block once there.
|
|
67
180
|
const made = new Set(found.map(referenceKey));
|
|
68
181
|
const reads = new Map<string, Reference>();
|
|
69
182
|
for (const reference of found) {
|
|
70
|
-
|
|
183
|
+
const { ids } = holders(reference.to);
|
|
184
|
+
// A test's read through such an interface is held by the test file, which the test
|
|
185
|
+
// runner loads whichever of its declarations runs.
|
|
186
|
+
const typed = reference.test && ids.some((id) => merged.has(id));
|
|
187
|
+
const from = typed ? (fileOf.get(reference.position.path) ?? reference.from) : reference.from;
|
|
188
|
+
for (const to of ids) {
|
|
71
189
|
const read: Reference = {
|
|
72
|
-
from
|
|
190
|
+
from,
|
|
73
191
|
to,
|
|
74
192
|
position: reference.position,
|
|
75
193
|
use: "read",
|
|
76
194
|
resolution: reference.resolution,
|
|
77
|
-
test: reference.test,
|
|
195
|
+
test: reference.test && !typed,
|
|
78
196
|
};
|
|
79
197
|
const key = referenceKey(read);
|
|
80
198
|
if (!made.has(key) && !reads.has(key)) {
|