@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/dist/src/analysis.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* makes: a view carries a project's identity in its type so a handle cannot cross to
|
|
6
6
|
* another project's checker, which holds only for views the session hands out.
|
|
7
7
|
*/
|
|
8
|
+
import { isStringLiteralLikeNode } from "@typescript/native/ast";
|
|
8
9
|
import { lineOf, readComponent } from "./component-files.js";
|
|
9
10
|
import { kindOf } from "./config.js";
|
|
10
11
|
import { consumerLoads, consumerReferences, moduleIdentity, } from "./consumers.js";
|
|
@@ -12,7 +13,7 @@ import { configuredDeclarations, configuredRef, declarationsNamed, } from "./con
|
|
|
12
13
|
import { decorator } from "./decorator.js";
|
|
13
14
|
import { configurationNeeds, dependenciesOf, projectNeeds } from "./dependencies.js";
|
|
14
15
|
import { deprecatedDeclarations } from "./deprecation.js";
|
|
15
|
-
import { discoverProjects, DiscoveryError, isGuess, projectErrors, renderDiagnostic, targetRelative, } from "./discover.js";
|
|
16
|
+
import { discoverProjects, DiscoveryError, isGuess, MISSING_GLOBAL_TYPE, projectErrors, renderDiagnostic, targetRelative, withBaseUrlPaths, } from "./discover.js";
|
|
16
17
|
import { conventionFailuresFor, generatedFiles, readConventions, } from "./conventions.js";
|
|
17
18
|
import { readEdgeSides } from "./edges.js";
|
|
18
19
|
import { enumGroup } from "./enum-group.js";
|
|
@@ -162,6 +163,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
162
163
|
parse,
|
|
163
164
|
publishedAPI: config.targetKind !== "" && kindOf(config.targetKind, LANGUAGE) === "library",
|
|
164
165
|
publishedMembers: new Set((workspace?.entries ?? []).filter((entry) => entry.published).map((entry) => entry.dir)),
|
|
166
|
+
runFiles: new Set(opened.flatMap((one) => one.fileNames().map((name) => resolvePath(host.workingDirectory(), name)))),
|
|
165
167
|
};
|
|
166
168
|
};
|
|
167
169
|
const notBuilt = [...discovered.notBuilt];
|
|
@@ -193,6 +195,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
193
195
|
const joins = discovered.derived
|
|
194
196
|
? joinedTestFiles(engine, host, absoluteRoot, discovered.configFiles, testPatterns)
|
|
195
197
|
: new Map();
|
|
198
|
+
const programs = withBaseUrlPaths(engine, host, [...discovered.configFiles, ...consumerOf.keys()], workspace?.programs ?? new Map());
|
|
196
199
|
const session = runSession(engine, [...discovered.configFiles, ...consumerOf.keys()], (opened) => {
|
|
197
200
|
const consumer = consumerOf.get(opened.configFile);
|
|
198
201
|
const setup = consumer === undefined ? workspace?.unbuildable(opened.configFile) : undefined;
|
|
@@ -259,11 +262,26 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
259
262
|
configFile: project.configFile,
|
|
260
263
|
error: renderDiagnostic(one, (path) => relativePath(absoluteRoot, path) ?? path),
|
|
261
264
|
})));
|
|
265
|
+
const missingGlobal = raw.unfound.find(({ diagnostic }) => diagnostic.code === MISSING_GLOBAL_TYPE);
|
|
266
|
+
const skips = missingGlobal === undefined
|
|
267
|
+
? reading.skips
|
|
268
|
+
: [
|
|
269
|
+
...reading.skips,
|
|
270
|
+
...project.ownSourceFiles().map((file) => ({
|
|
271
|
+
record: {
|
|
272
|
+
path: relativePath(absoluteRoot, file.fileName) ?? file.fileName,
|
|
273
|
+
line: 1,
|
|
274
|
+
message: missingGlobal.diagnostic.text,
|
|
275
|
+
},
|
|
276
|
+
file,
|
|
277
|
+
unit: file,
|
|
278
|
+
})),
|
|
279
|
+
];
|
|
262
280
|
const configuration = ids.get(project.configFile) ?? project.configFile;
|
|
263
281
|
const read = () => {
|
|
264
282
|
const held = inventory(project, host, targetRoot);
|
|
265
283
|
const found = roots(project, held, targetRoot, options);
|
|
266
|
-
const kept = skippedRoots(project, held,
|
|
284
|
+
const kept = skippedRoots(project, held, skips);
|
|
267
285
|
const rooted = kept.length === 0
|
|
268
286
|
? found
|
|
269
287
|
: {
|
|
@@ -279,8 +297,8 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
279
297
|
configFile: project.configFile,
|
|
280
298
|
held,
|
|
281
299
|
rooted,
|
|
282
|
-
typeErrorSkips:
|
|
283
|
-
skipped: skippedUnits(
|
|
300
|
+
typeErrorSkips: skips.map((one) => one.record),
|
|
301
|
+
skipped: skippedUnits(skips, absoluteRoot),
|
|
284
302
|
referenceOnly: project
|
|
285
303
|
.ownSourceFiles()
|
|
286
304
|
.filter((file) => referenceOnly(file.fileName))
|
|
@@ -293,9 +311,7 @@ function readProjects(engine, host, scope, config, stage) {
|
|
|
293
311
|
heldFiles.add(relativePath(absoluteRoot, file.fileName) ?? file.fileName);
|
|
294
312
|
}
|
|
295
313
|
return stage(project, { configuration, read });
|
|
296
|
-
}, discovered.derived ? "record" : "refuse", joins.size === 0
|
|
297
|
-
? workspace?.programs
|
|
298
|
-
: withJoinedTests(engine, host, workspace?.programs ?? new Map(), joins), () => {
|
|
314
|
+
}, discovered.derived ? "record" : "refuse", joins.size === 0 ? programs : withJoinedTests(engine, host, programs, joins), () => {
|
|
299
315
|
outsideTests = testFilesAt(host, targetRoot, sourcePaths(host, targetRoot).filter((path) => !heldFiles.has(path) && testPatterns.some((pattern) => pattern.test(path))), parse);
|
|
300
316
|
}, readManifests);
|
|
301
317
|
for (const configFile of session.unopened) {
|
|
@@ -403,6 +419,55 @@ export const DETECTORS = new Map([
|
|
|
403
419
|
["framework-lifecycle", frameworkLifecycle],
|
|
404
420
|
["serialization-contract", serializationContract],
|
|
405
421
|
]);
|
|
422
|
+
/** The manifest section of a package test evidence counts, and the sections that keep one out of it. */
|
|
423
|
+
const DEV_SECTION = "devDependencies";
|
|
424
|
+
const OTHER_SECTIONS = ["dependencies", "peerDependencies", "optionalDependencies"];
|
|
425
|
+
/**
|
|
426
|
+
* The imports of a run's own files and the manifests below the target root that test
|
|
427
|
+
* evidence reads: a package is a devDependency of a file where the nearest manifest at or
|
|
428
|
+
* above it that declares the package declares it under `devDependencies` alone.
|
|
429
|
+
*/
|
|
430
|
+
function testEvidence(host, targetRoot, projects) {
|
|
431
|
+
const imports = new Map();
|
|
432
|
+
for (const one of projects) {
|
|
433
|
+
for (const [path, specifiers] of one.imports) {
|
|
434
|
+
imports.set(path, specifiers);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const manifests = new Map();
|
|
438
|
+
const manifestAt = (dir) => {
|
|
439
|
+
if (!manifests.has(dir)) {
|
|
440
|
+
let value;
|
|
441
|
+
try {
|
|
442
|
+
value = JSON.parse(host.readFile(joinPath(dir, "package.json")));
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
value = undefined;
|
|
446
|
+
}
|
|
447
|
+
manifests.set(dir, typeof value === "object" && value !== null && !Array.isArray(value)
|
|
448
|
+
? value
|
|
449
|
+
: undefined);
|
|
450
|
+
}
|
|
451
|
+
return manifests.get(dir);
|
|
452
|
+
};
|
|
453
|
+
const declares = (section, name) => typeof section === "object" && section !== null && Object.hasOwn(section, name);
|
|
454
|
+
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
455
|
+
return {
|
|
456
|
+
imports,
|
|
457
|
+
devDependency: (path, name) => {
|
|
458
|
+
for (let dir = dirnamePath(joinPath(root, path));; dir = dirnamePath(dir)) {
|
|
459
|
+
const manifest = manifestAt(dir);
|
|
460
|
+
const other = OTHER_SECTIONS.some((one) => declares(manifest?.[one], name));
|
|
461
|
+
if (declares(manifest?.[DEV_SECTION], name) || other) {
|
|
462
|
+
return !other;
|
|
463
|
+
}
|
|
464
|
+
if (normalizePath(dir) === normalizePath(root) || dirnamePath(dir) === dir) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
}
|
|
406
471
|
/**
|
|
407
472
|
* Reads every project of the run, its references and exemptions, and `extra`'s answer,
|
|
408
473
|
* while the project's view is open, and merges the projects into one matrix in the run's
|
|
@@ -441,6 +506,12 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
441
506
|
testFiles: resolved.testFilePaths,
|
|
442
507
|
evidence,
|
|
443
508
|
componentFiles: components.map(pathOf),
|
|
509
|
+
imports: new Map(project.ownSourceFiles().flatMap((file) => {
|
|
510
|
+
const path = relativePath(targetRoot, file.fileName);
|
|
511
|
+
return path === undefined
|
|
512
|
+
? []
|
|
513
|
+
: [[path, file.imports.filter(isStringLiteralLikeNode).map((one) => one.text)]];
|
|
514
|
+
})),
|
|
444
515
|
componentWarnings: components.flatMap((file) => readComponent(file.originalText, extensionOf(file.fileName)).warnings.map((warning) => ({
|
|
445
516
|
path: pathOf(file),
|
|
446
517
|
line: lineOf(file.originalText, warning.offset),
|
|
@@ -460,7 +531,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
460
531
|
}));
|
|
461
532
|
// Test-support code is code only test code imports in any configuration of the run: a
|
|
462
533
|
// configuration that holds no test file says nothing about what the tests import.
|
|
463
|
-
const support = testSupportFiles(withOutside.flatMap((one) => one.projectRead.held.symbols), withOutside.flatMap((one) => one.references), withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability), withOutside.flatMap((one) => one.testFiles));
|
|
534
|
+
const { support, testUsed } = testSupportFiles(withOutside.flatMap((one) => one.projectRead.held.symbols), withOutside.flatMap((one) => one.references), withOutside.flatMap((one) => one.projectRead.rooted.liveUnderReachability), withOutside.flatMap((one) => one.testFiles), testEvidence(host, targetRoot, withOutside));
|
|
464
535
|
const settled = settledSatisfaction(withOutside.map((one) => ({ evidence: one.evidence, held: one.projectRead.held })), [...withOutside.flatMap((one) => one.references), ...read.consumers.references]);
|
|
465
536
|
const answers = withOutside.map((one, index) => {
|
|
466
537
|
const { projectRead } = one;
|
|
@@ -473,13 +544,16 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
473
544
|
const made = supportReferences([...one.references, ...(satisfied?.references ?? [])], support);
|
|
474
545
|
const held = new Set(projectRead.held.symbols.map((symbol) => symbol.position.path));
|
|
475
546
|
const supportFiles = [...support].filter((path) => held.has(path)).sort();
|
|
547
|
+
const testUsedFiles = [...testUsed].filter((path) => held.has(path)).sort();
|
|
476
548
|
const configured = {
|
|
477
549
|
configuration: projectRead.configuration,
|
|
478
550
|
symbols: projectRead.held.symbols,
|
|
479
551
|
references: [...made, ...evidenceReferences(projectRead.held.symbols, detected.inTestFiles)],
|
|
480
552
|
roots: projectRead.rooted.liveUnderReachability,
|
|
481
|
-
testFiles: one.testFiles,
|
|
553
|
+
testFiles: [...one.testFiles, ...supportFiles],
|
|
482
554
|
...(supportFiles.length === 0 ? {} : { supportFiles }),
|
|
555
|
+
...(testUsedFiles.length === 0 ? {} : { testUsedFiles }),
|
|
556
|
+
...(projectRead.held.ambient.size === 0 ? {} : { ambient: projectRead.held.ambient }),
|
|
483
557
|
...(projectRead.referenceOnly.length === 0
|
|
484
558
|
? {}
|
|
485
559
|
: { referenceOnly: projectRead.referenceOnly }),
|
|
@@ -500,6 +574,7 @@ function readRun(engine, host, scope, config, mode, detectors, extra) {
|
|
|
500
574
|
...exempt.filter(isUnansweredRecord).map((record) => record.id),
|
|
501
575
|
],
|
|
502
576
|
typeErrorSkips: projectRead.typeErrorSkips,
|
|
577
|
+
unmappedEntries: projectRead.rooted.unmappedEntries,
|
|
503
578
|
skipped: projectRead.skipped,
|
|
504
579
|
componentFiles: one.componentFiles,
|
|
505
580
|
componentWarnings: one.componentWarnings,
|
|
@@ -698,7 +773,8 @@ export function readEmitterRun(engine, host, scope, config, mode, detectors) {
|
|
|
698
773
|
* The workspace members whose dependencies the run judges: `read` holds those a built
|
|
699
774
|
* project holds a file below, `unbuilt` the others whose own directory, the nearest
|
|
700
775
|
* manifest's above the configuration, holds a configuration the run could not build. A
|
|
701
|
-
* member no configuration holds a file of is in neither.
|
|
776
|
+
* member no configuration holds a file of is in neither. A nested package no built project
|
|
777
|
+
* holds a file below joins `unbuilt` the same way.
|
|
702
778
|
*/
|
|
703
779
|
function judgedMembers(host, targetRoot, read) {
|
|
704
780
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
@@ -717,6 +793,14 @@ function judgedMembers(host, targetRoot, read) {
|
|
|
717
793
|
judged.unbuilt.push(dir);
|
|
718
794
|
}
|
|
719
795
|
}
|
|
796
|
+
for (const dir of read.nestedDirs) {
|
|
797
|
+
const below = relativePath(root, dir);
|
|
798
|
+
if (below !== undefined &&
|
|
799
|
+
owners.has(dir) &&
|
|
800
|
+
![...held].some((path) => path.startsWith(`${below}/`))) {
|
|
801
|
+
judged.unbuilt.push(dir);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
720
804
|
return judged;
|
|
721
805
|
}
|
|
722
806
|
/** The directory of the nearest `package.json` at or above a file, inside `root`, else `root`. */
|
|
@@ -801,6 +885,7 @@ function readAnalysis(engine, host, scope, config, provenance, mode, detectors)
|
|
|
801
885
|
componentsUnread: read.componentsUnread,
|
|
802
886
|
librariesUnfound: read.librariesUnfound,
|
|
803
887
|
conventionsApplied: read.conventionsApplied,
|
|
888
|
+
unmappedEntries: unmappedEverywhere(read.projects),
|
|
804
889
|
},
|
|
805
890
|
configured: read.projects.map((one) => one.configured),
|
|
806
891
|
symbols,
|
|
@@ -837,6 +922,19 @@ function inputsOver(host, scope, config, provenance, mode, read, documents) {
|
|
|
837
922
|
suppressions,
|
|
838
923
|
};
|
|
839
924
|
}
|
|
925
|
+
/** The manifest entries every project of the run reads back to no file, in the first project's order. */
|
|
926
|
+
function unmappedEverywhere(projects) {
|
|
927
|
+
const key = (entry) => `${entry.dir}\u0000${entry.member}`;
|
|
928
|
+
const [first, ...rest] = projects;
|
|
929
|
+
const others = rest.map((one) => new Set(one.unmappedEntries.map(key)));
|
|
930
|
+
const found = new Map();
|
|
931
|
+
for (const entry of first?.unmappedEntries ?? []) {
|
|
932
|
+
if (others.every((one) => one.has(key(entry)))) {
|
|
933
|
+
found.set(key(entry), entry);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return [...found.values()];
|
|
937
|
+
}
|
|
840
938
|
/**
|
|
841
939
|
* The analysis of the run the scope and the configuration describe: the run read with its
|
|
842
940
|
* suppressions bound, every family's findings with the suppressions applied and the
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* name, which the language or a library types values by where the target never names it,
|
|
7
7
|
* as `import.meta` or `process.env`, so a use of its member is a use of it.
|
|
8
8
|
*/
|
|
9
|
+
import { referenceKey } from "./matrix.js";
|
|
9
10
|
/** Whether one declaration is a `declare global` block: a module-level namespace named `global`. */
|
|
10
11
|
function isGlobalBlock(symbol, held, files) {
|
|
11
12
|
return (symbol.kind === "namespace" &&
|
|
@@ -14,39 +15,82 @@ function isGlobalBlock(symbol, held, files) {
|
|
|
14
15
|
!held.modules.has(symbol.id));
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
* file in `scripts
|
|
19
|
-
*
|
|
20
|
-
* files that are no module, by identifier.
|
|
18
|
+
* Per declaration, the interfaces and namespaces between it and the augmentation block or
|
|
19
|
+
* the file in `scripts` that holds it, with the block itself, or none where no such block
|
|
20
|
+
* or file holds it; and whether that holder is the global scope rather than a module
|
|
21
|
+
* declaration. `scripts` holds the files that are no module, by identifier.
|
|
21
22
|
*/
|
|
22
|
-
|
|
23
|
+
function holdersOf(held, scripts) {
|
|
23
24
|
const files = new Set(held.symbols.filter((one) => one.kind === "file").map((one) => one.id));
|
|
24
25
|
const globals = new Set(held.symbols.filter((one) => isGlobalBlock(one, held, files)).map((one) => one.id));
|
|
25
26
|
if (globals.size === 0 && held.modules.size === 0 && scripts.size === 0) {
|
|
26
|
-
return
|
|
27
|
+
return undefined;
|
|
27
28
|
}
|
|
28
29
|
const byId = new Map(held.symbols.map((one) => [one.id, one]));
|
|
29
|
-
|
|
30
|
+
return (id) => {
|
|
30
31
|
const ids = [];
|
|
31
32
|
for (let at = byId.get(byId.get(id)?.parent ?? ""); at !== undefined; at = byId.get(at.parent)) {
|
|
32
33
|
if (globals.has(at.id) || held.modules.has(at.id)) {
|
|
33
|
-
return [...ids, at.id];
|
|
34
|
+
return { ids: [...ids, at.id], global: globals.has(at.id) };
|
|
34
35
|
}
|
|
35
36
|
if (scripts.has(at.id)) {
|
|
36
|
-
return ids;
|
|
37
|
+
return { ids, global: true };
|
|
37
38
|
}
|
|
38
39
|
if (at.kind === "interface" || at.kind === "namespace") {
|
|
39
40
|
ids.push(at.id);
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
return [];
|
|
43
|
+
return { ids: [], global: false };
|
|
43
44
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The ambient interfaces of the global scope among `interfaces`: each one a declaration
|
|
48
|
+
* file or a `declare` holds that a `declare global` block, or the top level of a file in
|
|
49
|
+
* `scripts`, declares, directly or inside a namespace there.
|
|
50
|
+
*/
|
|
51
|
+
export function ambientGlobalInterfaces(held, interfaces, scripts) {
|
|
52
|
+
const holders = holdersOf(held, scripts);
|
|
53
|
+
return holders === undefined
|
|
54
|
+
? []
|
|
55
|
+
: interfaces.filter((id) => held.ambient.has(id) && holders(id).global);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* For each reference to a declaration an augmentation block, or the global scope of a
|
|
59
|
+
* file in `scripts` (the files that are no module), holds, a read at the same place of
|
|
60
|
+
* every interface and namespace between it and the block or the file, and of the block
|
|
61
|
+
* itself. A read through an interface of `merged`, an ambient global interface merging
|
|
62
|
+
* into a language or library one, is a production read whoever writes it: it types a
|
|
63
|
+
* value the runtime provides, so a test reading a member needs it as code does.
|
|
64
|
+
*/
|
|
65
|
+
export function augmentationReferences(held, found, scripts = new Set(), merged = new Set()) {
|
|
66
|
+
const holders = holdersOf(held, scripts);
|
|
67
|
+
if (holders === undefined) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const fileOf = new Map(held.symbols.filter((one) => one.kind === "file").map((one) => [one.position.path, one.id]));
|
|
71
|
+
// Two uses at one place of declarations one block holds read the block once there.
|
|
72
|
+
const made = new Set(found.map(referenceKey));
|
|
73
|
+
const reads = new Map();
|
|
74
|
+
for (const reference of found) {
|
|
75
|
+
const { ids } = holders(reference.to);
|
|
76
|
+
// A test's read through such an interface is held by the test file, which the test
|
|
77
|
+
// runner loads whichever of its declarations runs.
|
|
78
|
+
const typed = reference.test && ids.some((id) => merged.has(id));
|
|
79
|
+
const from = typed ? (fileOf.get(reference.position.path) ?? reference.from) : reference.from;
|
|
80
|
+
for (const to of ids) {
|
|
81
|
+
const read = {
|
|
82
|
+
from,
|
|
83
|
+
to,
|
|
84
|
+
position: reference.position,
|
|
85
|
+
use: "read",
|
|
86
|
+
resolution: reference.resolution,
|
|
87
|
+
test: reference.test && !typed,
|
|
88
|
+
};
|
|
89
|
+
const key = referenceKey(read);
|
|
90
|
+
if (!made.has(key) && !reads.has(key)) {
|
|
91
|
+
reads.set(key, read);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [...reads.values()];
|
|
52
96
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
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
|
+
import { isClassDeclaration, isEnumDeclaration, isExportAssignment, isExportDeclaration, isFunctionDeclaration, isIdentifier, isImportDeclaration, isImportEqualsDeclaration, isInterfaceDeclaration, isModuleBlock, isModuleDeclaration, isStringLiteral, isTypeAliasDeclaration, isVariableStatement, SyntaxKind, } from "@typescript/native/ast";
|
|
8
|
+
import { dirnamePath, joinPath, resolvePath } from "./paths.js";
|
|
9
|
+
const TYPE_ROOT = "node_modules/@types";
|
|
10
|
+
const NONE = { globals: new Map(), modules: new Map() };
|
|
11
|
+
/**
|
|
12
|
+
* The packages each host has read, so a run's configurations share one reading of a type
|
|
13
|
+
* root; a host reads one unchanging tree for its whole life.
|
|
14
|
+
*/
|
|
15
|
+
const READ = new WeakMap();
|
|
16
|
+
/** Whether one file is a module: it imports, exports or assigns an export at its top level. */
|
|
17
|
+
function isModuleFile(file) {
|
|
18
|
+
return file.statements.some((statement) => isImportDeclaration(statement) ||
|
|
19
|
+
isImportEqualsDeclaration(statement) ||
|
|
20
|
+
isExportDeclaration(statement) ||
|
|
21
|
+
isExportAssignment(statement) ||
|
|
22
|
+
(statement.modifiers ?? []).some((modifier) => modifier.kind === SyntaxKind.ExportKeyword));
|
|
23
|
+
}
|
|
24
|
+
/** The names one statement of a global scope declares, and the ambient modules it declares. */
|
|
25
|
+
function declared(statement, globals, modules) {
|
|
26
|
+
if (isVariableStatement(statement)) {
|
|
27
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
28
|
+
if (isIdentifier(declaration.name)) {
|
|
29
|
+
globals.push(declaration.name.text);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (isFunctionDeclaration(statement) ||
|
|
34
|
+
isClassDeclaration(statement) ||
|
|
35
|
+
isInterfaceDeclaration(statement) ||
|
|
36
|
+
isTypeAliasDeclaration(statement) ||
|
|
37
|
+
isEnumDeclaration(statement)) {
|
|
38
|
+
if (statement.name !== undefined) {
|
|
39
|
+
globals.push(statement.name.text);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (isModuleDeclaration(statement)) {
|
|
43
|
+
if (isStringLiteral(statement.name)) {
|
|
44
|
+
modules.push(statement.name.text);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
globals.push(statement.name.text);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* What one declaration file declares in the global scope: its top level where it is no
|
|
53
|
+
* module, and the body of each `declare global` block where it is one.
|
|
54
|
+
*/
|
|
55
|
+
function fileDeclarations(file, globals, modules) {
|
|
56
|
+
if (!isModuleFile(file)) {
|
|
57
|
+
file.statements.forEach((statement) => {
|
|
58
|
+
declared(statement, globals, modules);
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
for (const statement of file.statements) {
|
|
63
|
+
if (isModuleDeclaration(statement) &&
|
|
64
|
+
isIdentifier(statement.name) &&
|
|
65
|
+
statement.name.text === "global" &&
|
|
66
|
+
statement.body !== undefined &&
|
|
67
|
+
isModuleBlock(statement.body)) {
|
|
68
|
+
statement.body.statements.forEach((inner) => {
|
|
69
|
+
declared(inner, globals, modules);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** The entry declaration file of one installed type package's directory. */
|
|
75
|
+
function entryOf(host, dir) {
|
|
76
|
+
try {
|
|
77
|
+
const manifest = JSON.parse(host.readFile(joinPath(dir, "package.json")));
|
|
78
|
+
const named = typeof manifest === "object" && manifest !== null
|
|
79
|
+
? (manifest["types"] ??
|
|
80
|
+
manifest["typings"])
|
|
81
|
+
: undefined;
|
|
82
|
+
if (typeof named === "string" && named !== "") {
|
|
83
|
+
const path = resolvePath(dir, named);
|
|
84
|
+
return path.endsWith(".ts") ? path : `${path}.d.ts`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// A package with no readable manifest is read from its index, as the compiler reads it.
|
|
89
|
+
}
|
|
90
|
+
return joinPath(dir, "index.d.ts");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Every global name and ambient module one installed type package declares: in its entry
|
|
94
|
+
* file and each file a `/// <reference path>` of a file read reaches, inside the package.
|
|
95
|
+
*/
|
|
96
|
+
function packageDeclarations(host, dir, parse) {
|
|
97
|
+
const known = READ.get(host) ?? new Map();
|
|
98
|
+
READ.set(host, known);
|
|
99
|
+
const held = known.get(dir);
|
|
100
|
+
if (held !== undefined) {
|
|
101
|
+
return held;
|
|
102
|
+
}
|
|
103
|
+
const globals = [];
|
|
104
|
+
const modules = [];
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
const pending = [entryOf(host, dir)];
|
|
107
|
+
for (let path = pending.pop(); path !== undefined; path = pending.pop()) {
|
|
108
|
+
if (seen.has(path) || !path.startsWith(`${dir}/`)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
seen.add(path);
|
|
112
|
+
let text;
|
|
113
|
+
try {
|
|
114
|
+
text = host.readFile(path);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const file = parse(path, text);
|
|
120
|
+
fileDeclarations(file, globals, modules);
|
|
121
|
+
for (const reference of file.referencedFiles) {
|
|
122
|
+
pending.push(resolvePath(dirnamePath(path), reference.fileName));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const found = { globals, modules };
|
|
126
|
+
known.set(dir, found);
|
|
127
|
+
return found;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* What the `@types` packages a compiler before version 6 would include for one
|
|
131
|
+
* configuration declare: none where its options list `types`, else every package of each
|
|
132
|
+
* `node_modules/@types` directory at or above the configuration's directory, the nearest
|
|
133
|
+
* holding a name winning.
|
|
134
|
+
*/
|
|
135
|
+
export function automaticTypes(host, configFile, options, parse) {
|
|
136
|
+
if (options["types"] !== undefined) {
|
|
137
|
+
return NONE;
|
|
138
|
+
}
|
|
139
|
+
const globals = new Map();
|
|
140
|
+
const modules = new Map();
|
|
141
|
+
const add = (into, name, named) => {
|
|
142
|
+
const held = into.get(name) ?? [];
|
|
143
|
+
if (!held.includes(named)) {
|
|
144
|
+
into.set(name, [...held, named]);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const read = new Set();
|
|
148
|
+
for (const root of typeRoots(configFile, options)) {
|
|
149
|
+
let entries;
|
|
150
|
+
try {
|
|
151
|
+
entries = host.readDirectory(root);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
entries = [];
|
|
155
|
+
}
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (read.has(entry.name) || host.kindOf(joinPath(root, entry.name)) !== "directory") {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
read.add(entry.name);
|
|
161
|
+
const named = `@types/${entry.name}`;
|
|
162
|
+
const found = packageDeclarations(host, joinPath(root, entry.name), parse);
|
|
163
|
+
found.globals.forEach((name) => {
|
|
164
|
+
add(globals, name, named);
|
|
165
|
+
});
|
|
166
|
+
found.modules.forEach((name) => {
|
|
167
|
+
add(modules, name, named);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { globals, modules };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The type roots of one configuration that a package manager installs into: those its
|
|
175
|
+
* `typeRoots` lists, else each `node_modules/@types` directory at or above its directory.
|
|
176
|
+
*/
|
|
177
|
+
function typeRoots(configFile, options) {
|
|
178
|
+
const listed = options["typeRoots"];
|
|
179
|
+
if (Array.isArray(listed)) {
|
|
180
|
+
return listed.filter((root) => typeof root === "string" && root.endsWith(`/${TYPE_ROOT}`));
|
|
181
|
+
}
|
|
182
|
+
const roots = [];
|
|
183
|
+
for (let dir = dirnamePath(configFile);; dir = dirnamePath(dir)) {
|
|
184
|
+
roots.push(joinPath(dir, TYPE_ROOT));
|
|
185
|
+
if (dirnamePath(dir) === dir) {
|
|
186
|
+
return roots;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { resolvePath } from "./paths.js";
|
|
2
|
+
/**
|
|
3
|
+
* The `paths` one configuration's resolved options map under the compilers before version
|
|
4
|
+
* 7, each target absolute: a target is read against the effective `baseUrl`, and a specifier
|
|
5
|
+
* no other pattern maps is looked up below it. Undefined where no `baseUrl` is set, so the
|
|
6
|
+
* options' own `paths` stand. Version 7 reads no `baseUrl`:
|
|
7
|
+
* https://github.com/microsoft/TypeScript/issues/62207
|
|
8
|
+
*/
|
|
9
|
+
export function baseUrlPaths(options) {
|
|
10
|
+
const baseUrl = Reflect.get(options, "baseUrl");
|
|
11
|
+
if (typeof baseUrl !== "string") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return Object.fromEntries(Object.entries({ "*": ["*"], ...options.paths }).map(([key, targets]) => [
|
|
15
|
+
key,
|
|
16
|
+
targets.map((target) => resolvePath(baseUrl, target)),
|
|
17
|
+
]));
|
|
18
|
+
}
|
package/dist/src/components.js
CHANGED
|
@@ -129,12 +129,12 @@ function linesSpanned(symbols) {
|
|
|
129
129
|
return total;
|
|
130
130
|
}
|
|
131
131
|
/**
|
|
132
|
-
* The dead declarations by subgraph place (`at`, and `position` per graph declaration),
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* test
|
|
132
|
+
* The dead declarations by subgraph place (`at`, and `position` per graph declaration), a
|
|
133
|
+
* test file's only where it tests dead code or nothing the tests reach. `adjacent` holds each
|
|
134
|
+
* reference one makes to another, but an unreferenced test-file declaration's to production
|
|
135
|
+
* code, which decides nothing; an edge each way between a dead member and its dead container;
|
|
136
|
+
* and one back from each non-ambient production target of an admitted test, which puts the
|
|
137
|
+
* test in its subject's component. `references` holds the first alone.
|
|
138
138
|
*/
|
|
139
139
|
function deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest) {
|
|
140
140
|
const at = [];
|
|
@@ -158,6 +158,7 @@ function deadSubgraph(graph, dead, testOfDeadCode, unreferencedTest) {
|
|
|
158
158
|
references[from]?.push(to);
|
|
159
159
|
if (testOfDeadCode[index] === true &&
|
|
160
160
|
graph.test[edge.to] !== true &&
|
|
161
|
+
graph.ambient[edge.to] !== true &&
|
|
161
162
|
graph.subject[edge.to] === true) {
|
|
162
163
|
adjacent[to]?.push(from);
|
|
163
164
|
}
|
package/dist/src/config.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* every string either holds as it likes, so each one that spells a declared dependency
|
|
7
7
|
* uses it and each relative one that names a source file of the program roots the file.
|
|
8
8
|
*/
|
|
9
|
-
import { isExportDeclaration, isImportDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, } from "@typescript/native/ast";
|
|
9
|
+
import { isCallExpression, isExportDeclaration, isIdentifier, isImportDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
10
10
|
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
|
|
11
11
|
const MANIFEST = "package.json";
|
|
12
12
|
/** A configuration module's name, each form with the spelling a root reports it by. */
|
|
@@ -346,18 +346,56 @@ export function configurationFiles(host, targetRoot, files, parse) {
|
|
|
346
346
|
: outsideModules(host, root, [...dirs], files, holdsManifest, parse, named),
|
|
347
347
|
};
|
|
348
348
|
}
|
|
349
|
-
/** The
|
|
350
|
-
function
|
|
351
|
-
|
|
352
|
-
|
|
349
|
+
/** The literal module specifier one `require(...)` or `import(...)` call names, if any. */
|
|
350
|
+
function calledSpecifier(node) {
|
|
351
|
+
if (!isCallExpression(node) || node.arguments.length !== 1) {
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
const callee = node.expression;
|
|
355
|
+
const loads = callee.kind === SyntaxKind.ImportKeyword || (isIdentifier(callee) && callee.text === "require");
|
|
356
|
+
return loads ? node.arguments[0] : undefined;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The relative specifiers one file's imports and re-exports name, and those its
|
|
360
|
+
* `require(...)` and `import(...)` calls name by a literal.
|
|
361
|
+
*/
|
|
362
|
+
export function relativeSpecifiers(file) {
|
|
363
|
+
const known = specifiersOf.get(file);
|
|
364
|
+
if (known !== undefined) {
|
|
365
|
+
return known;
|
|
366
|
+
}
|
|
367
|
+
const found = [];
|
|
368
|
+
const named = (specifier) => {
|
|
369
|
+
if (specifier !== undefined &&
|
|
370
|
+
(isStringLiteral(specifier) || isNoSubstitutionTemplateLiteral(specifier)) &&
|
|
371
|
+
RELATIVE_PREFIXES.some((prefix) => specifier.text.startsWith(prefix))) {
|
|
372
|
+
found.push(specifier.text);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
const visit = (node) => {
|
|
376
|
+
named(calledSpecifier(node));
|
|
377
|
+
node.forEachChild(visit);
|
|
378
|
+
};
|
|
379
|
+
for (const statement of file.statements) {
|
|
380
|
+
named(isImportDeclaration(statement) || isExportDeclaration(statement)
|
|
353
381
|
? statement.moduleSpecifier
|
|
354
|
-
: undefined;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
382
|
+
: undefined);
|
|
383
|
+
visit(statement);
|
|
384
|
+
}
|
|
385
|
+
specifiersOf.set(file, found);
|
|
386
|
+
return found;
|
|
387
|
+
}
|
|
388
|
+
/** What {@link relativeSpecifiers} found in each file, which every project of a run reads alike. */
|
|
389
|
+
const specifiersOf = new WeakMap();
|
|
390
|
+
/**
|
|
391
|
+
* The files no program holds that a chain from `seeds` reads: each seed, and each file
|
|
392
|
+
* below the target root one imports, re-exports or loads by a relative specifier that no
|
|
393
|
+
* program holds either, until no further file joins, each read against the manifest
|
|
394
|
+
* directory its seed names. A file that cannot be read is skipped.
|
|
395
|
+
*/
|
|
396
|
+
export function outsideChain(host, targetRoot, files, parse, seeds) {
|
|
397
|
+
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
398
|
+
return outsideModules(host, root, [], files, () => false, parse, seeds);
|
|
361
399
|
}
|
|
362
400
|
/**
|
|
363
401
|
* The paths one relative specifier from a configuration file may reach, in the order they
|