@cplieger/deadset-ts 7.0.0-dev.1 → 7.0.0-dev.2
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 +6 -16
- package/dist/src/component-layer.js +1 -1
- package/dist/src/configuration-files.js +227 -27
- package/dist/src/contextual-uses.js +41 -7
- package/dist/src/convention-rows.js +6 -0
- package/dist/src/conventions.js +32 -0
- package/dist/src/dependencies.js +12 -8
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +1 -1
- package/dist/src/exempt-classes.js +9 -22
- package/dist/src/exempt.js +3 -15
- package/dist/src/findings/completion.js +35 -4
- package/dist/src/findings/emitters.js +0 -4
- package/dist/src/findings/self-check.js +14 -10
- package/dist/src/indexed-access.js +8 -2
- package/dist/src/inventory.js +3 -12
- package/dist/src/references.js +2 -2
- package/dist/src/report.js +3 -0
- package/dist/src/roots.js +0 -1
- package/dist/src/run.js +0 -2
- package/dist/src/schema.js +1 -1
- package/dist/src/scope.js +2 -2
- package/dist/src/session.js +0 -1
- package/dist/src/suppress-inline.js +1 -1
- package/dist/src/suppress.js +15 -16
- package/dist/src/workspace-programs.js +0 -3
- package/dist/src/workspace-resolution.js +0 -2
- package/dist/src/workspace.js +0 -3
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +10 -30
- package/src/calls.ts +3 -3
- package/src/component-files.ts +7 -7
- package/src/component-layer.ts +2 -2
- package/src/components.ts +1 -1
- package/src/config.ts +4 -4
- package/src/configuration-files.ts +246 -28
- package/src/contextual-uses.ts +63 -9
- package/src/convention-rows.ts +19 -0
- package/src/conventions.ts +43 -0
- package/src/dependencies.ts +14 -10
- package/src/discover.ts +3 -3
- package/src/edges.ts +3 -3
- package/src/entry-points.ts +1 -1
- package/src/exempt-classes.ts +10 -24
- package/src/exempt.ts +5 -29
- package/src/file-facts.ts +1 -1
- package/src/finding.ts +3 -1
- package/src/findings/completion.ts +46 -4
- package/src/findings/emitters.ts +3 -8
- package/src/findings/self-check.ts +17 -14
- package/src/findings-pass.ts +2 -2
- package/src/graph.ts +2 -2
- package/src/html-entries.ts +1 -1
- package/src/indexed-access.ts +10 -2
- package/src/inventory.ts +5 -33
- package/src/json-document.ts +1 -1
- package/src/manifest.ts +1 -1
- package/src/namespace-objects.ts +1 -1
- package/src/pnpm-workspace.ts +1 -1
- package/src/references.ts +7 -6
- package/src/report.ts +8 -4
- package/src/roots.ts +0 -3
- package/src/run.ts +0 -2
- package/src/schema.ts +2 -2
- package/src/scope.ts +2 -2
- package/src/session.ts +0 -2
- package/src/stores.ts +1 -1
- package/src/suppress-inline.ts +3 -3
- package/src/suppress.ts +21 -24
- package/src/template-field.ts +1 -1
- package/src/value-reach.ts +1 -1
- package/src/verbs/verb.ts +1 -4
- package/src/workspace-programs.ts +2 -9
- package/src/workspace-resolution.ts +0 -3
- package/src/workspace-run.ts +1 -1
- package/src/workspace.ts +1 -9
|
@@ -1,72 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The exemption classes of the Contract's vocabulary, in its order: each class's name,
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* reason the fixed severity codes are, and a test holds them equal to the Contract
|
|
8
|
-
* release this analyzer implements. A class name is a type, so a detector table or a
|
|
9
|
-
* record naming a class the vocabulary does not hold, or one that runs on another
|
|
10
|
-
* language only, does not compile.
|
|
2
|
+
* The exemption classes of the Contract's vocabulary, in its order: each class's name, its
|
|
3
|
+
* languages and, for a TypeScript class, which private members it may retain. The rows are
|
|
4
|
+
* written out, as the fixed severity codes are, and a test holds them equal to the Contract.
|
|
5
|
+
* A class name is a type, so a table or a record naming a class outside the vocabulary, or
|
|
6
|
+
* one of another language only, does not compile.
|
|
11
7
|
*/
|
|
12
8
|
/** Every class of the vocabulary, in the order the Contract lists them. */
|
|
13
9
|
export const EXEMPTION_CLASSES = [
|
|
14
10
|
{
|
|
15
11
|
class: "interface-satisfaction",
|
|
16
12
|
languages: ["go", "ts"],
|
|
17
|
-
confidence: "certain",
|
|
18
13
|
typescriptVisibility: { private: false, privateName: false },
|
|
19
14
|
},
|
|
20
|
-
{ class: "encoding-reflection", languages: ["go"]
|
|
21
|
-
{ class: "format-verb-contract", languages: ["go"]
|
|
22
|
-
{ class: "errors-duck-typing", languages: ["go"]
|
|
15
|
+
{ class: "encoding-reflection", languages: ["go"] },
|
|
16
|
+
{ class: "format-verb-contract", languages: ["go"] },
|
|
17
|
+
{ class: "errors-duck-typing", languages: ["go"] },
|
|
23
18
|
{
|
|
24
19
|
class: "enum-group",
|
|
25
20
|
languages: ["go", "ts"],
|
|
26
|
-
confidence: "certain",
|
|
27
21
|
typescriptVisibility: { private: false, privateName: false },
|
|
28
22
|
},
|
|
29
23
|
{
|
|
30
24
|
class: "generated-file",
|
|
31
25
|
languages: ["go", "ts"],
|
|
32
|
-
confidence: "certain",
|
|
33
26
|
typescriptVisibility: { private: true, privateName: false },
|
|
34
27
|
},
|
|
35
|
-
{ class: "linkname-cgo-asm-plugin", languages: ["go"]
|
|
28
|
+
{ class: "linkname-cgo-asm-plugin", languages: ["go"] },
|
|
36
29
|
{
|
|
37
30
|
class: "template-field",
|
|
38
31
|
languages: ["go", "ts"],
|
|
39
|
-
confidence: "possible",
|
|
40
32
|
typescriptVisibility: { private: true, privateName: false },
|
|
41
33
|
},
|
|
42
34
|
{
|
|
43
35
|
class: "reflective-lookup",
|
|
44
36
|
languages: ["go", "ts"],
|
|
45
|
-
confidence: "possible",
|
|
46
37
|
typescriptVisibility: { private: true, privateName: false },
|
|
47
38
|
},
|
|
48
39
|
{
|
|
49
40
|
class: "decorator",
|
|
50
41
|
languages: ["ts"],
|
|
51
|
-
confidence: "certain",
|
|
52
42
|
typescriptVisibility: { private: true, privateName: false },
|
|
53
43
|
},
|
|
54
44
|
{
|
|
55
45
|
class: "injection-container",
|
|
56
46
|
languages: ["ts"],
|
|
57
|
-
confidence: "certain",
|
|
58
47
|
typescriptVisibility: { private: true, privateName: false },
|
|
59
48
|
},
|
|
60
49
|
{
|
|
61
50
|
class: "framework-lifecycle",
|
|
62
51
|
languages: ["ts"],
|
|
63
|
-
confidence: "certain",
|
|
64
52
|
typescriptVisibility: { private: true, privateName: false },
|
|
65
53
|
},
|
|
66
54
|
{
|
|
67
55
|
class: "serialization-contract",
|
|
68
56
|
languages: ["ts"],
|
|
69
|
-
confidence: "certain",
|
|
70
57
|
typescriptVisibility: { private: true, privateName: false },
|
|
71
58
|
},
|
|
72
59
|
];
|
package/dist/src/exempt.js
CHANGED
|
@@ -80,23 +80,11 @@ function detected(exemptionClass, detect, input) {
|
|
|
80
80
|
.map((symbol) => ({ id: symbol.id, detail: UNANSWERED_DETAIL, site: symbol.position }));
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
-
/**
|
|
84
|
-
* The records one project's detectors found, each class in the vocabulary's order and
|
|
85
|
-
* none of a switched-off class. Evidence on a private member the class's row says it
|
|
86
|
-
* cannot retain is dropped first, whatever the detector found. Under a production mode
|
|
87
|
-
* a record whose evidence is written in a test file holds nothing, as a reference a
|
|
88
|
-
* test file makes is none there.
|
|
89
|
-
*/
|
|
90
|
-
export function computeExemptions(input, detectors, holding) {
|
|
91
|
-
return detectExemptions(input, detectors, holding).records;
|
|
92
|
-
}
|
|
93
|
-
/** The records of {@link computeExemptions}, beside the test-file evidence it set aside. */
|
|
94
|
-
export function detectExemptions(input, detectors, holding) {
|
|
95
|
-
return heldRecords(evidenceRecords(input, detectors, holding.disabled), holding);
|
|
96
|
-
}
|
|
97
83
|
/**
|
|
98
84
|
* Every record one project's detectors found, one per piece of evidence, before the
|
|
99
|
-
* mode decides which of them hold: what {@link heldRecords} reads.
|
|
85
|
+
* mode decides which of them hold: what {@link heldRecords} reads. Classes run in the
|
|
86
|
+
* vocabulary's order and a switched-off class runs not at all; evidence on a private
|
|
87
|
+
* member the class's row says it cannot retain is dropped, whatever the detector found.
|
|
100
88
|
*/
|
|
101
89
|
export function evidenceRecords(input, detectors, disabled) {
|
|
102
90
|
const visibility = new Map(input.held.symbols.map((symbol) => [symbol.id, symbol.visibility]));
|
|
@@ -64,6 +64,29 @@ function reachabilityClasses(input) {
|
|
|
64
64
|
return published.has(id) ? outside : "certain";
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* The confidence ceiling of a test of dead code: the lowest reachability class among the
|
|
69
|
+
* target symbols it references, because its deadness is theirs, whether or not they share
|
|
70
|
+
* its component. Every other declaration has none below `certain`.
|
|
71
|
+
*/
|
|
72
|
+
function testCeilings(input, classOf) {
|
|
73
|
+
const union = input.swept.matrix.union;
|
|
74
|
+
const tests = new Set(input.swept.sweep.candidates.filter((one) => one.testOfDeadCode).map((one) => one.id));
|
|
75
|
+
return (id) => {
|
|
76
|
+
let lowest = "certain";
|
|
77
|
+
if (!tests.has(id)) {
|
|
78
|
+
return lowest;
|
|
79
|
+
}
|
|
80
|
+
for (const edge of union.out[union.at(id)] ?? []) {
|
|
81
|
+
const target = union.symbols[edge.to];
|
|
82
|
+
const testCode = union.test[edge.to] === true || union.support[edge.to] === true;
|
|
83
|
+
if (target !== undefined && !testCode && union.subject[edge.to] === true) {
|
|
84
|
+
lowest = capped(lowest, classOf(target.id));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return lowest;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
67
90
|
/**
|
|
68
91
|
* The component each dead declaration of the run falls in, and none for a live one, with
|
|
69
92
|
* its members listed in the canonical order where the run lists a component in full.
|
|
@@ -120,6 +143,7 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
120
143
|
const positions = new Map(union.symbols.map((symbol, at) => [declarationKey(symbol.ref, symbol.position.path), at]));
|
|
121
144
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
122
145
|
const classOf = reachabilityClasses(input);
|
|
146
|
+
const ceilingOf = testCeilings(input, classOf);
|
|
123
147
|
const componentOf = findingComponents(swept, input.config.reporters.cascade);
|
|
124
148
|
let minted = swept.sweep.components.length;
|
|
125
149
|
const dead = new Set(swept.sweep.components.map((component) => component.id));
|
|
@@ -173,6 +197,10 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
173
197
|
: { production: candidate.productionRefs, test: candidate.testRefs };
|
|
174
198
|
const reachabilityClass = judged === undefined ? (finding.reachabilityClass ?? "certain") : classOf(judged.id);
|
|
175
199
|
const generated = input.files.generated?.has(finding.position.path) === true;
|
|
200
|
+
const fixability = generated ? "none" : row.fixability;
|
|
201
|
+
const lastUses = fixability === "deletable" && candidate !== undefined
|
|
202
|
+
? input.dependencies.lastUses.get(candidate.id)
|
|
203
|
+
: undefined;
|
|
176
204
|
return {
|
|
177
205
|
code: finding.code,
|
|
178
206
|
kind: row.name,
|
|
@@ -180,7 +208,7 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
180
208
|
position: finding.position,
|
|
181
209
|
symbol: finding.symbol,
|
|
182
210
|
reachabilityClass,
|
|
183
|
-
confidence: capped(reachabilityClass, row.maxClass),
|
|
211
|
+
confidence: capped(capped(reachabilityClass, row.maxClass), judged === undefined ? "certain" : ceilingOf(judged.id)),
|
|
184
212
|
...(candidate === undefined ? {} : { livenessRelation: candidate.relation }),
|
|
185
213
|
testOnly: judged !== undefined && counts?.production === 0 && counts.test > 0,
|
|
186
214
|
generated,
|
|
@@ -191,10 +219,12 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
191
219
|
? swept.matrix.configurations
|
|
192
220
|
: (swept.matrix.heldIn[at] ?? swept.matrix.configurations)),
|
|
193
221
|
consumersLoaded: input.boundary.consumers.loaded,
|
|
194
|
-
fixability
|
|
222
|
+
fixability,
|
|
195
223
|
severity: severityOf(input, finding.code, row.defaultSeverity),
|
|
196
224
|
message: finding.message,
|
|
197
|
-
details:
|
|
225
|
+
details: lastUses === undefined
|
|
226
|
+
? (finding.details ?? {})
|
|
227
|
+
: { ...finding.details, removesLastUseOf: lastUses },
|
|
198
228
|
};
|
|
199
229
|
});
|
|
200
230
|
return componentCapped(done, dead);
|
|
@@ -235,6 +265,7 @@ function dialed(finding, minConfidence) {
|
|
|
235
265
|
export function dialsOf(input, findings) {
|
|
236
266
|
const minConfidence = input.config.analysis.minConfidence;
|
|
237
267
|
const classOf = reachabilityClasses(input);
|
|
268
|
+
const ceilingOf = testCeilings(input, classOf);
|
|
238
269
|
const components = new Set(findings
|
|
239
270
|
.filter((finding) => finding.component.root && dialed(finding, minConfidence))
|
|
240
271
|
.map((finding) => finding.component.id));
|
|
@@ -244,7 +275,7 @@ export function dialsOf(input, findings) {
|
|
|
244
275
|
return (row !== undefined &&
|
|
245
276
|
dialed({
|
|
246
277
|
severity: severityOf(input, code, row.defaultSeverity),
|
|
247
|
-
confidence: capped(classOf(id), row.maxClass),
|
|
278
|
+
confidence: capped(capped(classOf(id), row.maxClass), ceilingOf(id)),
|
|
248
279
|
}, minConfidence));
|
|
249
280
|
},
|
|
250
281
|
withholds: (finding) => dialed(finding, minConfidence) || components.has(finding.component.id),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { REPOSITORY_DOCUMENT } from "../config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { FIXED_SEVERITY_CODES } from "../kinds.js";
|
|
3
|
+
import { isRowCode, STALE_SUPPRESSION, SUPPRESSION_WITHOUT_REASON, } from "../suppress.js";
|
|
3
4
|
/** The code a configured root that matches nothing is reported under. */
|
|
4
5
|
const UNMATCHED_ROOT = "DS1704";
|
|
5
6
|
/** The code a configured declaration that names nothing is reported under. */
|
|
@@ -12,13 +13,6 @@ const SUPPRESSION_SUBJECT = "suppression";
|
|
|
12
13
|
const ROOT_SUBJECT = "root";
|
|
13
14
|
/** The fixed position of a finding about a document rather than about a line of one. */
|
|
14
15
|
const DOCUMENT_POSITION = 1;
|
|
15
|
-
/** A run with nothing for the self-check family to report. */
|
|
16
|
-
export const NO_SELF_CHECK = {
|
|
17
|
-
refusals: [],
|
|
18
|
-
unmatchedRoots: [],
|
|
19
|
-
rootsDocument: "",
|
|
20
|
-
unmatchedDeclarations: [],
|
|
21
|
-
};
|
|
22
16
|
/** The words a message names a mechanism by. */
|
|
23
17
|
const WRITTEN = {
|
|
24
18
|
inline: "inline directive",
|
|
@@ -68,7 +62,7 @@ function suppressionRef(symbol, path, files) {
|
|
|
68
62
|
return symbol === "" ? (files(path) ?? path) : symbol;
|
|
69
63
|
}
|
|
70
64
|
/** One finding per refused suppression: the grammar's reason refusal and its scope refusal. */
|
|
71
|
-
|
|
65
|
+
function refusedSuppressions(refusals, files) {
|
|
72
66
|
return refusals.map((refused) => aboutSuppression(refused.reported, suppressionRef(refused.symbol, refused.path, files), refused.site, `${WRITTEN[refused.mechanism]} for ${refused.code} ${refused.reported === SUPPRESSION_WITHOUT_REASON
|
|
73
67
|
? "carries no reason"
|
|
74
68
|
: "names a symbol and no path"}`, { mechanism: refused.mechanism, entry: writtenEntry(refused) }));
|
|
@@ -93,7 +87,17 @@ export function staleSuppressions(stale, files) {
|
|
|
93
87
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
94
88
|
.filter((code) => !codes.includes(code))
|
|
95
89
|
.sort();
|
|
96
|
-
const
|
|
90
|
+
const rows = codes.filter(isRowCode);
|
|
91
|
+
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
92
|
+
const remedy = settable.length === 0
|
|
93
|
+
? "fix what the finding names instead"
|
|
94
|
+
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
95
|
+
const declared = reports.length === 0 ? "" : `its declaration reports ${spelled(reports)}`;
|
|
96
|
+
const instead = rows.length > 0
|
|
97
|
+
? `, because ${spelled(rows)} ${rows.length === 1 ? "reports a row" : "report rows"} of a document, which no suppression can name; ${remedy}${declared === "" ? "" : `; ${declared}`}`
|
|
98
|
+
: declared === ""
|
|
99
|
+
? ""
|
|
100
|
+
: `, and ${declared}`;
|
|
97
101
|
return {
|
|
98
102
|
code: STALE_SUPPRESSION,
|
|
99
103
|
mechanism: first.mechanism,
|
|
@@ -153,7 +153,10 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
153
153
|
const carried = new Map();
|
|
154
154
|
const names = new Set();
|
|
155
155
|
const sites = [];
|
|
156
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* Calls passing a literal argument whose callee a package binding heads, or that call a
|
|
158
|
+
* method, which a dependency's generic may fix.
|
|
159
|
+
*/
|
|
157
160
|
const literalCalls = [];
|
|
158
161
|
const carry = (generic, one) => {
|
|
159
162
|
const known = carried.get(generic) ?? [];
|
|
@@ -205,7 +208,10 @@ export function indexedAccessReferences(project, held, chains, root, testFiles)
|
|
|
205
208
|
sites.push(site);
|
|
206
209
|
const literal = (node.arguments ?? []).some((argument) => isStringLiteral(argument) || isNumericLiteral(argument));
|
|
207
210
|
const head = rootName(callee);
|
|
208
|
-
|
|
211
|
+
const method = isPropertyAccessExpression(callee);
|
|
212
|
+
if (literal &&
|
|
213
|
+
isCallExpression(node) &&
|
|
214
|
+
(method || (head !== undefined && imported.has(head)))) {
|
|
209
215
|
literalCalls.push(site);
|
|
210
216
|
}
|
|
211
217
|
}
|
package/dist/src/inventory.js
CHANGED
|
@@ -259,9 +259,7 @@ export function inventory(project, host, targetRoot) {
|
|
|
259
259
|
const byNode = new Map();
|
|
260
260
|
const exportScopes = [];
|
|
261
261
|
const containers = [];
|
|
262
|
-
const starReExports = [];
|
|
263
262
|
const modules = [];
|
|
264
|
-
let outsideOwnFiles = 0;
|
|
265
263
|
const keep = (record) => {
|
|
266
264
|
building.push(record);
|
|
267
265
|
byNode.set(nodeKey(record.file, record.node), record);
|
|
@@ -493,9 +491,8 @@ export function inventory(project, host, targetRoot) {
|
|
|
493
491
|
}
|
|
494
492
|
function walkExportDeclaration(node, owner) {
|
|
495
493
|
const clause = node.exportClause;
|
|
494
|
+
// A bare star re-export declares nothing: the names it carries are the named module's.
|
|
496
495
|
if (clause === undefined) {
|
|
497
|
-
const specifier = node.moduleSpecifier;
|
|
498
|
-
starReExports.push(specifier === undefined ? "" : literalTextOf(specifier));
|
|
499
496
|
return;
|
|
500
497
|
}
|
|
501
498
|
if (isNamespaceExport(clause)) {
|
|
@@ -579,7 +576,7 @@ export function inventory(project, host, targetRoot) {
|
|
|
579
576
|
memberTables += 1;
|
|
580
577
|
const table = project.queries.membersOf(symbol);
|
|
581
578
|
if (isAnswered(table)) {
|
|
582
|
-
|
|
579
|
+
readTable(container, table, seen, false);
|
|
583
580
|
}
|
|
584
581
|
else {
|
|
585
582
|
unansweredOwners.add(container.owner);
|
|
@@ -589,7 +586,7 @@ export function inventory(project, host, targetRoot) {
|
|
|
589
586
|
memberTables += 1;
|
|
590
587
|
const table = project.queries.exportsOf(symbol);
|
|
591
588
|
if (isAnswered(table)) {
|
|
592
|
-
|
|
589
|
+
readTable(container, table, seen, container.isClass);
|
|
593
590
|
}
|
|
594
591
|
else {
|
|
595
592
|
unansweredOwners.add(container.owner);
|
|
@@ -605,7 +602,6 @@ export function inventory(project, host, targetRoot) {
|
|
|
605
602
|
* belongs to another program; neither is a declaration of the target.
|
|
606
603
|
*/
|
|
607
604
|
function readTable(container, table, seen, areStatic) {
|
|
608
|
-
let outside = 0;
|
|
609
605
|
for (const [, member] of table) {
|
|
610
606
|
const declarations = member.declarations
|
|
611
607
|
.map((handle) => project.declarationAt(handle))
|
|
@@ -615,7 +611,6 @@ export function inventory(project, host, targetRoot) {
|
|
|
615
611
|
}
|
|
616
612
|
const own = declarations.filter((held) => ownFiles.has(held.node.getSourceFile().fileName));
|
|
617
613
|
if (own.length === 0) {
|
|
618
|
-
outside += declarations.length;
|
|
619
614
|
continue;
|
|
620
615
|
}
|
|
621
616
|
// A merged symbol's table holds the members every one of its declarations writes,
|
|
@@ -624,7 +619,6 @@ export function inventory(project, host, targetRoot) {
|
|
|
624
619
|
keepMember(container, held.node, seen, areStatic);
|
|
625
620
|
}
|
|
626
621
|
}
|
|
627
|
-
return outside;
|
|
628
622
|
}
|
|
629
623
|
/**
|
|
630
624
|
* One container's members as its own declaration writes them, for the members its
|
|
@@ -704,12 +698,9 @@ export function inventory(project, host, targetRoot) {
|
|
|
704
698
|
.map(render)
|
|
705
699
|
.sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
|
|
706
700
|
return {
|
|
707
|
-
configFile: project.configFile,
|
|
708
701
|
symbols,
|
|
709
702
|
declarations: new Map([...byNode].map(([key, record]) => [key, positionKey(record.position)])),
|
|
710
703
|
cost: { batches: 1, exportTables, memberTables },
|
|
711
|
-
starReExports,
|
|
712
|
-
outsideOwnFiles,
|
|
713
704
|
unanswered: symbols
|
|
714
705
|
.filter((symbol) => underUnanswered(byId.get(symbol.id), unansweredOwners))
|
|
715
706
|
.map((symbol) => symbol.id),
|
package/dist/src/references.js
CHANGED
|
@@ -816,7 +816,8 @@ export function references(project, held, targetRoot, options) {
|
|
|
816
816
|
const testFile = new Map();
|
|
817
817
|
for (const { file } of walked) {
|
|
818
818
|
const path = renderPosition(file, root, 0).path;
|
|
819
|
-
const test = patterns.some((pattern) => pattern.test(path))
|
|
819
|
+
const test = patterns.some((pattern) => pattern.test(path)) ||
|
|
820
|
+
options.testFileNames?.has(file.fileName) === true;
|
|
820
821
|
testFile.set(file, test);
|
|
821
822
|
if (test) {
|
|
822
823
|
tests.push(path);
|
|
@@ -1120,7 +1121,6 @@ export function references(project, held, targetRoot, options) {
|
|
|
1120
1121
|
compare(a.from, b.from) ||
|
|
1121
1122
|
compare(a.use, b.use));
|
|
1122
1123
|
return {
|
|
1123
|
-
configFile: project.configFile,
|
|
1124
1124
|
references: consumer === undefined
|
|
1125
1125
|
? found
|
|
1126
1126
|
: found.map((reference) => ({ ...reference, consumer: consumer.id })),
|
package/dist/src/report.js
CHANGED
|
@@ -55,6 +55,9 @@ function wireDetails(details) {
|
|
|
55
55
|
},
|
|
56
56
|
}),
|
|
57
57
|
...(details.overlap === undefined ? {} : { overlap: details.overlap }),
|
|
58
|
+
...(details.removesLastUseOf === undefined
|
|
59
|
+
? {}
|
|
60
|
+
: { removes_last_use_of: details.removesLastUseOf }),
|
|
58
61
|
};
|
|
59
62
|
}
|
|
60
63
|
/** One finding as the finding schema spells it, member for member in its order. */
|
package/dist/src/roots.js
CHANGED
|
@@ -268,7 +268,6 @@ export function roots(project, held, targetRoot, options) {
|
|
|
268
268
|
return site || compare(a.kind, b.kind) || compare(a.source, b.source);
|
|
269
269
|
});
|
|
270
270
|
return {
|
|
271
|
-
configFile: project.configFile,
|
|
272
271
|
liveUnderReachability: ordered,
|
|
273
272
|
unmatched,
|
|
274
273
|
unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
|
package/dist/src/run.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ConfigError, REPOSITORY_DOCUMENT } from "./config.js";
|
|
2
2
|
import { DiscoveryError, renderDiagnostic } from "./discover.js";
|
|
3
|
-
import { EMITTERS } from "./findings/emitters.js";
|
|
4
3
|
import { joinPath, resolvePath } from "./paths.js";
|
|
5
4
|
import { readScope, ScopeError, scopeForDir } from "./scope.js";
|
|
6
5
|
import { SetupError, setupLine } from "./setup-failure.js";
|
|
@@ -201,7 +200,6 @@ function invoke(verb, own, args, streams, openClient) {
|
|
|
201
200
|
});
|
|
202
201
|
return opened;
|
|
203
202
|
},
|
|
204
|
-
emitters: EMITTERS,
|
|
205
203
|
});
|
|
206
204
|
}
|
|
207
205
|
catch (error) {
|
package/dist/src/schema.js
CHANGED
|
@@ -216,7 +216,7 @@ export function malformed(label, path, detail) {
|
|
|
216
216
|
* A refusal of a key the closed key list does not declare, naming the nearest key
|
|
217
217
|
* it does.
|
|
218
218
|
*/
|
|
219
|
-
|
|
219
|
+
function unimplementedKey(label, path) {
|
|
220
220
|
const nearest = nearestKey(path);
|
|
221
221
|
let hint = "";
|
|
222
222
|
if (nearest === path) {
|
package/dist/src/scope.js
CHANGED
|
@@ -2,8 +2,8 @@ import { ConfigError } from "./config.js";
|
|
|
2
2
|
import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.js";
|
|
3
3
|
import { checkDocument } from "./schema.js";
|
|
4
4
|
/** The roles a scope document declares for a module. */
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const ROLE_TARGET = "target";
|
|
6
|
+
const ROLE_CONSUMER = "consumer";
|
|
7
7
|
/**
|
|
8
8
|
* A scope document above this length is refused rather than decoded. It names one
|
|
9
9
|
* target and its consumers, so a larger file is not one.
|
package/dist/src/session.js
CHANGED
|
@@ -190,7 +190,6 @@ export function ownedAs(project, own) {
|
|
|
190
190
|
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
191
191
|
export function diagnosticsOf(project) {
|
|
192
192
|
return {
|
|
193
|
-
configFile: project.configFile,
|
|
194
193
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
195
194
|
semantic: project.program.getSemanticDiagnostics(),
|
|
196
195
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
|
@@ -95,7 +95,7 @@ export function tokenStart(text, from) {
|
|
|
95
95
|
* syntax tree holds are skipped, so a `//` inside a string, a template or a regular
|
|
96
96
|
* expression starts nothing.
|
|
97
97
|
*/
|
|
98
|
-
|
|
98
|
+
function lineComments(file) {
|
|
99
99
|
const text = file.text;
|
|
100
100
|
const spans = [];
|
|
101
101
|
const visit = (node) => {
|
package/dist/src/suppress.js
CHANGED
|
@@ -14,8 +14,6 @@ export const SUPPRESSION_WITHOUT_REASON = "DS1701";
|
|
|
14
14
|
export const UNSCOPED_ENTRY = "DS1702";
|
|
15
15
|
/** The code a suppression that withholds nothing is reported under. */
|
|
16
16
|
export const STALE_SUPPRESSION = "DS1703";
|
|
17
|
-
/** A target that carries no suppression. */
|
|
18
|
-
export const NO_SUPPRESSIONS = { records: [], refusals: [] };
|
|
19
17
|
/**
|
|
20
18
|
* A directive, an entry or a document the grammar refuses before any finding exists: an
|
|
21
19
|
* instruction the analysis cannot carry out. It is a malformed input, so the command line
|
|
@@ -71,22 +69,27 @@ export function blankReason(reason) {
|
|
|
71
69
|
return reason === undefined || /^[ \t\r\n]*$/u.test(reason);
|
|
72
70
|
}
|
|
73
71
|
/**
|
|
74
|
-
* The subject kinds that are a row of a document rather than a declaration of the program
|
|
75
|
-
* A record binds to a declaration, so nothing
|
|
72
|
+
* The subject kinds that are a row of a document rather than a declaration of the program,
|
|
73
|
+
* each with the codes that report one. A record binds to a declaration, so nothing
|
|
74
|
+
* withholds a finding about one.
|
|
76
75
|
*/
|
|
77
|
-
const ROW_SUBJECTS = new
|
|
78
|
-
"file",
|
|
79
|
-
"dependency",
|
|
80
|
-
"module-directive",
|
|
81
|
-
"root",
|
|
82
|
-
"configured-declaration",
|
|
83
|
-
"suppression",
|
|
84
|
-
"edge",
|
|
76
|
+
const ROW_SUBJECTS = new Map([
|
|
77
|
+
["file", ["DS1501", "DS1502"]],
|
|
78
|
+
["dependency", ["DS1601"]],
|
|
79
|
+
["module-directive", ["DS1605"]],
|
|
80
|
+
["root", ["DS1704"]],
|
|
81
|
+
["configured-declaration", ["DS1706"]],
|
|
82
|
+
["suppression", ["DS1701", "DS1702", "DS1703"]],
|
|
83
|
+
["edge", ["DS1705"]],
|
|
85
84
|
]);
|
|
86
85
|
/** Whether a finding's subject is a row of a document, which no record can withhold. */
|
|
87
86
|
export function isRowSubject(kind) {
|
|
88
87
|
return ROW_SUBJECTS.has(kind);
|
|
89
88
|
}
|
|
89
|
+
/** Whether a code reports a row of a document, so no record naming it can match. */
|
|
90
|
+
export function isRowCode(code) {
|
|
91
|
+
return [...ROW_SUBJECTS.values()].some((codes) => codes.includes(code));
|
|
92
|
+
}
|
|
90
93
|
/**
|
|
91
94
|
* The subject kinds that are a part of a declaration rather than a declaration: a part
|
|
92
95
|
* has no reference of its own, so a finding about one names the declaration that holds
|
|
@@ -143,15 +146,12 @@ export function pairs(record, finding) {
|
|
|
143
146
|
export function ledgerOf(records, would, dials) {
|
|
144
147
|
const taken = new Set();
|
|
145
148
|
const verdicts = [];
|
|
146
|
-
const claims = [];
|
|
147
149
|
for (const record of records) {
|
|
148
150
|
if (record.bound !== "" && dials.withholdsCode(record.code, record.bound)) {
|
|
149
151
|
verdicts.push("dormant");
|
|
150
|
-
claims.push(undefined);
|
|
151
152
|
continue;
|
|
152
153
|
}
|
|
153
154
|
const claimed = would.find((finding) => !taken.has(claimKey(finding)) && pairs(record, finding));
|
|
154
|
-
claims.push(claimed);
|
|
155
155
|
if (claimed === undefined) {
|
|
156
156
|
verdicts.push("stale");
|
|
157
157
|
continue;
|
|
@@ -161,7 +161,6 @@ export function ledgerOf(records, would, dials) {
|
|
|
161
161
|
}
|
|
162
162
|
return {
|
|
163
163
|
verdicts,
|
|
164
|
-
claims,
|
|
165
164
|
withheld: (finding) => taken.has(claimKey(finding)) && !ROW_SUBJECTS.has(finding.symbol.kind),
|
|
166
165
|
};
|
|
167
166
|
}
|
|
@@ -205,8 +205,6 @@ function scan(engine, program, reading, resolver) {
|
|
|
205
205
|
reading.unresolved.push({
|
|
206
206
|
file: usage.file,
|
|
207
207
|
specifier,
|
|
208
|
-
member: answer.member,
|
|
209
|
-
subpath: answer.subpath,
|
|
210
208
|
reason: answer.reason,
|
|
211
209
|
});
|
|
212
210
|
}
|
|
@@ -309,7 +307,6 @@ export function workspacePrograms(engine, host, configFiles, resolver) {
|
|
|
309
307
|
readings: readings.map((reading) => ({
|
|
310
308
|
configFile: reading.configFile,
|
|
311
309
|
fileNames: reading.parsed.fileNames,
|
|
312
|
-
entries: reading.entries,
|
|
313
310
|
unresolved: reading.unresolved,
|
|
314
311
|
componentsUnread: unread.get(reading.configFile),
|
|
315
312
|
})),
|
|
@@ -259,7 +259,6 @@ export function workspaceResolver(host, workspace) {
|
|
|
259
259
|
return {
|
|
260
260
|
kind: "unresolved",
|
|
261
261
|
member: name,
|
|
262
|
-
subpath,
|
|
263
262
|
reason: `${String(linked.dirs.length)} workspace packages are named ${name} (${linked.dirs.join(", ")}) and nothing links the importer to one of them; install the workspace, or declare the dependency with a range only one of them satisfies`,
|
|
264
263
|
};
|
|
265
264
|
}
|
|
@@ -295,7 +294,6 @@ export function workspaceResolver(host, workspace) {
|
|
|
295
294
|
return {
|
|
296
295
|
kind: "unresolved",
|
|
297
296
|
member: name,
|
|
298
|
-
subpath,
|
|
299
297
|
reason: built
|
|
300
298
|
? `its manifest names ${named}, output no tsconfig*.json of the package maps back to source, and nothing installed links the importer to it; install the workspace, or point an exports condition the importing configuration's customConditions lists at its source`
|
|
301
299
|
: member.configurations.length === 0
|
package/dist/src/workspace.js
CHANGED
|
@@ -256,7 +256,6 @@ function configurationsOf(host, dir, parse) {
|
|
|
256
256
|
? []
|
|
257
257
|
: [
|
|
258
258
|
{
|
|
259
|
-
configFile,
|
|
260
259
|
fileNames: new Set(parsed.fileNames),
|
|
261
260
|
layout: layoutOf(configFile, parsed),
|
|
262
261
|
},
|
|
@@ -338,8 +337,6 @@ export function findWorkspace(host, targetRoot, parse) {
|
|
|
338
337
|
return found;
|
|
339
338
|
};
|
|
340
339
|
return {
|
|
341
|
-
root: declared.root,
|
|
342
|
-
declaredIn: declared.file,
|
|
343
340
|
members,
|
|
344
341
|
named,
|
|
345
342
|
memberOf: (path) => {
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/deadset-ts",
|
|
3
|
-
"version": "7.0.0-dev.
|
|
3
|
+
"version": "7.0.0-dev.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Deterministic dead-code analysis for TypeScript, class and type members included",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"typecheck:tests": "tsc -p tsconfig.test.json",
|
|
25
25
|
"test": "vitest --run",
|
|
26
26
|
"test:starters": "vitest --run --config starters/vitest.config.ts",
|
|
27
|
+
"test:mutation": "stryker run",
|
|
27
28
|
"lint": "eslint .",
|
|
28
29
|
"lint:prettier": "prettier --check .",
|
|
29
30
|
"lint:fix": "eslint . --fix && prettier --write ."
|