@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/analysis.js +106 -10
- package/dist/src/augmentations.js +21 -8
- package/dist/src/automatic-types.js +189 -0
- package/dist/src/base-url.js +18 -0
- package/dist/src/components.js +7 -6
- package/dist/src/config.js +1 -0
- package/dist/src/configuration-files.js +50 -12
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +75 -0
- package/dist/src/conventions.js +101 -5
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +183 -28
- package/dist/src/discover.js +132 -11
- package/dist/src/entry-point-gaps.js +7 -10
- package/dist/src/entry-points.js +112 -11
- package/dist/src/findings/completion.js +116 -21
- package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +44 -13
- package/dist/src/findings/non-code-artifacts.js +66 -33
- package/dist/src/findings/unused-declarations.js +21 -1
- package/dist/src/findings/visibility-narrowing.js +22 -3
- package/dist/src/generated-file.js +11 -0
- package/dist/src/git-ignore.js +86 -0
- package/dist/src/graph.js +4 -1
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +297 -45
- package/dist/src/inventory.js +52 -8
- package/dist/src/kinds.js +13 -13
- package/dist/src/matrix.js +18 -9
- package/dist/src/mdx-entries.js +197 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/overrides.js +41 -10
- package/dist/src/print.js +1 -0
- package/dist/src/ref.js +10 -0
- package/dist/src/references.js +4 -1
- package/dist/src/report.js +4 -2
- package/dist/src/resolve.js +2 -0
- package/dist/src/roots.js +110 -38
- package/dist/src/schema.js +1 -0
- package/dist/src/serialization-contract.js +54 -8
- package/dist/src/session.js +7 -0
- package/dist/src/source-files.js +39 -14
- package/dist/src/suppress-file.js +10 -2
- package/dist/src/suppress-inline.js +18 -2
- package/dist/src/suppress.js +7 -5
- package/dist/src/sweep.js +27 -10
- package/dist/src/symbol-keys.js +103 -2
- package/dist/src/test-support.js +121 -17
- package/dist/src/type-errors.js +34 -7
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/verbs/analyze.js +16 -0
- package/dist/src/verbs/explain.js +4 -2
- package/dist/src/version.js +1 -1
- package/dist/src/workflow-steps.js +119 -22
- package/dist/src/workspace-programs.js +7 -3
- package/dist/src/workspace-resolution.js +53 -10
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/analysis.ts +143 -10
- package/src/augmentations.ts +21 -10
- package/src/automatic-types.ts +246 -0
- package/src/base-url.ts +24 -0
- package/src/components.ts +7 -6
- package/src/config.ts +3 -0
- package/src/configuration-files.ts +66 -12
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +103 -0
- package/src/conventions.ts +121 -7
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +247 -41
- package/src/discover.ts +156 -13
- package/src/entry-point-gaps.ts +8 -11
- package/src/entry-points.ts +133 -11
- package/src/finding.ts +10 -0
- package/src/findings/completion.ts +147 -24
- package/src/findings/dependencies-and-module-machinery.ts +90 -28
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +56 -14
- package/src/findings/non-code-artifacts.ts +81 -41
- package/src/findings/unused-declarations.ts +25 -1
- package/src/findings/visibility-narrowing.ts +32 -3
- package/src/generated-file.ts +16 -0
- package/src/git-ignore.ts +98 -0
- package/src/graph.ts +6 -0
- package/src/host.ts +5 -9
- package/src/import-bindings.ts +128 -0
- package/src/indexed-access.ts +152 -30
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +394 -59
- package/src/inventory.ts +71 -7
- package/src/kinds.ts +13 -13
- package/src/matrix.ts +32 -14
- package/src/mdx-entries.ts +241 -0
- package/src/module-declarations.ts +11 -13
- package/src/overrides.ts +51 -12
- package/src/print.ts +1 -0
- package/src/ref.ts +12 -0
- package/src/references.ts +10 -1
- package/src/report.ts +16 -3
- package/src/resolve.ts +3 -0
- package/src/roots.ts +163 -45
- package/src/schema.ts +1 -0
- package/src/serialization-contract.ts +55 -4
- package/src/session.ts +7 -0
- package/src/source-files.ts +66 -16
- package/src/suppress-file.ts +12 -1
- package/src/suppress-inline.ts +18 -1
- package/src/suppress.ts +9 -5
- package/src/sweep.ts +32 -11
- package/src/symbol-keys.ts +129 -2
- package/src/test-support.ts +149 -16
- package/src/type-errors.ts +38 -6
- package/src/type-parameter-reads.ts +227 -0
- package/src/verbs/analyze.ts +22 -0
- package/src/verbs/explain.ts +5 -2
- package/src/version.ts +1 -1
- package/src/workflow-steps.ts +127 -22
- package/src/workspace-programs.ts +7 -3
- package/src/workspace-resolution.ts +62 -10
package/dist/src/report.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* renders this document, so no two renderings disagree about which findings exist.
|
|
5
5
|
*/
|
|
6
6
|
/** The version of the report schema this analyzer writes a report to. */
|
|
7
|
-
const SCHEMA_VERSION = "
|
|
7
|
+
const SCHEMA_VERSION = "9.0.0";
|
|
8
8
|
/** Every report schema version this analyzer reads: the one the Contract it implements admits. */
|
|
9
9
|
export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
|
|
10
10
|
/** The name this analyzer writes every document under and mints every identifier with. */
|
|
@@ -247,7 +247,9 @@ export function buildReport(input) {
|
|
|
247
247
|
]
|
|
248
248
|
.sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
|
|
249
249
|
.map(([, skip]) => skip),
|
|
250
|
-
notes: [],
|
|
250
|
+
notes: [...new Map((input.notes ?? []).map((one) => [JSON.stringify(one), one])).entries()]
|
|
251
|
+
.sort(([a, x], [b, y]) => compare(x.kind, y.kind) || compare(x.path, y.path) || compare(a, b))
|
|
252
|
+
.map(([, note]) => note),
|
|
251
253
|
unanswered_questions: [...input.unanswered]
|
|
252
254
|
.sort((a, b) => compare(a.configuration, b.configuration) ||
|
|
253
255
|
compare(JSON.stringify(a), JSON.stringify(b)))
|
package/dist/src/resolve.js
CHANGED
|
@@ -567,6 +567,7 @@ function readDoc(value, label) {
|
|
|
567
567
|
injectionRegistrations: readDeclarations(ts, "injection_registrations", "ts.injection_registrations", label),
|
|
568
568
|
lifecycleContracts: readLifecycleContracts(ts, label),
|
|
569
569
|
serializers: readDeclarations(ts, "serializers", "ts.serializers", label),
|
|
570
|
+
rootsComplete: readBoolean(ts, "roots_complete", "ts.roots_complete", label),
|
|
570
571
|
};
|
|
571
572
|
}
|
|
572
573
|
/** Parses one document's text and walks it against the closed key list. */
|
|
@@ -776,6 +777,7 @@ function settleAll(inputs) {
|
|
|
776
777
|
injectionRegistrations: at("ts.injection_registrations", defaults.ts.injectionRegistrations, (doc) => doc.injectionRegistrations),
|
|
777
778
|
lifecycleContracts: at("ts.lifecycle_contracts", defaults.ts.lifecycleContracts, (doc) => doc.lifecycleContracts),
|
|
778
779
|
serializers: at("ts.serializers", defaults.ts.serializers, (doc) => doc.serializers),
|
|
780
|
+
rootsComplete: at("ts.roots_complete", defaults.ts.rootsComplete, (doc) => doc.rootsComplete),
|
|
779
781
|
},
|
|
780
782
|
};
|
|
781
783
|
return { config, provenance };
|
package/dist/src/roots.js
CHANGED
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, } from "@typescript/native/ast";
|
|
11
11
|
import { aliasChains } from "./alias-chain.js";
|
|
12
|
+
import { filesNamedBy, outsideChain, relativeSpecifiers, } from "./configuration-files.js";
|
|
12
13
|
import { conventionEntries } from "./conventions.js";
|
|
13
14
|
import { entryPoints } from "./entry-points.js";
|
|
14
15
|
import { globExpression } from "./glob.js";
|
|
15
16
|
import { nodeKey } from "./inventory.js";
|
|
16
|
-
import { relativePath, resolvePath } from "./paths.js";
|
|
17
|
+
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
|
|
17
18
|
import { byPosition } from "./position.js";
|
|
18
19
|
import { UNANSWERED } from "./query.js";
|
|
19
20
|
import { sourceFilesOf } from "./source-files.js";
|
|
@@ -43,11 +44,22 @@ const READ_BACK_RULES = new Set([
|
|
|
43
44
|
const OUTPUT_EXTENSION = /\.(?:d\.ts|d\.mts|d\.cts|js|mjs|cjs|jsx)$/u;
|
|
44
45
|
/** The extension a source file's name drops. */
|
|
45
46
|
const SOURCE_EXTENSION = /\.(?:d\.)?[cm]?[jt]sx?$/u;
|
|
47
|
+
/** Whether no `package.json` sits between one file and the manifest's directory, which holds it. */
|
|
48
|
+
function ofPackage(host, dir, file) {
|
|
49
|
+
for (let at = dirnamePath(file); at !== dir; at = dirnamePath(at)) {
|
|
50
|
+
if (relativePath(dir, at) === undefined ||
|
|
51
|
+
host.kindOf(joinPath(at, "package.json")) === "file") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
46
57
|
/**
|
|
47
58
|
* The rooted files one manifest target reads back to by name: its path below the
|
|
48
59
|
* manifest's directory, past its first directory where it names one, without its output
|
|
49
|
-
* extension, is the name, and a rooted file
|
|
50
|
-
* without its source extension, ends with that name at a
|
|
60
|
+
* extension, is the name, and a rooted file of the manifest's own package is one whose
|
|
61
|
+
* path below the same directory, without its source extension, ends with that name at a
|
|
62
|
+
* directory boundary.
|
|
51
63
|
*/
|
|
52
64
|
function readBackByName(host, entry, rooted) {
|
|
53
65
|
const dir = resolvePath(host.workingDirectory(), entry.dir);
|
|
@@ -63,12 +75,41 @@ function readBackByName(host, entry, rooted) {
|
|
|
63
75
|
const found = new Map();
|
|
64
76
|
for (const file of rooted) {
|
|
65
77
|
const path = relativePath(dir, file.fileName)?.replace(SOURCE_EXTENSION, "");
|
|
66
|
-
if (path !== undefined &&
|
|
78
|
+
if (path !== undefined &&
|
|
79
|
+
(path === name || path.endsWith(`/${name}`)) &&
|
|
80
|
+
ofPackage(host, dir, file.fileName)) {
|
|
67
81
|
found.set(file.fileName, file);
|
|
68
82
|
}
|
|
69
83
|
}
|
|
70
84
|
return [...found.values()];
|
|
71
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The program files a manifest target no program holds reaches: the target is a file
|
|
88
|
+
* read as a configuration file outside the program is, and each file of the chain it
|
|
89
|
+
* starts loads the program files its relative specifiers name, read through the emit
|
|
90
|
+
* mapping, as a command's shim loads the compiled entry it runs.
|
|
91
|
+
*/
|
|
92
|
+
function throughOutside(options, targetRoot, files, entry) {
|
|
93
|
+
const cwd = options.host.workingDirectory();
|
|
94
|
+
const path = resolvePath(cwd, entry.path);
|
|
95
|
+
if (options.parse === undefined ||
|
|
96
|
+
options.runFiles?.has(path) === true ||
|
|
97
|
+
options.host.kindOf(path) !== "file") {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const chain = outsideChain(options.host, targetRoot, files, options.parse, [
|
|
101
|
+
{ path, manifestDir: resolvePath(cwd, entry.dir) },
|
|
102
|
+
]);
|
|
103
|
+
const found = new Map();
|
|
104
|
+
for (const { file } of chain) {
|
|
105
|
+
for (const specifier of relativeSpecifiers(file)) {
|
|
106
|
+
for (const named of filesNamedBy(files, dirnamePath(file.fileName), specifier)) {
|
|
107
|
+
found.set(named.fileName, named);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [...found.values()];
|
|
112
|
+
}
|
|
72
113
|
/** Whether a configured string carries either special character. */
|
|
73
114
|
function isPattern(text) {
|
|
74
115
|
return text.includes("*") || text.includes("?");
|
|
@@ -168,8 +209,8 @@ export function roots(project, held, targetRoot, options) {
|
|
|
168
209
|
found.set([id, kind, source].join("\u0000"), { id, kind, source });
|
|
169
210
|
};
|
|
170
211
|
const entered = [];
|
|
171
|
-
const enter = (file, kind, source, exports) => {
|
|
172
|
-
entered.push({ file, kind, source, exports });
|
|
212
|
+
const enter = (file, kind, source, exports, names) => {
|
|
213
|
+
entered.push({ file, kind, source, exports, ...(names === undefined ? {} : { names }) });
|
|
173
214
|
};
|
|
174
215
|
const points = entryPoints(project, files, options.testFiles, options.host, targetRoot, options.parse, options.conventions?.selected);
|
|
175
216
|
const published = [];
|
|
@@ -191,11 +232,28 @@ export function roots(project, held, targetRoot, options) {
|
|
|
191
232
|
...scripted,
|
|
192
233
|
...points.filter((point) => READ_BACK_RULES.has(point.rule)).map((point) => point.file),
|
|
193
234
|
];
|
|
235
|
+
const entryFilesOf = new Map();
|
|
194
236
|
const entryFiles = (entry) => {
|
|
237
|
+
const known = entryFilesOf.get(entry);
|
|
238
|
+
if (known !== undefined) {
|
|
239
|
+
return known;
|
|
240
|
+
}
|
|
241
|
+
const found = readEntryFiles(entry);
|
|
242
|
+
entryFilesOf.set(entry, found);
|
|
243
|
+
return found;
|
|
244
|
+
};
|
|
245
|
+
const readEntryFiles = (entry) => {
|
|
195
246
|
const named = files.named(entry.path);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
247
|
+
if (named.length > 0 || entry.role === "script") {
|
|
248
|
+
return named;
|
|
249
|
+
}
|
|
250
|
+
const cwd = options.host.workingDirectory();
|
|
251
|
+
const shipped = files.namedAsOutput(resolvePath(cwd, entry.dir), resolvePath(cwd, entry.path));
|
|
252
|
+
if (shipped.length > 0) {
|
|
253
|
+
return shipped;
|
|
254
|
+
}
|
|
255
|
+
const byName = readBackByName(options.host, entry, readBackTo);
|
|
256
|
+
return byName.length > 0 ? byName : throughOutside(options, targetRoot, files, entry);
|
|
199
257
|
};
|
|
200
258
|
for (const { entry, publishes } of entries) {
|
|
201
259
|
if (publishes) {
|
|
@@ -203,27 +261,39 @@ export function roots(project, held, targetRoot, options) {
|
|
|
203
261
|
}
|
|
204
262
|
}
|
|
205
263
|
const publishedNames = new Set(published.map((file) => file.fileName));
|
|
206
|
-
|
|
207
|
-
for (const file of entryFiles(entry)) {
|
|
208
|
-
const kind = ENTRY_KINDS[entry.role];
|
|
209
|
-
enter(file, kind, entry.member, !publishedNames.has(file.fileName));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
264
|
+
const patterned = [];
|
|
212
265
|
for (const pattern of options.entryFiles) {
|
|
213
266
|
const expression = globExpression(pattern);
|
|
214
267
|
for (const [path, file] of files.byPath) {
|
|
215
268
|
if (expression.test(path)) {
|
|
216
|
-
|
|
269
|
+
patterned.push({ file, pattern });
|
|
217
270
|
}
|
|
218
271
|
}
|
|
219
272
|
}
|
|
273
|
+
const unmapped = [];
|
|
274
|
+
const mapped = patterned.map(({ file }) => file);
|
|
275
|
+
for (const { entry } of entries) {
|
|
276
|
+
const named = entryFiles(entry);
|
|
277
|
+
if (named.length === 0 && entry.role !== "script") {
|
|
278
|
+
unmapped.push(entry);
|
|
279
|
+
}
|
|
280
|
+
mapped.push(...named);
|
|
281
|
+
for (const file of named) {
|
|
282
|
+
const kind = ENTRY_KINDS[entry.role];
|
|
283
|
+
enter(file, kind, entry.member, !publishedNames.has(file.fileName));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const unmappedEntries = unmapped.filter((entry) => readBackByName(options.host, entry, mapped).length === 0);
|
|
287
|
+
for (const { file, pattern } of patterned) {
|
|
288
|
+
enter(file, "entry-file", pattern, true);
|
|
289
|
+
}
|
|
220
290
|
for (const { file, row } of options.conventions === undefined
|
|
221
291
|
? []
|
|
222
292
|
: conventionEntries(options.conventions, files)) {
|
|
223
293
|
enter(file, "convention", row, true);
|
|
224
294
|
}
|
|
225
295
|
for (const point of points) {
|
|
226
|
-
enter(point.file, point.rule, point.source, point.exports);
|
|
296
|
+
enter(point.file, point.rule, point.source, point.exports, point.names);
|
|
227
297
|
}
|
|
228
298
|
for (const file of files.byPath.values()) {
|
|
229
299
|
for (const alias of typeQueryAliases(file)) {
|
|
@@ -236,7 +306,7 @@ export function roots(project, held, targetRoot, options) {
|
|
|
236
306
|
}
|
|
237
307
|
}
|
|
238
308
|
}
|
|
239
|
-
const { tables: exportsOf, answered } = exportTables(project, held, [
|
|
309
|
+
const { tables: exportsOf, named, answered, } = exportTables(project, held, [
|
|
240
310
|
...entered.filter((entry) => entry.exports).map((entry) => entry.file),
|
|
241
311
|
...published,
|
|
242
312
|
]);
|
|
@@ -247,8 +317,12 @@ export function roots(project, held, targetRoot, options) {
|
|
|
247
317
|
}
|
|
248
318
|
add(id, entry.kind, entry.source);
|
|
249
319
|
if (entry.exports) {
|
|
250
|
-
|
|
251
|
-
|
|
320
|
+
const byName = named.get(entry.file.fileName);
|
|
321
|
+
const exported = entry.names === undefined || byName === undefined
|
|
322
|
+
? (exportsOf.get(entry.file.fileName) ?? [])
|
|
323
|
+
: entry.names.flatMap((name) => byName.get(name) ?? []);
|
|
324
|
+
for (const one of exported) {
|
|
325
|
+
add(one, entry.kind, entry.source);
|
|
252
326
|
}
|
|
253
327
|
}
|
|
254
328
|
}
|
|
@@ -271,6 +345,7 @@ export function roots(project, held, targetRoot, options) {
|
|
|
271
345
|
liveUnderReachability: ordered,
|
|
272
346
|
unmatched,
|
|
273
347
|
unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
|
|
348
|
+
unmappedEntries,
|
|
274
349
|
};
|
|
275
350
|
}
|
|
276
351
|
/**
|
|
@@ -284,9 +359,10 @@ export function roots(project, held, targetRoot, options) {
|
|
|
284
359
|
function exportTables(project, held, wanted) {
|
|
285
360
|
const unique = [...new Map(wanted.map((file) => [file.fileName, file])).values()];
|
|
286
361
|
const tables = new Map();
|
|
362
|
+
const named = new Map();
|
|
287
363
|
let answered = true;
|
|
288
364
|
if (unique.length === 0) {
|
|
289
|
-
return { tables, answered };
|
|
365
|
+
return { tables, named, answered };
|
|
290
366
|
}
|
|
291
367
|
const modules = project.symbolsAt(unique.map((file) => project.handle(file)));
|
|
292
368
|
const chains = aliasChains(project, held);
|
|
@@ -307,15 +383,18 @@ function exportTables(project, held, wanted) {
|
|
|
307
383
|
return;
|
|
308
384
|
}
|
|
309
385
|
const ids = new Set();
|
|
386
|
+
const byName = new Map();
|
|
310
387
|
for (const exported of table) {
|
|
311
388
|
for (const target of chains.chainOf(exported)) {
|
|
312
389
|
answered &&= !target.guessed;
|
|
313
390
|
ids.add(target.id);
|
|
391
|
+
byName.set(exported.name, [...(byName.get(exported.name) ?? []), target.id]);
|
|
314
392
|
}
|
|
315
393
|
}
|
|
316
394
|
tables.set(file.fileName, [...ids]);
|
|
395
|
+
named.set(file.fileName, byName);
|
|
317
396
|
});
|
|
318
|
-
return { tables, answered };
|
|
397
|
+
return { tables, named, answered };
|
|
319
398
|
}
|
|
320
399
|
/** A release tag of a documentation comment: `@` and its name, between white space or the ends. */
|
|
321
400
|
const RELEASE_TAG = /(?:^|\s)@(?:public|beta|alpha)(?=\s|$)/u;
|
|
@@ -370,11 +449,13 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
|
|
|
370
449
|
const nodes = declarationNodes(held, byPath);
|
|
371
450
|
const chains = aliasChains(project, held);
|
|
372
451
|
const reached = new Set();
|
|
452
|
+
const entered = new Set();
|
|
373
453
|
const guessed = new Set();
|
|
374
454
|
const pending = [];
|
|
375
455
|
const reach = (id) => {
|
|
376
|
-
|
|
377
|
-
|
|
456
|
+
const file = byId.get(id)?.kind === "file";
|
|
457
|
+
if (!(file ? entered : reached).has(id)) {
|
|
458
|
+
(file ? entered : reached).add(id);
|
|
378
459
|
pending.push(id);
|
|
379
460
|
}
|
|
380
461
|
};
|
|
@@ -383,12 +464,7 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
|
|
|
383
464
|
}
|
|
384
465
|
for (const file of published) {
|
|
385
466
|
for (const id of exportsOf.get(file.fileName) ?? []) {
|
|
386
|
-
|
|
387
|
-
pending.push(id);
|
|
388
|
-
}
|
|
389
|
-
else {
|
|
390
|
-
reach(id);
|
|
391
|
-
}
|
|
467
|
+
reach(id);
|
|
392
468
|
}
|
|
393
469
|
}
|
|
394
470
|
while (pending.length > 0) {
|
|
@@ -411,8 +487,7 @@ function publishedAPI(project, published, exportsOf, held, byPath, tagged) {
|
|
|
411
487
|
});
|
|
412
488
|
for (const symbol of inferredTypeSymbols(project, round.flatMap((id) => {
|
|
413
489
|
const symbol = byId.get(id);
|
|
414
|
-
|
|
415
|
-
return node === undefined ? [] : [node];
|
|
490
|
+
return symbol === undefined ? [] : nodes.nodesOf(symbol);
|
|
416
491
|
}))) {
|
|
417
492
|
for (const target of chains.chainOf(symbol)) {
|
|
418
493
|
reach(target.id);
|
|
@@ -449,9 +524,7 @@ function declarationNodes(held, byPath) {
|
|
|
449
524
|
const id = held.declarations.get(nodeKey(file, node));
|
|
450
525
|
if (id !== undefined) {
|
|
451
526
|
found.nodes.add(node);
|
|
452
|
-
|
|
453
|
-
found.byId.set(id, node);
|
|
454
|
-
}
|
|
527
|
+
found.byId.set(id, [...(found.byId.get(id) ?? []), node]);
|
|
455
528
|
}
|
|
456
529
|
node.forEachChild(visit);
|
|
457
530
|
};
|
|
@@ -463,10 +536,9 @@ function declarationNodes(held, byPath) {
|
|
|
463
536
|
return {
|
|
464
537
|
typeNamesOf: (symbol) => {
|
|
465
538
|
const { byId, nodes } = nodesIn(symbol.position.path);
|
|
466
|
-
|
|
467
|
-
return declaration === undefined ? [] : typeNamesOf(declaration, nodes);
|
|
539
|
+
return (byId.get(symbol.id) ?? []).flatMap((declaration) => typeNamesOf(declaration, nodes));
|
|
468
540
|
},
|
|
469
|
-
|
|
541
|
+
nodesOf: (symbol) => nodesIn(symbol.position.path).byId.get(symbol.id) ?? [],
|
|
470
542
|
};
|
|
471
543
|
}
|
|
472
544
|
/** How deep the type arguments and constituents of an inferred type are followed. */
|
package/dist/src/schema.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The serialization-contract class: the data members and `toJSON` of every type a value's
|
|
3
3
|
* static type reaches, where the value reaches `JSON.stringify` or a call `ts.serializers`
|
|
4
|
-
* names, and `toString` too where it reaches an
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* names, and `toString` too where it reaches an erased parameter of a function outside the
|
|
5
|
+
* analysis, or an erased property of a type the program does not declare. A function of
|
|
6
|
+
* the project handing such a parameter to a destination is one, to a fixpoint, and a
|
|
7
|
+
* function invoked through `call`, `apply` or `bind` is the callee.
|
|
8
8
|
*/
|
|
9
9
|
import { isArrayLiteralExpression, isArrowFunction, isAsExpression, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyDeclaration, isSatisfiesExpression, isSetAccessorDeclaration, isSpreadElement, isStringLiteral, isTypeLiteralNode, isVariableDeclaration, ModifierFlags, NodeFlags, SyntaxKind, } from "@typescript/native/ast";
|
|
10
10
|
import { SignatureKind, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
|
|
@@ -25,6 +25,8 @@ const CONVERSIONS = new Set(["toJSON", "toString"]);
|
|
|
25
25
|
const SERIALIZED_CONVERSION = "toJSON";
|
|
26
26
|
/** The parameter types that keep nothing of the value they are given. */
|
|
27
27
|
const ERASED = TypeFlags.Any | TypeFlags.Unknown;
|
|
28
|
+
/** How deep a callee's result is searched for a type parameter: `Promise<Box<T>>` is two levels. */
|
|
29
|
+
const MENTION_DEPTH = 3;
|
|
28
30
|
/** The wider of two reaches, where either may be absent. */
|
|
29
31
|
function wider(a, b) {
|
|
30
32
|
return a === "conversions" || b === "conversions" ? "conversions" : (a ?? b);
|
|
@@ -381,6 +383,28 @@ function destinations(input, chains, walked) {
|
|
|
381
383
|
});
|
|
382
384
|
return containing.length === 0 ? target.declarations : containing;
|
|
383
385
|
};
|
|
386
|
+
/** Whether a type names a type parameter, through constituents, type arguments and call signatures. */
|
|
387
|
+
const mentions = (type, parameter, depth) => {
|
|
388
|
+
// A conditional type an alias names, `Awaited<T>` among them, carries its arguments
|
|
389
|
+
// only on the alias the client's type object reports.
|
|
390
|
+
const aliased = type
|
|
391
|
+
.aliasTypeArguments;
|
|
392
|
+
if (type.id === parameter.id || aliased?.includes(parameter.id) === true) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
if (depth >= MENTION_DEPTH) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
const inner = [
|
|
399
|
+
...(type.isUnionType() || type.isIntersectionType() ? must(queries.constituents(type)) : []),
|
|
400
|
+
...must(queries.typeArguments(type)),
|
|
401
|
+
...must(queries.signaturesOf(type, SignatureKind.Call)).flatMap((signature) => [
|
|
402
|
+
...signature.parameters.map((_parameter, at) => must(queries.typeAtPosition(signature, at))),
|
|
403
|
+
...[must(queries.returnTypeOf(signature))].filter((one) => one !== undefined),
|
|
404
|
+
]),
|
|
405
|
+
];
|
|
406
|
+
return inner.some((one) => mentions(one, parameter, depth + 1));
|
|
407
|
+
};
|
|
384
408
|
const erasedArguments = new Map();
|
|
385
409
|
const erasedAt = (call, argument) => {
|
|
386
410
|
let indexes = erasedArguments.get(call);
|
|
@@ -394,10 +418,32 @@ function destinations(input, chains, walked) {
|
|
|
394
418
|
? []
|
|
395
419
|
: must(queries.signaturesOf(type, SignatureKind.Call))
|
|
396
420
|
: [resolvedOf(call)].filter((one) => one !== undefined);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
421
|
+
// A type parameter of the callee that nothing constrains and its result does not name
|
|
422
|
+
// keeps nothing of the value either: the instantiated signature names the value's own
|
|
423
|
+
// type there instead, and a result that names it hands the value back.
|
|
424
|
+
const generic = invoked.indirect ? undefined : resolvedOf(call);
|
|
425
|
+
const declared = generic === undefined ? undefined : must(queries.genericOf(generic));
|
|
426
|
+
const unconstrained = (index) => {
|
|
427
|
+
const parameter = declared === undefined ? undefined : must(queries.parameterType(declared, index));
|
|
428
|
+
const parts = parameter === undefined
|
|
429
|
+
? []
|
|
430
|
+
: parameter.isUnionType()
|
|
431
|
+
? must(queries.constituents(parameter))
|
|
432
|
+
: [parameter];
|
|
433
|
+
const result = declared === undefined ? undefined : must(queries.returnTypeOf(declared));
|
|
434
|
+
return parts.some((part) => {
|
|
435
|
+
if (!part.isTypeParameter() || (result !== undefined && mentions(result, part, 0))) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
const constraint = must(queries.constraintOf(part));
|
|
439
|
+
return constraint === undefined || (constraint.flags & ERASED) !== 0;
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
indexes = new Set(invoked.arguments.flatMap((_unused, index) => unconstrained(index) ||
|
|
443
|
+
signatures.some((signature) => {
|
|
444
|
+
const parameter = must(queries.parameterType(signature, index));
|
|
445
|
+
return parameter !== undefined && (parameter.flags & ERASED) !== 0;
|
|
446
|
+
})
|
|
401
447
|
? [index]
|
|
402
448
|
: []));
|
|
403
449
|
erasedArguments.set(call, indexes);
|
package/dist/src/session.js
CHANGED
|
@@ -31,6 +31,7 @@ export function openEngine(options) {
|
|
|
31
31
|
...(mapper === undefined ? {} : { runExternalCode: true }),
|
|
32
32
|
});
|
|
33
33
|
let closed = false;
|
|
34
|
+
const parsed = new Map();
|
|
34
35
|
return {
|
|
35
36
|
parseConfigFile: (file) => api.parseConfigFile(file),
|
|
36
37
|
parseConfigJson: (json, configFile) => api.parseJsonConfigFileContent(json, { configFileName: configFile }),
|
|
@@ -54,8 +55,14 @@ export function openEngine(options) {
|
|
|
54
55
|
moduleResolutions: { fallback: "resolve", entries: [...entries] },
|
|
55
56
|
}),
|
|
56
57
|
parseSourceFile: (fileName, text) => {
|
|
58
|
+
// Every project of a run reads the same files outside its program, so one parse serves all.
|
|
59
|
+
const known = parsed.get(fileName);
|
|
60
|
+
if (known?.text === text) {
|
|
61
|
+
return known.file;
|
|
62
|
+
}
|
|
57
63
|
const retained = api.createSourceFile(fileName, text);
|
|
58
64
|
try {
|
|
65
|
+
parsed.set(fileName, { text, file: retained.sourceFile });
|
|
59
66
|
return retained.sourceFile;
|
|
60
67
|
}
|
|
61
68
|
finally {
|
package/dist/src/source-files.js
CHANGED
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
* compiler emits from it.
|
|
5
5
|
*/
|
|
6
6
|
import { commonDirectory, emittedFrom } from "./emit-map.js";
|
|
7
|
-
import { dirnamePath, relativePath, resolvePath } from "./paths.js";
|
|
7
|
+
import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.js";
|
|
8
8
|
/** The one character a subpath pattern of a manifest's `exports` holds. */
|
|
9
9
|
const SUBPATH_WILDCARD = "*";
|
|
10
|
+
/** The extension a source file's name drops when the project writes it out. */
|
|
11
|
+
const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/u;
|
|
12
|
+
/** The extensions the project may write a source file's output under. */
|
|
13
|
+
const OUTPUT_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".d.ts", ".d.mts", ".d.cts"];
|
|
10
14
|
/**
|
|
11
15
|
* Whether one path is what a subpath pattern stands for: the text before the
|
|
12
16
|
* wildcard, any run of characters including the solidus, then the text after it.
|
|
@@ -17,6 +21,27 @@ function matchesSubpath(pattern, path) {
|
|
|
17
21
|
const after = pattern.slice(at + SUBPATH_WILDCARD.length);
|
|
18
22
|
return (path.length >= before.length + after.length && path.startsWith(before) && path.endsWith(after));
|
|
19
23
|
}
|
|
24
|
+
/** The first own file of the paths given that the project holds, or none. */
|
|
25
|
+
function heldAt(byName, paths) {
|
|
26
|
+
for (const candidate of paths) {
|
|
27
|
+
const file = byName.get(candidate);
|
|
28
|
+
if (file !== undefined) {
|
|
29
|
+
return [file];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
/** Where one project writes its output, and the directory it writes it from, absolute. */
|
|
35
|
+
function layoutOf(project, fileNames) {
|
|
36
|
+
const options = project.program.getCompilerOptions();
|
|
37
|
+
const configDir = dirnamePath(project.configFile);
|
|
38
|
+
const at = (option) => option === undefined ? undefined : resolvePath(configDir, option);
|
|
39
|
+
return {
|
|
40
|
+
outDir: at(options.outDir),
|
|
41
|
+
declarationDir: at(options.declarationDir),
|
|
42
|
+
sourceDir: at(options.rootDir) ?? commonDirectory(fileNames),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
20
45
|
/**
|
|
21
46
|
* The lookups over one project's own files.
|
|
22
47
|
*
|
|
@@ -36,12 +61,7 @@ export function sourceFilesOf(project, targetRoot) {
|
|
|
36
61
|
byPath.set(path, file);
|
|
37
62
|
}
|
|
38
63
|
}
|
|
39
|
-
const
|
|
40
|
-
const configDir = dirnamePath(project.configFile);
|
|
41
|
-
const at = (option) => option === undefined ? undefined : resolvePath(configDir, option);
|
|
42
|
-
const outDir = at(options.outDir);
|
|
43
|
-
const declarationDir = at(options.declarationDir);
|
|
44
|
-
const sourceDir = at(options.rootDir) ?? commonDirectory([...byName.keys()]);
|
|
64
|
+
const { outDir, declarationDir, sourceDir } = layoutOf(project, [...byName.keys()]);
|
|
45
65
|
const layout = { outDir, declarationDir, sourceDirs: [sourceDir] };
|
|
46
66
|
/** The source paths one written path may stand for, the path itself first. */
|
|
47
67
|
const candidates = (path) => [path, ...emittedFrom(path, layout)];
|
|
@@ -49,15 +69,20 @@ export function sourceFilesOf(project, targetRoot) {
|
|
|
49
69
|
byPath,
|
|
50
70
|
byName,
|
|
51
71
|
outputDirs: [...new Set([outDir, declarationDir])].filter((dir) => dir !== undefined),
|
|
72
|
+
writtenAs: (file) => {
|
|
73
|
+
const below = relativePath(sourceDir, file.fileName);
|
|
74
|
+
if (below === undefined) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const written = [outDir, declarationDir].flatMap((dir) => dir === undefined
|
|
78
|
+
? []
|
|
79
|
+
: OUTPUT_EXTENSIONS.map((extension) => joinPath(dir, below.replace(SOURCE_EXTENSION, extension))));
|
|
80
|
+
return [...new Set(written)].filter((path) => heldAt(byName, candidates(path))[0] === file);
|
|
81
|
+
},
|
|
82
|
+
namedAsOutput: (outputDir, path) => heldAt(byName, emittedFrom(path, { outDir: outputDir, sourceDirs: [sourceDir] })),
|
|
52
83
|
named: (path) => {
|
|
53
84
|
if (!path.includes(SUBPATH_WILDCARD)) {
|
|
54
|
-
|
|
55
|
-
const held = byName.get(candidate);
|
|
56
|
-
if (held !== undefined) {
|
|
57
|
-
return [held];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return [];
|
|
85
|
+
return heldAt(byName, candidates(path));
|
|
61
86
|
}
|
|
62
87
|
const patterns = candidates(path).filter((candidate) => candidate.includes(SUBPATH_WILDCARD));
|
|
63
88
|
return [...byName.values()].filter((file) => patterns.some((pattern) => matchesSubpath(pattern, file.fileName)));
|
|
@@ -8,7 +8,7 @@ import { jsonChunks } from "./json-chunks.js";
|
|
|
8
8
|
import { joinPath } from "./paths.js";
|
|
9
9
|
import { positionAt, walkDocument } from "./json-document.js";
|
|
10
10
|
import { declarationKey } from "./ref.js";
|
|
11
|
-
import { CODE_FORM, SUPPRESSION_WITHOUT_REASON, SuppressionError, UNSCOPED_ENTRY, blankReason, isEntryPath, isSymbolRef, siteText, } from "./suppress.js";
|
|
11
|
+
import { CODE_FORM, SUPPRESSION_WITHOUT_REASON, SuppressionError, UNSCOPED_ENTRY, blankReason, isEntryPath, isPartKind, isSymbolRef, siteText, } from "./suppress.js";
|
|
12
12
|
/** The name and the location the grammar fixes for the ignore file. */
|
|
13
13
|
export const IGNORE_FILE = "deadset-ignore.json";
|
|
14
14
|
/** The name and the location the grammar fixes for the baseline. */
|
|
@@ -107,12 +107,20 @@ function readRecord(value, site, byReference, shape) {
|
|
|
107
107
|
if (refusals.length > 0) {
|
|
108
108
|
return { records: [], refusals };
|
|
109
109
|
}
|
|
110
|
-
const named = byReference.get(declarationKey(symbol, written.path)) ??
|
|
110
|
+
const named = byReference.get(declarationKey(symbol, written.path)) ??
|
|
111
|
+
(isPartKind(code)
|
|
112
|
+
? byReference.get(declarationKey(parameterHolder(symbol), written.path))
|
|
113
|
+
: undefined) ??
|
|
114
|
+
[];
|
|
111
115
|
if (named.length === 0) {
|
|
112
116
|
return { records: [{ ...written, bound: "" }], refusals: [] };
|
|
113
117
|
}
|
|
114
118
|
return { records: named.map((one) => ({ ...written, bound: one.id })), refusals: [] };
|
|
115
119
|
}
|
|
120
|
+
/** The reference of the declaration a parameter's reference names it under, or `ref` itself. */
|
|
121
|
+
function parameterHolder(ref) {
|
|
122
|
+
return ref.endsWith(")") ? ref.slice(0, ref.lastIndexOf("(")) : ref;
|
|
123
|
+
}
|
|
116
124
|
function byReferenceOf(symbols) {
|
|
117
125
|
const byReference = new Map();
|
|
118
126
|
for (const symbol of symbols) {
|
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* below it, or on its own line where it trails a token there.
|
|
5
5
|
*/
|
|
6
6
|
import { isConstructorDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
+
import { importBindings } from "./import-bindings.js";
|
|
7
8
|
import { isParameterProperty, nodeKey } from "./inventory.js";
|
|
8
9
|
import { byPosition, positionKey, renderPosition } from "./position.js";
|
|
10
|
+
import { importRef } from "./ref.js";
|
|
9
11
|
import { signatureOf } from "./signatures.js";
|
|
10
12
|
import { isPartKind, SUPPRESSION_WITHOUT_REASON, SuppressionError, siteText, } from "./suppress.js";
|
|
11
13
|
/**
|
|
@@ -144,7 +146,7 @@ function bindingKey(one) {
|
|
|
144
146
|
* function also begins, for a directive, on the line of each of its parameters, and a
|
|
145
147
|
* class, for a part kind, on the lines of its constructor and its constructor's parameters.
|
|
146
148
|
*/
|
|
147
|
-
function declarationLines(file, held, symbols) {
|
|
149
|
+
function declarationLines(file, held, symbols, targetRoot) {
|
|
148
150
|
const below = new Map();
|
|
149
151
|
const add = (offset, one) => {
|
|
150
152
|
const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
|
|
@@ -186,6 +188,20 @@ function declarationLines(file, held, symbols) {
|
|
|
186
188
|
node.forEachChild(visit);
|
|
187
189
|
};
|
|
188
190
|
file.forEachChild(visit);
|
|
191
|
+
// An import binding is a subject of its own, bound on its line as a declaration is.
|
|
192
|
+
const fileId = held.declarations.get(nodeKey(file, file));
|
|
193
|
+
const moduleRef = held.symbols.find((symbol) => symbol.id === fileId)?.ref ?? "";
|
|
194
|
+
for (const name of moduleRef === "" ? [] : importBindings(file)) {
|
|
195
|
+
const position = renderPosition(file, targetRoot, name.getStart());
|
|
196
|
+
const one = { id: positionKey(position), ref: importRef(moduleRef, name.text), position };
|
|
197
|
+
let statement = name;
|
|
198
|
+
while (statement.parent.kind !== SyntaxKind.SourceFile) {
|
|
199
|
+
statement = statement.parent;
|
|
200
|
+
}
|
|
201
|
+
for (const at of [statement.pos, name.getStart()]) {
|
|
202
|
+
add(at, { declared: one, part: one });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
189
205
|
return below;
|
|
190
206
|
}
|
|
191
207
|
/**
|
|
@@ -210,7 +226,7 @@ export function inlineDirectives(project, held, targetRoot) {
|
|
|
210
226
|
if (comments.length === 0) {
|
|
211
227
|
continue;
|
|
212
228
|
}
|
|
213
|
-
const below = declarationLines(file, held, symbols);
|
|
229
|
+
const below = declarationLines(file, held, symbols, targetRoot);
|
|
214
230
|
for (const comment of comments) {
|
|
215
231
|
const site = renderPosition(file, targetRoot, comment.offset);
|
|
216
232
|
const target = trails(file, comment.offset) ? site.line : site.line + 1;
|
package/dist/src/suppress.js
CHANGED
|
@@ -144,10 +144,11 @@ function claimKey(finding) {
|
|
|
144
144
|
}
|
|
145
145
|
/**
|
|
146
146
|
* Whether one record names one finding. A record reaches a finding in two ways: it bound
|
|
147
|
-
* the declaration at the finding's position, or it names the finding's reference
|
|
148
|
-
*
|
|
149
|
-
* finding
|
|
150
|
-
*
|
|
147
|
+
* the declaration at the finding's position, or it names the finding's reference, or the
|
|
148
|
+
* reference of the declaration holding a part that has one of its own, and the file the
|
|
149
|
+
* finding is reported in, each exactly. The second is how an entry copied from a finding
|
|
150
|
+
* about a part of a declaration reaches the part, and the only way any record reaches a
|
|
151
|
+
* dependency row.
|
|
151
152
|
*/
|
|
152
153
|
export function pairs(record, finding) {
|
|
153
154
|
if (record.code !== finding.code) {
|
|
@@ -164,7 +165,8 @@ export function pairs(record, finding) {
|
|
|
164
165
|
if (record.bound !== "" && record.bound === positionKey(finding.position)) {
|
|
165
166
|
return true;
|
|
166
167
|
}
|
|
167
|
-
return record.symbol === finding.symbol.ref
|
|
168
|
+
return ((record.symbol === finding.symbol.ref || record.symbol === finding.symbol.holder) &&
|
|
169
|
+
record.path === finding.position.path);
|
|
168
170
|
}
|
|
169
171
|
/**
|
|
170
172
|
* Decides every record against `would`, the findings the run would produce were no record
|