@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.
Files changed (77) hide show
  1. package/dist/src/analysis.js +6 -16
  2. package/dist/src/component-layer.js +1 -1
  3. package/dist/src/configuration-files.js +227 -27
  4. package/dist/src/contextual-uses.js +41 -7
  5. package/dist/src/convention-rows.js +6 -0
  6. package/dist/src/conventions.js +32 -0
  7. package/dist/src/dependencies.js +12 -8
  8. package/dist/src/discover.js +1 -1
  9. package/dist/src/edges.js +1 -1
  10. package/dist/src/exempt-classes.js +9 -22
  11. package/dist/src/exempt.js +3 -15
  12. package/dist/src/findings/completion.js +35 -4
  13. package/dist/src/findings/emitters.js +0 -4
  14. package/dist/src/findings/self-check.js +14 -10
  15. package/dist/src/indexed-access.js +8 -2
  16. package/dist/src/inventory.js +3 -12
  17. package/dist/src/references.js +2 -2
  18. package/dist/src/report.js +3 -0
  19. package/dist/src/roots.js +0 -1
  20. package/dist/src/run.js +0 -2
  21. package/dist/src/schema.js +1 -1
  22. package/dist/src/scope.js +2 -2
  23. package/dist/src/session.js +0 -1
  24. package/dist/src/suppress-inline.js +1 -1
  25. package/dist/src/suppress.js +15 -16
  26. package/dist/src/workspace-programs.js +0 -3
  27. package/dist/src/workspace-resolution.js +0 -2
  28. package/dist/src/workspace.js +0 -3
  29. package/jsr.json +1 -1
  30. package/package.json +2 -1
  31. package/src/analysis.ts +10 -30
  32. package/src/calls.ts +3 -3
  33. package/src/component-files.ts +7 -7
  34. package/src/component-layer.ts +2 -2
  35. package/src/components.ts +1 -1
  36. package/src/config.ts +4 -4
  37. package/src/configuration-files.ts +246 -28
  38. package/src/contextual-uses.ts +63 -9
  39. package/src/convention-rows.ts +19 -0
  40. package/src/conventions.ts +43 -0
  41. package/src/dependencies.ts +14 -10
  42. package/src/discover.ts +3 -3
  43. package/src/edges.ts +3 -3
  44. package/src/entry-points.ts +1 -1
  45. package/src/exempt-classes.ts +10 -24
  46. package/src/exempt.ts +5 -29
  47. package/src/file-facts.ts +1 -1
  48. package/src/finding.ts +3 -1
  49. package/src/findings/completion.ts +46 -4
  50. package/src/findings/emitters.ts +3 -8
  51. package/src/findings/self-check.ts +17 -14
  52. package/src/findings-pass.ts +2 -2
  53. package/src/graph.ts +2 -2
  54. package/src/html-entries.ts +1 -1
  55. package/src/indexed-access.ts +10 -2
  56. package/src/inventory.ts +5 -33
  57. package/src/json-document.ts +1 -1
  58. package/src/manifest.ts +1 -1
  59. package/src/namespace-objects.ts +1 -1
  60. package/src/pnpm-workspace.ts +1 -1
  61. package/src/references.ts +7 -6
  62. package/src/report.ts +8 -4
  63. package/src/roots.ts +0 -3
  64. package/src/run.ts +0 -2
  65. package/src/schema.ts +2 -2
  66. package/src/scope.ts +2 -2
  67. package/src/session.ts +0 -2
  68. package/src/stores.ts +1 -1
  69. package/src/suppress-inline.ts +3 -3
  70. package/src/suppress.ts +21 -24
  71. package/src/template-field.ts +1 -1
  72. package/src/value-reach.ts +1 -1
  73. package/src/verbs/verb.ts +1 -4
  74. package/src/workspace-programs.ts +2 -9
  75. package/src/workspace-resolution.ts +0 -3
  76. package/src/workspace-run.ts +1 -1
  77. 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(reachabilityClass, row.maxClass),
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: generated ? "none" : row.fixability,
272
+ fixability,
235
273
  severity: severityOf(input, finding.code, row.defaultSeverity),
236
274
  message: finding.message,
237
- details: finding.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
  )
@@ -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
- export interface Suppressed {
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
- export interface DecidedFindings {
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<CompletedFinding>;
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
- export type FileRefs = (path: string) => string | undefined;
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
- export function refusedSuppressions(
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 instead = reports.length === 0 ? "" : `, and its declaration reports ${spelled(reports)}`;
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,
@@ -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
- export interface PassTotals {
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<CompletedFinding>;
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
- export interface Counts {
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
- export interface Rooted {
96
+ interface Rooted {
97
97
  readonly at: number;
98
98
  readonly kind: RootKind;
99
99
  }
@@ -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
- export interface HTMLEntry {
19
+ interface HTMLEntry {
20
20
  readonly file: SourceFile;
21
21
  readonly source: string;
22
22
  }
@@ -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
- /** Calls passing a literal argument whose callee a package binding heads, which a dependency's generic may fix. */
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
- if (literal && isCallExpression(node) && head !== undefined && imported.has(head)) {
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
- outsideOwnFiles += readTable(container, table, seen, false);
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
- outsideOwnFiles += readTable(container, table, seen, container.isClass);
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
- ): number {
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),
@@ -100,7 +100,7 @@ class Walk {
100
100
  }
101
101
 
102
102
  /** What a walk of one document found. */
103
- export interface DocumentWalk {
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
- export type EntryRole =
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. */
@@ -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
- export type NamespaceUse =
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. */
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  /** What the document says about `packages`. */
9
- export type PnpmPackages =
9
+ type PnpmPackages =
10
10
  | { readonly kind: "listed"; readonly patterns: readonly string[] }
11
11
  /** The document declares no `packages` key. */
12
12
  | { readonly kind: "absent" }
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
- export type Use = "read" | "write" | "evaluation" | "decorator";
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
- export type Resolution =
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 = patterns.some((pattern) => pattern.test(path));
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
- export interface WirePosition {
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
- export interface WireDetails {
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
- export interface WireEdgeEvaluation {
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
- export interface UnavailableConsumer {
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
- export type KeyKind = "leaf" | "section" | "map" | "list" | "object";
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
- export function unimplementedKey(label: string, path: string): ConfigError {
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
- export const ROLE_TARGET = "target";
8
- export const ROLE_CONSUMER = "consumer";
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
- export interface ConfigurationStores {
40
+ interface ConfigurationStores {
41
41
  readonly references: readonly Reference[];
42
42
  readonly accessors: readonly string[];
43
43
  }
@@ -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
- export type Directive =
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
- export interface LineComment {
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
- export function lineComments(file: SourceFile): readonly LineComment[] {
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 => {