@cplieger/deadset-ts 7.0.0-dev.2 → 7.0.0-dev.4

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 (137) hide show
  1. package/README.md +1 -1
  2. package/dist/src/alias-chain.js +2 -2
  3. package/dist/src/analysis.js +89 -44
  4. package/dist/src/augmentations.js +14 -12
  5. package/dist/src/calls.js +2 -2
  6. package/dist/src/class-members.js +1 -1
  7. package/dist/src/components.js +62 -20
  8. package/dist/src/configuration-files.js +6 -6
  9. package/dist/src/configured-declarations.js +2 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/contextual-uses.js +71 -14
  12. package/dist/src/convention-rows.js +6 -2
  13. package/dist/src/conventions.js +4 -2
  14. package/dist/src/dependencies.js +81 -27
  15. package/dist/src/deprecation.js +1 -1
  16. package/dist/src/destructuring.js +2 -2
  17. package/dist/src/discover.js +1 -1
  18. package/dist/src/element-access.js +341 -0
  19. package/dist/src/entry-points.js +2 -2
  20. package/dist/src/enum-group.js +2 -2
  21. package/dist/src/erased-stores.js +317 -0
  22. package/dist/src/findings/completion.js +46 -15
  23. package/dist/src/findings/dependencies-and-module-machinery.js +1 -1
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/self-check.js +17 -4
  26. package/dist/src/findings-pass.js +6 -5
  27. package/dist/src/framework-lifecycle.js +1 -1
  28. package/dist/src/html-entries.js +1 -1
  29. package/dist/src/implementations.js +2 -2
  30. package/dist/src/in-operands.js +30 -0
  31. package/dist/src/indexed-access.js +264 -125
  32. package/dist/src/injection-container.js +1 -1
  33. package/dist/src/interface-satisfaction.js +2 -2
  34. package/dist/src/intra-function-parts.js +26 -12
  35. package/dist/src/inventory.js +26 -1
  36. package/dist/src/keyof-comparisons.js +170 -0
  37. package/dist/src/kinds.js +17 -7
  38. package/dist/src/manifest.js +6 -16
  39. package/dist/src/matrix.js +20 -2
  40. package/dist/src/member-names.js +1 -1
  41. package/dist/src/module-declarations.js +1 -1
  42. package/dist/src/namespace-objects.js +1 -1
  43. package/dist/src/outside-tests.js +1 -1
  44. package/dist/src/overrides.js +2 -2
  45. package/dist/src/query.js +4 -1
  46. package/dist/src/references.js +13 -2
  47. package/dist/src/reflective-lookup.js +2 -2
  48. package/dist/src/report.js +1 -1
  49. package/dist/src/reporters/sarif.js +4 -0
  50. package/dist/src/roots.js +1 -1
  51. package/dist/src/serialization-contract.js +45 -16
  52. package/dist/src/session.js +15 -8
  53. package/dist/src/signatures.js +1 -1
  54. package/dist/src/stores.js +1 -1
  55. package/dist/src/suppress-file.js +58 -6
  56. package/dist/src/suppress-inline.js +14 -5
  57. package/dist/src/suppress.js +54 -24
  58. package/dist/src/symbol-keys.js +1 -1
  59. package/dist/src/type-errors.js +1 -1
  60. package/dist/src/type-query-alias.js +1 -1
  61. package/dist/src/unimplemented-callees.js +312 -0
  62. package/dist/src/value-flow.js +1 -1
  63. package/dist/src/value-reach.js +8 -8
  64. package/dist/src/verbs/analyze.js +11 -9
  65. package/dist/src/workspace-programs.js +2 -2
  66. package/jsr.json +1 -1
  67. package/package.json +3 -3
  68. package/src/alias-chain.ts +2 -2
  69. package/src/analysis.ts +135 -52
  70. package/src/augmentations.ts +14 -11
  71. package/src/calls.ts +2 -2
  72. package/src/class-members.ts +2 -2
  73. package/src/component-layer.ts +1 -1
  74. package/src/components.ts +103 -21
  75. package/src/configuration-files.ts +6 -5
  76. package/src/configured-declarations.ts +2 -6
  77. package/src/conformance-record.ts +1 -1
  78. package/src/contextual-uses.ts +82 -18
  79. package/src/convention-rows.ts +6 -2
  80. package/src/conventions.ts +11 -2
  81. package/src/decorator.ts +1 -1
  82. package/src/dependencies.ts +108 -31
  83. package/src/deprecation.ts +1 -1
  84. package/src/destructuring.ts +2 -2
  85. package/src/discover.ts +2 -2
  86. package/src/element-access.ts +446 -0
  87. package/src/entry-points.ts +2 -2
  88. package/src/enum-group.ts +2 -2
  89. package/src/erased-stores.ts +413 -0
  90. package/src/file-facts.ts +3 -1
  91. package/src/findings/completion.ts +54 -20
  92. package/src/findings/dependencies-and-module-machinery.ts +1 -1
  93. package/src/findings/emitters.ts +1 -1
  94. package/src/findings/self-check.ts +18 -4
  95. package/src/findings-pass.ts +12 -5
  96. package/src/framework-lifecycle.ts +2 -2
  97. package/src/html-entries.ts +1 -1
  98. package/src/implementations.ts +2 -2
  99. package/src/in-operands.ts +45 -0
  100. package/src/indexed-access.ts +304 -146
  101. package/src/injection-container.ts +1 -1
  102. package/src/interface-satisfaction.ts +2 -2
  103. package/src/intra-function-parts.ts +42 -17
  104. package/src/inventory.ts +34 -2
  105. package/src/keyof-comparisons.ts +211 -0
  106. package/src/kinds.ts +20 -7
  107. package/src/manifest.ts +6 -21
  108. package/src/matrix.ts +34 -2
  109. package/src/member-names.ts +1 -1
  110. package/src/module-declarations.ts +1 -1
  111. package/src/namespace-objects.ts +1 -1
  112. package/src/outside-tests.ts +1 -1
  113. package/src/overrides.ts +2 -2
  114. package/src/position.ts +1 -1
  115. package/src/query.ts +12 -2
  116. package/src/references.ts +25 -3
  117. package/src/reflective-lookup.ts +2 -2
  118. package/src/report.ts +3 -1
  119. package/src/reporters/sarif.ts +4 -0
  120. package/src/roots.ts +2 -2
  121. package/src/serialization-contract.ts +55 -16
  122. package/src/session.ts +39 -32
  123. package/src/signatures.ts +1 -1
  124. package/src/source-files.ts +1 -1
  125. package/src/stores.ts +1 -1
  126. package/src/suppress-file.ts +68 -5
  127. package/src/suppress-inline.ts +15 -5
  128. package/src/suppress.ts +63 -28
  129. package/src/symbol-keys.ts +2 -2
  130. package/src/type-errors.ts +2 -2
  131. package/src/type-query-alias.ts +1 -1
  132. package/src/unimplemented-callees.ts +411 -0
  133. package/src/value-flow.ts +1 -1
  134. package/src/value-reach.ts +12 -8
  135. package/src/verbs/analyze.ts +15 -9
  136. package/src/workflow-steps.ts +1 -1
  137. package/src/workspace-programs.ts +4 -4
