@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
|
@@ -84,6 +84,35 @@ function reachabilityClasses(input: EmitterInput): (id: string) => Confidence {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* The confidence ceiling of a test of dead code: the lowest reachability class among the
|
|
89
|
+
* target symbols it references, because its deadness is theirs, whether or not they share
|
|
90
|
+
* its component. Every other declaration has none below `certain`.
|
|
91
|
+
*/
|
|
92
|
+
function testCeilings(
|
|
93
|
+
input: EmitterInput,
|
|
94
|
+
classOf: (id: string) => Confidence,
|
|
95
|
+
): (id: string) => Confidence {
|
|
96
|
+
const union = input.swept.matrix.union;
|
|
97
|
+
const tests = new Set(
|
|
98
|
+
input.swept.sweep.candidates.filter((one) => one.testOfDeadCode).map((one) => one.id),
|
|
99
|
+
);
|
|
100
|
+
return (id) => {
|
|
101
|
+
let lowest: Confidence = "certain";
|
|
102
|
+
if (!tests.has(id)) {
|
|
103
|
+
return lowest;
|
|
104
|
+
}
|
|
105
|
+
for (const edge of union.out[union.at(id)] ?? []) {
|
|
106
|
+
const target = union.symbols[edge.to];
|
|
107
|
+
const testCode = union.test[edge.to] === true || union.support[edge.to] === true;
|
|
108
|
+
if (target !== undefined && !testCode && union.subject[edge.to] === true) {
|
|
109
|
+
lowest = capped(lowest, classOf(target.id));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return lowest;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
87
116
|
/**
|
|
88
117
|
* The component each dead declaration of the run falls in, and none for a live one, with
|
|
89
118
|
* its members listed in the canonical order where the run lists a component in full.
|
|
@@ -154,6 +183,7 @@ export function completed(
|
|
|
154
183
|
);
|
|
155
184
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
156
185
|
const classOf = reachabilityClasses(input);
|
|
186
|
+
const ceilingOf = testCeilings(input, classOf);
|
|
157
187
|
const componentOf = findingComponents(swept, input.config.reporters.cascade);
|
|
158
188
|
let minted = swept.sweep.components.length;
|
|
159
189
|
const dead = new Set(swept.sweep.components.map((component) => component.id));
|
|
@@ -212,6 +242,11 @@ export function completed(
|
|
|
212
242
|
const reachabilityClass =
|
|
213
243
|
judged === undefined ? (finding.reachabilityClass ?? "certain") : classOf(judged.id);
|
|
214
244
|
const generated = input.files.generated?.has(finding.position.path) === true;
|
|
245
|
+
const fixability = generated ? "none" : row.fixability;
|
|
246
|
+
const lastUses =
|
|
247
|
+
fixability === "deletable" && candidate !== undefined
|
|
248
|
+
? input.dependencies.lastUses.get(candidate.id)
|
|
249
|
+
: undefined;
|
|
215
250
|
return {
|
|
216
251
|
code: finding.code,
|
|
217
252
|
kind: row.name,
|
|
@@ -219,7 +254,10 @@ export function completed(
|
|
|
219
254
|
position: finding.position,
|
|
220
255
|
symbol: finding.symbol,
|
|
221
256
|
reachabilityClass,
|
|
222
|
-
confidence: capped(
|
|
257
|
+
confidence: capped(
|
|
258
|
+
capped(reachabilityClass, row.maxClass),
|
|
259
|
+
judged === undefined ? "certain" : ceilingOf(judged.id),
|
|
260
|
+
),
|
|
223
261
|
...(candidate === undefined ? {} : { livenessRelation: candidate.relation }),
|
|
224
262
|
testOnly: judged !== undefined && counts?.production === 0 && counts.test > 0,
|
|
225
263
|
generated,
|
|
@@ -231,10 +269,13 @@ export function completed(
|
|
|
231
269
|
? swept.matrix.configurations
|
|
232
270
|
: (swept.matrix.heldIn[at] ?? swept.matrix.configurations)),
|
|
233
271
|
consumersLoaded: input.boundary.consumers.loaded,
|
|
234
|
-
fixability
|
|
272
|
+
fixability,
|
|
235
273
|
severity: severityOf(input, finding.code, row.defaultSeverity),
|
|
236
274
|
message: finding.message,
|
|
237
|
-
details:
|
|
275
|
+
details:
|
|
276
|
+
lastUses === undefined
|
|
277
|
+
? (finding.details ?? {})
|
|
278
|
+
: { ...finding.details, removesLastUseOf: lastUses },
|
|
238
279
|
};
|
|
239
280
|
});
|
|
240
281
|
return componentCapped(done, dead);
|
|
@@ -287,6 +328,7 @@ export function dialsOf(
|
|
|
287
328
|
): Dials<CompletedFinding> {
|
|
288
329
|
const minConfidence = input.config.analysis.minConfidence;
|
|
289
330
|
const classOf = reachabilityClasses(input);
|
|
331
|
+
const ceilingOf = testCeilings(input, classOf);
|
|
290
332
|
const components = new Set(
|
|
291
333
|
findings
|
|
292
334
|
.filter((finding) => finding.component.root && dialed(finding, minConfidence))
|
|
@@ -300,7 +342,7 @@ export function dialsOf(
|
|
|
300
342
|
dialed(
|
|
301
343
|
{
|
|
302
344
|
severity: severityOf(input, code, row.defaultSeverity),
|
|
303
|
-
confidence: capped(classOf(id), row.maxClass),
|
|
345
|
+
confidence: capped(capped(classOf(id), row.maxClass), ceilingOf(id)),
|
|
304
346
|
},
|
|
305
347
|
minConfidence,
|
|
306
348
|
)
|
package/src/findings/emitters.ts
CHANGED
|
@@ -67,7 +67,7 @@ function markupKeys(input: EmitterInput): ReadonlySet<string> {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/** What the suppressions of a run swept under their marks are decided against. */
|
|
70
|
-
|
|
70
|
+
interface Suppressed {
|
|
71
71
|
/** The same run swept with no mark, which is where a kind about liveness reports a marked declaration. */
|
|
72
72
|
readonly unmarked: EmitterInput;
|
|
73
73
|
/** The run's suppression records, in reading order. */
|
|
@@ -75,13 +75,13 @@ export interface Suppressed {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/** What the emitter table answers about one run. */
|
|
78
|
-
|
|
78
|
+
interface DecidedFindings {
|
|
79
79
|
/** Every finding the run reports, each family's in table order. */
|
|
80
80
|
readonly findings: readonly CompletedFinding[];
|
|
81
81
|
/** One evaluation per declared edge side of this language, ordered by edge, then by side. */
|
|
82
82
|
readonly evaluations: readonly EdgeEvaluation<CompletedFinding>[];
|
|
83
83
|
/** What every suppression record did, in reading order. */
|
|
84
|
-
readonly ledger: Ledger
|
|
84
|
+
readonly ledger: Ledger;
|
|
85
85
|
/** Every finding a record could name: the run's with no mark, then the run's as swept. */
|
|
86
86
|
readonly would: readonly CompletedFinding[];
|
|
87
87
|
/** The findings the minimum confidence withheld, by confidence. */
|
|
@@ -198,8 +198,3 @@ function withheldOf(input: EmitterInput, candidates: readonly CompletedFinding[]
|
|
|
198
198
|
}
|
|
199
199
|
return counts;
|
|
200
200
|
}
|
|
201
|
-
|
|
202
|
-
/** Every finding the run reports, each family's in table order. */
|
|
203
|
-
export function findingsOf(input: EmitterInput): readonly CompletedFinding[] {
|
|
204
|
-
return decidedFindings(input).findings;
|
|
205
|
-
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { REPOSITORY_DOCUMENT } from "../config.ts";
|
|
2
2
|
import type { Finding, FindingDetails, SuppressionEntry } from "../finding.ts";
|
|
3
3
|
import type { InventorySymbol } from "../inventory.ts";
|
|
4
|
+
import { FIXED_SEVERITY_CODES } from "../kinds.ts";
|
|
4
5
|
import type { Position } from "../position.ts";
|
|
5
6
|
import {
|
|
7
|
+
isRowCode,
|
|
6
8
|
STALE_SUPPRESSION,
|
|
7
9
|
SUPPRESSION_WITHOUT_REASON,
|
|
8
10
|
type Mechanism,
|
|
@@ -58,14 +60,6 @@ interface UnmatchedDeclaration {
|
|
|
58
60
|
readonly document: string;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
/** A run with nothing for the self-check family to report. */
|
|
62
|
-
export const NO_SELF_CHECK: SelfCheckFacts = {
|
|
63
|
-
refusals: [],
|
|
64
|
-
unmatchedRoots: [],
|
|
65
|
-
rootsDocument: "",
|
|
66
|
-
unmatchedDeclarations: [],
|
|
67
|
-
};
|
|
68
|
-
|
|
69
63
|
/** The words a message names a mechanism by. */
|
|
70
64
|
const WRITTEN: Readonly<Record<Mechanism, string>> = {
|
|
71
65
|
inline: "inline directive",
|
|
@@ -122,7 +116,7 @@ function writtenEntry(refused: Refusal): SuppressionEntry {
|
|
|
122
116
|
}
|
|
123
117
|
|
|
124
118
|
/** Each file of the run by its path, as the reference of the file it is. */
|
|
125
|
-
|
|
119
|
+
type FileRefs = (path: string) => string | undefined;
|
|
126
120
|
|
|
127
121
|
/**
|
|
128
122
|
* The reference a finding about a suppression names: the symbol it names or bound, else
|
|
@@ -133,10 +127,7 @@ function suppressionRef(symbol: string, path: string, files: FileRefs): string {
|
|
|
133
127
|
}
|
|
134
128
|
|
|
135
129
|
/** One finding per refused suppression: the grammar's reason refusal and its scope refusal. */
|
|
136
|
-
|
|
137
|
-
refusals: readonly Refusal[],
|
|
138
|
-
files: FileRefs,
|
|
139
|
-
): readonly Finding[] {
|
|
130
|
+
function refusedSuppressions(refusals: readonly Refusal[], files: FileRefs): readonly Finding[] {
|
|
140
131
|
return refusals.map((refused) =>
|
|
141
132
|
aboutSuppression(
|
|
142
133
|
refused.reported,
|
|
@@ -190,7 +181,19 @@ export function staleSuppressions(
|
|
|
190
181
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
191
182
|
.filter((code) => !codes.includes(code))
|
|
192
183
|
.sort();
|
|
193
|
-
const
|
|
184
|
+
const rows = codes.filter(isRowCode);
|
|
185
|
+
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
186
|
+
const remedy =
|
|
187
|
+
settable.length === 0
|
|
188
|
+
? "fix what the finding names instead"
|
|
189
|
+
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
190
|
+
const declared = reports.length === 0 ? "" : `its declaration reports ${spelled(reports)}`;
|
|
191
|
+
const instead =
|
|
192
|
+
rows.length > 0
|
|
193
|
+
? `, because ${spelled(rows)} ${rows.length === 1 ? "reports a row" : "report rows"} of a document, which no suppression can name; ${remedy}${declared === "" ? "" : `; ${declared}`}`
|
|
194
|
+
: declared === ""
|
|
195
|
+
? ""
|
|
196
|
+
: `, and ${declared}`;
|
|
194
197
|
return {
|
|
195
198
|
code: STALE_SUPPRESSION,
|
|
196
199
|
mechanism: first.mechanism,
|
package/src/findings-pass.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type { Provenance, Recorded } from "./suppress-file.ts";
|
|
|
20
20
|
import { EXIT_CLEAN, EXIT_FINDINGS } from "./verbs/verb.ts";
|
|
21
21
|
|
|
22
22
|
/** The counts a report's totals carry beside its findings. */
|
|
23
|
-
|
|
23
|
+
interface PassTotals {
|
|
24
24
|
readonly suppressionsInEffect: number;
|
|
25
25
|
readonly reasonsRecorded: number;
|
|
26
26
|
readonly staleSuppressions: number;
|
|
@@ -40,7 +40,7 @@ export interface PassResult {
|
|
|
40
40
|
readonly edgeEvaluations: readonly EdgeEvaluation<CompletedFinding>[];
|
|
41
41
|
readonly totals: PassTotals;
|
|
42
42
|
/** What every suppression record did, in reading order. */
|
|
43
|
-
readonly ledger: Ledger
|
|
43
|
+
readonly ledger: Ledger;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** The exit code of a report holding at least one pending finding. */
|
package/src/graph.ts
CHANGED
|
@@ -87,13 +87,13 @@ export interface Edge {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/** How many references one declaration carries, split by the classification of the file that made each. */
|
|
90
|
-
|
|
90
|
+
interface Counts {
|
|
91
91
|
readonly production: number;
|
|
92
92
|
readonly test: number;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/** One root the inventory holds, at the position of the declaration it names. */
|
|
96
|
-
|
|
96
|
+
interface Rooted {
|
|
97
97
|
readonly at: number;
|
|
98
98
|
readonly kind: RootKind;
|
|
99
99
|
}
|
package/src/html-entries.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
|
|
|
16
16
|
import type { SourceFiles } from "./source-files.ts";
|
|
17
17
|
|
|
18
18
|
/** One file a script loads, and the value or specifier that named it. */
|
|
19
|
-
|
|
19
|
+
interface HTMLEntry {
|
|
20
20
|
readonly file: SourceFile;
|
|
21
21
|
readonly source: string;
|
|
22
22
|
}
|
package/src/indexed-access.ts
CHANGED
|
@@ -271,7 +271,10 @@ export function indexedAccessReferences<Brand>(
|
|
|
271
271
|
const carried = new Map<Generic, Carried[]>();
|
|
272
272
|
const names = new Set<string>();
|
|
273
273
|
const sites: Site[] = [];
|
|
274
|
-
/**
|
|
274
|
+
/**
|
|
275
|
+
* Calls passing a literal argument whose callee a package binding heads, or that call a
|
|
276
|
+
* method, which a dependency's generic may fix.
|
|
277
|
+
*/
|
|
275
278
|
const literalCalls: Site[] = [];
|
|
276
279
|
|
|
277
280
|
const carry = (generic: Generic, one: Carried): boolean => {
|
|
@@ -331,7 +334,12 @@ export function indexedAccessReferences<Brand>(
|
|
|
331
334
|
(argument) => isStringLiteral(argument) || isNumericLiteral(argument),
|
|
332
335
|
);
|
|
333
336
|
const head = rootName(callee);
|
|
334
|
-
|
|
337
|
+
const method = isPropertyAccessExpression(callee);
|
|
338
|
+
if (
|
|
339
|
+
literal &&
|
|
340
|
+
isCallExpression(node) &&
|
|
341
|
+
(method || (head !== undefined && imported.has(head)))
|
|
342
|
+
) {
|
|
335
343
|
literalCalls.push(site);
|
|
336
344
|
}
|
|
337
345
|
}
|
package/src/inventory.ts
CHANGED
|
@@ -139,8 +139,6 @@ export interface InventoryCost {
|
|
|
139
139
|
|
|
140
140
|
/** One project's declarations, in position order, and what reading them cost. */
|
|
141
141
|
export interface Inventory {
|
|
142
|
-
/** The compiler configuration the project was opened from. */
|
|
143
|
-
readonly configFile: string;
|
|
144
142
|
readonly symbols: readonly InventorySymbol[];
|
|
145
143
|
/**
|
|
146
144
|
* Each declaring node's {@link nodeKey} mapped to the identifier of the declaration
|
|
@@ -154,20 +152,6 @@ export interface Inventory {
|
|
|
154
152
|
*/
|
|
155
153
|
readonly declarations: ReadonlyMap<string, string>;
|
|
156
154
|
readonly cost: InventoryCost;
|
|
157
|
-
/**
|
|
158
|
-
* The module specifiers of the bare star re-exports the project's files carry, in
|
|
159
|
-
* the order they were read. A bare star re-export declares nothing here: the names
|
|
160
|
-
* it carries forward are the declarations of the module it names, so it is a
|
|
161
|
-
* reference to that module and not a declaration of this one.
|
|
162
|
-
*/
|
|
163
|
-
readonly starReExports: readonly string[];
|
|
164
|
-
/**
|
|
165
|
-
* The number of member declarations that were left out because the file declaring
|
|
166
|
-
* them is not one of the target's own files. Such a declaration belongs to another
|
|
167
|
-
* program, so the target's inventory does not name it, and the count is reported so
|
|
168
|
-
* that the omission is a number rather than a silence.
|
|
169
|
-
*/
|
|
170
|
-
readonly outsideOwnFiles: number;
|
|
171
155
|
/**
|
|
172
156
|
* The declarations whose module, namespace or container table the checker did not
|
|
173
157
|
* answer, in position order: whether each is exported, and which members a table
|
|
@@ -534,9 +518,7 @@ export function inventory<Brand>(
|
|
|
534
518
|
const byNode = new Map<string, Building>();
|
|
535
519
|
const exportScopes: ExportScope[] = [];
|
|
536
520
|
const containers: Container[] = [];
|
|
537
|
-
const starReExports: string[] = [];
|
|
538
521
|
const modules: (readonly [Building, string])[] = [];
|
|
539
|
-
let outsideOwnFiles = 0;
|
|
540
522
|
|
|
541
523
|
const keep = (record: Building): Building => {
|
|
542
524
|
building.push(record);
|
|
@@ -782,14 +764,10 @@ export function inventory<Brand>(
|
|
|
782
764
|
}
|
|
783
765
|
}
|
|
784
766
|
|
|
785
|
-
function walkExportDeclaration(
|
|
786
|
-
node: { readonly exportClause?: Node; readonly moduleSpecifier?: Node },
|
|
787
|
-
owner: Building,
|
|
788
|
-
): void {
|
|
767
|
+
function walkExportDeclaration(node: { readonly exportClause?: Node }, owner: Building): void {
|
|
789
768
|
const clause = node.exportClause;
|
|
769
|
+
// A bare star re-export declares nothing: the names it carries are the named module's.
|
|
790
770
|
if (clause === undefined) {
|
|
791
|
-
const specifier = node.moduleSpecifier;
|
|
792
|
-
starReExports.push(specifier === undefined ? "" : literalTextOf(specifier));
|
|
793
771
|
return;
|
|
794
772
|
}
|
|
795
773
|
if (isNamespaceExport(clause)) {
|
|
@@ -876,7 +854,7 @@ export function inventory<Brand>(
|
|
|
876
854
|
memberTables += 1;
|
|
877
855
|
const table = project.queries.membersOf(symbol);
|
|
878
856
|
if (isAnswered(table)) {
|
|
879
|
-
|
|
857
|
+
readTable(container, table, seen, false);
|
|
880
858
|
} else {
|
|
881
859
|
unansweredOwners.add(container.owner);
|
|
882
860
|
}
|
|
@@ -885,7 +863,7 @@ export function inventory<Brand>(
|
|
|
885
863
|
memberTables += 1;
|
|
886
864
|
const table = project.queries.exportsOf(symbol);
|
|
887
865
|
if (isAnswered(table)) {
|
|
888
|
-
|
|
866
|
+
readTable(container, table, seen, container.isClass);
|
|
889
867
|
} else {
|
|
890
868
|
unansweredOwners.add(container.owner);
|
|
891
869
|
}
|
|
@@ -905,8 +883,7 @@ export function inventory<Brand>(
|
|
|
905
883
|
table: ReadonlyMap<string, TSSymbol>,
|
|
906
884
|
seen: Map<string, Building>,
|
|
907
885
|
areStatic: boolean,
|
|
908
|
-
):
|
|
909
|
-
let outside = 0;
|
|
886
|
+
): void {
|
|
910
887
|
for (const [, member] of table) {
|
|
911
888
|
const declarations = member.declarations
|
|
912
889
|
.map((handle) => project.declarationAt(handle))
|
|
@@ -916,7 +893,6 @@ export function inventory<Brand>(
|
|
|
916
893
|
}
|
|
917
894
|
const own = declarations.filter((held) => ownFiles.has(held.node.getSourceFile().fileName));
|
|
918
895
|
if (own.length === 0) {
|
|
919
|
-
outside += declarations.length;
|
|
920
896
|
continue;
|
|
921
897
|
}
|
|
922
898
|
// A merged symbol's table holds the members every one of its declarations writes,
|
|
@@ -925,7 +901,6 @@ export function inventory<Brand>(
|
|
|
925
901
|
keepMember(container, held.node, seen, areStatic);
|
|
926
902
|
}
|
|
927
903
|
}
|
|
928
|
-
return outside;
|
|
929
904
|
}
|
|
930
905
|
|
|
931
906
|
/**
|
|
@@ -1017,14 +992,11 @@ export function inventory<Brand>(
|
|
|
1017
992
|
.sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
|
|
1018
993
|
|
|
1019
994
|
return {
|
|
1020
|
-
configFile: project.configFile,
|
|
1021
995
|
symbols,
|
|
1022
996
|
declarations: new Map(
|
|
1023
997
|
[...byNode].map(([key, record]) => [key, positionKey(record.position)] as const),
|
|
1024
998
|
),
|
|
1025
999
|
cost: { batches: 1, exportTables, memberTables },
|
|
1026
|
-
starReExports,
|
|
1027
|
-
outsideOwnFiles,
|
|
1028
1000
|
unanswered: symbols
|
|
1029
1001
|
.filter((symbol) => underUnanswered(byId.get(symbol.id), unansweredOwners))
|
|
1030
1002
|
.map((symbol) => symbol.id),
|
package/src/json-document.ts
CHANGED
|
@@ -100,7 +100,7 @@ class Walk {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/** What a walk of one document found. */
|
|
103
|
-
|
|
103
|
+
interface DocumentWalk {
|
|
104
104
|
/** The offset of the brace opening each object of the document's array member, in order. */
|
|
105
105
|
readonly opened: readonly number[];
|
|
106
106
|
/** The first member one object writes twice, at any depth, as the offset of its name. */
|
package/src/manifest.ts
CHANGED
|
@@ -15,7 +15,7 @@ const MANIFEST = "package.json";
|
|
|
15
15
|
const LEGACY_MEMBERS = ["main", "module", "types"] as const;
|
|
16
16
|
|
|
17
17
|
/** What reaches one entry point the manifest names. */
|
|
18
|
-
|
|
18
|
+
type EntryRole =
|
|
19
19
|
/** A consumer importing the package, which is a caller the analysis cannot see. */
|
|
20
20
|
| "import"
|
|
21
21
|
/** A shell running the package's command, which is a caller that exists. */
|
package/src/namespace-objects.ts
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
} from "@typescript/native/unstable/ast";
|
|
35
35
|
|
|
36
36
|
/** What one use of a namespace object reads beyond the names the checker resolves. */
|
|
37
|
-
|
|
37
|
+
type NamespaceUse =
|
|
38
38
|
/** Every export of the module `specifier` names. */
|
|
39
39
|
| { readonly kind: "whole"; readonly specifier: Node }
|
|
40
40
|
/** The export a literal index names, the literal being the node to resolve. */
|
package/src/pnpm-workspace.ts
CHANGED
package/src/references.ts
CHANGED
|
@@ -78,7 +78,7 @@ import { handedOn, valuesOf } from "./value-flow.ts";
|
|
|
78
78
|
* `decorator` names the declaration a decorator expression is attached to, which the
|
|
79
79
|
* decorator receives when the class is defined.
|
|
80
80
|
*/
|
|
81
|
-
|
|
81
|
+
type Use = "read" | "write" | "evaluation" | "decorator";
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* Which path answered for one reference: `batch`, a file's batched lookup; `resolved-symbol`, the
|
|
@@ -88,7 +88,7 @@ export type Use = "read" | "write" | "evaluation" | "decorator";
|
|
|
88
88
|
* or a value's position reads; `override`, the override a base call dispatches to; `keyed`, the
|
|
89
89
|
* member a symbol key selects; `by-name`, each declaration so spelled, where the checker failed.
|
|
90
90
|
*/
|
|
91
|
-
|
|
91
|
+
type Resolution =
|
|
92
92
|
| "batch"
|
|
93
93
|
| "resolved-symbol"
|
|
94
94
|
| "shorthand"
|
|
@@ -189,8 +189,6 @@ export interface ReferenceCost {
|
|
|
189
189
|
|
|
190
190
|
/** One project's references, in position order, and what reading them cost. */
|
|
191
191
|
export interface References {
|
|
192
|
-
/** The compiler configuration the project was opened from. */
|
|
193
|
-
readonly configFile: string;
|
|
194
192
|
readonly references: readonly Reference[];
|
|
195
193
|
/**
|
|
196
194
|
* Every file the rules classified as a test file, by its path below the target root,
|
|
@@ -214,6 +212,8 @@ export interface ReferenceOptions {
|
|
|
214
212
|
* Each pattern is a rule of its own, which {@link testFileRulesOf} counts.
|
|
215
213
|
*/
|
|
216
214
|
readonly testFiles: readonly string[];
|
|
215
|
+
/** The files, absolute, that are test files beside those the patterns name. */
|
|
216
|
+
readonly testFileNames?: ReadonlySet<string>;
|
|
217
217
|
/**
|
|
218
218
|
* The consumer whose files the pass walks instead of the target's. Its files are the
|
|
219
219
|
* program's own files below its root and not below the target root, each position is
|
|
@@ -1225,7 +1225,9 @@ export function references<Brand>(
|
|
|
1225
1225
|
const testFile = new Map<SourceFile, boolean>();
|
|
1226
1226
|
for (const { file } of walked) {
|
|
1227
1227
|
const path = renderPosition(file, root, 0).path;
|
|
1228
|
-
const test =
|
|
1228
|
+
const test =
|
|
1229
|
+
patterns.some((pattern) => pattern.test(path)) ||
|
|
1230
|
+
options.testFileNames?.has(file.fileName) === true;
|
|
1229
1231
|
testFile.set(file, test);
|
|
1230
1232
|
if (test) {
|
|
1231
1233
|
tests.push(path);
|
|
@@ -1569,7 +1571,6 @@ export function references<Brand>(
|
|
|
1569
1571
|
);
|
|
1570
1572
|
|
|
1571
1573
|
return {
|
|
1572
|
-
configFile: project.configFile,
|
|
1573
1574
|
references:
|
|
1574
1575
|
consumer === undefined
|
|
1575
1576
|
? found
|
package/src/report.ts
CHANGED
|
@@ -30,14 +30,14 @@ export const ANALYZER_NAME = "deadset-ts";
|
|
|
30
30
|
/** The language this analyzer claims. */
|
|
31
31
|
export const LANGUAGE = "ts";
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
interface WirePosition {
|
|
34
34
|
readonly path: string;
|
|
35
35
|
readonly line: number;
|
|
36
36
|
readonly column: number;
|
|
37
37
|
readonly end_line: number;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
interface WireDetails {
|
|
41
41
|
readonly narrower_visibility?: string;
|
|
42
42
|
readonly implementations?: readonly {
|
|
43
43
|
readonly ref: string;
|
|
@@ -54,6 +54,7 @@ export interface WireDetails {
|
|
|
54
54
|
readonly reason?: string;
|
|
55
55
|
};
|
|
56
56
|
readonly overlap?: readonly string[];
|
|
57
|
+
readonly removes_last_use_of?: readonly string[];
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
/** One finding as the finding schema spells it. */
|
|
@@ -93,7 +94,7 @@ export interface WireFinding {
|
|
|
93
94
|
readonly details: WireDetails;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
interface WireEdgeEvaluation {
|
|
97
98
|
readonly edge: string;
|
|
98
99
|
readonly side: string;
|
|
99
100
|
readonly symbol: string;
|
|
@@ -178,7 +179,7 @@ interface ReportConsumer {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
/** One consumer the scope declared and the run did not load, with why. */
|
|
181
|
-
|
|
182
|
+
interface UnavailableConsumer {
|
|
182
183
|
readonly id: string;
|
|
183
184
|
readonly reason: string;
|
|
184
185
|
}
|
|
@@ -297,6 +298,9 @@ function wireDetails(details: FindingDetails): WireDetails {
|
|
|
297
298
|
},
|
|
298
299
|
}),
|
|
299
300
|
...(details.overlap === undefined ? {} : { overlap: details.overlap }),
|
|
301
|
+
...(details.removesLastUseOf === undefined
|
|
302
|
+
? {}
|
|
303
|
+
: { removes_last_use_of: details.removesLastUseOf }),
|
|
300
304
|
};
|
|
301
305
|
}
|
|
302
306
|
|
package/src/roots.ts
CHANGED
|
@@ -81,8 +81,6 @@ export interface Root {
|
|
|
81
81
|
|
|
82
82
|
/** One project's roots, and every configured string that named nothing in it. */
|
|
83
83
|
export interface Roots {
|
|
84
|
-
/** The compiler configuration the project was opened from. */
|
|
85
|
-
readonly configFile: string;
|
|
86
84
|
/**
|
|
87
85
|
* Every root of this project, ordered by the site of the declaration it names,
|
|
88
86
|
* then by kind, then by the string that named it. A declaration that is a root for
|
|
@@ -432,7 +430,6 @@ export function roots<Brand>(
|
|
|
432
430
|
});
|
|
433
431
|
|
|
434
432
|
return {
|
|
435
|
-
configFile: project.configFile,
|
|
436
433
|
liveUnderReachability: ordered,
|
|
437
434
|
unmatched,
|
|
438
435
|
unanswered: answered ? api.guessed : held.symbols.map((symbol) => symbol.id),
|
package/src/run.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ConfigError, REPOSITORY_DOCUMENT, type Inputs } from "./config.ts";
|
|
2
2
|
import { DiscoveryError, renderDiagnostic } from "./discover.ts";
|
|
3
|
-
import { EMITTERS } from "./findings/emitters.ts";
|
|
4
3
|
import type { Host } from "./host.ts";
|
|
5
4
|
import { joinPath, resolvePath } from "./paths.ts";
|
|
6
5
|
import { readScope, ScopeError, scopeForDir, type Scope } from "./scope.ts";
|
|
@@ -264,7 +263,6 @@ function invoke(
|
|
|
264
263
|
});
|
|
265
264
|
return opened;
|
|
266
265
|
},
|
|
267
|
-
emitters: EMITTERS,
|
|
268
266
|
});
|
|
269
267
|
} catch (error: unknown) {
|
|
270
268
|
opened?.close();
|
package/src/schema.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { ConfigError } from "./config.ts";
|
|
|
15
15
|
* - `object` is one setting written as an object: its member names are declared
|
|
16
16
|
* and a source supplies the object whole.
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
type KeyKind = "leaf" | "section" | "map" | "list" | "object";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* One node of the closed key list. `members` holds a section's declared members,
|
|
@@ -259,7 +259,7 @@ export function malformed(label: string, path: string, detail: string): ConfigEr
|
|
|
259
259
|
* A refusal of a key the closed key list does not declare, naming the nearest key
|
|
260
260
|
* it does.
|
|
261
261
|
*/
|
|
262
|
-
|
|
262
|
+
function unimplementedKey(label: string, path: string): ConfigError {
|
|
263
263
|
const nearest = nearestKey(path);
|
|
264
264
|
let hint = "";
|
|
265
265
|
if (nearest === path) {
|
package/src/scope.ts
CHANGED
|
@@ -4,8 +4,8 @@ import { dirnamePath, isAbsolutePath, joinPath, resolvePath } from "./paths.ts";
|
|
|
4
4
|
import { checkDocument, type KeyNode } from "./schema.ts";
|
|
5
5
|
|
|
6
6
|
/** The roles a scope document declares for a module. */
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const ROLE_TARGET = "target";
|
|
8
|
+
const ROLE_CONSUMER = "consumer";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A scope document above this length is refused rather than decoded. It names one
|
package/src/session.ts
CHANGED
|
@@ -417,7 +417,6 @@ export function ownedAs<Brand>(
|
|
|
417
417
|
|
|
418
418
|
/** Every diagnostic set a project is refused for carrying an error in. */
|
|
419
419
|
export interface ProjectDiagnostics {
|
|
420
|
-
readonly configFile: string;
|
|
421
420
|
readonly syntactic: readonly Diagnostic[];
|
|
422
421
|
readonly semantic: readonly Diagnostic[];
|
|
423
422
|
readonly configParsing: readonly Diagnostic[];
|
|
@@ -431,7 +430,6 @@ export interface ProjectDiagnostics {
|
|
|
431
430
|
/** The three diagnostic sets one project carries, read before any analysis. */
|
|
432
431
|
export function diagnosticsOf<Brand>(project: ProjectView<Brand>): ProjectDiagnostics {
|
|
433
432
|
return {
|
|
434
|
-
configFile: project.configFile,
|
|
435
433
|
syntactic: project.program.getSyntacticDiagnostics(),
|
|
436
434
|
semantic: project.program.getSemanticDiagnostics(),
|
|
437
435
|
configParsing: project.program.getConfigFileParsingDiagnostics(),
|
package/src/stores.ts
CHANGED
|
@@ -37,7 +37,7 @@ export interface Stores {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** What one configuration contributes to the run's stores. */
|
|
40
|
-
|
|
40
|
+
interface ConfigurationStores {
|
|
41
41
|
readonly references: readonly Reference[];
|
|
42
42
|
readonly accessors: readonly string[];
|
|
43
43
|
}
|
package/src/suppress-inline.ts
CHANGED
|
@@ -37,7 +37,7 @@ const NO_REASON = new RegExp(DIRECTIVE_EXPRESSIONS.noReason);
|
|
|
37
37
|
const DIRECTIVE_FORM = "// deadset:ignore DS0000[,DS0000...] -- <reason>";
|
|
38
38
|
|
|
39
39
|
/** What the grammar's decision procedure makes of one comment. */
|
|
40
|
-
|
|
40
|
+
type Directive =
|
|
41
41
|
| { readonly is: "none" }
|
|
42
42
|
| { readonly is: "directive"; readonly codes: readonly string[]; readonly reason: string }
|
|
43
43
|
| { readonly is: "no-reason"; readonly codes: readonly string[] }
|
|
@@ -64,7 +64,7 @@ export function classifyComment(text: string): Directive {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/** One `//` line comment: the offset of its first solidus, and its text to the end of its line. */
|
|
67
|
-
|
|
67
|
+
interface LineComment {
|
|
68
68
|
readonly offset: number;
|
|
69
69
|
readonly text: string;
|
|
70
70
|
}
|
|
@@ -123,7 +123,7 @@ export function tokenStart(text: string, from: number): number {
|
|
|
123
123
|
* syntax tree holds are skipped, so a `//` inside a string, a template or a regular
|
|
124
124
|
* expression starts nothing.
|
|
125
125
|
*/
|
|
126
|
-
|
|
126
|
+
function lineComments(file: SourceFile): readonly LineComment[] {
|
|
127
127
|
const text = file.text;
|
|
128
128
|
const spans: { readonly start: number; readonly end: number }[] = [];
|
|
129
129
|
const visit = (node: Node): void => {
|