@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
|
@@ -805,6 +805,9 @@ class Execution {
|
|
|
805
805
|
if (head === undefined) {
|
|
806
806
|
throw new RenderError("empty command");
|
|
807
807
|
}
|
|
808
|
+
if (head.k === "lit" && head.value === null) {
|
|
809
|
+
throw new RenderError("nil is not a command");
|
|
810
|
+
}
|
|
808
811
|
if (head.k !== "func") {
|
|
809
812
|
if (rest.length > 0 || piped !== undefined) {
|
|
810
813
|
throw new RenderError("cannot give an argument to a non-function");
|
package/dist/src/resolve.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfigError, defaultConfig, } from "./config.js";
|
|
2
|
+
import { CONVENTION_ROWS } from "./convention-rows.js";
|
|
2
3
|
import { FIXED_SEVERITY_CODES, LIVE_CODES } from "./kinds.js";
|
|
3
4
|
import { checkDocument, malformed, nestFlags, SCHEMA_ROOT, SEVERITY_SECTION, } from "./schema.js";
|
|
4
5
|
/** Names the command-line flags in a refusal, where a document is named by its path. */
|
|
@@ -132,23 +133,44 @@ function readStrings(parent, name, path, label, minItems, allowed) {
|
|
|
132
133
|
throw malformed(label, path, `holds ${String(value.length)} entries, want at least ${String(minItems)}`);
|
|
133
134
|
}
|
|
134
135
|
const seen = new Set();
|
|
135
|
-
for (const entry of value) {
|
|
136
|
+
for (const [index, entry] of value.entries()) {
|
|
137
|
+
const at = `${path}[${String(index)}]`;
|
|
136
138
|
if (typeof entry !== "string") {
|
|
137
|
-
throw malformed(label,
|
|
139
|
+
throw malformed(label, at, "is not a string");
|
|
138
140
|
}
|
|
139
141
|
if (entry === "") {
|
|
140
|
-
throw malformed(label,
|
|
142
|
+
throw malformed(label, at, "is empty");
|
|
141
143
|
}
|
|
142
144
|
if (seen.has(entry)) {
|
|
143
|
-
throw malformed(label,
|
|
145
|
+
throw malformed(label, at, `names ${JSON.stringify(entry)} again`);
|
|
144
146
|
}
|
|
145
147
|
if (allowed !== undefined && !allowed.includes(entry)) {
|
|
146
|
-
throw malformed(label,
|
|
148
|
+
throw malformed(label, at, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
|
|
147
149
|
}
|
|
148
150
|
seen.add(entry);
|
|
149
151
|
}
|
|
150
152
|
return value;
|
|
151
153
|
}
|
|
154
|
+
/** A component file's extension: a full stop followed by letters and digits. */
|
|
155
|
+
const COMPONENT_EXTENSION = /^\.[A-Za-z0-9]+$/u;
|
|
156
|
+
function readComponentExtensions(ts, label) {
|
|
157
|
+
const extensions = readStrings(ts, "component_extensions", "ts.component_extensions", label, 0);
|
|
158
|
+
const wrong = extensions?.findIndex((one) => !COMPONENT_EXTENSION.test(one)) ?? -1;
|
|
159
|
+
if (extensions !== undefined && wrong !== -1) {
|
|
160
|
+
throw malformed(label, `ts.component_extensions[${String(wrong)}]`, `${JSON.stringify(extensions[wrong])} is not a full stop followed by letters and digits`);
|
|
161
|
+
}
|
|
162
|
+
return extensions;
|
|
163
|
+
}
|
|
164
|
+
/** The names of the convention rows this analyzer's table carries. */
|
|
165
|
+
const CONVENTION_NAMES = new Set(CONVENTION_ROWS.map((row) => row.name));
|
|
166
|
+
function readDisabledConventions(ts, label) {
|
|
167
|
+
const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
|
|
168
|
+
const unknown = names?.findIndex((one) => !CONVENTION_NAMES.has(one)) ?? -1;
|
|
169
|
+
if (names !== undefined && unknown !== -1) {
|
|
170
|
+
throw malformed(label, `ts.disabled_conventions[${String(unknown)}]`, `${JSON.stringify(names[unknown])} names no convention row this analyzer carries`);
|
|
171
|
+
}
|
|
172
|
+
return names;
|
|
173
|
+
}
|
|
152
174
|
function requireMember(value, path, label) {
|
|
153
175
|
if (value === undefined || value === "") {
|
|
154
176
|
throw malformed(label, path, "is required and names nothing");
|
|
@@ -507,6 +529,8 @@ function readDoc(value, label) {
|
|
|
507
529
|
providers: readProviders(providers, label),
|
|
508
530
|
testFiles: readStrings(ts, "test_files", "ts.test_files", label, 1),
|
|
509
531
|
entryFiles: readStrings(ts, "entry_files", "ts.entry_files", label, 0),
|
|
532
|
+
componentExtensions: readComponentExtensions(ts, label),
|
|
533
|
+
disabledConventions: readDisabledConventions(ts, label),
|
|
510
534
|
injectionRegistrations: readDeclarations(ts, "injection_registrations", "ts.injection_registrations", label),
|
|
511
535
|
lifecycleContracts: readLifecycleContracts(ts, label),
|
|
512
536
|
serializers: readDeclarations(ts, "serializers", "ts.serializers", label),
|
|
@@ -667,13 +691,13 @@ export function resolve(inputs) {
|
|
|
667
691
|
return { config, provenance };
|
|
668
692
|
}
|
|
669
693
|
/**
|
|
670
|
-
* The
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
694
|
+
* The configuration the documents resolve to, every document read and refused as
|
|
695
|
+
* {@link resolve} refuses it, with no target kind required of them: which projects a
|
|
696
|
+
* run analyzes does not depend on the kind, so a question about the projects asks
|
|
697
|
+
* none. The kind is empty where no document names one.
|
|
674
698
|
*/
|
|
675
|
-
export function
|
|
676
|
-
return settleAll(inputs).config
|
|
699
|
+
export function resolveUnkinded(inputs) {
|
|
700
|
+
return settleAll(inputs).config;
|
|
677
701
|
}
|
|
678
702
|
/**
|
|
679
703
|
* Every setting the documents supply, each from the highest-ranked source carrying
|
|
@@ -714,6 +738,8 @@ function settleAll(inputs) {
|
|
|
714
738
|
ts: {
|
|
715
739
|
testFiles: at("ts.test_files", defaults.ts.testFiles, (doc) => doc.testFiles),
|
|
716
740
|
entryFiles: at("ts.entry_files", defaults.ts.entryFiles, (doc) => doc.entryFiles),
|
|
741
|
+
componentExtensions: at("ts.component_extensions", defaults.ts.componentExtensions, (doc) => doc.componentExtensions),
|
|
742
|
+
disabledConventions: at("ts.disabled_conventions", defaults.ts.disabledConventions, (doc) => doc.disabledConventions),
|
|
717
743
|
injectionRegistrations: at("ts.injection_registrations", defaults.ts.injectionRegistrations, (doc) => doc.injectionRegistrations),
|
|
718
744
|
lifecycleContracts: at("ts.lifecycle_contracts", defaults.ts.lifecycleContracts, (doc) => doc.lifecycleContracts),
|
|
719
745
|
serializers: at("ts.serializers", defaults.ts.serializers, (doc) => doc.serializers),
|
package/dist/src/roots.js
CHANGED
|
@@ -8,12 +8,20 @@
|
|
|
8
8
|
* under reachability and still a candidate under reference counting.
|
|
9
9
|
*/
|
|
10
10
|
import { aliasChains } from "./alias-chain.js";
|
|
11
|
+
import { conventionEntries } from "./conventions.js";
|
|
11
12
|
import { entryPoints } from "./entry-points.js";
|
|
12
13
|
import { globExpression } from "./glob.js";
|
|
13
14
|
import { nodeKey } from "./inventory.js";
|
|
14
15
|
import { byPosition } from "./position.js";
|
|
15
16
|
import { UNANSWERED } from "./query.js";
|
|
16
17
|
import { sourceFilesOf } from "./source-files.js";
|
|
18
|
+
import { typeQueryAliases } from "./type-query-alias.js";
|
|
19
|
+
/** The kind of root each role of a manifest entry makes. */
|
|
20
|
+
const ENTRY_KINDS = {
|
|
21
|
+
import: "manifest-entry",
|
|
22
|
+
run: "manifest-binary",
|
|
23
|
+
script: "script",
|
|
24
|
+
};
|
|
17
25
|
/** Whether a configured string carries either special character. */
|
|
18
26
|
function isPattern(text) {
|
|
19
27
|
return text.includes("*") || text.includes("?");
|
|
@@ -117,18 +125,23 @@ export function roots(project, held, targetRoot, options) {
|
|
|
117
125
|
entered.push({ file, kind, source, exports });
|
|
118
126
|
};
|
|
119
127
|
const published = [];
|
|
120
|
-
const
|
|
121
|
-
entry
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
const entries = [
|
|
129
|
+
...options.manifest.entries.map((entry) => ({
|
|
130
|
+
entry,
|
|
131
|
+
publishes: entry.published &&
|
|
132
|
+
(options.manifest.declaresExports ? entry.member.startsWith("exports") : true),
|
|
133
|
+
})),
|
|
134
|
+
...(options.workspaceEntries ?? []).map((entry) => ({ entry, publishes: entry.published })),
|
|
135
|
+
];
|
|
136
|
+
for (const { entry, publishes } of entries) {
|
|
137
|
+
if (options.publishedAPI && publishes) {
|
|
125
138
|
published.push(...files.named(entry.path));
|
|
126
139
|
}
|
|
127
140
|
}
|
|
128
141
|
const publishedNames = new Set(published.map((file) => file.fileName));
|
|
129
|
-
for (const entry of
|
|
142
|
+
for (const { entry } of entries) {
|
|
130
143
|
for (const file of files.named(entry.path)) {
|
|
131
|
-
const kind = entry.role
|
|
144
|
+
const kind = ENTRY_KINDS[entry.role];
|
|
132
145
|
enter(file, kind, entry.member, !publishedNames.has(file.fileName));
|
|
133
146
|
}
|
|
134
147
|
}
|
|
@@ -140,9 +153,25 @@ export function roots(project, held, targetRoot, options) {
|
|
|
140
153
|
}
|
|
141
154
|
}
|
|
142
155
|
}
|
|
143
|
-
for (const
|
|
156
|
+
for (const { file, row } of options.conventions === undefined
|
|
157
|
+
? []
|
|
158
|
+
: conventionEntries(options.conventions, files)) {
|
|
159
|
+
enter(file, "convention", row, true);
|
|
160
|
+
}
|
|
161
|
+
for (const point of entryPoints(project, files, options.testFiles, options.host, targetRoot, options.parse)) {
|
|
144
162
|
enter(point.file, point.rule, point.source, point.exports);
|
|
145
163
|
}
|
|
164
|
+
for (const file of files.byPath.values()) {
|
|
165
|
+
for (const alias of typeQueryAliases(file)) {
|
|
166
|
+
const source = alias.type.getText().replace(/\s+/gu, " ");
|
|
167
|
+
for (const node of [alias.declaration, ...alias.containers]) {
|
|
168
|
+
const id = declared.get(nodeKey(file, node));
|
|
169
|
+
if (id !== undefined) {
|
|
170
|
+
add(id, "type-query-alias", source);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
146
175
|
const { tables: exportsOf, answered } = exportTables(project, held, [
|
|
147
176
|
...entered.filter((entry) => entry.exports).map((entry) => entry.file),
|
|
148
177
|
...published,
|
package/dist/src/run.js
CHANGED
|
@@ -3,6 +3,7 @@ import { DiscoveryError, renderDiagnostic } from "./discover.js";
|
|
|
3
3
|
import { EMITTERS } from "./findings/emitters.js";
|
|
4
4
|
import { joinPath, resolvePath } from "./paths.js";
|
|
5
5
|
import { readScope, ScopeError, scopeForDir } from "./scope.js";
|
|
6
|
+
import { SetupError, setupLine } from "./setup-failure.js";
|
|
6
7
|
import { openEngine } from "./session.js";
|
|
7
8
|
import { printConfigVerb } from "./verbs/print-config.js";
|
|
8
9
|
import { printProjectsVerb } from "./verbs/print-projects.js";
|
|
@@ -158,7 +159,7 @@ function scopeOf(host, options) {
|
|
|
158
159
|
* `openClient` opens the compiler client; a caller that watches what a run reads
|
|
159
160
|
* supplies its own of either.
|
|
160
161
|
*/
|
|
161
|
-
export function run(args, out, err, host, openClient =
|
|
162
|
+
export function run(args, out, err, host, openClient = openEngine) {
|
|
162
163
|
const editing = sourceEditOption(args);
|
|
163
164
|
if (editing !== undefined) {
|
|
164
165
|
err.write(`deadset-ts: ${editing} is not supported: deadset-ts reports and never edits a source file\n`);
|
|
@@ -193,8 +194,11 @@ function invoke(verb, own, args, streams, openClient) {
|
|
|
193
194
|
scope: () => scopeOf(streams.host, read()),
|
|
194
195
|
option: (name) => read().named.get(name),
|
|
195
196
|
repeated: (name) => read().repeated.get(name) ?? [],
|
|
196
|
-
openClient: (
|
|
197
|
-
opened = openClient(
|
|
197
|
+
openClient: (config) => {
|
|
198
|
+
opened = openClient({
|
|
199
|
+
collectTiming: false,
|
|
200
|
+
components: { extensions: config.ts.componentExtensions, host: streams.host },
|
|
201
|
+
});
|
|
198
202
|
return opened;
|
|
199
203
|
},
|
|
200
204
|
emitters: EMITTERS,
|
|
@@ -219,6 +223,12 @@ function refuse(error, err) {
|
|
|
219
223
|
err.write(`deadset-ts: ${error.message}\n`);
|
|
220
224
|
return EXIT_FAILURE;
|
|
221
225
|
}
|
|
226
|
+
if (error instanceof SetupError) {
|
|
227
|
+
for (const failure of error.failures) {
|
|
228
|
+
err.write(`${setupLine(failure)}\n`);
|
|
229
|
+
}
|
|
230
|
+
return EXIT_FAILURE;
|
|
231
|
+
}
|
|
222
232
|
if (error instanceof ScopeError) {
|
|
223
233
|
err.write(`deadset-ts: ${error.message}\n`);
|
|
224
234
|
return EXIT_FAILURE;
|
package/dist/src/schema.js
CHANGED
package/dist/src/session.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { API, SymbolFlags } from "@typescript/native/unstable/sync";
|
|
2
|
+
import { componentLayer, mapperManifest } from "./component-layer.js";
|
|
2
3
|
import { DiscoveryError } from "./discover.js";
|
|
4
|
+
import { joinPath } from "./paths.js";
|
|
3
5
|
import { queriesOf, UnansweredLog, } from "./query.js";
|
|
4
6
|
/**
|
|
5
7
|
* Opens the compiler client the analysis reads through.
|
|
@@ -9,11 +11,55 @@ import { queriesOf, UnansweredLog, } from "./query.js";
|
|
|
9
11
|
* second call has nothing left to do.
|
|
10
12
|
*/
|
|
11
13
|
export function openEngine(options) {
|
|
12
|
-
const
|
|
14
|
+
const { components } = options;
|
|
15
|
+
let mapper;
|
|
16
|
+
if (components !== undefined && components.extensions.length > 0) {
|
|
17
|
+
const made = components.host.temporaryDirectory();
|
|
18
|
+
try {
|
|
19
|
+
components.host.writeDocument(joinPath(made.path, "package.json"), [
|
|
20
|
+
mapperManifest(components.host.componentMapperCommand()),
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
made.remove();
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
mapper = made;
|
|
28
|
+
}
|
|
29
|
+
const api = new API({
|
|
30
|
+
collectTiming: options.collectTiming,
|
|
31
|
+
...(mapper === undefined ? {} : { runExternalCode: true }),
|
|
32
|
+
});
|
|
13
33
|
let closed = false;
|
|
14
34
|
return {
|
|
15
35
|
parseConfigFile: (file) => api.parseConfigFile(file),
|
|
16
|
-
|
|
36
|
+
parseConfigJson: (json, configFile) => api.parseJsonConfigFileContent(json, { configFileName: configFile }),
|
|
37
|
+
createSnapshot: (openProjects, createPrograms, configurations = new Map()) => api.createSnapshot({
|
|
38
|
+
openProjects: [...openProjects],
|
|
39
|
+
...(createPrograms === undefined ? {} : { createPrograms: [...createPrograms] }),
|
|
40
|
+
...(components === undefined
|
|
41
|
+
? configurations.size === 0
|
|
42
|
+
? {}
|
|
43
|
+
: { fileSystem: { kind: "layer", files: Object.fromEntries(configurations) } }
|
|
44
|
+
: {
|
|
45
|
+
fileSystem: componentLayer(components.host, openProjects, mapper === undefined
|
|
46
|
+
? undefined
|
|
47
|
+
: { extensions: components.extensions, packageDirectory: mapper.path }, configurations),
|
|
48
|
+
}),
|
|
49
|
+
}),
|
|
50
|
+
readsComponents: mapper !== undefined,
|
|
51
|
+
createModuleResolver: (options, entries) => api.createModuleResolver(options, {
|
|
52
|
+
moduleResolutions: { fallback: "resolve", entries: [...entries] },
|
|
53
|
+
}),
|
|
54
|
+
parseSourceFile: (fileName, text) => {
|
|
55
|
+
const retained = api.createSourceFile(fileName, text);
|
|
56
|
+
try {
|
|
57
|
+
return retained.sourceFile;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
retained.dispose();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
17
63
|
batch: (questions) => api.batch(...questions),
|
|
18
64
|
ask: (_accessor, _locations, question) => question(),
|
|
19
65
|
getTimingInfo: () => api.getTimingInfo(),
|
|
@@ -22,10 +68,35 @@ export function openEngine(options) {
|
|
|
22
68
|
return;
|
|
23
69
|
}
|
|
24
70
|
closed = true;
|
|
25
|
-
|
|
71
|
+
try {
|
|
72
|
+
api.close();
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
mapper?.remove();
|
|
76
|
+
}
|
|
26
77
|
},
|
|
27
78
|
};
|
|
28
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The source files of one program that `own` admits, in the program's order, none of
|
|
82
|
+
* them one of the compiler's default libraries.
|
|
83
|
+
*/
|
|
84
|
+
function filesOwned(program, own) {
|
|
85
|
+
return (program
|
|
86
|
+
.getSourceFileNames()
|
|
87
|
+
// The metadata decides which files belong to the target, and it is read by
|
|
88
|
+
// name. Fetching each file first and filtering afterwards costs one round
|
|
89
|
+
// trip per library file the answer then discards, and a program holds far
|
|
90
|
+
// more library files than target files.
|
|
91
|
+
.filter((name) => {
|
|
92
|
+
const metadata = program.getSourceFileMetadata(name);
|
|
93
|
+
return (metadata !== undefined &&
|
|
94
|
+
!metadata.isDefaultLibrary &&
|
|
95
|
+
own(name, metadata.isFromExternalLibrary));
|
|
96
|
+
})
|
|
97
|
+
.map((name) => program.getSourceFile(name))
|
|
98
|
+
.filter((file) => file !== undefined));
|
|
99
|
+
}
|
|
29
100
|
function viewOf(project, configFile, engine, log) {
|
|
30
101
|
const queries = queriesOf(project.checker, configFile, (questions) => engine.batch(questions), log, (accessor, locations, question) => engine.ask(accessor, locations, question));
|
|
31
102
|
// The snapshot is never updated, so a project's own file set is the same answer
|
|
@@ -35,23 +106,13 @@ function viewOf(project, configFile, engine, log) {
|
|
|
35
106
|
let ownFiles;
|
|
36
107
|
let ownKeys;
|
|
37
108
|
const ownSourceFiles = () => {
|
|
38
|
-
ownFiles ??= project.program
|
|
39
|
-
.getSourceFileNames()
|
|
40
|
-
// The metadata decides which files belong to the target, and it is read by
|
|
41
|
-
// name. Fetching each file first and filtering afterwards costs one round
|
|
42
|
-
// trip per library file the answer then discards, and a program holds far
|
|
43
|
-
// more library files than target files.
|
|
44
|
-
.filter((name) => {
|
|
45
|
-
const metadata = project.program.getSourceFileMetadata(name);
|
|
46
|
-
return (metadata !== undefined && !metadata.isDefaultLibrary && !metadata.isFromExternalLibrary);
|
|
47
|
-
})
|
|
48
|
-
.map((name) => project.program.getSourceFile(name))
|
|
49
|
-
.filter((file) => file !== undefined);
|
|
109
|
+
ownFiles ??= filesOwned(project.program, (_name, fromExternalLibrary) => !fromExternalLibrary);
|
|
50
110
|
return ownFiles;
|
|
51
111
|
};
|
|
52
112
|
return {
|
|
53
113
|
configFile,
|
|
54
114
|
program: project.program,
|
|
115
|
+
rootFiles: project.parsedCommandLine.fileNames,
|
|
55
116
|
queries,
|
|
56
117
|
ownSourceFiles,
|
|
57
118
|
ownPaths: () => {
|
|
@@ -101,6 +162,26 @@ export function narrowedTo(project, keep) {
|
|
|
101
162
|
},
|
|
102
163
|
};
|
|
103
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* The same project, its own source files the ones `own` admits among every file its
|
|
167
|
+
* program holds, given whether the compiler reached the file as a library's.
|
|
168
|
+
*/
|
|
169
|
+
export function ownedAs(project, own) {
|
|
170
|
+
let owned;
|
|
171
|
+
let keys;
|
|
172
|
+
const ownSourceFiles = () => {
|
|
173
|
+
owned ??= filesOwned(project.program, own);
|
|
174
|
+
return owned;
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
...project,
|
|
178
|
+
ownSourceFiles,
|
|
179
|
+
ownPaths: () => {
|
|
180
|
+
keys ??= new Set(ownSourceFiles().map((file) => file.path));
|
|
181
|
+
return keys;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
104
185
|
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
105
186
|
export function diagnosticsOf(project) {
|
|
106
187
|
return {
|
|
@@ -119,28 +200,54 @@ export function diagnosticsOf(project) {
|
|
|
119
200
|
};
|
|
120
201
|
}
|
|
121
202
|
/**
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* The snapshot is opened once with every configuration, so each becomes a project
|
|
126
|
-
* with its own program and checker; it is never updated, because the analysis
|
|
127
|
-
* reads and the tree does not change under it. The order the snapshot lists its
|
|
128
|
-
* projects in is the compiler's own, so each named configuration is looked up in it
|
|
129
|
-
* instead. A configuration the snapshot opened no project for ends the run under
|
|
130
|
-
* `"refuse"` and is named in the result under `"record"`. The snapshot is disposed once
|
|
131
|
-
* the last project has been visited, and the client is released after it, whether the
|
|
132
|
-
* visit completed or threw.
|
|
203
|
+
* The projects one snapshot holds for the configurations named, by configuration file. A
|
|
204
|
+
* project opened from a written configuration is the project of the configuration
|
|
205
|
+
* `written` maps it to.
|
|
133
206
|
*/
|
|
134
|
-
|
|
207
|
+
function projectsOf(snapshot, built, written) {
|
|
208
|
+
const opened = new Map();
|
|
209
|
+
for (const project of snapshot.getProjects()) {
|
|
210
|
+
if (project.configFileName !== undefined) {
|
|
211
|
+
opened.set(written.get(project.configFileName) ?? project.configFileName, project);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
(snapshot.operation.createdPrograms ?? []).forEach((program, at) => {
|
|
215
|
+
const project = snapshot.getProject(program.id);
|
|
216
|
+
const configFile = built[at];
|
|
217
|
+
if (project !== undefined && configFile !== undefined) {
|
|
218
|
+
opened.set(configFile, project);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return opened;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Runs `visit` over every project the compiler configurations name, inside one never updated
|
|
225
|
+
* snapshot of one client, and answers in their order. A configuration `programs` describes is
|
|
226
|
+
* built as that program, or opened from the configuration it writes, instead of as a project.
|
|
227
|
+
* One the snapshot holds nothing for ends the run under `"refuse"` and is named in the result
|
|
228
|
+
* under `"record"`. The snapshot is disposed after the last visit and the client released
|
|
229
|
+
* after it, whether the visit completed or threw.
|
|
230
|
+
*/
|
|
231
|
+
export function runSession(engine, configFiles, visit, unopened = "refuse", programs = new Map()) {
|
|
135
232
|
try {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
233
|
+
const built = [];
|
|
234
|
+
const params = [];
|
|
235
|
+
const written = new Map();
|
|
236
|
+
const texts = new Map();
|
|
237
|
+
for (const configFile of configFiles) {
|
|
238
|
+
const program = programs.get(configFile);
|
|
239
|
+
if (program?.kind === "program") {
|
|
240
|
+
built.push(configFile);
|
|
241
|
+
params.push(program.params);
|
|
242
|
+
}
|
|
243
|
+
else if (program?.kind === "configuration") {
|
|
244
|
+
written.set(program.file, configFile);
|
|
245
|
+
texts.set(program.file, program.text);
|
|
143
246
|
}
|
|
247
|
+
}
|
|
248
|
+
const snapshot = engine.createSnapshot([...configFiles.filter((configFile) => !programs.has(configFile)), ...written.keys()], params.length === 0 ? undefined : params, texts);
|
|
249
|
+
try {
|
|
250
|
+
const opened = projectsOf(snapshot, built, written);
|
|
144
251
|
const log = new UnansweredLog();
|
|
145
252
|
const projects = [];
|
|
146
253
|
const missing = [];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A setup failure: a file or a component the analysis needs and the project does not
|
|
3
|
+
* provide. Each one names what is missing and the fix, on one line of the error stream
|
|
4
|
+
* that starts with its class.
|
|
5
|
+
*/
|
|
6
|
+
/** The failure as the one line of the error stream that names it. */
|
|
7
|
+
export function setupLine(failure) {
|
|
8
|
+
return `setup failure: ${failure.setupClass}: ${failure.detail}`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A run that met a setup failure where none may be dropped: in a configuration the
|
|
12
|
+
* configuration document or the scope declares, in a declared consumer, or in every
|
|
13
|
+
* configuration discovery derived. It ends the run with the failure code and no finding
|
|
14
|
+
* list.
|
|
15
|
+
*/
|
|
16
|
+
export class SetupError extends Error {
|
|
17
|
+
failures;
|
|
18
|
+
constructor(failures) {
|
|
19
|
+
super(failures.map(setupLine).join("\n"));
|
|
20
|
+
this.name = "SetupError";
|
|
21
|
+
this.failures = failures;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/src/source-files.js
CHANGED
|
@@ -3,45 +3,10 @@
|
|
|
3
3
|
* in a document or a string names one: as the file itself, or as a file the
|
|
4
4
|
* compiler emits from it.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const EMITTED = [
|
|
9
|
-
{ emitted: ".d.ts", sources: [".ts", ".tsx"] },
|
|
10
|
-
{ emitted: ".d.mts", sources: [".mts"] },
|
|
11
|
-
{ emitted: ".d.cts", sources: [".cts"] },
|
|
12
|
-
{ emitted: ".js", sources: [".ts", ".tsx"] },
|
|
13
|
-
{ emitted: ".jsx", sources: [".tsx"] },
|
|
14
|
-
{ emitted: ".mjs", sources: [".mts"] },
|
|
15
|
-
{ emitted: ".cjs", sources: [".cts"] },
|
|
16
|
-
];
|
|
6
|
+
import { commonDirectory, emittedFrom } from "./emit-map.js";
|
|
7
|
+
import { dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
17
8
|
/** The one character a subpath pattern of a manifest's `exports` holds. */
|
|
18
9
|
const SUBPATH_WILDCARD = "*";
|
|
19
|
-
/** One path with its extension replaced, where it carries the one named. */
|
|
20
|
-
function reExtended(path, emitted, source) {
|
|
21
|
-
return path.endsWith(emitted) ? path.slice(0, path.length - emitted.length) + source : undefined;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* The longest directory every path shares, which is the directory the compiler
|
|
25
|
-
* emits from where the configuration declares no root directory.
|
|
26
|
-
*/
|
|
27
|
-
function commonDirectory(paths) {
|
|
28
|
-
const first = paths[0];
|
|
29
|
-
if (first === undefined) {
|
|
30
|
-
return "";
|
|
31
|
-
}
|
|
32
|
-
let common = dirnamePath(first).split("/");
|
|
33
|
-
for (const path of paths.slice(1)) {
|
|
34
|
-
const segments = dirnamePath(path).split("/");
|
|
35
|
-
let shared = 0;
|
|
36
|
-
while (shared < common.length &&
|
|
37
|
-
shared < segments.length &&
|
|
38
|
-
common[shared] === segments[shared]) {
|
|
39
|
-
shared += 1;
|
|
40
|
-
}
|
|
41
|
-
common = common.slice(0, shared);
|
|
42
|
-
}
|
|
43
|
-
return common.join("/");
|
|
44
|
-
}
|
|
45
10
|
/**
|
|
46
11
|
* Whether one path is what a subpath pattern stands for: the text before the
|
|
47
12
|
* wildcard, any run of characters including the solidus, then the text after it.
|
|
@@ -77,31 +42,9 @@ export function sourceFilesOf(project, targetRoot) {
|
|
|
77
42
|
const outDir = at(options.outDir);
|
|
78
43
|
const declarationDir = at(options.declarationDir);
|
|
79
44
|
const sourceDir = at(options.rootDir) ?? commonDirectory([...byName.keys()]);
|
|
80
|
-
|
|
81
|
-
const fromEmit = (path, emitted) => {
|
|
82
|
-
const emitDir = emitted.startsWith(".d.") ? (declarationDir ?? outDir) : outDir;
|
|
83
|
-
if (emitDir === undefined) {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
const below = relativePath(emitDir, path);
|
|
87
|
-
return below === undefined ? undefined : joinPath(sourceDir, below);
|
|
88
|
-
};
|
|
45
|
+
const layout = { outDir, declarationDir, sourceDirs: [sourceDir] };
|
|
89
46
|
/** The source paths one written path may stand for, the path itself first. */
|
|
90
|
-
const candidates = (path) =>
|
|
91
|
-
const found = [path];
|
|
92
|
-
for (const { emitted, sources } of EMITTED) {
|
|
93
|
-
const mapped = fromEmit(path, emitted);
|
|
94
|
-
for (const source of sources) {
|
|
95
|
-
for (const from of [path, mapped]) {
|
|
96
|
-
const candidate = from === undefined ? undefined : reExtended(from, emitted, source);
|
|
97
|
-
if (candidate !== undefined) {
|
|
98
|
-
found.push(candidate);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return found;
|
|
104
|
-
};
|
|
47
|
+
const candidates = (path) => [path, ...emittedFrom(path, layout)];
|
|
105
48
|
return {
|
|
106
49
|
byPath,
|
|
107
50
|
byName,
|
package/dist/src/stores.js
CHANGED
|
@@ -26,6 +26,9 @@ export function accessorsOf(project, held) {
|
|
|
26
26
|
export function storesOf(per, exempt, mode) {
|
|
27
27
|
return {
|
|
28
28
|
references: per.flatMap((one) => one.references.filter((reference) => !mode.production || !reference.test)),
|
|
29
|
+
readInTests: new Set(per.flatMap((one) => one.references
|
|
30
|
+
.filter((reference) => reference.test && reference.use !== "write")
|
|
31
|
+
.map((reference) => reference.to))),
|
|
29
32
|
accessors: new Set(per.flatMap((one) => one.accessors)),
|
|
30
33
|
exempt: new Set(exempt.map((record) => record.id)),
|
|
31
34
|
};
|
package/dist/src/sweep.js
CHANGED
|
@@ -153,13 +153,15 @@ function reachable(graph, held, mode) {
|
|
|
153
153
|
function admitTestsOfDeadCode(graph, dead, held) {
|
|
154
154
|
const admitted = graph.symbols.map(() => false);
|
|
155
155
|
graph.symbols.forEach((_symbol, at) => {
|
|
156
|
-
|
|
156
|
+
const testCode = graph.test[at] === true || graph.support[at] === true;
|
|
157
|
+
if (!testCode || graph.subject[at] !== true || held[at] === true) {
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
159
160
|
let targets = 0;
|
|
160
161
|
let live = 0;
|
|
161
162
|
for (const edge of graph.out[at] ?? []) {
|
|
162
|
-
|
|
163
|
+
const target = graph.test[edge.to] === true || graph.support[edge.to] === true;
|
|
164
|
+
if (target || graph.subject[edge.to] !== true) {
|
|
163
165
|
continue;
|
|
164
166
|
}
|
|
165
167
|
targets += 1;
|