@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/analysis.js +106 -10
- package/dist/src/augmentations.js +21 -8
- 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 +75 -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 +116 -21
- 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 +21 -1
- package/dist/src/findings/visibility-narrowing.js +22 -3
- 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 +18 -9
- 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 +4 -1
- 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 +7 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/test-support.js +121 -17
- 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 +143 -10
- package/src/augmentations.ts +21 -10
- 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 +103 -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 +10 -0
- package/src/findings/completion.ts +147 -24
- 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 +25 -1
- package/src/findings/visibility-narrowing.ts +32 -3
- 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 +32 -14
- 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 +10 -1
- 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 +9 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/test-support.ts +149 -16
- 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/discover.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { DiagnosticCategory } from "@typescript/native/sync";
|
|
2
2
|
import type { Diagnostic } from "@typescript/native/sync";
|
|
3
|
+
import { baseUrlPaths } from "./base-url.ts";
|
|
3
4
|
import type { BuildConfiguration, ProjectConfiguration } from "./config.ts";
|
|
4
5
|
import type { Host } from "./host.ts";
|
|
5
|
-
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
6
|
+
import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
6
7
|
import type { Scope } from "./scope.ts";
|
|
7
8
|
import { SetupError } from "./setup-failure.ts";
|
|
8
9
|
import type { Engine, ProjectDiagnostics } from "./session.ts";
|
|
10
|
+
import { writtenPath, type WorkspaceProgram } from "./workspace-programs.ts";
|
|
9
11
|
|
|
10
12
|
/** Directory names discovery does not descend into. */
|
|
11
13
|
export const IGNORED_DIRECTORIES: ReadonlySet<string> = new Set(["node_modules", ".git"]);
|
|
@@ -112,10 +114,18 @@ interface ProjectErrors {
|
|
|
112
114
|
|
|
113
115
|
/**
|
|
114
116
|
* The codes of a type library and a lib the configuration names that the compiler
|
|
115
|
-
* cannot find, which it reports while it gathers the program's files
|
|
116
|
-
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/compiler/processingDiagnostic.go
|
|
117
|
+
* cannot find, which it reports while it gathers the program's files,
|
|
118
|
+
* https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/compiler/processingDiagnostic.go,
|
|
119
|
+
* and {@link MISSING_GLOBAL_TYPE}, which the checker reports.
|
|
117
120
|
*/
|
|
118
|
-
const UNFOUND_LIBRARY: ReadonlySet<number> = new Set([2688, 2726, 2727]);
|
|
121
|
+
const UNFOUND_LIBRARY: ReadonlySet<number> = new Set([2318, 2688, 2726, 2727]);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The code of a global type the checker finds in no library the program holds. The checker
|
|
125
|
+
* types every use of one against nothing and places no error in the file, so the analysis
|
|
126
|
+
* skips each file of such a project whole.
|
|
127
|
+
*/
|
|
128
|
+
export const MISSING_GLOBAL_TYPE = 2318;
|
|
119
129
|
|
|
120
130
|
/** The errors one open project carries, each located in the file it names. */
|
|
121
131
|
export function projectErrors(sets: ProjectDiagnostics): ProjectErrors {
|
|
@@ -337,9 +347,110 @@ function derivedId(targetRoot: string, configFile: string): string {
|
|
|
337
347
|
return relativePath(targetRoot, configFile) ?? configFile;
|
|
338
348
|
}
|
|
339
349
|
|
|
350
|
+
/**
|
|
351
|
+
* The code of a configuration whose `extends` chain returns to a configuration it already
|
|
352
|
+
* passed. The parse reports it and returns, and a snapshot opened from it never answers:
|
|
353
|
+
* https://github.com/microsoft/TypeScript/issues/64754
|
|
354
|
+
*/
|
|
355
|
+
const CIRCULAR_EXTENDS = 18000;
|
|
356
|
+
|
|
357
|
+
type Parsed = ReturnType<Engine["parseConfigFile"]>;
|
|
358
|
+
|
|
359
|
+
/** The configuration files one parsed configuration's own `extends` names by path. */
|
|
360
|
+
function extendedPaths(parsed: Parsed, configFile: string): readonly string[] {
|
|
361
|
+
const { raw } = parsed as { readonly raw?: unknown };
|
|
362
|
+
const named: unknown = typeof raw === "object" && raw !== null ? Reflect.get(raw, "extends") : [];
|
|
363
|
+
return (Array.isArray(named) ? named : [named])
|
|
364
|
+
.filter((one): one is string => typeof one === "string")
|
|
365
|
+
.filter((one) => one.startsWith("./") || one.startsWith("../") || isAbsolutePath(one))
|
|
366
|
+
.map((one) => resolvePath(dirnamePath(configFile), one));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* The `extends` chain from `configFile` to the first configuration it reaches twice, each
|
|
371
|
+
* link by path, or undefined where no chain written by path returns.
|
|
372
|
+
*/
|
|
373
|
+
function extendsCycle(
|
|
374
|
+
engine: Engine,
|
|
375
|
+
configFile: string,
|
|
376
|
+
parsed: Parsed,
|
|
377
|
+
): readonly string[] | undefined {
|
|
378
|
+
const walk = (chain: readonly string[], at: Parsed): readonly string[] | undefined => {
|
|
379
|
+
for (const next of extendedPaths(at, chain.at(-1) ?? configFile)) {
|
|
380
|
+
if (chain.includes(next)) {
|
|
381
|
+
return [...chain, next];
|
|
382
|
+
}
|
|
383
|
+
let read: Parsed;
|
|
384
|
+
try {
|
|
385
|
+
read = engine.parseConfigFile(next);
|
|
386
|
+
} catch {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const cycle = walk([...chain, next], read);
|
|
390
|
+
if (cycle !== undefined) {
|
|
391
|
+
return cycle;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return undefined;
|
|
395
|
+
};
|
|
396
|
+
return walk([configFile], parsed);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** The configuration files one parsed configuration's `references` name. */
|
|
400
|
+
function referencesOf(parsed: Parsed): readonly string[] {
|
|
401
|
+
// The parse answers a directory reference as the directory; the compiler reads a
|
|
402
|
+
// path not ending `.json` as its `tsconfig.json`:
|
|
403
|
+
// https://github.com/microsoft/typescript-go/blob/89d5d5b2849a0db0957065889ca58536fa6d2e4a/internal/core/projectreference.go#L18
|
|
404
|
+
return (parsed.projectReferences ?? []).map(({ path }) =>
|
|
405
|
+
path.endsWith(".json") ? path : joinPath(path, "tsconfig.json"),
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Why one parsed configuration's own `extends` chain cannot be opened, where it is circular. */
|
|
410
|
+
function circularity(engine: Engine, configFile: string, parsed: Parsed): string | undefined {
|
|
411
|
+
const chain = extendsCycle(engine, configFile, parsed);
|
|
412
|
+
if (chain === undefined && !parsed.errors.some(({ code }) => code === CIRCULAR_EXTENDS)) {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
return `TS${String(CIRCULAR_EXTENDS)}: Circularity detected while resolving configuration: ${(chain ?? [configFile]).join(" -> ")}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Why a configuration the `references` reach, transitively, cannot be opened, where one's
|
|
420
|
+
* `extends` chain is circular: the compiler opens every referenced project with the one
|
|
421
|
+
* that references it.
|
|
422
|
+
*/
|
|
423
|
+
function referencedCircularity(
|
|
424
|
+
engine: Engine,
|
|
425
|
+
references: readonly string[],
|
|
426
|
+
visited: Set<string>,
|
|
427
|
+
): string | undefined {
|
|
428
|
+
for (const reference of references) {
|
|
429
|
+
if (visited.has(reference)) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
visited.add(reference);
|
|
433
|
+
let parsed: Parsed;
|
|
434
|
+
try {
|
|
435
|
+
parsed = engine.parseConfigFile(reference);
|
|
436
|
+
} catch {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const reason =
|
|
440
|
+
circularity(engine, reference, parsed) ??
|
|
441
|
+
referencedCircularity(engine, referencesOf(parsed), visited);
|
|
442
|
+
if (reason !== undefined) {
|
|
443
|
+
return reason;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
|
|
340
449
|
/**
|
|
341
450
|
* The configuration files one configuration's `references` name, or why the compiler
|
|
342
|
-
* cannot read the configuration
|
|
451
|
+
* cannot read the configuration: it throws, or its `extends` chain or that of a
|
|
452
|
+
* configuration it references is circular, which the analysis refuses before any
|
|
453
|
+
* snapshot is asked to open it.
|
|
343
454
|
*/
|
|
344
455
|
function readConfiguration(
|
|
345
456
|
engine: Engine,
|
|
@@ -347,14 +458,11 @@ function readConfiguration(
|
|
|
347
458
|
): { readonly references: readonly string[] } | { readonly reason: string } {
|
|
348
459
|
try {
|
|
349
460
|
const parsed = engine.parseConfigFile(configFile);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
path.endsWith(".json") ? path : joinPath(path, "tsconfig.json"),
|
|
356
|
-
),
|
|
357
|
-
};
|
|
461
|
+
const references = referencesOf(parsed);
|
|
462
|
+
const reason =
|
|
463
|
+
circularity(engine, configFile, parsed) ??
|
|
464
|
+
referencedCircularity(engine, references, new Set([configFile]));
|
|
465
|
+
return reason === undefined ? { references } : { reason };
|
|
358
466
|
} catch (error: unknown) {
|
|
359
467
|
return { reason: error instanceof Error ? error.message : String(error) };
|
|
360
468
|
}
|
|
@@ -508,3 +616,38 @@ export function discoverProjects(
|
|
|
508
616
|
}
|
|
509
617
|
return derivedProjects(engine, host, scope);
|
|
510
618
|
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* How the snapshot reads each configuration of `configFiles` that sets `baseUrl` and
|
|
622
|
+
* `programs` does not already describe: a configuration only the snapshot holds, which
|
|
623
|
+
* extends it and states the `paths` {@link baseUrlPaths} gives, so its imports resolve as
|
|
624
|
+
* the compilers before version 7 resolve them. A configuration the compiler cannot read is
|
|
625
|
+
* left for its project to refuse.
|
|
626
|
+
*/
|
|
627
|
+
export function withBaseUrlPaths(
|
|
628
|
+
engine: Engine,
|
|
629
|
+
host: Host,
|
|
630
|
+
configFiles: readonly string[],
|
|
631
|
+
programs: ReadonlyMap<string, WorkspaceProgram>,
|
|
632
|
+
): ReadonlyMap<string, WorkspaceProgram> {
|
|
633
|
+
const found = new Map(programs);
|
|
634
|
+
for (const configFile of configFiles) {
|
|
635
|
+
if (found.has(configFile)) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
let paths: Record<string, string[]> | undefined;
|
|
639
|
+
try {
|
|
640
|
+
paths = baseUrlPaths(engine.parseConfigFile(configFile).options);
|
|
641
|
+
} catch {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (paths !== undefined) {
|
|
645
|
+
found.set(configFile, {
|
|
646
|
+
kind: "configuration",
|
|
647
|
+
file: writtenPath(host, configFile),
|
|
648
|
+
text: `${JSON.stringify({ extends: configFile, compilerOptions: { paths } }, null, 2)}\n`,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return found;
|
|
653
|
+
}
|
package/src/entry-point-gaps.ts
CHANGED
|
@@ -58,7 +58,6 @@ const UNREAD_TOOLS: readonly string[] = [
|
|
|
58
58
|
"cypress",
|
|
59
59
|
"danger",
|
|
60
60
|
"dependency-cruiser",
|
|
61
|
-
"docusaurus",
|
|
62
61
|
"dotenv",
|
|
63
62
|
"drizzle",
|
|
64
63
|
"electron-vite",
|
|
@@ -112,14 +111,12 @@ const UNREAD_TOOLS: readonly string[] = [
|
|
|
112
111
|
"nuxtjs-i18n",
|
|
113
112
|
"nx",
|
|
114
113
|
"nyc",
|
|
115
|
-
"oclif",
|
|
116
114
|
"openapi-ts",
|
|
117
115
|
"openclaw",
|
|
118
116
|
"orval",
|
|
119
117
|
"oxfmt",
|
|
120
118
|
"oxlint",
|
|
121
119
|
"panda-css",
|
|
122
|
-
"parcel",
|
|
123
120
|
"payload",
|
|
124
121
|
"pino",
|
|
125
122
|
"playwright-ct",
|
|
@@ -164,7 +161,6 @@ const UNREAD_TOOLS: readonly string[] = [
|
|
|
164
161
|
"swc",
|
|
165
162
|
"syncpack",
|
|
166
163
|
"tailwind",
|
|
167
|
-
"tanstack-router",
|
|
168
164
|
"taskfile",
|
|
169
165
|
"tauri",
|
|
170
166
|
"temporal",
|
|
@@ -210,18 +206,19 @@ const UNREAD_TOOLS: readonly string[] = [
|
|
|
210
206
|
/** The conventions declined for a tool some of whose conventions are entered. */
|
|
211
207
|
const PARTLY_ENTERED: readonly DeclinedConvention[] = [
|
|
212
208
|
{
|
|
213
|
-
tool: "
|
|
214
|
-
convention: "a
|
|
209
|
+
tool: "docusaurus",
|
|
210
|
+
convention: "a page or theme directory a plugin's configuration moves, and the docs",
|
|
215
211
|
reason:
|
|
216
|
-
"
|
|
217
|
-
"
|
|
212
|
+
"The convention row reads src/pages and src/theme only, and a document's imports are " +
|
|
213
|
+
"read from every Markdown and MDX file, so a module only a moved directory holds is " +
|
|
214
|
+
"reported as never imported unless ts.entry_files names it.",
|
|
218
215
|
},
|
|
219
216
|
{
|
|
220
217
|
tool: "eslint",
|
|
221
|
-
convention: "a
|
|
218
|
+
convention: "a legacy configuration written in YAML or in JSON with comments",
|
|
222
219
|
reason:
|
|
223
|
-
"
|
|
224
|
-
"dependency only a
|
|
220
|
+
"Only a configuration module and a strict JSON configuration file are read, so a " +
|
|
221
|
+
"dependency only such a file names is reported as unused.",
|
|
225
222
|
},
|
|
226
223
|
{
|
|
227
224
|
tool: "playwright",
|
package/src/entry-points.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import {
|
|
15
15
|
isArrayLiteralExpression,
|
|
16
16
|
isAsExpression,
|
|
17
|
+
isBinaryExpression,
|
|
17
18
|
isCallExpression,
|
|
18
19
|
isExportAssignment,
|
|
19
20
|
isIdentifier,
|
|
@@ -26,6 +27,8 @@ import {
|
|
|
26
27
|
isPropertyAssignment,
|
|
27
28
|
isSatisfiesExpression,
|
|
28
29
|
isStringLiteral,
|
|
30
|
+
isTemplateExpression,
|
|
31
|
+
SyntaxKind,
|
|
29
32
|
type Node,
|
|
30
33
|
type SourceFile,
|
|
31
34
|
} from "@typescript/native/ast";
|
|
@@ -40,6 +43,7 @@ import type { Selected } from "./conventions.ts";
|
|
|
40
43
|
import { globExpression } from "./glob.ts";
|
|
41
44
|
import type { Host } from "./host.ts";
|
|
42
45
|
import { htmlEntries } from "./html-entries.ts";
|
|
46
|
+
import { documentEntries } from "./mdx-entries.ts";
|
|
43
47
|
import { dirnamePath, relativePath, resolvePath } from "./paths.ts";
|
|
44
48
|
import { UNANSWERED } from "./query.ts";
|
|
45
49
|
import type { ProjectView } from "./session.ts";
|
|
@@ -61,7 +65,15 @@ export type EntryRule =
|
|
|
61
65
|
/** A file a module script of an HTML file below the target root loads. */
|
|
62
66
|
| "html-entry"
|
|
63
67
|
/** A file a `run:` step of a workflow in the target root's `.github/workflows` names. */
|
|
64
|
-
| "workflow-step"
|
|
68
|
+
| "workflow-step"
|
|
69
|
+
/**
|
|
70
|
+
* A file a load whose path is computed may name: below the directory the literal
|
|
71
|
+
* start of an `import()` or `require()` argument names, or matched by the literal
|
|
72
|
+
* glob of `import.meta.glob`.
|
|
73
|
+
*/
|
|
74
|
+
| "computed-import"
|
|
75
|
+
/** A file an import line of a Markdown or MDX document below the target root names. */
|
|
76
|
+
| "document-import";
|
|
65
77
|
|
|
66
78
|
/** One file a rule enters. */
|
|
67
79
|
interface EntryPoint {
|
|
@@ -78,6 +90,8 @@ interface EntryPoint {
|
|
|
78
90
|
* level and names none of its exports.
|
|
79
91
|
*/
|
|
80
92
|
readonly exports: boolean;
|
|
93
|
+
/** Where the file is entered for some of its exports only, their export names. */
|
|
94
|
+
readonly names?: readonly string[];
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
/** One file-name convention: the stems a tool looks for, under each extension it loads. */
|
|
@@ -114,8 +128,11 @@ const SETUP_MEMBERS = ["setupFiles", "globalSetup"] as const;
|
|
|
114
128
|
/** A member that moves the directory every path of a test-runner configuration is read from. */
|
|
115
129
|
const ROOT_MEMBER = "root";
|
|
116
130
|
|
|
117
|
-
/**
|
|
118
|
-
|
|
131
|
+
/**
|
|
132
|
+
* A URL reference's scheme. A reference with none that does not start with a solidus is
|
|
133
|
+
* relative, the bare name included: https://url.spec.whatwg.org/#relative-url-string
|
|
134
|
+
*/
|
|
135
|
+
const URL_SCHEME = /^[a-z][a-z\d+.-]*:/iu;
|
|
119
136
|
|
|
120
137
|
/** The constructors whose first argument addresses a worker script. */
|
|
121
138
|
const WORKER_CONSTRUCTORS: ReadonlySet<string> = new Set(["Worker", "SharedWorker"]);
|
|
@@ -228,9 +245,17 @@ function literalsOf(value: Node): readonly string[] | undefined {
|
|
|
228
245
|
return found;
|
|
229
246
|
}
|
|
230
247
|
|
|
231
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* Whether a literal is a relative URL reference, which is also how the test runner reads a
|
|
250
|
+
* setup file's path against its root.
|
|
251
|
+
*/
|
|
232
252
|
function isRelative(text: string): boolean {
|
|
233
|
-
return
|
|
253
|
+
return text !== "" && !text.startsWith("/") && !URL_SCHEME.test(text);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Whether a module specifier is relative, which a bare name is not: it names a package. */
|
|
257
|
+
function isModuleRelative(text: string): boolean {
|
|
258
|
+
return text.startsWith("./") || text.startsWith("../");
|
|
234
259
|
}
|
|
235
260
|
|
|
236
261
|
/** A URL reference with its query and its fragment removed, which name no part of the file. */
|
|
@@ -470,10 +495,10 @@ function workerSitesOf(file: SourceFile): readonly WorkerSite[] {
|
|
|
470
495
|
}
|
|
471
496
|
|
|
472
497
|
/**
|
|
473
|
-
* The workers one file addresses. A call's names must resolve to the platform
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
498
|
+
* The workers one file addresses. A call's names must resolve to a symbol the platform
|
|
499
|
+
* declares, which a file of the target may augment and no import brings in; a name only
|
|
500
|
+
* the target declares is the target's own constructor or object, which addresses no
|
|
501
|
+
* worker this rule knows. One batch per file that writes such a call.
|
|
477
502
|
*/
|
|
478
503
|
function workersOf<Brand>(
|
|
479
504
|
project: ProjectView<Brand>,
|
|
@@ -498,8 +523,7 @@ function workersOf<Brand>(
|
|
|
498
523
|
if (
|
|
499
524
|
symbol !== undefined &&
|
|
500
525
|
(symbol.flags & SymbolFlags.Alias) === 0 &&
|
|
501
|
-
symbol.declarations.
|
|
502
|
-
symbol.declarations.every((handle) => !files.byName.has(handle.path))
|
|
526
|
+
symbol.declarations.some((handle) => !files.byName.has(handle.path))
|
|
503
527
|
) {
|
|
504
528
|
platform.add(name);
|
|
505
529
|
}
|
|
@@ -517,6 +541,94 @@ function workersOf<Brand>(
|
|
|
517
541
|
return found;
|
|
518
542
|
}
|
|
519
543
|
|
|
544
|
+
/** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
|
|
545
|
+
function leadingLiteral(node: Node): string | undefined {
|
|
546
|
+
const at = unwrapped(node);
|
|
547
|
+
if (isTemplateExpression(at)) {
|
|
548
|
+
return at.head.text;
|
|
549
|
+
}
|
|
550
|
+
if (isBinaryExpression(at) && at.operatorToken.kind === SyntaxKind.PlusToken) {
|
|
551
|
+
return literalOf(unwrapped(at.left)) ?? leadingLiteral(at.left);
|
|
552
|
+
}
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Whether one call is `import.meta.glob(...)`. */
|
|
557
|
+
function isMetaGlob(node: Node): boolean {
|
|
558
|
+
if (!isCallExpression(node) || !isPropertyAccessExpression(node.expression)) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
const { expression, name } = node.expression;
|
|
562
|
+
return (
|
|
563
|
+
isIdentifier(name) &&
|
|
564
|
+
name.text === "glob" &&
|
|
565
|
+
isMetaProperty(expression) &&
|
|
566
|
+
expression.keywordToken === SyntaxKind.ImportKeyword
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* The files one file loads by a computed path, each with what exports: every own file
|
|
572
|
+
* below the directory a relative literal start of an `import()` or `require()` argument
|
|
573
|
+
* names, other than the calling file's own directory or one holding it, which would name
|
|
574
|
+
* every sibling, and
|
|
575
|
+
* every own file a relative literal glob of `import.meta.glob` matches, both read against
|
|
576
|
+
* the calling file's directory, as a bundler's context module reads them.
|
|
577
|
+
*/
|
|
578
|
+
function computedImportsOf(file: SourceFile, files: SourceFiles): readonly EntryPoint[] {
|
|
579
|
+
const dir = dirnamePath(file.fileName);
|
|
580
|
+
const found: EntryPoint[] = [];
|
|
581
|
+
const below = (prefix: string): void => {
|
|
582
|
+
const target = resolvePath(dir, prefix);
|
|
583
|
+
if (
|
|
584
|
+
!isModuleRelative(prefix) ||
|
|
585
|
+
!prefix.endsWith("/") ||
|
|
586
|
+
target === dir ||
|
|
587
|
+
relativePath(target, dir) !== undefined
|
|
588
|
+
) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
for (const candidate of files.byName.values()) {
|
|
592
|
+
if (candidate !== file && relativePath(target, candidate.fileName) !== undefined) {
|
|
593
|
+
found.push({ file: candidate, rule: "computed-import", source: prefix, exports: true });
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
const globbed = (pattern: string): void => {
|
|
598
|
+
if (!isModuleRelative(pattern)) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
const expression = globExpression(resolvePath(dir, pattern));
|
|
602
|
+
for (const candidate of files.byName.values()) {
|
|
603
|
+
if (candidate !== file && expression.test(candidate.fileName)) {
|
|
604
|
+
found.push({ file: candidate, rule: "computed-import", source: pattern, exports: true });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
const visit = (node: Node): void => {
|
|
609
|
+
if (isCallExpression(node)) {
|
|
610
|
+
const [first] = node.arguments;
|
|
611
|
+
const callee = node.expression;
|
|
612
|
+
const loads =
|
|
613
|
+
callee.kind === SyntaxKind.ImportKeyword ||
|
|
614
|
+
(isIdentifier(callee) && callee.text === "require");
|
|
615
|
+
if (loads && first !== undefined) {
|
|
616
|
+
const prefix = leadingLiteral(first);
|
|
617
|
+
if (prefix !== undefined) {
|
|
618
|
+
below(prefix);
|
|
619
|
+
}
|
|
620
|
+
} else if (isMetaGlob(node) && first !== undefined) {
|
|
621
|
+
for (const pattern of literalsOf(first) ?? []) {
|
|
622
|
+
globbed(pattern);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
node.forEachChild(visit);
|
|
627
|
+
};
|
|
628
|
+
file.forEachChild(visit);
|
|
629
|
+
return found;
|
|
630
|
+
}
|
|
631
|
+
|
|
520
632
|
/**
|
|
521
633
|
* Every file of one project the rules enter, in no particular order and possibly more
|
|
522
634
|
* than once: a file two rules enter appears once per rule.
|
|
@@ -554,10 +666,20 @@ export function entryPoints<Brand>(
|
|
|
554
666
|
}
|
|
555
667
|
}
|
|
556
668
|
found.push(...workersOf(project, file, files));
|
|
669
|
+
found.push(...computedImportsOf(file, files));
|
|
557
670
|
}
|
|
558
671
|
for (const { file, source } of htmlEntries(host, targetRoot, files, parse)) {
|
|
559
672
|
found.push({ file, rule: "html-entry", source, exports: true });
|
|
560
673
|
}
|
|
674
|
+
for (const { file, source, names } of documentEntries(host, targetRoot, files)) {
|
|
675
|
+
found.push({
|
|
676
|
+
file,
|
|
677
|
+
rule: "document-import",
|
|
678
|
+
source,
|
|
679
|
+
exports: true,
|
|
680
|
+
...(names === undefined ? {} : { names }),
|
|
681
|
+
});
|
|
682
|
+
}
|
|
561
683
|
for (const { file, source } of workflowEntries(host, targetRoot, files)) {
|
|
562
684
|
found.push({ file, rule: "workflow-step", source, exports: true });
|
|
563
685
|
}
|
package/src/finding.ts
CHANGED
|
@@ -34,6 +34,12 @@ interface FindingSubject {
|
|
|
34
34
|
readonly name: string;
|
|
35
35
|
/** The number of source lines the subject spans. */
|
|
36
36
|
readonly sizeLines: number;
|
|
37
|
+
/**
|
|
38
|
+
* Where the subject is a part whose reference is its own, a parameter's, the reference
|
|
39
|
+
* of the declaration that holds it, which a record binding that declaration matches.
|
|
40
|
+
* No report carries it.
|
|
41
|
+
*/
|
|
42
|
+
readonly holder?: string;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/** A declaration a finding names beside the subject, with where it is written. */
|
|
@@ -87,6 +93,10 @@ export interface Finding {
|
|
|
87
93
|
readonly details?: FindingDetails;
|
|
88
94
|
/** The reachability class of a subject that is no declaration, where it is not `certain`. */
|
|
89
95
|
readonly reachabilityClass?: Confidence;
|
|
96
|
+
/** Whether references to a subject that is no declaration exist, all from dead code. */
|
|
97
|
+
readonly unreachableOnly?: true;
|
|
98
|
+
/** The dead declarations and files whose component a subject that is no declaration falls with. */
|
|
99
|
+
readonly fallsWith?: readonly string[];
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
/** The dead component a finding names, as the finding schema spells its members. */
|