@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
package/dist/src/roots.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * decides; the published API only supposes a consumer, so a published export is live
8
8
  * under reachability and still a candidate under reference counting.
9
9
  */
10
- import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, } from "@typescript/native/unstable/ast";
10
+ import { isExpressionWithTypeArguments, isFunctionLikeDeclaration, isPropertyAccessExpression, isQualifiedName, isTypeQueryNode, isTypeReferenceNode, isPropertyDeclaration, isVariableDeclaration, isVariableStatement, } from "@typescript/native/ast";
11
11
  import { aliasChains } from "./alias-chain.js";
12
12
  import { conventionEntries } from "./conventions.js";
13
13
  import { entryPoints } from "./entry-points.js";
@@ -1,15 +1,13 @@
1
1
  /**
2
- * The serialization-contract class: the data members and `toJSON` of every class,
3
- * interface and object type a value's static type names, and of every such type its
4
- * data members' types reach, where the value reaches `JSON.stringify` or a call
5
- * `ts.serializers` names, and those and `toString` where it is passed to an `unknown`
6
- * or `any` parameter of a function outside the analysis. A function of the project
7
- * handing such a parameter of its own to a destination is one, holding back what that
8
- * destination does, to a fixpoint. A function invoked through `Function.prototype.call`,
9
- * `apply` or `bind` is the callee, given the arguments those methods pass it.
2
+ * The serialization-contract class: the data members and `toJSON` of every type a value's
3
+ * static type reaches, where the value reaches `JSON.stringify` or a call `ts.serializers`
4
+ * names, and `toString` too where it reaches an `unknown` or `any` parameter of a function
5
+ * outside the analysis or an erased property of a type the program does not declare. A
6
+ * function of the project handing such a parameter to a destination is one, to a fixpoint,
7
+ * and a function invoked through `call`, `apply` or `bind` is the callee.
10
8
  */
11
- import { isArrayLiteralExpression, isArrowFunction, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isSetAccessorDeclaration, isSpreadElement, isStringLiteral, isVariableDeclaration, ModifierFlags, SyntaxKind, } from "@typescript/native/unstable/ast";
12
- import { SignatureKind, SymbolFlags, TypeFlags, } from "@typescript/native/unstable/sync";
9
+ import { isArrayLiteralExpression, isArrowFunction, isCallExpression, isClassDeclaration, isClassExpression, isClassStaticBlockDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isSetAccessorDeclaration, isSpreadElement, isStringLiteral, isVariableDeclaration, ModifierFlags, SyntaxKind, } from "@typescript/native/ast";
10
+ import { SignatureKind, SymbolFlags, TypeFlags, } from "@typescript/native/sync";
13
11
  import { aliasChains } from "./alias-chain.js";
14
12
  import { calleeName, resolvedTargets, valuesPassed } from "./calls.js";
15
13
  import { classMembers, memberComponent, typesAt } from "./class-members.js";
@@ -17,6 +15,7 @@ import { names, namesADeclaration, resolveEntries, spelledEntry, } from "./confi
17
15
  import { nodeKey } from "./inventory.js";
18
16
  import { renderPosition } from "./position.js";
19
17
  import { must } from "./query.js";
18
+ import { erasedStores } from "./erased-stores.js";
20
19
  import { valueReach } from "./value-reach.js";
21
20
  /** The serializer every run reads, whatever the configuration names. */
22
21
  const JSON_STRINGIFY = { shape: "global", global: "JSON.stringify" };
@@ -351,9 +350,13 @@ function destinations(input, chains, walked) {
351
350
  };
352
351
  return { invocation, at, mayReceive };
353
352
  }
353
+ /** What two reaches carry together. */
354
+ function joined(a, b) {
355
+ return { classes: new Set([...a.classes, ...b.classes]), members: [...a.members, ...b.members] };
356
+ }
354
357
  /** Each argument of a call that can receive one whose value carries a class of the target. */
