@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
@@ -1,72 +1,59 @@
1
1
  /**
2
- * The exemption classes of the Contract's vocabulary, in its order: each class's name,
3
- * the languages it runs on, the confidence it is recorded at and, for a class that runs
4
- * on TypeScript, which private members it may retain.
5
- *
6
- * The rows are written out rather than read from the Contract at run time, for the
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"], confidence: "certain" },
21
- { class: "format-verb-contract", languages: ["go"], confidence: "certain" },
22
- { class: "errors-duck-typing", languages: ["go"], confidence: "certain" },
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"], confidence: "certain" },
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
  ];
@@ -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: generated ? "none" : row.fixability,
222
+ fixability,
195
223
  severity: severityOf(input, finding.code, row.defaultSeverity),
196
224
  message: finding.message,
197
- details: finding.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),
@@ -130,7 +130,3 @@ function withheldOf(input, candidates) {
130
130
  }
131
131
  return counts;
132
132
  }
133
- /** Every finding the run reports, each family's in table order. */
134
- export function findingsOf(input) {
135
- return decidedFindings(input).findings;
136
- }
@@ -1,5 +1,6 @@
1
1
  import { REPOSITORY_DOCUMENT } from "../config.js";
2
- import { STALE_SUPPRESSION, SUPPRESSION_WITHOUT_REASON, } from "../suppress.js";
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
- export function refusedSuppressions(refusals, files) {
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 instead = reports.length === 0 ? "" : `, and its declaration reports ${spelled(reports)}`;
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
- /** Calls passing a literal argument whose callee a package binding heads, which a dependency's generic may fix. */
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
- if (literal && isCallExpression(node) && head !== undefined && imported.has(head)) {
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
  }
@@ -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
- outsideOwnFiles += readTable(container, table, seen, false);
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
- outsideOwnFiles += readTable(container, table, seen, container.isClass);
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),
@@ -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 })),
@@ -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) {
@@ -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
- export function unimplementedKey(label, path) {
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
- export const ROLE_TARGET = "target";
6
- export const ROLE_CONSUMER = "consumer";
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.
@@ -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
- export function lineComments(file) {
98
+ function lineComments(file) {
99
99
  const text = file.text;
100
100
  const spans = [];
101
101
  const visit = (node) => {
@@ -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 withholds a finding about one.
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 Set([
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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "7.0.0-dev.1",
3
+ "version": "7.0.0-dev.2",
4
4
  "license": "GPL-3.0-or-later",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cplieger/deadset-ts",
3
- "version": "7.0.0-dev.1",
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 ."