@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.2
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 +108 -10
- package/dist/src/augmentations.js +62 -18
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +50 -12
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +98 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +183 -28
- package/dist/src/discover.js +132 -11
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +172 -40
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +30 -5
- package/dist/src/findings/visibility-narrowing.js +29 -5
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +297 -45
- package/dist/src/inventory.js +52 -8
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +57 -10
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/overrides.js +41 -10
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +38 -4
- package/dist/src/report.js +4 -2
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +54 -8
- package/dist/src/session.js +7 -0
- package/dist/src/source-files.js +39 -14
- package/dist/src/suppress-file.js +10 -2
- package/dist/src/suppress-inline.js +18 -2
- package/dist/src/suppress.js +8 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/test-support.js +119 -18
- package/dist/src/type-errors.js +34 -7
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/verbs/analyze.js +16 -0
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +53 -10
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +146 -11
- package/src/augmentations.ts +76 -22
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +66 -12
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +128 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +247 -41
- package/src/discover.ts +156 -13
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +15 -0
- package/src/findings/completion.ts +222 -46
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +36 -5
- package/src/findings/visibility-narrowing.ts +40 -5
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +394 -59
- package/src/inventory.ts +71 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +104 -15
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/overrides.ts +51 -12
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +48 -3
- package/src/report.ts +16 -3
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +55 -4
- package/src/session.ts +7 -0
- package/src/source-files.ts +66 -16
- package/src/suppress-file.ts +12 -1
- package/src/suppress-inline.ts +18 -1
- package/src/suppress.ts +10 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/test-support.ts +155 -18
- package/src/type-errors.ts +38 -6
- package/src/type-parameter-reads.ts +227 -0
- package/src/verbs/analyze.ts +22 -0
- package/src/verbs/explain.ts +5 -2
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +62 -10
package/src/analysis.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* another project's checker, which holds only for views the session hands out.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { isStringLiteralLikeNode } from "@typescript/native/ast";
|
|
9
10
|
import { lineOf, readComponent } from "./component-files.ts";
|
|
10
11
|
import { kindOf, type Config, type Provenance } from "./config.ts";
|
|
11
12
|
import {
|
|
@@ -26,9 +27,11 @@ import {
|
|
|
26
27
|
discoverProjects,
|
|
27
28
|
DiscoveryError,
|
|
28
29
|
isGuess,
|
|
30
|
+
MISSING_GLOBAL_TYPE,
|
|
29
31
|
projectErrors,
|
|
30
32
|
renderDiagnostic,
|
|
31
33
|
targetRelative,
|
|
34
|
+
withBaseUrlPaths,
|
|
32
35
|
type LocatedDiagnostic,
|
|
33
36
|
type NotBuilt,
|
|
34
37
|
} from "./discover.ts";
|
|
@@ -70,7 +73,7 @@ import { injectionContainer } from "./injection-container.ts";
|
|
|
70
73
|
import { interfaceSatisfaction, settledSatisfaction } from "./interface-satisfaction.ts";
|
|
71
74
|
import { intraFunctionFacts, projectParts, type ProjectParts } from "./intra-function-parts.ts";
|
|
72
75
|
import { inventory, type Inventory, type InventorySymbol } from "./inventory.ts";
|
|
73
|
-
import { packageEntries, readManifest } from "./manifest.ts";
|
|
76
|
+
import { packageEntries, readManifest, type ManifestEntry } from "./manifest.ts";
|
|
74
77
|
import {
|
|
75
78
|
liveFilesAt,
|
|
76
79
|
matrixOf,
|
|
@@ -112,7 +115,7 @@ import type { Scope } from "./scope.ts";
|
|
|
112
115
|
import { serializationContract } from "./serialization-contract.ts";
|
|
113
116
|
import { outsideTestReferences, testFilesAt, type OutsideTest } from "./outside-tests.ts";
|
|
114
117
|
import { joinedTestFiles, withJoinedTests } from "./test-joins.ts";
|
|
115
|
-
import { supportReferences, testSupportFiles } from "./test-support.ts";
|
|
118
|
+
import { supportReferences, testSupportFiles, type TestEvidence } from "./test-support.ts";
|
|
116
119
|
import {
|
|
117
120
|
diagnosticsOf,
|
|
118
121
|
ownedAs,
|
|
@@ -390,6 +393,11 @@ function readProjects<Answer>(
|
|
|
390
393
|
publishedMembers: new Set(
|
|
391
394
|
(workspace?.entries ?? []).filter((entry) => entry.published).map((entry) => entry.dir),
|
|
392
395
|
),
|
|
396
|
+
runFiles: new Set(
|
|
397
|
+
opened.flatMap((one) =>
|
|
398
|
+
one.fileNames().map((name) => resolvePath(host.workingDirectory(), name)),
|
|
399
|
+
),
|
|
400
|
+
),
|
|
393
401
|
};
|
|
394
402
|
};
|
|
395
403
|
const notBuilt: NotBuilt[] = [...discovered.notBuilt];
|
|
@@ -420,6 +428,12 @@ function readProjects<Answer>(
|
|
|
420
428
|
const joins = discovered.derived
|
|
421
429
|
? joinedTestFiles(engine, host, absoluteRoot, discovered.configFiles, testPatterns)
|
|
422
430
|
: new Map<string, readonly string[]>();
|
|
431
|
+
const programs = withBaseUrlPaths(
|
|
432
|
+
engine,
|
|
433
|
+
host,
|
|
434
|
+
[...discovered.configFiles, ...consumerOf.keys()],
|
|
435
|
+
workspace?.programs ?? new Map(),
|
|
436
|
+
);
|
|
423
437
|
const session = runSession(
|
|
424
438
|
engine,
|
|
425
439
|
[...discovered.configFiles, ...consumerOf.keys()],
|
|
@@ -514,11 +528,29 @@ function readProjects<Answer>(
|
|
|
514
528
|
error: renderDiagnostic(one, (path) => relativePath(absoluteRoot, path) ?? path),
|
|
515
529
|
})),
|
|
516
530
|
);
|
|
531
|
+
const missingGlobal = raw.unfound.find(
|
|
532
|
+
({ diagnostic }) => diagnostic.code === MISSING_GLOBAL_TYPE,
|
|
533
|
+
);
|
|
534
|
+
const skips =
|
|
535
|
+
missingGlobal === undefined
|
|
536
|
+
? reading.skips
|
|
537
|
+
: [
|
|
538
|
+
...reading.skips,
|
|
539
|
+
...project.ownSourceFiles().map((file) => ({
|
|
540
|
+
record: {
|
|
541
|
+
path: relativePath(absoluteRoot, file.fileName) ?? file.fileName,
|
|
542
|
+
line: 1,
|
|
543
|
+
message: missingGlobal.diagnostic.text,
|
|
544
|
+
},
|
|
545
|
+
file,
|
|
546
|
+
unit: file,
|
|
547
|
+
})),
|
|
548
|
+
];
|
|
517
549
|
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
518
550
|
const read = (): ProjectRead => {
|
|
519
551
|
const held = inventory(project, host, targetRoot);
|
|
520
552
|
const found = roots(project, held, targetRoot, options);
|
|
521
|
-
const kept = skippedRoots(project, held,
|
|
553
|
+
const kept = skippedRoots(project, held, skips);
|
|
522
554
|
const rooted =
|
|
523
555
|
kept.length === 0
|
|
524
556
|
? found
|
|
@@ -535,8 +567,8 @@ function readProjects<Answer>(
|
|
|
535
567
|
configFile: project.configFile,
|
|
536
568
|
held,
|
|
537
569
|
rooted,
|
|
538
|
-
typeErrorSkips:
|
|
539
|
-
skipped: skippedUnits(
|
|
570
|
+
typeErrorSkips: skips.map((one) => one.record),
|
|
571
|
+
skipped: skippedUnits(skips, absoluteRoot),
|
|
540
572
|
referenceOnly: project
|
|
541
573
|
.ownSourceFiles()
|
|
542
574
|
.filter((file) => referenceOnly(file.fileName))
|
|
@@ -551,9 +583,7 @@ function readProjects<Answer>(
|
|
|
551
583
|
return stage(project, { configuration, read });
|
|
552
584
|
},
|
|
553
585
|
discovered.derived ? "record" : "refuse",
|
|
554
|
-
joins.size === 0
|
|
555
|
-
? workspace?.programs
|
|
556
|
-
: withJoinedTests(engine, host, workspace?.programs ?? new Map(), joins),
|
|
586
|
+
joins.size === 0 ? programs : withJoinedTests(engine, host, programs, joins),
|
|
557
587
|
() => {
|
|
558
588
|
outsideTests = testFilesAt(
|
|
559
589
|
host,
|
|
@@ -759,6 +789,8 @@ interface SweptProject<Extra> {
|
|
|
759
789
|
/** The component files the project holds of its own, by path below the target root. */
|
|
760
790
|
readonly componentFiles: readonly string[];
|
|
761
791
|
readonly componentWarnings: readonly ComponentWarning[];
|
|
792
|
+
/** The manifest entries the project reads back to no file of its own. */
|
|
793
|
+
readonly unmappedEntries: readonly ManifestEntry[];
|
|
762
794
|
readonly extra: Extra;
|
|
763
795
|
}
|
|
764
796
|
|
|
@@ -770,6 +802,8 @@ interface ReadProject<Extra> {
|
|
|
770
802
|
readonly evidence: readonly Exemption[];
|
|
771
803
|
readonly componentFiles: readonly string[];
|
|
772
804
|
readonly componentWarnings: readonly ComponentWarning[];
|
|
805
|
+
/** Per own file below the target root, the module specifiers its imports write. */
|
|
806
|
+
readonly imports: ReadonlyMap<string, readonly string[]>;
|
|
773
807
|
readonly extra: Extra;
|
|
774
808
|
}
|
|
775
809
|
|
|
@@ -800,6 +834,64 @@ interface ReadRun<Extra> {
|
|
|
800
834
|
readonly nestedDirs: readonly string[];
|
|
801
835
|
}
|
|
802
836
|
|
|
837
|
+
/** The manifest section of a package test evidence counts, and the sections that keep one out of it. */
|
|
838
|
+
const DEV_SECTION = "devDependencies";
|
|
839
|
+
const OTHER_SECTIONS = ["dependencies", "peerDependencies", "optionalDependencies"] as const;
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* The imports of a run's own files and the manifests below the target root that test
|
|
843
|
+
* evidence reads: a package is a devDependency of a file where the nearest manifest at or
|
|
844
|
+
* above it that declares the package declares it under `devDependencies` alone.
|
|
845
|
+
*/
|
|
846
|
+
function testEvidence(
|
|
847
|
+
host: Host,
|
|
848
|
+
targetRoot: string,
|
|
849
|
+
projects: readonly { readonly imports: ReadonlyMap<string, readonly string[]> }[],
|
|
850
|
+
): TestEvidence {
|
|
851
|
+
const imports = new Map<string, readonly string[]>();
|
|
852
|
+
for (const one of projects) {
|
|
853
|
+
for (const [path, specifiers] of one.imports) {
|
|
854
|
+
imports.set(path, specifiers);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
const manifests = new Map<string, Readonly<Record<string, unknown>> | undefined>();
|
|
858
|
+
const manifestAt = (dir: string): Readonly<Record<string, unknown>> | undefined => {
|
|
859
|
+
if (!manifests.has(dir)) {
|
|
860
|
+
let value: unknown;
|
|
861
|
+
try {
|
|
862
|
+
value = JSON.parse(host.readFile(joinPath(dir, "package.json")));
|
|
863
|
+
} catch {
|
|
864
|
+
value = undefined;
|
|
865
|
+
}
|
|
866
|
+
manifests.set(
|
|
867
|
+
dir,
|
|
868
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
869
|
+
? (value as Record<string, unknown>)
|
|
870
|
+
: undefined,
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
return manifests.get(dir);
|
|
874
|
+
};
|
|
875
|
+
const declares = (section: unknown, name: string): boolean =>
|
|
876
|
+
typeof section === "object" && section !== null && Object.hasOwn(section, name);
|
|
877
|
+
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
878
|
+
return {
|
|
879
|
+
imports,
|
|
880
|
+
devDependency: (path, name) => {
|
|
881
|
+
for (let dir = dirnamePath(joinPath(root, path)); ; dir = dirnamePath(dir)) {
|
|
882
|
+
const manifest = manifestAt(dir);
|
|
883
|
+
const other = OTHER_SECTIONS.some((one) => declares(manifest?.[one], name));
|
|
884
|
+
if (declares(manifest?.[DEV_SECTION], name) || other) {
|
|
885
|
+
return !other;
|
|
886
|
+
}
|
|
887
|
+
if (normalizePath(dir) === normalizePath(root) || dirnamePath(dir) === dir) {
|
|
888
|
+
return false;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
803
895
|
/**
|
|
804
896
|
* Reads every project of the run, its references and exemptions, and `extra`'s answer,
|
|
805
897
|
* while the project's view is open, and merges the projects into one matrix in the run's
|
|
@@ -856,6 +948,14 @@ function readRun<Extra>(
|
|
|
856
948
|
testFiles: resolved.testFilePaths,
|
|
857
949
|
evidence,
|
|
858
950
|
componentFiles: components.map(pathOf),
|
|
951
|
+
imports: new Map(
|
|
952
|
+
project.ownSourceFiles().flatMap((file) => {
|
|
953
|
+
const path = relativePath(targetRoot, file.fileName);
|
|
954
|
+
return path === undefined
|
|
955
|
+
? []
|
|
956
|
+
: [[path, file.imports.filter(isStringLiteralLikeNode).map((one) => one.text)]];
|
|
957
|
+
}),
|
|
958
|
+
),
|
|
859
959
|
componentWarnings: components.flatMap((file) =>
|
|
860
960
|
readComponent(file.originalText, extensionOf(file.fileName)).warnings.map((warning) => ({
|
|
861
961
|
path: pathOf(file),
|
|
@@ -877,11 +977,12 @@ function readRun<Extra>(
|
|
|
877
977
|
}));
|
|
878
978
|
// Test-support code is code only test code imports in any configuration of the run: a
|
|
879
979
|
// configuration that holds no test file says nothing about what the tests import.
|
|
880
|
-
const support = testSupportFiles(
|
|
980
|
+
const { support, testUsed } = testSupportFiles(
|
|
881
981
|
withOutside.flatMap((one) => one.projectRead.held.symbols),
|
|
882
982
|
withOutside.flatMap((one) => one.references),
|
|
883
983
|
withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability),
|
|
884
984
|
withOutside.flatMap((one) => one.testFiles),
|
|
985
|
+
testEvidence(host, targetRoot, withOutside),
|
|
885
986
|
);
|
|
886
987
|
const settled = settledSatisfaction(
|
|
887
988
|
withOutside.map((one) => ({ evidence: one.evidence, held: one.projectRead.held })),
|
|
@@ -898,13 +999,16 @@ function readRun<Extra>(
|
|
|
898
999
|
const made = supportReferences([...one.references, ...(satisfied?.references ?? [])], support);
|
|
899
1000
|
const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
|
|
900
1001
|
const supportFiles = [...support].filter((path) => held.has(path)).sort();
|
|
1002
|
+
const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort();
|
|
901
1003
|
const configured: Configured = {
|
|
902
1004
|
configuration: projectRead.configuration,
|
|
903
1005
|
symbols: projectRead.held.symbols,
|
|
904
1006
|
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
905
1007
|
roots: projectRead.rooted.liveUnderReachability,
|
|
906
|
-
testFiles: one.testFiles,
|
|
1008
|
+
testFiles: [...one.testFiles, ...supportFiles],
|
|
907
1009
|
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
1010
|
+
...(testUsedFiles.length === 0 ? {} : { testUsedFiles }),
|
|
1011
|
+
...(projectRead.held.ambient.size === 0 ? {} : { ambient: projectRead.held.ambient }),
|
|
908
1012
|
...(projectRead.referenceOnly.length === 0
|
|
909
1013
|
? {}
|
|
910
1014
|
: { referenceOnly: projectRead.referenceOnly }),
|
|
@@ -925,6 +1029,7 @@ function readRun<Extra>(
|
|
|
925
1029
|
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
926
1030
|
],
|
|
927
1031
|
typeErrorSkips: projectRead.typeErrorSkips,
|
|
1032
|
+
unmappedEntries: projectRead.rooted.unmappedEntries,
|
|
928
1033
|
skipped: projectRead.skipped,
|
|
929
1034
|
componentFiles: one.componentFiles,
|
|
930
1035
|
componentWarnings: one.componentWarnings,
|
|
@@ -1190,7 +1295,8 @@ export function readEmitterRun(
|
|
|
1190
1295
|
* The workspace members whose dependencies the run judges: `read` holds those a built
|
|
1191
1296
|
* project holds a file below, `unbuilt` the others whose own directory, the nearest
|
|
1192
1297
|
* manifest's above the configuration, holds a configuration the run could not build. A
|
|
1193
|
-
* member no configuration holds a file of is in neither.
|
|
1298
|
+
* member no configuration holds a file of is in neither. A nested package no built project
|
|
1299
|
+
* holds a file below joins `unbuilt` the same way.
|
|
1194
1300
|
*/
|
|
1195
1301
|
function judgedMembers(
|
|
1196
1302
|
host: Host,
|
|
@@ -1214,6 +1320,16 @@ function judgedMembers(
|
|
|
1214
1320
|
judged.unbuilt.push(dir);
|
|
1215
1321
|
}
|
|
1216
1322
|
}
|
|
1323
|
+
for (const dir of read.nestedDirs) {
|
|
1324
|
+
const below = relativePath(root, dir);
|
|
1325
|
+
if (
|
|
1326
|
+
below !== undefined &&
|
|
1327
|
+
owners.has(dir) &&
|
|
1328
|
+
![...held].some((path) => path.startsWith(`${below}/`))
|
|
1329
|
+
) {
|
|
1330
|
+
judged.unbuilt.push(dir);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1217
1333
|
return judged;
|
|
1218
1334
|
}
|
|
1219
1335
|
|
|
@@ -1359,6 +1475,7 @@ function readAnalysis(
|
|
|
1359
1475
|
componentsUnread: read.componentsUnread,
|
|
1360
1476
|
librariesUnfound: read.librariesUnfound,
|
|
1361
1477
|
conventionsApplied: read.conventionsApplied,
|
|
1478
|
+
unmappedEntries: unmappedEverywhere(read.projects),
|
|
1362
1479
|
},
|
|
1363
1480
|
configured: read.projects.map((one) => one.configured),
|
|
1364
1481
|
symbols,
|
|
@@ -1410,6 +1527,22 @@ function inputsOver(
|
|
|
1410
1527
|
};
|
|
1411
1528
|
}
|
|
1412
1529
|
|
|
1530
|
+
/** The manifest entries every project of the run reads back to no file, in the first project's order. */
|
|
1531
|
+
function unmappedEverywhere(
|
|
1532
|
+
projects: readonly { readonly unmappedEntries: readonly ManifestEntry[] }[],
|
|
1533
|
+
): readonly ManifestEntry[] {
|
|
1534
|
+
const key = (entry: ManifestEntry): string => `${entry.dir}\u0000${entry.member}`;
|
|
1535
|
+
const [first, ...rest] = projects;
|
|
1536
|
+
const others = rest.map((one) => new Set(one.unmappedEntries.map(key)));
|
|
1537
|
+
const found = new Map<string, ManifestEntry>();
|
|
1538
|
+
for (const entry of first?.unmappedEntries ?? []) {
|
|
1539
|
+
if (others.every((one) => one.has(key(entry)))) {
|
|
1540
|
+
found.set(key(entry), entry);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return [...found.values()];
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1413
1546
|
/** One project of a run: the name the run gives it and the file it was opened from. */
|
|
1414
1547
|
export interface RunProject {
|
|
1415
1548
|
readonly id: string;
|
|
@@ -1439,6 +1572,8 @@ interface RunFacts {
|
|
|
1439
1572
|
readonly librariesUnfound: readonly LibraryUnfound[];
|
|
1440
1573
|
/** The convention rows the run applied. */
|
|
1441
1574
|
readonly conventionsApplied: readonly AppliedConvention[];
|
|
1575
|
+
/** The manifest entries no project of the run reads back to a file, each once. */
|
|
1576
|
+
readonly unmappedEntries: readonly ManifestEntry[];
|
|
1442
1577
|
}
|
|
1443
1578
|
|
|
1444
1579
|
/** One configuration whose component files a run cannot read. */
|
package/src/augmentations.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Inventory, InventorySymbol } from "./inventory.ts";
|
|
11
|
+
import { referenceKey } from "./matrix.ts";
|
|
11
12
|
import type { Reference } from "./references.ts";
|
|
12
13
|
|
|
13
14
|
/** Whether one declaration is a `declare global` block: a module-level namespace named `global`. */
|
|
@@ -25,25 +26,24 @@ function isGlobalBlock(
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
*
|
|
29
|
-
* file in `scripts
|
|
30
|
-
*
|
|
31
|
-
* files that are no module, by identifier.
|
|
29
|
+
* Per declaration, the interfaces and namespaces between it and the augmentation block or
|
|
30
|
+
* the file in `scripts` that holds it, with the block itself, or none where no such block
|
|
31
|
+
* or file holds it; and whether that holder is the global scope rather than a module
|
|
32
|
+
* declaration. `scripts` holds the files that are no module, by identifier.
|
|
32
33
|
*/
|
|
33
|
-
|
|
34
|
+
function holdersOf(
|
|
34
35
|
held: Inventory,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
): readonly Reference[] {
|
|
36
|
+
scripts: ReadonlySet<string>,
|
|
37
|
+
): ((id: string) => { readonly ids: readonly string[]; readonly global: boolean }) | undefined {
|
|
38
38
|
const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
|
|
39
39
|
const globals = new Set(
|
|
40
40
|
held.symbols.filter((one) => isGlobalBlock(one, held, files)).map((one) => one.id),
|
|
41
41
|
);
|
|
42
42
|
if (globals.size === 0 && held.modules.size === 0 && scripts.size === 0) {
|
|
43
|
-
return
|
|
43
|
+
return undefined;
|
|
44
44
|
}
|
|
45
45
|
const byId = new Map(held.symbols.map((one) => [one.id, one]));
|
|
46
|
-
|
|
46
|
+
return (id) => {
|
|
47
47
|
const ids: string[] = [];
|
|
48
48
|
for (
|
|
49
49
|
let at = byId.get(byId.get(id)?.parent ?? "");
|
|
@@ -51,25 +51,79 @@ export function augmentationReferences(
|
|
|
51
51
|
at = byId.get(at.parent)
|
|
52
52
|
) {
|
|
53
53
|
if (globals.has(at.id) || held.modules.has(at.id)) {
|
|
54
|
-
return [...ids, at.id];
|
|
54
|
+
return { ids: [...ids, at.id], global: globals.has(at.id) };
|
|
55
55
|
}
|
|
56
56
|
if (scripts.has(at.id)) {
|
|
57
|
-
return ids;
|
|
57
|
+
return { ids, global: true };
|
|
58
58
|
}
|
|
59
59
|
if (at.kind === "interface" || at.kind === "namespace") {
|
|
60
60
|
ids.push(at.id);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
return [];
|
|
63
|
+
return { ids: [], global: false };
|
|
64
64
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The ambient interfaces of the global scope among `interfaces`: each one a declaration
|
|
69
|
+
* file or a `declare` holds that a `declare global` block, or the top level of a file in
|
|
70
|
+
* `scripts`, declares, directly or inside a namespace there.
|
|
71
|
+
*/
|
|
72
|
+
export function ambientGlobalInterfaces(
|
|
73
|
+
held: Inventory,
|
|
74
|
+
interfaces: readonly string[],
|
|
75
|
+
scripts: ReadonlySet<string>,
|
|
76
|
+
): readonly string[] {
|
|
77
|
+
const holders = holdersOf(held, scripts);
|
|
78
|
+
return holders === undefined
|
|
79
|
+
? []
|
|
80
|
+
: interfaces.filter((id) => held.ambient.has(id) && holders(id).global);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* For each reference to a declaration an augmentation block, or the global scope of a
|
|
85
|
+
* file in `scripts` (the files that are no module), holds, a read at the same place of
|
|
86
|
+
* every interface and namespace between it and the block or the file, and of the block
|
|
87
|
+
* itself. A read through an interface of `merged`, an ambient global interface merging
|
|
88
|
+
* into a language or library one, is a production read whoever writes it: it types a
|
|
89
|
+
* value the runtime provides, so a test reading a member needs it as code does.
|
|
90
|
+
*/
|
|
91
|
+
export function augmentationReferences(
|
|
92
|
+
held: Inventory,
|
|
93
|
+
found: readonly Reference[],
|
|
94
|
+
scripts: ReadonlySet<string> = new Set(),
|
|
95
|
+
merged: ReadonlySet<string> = new Set(),
|
|
96
|
+
): readonly Reference[] {
|
|
97
|
+
const holders = holdersOf(held, scripts);
|
|
98
|
+
if (holders === undefined) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
const fileOf = new Map(
|
|
102
|
+
held.symbols.filter((one) => one.kind === "file").map((one) => [one.position.path, one.id]),
|
|
74
103
|
);
|
|
104
|
+
// Two uses at one place of declarations one block holds read the block once there.
|
|
105
|
+
const made = new Set(found.map(referenceKey));
|
|
106
|
+
const reads = new Map<string, Reference>();
|
|
107
|
+
for (const reference of found) {
|
|
108
|
+
const { ids } = holders(reference.to);
|
|
109
|
+
// A test's read through such an interface is held by the test file, which the test
|
|
110
|
+
// runner loads whichever of its declarations runs.
|
|
111
|
+
const typed = reference.test && ids.some((id) => merged.has(id));
|
|
112
|
+
const from = typed ? (fileOf.get(reference.position.path) ?? reference.from) : reference.from;
|
|
113
|
+
for (const to of ids) {
|
|
114
|
+
const read: Reference = {
|
|
115
|
+
from,
|
|
116
|
+
to,
|
|
117
|
+
position: reference.position,
|
|
118
|
+
use: "read",
|
|
119
|
+
resolution: reference.resolution,
|
|
120
|
+
test: reference.test && !typed,
|
|
121
|
+
};
|
|
122
|
+
const key = referenceKey(read);
|
|
123
|
+
if (!made.has(key) && !reads.has(key)) {
|
|
124
|
+
reads.set(key, read);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return [...reads.values()];
|
|
75
129
|
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The global names and ambient modules of the `@types` packages a compiler before version 6
|
|
3
|
+
* includes automatically: every package of each type root when the configuration lists no
|
|
4
|
+
* `types`. Version 6 and later include none, so the analysis program holds none of their
|
|
5
|
+
* declarations, and a use of one resolves to nothing; each such use is a use of the package.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
isClassDeclaration,
|
|
10
|
+
isEnumDeclaration,
|
|
11
|
+
isExportAssignment,
|
|
12
|
+
isExportDeclaration,
|
|
13
|
+
isFunctionDeclaration,
|
|
14
|
+
isIdentifier,
|
|
15
|
+
isImportDeclaration,
|
|
16
|
+
isImportEqualsDeclaration,
|
|
17
|
+
isInterfaceDeclaration,
|
|
18
|
+
isModuleBlock,
|
|
19
|
+
isModuleDeclaration,
|
|
20
|
+
isStringLiteral,
|
|
21
|
+
isTypeAliasDeclaration,
|
|
22
|
+
isVariableStatement,
|
|
23
|
+
SyntaxKind,
|
|
24
|
+
type Node,
|
|
25
|
+
type SourceFile,
|
|
26
|
+
type Statement,
|
|
27
|
+
} from "@typescript/native/ast";
|
|
28
|
+
import type { ParseFile } from "./configuration-files.ts";
|
|
29
|
+
import type { DirectoryEntry, Host } from "./host.ts";
|
|
30
|
+
import { dirnamePath, joinPath, resolvePath } from "./paths.ts";
|
|
31
|
+
|
|
32
|
+
const TYPE_ROOT = "node_modules/@types";
|
|
33
|
+
|
|
34
|
+
/** What the automatically included `@types` packages of one configuration declare. */
|
|
35
|
+
export interface AutomaticTypes {
|
|
36
|
+
/** Per global name, the packages that declare it. */
|
|
37
|
+
readonly globals: ReadonlyMap<string, readonly string[]>;
|
|
38
|
+
/** Per ambient module name, the packages that declare it. */
|
|
39
|
+
readonly modules: ReadonlyMap<string, readonly string[]>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const NONE: AutomaticTypes = { globals: new Map(), modules: new Map() };
|
|
43
|
+
|
|
44
|
+
/** What one installed type package declares, by its directory. */
|
|
45
|
+
interface PackageDeclarations {
|
|
46
|
+
readonly globals: readonly string[];
|
|
47
|
+
readonly modules: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The packages each host has read, so a run's configurations share one reading of a type
|
|
52
|
+
* root; a host reads one unchanging tree for its whole life.
|
|
53
|
+
*/
|
|
54
|
+
const READ = new WeakMap<Host, Map<string, PackageDeclarations>>();
|
|
55
|
+
|
|
56
|
+
/** Whether one file is a module: it imports, exports or assigns an export at its top level. */
|
|
57
|
+
function isModuleFile(file: SourceFile): boolean {
|
|
58
|
+
return file.statements.some(
|
|
59
|
+
(statement) =>
|
|
60
|
+
isImportDeclaration(statement) ||
|
|
61
|
+
isImportEqualsDeclaration(statement) ||
|
|
62
|
+
isExportDeclaration(statement) ||
|
|
63
|
+
isExportAssignment(statement) ||
|
|
64
|
+
((statement as { readonly modifiers?: readonly Node[] }).modifiers ?? []).some(
|
|
65
|
+
(modifier) => modifier.kind === SyntaxKind.ExportKeyword,
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The names one statement of a global scope declares, and the ambient modules it declares. */
|
|
71
|
+
function declared(statement: Statement, globals: string[], modules: string[]): void {
|
|
72
|
+
if (isVariableStatement(statement)) {
|
|
73
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
74
|
+
if (isIdentifier(declaration.name)) {
|
|
75
|
+
globals.push(declaration.name.text);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else if (
|
|
79
|
+
isFunctionDeclaration(statement) ||
|
|
80
|
+
isClassDeclaration(statement) ||
|
|
81
|
+
isInterfaceDeclaration(statement) ||
|
|
82
|
+
isTypeAliasDeclaration(statement) ||
|
|
83
|
+
isEnumDeclaration(statement)
|
|
84
|
+
) {
|
|
85
|
+
if (statement.name !== undefined) {
|
|
86
|
+
globals.push(statement.name.text);
|
|
87
|
+
}
|
|
88
|
+
} else if (isModuleDeclaration(statement)) {
|
|
89
|
+
if (isStringLiteral(statement.name)) {
|
|
90
|
+
modules.push(statement.name.text);
|
|
91
|
+
} else {
|
|
92
|
+
globals.push(statement.name.text);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* What one declaration file declares in the global scope: its top level where it is no
|
|
99
|
+
* module, and the body of each `declare global` block where it is one.
|
|
100
|
+
*/
|
|
101
|
+
function fileDeclarations(file: SourceFile, globals: string[], modules: string[]): void {
|
|
102
|
+
if (!isModuleFile(file)) {
|
|
103
|
+
file.statements.forEach((statement) => {
|
|
104
|
+
declared(statement, globals, modules);
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const statement of file.statements) {
|
|
109
|
+
if (
|
|
110
|
+
isModuleDeclaration(statement) &&
|
|
111
|
+
isIdentifier(statement.name) &&
|
|
112
|
+
statement.name.text === "global" &&
|
|
113
|
+
statement.body !== undefined &&
|
|
114
|
+
isModuleBlock(statement.body)
|
|
115
|
+
) {
|
|
116
|
+
statement.body.statements.forEach((inner) => {
|
|
117
|
+
declared(inner, globals, modules);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The entry declaration file of one installed type package's directory. */
|
|
124
|
+
function entryOf(host: Host, dir: string): string {
|
|
125
|
+
try {
|
|
126
|
+
const manifest: unknown = JSON.parse(host.readFile(joinPath(dir, "package.json")));
|
|
127
|
+
const named =
|
|
128
|
+
typeof manifest === "object" && manifest !== null
|
|
129
|
+
? ((manifest as Record<string, unknown>)["types"] ??
|
|
130
|
+
(manifest as Record<string, unknown>)["typings"])
|
|
131
|
+
: undefined;
|
|
132
|
+
if (typeof named === "string" && named !== "") {
|
|
133
|
+
const path = resolvePath(dir, named);
|
|
134
|
+
return path.endsWith(".ts") ? path : `${path}.d.ts`;
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
// A package with no readable manifest is read from its index, as the compiler reads it.
|
|
138
|
+
}
|
|
139
|
+
return joinPath(dir, "index.d.ts");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Every global name and ambient module one installed type package declares: in its entry
|
|
144
|
+
* file and each file a `/// <reference path>` of a file read reaches, inside the package.
|
|
145
|
+
*/
|
|
146
|
+
function packageDeclarations(host: Host, dir: string, parse: ParseFile): PackageDeclarations {
|
|
147
|
+
const known = READ.get(host) ?? new Map<string, PackageDeclarations>();
|
|
148
|
+
READ.set(host, known);
|
|
149
|
+
const held = known.get(dir);
|
|
150
|
+
if (held !== undefined) {
|
|
151
|
+
return held;
|
|
152
|
+
}
|
|
153
|
+
const globals: string[] = [];
|
|
154
|
+
const modules: string[] = [];
|
|
155
|
+
const seen = new Set<string>();
|
|
156
|
+
const pending = [entryOf(host, dir)];
|
|
157
|
+
for (let path = pending.pop(); path !== undefined; path = pending.pop()) {
|
|
158
|
+
if (seen.has(path) || !path.startsWith(`${dir}/`)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
seen.add(path);
|
|
162
|
+
let text: string;
|
|
163
|
+
try {
|
|
164
|
+
text = host.readFile(path);
|
|
165
|
+
} catch {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const file = parse(path, text);
|
|
169
|
+
fileDeclarations(file, globals, modules);
|
|
170
|
+
for (const reference of file.referencedFiles) {
|
|
171
|
+
pending.push(resolvePath(dirnamePath(path), reference.fileName));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const found = { globals, modules };
|
|
175
|
+
known.set(dir, found);
|
|
176
|
+
return found;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* What the `@types` packages a compiler before version 6 would include for one
|
|
181
|
+
* configuration declare: none where its options list `types`, else every package of each
|
|
182
|
+
* `node_modules/@types` directory at or above the configuration's directory, the nearest
|
|
183
|
+
* holding a name winning.
|
|
184
|
+
*/
|
|
185
|
+
export function automaticTypes(
|
|
186
|
+
host: Host,
|
|
187
|
+
configFile: string,
|
|
188
|
+
options: Readonly<Record<string, unknown>>,
|
|
189
|
+
parse: ParseFile,
|
|
190
|
+
): AutomaticTypes {
|
|
191
|
+
if (options["types"] !== undefined) {
|
|
192
|
+
return NONE;
|
|
193
|
+
}
|
|
194
|
+
const globals = new Map<string, string[]>();
|
|
195
|
+
const modules = new Map<string, string[]>();
|
|
196
|
+
const add = (into: Map<string, string[]>, name: string, named: string): void => {
|
|
197
|
+
const held = into.get(name) ?? [];
|
|
198
|
+
if (!held.includes(named)) {
|
|
199
|
+
into.set(name, [...held, named]);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const read = new Set<string>();
|
|
203
|
+
for (const root of typeRoots(configFile, options)) {
|
|
204
|
+
let entries: readonly DirectoryEntry[];
|
|
205
|
+
try {
|
|
206
|
+
entries = host.readDirectory(root);
|
|
207
|
+
} catch {
|
|
208
|
+
entries = [];
|
|
209
|
+
}
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (read.has(entry.name) || host.kindOf(joinPath(root, entry.name)) !== "directory") {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
read.add(entry.name);
|
|
215
|
+
const named = `@types/${entry.name}`;
|
|
216
|
+
const found = packageDeclarations(host, joinPath(root, entry.name), parse);
|
|
217
|
+
found.globals.forEach((name) => {
|
|
218
|
+
add(globals, name, named);
|
|
219
|
+
});
|
|
220
|
+
found.modules.forEach((name) => {
|
|
221
|
+
add(modules, name, named);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { globals, modules };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The type roots of one configuration that a package manager installs into: those its
|
|
230
|
+
* `typeRoots` lists, else each `node_modules/@types` directory at or above its directory.
|
|
231
|
+
*/
|
|
232
|
+
function typeRoots(configFile: string, options: Readonly<Record<string, unknown>>): string[] {
|
|
233
|
+
const listed = options["typeRoots"];
|
|
234
|
+
if (Array.isArray(listed)) {
|
|
235
|
+
return listed.filter(
|
|
236
|
+
(root): root is string => typeof root === "string" && root.endsWith(`/${TYPE_ROOT}`),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const roots: string[] = [];
|
|
240
|
+
for (let dir = dirnamePath(configFile); ; dir = dirnamePath(dir)) {
|
|
241
|
+
roots.push(joinPath(dir, TYPE_ROOT));
|
|
242
|
+
if (dirnamePath(dir) === dir) {
|
|
243
|
+
return roots;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|