355
- function flowsOf(project, held, sites, to) {
356
- const reach = valueReach(project, held);
358
+ function flowsOf(project, held, sites, to, stores) {
359
+ const reach = valueReach(project, held, stores.inside);
357
360
  const arguments_ = sites.flatMap((site) => to.mayReceive(site.call)
358
361
  ? to.invocation(site.call).arguments.flatMap((argument, index) => {
359
362
  const value = evaluated(argument);
@@ -363,7 +366,7 @@ function flowsOf(project, held, sites, to) {
363
366
  const asked = arguments_.filter((one) => one.value.kind !== SyntaxKind.ThisKeyword);
364
367
  const types = typesAt(project, asked.map((one) => one.value));
365
368
  const typeOf = new Map(asked.map((one, index) => [one.value, types[index]]));
366
- return arguments_.flatMap((one) => {
369
+ const passed = arguments_.flatMap((one) => {
367
370
  const type = typeOf.get(one.value);
368
371
  let reached = { classes: new Set(), members: [] };
369
372
  if (one.value.kind === SyntaxKind.ThisKeyword) {
@@ -375,12 +378,38 @@ function flowsOf(project, held, sites, to) {
375
378
  else if (type !== undefined) {
376
379
  reached = reach.reachedFrom(type);
377
380
  }
381
+ for (const stored of stores.ofVariable(one.value) ?? []) {
382
+ reached = joined(reached, reach.reachedFrom(stored));
383
+ }
378
384
  const carries = reached.classes.size > 0 || reached.members.length > 0;
379
385
  const destination = carries ? to.at(one.site.call, one.argument) : undefined;
380
386
  return destination === undefined
381
387
  ? []
382
- : [{ site: one.value, classes: reached.classes, members: reached.members, to: destination }];
388
+ : [
389
+ {
390
+ site: one.value,
391
+ classes: reached.classes,
392
+ members: reached.members,
393
+ to: destination,
394
+ detail: `passed to ${destination.callee}`,
395
+ },
396
+ ];
397
+ });
398
+ const left = stores.outside.flatMap((store) => {
399
+ const reached = reach.reachedFrom(store.type);
400
+ return reached.classes.size === 0 && reached.members.length === 0
401
+ ? []
402
+ : [
403
+ {
404
+ site: store.value,
405
+ classes: reached.classes,
406
+ members: reached.members,
407
+ to: { reach: "conversions", callee: store.property },
408
+ detail: `stored in the property ${store.property} of a type the program does not declare`,
409
+ },
410
+ ];
383
411
  });
412
+ return [...passed, ...left];
384
413
  }
385
414
  /**
386
415
  * The serialization-contract detector: each data member and `toJSON` of each class,
@@ -392,7 +421,7 @@ export function serializationContract(input) {
392
421
  const { project, held, targetRoot } = input;
393
422
  const walked = walkProject(project, held);
394
423
  const chains = aliasChains(project, held);
395
- const flows = flowsOf(project, held, walked.sites, destinations(input, chains, walked));
424
+ const flows = flowsOf(project, held, walked.sites, destinations(input, chains, walked), erasedStores(project));
396
425
  if (flows.length === 0) {
397
426
  return [];
398
427
  }
@@ -406,7 +435,7 @@ export function serializationContract(input) {
406
435
  const found = [];
407
436
  for (const flow of flows) {
408
437
  const site = renderPosition(flow.site.getSourceFile(), targetRoot, flow.site.getStart());
409
- const detail = `passed to ${flow.to.callee}`;
438
+ const { detail } = flow;
410
439
  for (const member of [...flow.classes].flatMap((id) => members.get(id) ?? [])) {
411
440
  const symbol = byId.get(member.id);
412
441
  const name = symbol === undefined ? "" : (memberComponent(symbol, byId) ?? "");
@@ -1,4 +1,4 @@
1
- import { API, SymbolFlags } from "@typescript/native/unstable/sync";
1
+ import { API, SymbolFlags } from "@typescript/native/sync";
2
2
  import { componentLayer, mapperManifest } from "./component-layer.js";
3
3
  import { DiscoveryError } from "./discover.js";
4
4
  import { joinPath } from "./paths.js";
@@ -48,6 +48,8 @@ export function openEngine(options) {
48
48
  }),
49
49
  }),
50
50
  readsComponents: mapper !== undefined,
51
+ // Entries, not a resolveModuleName callback: this fallback resolves with the importing
52
+ // file's project-reference options, which the compiler never passes to a callback.
51
53
  createModuleResolver: (options, entries) => api.createModuleResolver(options, {
52
54
  moduleResolutions: { fallback: "resolve", entries: [...entries] },
53
55
  }),
@@ -227,12 +229,12 @@ function projectsOf(snapshot, built, written) {
227
229
  /**
228
230
  * Runs `visit` over every project the compiler configurations name, inside one never updated
229
231
  * snapshot of one client, and answers in their order. A configuration `programs` describes is
230
- * built as that program, or opened from the configuration it writes, instead of as a project.
231
- * One the snapshot holds nothing for ends the run under `"refuse"` and is named in the result
232
- * under `"record"`. `last` runs after the last visit while the client still answers; the
233
- * snapshot is disposed and the client released after it, whether the visit completed or threw.
232
+ * built as that program, or opened from the configuration it writes. One the snapshot holds
233
+ * nothing for ends the run under `"refuse"` and is named in the result under `"record"`.
234
+ * `first` runs over every opened project before the first visit and `last` after the last,
235
+ * both while the client answers; the snapshot and the client are then released.
234
236
  */
235
- export function runSession(engine, configFiles, visit, unopened = "refuse", programs = new Map(), last = () => undefined) {
237
+ export function runSession(engine, configFiles, visit, unopened = "refuse", programs = new Map(), last = () => undefined, first = () => undefined) {
236
238
  try {
237
239
  const built = [];
238
240
  const params = [];
@@ -253,7 +255,7 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
253
255
  try {
254
256
  const opened = projectsOf(snapshot, built, written);
255
257
  const log = new UnansweredLog();
256
- const projects = [];
258
+ const views = [];
257
259
  const missing = [];
258
260
  for (const configFile of configFiles) {
259
261
  const project = opened.get(configFile);
@@ -264,8 +266,13 @@ export function runSession(engine, configFiles, visit, unopened = "refuse", prog
264
266
  missing.push(configFile);
265
267
  continue;
266
268
  }
267
- projects.push(visit(viewOf(project, configFile, engine, log)));
269
+ views.push(viewOf(project, configFile, engine, log));
268
270
  }
271
+ first(views.map((view) => ({
272
+ configFile: view.configFile,
273
+ fileNames: () => view.ownSourceFiles().map((file) => file.fileName),
274
+ })));
275
+ const projects = views.map((view) => visit(view));
269
276
  last();
270
277
  return {
271
278
  projects,
@@ -2,7 +2,7 @@
2
2
  * The function a declaration of the inventory declares, where it declares one: the node
3
3
  * whose parameters, body and result are the declaration's own parts.
4
4
  */
5
- import { isArrowFunction, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isMethodDeclaration, isPropertyDeclaration, isVariableDeclaration, } from "@typescript/native/unstable/ast";
5
+ import { isArrowFunction, isClassDeclaration, isClassExpression, isFunctionDeclaration, isFunctionExpression, isMethodDeclaration, isPropertyDeclaration, isVariableDeclaration, } from "@typescript/native/ast";
6
6
  /** Whether one node is a function value written in place: an arrow or a function expression. */
7
7
  function isFunctionValue(node) {
8
8
  return node !== undefined && (isArrowFunction(node) || isFunctionExpression(node));
@@ -3,7 +3,7 @@
3
3
  * a sweep does not keep: a sweep asks whether anything names a declaration, and a store
4
4
  * names it as surely as a read does.
5
5
  */
6
- import { isGetAccessorDeclaration, isSetAccessorDeclaration, } from "@typescript/native/unstable/ast";
6
+ import { isGetAccessorDeclaration, isSetAccessorDeclaration, } from "@typescript/native/ast";
7
7
  import { nodeKey } from "./inventory.js";
8
8
  /** The class members one project's own files declare by a getter or a setter. */
9
9
  export function accessorsOf(project, held) {
@@ -27,6 +27,12 @@ const BASELINE_SHAPE = {
27
27
  documentWant: "an object carrying the baseline array, and an optional description",
28
28
  recordWant: "a row naming code, symbol, path and reason, each a string",
29
29
  };
30
+ /**
31
+ * The reference scheme of this analyzer's language. A record of the other scheme is
32
+ * form-checked and then left to the analyzer of its language: it binds nothing here, counts
33
+ * in neither total and is reported under no code.
34
+ */
35
+ const OWN_SCHEME = "ts://";
30
36
  /** The closed key list of one entry or row. */
31
37
  const RECORD_KEYS = new Set(["code", "symbol", "path", "reason"]);
32
38
  const CODE_WANT = "DS followed by four digits";
@@ -80,6 +86,9 @@ function readRecord(value, site, byReference, shape) {
80
86
  if (path !== undefined && path !== "" && !isEntryPath(path)) {
81
87
  return refuse(JSON.stringify(path), PATH_WANT);
82
88
  }
89
+ if (!symbol.startsWith(OWN_SCHEME)) {
90
+ return { records: [], refusals: [] };
91
+ }
83
92
  const written = {
84
93
  code,
85
94
  symbol,
@@ -196,12 +205,55 @@ export function rowReason(provenance) {
196
205
  return `recorded by ${provenance.analyzer} ${provenance.version}`;
197
206
  }
198
207
  /**
199
- * A baseline holding one row per finding, in the order given, which is the report's. Every
200
- * row carries the provenance as its reason. A finding missing a value a row needs, and an
201
- * identity missing its name or version, are refused rather than written, because the
202
- * grammar requires a reason on every row.
208
+ * The rows of the other language's scheme that the baseline document `text` holds, in
209
+ * document order and as written, which a write keeps. A document that is not a baseline,
210
+ * or a row in a shape no record takes, is refused rather than dropped.
211
+ */
212
+ export function otherSchemeRows(text) {
213
+ const refuse = (detail) => {
214
+ throw new Error(`${BASELINE_FILE} cannot be rewritten keeping the other language's rows: ${detail}`);
215
+ };
216
+ let parsed;
217
+ try {
218
+ parsed = JSON.parse(text);
219
+ }
220
+ catch (error) {
221
+ return refuse(error instanceof Error ? error.message : String(error));
222
+ }
223
+ const held = isObject(parsed) ? parsed[BASELINE_SHAPE.array] : undefined;
224
+ if (!Array.isArray(held)) {
225
+ return refuse(BASELINE_SHAPE.documentWant);
226
+ }
227
+ return held.flatMap((value) => {
228
+ try {
229
+ readRecord(value, documentSite(BASELINE_SHAPE), new Map(), BASELINE_SHAPE);
230
+ }
231
+ catch (error) {
232
+ return refuse(error instanceof Error ? error.message : String(error));
233
+ }
234
+ // readRecord has checked every member is a string and the code and symbol are present.
235
+ const { code, symbol, path, reason } = value;
236
+ if (code === undefined || symbol === undefined || symbol.startsWith(OWN_SCHEME)) {
237
+ return [];
238
+ }
239
+ return [
240
+ {
241
+ code,
242
+ symbol,
243
+ ...(path === undefined ? {} : { path }),
244
+ ...(reason === undefined ? {} : { reason }),
245
+ },
246
+ ];
247
+ });
248
+ }
249
+ /**
250
+ * A baseline holding one row per finding, in the order given, which is the report's, after
251
+ * the other language's rows `kept`, so two analyzers writing one document in either order
252
+ * write the same bytes. Every recorded row carries the provenance as its reason. A finding
253
+ * missing a value a row needs, and an identity missing its name or version, are refused
254
+ * rather than written, because the grammar requires a reason on every row.
203
255
  */
204
- export function writeBaseline(findings, provenance) {
256
+ export function writeBaseline(findings, provenance, kept = []) {
205
257
  if (provenance.analyzer === "" || provenance.version === "") {
206
258
  throw new Error(`a baseline row would carry no reason: the analyzer identity names ${JSON.stringify(provenance.analyzer)} at version ${JSON.stringify(provenance.version)}`);
207
259
  }
@@ -212,5 +264,5 @@ export function writeBaseline(findings, provenance) {
212
264
  }
213
265
  return { code: found.code, symbol: found.symbol, path: found.path, reason };
214
266
  });
215
- return jsonChunks({ description: BASELINE_DESCRIPTION, baseline }, 2);
267
+ return jsonChunks({ description: BASELINE_DESCRIPTION, baseline: [...kept, ...baseline] }, 2);
216
268
  }
@@ -1,9 +1,9 @@
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
- import { SyntaxKind } from "@typescript/native/unstable/ast";
6
+ import { SyntaxKind } from "@typescript/native/ast";
7
7
  import { nodeKey } from "./inventory.js";
8
8
  import { byPosition, positionKey, renderPosition } from "./position.js";
9
9
  import { signatureOf } from "./signatures.js";
@@ -170,6 +170,14 @@ function declarationLines(file, held, symbols) {
170
170
  file.forEachChild(visit);
171
171
  return below;
172
172
  }
173
+ /**
174
+ * Whether the comment at `offset` follows a token on its own line, so it binds the
175
+ * declarations that begin on that line rather than on the next.
176
+ */
177
+ function trails(file, offset) {
178
+ const { line } = file.getLineAndCharacterOfPosition(offset);
179
+ return tokenStart(file.text, file.getPositionOfLineAndCharacter(line, 0)) < offset;
180
+ }
173
181
  /**
174
182
  * Every comment in the namespace one project's own files hold, each with the declarations
175
183
  * it binds, read while the project's view is open.
@@ -187,19 +195,20 @@ export function inlineDirectives(project, held, targetRoot) {
187
195
  const below = declarationLines(file, held, symbols);
188
196
  for (const comment of comments) {
189
197
  const site = renderPosition(file, targetRoot, comment.offset);
198
+ const target = trails(file, comment.offset) ? site.line : site.line + 1;
190
199
  found.push({
191
200
  site,
192
201
  text: comment.text,
193
202
  directive: classifyComment(comment.text),
194
- bound: [...(below.get(site.line + 1) ?? [])],
203
+ bound: [...(below.get(target) ?? [])],
195
204
  });
196
205
  }
197
206
  }
198
207
  return found;
199
208
  }
200
209
  /**
201
- * The inline directives of a run's projects, each bound to the declarations that begin on
202
- * the line below it, one directive binding the union of what each project holding its file
210
+ * The inline directives of a run's projects, each bound to the declarations the placement
211
+ * rule gives it, one directive binding the union of what each project holding its file
203
212
  * declares there. A directive that lacks only its reason is one {@link Refusal} per code;
204
213
  * any other malformed comment in the namespace ends the read with a
205
214
  * {@link SuppressionError} naming the first one written.
@@ -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
  import { ConfigError } from "./config.js";
9
10
  import { positionKey } from "./position.js";
@@ -68,28 +69,48 @@ export function isSymbolRef(value) {
68
69
  export function blankReason(reason) {
69
70
  return reason === undefined || /^[ \t\r\n]*$/u.test(reason);
70
71
  }
72
+ /**
73
+ * The subject kinds that are a dependency row, each with the code that reports one. An
74
+ * ignore entry or a baseline row naming the row's reference and file withholds its finding
75
+ * and marks nothing, and an inline directive never binds one.
76
+ */
77
+ const DEPENDENCY_SUBJECTS = new Map([
78
+ ["dependency", ["DS1601"]],
79
+ ["module-directive", ["DS1605"]],
80
+ ]);
71
81
  /**
72
82
  * 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.
83
+ * each with the codes that report one. Apart from a dependency row, nothing withholds a
84
+ * finding about one.
75
85
  */
76
86
  const ROW_SUBJECTS = new Map([
77
87
  ["file", ["DS1501", "DS1502"]],
78
- ["dependency", ["DS1601"]],
79
- ["module-directive", ["DS1605"]],
88
+ ...DEPENDENCY_SUBJECTS,
80
89
  ["root", ["DS1704"]],
81
90
  ["configured-declaration", ["DS1706"]],
82
91
  ["suppression", ["DS1701", "DS1702", "DS1703"]],
83
92
  ["edge", ["DS1705"]],
84
93
  ]);
85
- /** Whether a finding's subject is a row of a document, which no record can withhold. */
94
+ /** Whether a finding's subject is a row of a document rather than a declaration. */
86
95
  export function isRowSubject(kind) {
87
96
  return ROW_SUBJECTS.has(kind);
88
97
  }
89
- /** Whether a code reports a row of a document, so no record naming it can match. */
98
+ /** Whether a code reports a row of a document. */
90
99
  export function isRowCode(code) {
91
100
  return [...ROW_SUBJECTS.values()].some((codes) => codes.includes(code));
92
101
  }
102
+ /** Whether a finding's subject is a dependency row. */
103
+ export function isDependencySubject(kind) {
104
+ return DEPENDENCY_SUBJECTS.has(kind);
105
+ }
106
+ /** Whether a code reports a dependency row, which only an ignore entry or a baseline row names. */
107
+ export function isDependencyCode(code) {
108
+ return [...DEPENDENCY_SUBJECTS.values()].some((codes) => codes.includes(code));
109
+ }
110
+ /** Whether a code reports a row of a document that no record of any mechanism can name. */
111
+ export function isUnsuppressibleCode(code) {
112
+ return isRowCode(code) && !isDependencyCode(code);
113
+ }
93
114
  /**
94
115
  * The subject kinds that are a part of a declaration rather than a declaration: a part
95
116
  * has no reference of its own, so a finding about one names the declaration that holds
@@ -124,10 +145,19 @@ function claimKey(finding) {
124
145
  * Whether one record names one finding. A record reaches a finding in two ways: it bound
125
146
  * the declaration at the finding's position, or it names the finding's reference and the
126
147
  * file the finding is reported in, each exactly. The second is how an entry copied from a
127
- * finding about a part of a declaration reaches the part.
148
+ * finding about a part of a declaration reaches the part, and the only way any record
149
+ * reaches a dependency row.
128
150
  */
129
151
  export function pairs(record, finding) {
130
- if (record.code !== finding.code || ROW_SUBJECTS.has(finding.symbol.kind)) {
152
+ if (record.code !== finding.code) {
153
+ return false;
154
+ }
155
+ if (DEPENDENCY_SUBJECTS.has(finding.symbol.kind)) {
156
+ return (record.mechanism !== "inline" &&
157
+ record.symbol === finding.symbol.ref &&
158
+ record.path === finding.position.path);
159
+ }
160
+ if (ROW_SUBJECTS.has(finding.symbol.kind)) {
131
161
  return false;
132
162
  }
133
163
  if (record.bound !== "" && record.bound === positionKey(finding.position)) {
@@ -137,31 +167,31 @@ export function pairs(record, finding) {
137
167
  }
138
168
  /**
139
169
  * Decides every record against `would`, the findings the run would produce were no record
140
- * there: swept with no mark, where a kind about liveness reports the marked declaration,
141
- * and as swept, where a kind whose subject the sweep holds live reports it. The first
142
- * record in reading order claims a finding, so a second naming it is stale. A record whose
143
- * code the dials withhold from its declaration, or whose claimed finding they withhold, is
144
- * dormant.
170
+ * there: swept with no mark for a liveness kind, and as swept otherwise. The first record
171
+ * in reading order claims a finding, so a second naming it is stale; a part-kind record
172
+ * claims every finding of its code about a part of its declaration. A record is dormant
173
+ * when the dials withhold every finding it claims, read from those findings alone, or when
174
+ * it claims none and its code's severity is `allow`.
145
175
  */
146
176
  export function ledgerOf(records, would, dials) {
147
177
  const taken = new Set();
148
178
  const verdicts = [];
149
179
  for (const record of records) {
150
- if (record.bound !== "" && dials.withholdsCode(record.code, record.bound)) {
151
- verdicts.push("dormant");
180
+ const open = (finding) => !taken.has(claimKey(finding)) && pairs(record, finding);
181
+ const first = would.find(open);
182
+ const claimed = first === undefined ? [] : isPartKind(record.code) ? would.filter(open) : [first];
183
+ if (claimed.length === 0) {
184
+ verdicts.push(record.bound !== "" && dials.allows(record.code) ? "dormant" : "stale");
152
185
  continue;
153
186
  }
154
- const claimed = would.find((finding) => !taken.has(claimKey(finding)) && pairs(record, finding));
155
- if (claimed === undefined) {
156
- verdicts.push("stale");
157
- continue;
187
+ for (const finding of claimed) {
188
+ taken.add(claimKey(finding));
158
189
  }
159
- taken.add(claimKey(claimed));
160
- verdicts.push(dials.withholds(claimed) ? "dormant" : "in-effect");
190
+ verdicts.push(claimed.every((finding) => dials.withholds(finding)) ? "dormant" : "in-effect");
161
191
  }
162
192
  return {
163
193
  verdicts,
164
- withheld: (finding) => taken.has(claimKey(finding)) && !ROW_SUBJECTS.has(finding.symbol.kind),
194
+ withheld: (finding) => taken.has(claimKey(finding)),
165
195
  };
166
196
  }
167
197
  /** The suppression counts of one run's records and the ledger over them. */
@@ -4,7 +4,7 @@
4
4
  * whose computed name is that same constant, and a store through the access writes it.
5
5
  * No name node resolves to such a member: the key's name resolves to the constant.
6
6
  */
7
- import { isComputedPropertyName, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedExpression, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/unstable/ast";
7
+ import { isComputedPropertyName, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isParenthesizedExpression, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
8
8
  import { isAnswered } from "./query.js";
9
9
  /** The identifier an element access's argument names its key by, where it is a name. */
10
10
  export function keyNameOf(argument) {
@@ -5,7 +5,7 @@
5
5
  * which only ever withholds a finding. An error the compiler places in no file is read as
6
6
  * a project that cannot be analyzed.
7
7
  */
8
- import { isClassDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/unstable/ast";
8
+ import { isClassDeclaration, isNoSubstitutionTemplateLiteral, isPropertyAccessExpression, isStringLiteral, } from "@typescript/native/ast";
9
9
  import { packageOfSpecifier } from "./dependencies.js";
10
10
  import { nodeKey } from "./inventory.js";
11
11
  import { dirnamePath, isAbsolutePath, joinPath, relativePath, resolvePath } from "./paths.js";
@@ -4,7 +4,7 @@
4
4
  * `typeof import("<module>").<name>`. A use of the global is a use of the export,
5
5
  * and the type query that says so is no use of its own.
6
6
  */
7
- import { isIdentifier, isImportTypeNode, isIndexedAccessTypeNode, isLiteralTypeNode, isModuleBlock, isModuleDeclaration, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/unstable/ast";
7
+ import { isIdentifier, isImportTypeNode, isIndexedAccessTypeNode, isLiteralTypeNode, isModuleBlock, isModuleDeclaration, isStringLiteral, isVariableStatement, SyntaxKind, } from "@typescript/native/ast";
8
8
  /** Whether one type is `typeof import("<string>")`, with no type argument. */
9
9
  function typeOfImport(node) {
10
10
  if (!isImportTypeNode(node) || !node.isTypeOf || node.typeArguments !== undefined) {