@cplieger/deadset-ts 8.0.0-dev.15 → 8.0.0-dev.17
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/alias-chain.js +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +94 -54
- package/dist/src/augmentations.js +23 -24
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +25 -8
- package/dist/src/commands/read.js +6 -7
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +9 -10
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +5 -2
- package/dist/src/contract/contract.generated.js +119 -2
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +98 -71
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
- package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
- package/dist/src/entries/conventions.js +739 -0
- package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +670 -0
- package/dist/src/entries/test-runners.js +684 -0
- package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
- package/dist/src/extensions.js +37 -0
- package/dist/src/facts.js +38 -0
- package/dist/src/findings/completion.js +40 -4
- package/dist/src/findings/emitters.js +5 -8
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/findings/self-check.js +36 -20
- package/dist/src/findings-pass.js +5 -6
- package/dist/src/framework-lifecycle.js +42 -15
- package/dist/src/fs/manifest.js +15 -20
- package/dist/src/generated-file.js +172 -29
- package/dist/src/graph.js +14 -8
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/inventory/commonjs.js +62 -17
- package/dist/src/inventory/enumerate.js +2 -1
- package/dist/src/javascript-file.js +26 -11
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/load/classify.js +28 -9
- package/dist/src/load/diagnostics.js +8 -1
- package/dist/src/load/discover.js +15 -7
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/plan.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +10 -5
- package/dist/src/load/workspace-manifests.js +1 -1
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/matrix.js +12 -17
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +56 -1
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +134 -0
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +151 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +111 -1116
- package/dist/src/resolve.js +1 -1
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
- package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
- package/dist/src/{manifest.js → roots/manifest.js} +4 -4
- package/dist/src/roots/published-reach.js +412 -0
- package/dist/src/roots/root-set.js +489 -0
- package/dist/src/roots/unseen-loads.js +236 -0
- package/dist/src/serialization-contract.js +9 -4
- package/dist/src/source-files.js +5 -9
- package/dist/src/stores.js +4 -2
- package/dist/src/suppress.js +30 -32
- package/dist/src/sweep.js +25 -13
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +5 -12
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +15 -3
- package/dist/src/verbs/describe.js +5 -3
- package/dist/src/verbs/explain.js +50 -28
- package/dist/src/verbs/print-retained.js +2 -2
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +156 -70
- package/src/augmentations.ts +41 -27
- package/src/automatic-types.ts +1 -1
- package/src/bindings.ts +460 -0
- package/src/calls.ts +25 -7
- package/src/commands/read.ts +6 -8
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +12 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +5 -2
- package/src/contract/contract.generated.ts +138 -2
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +117 -71
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
- package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
- package/src/{conventions.ts → entries/conventions.ts} +349 -523
- package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +858 -0
- package/src/entries/test-runners.ts +868 -0
- package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
- package/src/exempt.ts +3 -2
- package/src/extensions.ts +45 -0
- package/src/facts.ts +46 -0
- package/src/file-facts.ts +1 -1
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +43 -4
- package/src/findings/emitter.ts +1 -1
- package/src/findings/emitters.ts +14 -10
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/findings/self-check.ts +41 -22
- package/src/findings/visibility-narrowing.ts +1 -1
- package/src/findings-pass.ts +5 -12
- package/src/framework-lifecycle.ts +43 -15
- package/src/fs/manifest.ts +25 -21
- package/src/generated-file.ts +240 -36
- package/src/graph.ts +27 -11
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +570 -527
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +1 -1
- package/src/inventory/commonjs.ts +102 -17
- package/src/inventory/enumerate.ts +2 -1
- package/src/javascript-file.ts +29 -12
- package/src/keyof-comparisons.ts +131 -116
- package/src/load/classify.ts +49 -13
- package/src/load/diagnostics.ts +11 -1
- package/src/load/discover.ts +27 -11
- package/src/load/expected-module.ts +8 -2
- package/src/load/plan.ts +11 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +12 -6
- package/src/load/test-globals.ts +1 -1
- package/src/load/workspace-manifests.ts +1 -1
- package/src/load/workspace-resolution.ts +9 -2
- package/src/load/workspace-run.ts +1 -1
- package/src/matrix.ts +13 -17
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +65 -1
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +189 -0
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +213 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +174 -1492
- package/src/report.ts +2 -2
- package/src/resolve.ts +1 -1
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
- package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
- package/src/{manifest.ts → roots/manifest.ts} +5 -5
- package/src/roots/published-reach.ts +481 -0
- package/src/{roots.ts → roots/root-set.ts} +203 -630
- package/src/roots/unseen-loads.ts +345 -0
- package/src/serialization-contract.ts +9 -4
- package/src/source-files.ts +5 -10
- package/src/stores.ts +13 -2
- package/src/suppress.ts +48 -33
- package/src/sweep.ts +37 -14
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +118 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +15 -21
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +19 -5
- package/src/verbs/describe.ts +5 -2
- package/src/verbs/explain.ts +75 -32
- package/src/verbs/print-retained.ts +2 -2
- package/dist/src/build-scripts.js +0 -418
- package/dist/src/conventions.js +0 -907
- package/dist/src/entry-points.js +0 -444
- package/dist/src/own-body.js +0 -28
- package/dist/src/roots.js +0 -865
- package/dist/src/unseen-loads.js +0 -189
- package/src/build-scripts.ts +0 -536
- package/src/entry-points.ts +0 -584
- package/src/own-body.ts +0 -42
- package/src/unseen-loads.ts +0 -282
package/src/fs/manifest.ts
CHANGED
|
@@ -22,24 +22,36 @@ export function inInstalledPackages(path: string): boolean {
|
|
|
22
22
|
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
/** What one JSON file reads as: its text and decoded value, text that does not parse, or a file that cannot be read, with the read's error. */
|
|
26
|
+
type JSONRead =
|
|
27
|
+
| { readonly kind: "read"; readonly text: string; readonly value: unknown }
|
|
28
|
+
| { readonly kind: "malformed" }
|
|
29
|
+
| { readonly kind: "unread"; readonly reason: string };
|
|
30
|
+
|
|
31
|
+
/** One JSON file, read and parsed, as {@link JSONRead} tells its states apart. */
|
|
32
|
+
export function readJSONFile(host: Host, path: string): JSONRead {
|
|
30
33
|
let text: string;
|
|
31
34
|
try {
|
|
32
35
|
text = host.readFile(path);
|
|
33
|
-
} catch {
|
|
34
|
-
return
|
|
36
|
+
} catch (error: unknown) {
|
|
37
|
+
return { kind: "unread", reason: message(error) };
|
|
35
38
|
}
|
|
36
39
|
try {
|
|
37
|
-
return { text, value: JSON.parse(text) as unknown };
|
|
40
|
+
return { kind: "read", text, value: JSON.parse(text) as unknown };
|
|
38
41
|
} catch {
|
|
39
|
-
return
|
|
42
|
+
return { kind: "malformed" };
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
47
|
+
export function readDocument(
|
|
48
|
+
host: Host,
|
|
49
|
+
path: string,
|
|
50
|
+
): { text: string; value: unknown } | undefined {
|
|
51
|
+
const read = readJSONFile(host, path);
|
|
52
|
+
return read.kind === "read" ? { text: read.text, value: read.value } : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
44
56
|
export function readJSON(host: Host, path: string): unknown {
|
|
45
57
|
return readDocument(host, path)?.value;
|
|
@@ -71,19 +83,11 @@ export function readManifestAt(host: Host, dir: string): ManifestRead {
|
|
|
71
83
|
if (!holdsManifest(host, dir)) {
|
|
72
84
|
return { kind: "absent" };
|
|
73
85
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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" };
|
|
86
|
+
const read = readJSONFile(host, joinPath(dir, MANIFEST));
|
|
87
|
+
if (read.kind !== "read") {
|
|
88
|
+
return read;
|
|
85
89
|
}
|
|
86
|
-
return isRecord(value) ? { kind: "read", manifest: value } : { kind: "malformed" };
|
|
90
|
+
return isRecord(read.value) ? { kind: "read", manifest: read.value } : { kind: "malformed" };
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
/** Whether one directory holds a manifest file. */
|
package/src/generated-file.ts
CHANGED
|
@@ -2,12 +2,25 @@
|
|
|
2
2
|
* The generated-file class on TypeScript: every declaration in a file that marks itself
|
|
3
3
|
* generated, below a directory an applied convention row names as generated, or minified,
|
|
4
4
|
* is retained, because a generator owns the file and a deletion there is undone at the next
|
|
5
|
-
* generation.
|
|
5
|
+
* generation. The same decision is each `generated` entry of the facts document.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
isIdentifier,
|
|
10
|
+
isImportDeclaration,
|
|
11
|
+
isPropertyAssignment,
|
|
12
|
+
isStringLiteralLikeNode,
|
|
13
|
+
isVariableStatement,
|
|
14
|
+
NodeFlags,
|
|
15
|
+
type Node,
|
|
16
|
+
type SourceFile,
|
|
17
|
+
} from "@typescript/native/ast";
|
|
18
|
+
import { GENERATORS } from "./contract/contract.generated.ts";
|
|
8
19
|
import type { DetectorInput, Evidence } from "./exempt.ts";
|
|
9
|
-
import {
|
|
20
|
+
import { minifiedBy } from "./javascript-file.ts";
|
|
10
21
|
import type { SourceFiles } from "./source-files.ts";
|
|
22
|
+
import { bytewise } from "./text/compare.ts";
|
|
23
|
+
import { satisfies } from "./version-range.ts";
|
|
11
24
|
|
|
12
25
|
/** The generated-code header, matched against each line of a leading comment. */
|
|
13
26
|
const HEADER_LINE = /^\/\/ Code generated .* DO NOT EDIT\.$/u;
|
|
@@ -24,9 +37,44 @@ const GENERATED_WORD = /\bgenerated\b/iu;
|
|
|
24
37
|
/** The phrases a generator's notice asks a reader not to edit the file by. */
|
|
25
38
|
const KEEP_OUT = /\b(?:do not edit|will be overwritten|should not make any changes)\b/iu;
|
|
26
39
|
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
/** What decided a file generated, as the facts document's `evidence` names it. */
|
|
41
|
+
type GeneratedEvidence = "header" | "tag" | "notice" | "convention" | "minified";
|
|
42
|
+
|
|
43
|
+
/** The generator row one generated file matches, with the version and the schema files its output states. */
|
|
44
|
+
interface GeneratorMatch {
|
|
45
|
+
readonly row: string;
|
|
46
|
+
readonly version?: string;
|
|
47
|
+
readonly schemaFiles: readonly string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Why one file is generated, and which generator wrote it. */
|
|
51
|
+
export interface GeneratedFact {
|
|
52
|
+
readonly evidence: GeneratedEvidence;
|
|
53
|
+
/** The 1-based line of the comment line that marks the file; 1 for a row or a minified file. */
|
|
54
|
+
readonly line: number;
|
|
55
|
+
/** The marking line's text, the convention row's name, or what decided the file minified. */
|
|
56
|
+
readonly detail: string;
|
|
57
|
+
readonly generator?: GeneratorMatch;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** One line of a leading comment, with its 1-based line in the file. */
|
|
61
|
+
interface CommentLine {
|
|
62
|
+
readonly text: string;
|
|
63
|
+
readonly line: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The comments before a text's first token, a leading hashbang line skipped, each as its lines. */
|
|
67
|
+
function leadingComments(text: string): readonly (readonly CommentLine[])[] {
|
|
68
|
+
const comments: CommentLine[][] = [];
|
|
69
|
+
const lineAt = (offset: number): number =>
|
|
70
|
+
text.slice(0, offset).split(/\r\n|[\n\r\u2028\u2029]/u).length;
|
|
71
|
+
const lines = (from: number, to: number): CommentLine[] => {
|
|
72
|
+
const first = lineAt(from);
|
|
73
|
+
return text
|
|
74
|
+
.slice(from, to)
|
|
75
|
+
.split(/\r\n|[\n\r\u2028\u2029]/u)
|
|
76
|
+
.map((one, index) => ({ text: one, line: first + index }));
|
|
77
|
+
};
|
|
30
78
|
let at = text.startsWith("\uFEFF") ? 1 : 0;
|
|
31
79
|
if (text.startsWith("#!", at)) {
|
|
32
80
|
const end = text.indexOf("\n", at);
|
|
@@ -39,14 +87,14 @@ function leadingComments(text: string): readonly string[] {
|
|
|
39
87
|
if (text.startsWith("//", at)) {
|
|
40
88
|
const end = text.slice(at).search(/[\r\n\u2028\u2029]/u);
|
|
41
89
|
const stop = end === -1 ? text.length : at + end;
|
|
42
|
-
comments.push(
|
|
90
|
+
comments.push(lines(at, stop));
|
|
43
91
|
at = stop;
|
|
44
92
|
} else if (text.startsWith("/*", at)) {
|
|
45
93
|
const end = text.indexOf("*/", at + 2);
|
|
46
94
|
if (end === -1) {
|
|
47
95
|
return comments;
|
|
48
96
|
}
|
|
49
|
-
comments.push(
|
|
97
|
+
comments.push(lines(at, end + 2));
|
|
50
98
|
at = end + 2;
|
|
51
99
|
} else {
|
|
52
100
|
return comments;
|
|
@@ -54,51 +102,201 @@ function leadingComments(text: string): readonly string[] {
|
|
|
54
102
|
}
|
|
55
103
|
}
|
|
56
104
|
|
|
57
|
-
/** The
|
|
58
|
-
export function
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
105
|
+
/** The mark one file's leading comments make it generated by, or undefined where they mark nothing. */
|
|
106
|
+
export function generatedMark(text: string): Omit<GeneratedFact, "generator"> | undefined {
|
|
107
|
+
const lines = leadingComments(text).flat();
|
|
108
|
+
const header = lines.find((one) => HEADER_LINE.test(one.text));
|
|
109
|
+
if (header !== undefined) {
|
|
110
|
+
return { evidence: "header", line: header.line, detail: header.text };
|
|
111
|
+
}
|
|
112
|
+
const tag = lines.find((one) => GENERATED_TAG.test(one.text));
|
|
113
|
+
if (tag !== undefined) {
|
|
114
|
+
return { evidence: "tag", line: tag.line, detail: tag.text };
|
|
62
115
|
}
|
|
63
|
-
|
|
64
|
-
|
|
116
|
+
const [first] = lines;
|
|
117
|
+
if (first !== undefined && GENERATED_BY.test(first.text)) {
|
|
118
|
+
return { evidence: "notice", line: first.line, detail: first.text };
|
|
65
119
|
}
|
|
66
|
-
const notice =
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
120
|
+
const notice = lines.map((one) => one.text).join("\n");
|
|
121
|
+
if (GENERATED_WORD.test(notice) && KEEP_OUT.test(notice)) {
|
|
122
|
+
const marked = lines.find((one) => GENERATED_WORD.test(one.text) || KEEP_OUT.test(one.text));
|
|
123
|
+
return marked === undefined
|
|
124
|
+
? undefined
|
|
125
|
+
: { evidence: "notice", line: marked.line, detail: marked.text };
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** What one form's first group captures, for each line the form matches. */
|
|
131
|
+
function after(lines: readonly CommentLine[], form: RegExp): readonly string[] {
|
|
132
|
+
return lines.flatMap((one) => form.exec(one.text)?.[1] ?? []);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Where each plugin's output states its version and its schema files, as `references-ts.36`
|
|
137
|
+
* states: a plugin missing here states no version, and one with no schema form names none.
|
|
138
|
+
*/
|
|
139
|
+
const STATEMENTS: Readonly<Record<string, { version?: RegExp; schemaFiles?: RegExp }>> = {
|
|
140
|
+
"protoc-gen-es": {
|
|
141
|
+
version: /^\/\/ @generated by protoc-gen-es (\S+)/u,
|
|
142
|
+
schemaFiles: /^\/\/ @generated from file (\S+)/u,
|
|
143
|
+
},
|
|
144
|
+
"protoc-gen-connect-es": {
|
|
145
|
+
version: /^\/\/ @generated by protoc-gen-connect-es (\S+)/u,
|
|
146
|
+
schemaFiles: /^\/\/ @generated from file (\S+)/u,
|
|
147
|
+
},
|
|
148
|
+
"protobuf-ts": {
|
|
149
|
+
version: /^\/\/ @generated by protobuf-ts (\S+)/u,
|
|
150
|
+
schemaFiles: /^\/\/ @generated from protobuf file "([^"]+)"/u,
|
|
151
|
+
},
|
|
152
|
+
"ts-proto": {
|
|
153
|
+
version: /^\/\/\s+protoc-gen-ts_proto\s+(\S+)$/u,
|
|
154
|
+
schemaFiles: /^\/\/ source: (.+)$/u,
|
|
155
|
+
},
|
|
156
|
+
"protoc-gen-grpc-web": {
|
|
157
|
+
version: /^\/\/\s+protoc-gen-grpc-web\s+(\S+)$/u,
|
|
158
|
+
schemaFiles: /^\/\/ source: (.+)$/u,
|
|
159
|
+
},
|
|
160
|
+
"protoc-gen-js": { schemaFiles: /^\/\/ source: (.+)$/u },
|
|
161
|
+
"ts-protoc-gen": { schemaFiles: /^\/\/ file: (.+)$/u },
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** A ts-proto service path literal: `/<package>.<service>/<method>`. */
|
|
165
|
+
const SERVICE_PATH = /^\/(?:\w+\.)+\w+\/\w+$/u;
|
|
166
|
+
|
|
167
|
+
/** Whether one ts-proto file writes a service path, as `references-ts.36` reads one. */
|
|
168
|
+
function writesServicePath(file: SourceFile): boolean {
|
|
169
|
+
const named = file.statements.some(
|
|
170
|
+
(statement) =>
|
|
171
|
+
isVariableStatement(statement) &&
|
|
172
|
+
(statement.declarationList.flags & NodeFlags.Const) !== 0 &&
|
|
173
|
+
statement.declarationList.declarations.some(
|
|
174
|
+
(one) =>
|
|
175
|
+
isIdentifier(one.name) &&
|
|
176
|
+
one.name.text.endsWith("ServiceName") &&
|
|
177
|
+
one.initializer !== undefined &&
|
|
178
|
+
isStringLiteralLikeNode(one.initializer),
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
let found = named;
|
|
182
|
+
const visit = (node: Node): void => {
|
|
183
|
+
if (found) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (
|
|
187
|
+
isPropertyAssignment(node) &&
|
|
188
|
+
isIdentifier(node.name) &&
|
|
189
|
+
node.name.text === "path" &&
|
|
190
|
+
isStringLiteralLikeNode(node.initializer) &&
|
|
191
|
+
SERVICE_PATH.test(node.initializer.text)
|
|
192
|
+
) {
|
|
193
|
+
found = true;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
node.forEachChild(visit);
|
|
197
|
+
};
|
|
198
|
+
file.forEachChild(visit);
|
|
199
|
+
return found;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Whether one file imports a module by its specifier. */
|
|
203
|
+
function imports(file: SourceFile, module: string): boolean {
|
|
204
|
+
return file.statements.some(
|
|
205
|
+
(statement) =>
|
|
206
|
+
isImportDeclaration(statement) &&
|
|
207
|
+
isStringLiteralLikeNode(statement.moduleSpecifier) &&
|
|
208
|
+
statement.moduleSpecifier.text === module,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** The option sets the rows of `references-ts.36` name, by row; every other row's is met by any output. */
|
|
213
|
+
const OPTION_SETS: Readonly<Record<string, (file: SourceFile) => boolean>> = {
|
|
214
|
+
"ts-proto-2-output-schema": (file) => imports(file, "ts-proto-descriptors"),
|
|
215
|
+
"ts-proto-2": writesServicePath,
|
|
216
|
+
"ts-proto-2-types": (file) => !writesServicePath(file),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/** The first generator row one file matches, in `generators.json`'s order, as `bindings.3` reads it. */
|
|
220
|
+
export function generatorOf(file: SourceFile): GeneratorMatch | undefined {
|
|
221
|
+
const lines = leadingComments(file.text).flat();
|
|
222
|
+
for (const row of GENERATORS) {
|
|
223
|
+
const header = new RegExp(row.header, "u");
|
|
224
|
+
if (!lines.some((one) => header.test(one.text))) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const statements = STATEMENTS[row.plugin] ?? {};
|
|
228
|
+
const [version] = statements.version === undefined ? [] : after(lines, statements.version);
|
|
229
|
+
if (
|
|
230
|
+
"versions" in row &&
|
|
231
|
+
(version === undefined ||
|
|
232
|
+
!satisfies(
|
|
233
|
+
version.replace(/^v/u, ""),
|
|
234
|
+
`>=${row.versions.min.replace(/^v/u, "")} <=${row.versions.max.replace(/^v/u, "")}`,
|
|
235
|
+
))
|
|
236
|
+
) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (OPTION_SETS[row.id]?.(file) === false) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const schemaFiles =
|
|
243
|
+
statements.schemaFiles === undefined
|
|
244
|
+
? []
|
|
245
|
+
: [
|
|
246
|
+
...new Set(
|
|
247
|
+
after(lines, statements.schemaFiles).filter((one) => one.endsWith(".proto")),
|
|
248
|
+
),
|
|
249
|
+
].sort(bytewise);
|
|
250
|
+
return { row: row.id, ...(version === undefined ? {} : { version }), schemaFiles };
|
|
72
251
|
}
|
|
73
252
|
return undefined;
|
|
74
253
|
}
|
|
75
254
|
|
|
76
255
|
/**
|
|
77
|
-
* Every own file of one project that is generated, by its path below the target root,
|
|
78
|
-
*
|
|
79
|
-
*
|
|
256
|
+
* Every own file of one project that is generated, by its path below the target root, with
|
|
257
|
+
* what decided it: its leading comments' mark, else the row naming its directory as
|
|
258
|
+
* generated, else its minified name or text; and the generator row it matches.
|
|
80
259
|
*/
|
|
81
260
|
export function generatedFilesOf(
|
|
82
261
|
files: SourceFiles,
|
|
83
262
|
byRow: ReadonlyMap<string, string>,
|
|
84
|
-
): ReadonlyMap<string,
|
|
85
|
-
const found = new Map<string,
|
|
263
|
+
): ReadonlyMap<string, GeneratedFact> {
|
|
264
|
+
const found = new Map<string, GeneratedFact>();
|
|
86
265
|
for (const [path, file] of files.byPath) {
|
|
87
266
|
const row = byRow.get(path);
|
|
88
|
-
const
|
|
89
|
-
|
|
267
|
+
const minified = row === undefined ? minifiedBy(file.fileName, file.text) : undefined;
|
|
268
|
+
const fact =
|
|
269
|
+
generatedMark(file.text) ??
|
|
90
270
|
(row === undefined
|
|
91
|
-
? minified
|
|
92
|
-
?
|
|
93
|
-
:
|
|
94
|
-
:
|
|
95
|
-
if (
|
|
96
|
-
|
|
271
|
+
? minified === undefined
|
|
272
|
+
? undefined
|
|
273
|
+
: { evidence: "minified" as const, line: 1, detail: minified }
|
|
274
|
+
: { evidence: "convention" as const, line: 1, detail: row });
|
|
275
|
+
if (fact === undefined) {
|
|
276
|
+
continue;
|
|
97
277
|
}
|
|
278
|
+
const generator = generatorOf(file);
|
|
279
|
+
found.set(path, generator === undefined ? fact : { ...fact, generator });
|
|
98
280
|
}
|
|
99
281
|
return found;
|
|
100
282
|
}
|
|
101
283
|
|
|
284
|
+
/** The detail a retention in a generated file names, from what decided the file. */
|
|
285
|
+
function retentionDetail(fact: GeneratedFact): string {
|
|
286
|
+
switch (fact.evidence) {
|
|
287
|
+
case "header":
|
|
288
|
+
return "generated by the header line its leading comments hold";
|
|
289
|
+
case "tag":
|
|
290
|
+
return "generated by the @generated tag its leading comments hold";
|
|
291
|
+
case "notice":
|
|
292
|
+
return "generated by the notice its leading comments hold";
|
|
293
|
+
case "convention":
|
|
294
|
+
return `generated below a directory the convention row ${fact.detail} names`;
|
|
295
|
+
case "minified":
|
|
296
|
+
return "generated as a minified JavaScript file";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
102
300
|
/** The generated-file detector, recording each file's first line. */
|
|
103
301
|
export function generatedFile<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
|
|
104
302
|
const generated = input.generated;
|
|
@@ -106,9 +304,15 @@ export function generatedFile<Brand>(input: DetectorInput<Brand>): readonly Evid
|
|
|
106
304
|
return [];
|
|
107
305
|
}
|
|
108
306
|
return input.held.symbols.flatMap((symbol): Evidence[] => {
|
|
109
|
-
const
|
|
110
|
-
return symbol.kind === "file" ||
|
|
307
|
+
const fact = generated.get(symbol.position.path);
|
|
308
|
+
return symbol.kind === "file" || fact === undefined
|
|
111
309
|
? []
|
|
112
|
-
: [
|
|
310
|
+
: [
|
|
311
|
+
{
|
|
312
|
+
id: symbol.id,
|
|
313
|
+
detail: retentionDetail(fact),
|
|
314
|
+
site: { path: symbol.position.path, line: 1, column: 1 },
|
|
315
|
+
},
|
|
316
|
+
];
|
|
113
317
|
});
|
|
114
318
|
}
|
package/src/graph.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import type { InventorySymbol, SymbolKind } from "./inventory.ts";
|
|
9
9
|
import type { Reference } from "./references.ts";
|
|
10
|
-
import
|
|
10
|
+
import { madeByTestCode } from "./tests/file-class.ts";
|
|
11
|
+
import type { Root, RootKind } from "./roots/root-set.ts";
|
|
11
12
|
|
|
12
13
|
/** The position of a declaration the inventory does not hold. */
|
|
13
14
|
export const OUTSIDE = -1;
|
|
@@ -39,8 +40,8 @@ const SWEPT: Readonly<Record<SymbolKind, boolean>> = {
|
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Whether a root of each kind names a caller, which holds it live under both relations: a
|
|
42
|
-
* tool reads what it loads and what its strings name, a runtime runs a test
|
|
43
|
-
*
|
|
43
|
+
* tool reads what it loads and what its strings name, a runtime runs a test or setup
|
|
44
|
+
* file, a page's module script, a command, a script or a workflow step, the manifest
|
|
44
45
|
* names what enters the package, an alias global stands for its export, an outside
|
|
45
46
|
* declaration names a global interface merging into it, and an entry file, a configured root
|
|
46
47
|
* and a pattern assert a caller. A published API supposes a consumer: reachability alone.
|
|
@@ -61,8 +62,6 @@ const NAMES_A_CALLER: Readonly<Record<RootKind, boolean>> = {
|
|
|
61
62
|
"configuration-string": true,
|
|
62
63
|
"bundler-entry": true,
|
|
63
64
|
"test-runner": true,
|
|
64
|
-
"browser-tests": true,
|
|
65
|
-
worker: true,
|
|
66
65
|
"html-entry": true,
|
|
67
66
|
"workflow-step": true,
|
|
68
67
|
"document-import": true,
|
|
@@ -87,12 +86,22 @@ export interface Edge {
|
|
|
87
86
|
* module's top level and none of what it exports.
|
|
88
87
|
*/
|
|
89
88
|
readonly evaluation: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Whether a loaded consumer made it, whose test references a mode may class as
|
|
91
|
+
* production. Only a consumed edge can be one.
|
|
92
|
+
*/
|
|
93
|
+
readonly consumer: boolean;
|
|
90
94
|
}
|
|
91
95
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
96
|
+
/**
|
|
97
|
+
* How many references one declaration carries, split by the classification of the file that
|
|
98
|
+
* made each; `consumerTest` counts the test references a loaded consumer's test files made,
|
|
99
|
+
* which a mode may class as production (`sweep.ts` `countedRefs`).
|
|
100
|
+
*/
|
|
101
|
+
export interface Counts {
|
|
94
102
|
readonly production: number;
|
|
95
103
|
readonly test: number;
|
|
104
|
+
readonly consumerTest: number;
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
/** One root the inventory holds, at the position of the declaration it names. */
|
|
@@ -158,23 +167,30 @@ export function graphOf(
|
|
|
158
167
|
const tests = new Set(testFiles);
|
|
159
168
|
|
|
160
169
|
const out: Edge[][] = symbols.map(() => []);
|
|
161
|
-
const made = symbols.map(() => ({ production: 0, test: 0 }));
|
|
170
|
+
const made = symbols.map(() => ({ production: 0, test: 0, consumerTest: 0 }));
|
|
162
171
|
const consumed: Edge[] = [];
|
|
163
172
|
for (const reference of references) {
|
|
164
173
|
const to = at(reference.to);
|
|
165
174
|
if (to === OUTSIDE) {
|
|
166
175
|
continue;
|
|
167
176
|
}
|
|
177
|
+
const test = madeByTestCode(reference, supports);
|
|
168
178
|
const counts = made[to];
|
|
169
179
|
if (counts !== undefined) {
|
|
170
|
-
if (
|
|
180
|
+
if (test) {
|
|
171
181
|
counts.test += 1;
|
|
182
|
+
counts.consumerTest += reference.consumer === undefined ? 0 : 1;
|
|
172
183
|
} else {
|
|
173
184
|
counts.production += 1;
|
|
174
185
|
}
|
|
175
186
|
}
|
|
176
|
-
const edge = {
|
|
177
|
-
|
|
187
|
+
const edge = {
|
|
188
|
+
to,
|
|
189
|
+
test,
|
|
190
|
+
evaluation: reference.use === "evaluation",
|
|
191
|
+
consumer: reference.consumer !== undefined,
|
|
192
|
+
};
|
|
193
|
+
if (edge.consumer || reference.unheld === true) {
|
|
178
194
|
consumed.push(edge);
|
|
179
195
|
continue;
|
|
180
196
|
}
|
package/src/import-bindings.ts
CHANGED
|
@@ -4,17 +4,20 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
+
isExportDeclaration,
|
|
7
8
|
isExportSpecifier,
|
|
8
9
|
isExternalModuleReference,
|
|
9
|
-
isIdentifier,
|
|
10
10
|
isImportDeclaration,
|
|
11
11
|
isImportEqualsDeclaration,
|
|
12
12
|
isJsxElement,
|
|
13
13
|
isJsxFragment,
|
|
14
14
|
isJsxSelfClosingElement,
|
|
15
|
+
isNamedExports,
|
|
15
16
|
isNamespaceImport,
|
|
16
17
|
SyntaxKind,
|
|
17
18
|
type Identifier,
|
|
19
|
+
type ImportDeclaration,
|
|
20
|
+
type ImportEqualsDeclaration,
|
|
18
21
|
type Node,
|
|
19
22
|
type SourceFile,
|
|
20
23
|
} from "@typescript/native/ast";
|
|
@@ -41,42 +44,108 @@ export interface JsxOptions {
|
|
|
41
44
|
readonly jsxFragmentFactory?: unknown;
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/** One name a file's top-level import declaration binds. */
|
|
48
|
+
interface ImportBinding {
|
|
49
|
+
readonly name: Identifier;
|
|
50
|
+
/** The export name it binds, `default` for a default import; absent where it binds a whole module. */
|
|
51
|
+
readonly imported?: string;
|
|
52
|
+
/** An `import x = …` declaration: whether it is exported, and whether it names an external module. */
|
|
53
|
+
readonly equals?: { readonly exported: boolean; readonly external: boolean };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const bindingsCache = new WeakMap<SourceFile, readonly ImportBinding[]>();
|
|
57
|
+
|
|
44
58
|
/**
|
|
45
|
-
*
|
|
46
|
-
* an `import` declaration
|
|
47
|
-
*
|
|
59
|
+
* Every name one import declaration binds, in source order: the default, named and
|
|
60
|
+
* namespace names of an `import` declaration and the name of an `import x = …` declaration.
|
|
61
|
+
* A namespace import and an import assignment bind a whole module.
|
|
48
62
|
*/
|
|
49
|
-
export function
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
export function declarationBindings(
|
|
64
|
+
declaration: ImportDeclaration | ImportEqualsDeclaration,
|
|
65
|
+
): readonly ImportBinding[] {
|
|
66
|
+
if (isImportEqualsDeclaration(declaration)) {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
name: declaration.name,
|
|
70
|
+
equals: {
|
|
71
|
+
exported: (declaration.modifiers ?? []).some(
|
|
72
|
+
(modifier) => modifier.kind === SyntaxKind.ExportKeyword,
|
|
73
|
+
),
|
|
74
|
+
external: isExternalModuleReference(declaration.moduleReference),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
];
|
|
52
78
|
}
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
for (const element of named.elements) {
|
|
65
|
-
found.push(element.name);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
} else if (
|
|
69
|
-
isImportEqualsDeclaration(statement) &&
|
|
70
|
-
isExternalModuleReference(statement.moduleReference) &&
|
|
71
|
-
isIdentifier(statement.name) &&
|
|
72
|
-
!(statement.modifiers ?? []).some((modifier) => modifier.kind === SyntaxKind.ExportKeyword)
|
|
73
|
-
) {
|
|
74
|
-
found.push(statement.name);
|
|
79
|
+
const clause = declaration.importClause;
|
|
80
|
+
const found: ImportBinding[] = [];
|
|
81
|
+
if (clause?.name !== undefined) {
|
|
82
|
+
found.push({ name: clause.name, imported: "default" });
|
|
83
|
+
}
|
|
84
|
+
const named = clause?.namedBindings;
|
|
85
|
+
if (named !== undefined && isNamespaceImport(named)) {
|
|
86
|
+
found.push({ name: named.name });
|
|
87
|
+
} else if (named !== undefined) {
|
|
88
|
+
for (const element of named.elements) {
|
|
89
|
+
found.push({ name: element.name, imported: element.propertyName?.text ?? element.name.text });
|
|
75
90
|
}
|
|
76
91
|
}
|
|
77
92
|
return found;
|
|
78
93
|
}
|
|
79
94
|
|
|
95
|
+
/** Every name one file's top-level import declarations bind, in source order. */
|
|
96
|
+
export function bindingsOf(file: SourceFile): readonly ImportBinding[] {
|
|
97
|
+
const known = bindingsCache.get(file);
|
|
98
|
+
if (known !== undefined) {
|
|
99
|
+
return known;
|
|
100
|
+
}
|
|
101
|
+
const found = file.statements.flatMap((statement) =>
|
|
102
|
+
isImportDeclaration(statement) || isImportEqualsDeclaration(statement)
|
|
103
|
+
? declarationBindings(statement)
|
|
104
|
+
: [],
|
|
105
|
+
);
|
|
106
|
+
bindingsCache.set(file, found);
|
|
107
|
+
return found;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The export names an import or a re-export declaration reads from the module it names,
|
|
112
|
+
* `undefined` standing for the whole module where a namespace or a star reads it; none for
|
|
113
|
+
* any other node.
|
|
114
|
+
*/
|
|
115
|
+
export function importedNames(node: Node): readonly (string | undefined)[] {
|
|
116
|
+
if (isImportDeclaration(node)) {
|
|
117
|
+
return declarationBindings(node).map((binding) => binding.imported);
|
|
118
|
+
}
|
|
119
|
+
if (!isExportDeclaration(node)) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const clause = node.exportClause;
|
|
123
|
+
return clause === undefined || !isNamedExports(clause)
|
|
124
|
+
? [undefined]
|
|
125
|
+
: clause.elements.map((one) => (one.propertyName ?? one.name).text);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The export names {@link importedNames} gives one node, without the whole module. */
|
|
129
|
+
export function namesImported(node: Node): readonly string[] {
|
|
130
|
+
return importedNames(node).flatMap((name) => name ?? []);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The bindings of one file that are judged as imports: those of {@link bindingsOf} but an
|
|
135
|
+
* `import x = …` that is exported or names no external module. None is judged in a
|
|
136
|
+
* declaration file or a component file.
|
|
137
|
+
*/
|
|
138
|
+
export function importBindings(file: SourceFile): readonly Identifier[] {
|
|
139
|
+
if (file.isDeclarationFile || !JUDGED.test(file.fileName)) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
return bindingsOf(file).flatMap((binding) =>
|
|
143
|
+
binding.equals !== undefined && (binding.equals.exported || !binding.equals.external)
|
|
144
|
+
? []
|
|
145
|
+
: [binding.name],
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
80
149
|
/** Whether one identifier names a binding in a local export, which reads it whatever it resolves to. */
|
|
81
150
|
export function exportsLocally(use: Identifier): boolean {
|
|
82
151
|
return isExportSpecifier(use.parent);
|