@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +245 -309
- package/dist/src/augmentations.js +124 -18
- package/dist/src/automatic-types.js +170 -0
- package/dist/src/build-scripts.js +425 -0
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +9 -9
- package/dist/src/config.js +6 -81
- package/dist/src/configuration-files.js +78 -188
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/consumers.js +3 -30
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/contract/config.generated.js +558 -0
- package/dist/src/contract/contract.generated.js +1244 -0
- package/dist/src/contract/report.generated.js +862 -0
- package/dist/src/convention-rows.js +146 -1
- package/dist/src/conventions.js +136 -70
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +280 -62
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +9 -18
- package/dist/src/entry-points.js +106 -21
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +1 -63
- package/dist/src/exempt.js +21 -40
- package/dist/src/failure.js +36 -0
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +225 -63
- package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
- package/dist/src/findings/emitters.js +6 -6
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +53 -19
- package/dist/src/findings/non-code-artifacts.js +71 -39
- package/dist/src/findings/reads-and-writes.js +16 -13
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +57 -28
- package/dist/src/findings/visibility-narrowing.js +40 -18
- package/dist/src/findings-pass.js +8 -15
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +72 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/generated-file.js +18 -4
- package/dist/src/git-ignore.js +99 -0
- package/dist/src/graph.js +9 -4
- package/dist/src/html-entries.js +6 -33
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +341 -69
- package/dist/src/inventory.js +54 -29
- package/dist/src/javascript-file.js +33 -0
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +3 -366
- package/dist/src/load/classify.js +270 -0
- package/dist/src/load/diagnostics.js +80 -0
- package/dist/src/load/discover.js +277 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +27 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +313 -0
- package/dist/src/load/specifiers.js +115 -0
- package/dist/src/load/synthesize.js +140 -0
- package/dist/src/load/test-globals.js +207 -0
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
- package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
- package/dist/src/{workspace.js → load/workspace.js} +62 -124
- package/dist/src/manifest.js +49 -54
- package/dist/src/matrix.js +94 -13
- package/dist/src/mdx-entries.js +174 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/overrides.js +41 -10
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -78
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +1 -0
- package/dist/src/ref.js +11 -0
- package/dist/src/references.js +52 -31
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +59 -41
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +16 -10
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -727
- package/dist/src/roots.js +374 -116
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -342
- package/dist/src/scope.js +38 -115
- package/dist/src/serialization-contract.js +649 -29
- package/dist/src/session.js +41 -29
- package/dist/src/source-files.js +40 -15
- package/dist/src/suppress-file.js +34 -29
- package/dist/src/suppress-inline.js +21 -4
- package/dist/src/suppress.js +40 -56
- package/dist/src/sweep.js +32 -17
- package/dist/src/symbol-keys.js +105 -6
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-support.js +119 -18
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/named-files.js +54 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/text/yaml.js +393 -0
- package/dist/src/type-errors.js +27 -221
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/unseen-loads.js +100 -0
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +181 -125
- package/dist/src/verbs/describe.js +9 -16
- package/dist/src/verbs/explain.js +14 -9
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +6 -4
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +14 -3
- package/dist/src/workflow-steps.js +128 -33
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/analysis.ts +333 -457
- package/src/augmentations.ts +152 -23
- package/src/automatic-types.ts +228 -0
- package/src/build-scripts.ts +543 -0
- package/src/component-layer.ts +4 -2
- package/src/components.ts +9 -10
- package/src/config.ts +31 -299
- package/src/configuration-files.ts +100 -191
- package/src/configured-declarations.ts +18 -23
- package/src/conformance-record.ts +2 -2
- package/src/consumers.ts +6 -57
- package/src/contextual-uses.ts +64 -12
- package/src/contract/config.generated.ts +818 -0
- package/src/contract/contract.generated.ts +1308 -0
- package/src/contract/report.generated.ts +1237 -0
- package/src/convention-rows.ts +186 -1
- package/src/conventions.ts +166 -90
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +356 -78
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +11 -20
- package/src/entry-points.ts +124 -28
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +1 -75
- package/src/exempt.ts +36 -50
- package/src/failure.ts +63 -0
- package/src/file-facts.ts +9 -24
- package/src/finding.ts +24 -9
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +297 -77
- package/src/findings/dependencies-and-module-machinery.ts +90 -31
- package/src/findings/emitter.ts +8 -9
- package/src/findings/emitters.ts +8 -8
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +64 -20
- package/src/findings/non-code-artifacts.ts +88 -50
- package/src/findings/reads-and-writes.ts +20 -16
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +65 -28
- package/src/findings/visibility-narrowing.ts +51 -19
- package/src/findings-pass.ts +10 -19
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +92 -0
- package/src/fs/tree.ts +110 -0
- package/src/generated-file.ts +23 -4
- package/src/git-ignore.ts +122 -0
- package/src/graph.ts +11 -3
- package/src/host.ts +17 -14
- package/src/html-entries.ts +6 -33
- package/src/import-bindings.ts +128 -0
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +152 -30
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +449 -85
- package/src/inventory.ts +73 -27
- package/src/javascript-file.ts +37 -0
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +5 -413
- package/src/load/classify.ts +397 -0
- package/src/load/diagnostics.ts +144 -0
- package/src/load/discover.ts +455 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +188 -0
- package/src/load/load.ts +73 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +504 -0
- package/src/load/specifiers.ts +168 -0
- package/src/load/synthesize.ts +211 -0
- package/src/load/test-globals.ts +251 -0
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
- package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
- package/src/{workspace.ts → load/workspace.ts} +90 -134
- package/src/manifest.ts +81 -75
- package/src/matrix.ts +154 -18
- package/src/mdx-entries.ts +218 -0
- package/src/module-declarations.ts +11 -13
- package/src/outside-tests.ts +1 -1
- package/src/overrides.ts +51 -12
- package/src/partial.ts +3 -2
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -91
- package/src/product.ts +18 -0
- package/src/query.ts +11 -0
- package/src/ref.ts +13 -0
- package/src/references.ts +62 -32
- package/src/report-schema.ts +68 -0
- package/src/report.ts +105 -215
- package/src/reporters/annotations.ts +22 -11
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -4
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +29 -16
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +283 -1111
- package/src/roots.ts +490 -137
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -387
- package/src/scope.ts +46 -138
- package/src/serialization-contract.ts +818 -26
- package/src/session.ts +77 -54
- package/src/source-files.ts +67 -17
- package/src/suppress-file.ts +36 -34
- package/src/suppress-inline.ts +20 -3
- package/src/suppress.ts +52 -63
- package/src/sweep.ts +53 -18
- package/src/symbol-keys.ts +131 -7
- package/src/template-field.ts +15 -25
- package/src/test-support.ts +155 -18
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/named-files.inputs.json +1522 -0
- package/src/text/named-files.ts +68 -0
- package/src/text/shell.ts +161 -0
- package/src/text/yaml.ts +423 -0
- package/src/type-errors.ts +35 -273
- package/src/type-parameter-reads.ts +227 -0
- package/src/unseen-loads.ts +134 -0
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +245 -161
- package/src/verbs/describe.ts +14 -15
- package/src/verbs/explain.ts +15 -13
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +6 -4
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +2 -8
- package/src/verbs/version.ts +15 -3
- package/src/workflow-steps.ts +149 -34
- package/dist/src/discover.js +0 -347
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/setup-failure.js +0 -23
- package/dist/src/test-joins.js +0 -101
- package/dist/src/version.js +0 -7
- package/src/discover.ts +0 -510
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/setup-failure.ts +0 -39
- package/src/test-joins.ts +0 -120
- package/src/version.ts +0 -7
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Host } from "../host.ts";
|
|
2
|
+
import { isRecord } from "../json-document.ts";
|
|
3
|
+
import { joinPath } from "../paths.ts";
|
|
4
|
+
import { message } from "../product.ts";
|
|
5
|
+
|
|
6
|
+
/** The file a package's manifest is read from. */
|
|
7
|
+
export const MANIFEST = "package.json";
|
|
8
|
+
|
|
9
|
+
/** The directory a package manager installs packages into, whose files are no project's own. */
|
|
10
|
+
export const INSTALLED_PACKAGES = "node_modules";
|
|
11
|
+
|
|
12
|
+
/** The manifest members a package declares the packages it depends on under. */
|
|
13
|
+
export const DEPENDENCY_SECTIONS = [
|
|
14
|
+
"dependencies",
|
|
15
|
+
"devDependencies",
|
|
16
|
+
"peerDependencies",
|
|
17
|
+
"optionalDependencies",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
/** Whether one path passes through an installed-packages directory. */
|
|
21
|
+
export function inInstalledPackages(path: string): boolean {
|
|
22
|
+
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
26
|
+
export function readDocument(
|
|
27
|
+
host: Host,
|
|
28
|
+
path: string,
|
|
29
|
+
): { text: string; value: unknown } | undefined {
|
|
30
|
+
let text: string;
|
|
31
|
+
try {
|
|
32
|
+
text = host.readFile(path);
|
|
33
|
+
} catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return { text, value: JSON.parse(text) as unknown };
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
44
|
+
export function readJSON(host: Host, path: string): unknown {
|
|
45
|
+
return readDocument(host, path)?.value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The manifest in one directory, decoded, or undefined where none reads as an object. A
|
|
50
|
+
* manifest this program cannot read declares nothing, the same answer as an absent one:
|
|
51
|
+
* the package manager owns the refusal of a manifest it rejects.
|
|
52
|
+
*/
|
|
53
|
+
export function manifestAt(host: Host, dir: string): Record<string, unknown> | undefined {
|
|
54
|
+
const value = readJSON(host, joinPath(dir, MANIFEST));
|
|
55
|
+
return isRecord(value) ? value : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* What one directory's manifest reads as: none, an object, a file that does not parse as one,
|
|
60
|
+
* or a file that cannot be read, with the read's error. Either of the last two still bounds
|
|
61
|
+
* the package it would declare.
|
|
62
|
+
*/
|
|
63
|
+
export type ManifestRead =
|
|
64
|
+
| { readonly kind: "absent" }
|
|
65
|
+
| { readonly kind: "read"; readonly manifest: Record<string, unknown> }
|
|
66
|
+
| { readonly kind: "malformed" }
|
|
67
|
+
| { readonly kind: "unread"; readonly reason: string };
|
|
68
|
+
|
|
69
|
+
/** The manifest in one directory, as {@link ManifestRead} tells its states apart. */
|
|
70
|
+
export function readManifestAt(host: Host, dir: string): ManifestRead {
|
|
71
|
+
if (!holdsManifest(host, dir)) {
|
|
72
|
+
return { kind: "absent" };
|
|
73
|
+
}
|
|
74
|
+
let text: string;
|
|
75
|
+
try {
|
|
76
|
+
text = host.readFile(joinPath(dir, MANIFEST));
|
|
77
|
+
} catch (error: unknown) {
|
|
78
|
+
return { kind: "unread", reason: message(error) };
|
|
79
|
+
}
|
|
80
|
+
let value: unknown;
|
|
81
|
+
try {
|
|
82
|
+
value = JSON.parse(text);
|
|
83
|
+
} catch {
|
|
84
|
+
return { kind: "malformed" };
|
|
85
|
+
}
|
|
86
|
+
return isRecord(value) ? { kind: "read", manifest: value } : { kind: "malformed" };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Whether one directory holds a manifest file. */
|
|
90
|
+
export function holdsManifest(host: Host, dir: string): boolean {
|
|
91
|
+
return host.kindOf(joinPath(dir, MANIFEST)) === "file";
|
|
92
|
+
}
|
package/src/fs/tree.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { DirectoryEntry, Host } from "../host.ts";
|
|
2
|
+
import { ancestors, joinPath, resolvePath } from "../paths.ts";
|
|
3
|
+
import { LANGUAGE_ROWS } from "../contract/contract.generated.ts";
|
|
4
|
+
import { LANGUAGE } from "../product.ts";
|
|
5
|
+
import { bytewise } from "../text/compare.ts";
|
|
6
|
+
import { globExpression } from "../text/glob.ts";
|
|
7
|
+
|
|
8
|
+
/** The directories no walk of the tree enters, by the patterns the language's row names. */
|
|
9
|
+
const SKIPPED_DIRECTORIES = LANGUAGE_ROWS[LANGUAGE].skippedDirectories.map(globExpression);
|
|
10
|
+
|
|
11
|
+
function skipped(name: string): boolean {
|
|
12
|
+
return SKIPPED_DIRECTORIES.some((pattern) => pattern.test(name));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A git repository's working tree root, and the `info/exclude` file git reads for it. */
|
|
16
|
+
interface Repository {
|
|
17
|
+
readonly root: string;
|
|
18
|
+
/** Undefined where a `.git` file names no repository directory. */
|
|
19
|
+
readonly exclude: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One file's text without its line ending, or undefined where it cannot be read. */
|
|
23
|
+
function lineOf(host: Host, path: string): string | undefined {
|
|
24
|
+
try {
|
|
25
|
+
return host.readFile(path).replace(/\r?\n$/u, "");
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The repository whose working tree holds `dir`: the nearest directory at or above it with a
|
|
33
|
+
* `.git`. A `.git` file, which a linked worktree or a submodule holds, names the repository's
|
|
34
|
+
* directory on its `gitdir:` line; that directory's `commondir` file, where it has one, names
|
|
35
|
+
* the directory shared with the main worktree, which holds `info/exclude`:
|
|
36
|
+
* https://git-scm.com/docs/gitrepository-layout
|
|
37
|
+
*/
|
|
38
|
+
export function repositoryOf(host: Host, dir: string): Repository | undefined {
|
|
39
|
+
for (const root of ancestors(dir)) {
|
|
40
|
+
const dotGit = joinPath(root, ".git");
|
|
41
|
+
const kind = host.kindOf(dotGit);
|
|
42
|
+
if (kind === "directory") {
|
|
43
|
+
return { root, exclude: joinPath(dotGit, "info/exclude") };
|
|
44
|
+
}
|
|
45
|
+
if (kind === "file") {
|
|
46
|
+
const named = /^gitdir: (.+)$/u.exec(lineOf(host, dotGit) ?? "")?.[1];
|
|
47
|
+
const gitDir = named === undefined ? undefined : resolvePath(root, named);
|
|
48
|
+
const common = gitDir === undefined ? undefined : lineOf(host, joinPath(gitDir, "commondir"));
|
|
49
|
+
const shared =
|
|
50
|
+
gitDir === undefined || common === undefined ? gitDir : resolvePath(gitDir, common);
|
|
51
|
+
return { root, exclude: shared === undefined ? undefined : joinPath(shared, "info/exclude") };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The entries of one directory, none where it cannot be read. */
|
|
58
|
+
export function entriesOf(host: Host, dir: string): readonly DirectoryEntry[] {
|
|
59
|
+
try {
|
|
60
|
+
return host.readDirectory(dir);
|
|
61
|
+
} catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The names of the files directly in `dir` whose name `accepts`, in bytewise order. */
|
|
67
|
+
export function filesIn(
|
|
68
|
+
host: Host,
|
|
69
|
+
dir: string,
|
|
70
|
+
accepts: (name: string) => boolean,
|
|
71
|
+
): readonly string[] {
|
|
72
|
+
return entriesOf(host, dir)
|
|
73
|
+
.filter((entry) => !entry.directory && accepts(entry.name))
|
|
74
|
+
.map((entry) => entry.name)
|
|
75
|
+
.sort(bytewise);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The names of the directories directly in `dir` a walk enters, in bytewise order. */
|
|
79
|
+
export function directoriesIn(host: Host, dir: string): readonly string[] {
|
|
80
|
+
return entriesOf(host, dir)
|
|
81
|
+
.filter((entry) => entry.directory && !skipped(entry.name))
|
|
82
|
+
.map((entry) => entry.name)
|
|
83
|
+
.sort(bytewise);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Every file below `root` whose name `accepts`, by its path relative to `root` in bytewise
|
|
88
|
+
* order, read in no skipped directory.
|
|
89
|
+
*/
|
|
90
|
+
export function filesBelow(
|
|
91
|
+
host: Host,
|
|
92
|
+
root: string,
|
|
93
|
+
accepts: (name: string) => boolean,
|
|
94
|
+
): readonly string[] {
|
|
95
|
+
const found: string[] = [];
|
|
96
|
+
const pending = [""];
|
|
97
|
+
for (let dir = pending.pop(); dir !== undefined; dir = pending.pop()) {
|
|
98
|
+
for (const entry of entriesOf(host, joinPath(root, dir))) {
|
|
99
|
+
const path = dir === "" ? entry.name : `${dir}/${entry.name}`;
|
|
100
|
+
if (!entry.directory) {
|
|
101
|
+
if (accepts(entry.name)) {
|
|
102
|
+
found.push(path);
|
|
103
|
+
}
|
|
104
|
+
} else if (!skipped(entry.name)) {
|
|
105
|
+
pending.push(path);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return found.sort(bytewise);
|
|
110
|
+
}
|
package/src/generated-file.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The generated-file class on TypeScript: every declaration in a file that marks itself
|
|
3
|
-
* generated,
|
|
4
|
-
* retained, because a generator owns the file and a deletion there is undone at the next
|
|
3
|
+
* generated, below a directory an applied convention row names as generated, or minified,
|
|
4
|
+
* is retained, because a generator owns the file and a deletion there is undone at the next
|
|
5
5
|
* generation.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
9
|
+
import { minified } from "./javascript-file.ts";
|
|
9
10
|
import type { SourceFiles } from "./source-files.ts";
|
|
10
11
|
|
|
11
12
|
/** The generated-code header, matched against each line of a leading comment. */
|
|
@@ -14,6 +15,15 @@ const HEADER_LINE = /^\/\/ Code generated .* DO NOT EDIT\.$/u;
|
|
|
14
15
|
/** The documentation tag `@generated` as a word of a comment. */
|
|
15
16
|
const GENERATED_TAG = /(?<![\w@$])@generated(?![\w$-])/u;
|
|
16
17
|
|
|
18
|
+
/** A first comment naming the tool that wrote the file. */
|
|
19
|
+
const GENERATED_BY = /^\/\/ Generated by \S/u;
|
|
20
|
+
|
|
21
|
+
/** The word a generator's notice holds, beside one of {@link KEEP_OUT}. */
|
|
22
|
+
const GENERATED_WORD = /\bgenerated\b/iu;
|
|
23
|
+
|
|
24
|
+
/** The phrases a generator's notice asks a reader not to edit the file by. */
|
|
25
|
+
const KEEP_OUT = /\b(?:do not edit|will be overwritten|should not make any changes)\b/iu;
|
|
26
|
+
|
|
17
27
|
/** The comments before a text's first token, a leading hashbang line skipped. */
|
|
18
28
|
function leadingComments(text: string): readonly string[] {
|
|
19
29
|
const comments: string[] = [];
|
|
@@ -53,13 +63,20 @@ export function generatedByComment(text: string): string | undefined {
|
|
|
53
63
|
if (comments.some((one) => GENERATED_TAG.test(one))) {
|
|
54
64
|
return "generated by the @generated tag its leading comments hold";
|
|
55
65
|
}
|
|
66
|
+
const notice = comments.join("\n");
|
|
67
|
+
if (
|
|
68
|
+
GENERATED_BY.test(comments[0] ?? "") ||
|
|
69
|
+
(GENERATED_WORD.test(notice) && KEEP_OUT.test(notice))
|
|
70
|
+
) {
|
|
71
|
+
return "generated by the notice its leading comments hold";
|
|
72
|
+
}
|
|
56
73
|
return undefined;
|
|
57
74
|
}
|
|
58
75
|
|
|
59
76
|
/**
|
|
60
77
|
* Every own file of one project that is generated, by its path below the target root,
|
|
61
78
|
* with the detail a retention in it names: its leading comments' mark, else the row
|
|
62
|
-
* naming its directory as generated.
|
|
79
|
+
* naming its directory as generated, else its minified text.
|
|
63
80
|
*/
|
|
64
81
|
export function generatedFilesOf(
|
|
65
82
|
files: SourceFiles,
|
|
@@ -71,7 +88,9 @@ export function generatedFilesOf(
|
|
|
71
88
|
const detail =
|
|
72
89
|
generatedByComment(file.text) ??
|
|
73
90
|
(row === undefined
|
|
74
|
-
?
|
|
91
|
+
? minified(file.fileName, file.text)
|
|
92
|
+
? "generated as a minified JavaScript file"
|
|
93
|
+
: undefined
|
|
75
94
|
: `generated below a directory the convention row ${row} names`);
|
|
76
95
|
if (detail !== undefined) {
|
|
77
96
|
found.set(path, detail);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files git ignores below a tree's root, read as git reads its ignore files:
|
|
3
|
+
* https://git-scm.com/docs/gitignore#_pattern_format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Host } from "./host.ts";
|
|
7
|
+
import { ancestors, dirnamePath, joinPath, relativePath } from "./paths.ts";
|
|
8
|
+
import { message } from "./product.ts";
|
|
9
|
+
import { gitignoreExpression } from "./text/glob.ts";
|
|
10
|
+
import { repositoryOf } from "./fs/tree.ts";
|
|
11
|
+
|
|
12
|
+
/** One pattern line of an ignore file. */
|
|
13
|
+
interface Rule {
|
|
14
|
+
readonly negated: boolean;
|
|
15
|
+
/** Whether the pattern ends with `/`, so it matches a directory alone. */
|
|
16
|
+
readonly directoryOnly: boolean;
|
|
17
|
+
/** The expression over a path below the file's own directory. */
|
|
18
|
+
readonly expression: RegExp;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The rules one ignore file's text writes, in order. */
|
|
22
|
+
function rulesOf(text: string): readonly Rule[] {
|
|
23
|
+
const rules: Rule[] = [];
|
|
24
|
+
for (const raw of text.split(/\r?\n/u)) {
|
|
25
|
+
let line = raw.replace(/(?<!\\)\s+$/u, "");
|
|
26
|
+
if (line === "" || line.startsWith("#")) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const negated = line.startsWith("!");
|
|
30
|
+
line = negated ? line.slice(1) : line;
|
|
31
|
+
const directoryOnly = line.endsWith("/");
|
|
32
|
+
line = line.replace(/\/+$/u, "");
|
|
33
|
+
if (line === "") {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// A separator at the start or in the middle anchors the pattern to the file's
|
|
37
|
+
// directory; with none, the pattern matches at any depth below it.
|
|
38
|
+
const anchored = line.includes("/");
|
|
39
|
+
const pattern = line.replace(/^\//u, "");
|
|
40
|
+
rules.push({
|
|
41
|
+
negated,
|
|
42
|
+
directoryOnly,
|
|
43
|
+
expression: gitignoreExpression(anchored ? pattern : `**/${pattern}`),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return rules;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Told each ignore file that exists and cannot be read, with why. */
|
|
50
|
+
type UnreadIgnoreFile = (path: string, reason: string) => void;
|
|
51
|
+
|
|
52
|
+
function rulesIn(host: Host, path: string, unread: UnreadIgnoreFile): readonly Rule[] {
|
|
53
|
+
try {
|
|
54
|
+
return rulesOf(host.readFile(path));
|
|
55
|
+
} catch (error: unknown) {
|
|
56
|
+
if (host.kindOf(path) !== "absent") {
|
|
57
|
+
unread(path, message(error));
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether git ignores one absolute path below `root`: by the repository's `info/exclude`, then
|
|
65
|
+
* the `.gitignore` files from its root down to the path's directory, a later pattern overriding
|
|
66
|
+
* an earlier one, though a file below an ignored directory stays ignored, as git cannot re-include
|
|
67
|
+
* it. A path outside `root` is not ignored. With no repository, the files at and below `root` are
|
|
68
|
+
* read; with one, the directories above `root` give patterns but are not tested. An unreadable
|
|
69
|
+
* ignore file ignores nothing, and `unread` is told it.
|
|
70
|
+
*/
|
|
71
|
+
export function gitIgnored(
|
|
72
|
+
host: Host,
|
|
73
|
+
root: string,
|
|
74
|
+
unread: UnreadIgnoreFile = () => undefined,
|
|
75
|
+
): (path: string) => boolean {
|
|
76
|
+
const found = repositoryOf(host, root);
|
|
77
|
+
const repository = found?.root;
|
|
78
|
+
const above =
|
|
79
|
+
repository === undefined ? [] : [...ancestors(dirnamePath(root), repository)].reverse();
|
|
80
|
+
const excluded = found?.exclude === undefined ? [] : rulesIn(host, found.exclude, unread);
|
|
81
|
+
const read = new Map<string, readonly Rule[]>();
|
|
82
|
+
const rulesAt = (dir: string): readonly Rule[] => {
|
|
83
|
+
let rules = read.get(dir);
|
|
84
|
+
if (rules === undefined) {
|
|
85
|
+
rules = rulesIn(host, joinPath(dir, ".gitignore"), unread);
|
|
86
|
+
read.set(dir, rules);
|
|
87
|
+
}
|
|
88
|
+
return rules;
|
|
89
|
+
};
|
|
90
|
+
const decided = (path: string, directory: boolean): boolean => {
|
|
91
|
+
const sources: [string, readonly Rule[]][] = [
|
|
92
|
+
...(repository === undefined ? [] : [[repository, excluded] as [string, readonly Rule[]]]),
|
|
93
|
+
...[...above, ...[...ancestors(dirnamePath(path), root)].reverse()].map(
|
|
94
|
+
(dir): [string, readonly Rule[]] => [dir, rulesAt(dir)],
|
|
95
|
+
),
|
|
96
|
+
];
|
|
97
|
+
let ignored = false;
|
|
98
|
+
for (const [dir, rules] of sources) {
|
|
99
|
+
const below = relativePath(dir, path) ?? "";
|
|
100
|
+
for (const rule of rules) {
|
|
101
|
+
if ((directory || !rule.directoryOnly) && rule.expression.test(below)) {
|
|
102
|
+
ignored = !rule.negated;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return ignored;
|
|
107
|
+
};
|
|
108
|
+
return (path) => {
|
|
109
|
+
const below = relativePath(root, path);
|
|
110
|
+
if (below === undefined) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const parts = below.split("/");
|
|
114
|
+
for (let count = 1; count <= parts.length; count += 1) {
|
|
115
|
+
const at = joinPath(root, parts.slice(0, count).join("/"));
|
|
116
|
+
if (decided(at, count < parts.length)) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
};
|
|
122
|
+
}
|
package/src/graph.ts
CHANGED
|
@@ -41,9 +41,9 @@ const SWEPT: Readonly<Record<SymbolKind, boolean>> = {
|
|
|
41
41
|
* Whether a root of each kind names a caller, which holds it live under both relations: a
|
|
42
42
|
* tool reads what it loads and what its strings name, a runtime runs a test, setup or
|
|
43
43
|
* worker file, a page's module script, a command, a script or a workflow step, the manifest
|
|
44
|
-
* names what enters the package, an alias global stands for its export,
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* names what enters the package, an alias global stands for its export, an outside
|
|
45
|
+
* declaration names a global interface merging into it, and an entry file, a configured root
|
|
46
|
+
* and a pattern assert a caller. A published API supposes a consumer: reachability alone.
|
|
47
47
|
*/
|
|
48
48
|
const NAMES_A_CALLER: Readonly<Record<RootKind, boolean>> = {
|
|
49
49
|
"entry-file": true,
|
|
@@ -56,13 +56,17 @@ const NAMES_A_CALLER: Readonly<Record<RootKind, boolean>> = {
|
|
|
56
56
|
pattern: true,
|
|
57
57
|
"type-error": true,
|
|
58
58
|
"type-query-alias": true,
|
|
59
|
+
"global-augmentation": true,
|
|
59
60
|
"configuration-file": true,
|
|
60
61
|
"configuration-string": true,
|
|
62
|
+
"bundler-entry": true,
|
|
61
63
|
"test-runner": true,
|
|
62
64
|
"browser-tests": true,
|
|
63
65
|
worker: true,
|
|
64
66
|
"html-entry": true,
|
|
65
67
|
"workflow-step": true,
|
|
68
|
+
"computed-import": true,
|
|
69
|
+
"document-import": true,
|
|
66
70
|
};
|
|
67
71
|
|
|
68
72
|
/** Whether the sweep judges a declaration of one kind. */
|
|
@@ -112,6 +116,8 @@ export interface Graph {
|
|
|
112
116
|
readonly test: readonly boolean[];
|
|
113
117
|
/** Per declaration, whether a test-support file holds it. */
|
|
114
118
|
readonly support: readonly boolean[];
|
|
119
|
+
/** Per declaration, whether it is ambient, describing code the target does not implement. */
|
|
120
|
+
readonly ambient: readonly boolean[];
|
|
115
121
|
/** Per declaration, whether the sweep judges its liveness. */
|
|
116
122
|
readonly subject: readonly boolean[];
|
|
117
123
|
/**
|
|
@@ -145,6 +151,7 @@ export function graphOf(
|
|
|
145
151
|
roots: readonly Root[],
|
|
146
152
|
testFiles: readonly string[],
|
|
147
153
|
supportFiles: readonly string[] = [],
|
|
154
|
+
ambient: ReadonlySet<string> = new Set(),
|
|
148
155
|
): Graph {
|
|
149
156
|
const supports = new Set(supportFiles);
|
|
150
157
|
const index = new Map(symbols.map((symbol, at) => [symbol.id, at]));
|
|
@@ -199,6 +206,7 @@ export function graphOf(
|
|
|
199
206
|
parent,
|
|
200
207
|
test: symbols.map((symbol) => tests.has(symbol.position.path)),
|
|
201
208
|
support: symbols.map((symbol) => supports.has(symbol.position.path)),
|
|
209
|
+
ambient: symbols.map((symbol) => ambient.has(symbol.id)),
|
|
202
210
|
subject: symbols.map((symbol) => swept(symbol.kind)),
|
|
203
211
|
exportsOf,
|
|
204
212
|
rooted,
|
package/src/host.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** What one path names. */
|
|
2
|
-
export type PathKind = "file" | "directory" | "absent";
|
|
1
|
+
/** What one path names: `"other"` is anything else there, a named pipe, a device or a socket. */
|
|
2
|
+
export type PathKind = "file" | "directory" | "other" | "absent";
|
|
3
3
|
|
|
4
4
|
/** One entry of a directory. */
|
|
5
5
|
export interface DirectoryEntry {
|
|
@@ -8,24 +8,27 @@ export interface DirectoryEntry {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* The platform one run reads: the filesystem, the directory
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* the filesystem directly carries the whole platform's type surface into every
|
|
17
|
-
* consumer's compile, and because the run's own reads are then the one thing a
|
|
18
|
-
* caller can watch. The command line binds it to the platform it runs on; nothing
|
|
19
|
-
* under this module does.
|
|
11
|
+
* The platform one run reads: the filesystem, the working directory and the analyzer's
|
|
12
|
+
* installed version. A run may keep what it reads for the host's life, so a caller whose
|
|
13
|
+
* tree changes passes a new host. It is a parameter, not an import, so a published module
|
|
14
|
+
* carries no platform type surface into a consumer's compile and a caller can watch the
|
|
15
|
+
* run's reads. The command line binds it to the platform; nothing under this module does.
|
|
20
16
|
*/
|
|
21
17
|
export interface Host {
|
|
22
18
|
/** The directory a relative path resolves against. */
|
|
23
19
|
workingDirectory(): string;
|
|
24
|
-
/**
|
|
25
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The text of one file, or a throw naming why it cannot be read. Given `limit`, the
|
|
22
|
+
* text of no more than the file's first `limit` bytes, so a caller bounding a document
|
|
23
|
+
* asks for one byte past its bound and never holds a larger file whole.
|
|
24
|
+
*/
|
|
25
|
+
readFile(path: string, limit?: number): string;
|
|
26
26
|
/** The entries of one directory, or a throw naming why it cannot be read. */
|
|
27
27
|
readDirectory(path: string): readonly DirectoryEntry[];
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* What one path names once every symbolic link along it is followed, `"absent"` where
|
|
30
|
+
* nothing is there, or a throw naming why it cannot be known, as for a link loop.
|
|
31
|
+
*/
|
|
29
32
|
kindOf(path: string): PathKind;
|
|
30
33
|
/**
|
|
31
34
|
* The path one path names once every symbolic link along it is followed, or a throw
|
package/src/html-entries.ts
CHANGED
|
@@ -12,8 +12,10 @@ import {
|
|
|
12
12
|
} from "@typescript/native/ast";
|
|
13
13
|
import { resolveRelativeSpecifier, type ParseFile } from "./configuration-files.ts";
|
|
14
14
|
import type { Host } from "./host.ts";
|
|
15
|
-
import { dirnamePath, joinPath,
|
|
15
|
+
import { ancestors, dirnamePath, joinPath, resolvePath } from "./paths.ts";
|
|
16
16
|
import type { SourceFiles } from "./source-files.ts";
|
|
17
|
+
import { filesBelow } from "./fs/tree.ts";
|
|
18
|
+
import { holdsManifest } from "./fs/manifest.ts";
|
|
17
19
|
|
|
18
20
|
/** One file a script loads, and the value or specifier that named it. */
|
|
19
21
|
interface HTMLEntry {
|
|
@@ -21,9 +23,6 @@ interface HTMLEntry {
|
|
|
21
23
|
readonly source: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
/** The directory no HTML file is read below. */
|
|
25
|
-
const SKIPPED = "node_modules";
|
|
26
|
-
|
|
27
26
|
/** A `<script` start tag, its attribute text captured. */
|
|
28
27
|
const SCRIPT_TAG = /<script(?=[\s>/])([^>]*)>/giu;
|
|
29
28
|
|
|
@@ -42,28 +41,6 @@ const CLASSIC_TYPES: ReadonlySet<string> = new Set(["text/javascript", "applicat
|
|
|
42
41
|
/** The prefixes an inline script's specifier names a file of the target with. */
|
|
43
42
|
const FILE_PREFIXES = ["/", "./", "../"] as const;
|
|
44
43
|
|
|
45
|
-
/** Every HTML file below `dir`, outside every `node_modules` directory, by absolute path. */
|
|
46
|
-
function htmlFiles(host: Host, dir: string): readonly string[] {
|
|
47
|
-
let entries;
|
|
48
|
-
try {
|
|
49
|
-
entries = host.readDirectory(dir);
|
|
50
|
-
} catch {
|
|
51
|
-
return [];
|
|
52
|
-
}
|
|
53
|
-
const found: string[] = [];
|
|
54
|
-
for (const entry of entries) {
|
|
55
|
-
const path = joinPath(dir, entry.name);
|
|
56
|
-
if (entry.directory) {
|
|
57
|
-
if (entry.name !== SKIPPED) {
|
|
58
|
-
found.push(...htmlFiles(host, path));
|
|
59
|
-
}
|
|
60
|
-
} else if (entry.name.endsWith(".html")) {
|
|
61
|
-
found.push(path);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return found.sort();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
44
|
/** Each attribute of one start tag's attribute text, by its lowercase name. */
|
|
68
45
|
function attributes(text: string): ReadonlyMap<string, string> {
|
|
69
46
|
const found = new Map<string, string>();
|
|
@@ -127,12 +104,7 @@ function scriptValues(parse: ParseFile | undefined, path: string, text: string):
|
|
|
127
104
|
|
|
128
105
|
/** The directory of the nearest `package.json` at or above `dir`, inside `root`, else `root`. */
|
|
129
106
|
function manifestDirectory(host: Host, root: string, dir: string): string {
|
|
130
|
-
|
|
131
|
-
if (host.kindOf(joinPath(at, "package.json")) === "file") {
|
|
132
|
-
return at;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return root;
|
|
107
|
+
return ancestors(dir, root).find((at) => holdsManifest(host, at)) ?? root;
|
|
136
108
|
}
|
|
137
109
|
|
|
138
110
|
/**
|
|
@@ -150,7 +122,8 @@ export function htmlEntries(
|
|
|
150
122
|
): readonly HTMLEntry[] {
|
|
151
123
|
const root = resolvePath(host.workingDirectory(), targetRoot);
|
|
152
124
|
const found: HTMLEntry[] = [];
|
|
153
|
-
for (const
|
|
125
|
+
for (const below of filesBelow(host, root, (name) => name.endsWith(".html"))) {
|
|
126
|
+
const path = joinPath(root, below);
|
|
154
127
|
let text: string;
|
|
155
128
|
try {
|
|
156
129
|
text = host.readFile(path);
|