@@ -59,6 +59,13 @@ const BASELINE_SHAPE: Shape = {
59
59
  recordWant: "a row naming code, symbol, path and reason, each a string",
60
60
  };
61
61
 
62
+ /**
63
+ * The reference scheme of this analyzer's language. A record of the other scheme is
64
+ * form-checked and then left to the analyzer of its language: it binds nothing here, counts
65
+ * in neither total and is reported under no code.
66
+ */
67
+ const OWN_SCHEME = "ts://";
68
+
62
69
  /** The closed key list of one entry or row. */
63
70
  const RECORD_KEYS: ReadonlySet<string> = new Set(["code", "symbol", "path", "reason"]);
64
71
 
@@ -126,6 +133,9 @@ function readRecord(value: unknown, site: Position, byReference: ByReference, sh
126
133
  if (path !== undefined && path !== "" && !isEntryPath(path)) {
127
134
  return refuse(JSON.stringify(path), PATH_WANT);
128
135
  }
136
+ if (!symbol.startsWith(OWN_SCHEME)) {
137
+ return { records: [], refusals: [] };
138
+ }
129
139
 
130
140
  const written = {
131
141
  code,
@@ -284,15 +294,68 @@ export function rowReason(provenance: Provenance): string {
284
294
  return `recorded by ${provenance.analyzer} ${provenance.version}`;
285
295
  }
286
296
 
297
+ /** A row of the other language's scheme a write keeps: the members the document held, and no other. */
298
+ interface KeptRow {
299
+ readonly code: string;
300
+ readonly symbol: string;
301
+ readonly path?: string;
302
+ readonly reason?: string;
303
+ }
304
+
305
+ /**
306
+ * The rows of the other language's scheme that the baseline document `text` holds, in
307
+ * document order and as written, which a write keeps. A document that is not a baseline,
308
+ * or a row in a shape no record takes, is refused rather than dropped.
309
+ */
310
+ export function otherSchemeRows(text: string): readonly KeptRow[] {
311
+ const refuse = (detail: string): never => {
312
+ throw new Error(
313
+ `${BASELINE_FILE} cannot be rewritten keeping the other language's rows: ${detail}`,
314
+ );
315
+ };
316
+ let parsed: unknown;
317
+ try {
318
+ parsed = JSON.parse(text);
319
+ } catch (error: unknown) {
320
+ return refuse(error instanceof Error ? error.message : String(error));
321
+ }
322
+ const held = isObject(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
323
+ if (!Array.isArray(held)) {
324
+ return refuse(BASELINE_SHAPE.documentWant);
325
+ }
326
+ return (held as readonly unknown[]).flatMap((value): KeptRow[] => {
327
+ try {
328
+ readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
329
+ } catch (error: unknown) {
330
+ return refuse(error instanceof Error ? error.message : String(error));
331
+ }
332
+ // readRecord has checked every member is a string and the code and symbol are present.
333
+ const { code, symbol, path, reason } = value as Readonly<Record<string, string>>;
334
+ if (code === undefined || symbol === undefined || symbol.startsWith(OWN_SCHEME)) {
335
+ return [];
336
+ }
337
+ return [
338
+ {
339
+ code,
340
+ symbol,
341
+ ...(path === undefined ? {} : { path }),
342
+ ...(reason === undefined ? {} : { reason }),
343
+ },
344
+ ];
345
+ });
346
+ }
347
+
287
348
  /**
288
- * A baseline holding one row per finding, in the order given, which is the report's. Every
289
- * row carries the provenance as its reason. A finding missing a value a row needs, and an
290
- * identity missing its name or version, are refused rather than written, because the
291
- * grammar requires a reason on every row.
349
+ * A baseline holding one row per finding, in the order given, which is the report's, after
350
+ * the other language's rows `kept`, so two analyzers writing one document in either order
351
+ * write the same bytes. Every recorded row carries the provenance as its reason. A finding
352
+ * missing a value a row needs, and an identity missing its name or version, are refused
353
+ * rather than written, because the grammar requires a reason on every row.
292
354
  */
293
355
  export function writeBaseline(
294
356
  findings: readonly Recorded[],
295
357
  provenance: Provenance,
358
+ kept: readonly KeptRow[] = [],
296
359
  ): Iterable<string> {
297
360
  if (provenance.analyzer === "" || provenance.version === "") {
298
361
  throw new Error(
@@ -308,5 +371,5 @@ export function writeBaseline(
308
371
  }
309
372
  return { code: found.code, symbol: found.symbol, path: found.path, reason };
310
373
  });
311
- return jsonChunks({ description: BASELINE_DESCRIPTION, baseline }, 2);
374
+ return jsonChunks({ description: BASELINE_DESCRIPTION, baseline: [...kept, ...baseline] }, 2);
312
375
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The inline directive: `deadset:ignore`, the first token of a `//` line comment, naming
3
3
  * one or more codes and a reason, and binding to every declaration that begins on the line
4
- * below it.
4
+ * below it, or on its own line where it trails a token there.
5
5
  */
6
6
 
7
- import { SyntaxKind, type Node, type SourceFile } from "@typescript/native/unstable/ast";
7
+ import { SyntaxKind, type Node, type SourceFile } from "@typescript/native/ast";
8
8
  import { nodeKey, type Inventory } from "./inventory.ts";
9
9
  import { byPosition, positionKey, renderPosition, type Position } from "./position.ts";
10
10
  import type { ProjectView } from "./session.ts";
@@ -211,6 +211,15 @@ function declarationLines(
211
211
  return below;
212
212
  }
213
213
 
214
+ /**
215
+ * Whether the comment at `offset` follows a token on its own line, so it binds the
216
+ * declarations that begin on that line rather than on the next.
217
+ */
218
+ function trails(file: SourceFile, offset: number): boolean {
219
+ const { line } = file.getLineAndCharacterOfPosition(offset);
220
+ return tokenStart(file.text, file.getPositionOfLineAndCharacter(line, 0)) < offset;
221
+ }
222
+
214
223
  /** One comment in the namespace, what the grammar made of it, and what it bound. */
215
224
  export interface InlineDirective {
216
225
  readonly site: Position;
@@ -242,11 +251,12 @@ export function inlineDirectives<Brand>(
242
251
  const below = declarationLines(file, held, symbols);
243
252
  for (const comment of comments) {
244
253
  const site = renderPosition(file, targetRoot, comment.offset);
254
+ const target = trails(file, comment.offset) ? site.line : site.line + 1;
245
255
  found.push({
246
256
  site,
247
257
  text: comment.text,
248
258
  directive: classifyComment(comment.text),
249
- bound: [...(below.get(site.line + 1) ?? [])],
259
+ bound: [...(below.get(target) ?? [])],
250
260
  });
251
261
  }
252
262
  }
@@ -254,8 +264,8 @@ export function inlineDirectives<Brand>(
254
264
  }
255
265
 
256
266
  /**
257
- * The inline directives of a run's projects, each bound to the declarations that begin on
258
- * the line below it, one directive binding the union of what each project holding its file
267
+ * The inline directives of a run's projects, each bound to the declarations the placement
268
+ * rule gives it, one directive binding the union of what each project holding its file
259
269
  * declares there. A directive that lacks only its reason is one {@link Refusal} per code;
260
270
  * any other malformed comment in the namespace ends the read with a
261
271
  * {@link SuppressionError} naming the first one written.
package/src/suppress.ts CHANGED
@@ -2,8 +2,9 @@
2
2
  * The suppression grammar's records, and the ledger that decides what each one did. A
3
3
  * record is one code bound to one site with its reason, from an inline directive, an
4
4
  * ignore entry or a baseline row. A bound record marks its declaration live before the
5
- * sweep, and is in effect when it withholds the finding its code would have produced
6
- * there, dormant when the configuration withholds that finding anyway, and stale otherwise.
5
+ * sweep, unless its code reports a part or a dependency row, and is in effect when it
6
+ * withholds a finding its code would have produced there, dormant when the configuration
7
+ * withholds every such finding anyway, and stale otherwise.
7
8
  */
8
9
 
9
10
  import { ConfigError } from "./config.ts";
@@ -141,8 +142,8 @@ export type Verdict = "in-effect" | "dormant" | "stale";
141
142
  * them withholds is dormant.
142
143
  */
143
144
  export interface Dials<F extends Finding = Finding> {
144
- /** Whether the configuration withholds every finding of one code from one declaration. */
145
- readonly withholdsCode: (code: string, bound: string) => boolean;
145
+ /** Whether the configuration sets the severity of one code to `allow`. */
146
+ readonly allows: (code: string) => boolean;
146
147
  /** Whether it withholds one finding: by its severity, its confidence, or its component's root. */
147
148
  readonly withholds: (finding: F) => boolean;
148
149
  }
@@ -155,31 +156,55 @@ export interface Ledger {
155
156
  readonly withheld: (finding: Finding) => boolean;
156
157
  }
157
158
 
159
+ /**
160
+ * The subject kinds that are a dependency row, each with the code that reports one. An
161
+ * ignore entry or a baseline row naming the row's reference and file withholds its finding
162
+ * and marks nothing, and an inline directive never binds one.
163
+ */
164
+ const DEPENDENCY_SUBJECTS: ReadonlyMap<string, readonly string[]> = new Map([
165
+ ["dependency", ["DS1601"]],
166
+ ["module-directive", ["DS1605"]],
167
+ ]);
168
+
158
169
  /**
159
170
  * The subject kinds that are a row of a document rather than a declaration of the program,
160
- * each with the codes that report one. A record binds to a declaration, so nothing
161
- * withholds a finding about one.
171
+ * each with the codes that report one. Apart from a dependency row, nothing withholds a
172
+ * finding about one.
162
173
  */
163
174
  const ROW_SUBJECTS: ReadonlyMap<string, readonly string[]> = new Map([
164
175
  ["file", ["DS1501", "DS1502"]],
165
- ["dependency", ["DS1601"]],
166
- ["module-directive", ["DS1605"]],
176
+ ...DEPENDENCY_SUBJECTS,
167
177
  ["root", ["DS1704"]],
168
178
  ["configured-declaration", ["DS1706"]],
169
179
  ["suppression", ["DS1701", "DS1702", "DS1703"]],
170
180
  ["edge", ["DS1705"]],
171
181
  ]);
172
182
 
173
- /** Whether a finding's subject is a row of a document, which no record can withhold. */
183
+ /** Whether a finding's subject is a row of a document rather than a declaration. */
174
184
  export function isRowSubject(kind: string): boolean {
175
185
  return ROW_SUBJECTS.has(kind);
176
186
  }
177
187
 
178
- /** Whether a code reports a row of a document, so no record naming it can match. */
188
+ /** Whether a code reports a row of a document. */
179
189
  export function isRowCode(code: string): boolean {
180
190
  return [...ROW_SUBJECTS.values()].some((codes) => codes.includes(code));
181
191
  }
182
192
 
193
+ /** Whether a finding's subject is a dependency row. */
194
+ export function isDependencySubject(kind: string): boolean {
195
+ return DEPENDENCY_SUBJECTS.has(kind);
196
+ }
197
+
198
+ /** Whether a code reports a dependency row, which only an ignore entry or a baseline row names. */
199
+ export function isDependencyCode(code: string): boolean {
200
+ return [...DEPENDENCY_SUBJECTS.values()].some((codes) => codes.includes(code));
201
+ }
202
+
203
+ /** Whether a code reports a row of a document that no record of any mechanism can name. */
204
+ export function isUnsuppressibleCode(code: string): boolean {
205
+ return isRowCode(code) && !isDependencyCode(code);
206
+ }
207
+
183
208
  /**
184
209
  * The subject kinds that are a part of a declaration rather than a declaration: a part
185
210
  * has no reference of its own, so a finding about one names the declaration that holds
@@ -219,10 +244,21 @@ function claimKey(finding: Finding): string {
219
244
  * Whether one record names one finding. A record reaches a finding in two ways: it bound
220
245
  * the declaration at the finding's position, or it names the finding's reference and the
221
246
  * file the finding is reported in, each exactly. The second is how an entry copied from a
222
- * finding about a part of a declaration reaches the part.
247
+ * finding about a part of a declaration reaches the part, and the only way any record
248
+ * reaches a dependency row.
223
249
  */
224
250
  export function pairs(record: SuppressionRecord, finding: Finding): boolean {
225
- if (record.code !== finding.code || ROW_SUBJECTS.has(finding.symbol.kind)) {
251
+ if (record.code !== finding.code) {
252
+ return false;
253
+ }
254
+ if (DEPENDENCY_SUBJECTS.has(finding.symbol.kind)) {
255
+ return (
256
+ record.mechanism !== "inline" &&
257
+ record.symbol === finding.symbol.ref &&
258
+ record.path === finding.position.path
259
+ );
260
+ }
261
+ if (ROW_SUBJECTS.has(finding.symbol.kind)) {
226
262
  return false;
227
263
  }
228
264
  if (record.bound !== "" && record.bound === positionKey(finding.position)) {
@@ -233,11 +269,11 @@ export function pairs(record: SuppressionRecord, finding: Finding): boolean {
233
269
 
234
270
  /**
235
271
  * Decides every record against `would`, the findings the run would produce were no record
236
- * there: swept with no mark, where a kind about liveness reports the marked declaration,
237
- * and as swept, where a kind whose subject the sweep holds live reports it. The first
238
- * record in reading order claims a finding, so a second naming it is stale. A record whose
239
- * code the dials withhold from its declaration, or whose claimed finding they withhold, is
240
- * dormant.
272
+ * there: swept with no mark for a liveness kind, and as swept otherwise. The first record
273
+ * in reading order claims a finding, so a second naming it is stale; a part-kind record
274
+ * claims every finding of its code about a part of its declaration. A record is dormant
275
+ * when the dials withhold every finding it claims, read from those findings alone, or when
276
+ * it claims none and its code's severity is `allow`.
241
277
  */
242
278
  export function ledgerOf<F extends Finding>(
243
279
  records: readonly SuppressionRecord[],
@@ -247,23 +283,22 @@ export function ledgerOf<F extends Finding>(
247
283
  const taken = new Set<string>();
248
284
  const verdicts: Verdict[] = [];
249
285
  for (const record of records) {
250
- if (record.bound !== "" && dials.withholdsCode(record.code, record.bound)) {
251
- verdicts.push("dormant");
286
+ const open = (finding: F): boolean => !taken.has(claimKey(finding)) && pairs(record, finding);
287
+ const first = would.find(open);
288
+ const claimed =
289
+ first === undefined ? [] : isPartKind(record.code) ? would.filter(open) : [first];
290
+ if (claimed.length === 0) {
291
+ verdicts.push(record.bound !== "" && dials.allows(record.code) ? "dormant" : "stale");
252
292
  continue;
253
293
  }
254
- const claimed = would.find(
255
- (finding) => !taken.has(claimKey(finding)) && pairs(record, finding),
256
- );
257
- if (claimed === undefined) {
258
- verdicts.push("stale");
259
- continue;
294
+ for (const finding of claimed) {
295
+ taken.add(claimKey(finding));
260
296
  }
261
- taken.add(claimKey(claimed));
262
- verdicts.push(dials.withholds(claimed) ? "dormant" : "in-effect");
297
+ verdicts.push(claimed.every((finding) => dials.withholds(finding)) ? "dormant" : "in-effect");
263
298
  }
264
299
  return {
265
300
  verdicts,
266
- withheld: (finding) => taken.has(claimKey(finding)) && !ROW_SUBJECTS.has(finding.symbol.kind),
301
+ withheld: (finding) => taken.has(claimKey(finding)),
267
302
  };
268
303
  }
269
304
 
@@ -15,8 +15,8 @@ import {
15
15
  isStringLiteral,
16
16
  type ElementAccessExpression,
17
17
  type Node,
18
- } from "@typescript/native/unstable/ast";
19
- import type { Symbol as TSSymbol, Type } from "@typescript/native/unstable/sync";
18
+ } from "@typescript/native/ast";
19
+ import type { Symbol as TSSymbol, Type } from "@typescript/native/sync";
20
20
  import type { AliasChains } from "./alias-chain.ts";
21
21
  import type { InventorySymbol } from "./inventory.ts";
22
22
  import { isAnswered, type PropertyTables } from "./query.ts";
@@ -14,8 +14,8 @@ import {
14
14
  type Node,
15
15
  type PropertyAccessExpression,
16
16
  type SourceFile,
17
- } from "@typescript/native/unstable/ast";
18
- import type { Symbol as TSSymbol, Type } from "@typescript/native/unstable/sync";
17
+ } from "@typescript/native/ast";
18
+ import type { Symbol as TSSymbol, Type } from "@typescript/native/sync";
19
19
  import { packageOfSpecifier } from "./dependencies.ts";
20
20
  import type { LocatedDiagnostic } from "./discover.ts";
21
21
  import type { Host } from "./host.ts";
@@ -17,7 +17,7 @@ import {
17
17
  SyntaxKind,
18
18
  type Node,
19
19
  type SourceFile,
20
- } from "@typescript/native/unstable/ast";
20
+ } from "@typescript/native/ast";
21
21
 
22
22
  /** One global that aliases a module's export. */
23
23
  interface TypeQueryAlias {