@cplieger/deadset-ts 4.0.1 → 5.1.0
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 +4 -4
- package/dist/bin/component-mapper.js +5 -0
- package/dist/bin/mapper-connection.js +101 -0
- package/dist/bin/node-host.js +24 -2
- package/dist/src/analysis.js +301 -41
- package/dist/src/component-files.js +612 -0
- package/dist/src/component-layer.js +132 -0
- package/dist/src/config.js +9 -2
- package/dist/src/configuration-files.js +290 -0
- package/dist/src/conformance-record.js +3 -9
- package/dist/src/contextual-uses.js +240 -0
- package/dist/src/convention-rows.js +389 -0
- package/dist/src/conventions.js +565 -0
- package/dist/src/dependencies.js +36 -3
- package/dist/src/destructuring.js +6 -6
- package/dist/src/discover.js +10 -3
- package/dist/src/emit-map.js +72 -0
- package/dist/src/entry-point-gaps.js +81 -35
- package/dist/src/entry-points.js +50 -32
- package/dist/src/exempt-classes.js +6 -1
- package/dist/src/exempt.js +32 -20
- package/dist/src/findings/completion.js +46 -32
- package/dist/src/findings/emitters.js +22 -1
- package/dist/src/findings/interfaces.js +6 -2
- package/dist/src/findings/reads-and-writes.js +53 -9
- package/dist/src/findings/unused-declarations.js +47 -3
- package/dist/src/generated-file.js +24 -0
- package/dist/src/graph.js +13 -5
- package/dist/src/interface-satisfaction.js +2 -68
- package/dist/src/inventory.js +8 -3
- package/dist/src/kinds.js +6 -5
- package/dist/src/manifest.js +37 -11
- package/dist/src/matrix.js +21 -13
- package/dist/src/partial.js +3 -1
- package/dist/src/pnpm-workspace.js +120 -0
- package/dist/src/position.js +17 -2
- package/dist/src/print.js +2 -0
- package/dist/src/query.js +15 -0
- package/dist/src/references.js +404 -36
- package/dist/src/report.js +26 -6
- package/dist/src/reporters/annotations.js +10 -3
- package/dist/src/reporters/template.js +3 -0
- package/dist/src/resolve.js +37 -11
- package/dist/src/roots.js +37 -8
- package/dist/src/run.js +13 -3
- package/dist/src/schema.js +2 -0
- package/dist/src/session.js +141 -34
- package/dist/src/setup-failure.js +23 -0
- package/dist/src/source-files.js +4 -61
- package/dist/src/stores.js +3 -0
- package/dist/src/sweep.js +4 -2
- package/dist/src/template-field.js +38 -6
- package/dist/src/test-support.js +59 -0
- package/dist/src/type-errors.js +252 -0
- package/dist/src/type-query-alias.js +92 -0
- package/dist/src/value-flow.js +74 -0
- package/dist/src/verbs/analyze.js +24 -4
- package/dist/src/verbs/explain.js +4 -1
- package/dist/src/verbs/print-projects.js +17 -56
- package/dist/src/verbs/print-retained.js +7 -2
- package/dist/src/verbs/print-roots.js +7 -2
- package/dist/src/version-range.js +255 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-manifests.js +68 -0
- package/dist/src/workspace-programs.js +342 -0
- package/dist/src/workspace-resolution.js +307 -0
- package/dist/src/workspace-run.js +75 -0
- package/dist/src/workspace.js +358 -0
- package/jsr.json +1 -1
- package/package.json +3 -2
- package/src/analysis.ts +475 -72
- package/src/component-files.ts +718 -0
- package/src/component-layer.ts +160 -0
- package/src/config.ts +11 -2
- package/src/configuration-files.ts +370 -0
- package/src/conformance-record.ts +3 -10
- package/src/contextual-uses.ts +354 -0
- package/src/convention-rows.ts +448 -0
- package/src/conventions.ts +788 -0
- package/src/dependencies.ts +49 -2
- package/src/destructuring.ts +16 -6
- package/src/discover.ts +10 -6
- package/src/emit-map.ts +93 -0
- package/src/entry-point-gaps.ts +92 -38
- package/src/entry-points.ts +79 -36
- package/src/exempt-classes.ts +6 -1
- package/src/exempt.ts +69 -20
- package/src/file-facts.ts +2 -0
- package/src/findings/completion.ts +55 -36
- package/src/findings/emitters.ts +32 -1
- package/src/findings/interfaces.ts +9 -2
- package/src/findings/reads-and-writes.ts +70 -9
- package/src/findings/unused-declarations.ts +60 -3
- package/src/generated-file.ts +27 -0
- package/src/graph.ts +15 -4
- package/src/host.ts +23 -0
- package/src/index.ts +1 -1
- package/src/interface-satisfaction.ts +1 -92
- package/src/inventory.ts +17 -3
- package/src/kinds.ts +7 -5
- package/src/manifest.ts +47 -14
- package/src/matrix.ts +43 -15
- package/src/partial.ts +6 -1
- package/src/pnpm-workspace.ts +135 -0
- package/src/position.ts +23 -2
- package/src/print.ts +2 -0
- package/src/query.ts +26 -0
- package/src/references.ts +472 -40
- package/src/report.ts +56 -6
- package/src/reporters/annotations.ts +12 -3
- package/src/reporters/template.ts +3 -0
- package/src/resolve.ts +65 -11
- package/src/roots.ts +70 -9
- package/src/run.ts +15 -5
- package/src/schema.ts +2 -0
- package/src/session.ts +218 -37
- package/src/setup-failure.ts +39 -0
- package/src/source-files.ts +4 -66
- package/src/stores.ts +9 -0
- package/src/sweep.ts +9 -2
- package/src/template-field.ts +42 -7
- package/src/test-support.ts +77 -0
- package/src/type-errors.ts +340 -0
- package/src/type-query-alias.ts +133 -0
- package/src/value-flow.ts +104 -0
- package/src/verbs/analyze.ts +38 -4
- package/src/verbs/explain.ts +4 -1
- package/src/verbs/print-projects.ts +17 -69
- package/src/verbs/print-retained.ts +7 -2
- package/src/verbs/print-roots.ts +7 -2
- package/src/verbs/verb.ts +6 -3
- package/src/version-range.ts +309 -0
- package/src/version.ts +1 -1
- package/src/workspace-manifests.ts +100 -0
- package/src/workspace-programs.ts +486 -0
- package/src/workspace-resolution.ts +393 -0
- package/src/workspace-run.ts +148 -0
- package/src/workspace.ts +463 -0
package/src/manifest.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The entry points a package manifest names, which are roots because something
|
|
3
3
|
* outside the analyzed program reaches them: a consumer importing the package, or a
|
|
4
|
-
* shell running its command.
|
|
4
|
+
* shell running its command or one of its scripts.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* to another package, whose entry points are that package's roots and not this
|
|
8
|
-
* target's.
|
|
6
|
+
* One call reads one manifest, the one in the directory it is given.
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
9
|
import type { Host } from "./host.ts";
|
|
@@ -22,7 +20,9 @@ export type EntryRole =
|
|
|
22
20
|
/** A consumer importing the package, which is a caller the analysis cannot see. */
|
|
23
21
|
| "import"
|
|
24
22
|
/** A shell running the package's command, which is a caller that exists. */
|
|
25
|
-
| "run"
|
|
23
|
+
| "run"
|
|
24
|
+
/** A token of one of the package's scripts, which the package manager runs. */
|
|
25
|
+
| "script";
|
|
26
26
|
|
|
27
27
|
/** One entry point the manifest names. */
|
|
28
28
|
export interface ManifestEntry {
|
|
@@ -59,6 +59,24 @@ function isTarget(value: string, requireDot: boolean): boolean {
|
|
|
59
59
|
return !requireDot || value.startsWith("./");
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** Where a script's command line splits into tokens: white space and the shell's operators. */
|
|
63
|
+
const SCRIPT_SEPARATORS = /\s+|&&|\|\||;|\|/u;
|
|
64
|
+
|
|
65
|
+
/** A token written inside one pair of quotes. */
|
|
66
|
+
const QUOTED = /^(["'])(.*)\1$/su;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Every token one script's command line holds, each with one pair of enclosing quotes
|
|
70
|
+
* removed. A token holding a wildcard is a pattern rather than a path, and names no
|
|
71
|
+
* entry point.
|
|
72
|
+
*/
|
|
73
|
+
export function scriptTokens(command: string): readonly string[] {
|
|
74
|
+
return command
|
|
75
|
+
.split(SCRIPT_SEPARATORS)
|
|
76
|
+
.map((token) => token.replace(QUOTED, "$2"))
|
|
77
|
+
.filter((token) => token !== "" && !token.includes("*"));
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
/** One member's spelling below another: `exports` then `exports["."]`. */
|
|
63
81
|
function below(member: string, key: string): string {
|
|
64
82
|
return `${member}[${JSON.stringify(key)}]`;
|
|
@@ -67,12 +85,9 @@ function below(member: string, key: string): string {
|
|
|
67
85
|
/**
|
|
68
86
|
* Every target one `exports` value carries, at the member each is written under.
|
|
69
87
|
*
|
|
70
|
-
* The value
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* file of the same package and each is an entry point of its own; a key's meaning
|
|
74
|
-
* is not read, because a subpath and a condition both lead to a target and the
|
|
75
|
-
* walk needs no more than that.
|
|
88
|
+
* The value nests strings, subpath or condition objects and alternative arrays to
|
|
89
|
+
* any depth; a `null` blocks a subpath. Every level is walked, because each
|
|
90
|
+
* condition names its own entry file, and a key's meaning is never read.
|
|
76
91
|
*/
|
|
77
92
|
function exportTargets(value: unknown, member: string, found: ManifestEntry[]): void {
|
|
78
93
|
if (typeof value === "string") {
|
|
@@ -116,11 +131,12 @@ function manifestOf(host: Host, targetRoot: string): Record<string, unknown> | u
|
|
|
116
131
|
return isRecord(value) ? value : undefined;
|
|
117
132
|
}
|
|
118
133
|
|
|
119
|
-
/** What
|
|
134
|
+
/** What one manifest declares about where its package is entered. */
|
|
120
135
|
export interface Manifest {
|
|
121
136
|
/**
|
|
122
137
|
* Every entry point the manifest names, in the order the members are read: `main`,
|
|
123
|
-
* `module`, `types`, then each `bin` command, then every target of `exports
|
|
138
|
+
* `module`, `types`, then each `bin` command, then every target of `exports`, then
|
|
139
|
+
* every token of each script, which names a file only where one is at its path. Each
|
|
124
140
|
* path is absolute.
|
|
125
141
|
*/
|
|
126
142
|
readonly entries: readonly ManifestEntry[];
|
|
@@ -134,7 +150,7 @@ export interface Manifest {
|
|
|
134
150
|
}
|
|
135
151
|
|
|
136
152
|
/**
|
|
137
|
-
* What the
|
|
153
|
+
* What the manifest in `targetRoot` declares about where its package is entered.
|
|
138
154
|
*
|
|
139
155
|
* A target is taken as written. It is not resolved to a source file here, because
|
|
140
156
|
* which file a target names is a fact about the compiler configuration that emits
|
|
@@ -167,6 +183,23 @@ export function readManifest(host: Host, targetRoot: string): Manifest {
|
|
|
167
183
|
|
|
168
184
|
exportTargets(manifest["exports"], "exports", found);
|
|
169
185
|
|
|
186
|
+
const scripts = manifest["scripts"];
|
|
187
|
+
if (isRecord(scripts)) {
|
|
188
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
189
|
+
if (typeof command !== "string") {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
for (const token of new Set(scriptTokens(command))) {
|
|
193
|
+
found.push({
|
|
194
|
+
member: below("scripts", name),
|
|
195
|
+
path: token,
|
|
196
|
+
role: "script",
|
|
197
|
+
published: false,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
170
203
|
return {
|
|
171
204
|
entries: found.map((entry) => ({ ...entry, path: resolvePath(targetRoot, entry.path) })),
|
|
172
205
|
declaresExports: manifest["exports"] !== undefined,
|
package/src/matrix.ts
CHANGED
|
@@ -21,6 +21,14 @@ export interface Configured {
|
|
|
21
21
|
readonly roots: readonly Root[];
|
|
22
22
|
/** The paths the reference pass classified as test files. */
|
|
23
23
|
readonly testFiles: readonly string[];
|
|
24
|
+
/** The paths of the test-support files, which only test code imports. */
|
|
25
|
+
readonly supportFiles?: readonly string[] | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* The paths of the workspace-package files the configuration holds but does not
|
|
28
|
+
* compile itself. Their declarations count in its graph and its verdict on them is no
|
|
29
|
+
* finding.
|
|
30
|
+
*/
|
|
31
|
+
readonly referenceOnly?: readonly string[] | undefined;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
/** The run's configurations, indexed for the sweep. */
|
|
@@ -30,9 +38,10 @@ export interface Matrix {
|
|
|
30
38
|
/** Per configuration, in the same order, the graph of what that configuration holds. */
|
|
31
39
|
readonly graphs: readonly Graph[];
|
|
32
40
|
/**
|
|
33
|
-
* The graph over every configuration at once: every declaration
|
|
34
|
-
* holds
|
|
35
|
-
*
|
|
41
|
+
* The graph over every configuration at once: every declaration a configuration
|
|
42
|
+
* holds in a file it does not hold only for its references, once per position and in
|
|
43
|
+
* site order, and every reference any configuration makes, a reference several
|
|
44
|
+
* configurations make at one position counted once.
|
|
36
45
|
*/
|
|
37
46
|
readonly union: Graph;
|
|
38
47
|
/**
|
|
@@ -88,31 +97,30 @@ export function referenceKey(reference: Reference): string {
|
|
|
88
97
|
|
|
89
98
|
/**
|
|
90
99
|
* Indexes what the passes answered for each configuration of one run, in the run's order.
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* configuration alone holds is in the union with that one configuration, so it is
|
|
96
|
-
* judged where it exists rather than skipped.
|
|
97
|
-
*
|
|
98
|
-
* One reference written at one position of a file several configurations compile is seen
|
|
99
|
-
* once per configuration and is one reference, so the union holds it once; two references
|
|
100
|
-
* one configuration makes at one position are two.
|
|
100
|
+
* A declaration is keyed by its rendered position, with the fields of the first
|
|
101
|
+
* configuration that judges it; one in a file every holder holds only for its references
|
|
102
|
+
* is judged by none and is not in the union. One reference at one position of a file
|
|
103
|
+
* several configurations compile is one reference; two one configuration makes are two.
|
|
101
104
|
*/
|
|
102
105
|
export function matrixOf(per: readonly Configured[]): Matrix {
|
|
103
106
|
const symbols: InventorySymbol[] = [];
|
|
104
107
|
const holders = new Map<string, string[]>();
|
|
105
108
|
const references: Reference[] = [];
|
|
106
109
|
const firstSeen = new Map<string, number>();
|
|
110
|
+
const judged = new Set<string>();
|
|
107
111
|
per.forEach((one, index) => {
|
|
112
|
+
const referenceOnly = new Set(one.referenceOnly);
|
|
108
113
|
for (const symbol of one.symbols) {
|
|
109
114
|
const held = holders.get(symbol.id);
|
|
110
115
|
if (held === undefined) {
|
|
111
116
|
holders.set(symbol.id, [one.configuration]);
|
|
112
|
-
symbols.push(symbol);
|
|
113
117
|
} else if (!held.includes(one.configuration)) {
|
|
114
118
|
held.push(one.configuration);
|
|
115
119
|
}
|
|
120
|
+
if (!referenceOnly.has(symbol.position.path) && !judged.has(symbol.id)) {
|
|
121
|
+
judged.add(symbol.id);
|
|
122
|
+
symbols.push(symbol);
|
|
123
|
+
}
|
|
116
124
|
}
|
|
117
125
|
for (const reference of one.references) {
|
|
118
126
|
const key = referenceKey(reference);
|
|
@@ -129,12 +137,15 @@ export function matrixOf(per: readonly Configured[]): Matrix {
|
|
|
129
137
|
|
|
130
138
|
return {
|
|
131
139
|
configurations: per.map((one) => one.configuration),
|
|
132
|
-
graphs: per.map((one) =>
|
|
140
|
+
graphs: per.map((one) =>
|
|
141
|
+
graphOf(one.symbols, one.references, one.roots, one.testFiles, one.supportFiles),
|
|
142
|
+
),
|
|
133
143
|
union: graphOf(
|
|
134
144
|
symbols,
|
|
135
145
|
references,
|
|
136
146
|
per.flatMap((one) => one.roots),
|
|
137
147
|
[...new Set(per.flatMap((one) => one.testFiles))],
|
|
148
|
+
[...new Set(per.flatMap((one) => one.supportFiles ?? []))],
|
|
138
149
|
),
|
|
139
150
|
heldIn: symbols.map((symbol) => holders.get(symbol.id) ?? []),
|
|
140
151
|
};
|
|
@@ -197,6 +208,23 @@ export function sweepMatrix(matrix: Matrix, input: SweepInput): SweepResult {
|
|
|
197
208
|
};
|
|
198
209
|
}
|
|
199
210
|
|
|
211
|
+
/**
|
|
212
|
+
* The file declarations at `paths`, below the target root, that one sweep holds live
|
|
213
|
+
* under both relations, the rule a judged declaration is live by.
|
|
214
|
+
*/
|
|
215
|
+
export function liveFilesAt(
|
|
216
|
+
matrix: Matrix,
|
|
217
|
+
result: SweepResult,
|
|
218
|
+
paths: ReadonlySet<string>,
|
|
219
|
+
): readonly InventorySymbol[] {
|
|
220
|
+
return matrix.union.symbols.filter(
|
|
221
|
+
(symbol) =>
|
|
222
|
+
symbol.kind === "file" &&
|
|
223
|
+
paths.has(symbol.position.path) &&
|
|
224
|
+
(result.liveUnder.get(symbol.id)?.length ?? 0) === RELATIONS.length,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
200
228
|
/** The relations in the order a declaration's entry lists them. */
|
|
201
229
|
const RELATIONS: readonly Relation[] = ["reference-counting", "reachability"];
|
|
202
230
|
|
package/src/partial.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { NotBuilt } from "./discover.ts";
|
|
2
|
+
import { relativePath } from "./paths.ts";
|
|
2
3
|
import type { UnansweredQuestion } from "./query.ts";
|
|
3
4
|
|
|
4
5
|
/** What a run read only in part. */
|
|
@@ -7,6 +8,8 @@ interface Partial {
|
|
|
7
8
|
readonly notBuilt: readonly NotBuilt[];
|
|
8
9
|
/** Every question the checker could not answer, each once. */
|
|
9
10
|
readonly unanswered: readonly UnansweredQuestion[];
|
|
11
|
+
/** The target root, absolute, which every configuration file is named below. */
|
|
12
|
+
readonly targetRoot: string;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
/** A count with its noun, so a line reads for one as well as for several. */
|
|
@@ -29,7 +32,9 @@ export function partialNotes(partial: Partial): readonly string[] {
|
|
|
29
32
|
if (partial.unanswered.length > 0) {
|
|
30
33
|
const byProject = new Map<string, number>();
|
|
31
34
|
for (const question of partial.unanswered) {
|
|
32
|
-
|
|
35
|
+
const configFile =
|
|
36
|
+
relativePath(partial.targetRoot, question.configFile) ?? question.configFile;
|
|
37
|
+
byProject.set(configFile, (byProject.get(configFile) ?? 0) + 1);
|
|
33
38
|
}
|
|
34
39
|
const where = [...byProject].map(([configFile, n]) => `${configFile}: ${String(n)}`).join(", ");
|
|
35
40
|
notes.push(
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `packages` list of a `pnpm-workspace.yaml`. The document is read as far as that
|
|
3
|
+
* one key needs and no further: a top-level `packages` key holding a block sequence or
|
|
4
|
+
* a one-line flow sequence of plain or quoted strings. Any other value of the key is
|
|
5
|
+
* refused rather than guessed at, because a list read wrong names the wrong packages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** What the document says about `packages`. */
|
|
9
|
+
export type PnpmPackages =
|
|
10
|
+
| { readonly kind: "listed"; readonly patterns: readonly string[] }
|
|
11
|
+
/** The document declares no `packages` key. */
|
|
12
|
+
| { readonly kind: "absent" }
|
|
13
|
+
/** The key holds something this reader does not read, with what it holds. */
|
|
14
|
+
| { readonly kind: "refused"; readonly reason: string };
|
|
15
|
+
|
|
16
|
+
/** The characters a plain scalar may not start with. */
|
|
17
|
+
const INDICATORS = new Set(["[", "]", "{", "}", ",", "&", "*", "!", "|", ">", "%", "@", "`", "#"]);
|
|
18
|
+
|
|
19
|
+
/** One line with its comment removed, a `#` inside quotes kept. */
|
|
20
|
+
function uncommented(line: string): string {
|
|
21
|
+
let quote: string | undefined;
|
|
22
|
+
for (let at = 0; at < line.length; at += 1) {
|
|
23
|
+
const character = line[at];
|
|
24
|
+
if (quote !== undefined) {
|
|
25
|
+
if (character === quote) {
|
|
26
|
+
quote = undefined;
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (character === "'" || character === '"') {
|
|
31
|
+
quote = character;
|
|
32
|
+
} else if (character === "#" && (at === 0 || /\s/u.test(line[at - 1] ?? ""))) {
|
|
33
|
+
return line.slice(0, at).trimEnd();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return line.trimEnd();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One scalar as written, or `undefined` where it is not a string this reader reads. */
|
|
40
|
+
function scalarOf(text: string): string | undefined {
|
|
41
|
+
const value = text.trim();
|
|
42
|
+
if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
|
|
43
|
+
const inner = value.slice(1, -1);
|
|
44
|
+
return /'(?!')/u.test(inner.replaceAll("''", "")) ? undefined : inner.replaceAll("''", "'");
|
|
45
|
+
}
|
|
46
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
47
|
+
const inner = value.slice(1, -1);
|
|
48
|
+
return inner.includes("\\") || inner.includes('"') ? undefined : inner;
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
value === "" ||
|
|
52
|
+
INDICATORS.has(value[0] ?? "") ||
|
|
53
|
+
value.startsWith("'") ||
|
|
54
|
+
value.startsWith('"')
|
|
55
|
+
) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (value.startsWith("- ") || value.startsWith("? ") || /:(?:\s|$)/u.test(value)) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The entries of a one-line flow sequence, or `undefined` where one does not read. */
|
|
65
|
+
function flowOf(text: string): readonly string[] | undefined {
|
|
66
|
+
const inner = text.slice(1, -1).trim();
|
|
67
|
+
if (inner === "") {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const entries = inner.split(",");
|
|
71
|
+
if (entries.length > 1 && entries.at(-1)?.trim() === "") {
|
|
72
|
+
entries.pop();
|
|
73
|
+
}
|
|
74
|
+
const found: string[] = [];
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const value = scalarOf(entry);
|
|
77
|
+
if (value === undefined) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
found.push(value);
|
|
81
|
+
}
|
|
82
|
+
return found;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const KEY = /^(?:packages|'packages'|"packages")\s*:(?:\s(.*))?$/u;
|
|
86
|
+
|
|
87
|
+
/** The `packages` list one `pnpm-workspace.yaml` text declares. */
|
|
88
|
+
export function pnpmPackages(text: string): PnpmPackages {
|
|
89
|
+
const lines = text.split(/\r?\n/u).map(uncommented);
|
|
90
|
+
const keys = lines.flatMap((line, at) => (KEY.test(line) ? [at] : []));
|
|
91
|
+
const [at] = keys;
|
|
92
|
+
if (at === undefined) {
|
|
93
|
+
return { kind: "absent" };
|
|
94
|
+
}
|
|
95
|
+
if (keys.length > 1) {
|
|
96
|
+
return { kind: "refused", reason: "the packages key is declared more than once" };
|
|
97
|
+
}
|
|
98
|
+
const rest = (KEY.exec(lines[at] ?? "")?.[1] ?? "").trim();
|
|
99
|
+
if (rest !== "") {
|
|
100
|
+
const listed = rest.startsWith("[") && rest.endsWith("]") ? flowOf(rest) : undefined;
|
|
101
|
+
return listed === undefined
|
|
102
|
+
? {
|
|
103
|
+
kind: "refused",
|
|
104
|
+
reason: `packages holds ${JSON.stringify(rest)}, not a sequence of strings`,
|
|
105
|
+
}
|
|
106
|
+
: { kind: "listed", patterns: listed };
|
|
107
|
+
}
|
|
108
|
+
const patterns: string[] = [];
|
|
109
|
+
for (const line of lines.slice(at + 1)) {
|
|
110
|
+
if (line.trim() === "") {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const item = /^(\s*)-(?:\s+(.*))?$/u.exec(line);
|
|
114
|
+
if (item === null) {
|
|
115
|
+
if (/^\s/u.test(line)) {
|
|
116
|
+
return {
|
|
117
|
+
kind: "refused",
|
|
118
|
+
reason: `packages holds ${JSON.stringify(line.trim())}, not a sequence item`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
const value = scalarOf(item[2] ?? "");
|
|
124
|
+
if (value === undefined) {
|
|
125
|
+
return {
|
|
126
|
+
kind: "refused",
|
|
127
|
+
reason: `the packages item ${JSON.stringify(line.trim())} is not a plain or quoted string`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
patterns.push(value);
|
|
131
|
+
}
|
|
132
|
+
return patterns.length === 0
|
|
133
|
+
? { kind: "refused", reason: "packages holds no sequence" }
|
|
134
|
+
: { kind: "listed", patterns };
|
|
135
|
+
}
|
package/src/position.ts
CHANGED
|
@@ -34,18 +34,39 @@ export class PositionError extends Error {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** Whether the compiler read one file through a content mapper, which makes it a component file. */
|
|
38
|
+
export function isComponentFile(file: SourceFile): boolean {
|
|
39
|
+
return file.contentMapper !== undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The length of the file as it is written. A component file's module holds the file's
|
|
44
|
+
* text at its own offsets and appends what it needs past its end.
|
|
45
|
+
*/
|
|
46
|
+
export function writtenLength(file: SourceFile): number {
|
|
47
|
+
return isComponentFile(file) ? file.originalText.length : file.text.length;
|
|
48
|
+
}
|
|
49
|
+
|
|
37
50
|
/**
|
|
38
51
|
* The position of one offset of one file, rendered against the target root.
|
|
39
52
|
*
|
|
40
53
|
* `offset` is a UTF-16 code unit offset into the file's text, which is what every
|
|
41
|
-
* node of the tree carries.
|
|
54
|
+
* node of the tree carries. An offset a component file's module appends renders where
|
|
55
|
+
* the module's span map places it in the file.
|
|
42
56
|
*/
|
|
43
57
|
export function renderPosition(file: SourceFile, root: string, offset: number): Position {
|
|
44
58
|
const path = relativePath(root, file.fileName);
|
|
45
59
|
if (path === undefined) {
|
|
46
60
|
throw new PositionError(file.fileName, root);
|
|
47
61
|
}
|
|
48
|
-
const
|
|
62
|
+
const written =
|
|
63
|
+
isComponentFile(file) && offset >= file.originalText.length
|
|
64
|
+
? Math.min(
|
|
65
|
+
file.spanMap?.virtualToOriginalPosition(offset).position ?? offset,
|
|
66
|
+
file.originalText.length,
|
|
67
|
+
)
|
|
68
|
+
: offset;
|
|
69
|
+
const { line, character } = file.getLineAndCharacterOfPosition(written);
|
|
49
70
|
return { path, line: line + 1, column: character + 1 };
|
|
50
71
|
}
|
|
51
72
|
|
package/src/print.ts
CHANGED
|
@@ -75,6 +75,8 @@ export function printConfig(config: Config, provenance: Provenance): string {
|
|
|
75
75
|
ts: {
|
|
76
76
|
test_files: config.ts.testFiles,
|
|
77
77
|
entry_files: config.ts.entryFiles,
|
|
78
|
+
component_extensions: config.ts.componentExtensions,
|
|
79
|
+
disabled_conventions: config.ts.disabledConventions,
|
|
78
80
|
injection_registrations: config.ts.injectionRegistrations.map(declarationOf),
|
|
79
81
|
lifecycle_contracts: config.ts.lifecycleContracts.map((entry) => ({
|
|
80
82
|
components: entry.components.map(declarationOf),
|
package/src/query.ts
CHANGED
|
@@ -127,6 +127,32 @@ interface Assignability {
|
|
|
127
127
|
readonly target: Type;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Each type's properties, asked of the checker once per type for every pass that shares
|
|
132
|
+
* the tables: the client keeps a type's properties once fetched, so a second pass asking
|
|
133
|
+
* for the same type makes no request, and only the first ask is counted as one.
|
|
134
|
+
*/
|
|
135
|
+
export interface PropertyTables {
|
|
136
|
+
/** One type's properties, and whether this call was the one that asked for them. */
|
|
137
|
+
of(type: Type): { readonly properties: Answer<readonly TSSymbol[]>; readonly asked: boolean };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The property tables over one project's questions. */
|
|
141
|
+
export function propertyTables(queries: Pick<Queries, "propertiesOf">): PropertyTables {
|
|
142
|
+
const held = new Map<number, Answer<readonly TSSymbol[]>>();
|
|
143
|
+
return {
|
|
144
|
+
of: (type) => {
|
|
145
|
+
const known = held.get(type.id);
|
|
146
|
+
if (known !== undefined) {
|
|
147
|
+
return { properties: known, asked: false };
|
|
148
|
+
}
|
|
149
|
+
const properties = queries.propertiesOf(type);
|
|
150
|
+
held.set(type.id, properties);
|
|
151
|
+
return { properties, asked: true };
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
/** The questions one project's analysis asks, each guarded. */
|
|
131
157
|
export interface Queries {
|
|
132
158
|
/** The symbol each node resolves to. */
|