@cplieger/deadset-ts 6.0.0 → 6.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 +1 -1
- package/dist/src/configured-declarations.js +7 -5
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +67 -1
- package/dist/src/conventions.js +51 -9
- package/dist/src/edges.js +19 -16
- package/dist/src/findings/completion.js +9 -4
- package/dist/src/findings/emitters.js +35 -34
- package/dist/src/findings/reads-and-writes.js +3 -2
- package/dist/src/findings/visibility-narrowing.js +2 -1
- package/dist/src/inventory.js +6 -1
- package/dist/src/module-declarations.js +3 -3
- package/dist/src/outside-tests.js +5 -11
- package/dist/src/overrides.js +22 -10
- package/dist/src/ref.js +18 -2
- package/dist/src/reporters/sarif.js +3 -1
- package/dist/src/suppress-file.js +3 -5
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-programs.js +6 -31
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/configured-declarations.ts +7 -5
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +84 -1
- package/src/conventions.ts +77 -9
- package/src/edges.ts +20 -17
- package/src/findings/completion.ts +11 -4
- package/src/findings/emitters.ts +38 -51
- package/src/findings/reads-and-writes.ts +7 -2
- package/src/findings/visibility-narrowing.ts +2 -1
- package/src/inventory.ts +6 -1
- package/src/module-declarations.ts +3 -3
- package/src/outside-tests.ts +5 -11
- package/src/overrides.ts +26 -11
- package/src/ref.ts +27 -4
- package/src/reporters/sarif.ts +5 -1
- package/src/suppress-file.ts +3 -6
- package/src/version.ts +1 -1
- package/src/workspace-programs.ts +6 -35
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
deadset-ts finds dead code in TypeScript projects, down to class members, type members and dead stores inside functions, and writes a report your CI can fail on.
|
|
6
6
|
|
|
7
|
-
It reads your `tsconfig.json` projects, `allowJs` JavaScript and Vue, Svelte and Astro scripts included, with the type information of TypeScript 7.1.0-dev.
|
|
7
|
+
It reads your `tsconfig.json` projects, `allowJs` JavaScript and Vue, Svelte and Astro scripts included, with the type information of TypeScript 7.1.0-dev.20261007.1, its one dependency. Your project keeps its own TypeScript version. A type error under that version skips the function or statement that holds it. deadset-ts reports and never edits your code. It is pre-release, tested only on Linux, needs Node.js 24 or later and is licensed under GPL-3.0-or-later.
|
|
8
8
|
|
|
9
9
|
## Why use it
|
|
10
10
|
|
|
@@ -143,8 +143,8 @@ function modulesNamed(project, specifiers) {
|
|
|
143
143
|
return modules;
|
|
144
144
|
}
|
|
145
145
|
/**
|
|
146
|
-
* The declaration each entry names in one project. A symbol entry names
|
|
147
|
-
* inventory
|
|
146
|
+
* The declaration each entry names in one project. A symbol entry names every
|
|
147
|
+
* declaration of the inventory whose reference it spells exactly; a module entry names the
|
|
148
148
|
* declaration its path reaches from the exports of the module its specifier resolves
|
|
149
149
|
* to; a global entry names the declaration its path reaches from the global scope.
|
|
150
150
|
* Every alias along a path is followed to the declaration it stands for.
|
|
@@ -153,12 +153,14 @@ export function resolveEntries(project, held, entries) {
|
|
|
153
153
|
if (entries.length === 0) {
|
|
154
154
|
return [];
|
|
155
155
|
}
|
|
156
|
-
const byRef = new Map(
|
|
156
|
+
const byRef = new Map();
|
|
157
|
+
for (const symbol of held.symbols) {
|
|
158
|
+
byRef.set(symbol.ref, [...(byRef.get(symbol.ref) ?? []), symbol.id]);
|
|
159
|
+
}
|
|
157
160
|
const modules = modulesNamed(project, new Set(entries.flatMap((entry) => (entry.shape === "module" ? [entry.module] : []))));
|
|
158
161
|
return entries.map((entry) => {
|
|
159
162
|
if (entry.shape === "symbol") {
|
|
160
|
-
|
|
161
|
-
return { entry, handles: new Set(), ids: new Set(id === undefined ? [] : [id]) };
|
|
163
|
+
return { entry, handles: new Set(), ids: new Set(byRef.get(entry.symbol) ?? []) };
|
|
162
164
|
}
|
|
163
165
|
const { components, static: isStatic } = componentsOf(entry.shape === "module" ? entry.name : entry.global);
|
|
164
166
|
const [first = "", ...rest] = components;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Regenerate it with `UPDATE_GOLDEN=1 npx vitest --run src/conformance.corpus.test.ts`.
|
|
3
3
|
export const RECORD = {
|
|
4
4
|
conformance: {
|
|
5
|
-
corpusVersion: "4.
|
|
5
|
+
corpusVersion: "4.1.0",
|
|
6
6
|
result: "pass",
|
|
7
|
-
digest: "sha256:
|
|
7
|
+
digest: "sha256:167f770cdff2776fa7ab28b7ff6121c4d7bdb01d83c5ba0057d9303281614008",
|
|
8
8
|
},
|
|
9
9
|
gaps: [],
|
|
10
10
|
};
|
|
@@ -31,6 +31,11 @@ const APP_DIRECTORY_ENTRIES = [
|
|
|
31
31
|
];
|
|
32
32
|
const SOLIDSTART_PLUGIN = { module: "@solidjs/start/config", export: "solidStart" };
|
|
33
33
|
const NUXT_CONFIG = [`nuxt.config.${ANY_MODULE}`];
|
|
34
|
+
const BABEL_CONFIG = [
|
|
35
|
+
"babel.config.{json,js,cjs,mjs,cts,mts,ts}",
|
|
36
|
+
".babelrc",
|
|
37
|
+
".babelrc.{json,js,cjs,mjs,cts}",
|
|
38
|
+
];
|
|
34
39
|
const STRYKER_CONFIG = "{stryker,.stryker}.{conf,config}.{json,js,mjs,cjs}";
|
|
35
40
|
const STRYKER_MODULE = "{stryker,.stryker}.{conf,config}.{js,mjs,cjs}";
|
|
36
41
|
const VITEST_CONFIG = `{vitest,vite}.config.${ANY_MODULE}`;
|
|
@@ -57,6 +62,19 @@ export const CONVENTION_ROWS = [
|
|
|
57
62
|
},
|
|
58
63
|
],
|
|
59
64
|
},
|
|
65
|
+
{
|
|
66
|
+
name: "babel",
|
|
67
|
+
package: "@babel/core",
|
|
68
|
+
range: ">=7.0.0 <9.0.0",
|
|
69
|
+
entries: [],
|
|
70
|
+
moves: [],
|
|
71
|
+
helpers: ["@babel/plugin-transform-runtime", "@babel/transform-runtime"].map((value) => ({
|
|
72
|
+
property: "plugins",
|
|
73
|
+
files: BABEL_CONFIG,
|
|
74
|
+
value,
|
|
75
|
+
package: "@babel/runtime",
|
|
76
|
+
})),
|
|
77
|
+
},
|
|
60
78
|
{
|
|
61
79
|
name: "eslint",
|
|
62
80
|
package: "eslint",
|
|
@@ -229,6 +247,21 @@ export const CONVENTION_ROWS = [
|
|
|
229
247
|
},
|
|
230
248
|
],
|
|
231
249
|
},
|
|
250
|
+
{
|
|
251
|
+
name: "rolldown",
|
|
252
|
+
package: "rolldown",
|
|
253
|
+
range: ">=1.0.0 <2.0.0",
|
|
254
|
+
entries: [],
|
|
255
|
+
moves: [],
|
|
256
|
+
helpers: [
|
|
257
|
+
{
|
|
258
|
+
property: "transform.helpers.mode",
|
|
259
|
+
files: [`rolldown.config.${ANY_MODULE}`],
|
|
260
|
+
value: "Runtime",
|
|
261
|
+
package: "@oxc-project/runtime",
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
},
|
|
232
265
|
{
|
|
233
266
|
name: "solidstart",
|
|
234
267
|
package: "@solidjs/start",
|
|
@@ -275,7 +308,10 @@ export const CONVENTION_ROWS = [
|
|
|
275
308
|
range: ">=7.0.0 <11.0.0",
|
|
276
309
|
entries: [STRYKER_MODULE],
|
|
277
310
|
moves: [],
|
|
278
|
-
selections: [
|
|
311
|
+
selections: [
|
|
312
|
+
{ property: "mutate", files: [STRYKER_CONFIG] },
|
|
313
|
+
{ property: "ignorePatterns", files: [STRYKER_CONFIG] },
|
|
314
|
+
],
|
|
279
315
|
shortNames: [
|
|
280
316
|
{
|
|
281
317
|
property: "testRunner",
|
|
@@ -406,6 +442,36 @@ export const CONVENTION_ROWS = [
|
|
|
406
442
|
},
|
|
407
443
|
],
|
|
408
444
|
},
|
|
445
|
+
{
|
|
446
|
+
name: "swc",
|
|
447
|
+
package: "@swc/core",
|
|
448
|
+
range: ">=1.0.0 <2.0.0",
|
|
449
|
+
entries: [],
|
|
450
|
+
moves: [],
|
|
451
|
+
helpers: [
|
|
452
|
+
{
|
|
453
|
+
property: "jsc.externalHelpers",
|
|
454
|
+
files: [".swcrc"],
|
|
455
|
+
value: true,
|
|
456
|
+
package: "@swc/helpers",
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "vite",
|
|
462
|
+
package: "vite",
|
|
463
|
+
range: ">=8.0.0 <9.0.0",
|
|
464
|
+
entries: [],
|
|
465
|
+
moves: [],
|
|
466
|
+
helpers: [
|
|
467
|
+
{
|
|
468
|
+
property: "oxc.helpers.mode",
|
|
469
|
+
files: [VITE_CONFIG],
|
|
470
|
+
value: "Runtime",
|
|
471
|
+
package: "@oxc-project/runtime",
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
},
|
|
409
475
|
{
|
|
410
476
|
name: "vitest",
|
|
411
477
|
package: "vitest",
|
package/dist/src/conventions.js
CHANGED
|
@@ -13,6 +13,8 @@ import { globExpression } from "./glob.js";
|
|
|
13
13
|
import { dirnamePath, joinPath, normalizePath, relativePath, resolvePath } from "./paths.js";
|
|
14
14
|
import { satisfies } from "./version-range.js";
|
|
15
15
|
const JSON_EXTENSION = ".json";
|
|
16
|
+
/** A configuration file with no extension that its tool reads as a JSON document. */
|
|
17
|
+
const JSON_RC = /^\.[\w-]+rc$/u;
|
|
16
18
|
/** The sections an installed package is required by, so its absence fails the run. */
|
|
17
19
|
const REQUIRED_SECTIONS = ["dependencies", "devDependencies"];
|
|
18
20
|
const DEPENDENCY_SECTIONS = [
|
|
@@ -351,11 +353,10 @@ function readPath(object, keys, values, elements) {
|
|
|
351
353
|
return undefined;
|
|
352
354
|
}
|
|
353
355
|
/**
|
|
354
|
-
* Every
|
|
355
|
-
*
|
|
356
|
-
* array value, whatever else the path holds.
|
|
356
|
+
* Every value one file writes at one property's path inside the objects it is read from,
|
|
357
|
+
* as {@link readProperty} reads them, whatever else the path holds.
|
|
357
358
|
*/
|
|
358
|
-
function
|
|
359
|
+
function valuesAt(file, property, call) {
|
|
359
360
|
const declared = declaredValues(file);
|
|
360
361
|
const objects = call === undefined
|
|
361
362
|
? defaultExports(file).map((one) => objectOf(one, declared, true))
|
|
@@ -374,12 +375,9 @@ function stringsAt(file, property, call) {
|
|
|
374
375
|
if (rest.length > 0) {
|
|
375
376
|
visit(held, rest);
|
|
376
377
|
}
|
|
377
|
-
else
|
|
378
|
+
else {
|
|
378
379
|
found.push(held);
|
|
379
380
|
}
|
|
380
|
-
else if (isArrayLiteralExpression(held)) {
|
|
381
|
-
found.push(...held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined));
|
|
382
|
-
}
|
|
383
381
|
}
|
|
384
382
|
};
|
|
385
383
|
for (const object of objects) {
|
|
@@ -389,6 +387,32 @@ function stringsAt(file, property, call) {
|
|
|
389
387
|
}
|
|
390
388
|
return found;
|
|
391
389
|
}
|
|
390
|
+
/** The string literals among values: each value itself, or each element of an array value. */
|
|
391
|
+
function stringsAt(file, property, call) {
|
|
392
|
+
return valuesAt(file, property, call).flatMap((held) => {
|
|
393
|
+
if (literalText(held) !== undefined) {
|
|
394
|
+
return [held];
|
|
395
|
+
}
|
|
396
|
+
return isArrayLiteralExpression(held)
|
|
397
|
+
? held.elements.map(unwrapped).filter((one) => literalText(one) !== undefined)
|
|
398
|
+
: [];
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/** Whether one value holds a helper setting's value, as {@link HelperReading} states it. */
|
|
402
|
+
function holdsValue(held, value) {
|
|
403
|
+
if (value === true) {
|
|
404
|
+
return held.kind === SyntaxKind.TrueKeyword;
|
|
405
|
+
}
|
|
406
|
+
if (literalText(held) === value) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
return (isArrayLiteralExpression(held) &&
|
|
410
|
+
held.elements.some((element) => {
|
|
411
|
+
const one = unwrapped(element);
|
|
412
|
+
const first = isArrayLiteralExpression(one) ? one.elements[0] : one;
|
|
413
|
+
return first !== undefined && literalText(first) === value;
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
392
416
|
/** The strings an applied row's selection keys hold, added per configuration file to `into`. */
|
|
393
417
|
function selectedStrings(host, parse, dir, readings, into) {
|
|
394
418
|
for (const reading of readings) {
|
|
@@ -527,7 +551,11 @@ function readingFiles(host, parse, dir, files) {
|
|
|
527
551
|
catch {
|
|
528
552
|
continue;
|
|
529
553
|
}
|
|
530
|
-
found.push({
|
|
554
|
+
found.push({
|
|
555
|
+
name,
|
|
556
|
+
text,
|
|
557
|
+
file: parse(JSON_RC.test(name) ? `${path}${JSON_EXTENSION}` : path, text),
|
|
558
|
+
});
|
|
531
559
|
}
|
|
532
560
|
}
|
|
533
561
|
return found;
|
|
@@ -563,6 +591,17 @@ function shortNameUses(host, parse, dir, manifest, readings) {
|
|
|
563
591
|
}
|
|
564
592
|
return [...used].filter((name) => declared.has(name)).sort(compare);
|
|
565
593
|
}
|
|
594
|
+
/** The declared dependencies an applied row's helper settings use. */
|
|
595
|
+
function helperUses(host, parse, dir, manifest, readings) {
|
|
596
|
+
const declared = new Set(DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
597
|
+
const held = isRecord(manifest) ? manifest[section] : undefined;
|
|
598
|
+
return isRecord(held) ? Object.keys(held) : [];
|
|
599
|
+
}));
|
|
600
|
+
return readings
|
|
601
|
+
.filter((reading) => declared.has(reading.package) &&
|
|
602
|
+
readingFiles(host, parse, dir, reading.files).some(({ file }) => valuesAt(file, reading.property, reading.call).some((held) => holdsValue(held, reading.value))))
|
|
603
|
+
.map((reading) => reading.package);
|
|
604
|
+
}
|
|
566
605
|
/** Two strings ordered bytewise. */
|
|
567
606
|
function compare(a, b) {
|
|
568
607
|
if (a === b) {
|
|
@@ -644,6 +683,9 @@ export function readConventions(host, parse, manifests, disabled, rows = CONVENT
|
|
|
644
683
|
for (const name of manifestKeyUses(declared, row.manifestKeys ?? [])) {
|
|
645
684
|
uses.add(name);
|
|
646
685
|
}
|
|
686
|
+
for (const name of helperUses(host, parse, manifest.dir, declared, row.helpers ?? [])) {
|
|
687
|
+
uses.add(name);
|
|
688
|
+
}
|
|
647
689
|
selectedStrings(host, parse, manifest.dir, row.selections ?? [], selected);
|
|
648
690
|
const excludes = [
|
|
649
691
|
...new Set((row.excludes ?? []).flatMap((one) => expand(one, resolved))),
|
package/dist/src/edges.js
CHANGED
|
@@ -154,9 +154,10 @@ function compare(a, b) {
|
|
|
154
154
|
}
|
|
155
155
|
/**
|
|
156
156
|
* Evaluates every declared side of this language over the run's findings. A side naming a
|
|
157
|
-
* symbol the run enumerates nothing under is `absent`;
|
|
158
|
-
*
|
|
159
|
-
*
|
|
157
|
+
* symbol the run enumerates nothing under is `absent`; otherwise it is evaluated once per
|
|
158
|
+
* declaration its reference spells. A declaration the run holds a finding about is `dead`
|
|
159
|
+
* and carries that finding, pending until a merge reads the other side and reported
|
|
160
|
+
* nowhere else, a narrowing finding included; any other is `live`.
|
|
160
161
|
*/
|
|
161
162
|
export function evaluateEdges(findings, sides, symbols) {
|
|
162
163
|
if (sides.length === 0) {
|
|
@@ -164,8 +165,8 @@ export function evaluateEdges(findings, sides, symbols) {
|
|
|
164
165
|
}
|
|
165
166
|
const byRef = new Map();
|
|
166
167
|
for (const symbol of symbols) {
|
|
167
|
-
if (symbol.kind !== "file"
|
|
168
|
-
byRef.set(symbol.ref, symbol.id);
|
|
168
|
+
if (symbol.kind !== "file") {
|
|
169
|
+
byRef.set(symbol.ref, [...(byRef.get(symbol.ref) ?? []), symbol.id]);
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
const held = new Map();
|
|
@@ -175,18 +176,20 @@ export function evaluateEdges(findings, sides, symbols) {
|
|
|
175
176
|
}
|
|
176
177
|
});
|
|
177
178
|
const pending = new Set();
|
|
178
|
-
const evaluations = sides.
|
|
179
|
-
const
|
|
180
|
-
if (
|
|
181
|
-
return { ...side, state: "absent" };
|
|
179
|
+
const evaluations = sides.flatMap((side) => {
|
|
180
|
+
const ids = byRef.get(side.symbol);
|
|
181
|
+
if (ids === undefined) {
|
|
182
|
+
return [{ ...side, state: "absent" }];
|
|
182
183
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
return ids.map((id) => {
|
|
185
|
+
const at = held.get(id);
|
|
186
|
+
const finding = at === undefined ? undefined : findings[at];
|
|
187
|
+
if (at === undefined || finding === undefined) {
|
|
188
|
+
return { ...side, state: "live" };
|
|
189
|
+
}
|
|
190
|
+
pending.add(at);
|
|
191
|
+
return { ...side, state: "dead", finding };
|
|
192
|
+
});
|
|
190
193
|
});
|
|
191
194
|
evaluations.sort((a, b) => compare(a.edge, b.edge) || compare(a.side, b.side));
|
|
192
195
|
return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { componentId } from "../components.js";
|
|
7
7
|
import { OUTSIDE } from "../graph.js";
|
|
8
8
|
import { KINDS } from "../kinds.js";
|
|
9
|
-
import { byPosition } from "../position.js";
|
|
9
|
+
import { byPosition, positionKey } from "../position.js";
|
|
10
|
+
import { declarationKey } from "../ref.js";
|
|
10
11
|
import { FAMILY_KEY_LENGTH } from "../resolve.js";
|
|
11
12
|
import { isPartSubject, isRowSubject } from "../suppress.js";
|
|
12
13
|
/** The classes ranked by the claim each makes, the strongest highest. */
|
|
@@ -117,7 +118,7 @@ function findingComponents(swept, cascade) {
|
|
|
117
118
|
export function completed(input, findings, kinds = KINDS) {
|
|
118
119
|
const { swept } = input;
|
|
119
120
|
const union = swept.matrix.union;
|
|
120
|
-
const positions = new Map(union.symbols.map((symbol, at) => [symbol.ref, at]));
|
|
121
|
+
const positions = new Map(union.symbols.map((symbol, at) => [declarationKey(symbol.ref, symbol.position.path), at]));
|
|
121
122
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
122
123
|
const classOf = reachabilityClasses(input);
|
|
123
124
|
const componentOf = findingComponents(swept, input.config.reporters.cascade);
|
|
@@ -135,10 +136,14 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
135
136
|
throw new Error(`${finding.code} names no live row of the issue-kind vocabulary`);
|
|
136
137
|
}
|
|
137
138
|
// A row's reference may spell a declaration's, as an ignore entry naming one does, and
|
|
138
|
-
// a row is still never the declaration.
|
|
139
|
+
// a row is still never the declaration. Two declarations of one file can share a
|
|
140
|
+
// reference, so the one the finding is positioned at is the subject where there is one.
|
|
141
|
+
const declared = union.at(positionKey(finding.position));
|
|
139
142
|
const at = isRowSubject(finding.symbol.kind)
|
|
140
143
|
? OUTSIDE
|
|
141
|
-
:
|
|
144
|
+
: union.symbols[declared]?.ref === finding.symbol.ref
|
|
145
|
+
? declared
|
|
146
|
+
: (positions.get(declarationKey(finding.symbol.ref, finding.position.path)) ?? OUTSIDE);
|
|
142
147
|
// A part names the declaration that holds it, and is decided inside it rather than by
|
|
143
148
|
// the declaration's liveness, so it carries none of the declaration's sweep facts.
|
|
144
149
|
const part = isPartSubject(finding.symbol.kind);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { evaluateEdges } from "../edges.js";
|
|
2
2
|
import { liveFilesAt } from "../matrix.js";
|
|
3
|
+
import { declarationKey } from "../ref.js";
|
|
3
4
|
import { ledgerOf } from "../suppress.js";
|
|
4
5
|
import { insideSkipped } from "../type-errors.js";
|
|
5
6
|
import { completed, dialsOf, reportable } from "./completion.js";
|
|
@@ -23,24 +24,24 @@ export const EMITTERS = new Map([
|
|
|
23
24
|
["intra-function", intraFunction],
|
|
24
25
|
]);
|
|
25
26
|
/**
|
|
26
|
-
* The
|
|
27
|
-
*
|
|
27
|
+
* The keys of every declaration an unanswered question could have kept live, which the run
|
|
28
|
+
* reports nothing about: a finding about one would be computed from the gap.
|
|
28
29
|
*/
|
|
29
|
-
function
|
|
30
|
+
function heldKeys(input) {
|
|
30
31
|
const held = new Set(input.swept.heldByUnanswered);
|
|
31
32
|
if (held.size === 0) {
|
|
32
33
|
return held;
|
|
33
34
|
}
|
|
34
35
|
return new Set(input.swept.matrix.union.symbols
|
|
35
36
|
.filter((symbol) => held.has(symbol.id))
|
|
36
|
-
.map((symbol) => symbol.ref));
|
|
37
|
+
.map((symbol) => declarationKey(symbol.ref, symbol.position.path)));
|
|
37
38
|
}
|
|
38
39
|
/**
|
|
39
|
-
* The
|
|
40
|
+
* The keys of every top-level declaration of a live component file. The file's
|
|
40
41
|
* markup may use each of them, which no analysis of the markup can rule out, so the run
|
|
41
42
|
* reports nothing about one while the file is live.
|
|
42
43
|
*/
|
|
43
|
-
function
|
|
44
|
+
function markupKeys(input) {
|
|
44
45
|
const { swept } = input;
|
|
45
46
|
if (swept.componentFiles.length === 0) {
|
|
46
47
|
return new Set();
|
|
@@ -48,7 +49,7 @@ function markupRefs(input) {
|
|
|
48
49
|
const live = new Set(liveFilesAt(swept.matrix, swept.sweep, new Set(swept.componentFiles)).map((symbol) => symbol.id));
|
|
49
50
|
return new Set(swept.matrix.union.symbols
|
|
50
51
|
.filter((symbol) => live.has(symbol.parent))
|
|
51
|
-
.map((symbol) => symbol.ref));
|
|
52
|
+
.map((symbol) => declarationKey(symbol.ref, symbol.position.path)));
|
|
52
53
|
}
|
|
53
54
|
/** Every family's findings over one sweep, and those of any further emitter, completed by one step. */
|
|
54
55
|
function completedOver(input, ...more) {
|
|
@@ -72,21 +73,13 @@ function dialsOver(input, found, suppressed, unmarked) {
|
|
|
72
73
|
* family's findings completed by one step, so every component number is minted once, the
|
|
73
74
|
* finding each record claims withheld, the rest withheld by the dials, so a root one family
|
|
74
75
|
* reports withholds what another reports in its component, and every declared edge
|
|
75
|
-
* evaluated over what remains, so a pending finding is in its evaluation alone.
|
|
76
|
-
* minimum confidence withheld is what the same decision at the lowest minimum adds.
|
|
76
|
+
* evaluated over what remains, so a pending finding is in its evaluation alone.
|
|
77
77
|
*/
|
|
78
78
|
export function decidedFindings(input, suppressed = { unmarked: input, records: [] }) {
|
|
79
79
|
const found = completedOver(input);
|
|
80
80
|
const unmarked = suppressed.records.length === 0 ? [] : completedOver(suppressed.unmarked, fallenMembers);
|
|
81
81
|
const would = [...unmarked, ...found];
|
|
82
|
-
|
|
83
|
-
const shown = input.config.analysis.minConfidence === "possible"
|
|
84
|
-
? decided
|
|
85
|
-
: dialledOver(atPossible(input), {
|
|
86
|
-
unmarked: atPossible(suppressed.unmarked),
|
|
87
|
-
records: suppressed.records,
|
|
88
|
-
}, found, unmarked);
|
|
89
|
-
return { ...decided, would, withheld: withheldOf(decided.findings, shown.findings) };
|
|
82
|
+
return { ...dialledOver(input, suppressed, found, unmarked), would };
|
|
90
83
|
}
|
|
91
84
|
/** The run's input with the minimum confidence at its lowest, which withholds nothing. */
|
|
92
85
|
function atPossible(input) {
|
|
@@ -104,31 +97,39 @@ function atPossible(input) {
|
|
|
104
97
|
function dialledOver(input, suppressed, found, unmarked) {
|
|
105
98
|
const would = [...unmarked, ...found];
|
|
106
99
|
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
|
|
107
|
-
const unanswered =
|
|
108
|
-
const markup =
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
!
|
|
112
|
-
|
|
100
|
+
const unanswered = heldKeys(input);
|
|
101
|
+
const markup = markupKeys(input);
|
|
102
|
+
const candidates = found.filter((finding) => {
|
|
103
|
+
const key = declarationKey(finding.symbol.ref, finding.position.path);
|
|
104
|
+
return (!ledger.withheld(finding) &&
|
|
105
|
+
!unanswered.has(key) &&
|
|
106
|
+
!markup.has(key) &&
|
|
107
|
+
!insideSkipped(input.swept.skipped, finding.position));
|
|
108
|
+
});
|
|
109
|
+
const kept = reportable(input, candidates);
|
|
113
110
|
const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
|
|
114
|
-
return {
|
|
111
|
+
return {
|
|
112
|
+
findings: evaluated.findings,
|
|
113
|
+
evaluations: evaluated.evaluations,
|
|
114
|
+
ledger,
|
|
115
|
+
withheld: withheldOf(input, candidates),
|
|
116
|
+
};
|
|
115
117
|
}
|
|
116
|
-
/**
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The findings the dials withhold only because their confidence is below the minimum, each
|
|
120
|
+
* counted at the confidence this run gives it: the dials at the lowest minimum keep them.
|
|
121
|
+
*/
|
|
122
|
+
function withheldOf(input, candidates) {
|
|
123
|
+
const dials = dialsOf(input, candidates);
|
|
124
|
+
const lowest = dialsOf(atPossible(input), candidates);
|
|
119
125
|
const counts = { certain: 0, probable: 0, possible: 0 };
|
|
120
|
-
for (const finding of
|
|
121
|
-
if (!
|
|
126
|
+
for (const finding of candidates) {
|
|
127
|
+
if (dials.withholds(finding) && !lowest.withholds(finding)) {
|
|
122
128
|
counts[finding.confidence] += 1;
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
return counts;
|
|
126
132
|
}
|
|
127
|
-
/** A finding's identity across two decisions of one run: its position, code and subject. */
|
|
128
|
-
function findingKey(finding) {
|
|
129
|
-
const { path, line, column } = finding.position;
|
|
130
|
-
return JSON.stringify([path, line, column, finding.code, finding.symbol.ref]);
|
|
131
|
-
}
|
|
132
133
|
/** Every finding the run reports, each family's in table order. */
|
|
133
134
|
export function findingsOf(input) {
|
|
134
135
|
return decidedFindings(input).findings;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { positionKey } from "../position.js";
|
|
2
|
+
import { declarationKey } from "../ref.js";
|
|
2
3
|
/** The containers whose type parameters `DS1303` reports: a function and a class method. */
|
|
3
4
|
const SIGNATURES = new Set(["function", "method"]);
|
|
4
5
|
function indexed(input) {
|
|
@@ -175,12 +176,12 @@ function unusedTypeParameters(run) {
|
|
|
175
176
|
.map((symbol) => findingAbout(symbol, "DS1303", `type parameter ${symbol.name} is named in neither its signature nor its body`));
|
|
176
177
|
}
|
|
177
178
|
/**
|
|
178
|
-
* The
|
|
179
|
+
* The keys of the declarations the write-only kind reports, whatever the
|
|
179
180
|
* severity: a narrowing kind yields each to it, since deleting the declaration
|
|
180
181
|
* supersedes narrowing it.
|
|
181
182
|
*/
|
|
182
183
|
export function writeOnlyDeclarations(input) {
|
|
183
|
-
return new Set(writeOnlySymbols(indexed(input)).map((finding) => finding.symbol.ref));
|
|
184
|
+
return new Set(writeOnlySymbols(indexed(input)).map((finding) => declarationKey(finding.symbol.ref, finding.position.path)));
|
|
184
185
|
}
|
|
185
186
|
/**
|
|
186
187
|
* The findings of the reads-and-writes family, `DS1300` to `DS1399`, in code order and
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OUTSIDE } from "../graph.js";
|
|
2
|
+
import { declarationKey } from "../ref.js";
|
|
2
3
|
import { writeOnlyDeclarations } from "./reads-and-writes.js";
|
|
3
4
|
const UNNECESSARY_EXPORT = "DS1101";
|
|
4
5
|
const UNREACHABLE_EXPORT = "DS1103";
|
|
@@ -119,7 +120,7 @@ function narrowing(facts, at, symbol, world) {
|
|
|
119
120
|
const { closed } = world;
|
|
120
121
|
const file = facts.fileOf[at] ?? OUTSIDE;
|
|
121
122
|
if (!topLevelExport(facts, at, symbol) ||
|
|
122
|
-
world.writeOnly.has(symbol.ref) ||
|
|
123
|
+
world.writeOnly.has(declarationKey(symbol.ref, symbol.position.path)) ||
|
|
123
124
|
symbol.kind === "export-alias" ||
|
|
124
125
|
entersByName(facts.rootedBy[at]) ||
|
|
125
126
|
entersByName(facts.rootedBy[file]) ||
|
package/dist/src/inventory.js
CHANGED
|
@@ -787,7 +787,12 @@ function refOf(record, chain) {
|
|
|
787
787
|
return renderRef(record.module, { of: "module" });
|
|
788
788
|
}
|
|
789
789
|
if (record.kind === "export-alias") {
|
|
790
|
-
|
|
790
|
+
const block = record.parentChain.at(-1);
|
|
791
|
+
return renderRef(record.module, {
|
|
792
|
+
of: "alias",
|
|
793
|
+
name: record.component.text,
|
|
794
|
+
...(block === undefined ? {} : { block: block.text }),
|
|
795
|
+
});
|
|
791
796
|
}
|
|
792
797
|
return renderRef(record.module, {
|
|
793
798
|
of: "declaration",
|
|
@@ -9,7 +9,7 @@ import { isCallExpression, isExportDeclaration, isExternalModuleReference, isImp
|
|
|
9
9
|
import { nodeKey } from "./inventory.js";
|
|
10
10
|
import { renderPosition } from "./position.js";
|
|
11
11
|
import { UNANSWERED } from "./query.js";
|
|
12
|
-
import { nameComponent } from "./ref.js";
|
|
12
|
+
import { aliasFragment, nameComponent } from "./ref.js";
|
|
13
13
|
/** Whether a node is a specifier's text: a string literal or a template with no substitution. */
|
|
14
14
|
function literalText(node) {
|
|
15
15
|
return node !== undefined && (isStringLiteral(node) || isNoSubstitutionTemplateLiteral(node));
|
|
@@ -78,7 +78,7 @@ function namesModule(specifier, name) {
|
|
|
78
78
|
* The module declarations of an inventory a specifier names by name or pattern, each with
|
|
79
79
|
* the members that hold the export names given.
|
|
80
80
|
*/
|
|
81
|
-
|
|
81
|
+
function namedBy(held) {
|
|
82
82
|
if (held.modules.size === 0) {
|
|
83
83
|
return () => [];
|
|
84
84
|
}
|
|
@@ -103,7 +103,7 @@ export function namedBy(held) {
|
|
|
103
103
|
const scope = ref.slice(0, ref.indexOf("#"));
|
|
104
104
|
const wanted = new Set(names.flatMap((one) => [
|
|
105
105
|
`${ref}.${nameComponent(one)}`,
|
|
106
|
-
`${scope}#${
|
|
106
|
+
`${scope}#${aliasFragment(one, name)}`,
|
|
107
107
|
]));
|
|
108
108
|
const members = (children.get(id) ?? []).filter((child) => wanted.has(child.ref));
|
|
109
109
|
return [{ id, members: members.map((child) => child.id) }];
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The test files no compiler configuration of a run holds. The runner executes them all
|
|
3
3
|
* the same, so each module one names is a test reference: the own file a relative
|
|
4
|
-
* specifier names, and the declarations that file exports under the names read from it
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* specifier names, and the declarations that file exports under the names read from it.
|
|
5
|
+
* No compiler run reads a module declaration for such a file, so its imports name none.
|
|
6
|
+
* Such a file is read without a program, so what its body references beyond the modules
|
|
7
|
+
* it names is not known.
|
|
8
8
|
*/
|
|
9
9
|
import { isCallExpression, isExportDeclaration, isImportDeclaration, isNamespaceExport, isNamespaceImport, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
|
|
10
10
|
import { RESOLVED_EXTENSIONS } from "./configuration-files.js";
|
|
11
11
|
import { emittedFrom } from "./emit-map.js";
|
|
12
|
-
import {
|
|
12
|
+
import { namesImported } from "./module-declarations.js";
|
|
13
13
|
import { dirnamePath, joinPath, normalizePath, queriedModulePath } from "./paths.js";
|
|
14
14
|
import { renderPosition } from "./position.js";
|
|
15
15
|
import { nameComponent } from "./ref.js";
|
|
@@ -102,7 +102,6 @@ export function outsideTestReferences(tests, held, targetRoot) {
|
|
|
102
102
|
exported.set(key, [...(exported.get(key) ?? []), symbol.id]);
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
const declared = namedBy(held);
|
|
106
105
|
const found = [];
|
|
107
106
|
for (const { path, file } of tests) {
|
|
108
107
|
const from = `${path}#`;
|
|
@@ -112,11 +111,6 @@ export function outsideTestReferences(tests, held, targetRoot) {
|
|
|
112
111
|
const read = (to, use) => {
|
|
113
112
|
found.push({ from, to, position, use, resolution: "syntax", test: true, unheld: true });
|
|
114
113
|
};
|
|
115
|
-
for (const declaration of declared(named.text, named.names)) {
|
|
116
|
-
for (const to of [declaration.id, ...declaration.members]) {
|
|
117
|
-
read(to, "read");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
114
|
const written = named.text.includes("?") ? queriedModulePath(named.text) : named.text;
|
|
121
115
|
if (written === undefined) {
|
|
122
116
|
continue;
|