@cplieger/deadset-ts 4.0.1 → 5.1.0

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 (138) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/component-mapper.js +5 -0
  3. package/dist/bin/mapper-connection.js +101 -0
  4. package/dist/bin/node-host.js +24 -2
  5. package/dist/src/analysis.js +301 -41
  6. package/dist/src/component-files.js +612 -0
  7. package/dist/src/component-layer.js +132 -0
  8. package/dist/src/config.js +9 -2
  9. package/dist/src/configuration-files.js +290 -0
  10. package/dist/src/conformance-record.js +3 -9
  11. package/dist/src/contextual-uses.js +240 -0
  12. package/dist/src/convention-rows.js +389 -0
  13. package/dist/src/conventions.js +565 -0
  14. package/dist/src/dependencies.js +36 -3
  15. package/dist/src/destructuring.js +6 -6
  16. package/dist/src/discover.js +10 -3
  17. package/dist/src/emit-map.js +72 -0
  18. package/dist/src/entry-point-gaps.js +81 -35
  19. package/dist/src/entry-points.js +50 -32
  20. package/dist/src/exempt-classes.js +6 -1
  21. package/dist/src/exempt.js +32 -20
  22. package/dist/src/findings/completion.js +46 -32
  23. package/dist/src/findings/emitters.js +22 -1
  24. package/dist/src/findings/interfaces.js +6 -2
  25. package/dist/src/findings/reads-and-writes.js +53 -9
  26. package/dist/src/findings/unused-declarations.js +47 -3
  27. package/dist/src/generated-file.js +24 -0
  28. package/dist/src/graph.js +13 -5
  29. package/dist/src/interface-satisfaction.js +2 -68
  30. package/dist/src/inventory.js +8 -3
  31. package/dist/src/kinds.js +6 -5
  32. package/dist/src/manifest.js +37 -11
  33. package/dist/src/matrix.js +21 -13
  34. package/dist/src/partial.js +3 -1
  35. package/dist/src/pnpm-workspace.js +120 -0
  36. package/dist/src/position.js +17 -2
  37. package/dist/src/print.js +2 -0
  38. package/dist/src/query.js +15 -0
  39. package/dist/src/references.js +404 -36
  40. package/dist/src/report.js +26 -6
  41. package/dist/src/reporters/annotations.js +10 -3
  42. package/dist/src/reporters/template.js +3 -0
  43. package/dist/src/resolve.js +37 -11
  44. package/dist/src/roots.js +37 -8
  45. package/dist/src/run.js +13 -3
  46. package/dist/src/schema.js +2 -0
  47. package/dist/src/session.js +141 -34
  48. package/dist/src/setup-failure.js +23 -0
  49. package/dist/src/source-files.js +4 -61
  50. package/dist/src/stores.js +3 -0
  51. package/dist/src/sweep.js +4 -2
  52. package/dist/src/template-field.js +38 -6
  53. package/dist/src/test-support.js +59 -0
  54. package/dist/src/type-errors.js +252 -0
  55. package/dist/src/type-query-alias.js +92 -0
  56. package/dist/src/value-flow.js +74 -0
  57. package/dist/src/verbs/analyze.js +24 -4
  58. package/dist/src/verbs/explain.js +4 -1
  59. package/dist/src/verbs/print-projects.js +17 -56
  60. package/dist/src/verbs/print-retained.js +7 -2
  61. package/dist/src/verbs/print-roots.js +7 -2
  62. package/dist/src/version-range.js +255 -0
  63. package/dist/src/version.js +1 -1
  64. package/dist/src/workspace-manifests.js +68 -0
  65. package/dist/src/workspace-programs.js +342 -0
  66. package/dist/src/workspace-resolution.js +307 -0
  67. package/dist/src/workspace-run.js +75 -0
  68. package/dist/src/workspace.js +358 -0
  69. package/jsr.json +1 -1
  70. package/package.json +3 -2
  71. package/src/analysis.ts +475 -72
  72. package/src/component-files.ts +718 -0
  73. package/src/component-layer.ts +160 -0
  74. package/src/config.ts +11 -2
  75. package/src/configuration-files.ts +370 -0
  76. package/src/conformance-record.ts +3 -10
  77. package/src/contextual-uses.ts +354 -0
  78. package/src/convention-rows.ts +448 -0
  79. package/src/conventions.ts +788 -0
  80. package/src/dependencies.ts +49 -2
  81. package/src/destructuring.ts +16 -6
  82. package/src/discover.ts +10 -6
  83. package/src/emit-map.ts +93 -0
  84. package/src/entry-point-gaps.ts +92 -38
  85. package/src/entry-points.ts +79 -36
  86. package/src/exempt-classes.ts +6 -1
  87. package/src/exempt.ts +69 -20
  88. package/src/file-facts.ts +2 -0
  89. package/src/findings/completion.ts +55 -36
  90. package/src/findings/emitters.ts +32 -1
  91. package/src/findings/interfaces.ts +9 -2
  92. package/src/findings/reads-and-writes.ts +70 -9
  93. package/src/findings/unused-declarations.ts +60 -3
  94. package/src/generated-file.ts +27 -0
  95. package/src/graph.ts +15 -4
  96. package/src/host.ts +23 -0
  97. package/src/index.ts +1 -1
  98. package/src/interface-satisfaction.ts +1 -92
  99. package/src/inventory.ts +17 -3
  100. package/src/kinds.ts +7 -5
  101. package/src/manifest.ts +47 -14
  102. package/src/matrix.ts +43 -15
  103. package/src/partial.ts +6 -1
  104. package/src/pnpm-workspace.ts +135 -0
  105. package/src/position.ts +23 -2
  106. package/src/print.ts +2 -0
  107. package/src/query.ts +26 -0
  108. package/src/references.ts +472 -40
  109. package/src/report.ts +56 -6
  110. package/src/reporters/annotations.ts +12 -3
  111. package/src/reporters/template.ts +3 -0
  112. package/src/resolve.ts +65 -11
  113. package/src/roots.ts +70 -9
  114. package/src/run.ts +15 -5
  115. package/src/schema.ts +2 -0
  116. package/src/session.ts +218 -37
  117. package/src/setup-failure.ts +39 -0
  118. package/src/source-files.ts +4 -66
  119. package/src/stores.ts +9 -0
  120. package/src/sweep.ts +9 -2
  121. package/src/template-field.ts +42 -7
  122. package/src/test-support.ts +77 -0
  123. package/src/type-errors.ts +340 -0
  124. package/src/type-query-alias.ts +133 -0
  125. package/src/value-flow.ts +104 -0
  126. package/src/verbs/analyze.ts +38 -4
  127. package/src/verbs/explain.ts +4 -1
  128. package/src/verbs/print-projects.ts +17 -69
  129. package/src/verbs/print-retained.ts +7 -2
  130. package/src/verbs/print-roots.ts +7 -2
  131. package/src/verbs/verb.ts +6 -3
  132. package/src/version-range.ts +309 -0
  133. package/src/version.ts +1 -1
  134. package/src/workspace-manifests.ts +100 -0
  135. package/src/workspace-programs.ts +486 -0
  136. package/src/workspace-resolution.ts +393 -0
  137. package/src/workspace-run.ts +148 -0
  138. package/src/workspace.ts +463 -0
@@ -1,16 +1,18 @@
1
- import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExternalModuleReference, isForInStatement, isForOfStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, SyntaxKind, } from "@typescript/native/unstable/ast";
1
+ import { isArrayLiteralExpression, isAsExpression, isBinaryExpression, isCallExpression, isDecorator, isDeleteExpression, isElementAccessExpression, isExportDeclaration, isExpressionStatement, isExternalModuleReference, isForInStatement, isForOfStatement, isForStatement, isImportDeclaration, isImportEqualsDeclaration, isNamespaceExport, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPostfixUnaryExpression, isPrefixUnaryExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadAssignment, isSpreadElement, isStringLiteral, isVoidExpression, SyntaxKind, } from "@typescript/native/unstable/ast";
2
2
  import { aliasChains } from "./alias-chain.js";
3
+ import { contextualUses } from "./contextual-uses.js";
3
4
  import { destructuring, propertyReadsOf } from "./destructuring.js";
4
5
  import { globExpression } from "./glob.js";
5
6
  import { declarationsByName, nodeKey } from "./inventory.js";
6
7
  import { relativePath } from "./paths.js";
7
- import { byPosition, renderPosition } from "./position.js";
8
- import { DEFAULT_BATCH_CAP, UNANSWERED } from "./query.js";
8
+ import { byPosition, isComponentFile, renderPosition } from "./position.js";
9
+ import { DEFAULT_BATCH_CAP, propertyTables, UNANSWERED } from "./query.js";
10
+ import { typeQueryAliases } from "./type-query-alias.js";
11
+ import { handedOn, valuesOf } from "./value-flow.js";
9
12
  /**
10
13
  * The operators whose left side one assignment stores into. A compound assignment is
11
- * among them: it reads its target only to write the result back, so nothing else
12
- * receives what it read, and a declaration whose every other reference is one of these
13
- * carries no information out of itself.
14
+ * among them: where its value is discarded it reads its target only to write the result
15
+ * back, so nothing else receives what it read.
14
16
  */
15
17
  const ASSIGNMENTS = new Set([
16
18
  SyntaxKind.EqualsToken,
@@ -30,6 +32,36 @@ const ASSIGNMENTS = new Set([
30
32
  SyntaxKind.QuestionQuestionEqualsToken,
31
33
  SyntaxKind.CaretEqualsToken,
32
34
  ]);
35
+ /**
36
+ * The assignments that read their target to decide whether to store at all, so the read
37
+ * carries information out whatever becomes of the result.
38
+ */
39
+ const LOGICAL_ASSIGNMENTS = new Set([
40
+ SyntaxKind.BarBarEqualsToken,
41
+ SyntaxKind.AmpersandAmpersandEqualsToken,
42
+ SyntaxKind.QuestionQuestionEqualsToken,
43
+ ]);
44
+ /**
45
+ * Whether the value of one store expression is discarded: it is a statement of its own, a
46
+ * clause of a `for` loop, the operand of `void`, or the left operand of a comma.
47
+ */
48
+ function valueDiscarded(node) {
49
+ let child = node;
50
+ let parent = node.parent;
51
+ while (isParenthesizedExpression(parent)) {
52
+ child = parent;
53
+ parent = parent.parent;
54
+ }
55
+ if (isExpressionStatement(parent) || isVoidExpression(parent)) {
56
+ return true;
57
+ }
58
+ if (isForStatement(parent)) {
59
+ return parent.initializer === child || parent.incrementor === child;
60
+ }
61
+ return (isBinaryExpression(parent) &&
62
+ parent.operatorToken.kind === SyntaxKind.CommaToken &&
63
+ parent.left === child);
64
+ }
33
65
  /** Whether one node is an identifier or a private name, the two forms a name takes. */
34
66
  function isName(node) {
35
67
  return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.PrivateIdentifier;
@@ -64,7 +96,11 @@ function markWrite(target, writes) {
64
96
  return;
65
97
  }
66
98
  if (isElementAccessExpression(held)) {
67
- markWrite(held.expression, writes);
99
+ // A store through a member's value lands in storage every holder of the value
100
+ // reads, so it reads the member; a variable only ever stored into stays written.
101
+ if (!isPropertyAccessExpression(unparenthesized(held.expression))) {
102
+ markWrite(held.expression, writes);
103
+ }
68
104
  return;
69
105
  }
70
106
  if (isArrayLiteralExpression(held)) {
@@ -97,12 +133,18 @@ function markWrite(target, writes) {
97
133
  * Records every store one node performs: an assignment, a compound assignment, an
98
134
  * increment or a decrement, a `delete`, and an iteration that assigns to a name declared
99
135
  * elsewhere. An iteration that declares its own name stores into nothing the inventory
100
- * holds.
136
+ * holds. A store that reads its target as well records it in `reads`: a logical
137
+ * assignment, and a compound assignment, an increment or a decrement whose value is used.
101
138
  */
102
- function markStores(node, writes) {
139
+ function markStores(node, writes, reads) {
103
140
  if (isBinaryExpression(node)) {
104
- if (ASSIGNMENTS.has(node.operatorToken.kind)) {
141
+ const operator = node.operatorToken.kind;
142
+ if (ASSIGNMENTS.has(operator)) {
105
143
  markWrite(node.left, writes);
144
+ if (LOGICAL_ASSIGNMENTS.has(operator) ||
145
+ (operator !== SyntaxKind.EqualsToken && !valueDiscarded(node))) {
146
+ markWrite(node.left, reads);
147
+ }
106
148
  }
107
149
  return;
108
150
  }
@@ -110,6 +152,9 @@ function markStores(node, writes) {
110
152
  if (node.operator === SyntaxKind.PlusPlusToken ||
111
153
  node.operator === SyntaxKind.MinusMinusToken) {
112
154
  markWrite(node.operand, writes);
155
+ if (!valueDiscarded(node)) {
156
+ markWrite(node.operand, reads);
157
+ }
113
158
  }
114
159
  return;
115
160
  }
@@ -193,20 +238,45 @@ function linksOf(file, node, declarations, enclosing) {
193
238
  }
194
239
  return found;
195
240
  }
241
+ /**
242
+ * Whether one object literal is a destructuring target, written on the left of an
243
+ * assignment or as a `for` loop's initializer, through the literals and spreads it nests in.
244
+ */
245
+ function isAssignmentPattern(node) {
246
+ let child = node;
247
+ let parent = node.parent;
248
+ while (isParenthesizedExpression(parent) ||
249
+ isArrayLiteralExpression(parent) ||
250
+ isObjectLiteralExpression(parent) ||
251
+ isSpreadElement(parent) ||
252
+ isSpreadAssignment(parent) ||
253
+ (isPropertyAssignment(parent) && parent.initializer === child)) {
254
+ child = parent;
255
+ parent = parent.parent;
256
+ }
257
+ if (isBinaryExpression(parent)) {
258
+ return parent.operatorToken.kind === SyntaxKind.EqualsToken && parent.left === child;
259
+ }
260
+ return (isForInStatement(parent) || isForOfStatement(parent)) && parent.initializer === child;
261
+ }
196
262
  /**
197
263
  * Every name node one file holds that is a use rather than a declaration, in source
198
264
  * order, each with its enclosing declaration and its use, and every re-export the file
199
265
  * writes. One walk answers all of it, because a second would have to agree with this one
200
266
  * about which nodes are names. A declaration's own declared name is left out, which is
201
- * what makes a declaration referenced only from its own site an unreferenced one.
267
+ * what makes a declaration referenced only from its own site an unreferenced one, and so
268
+ * is every node of `unread`, a subtree that is no use.
202
269
  */
203
- function sitesOf(file, declarations, fileId) {
270
+ function sitesOf(file, declarations, fileId, unread) {
204
271
  const found = [];
205
272
  const reads = [];
206
273
  const links = [];
207
274
  const evaluations = [];
208
275
  const decorated = [];
276
+ const literals = [];
277
+ const flows = [];
209
278
  const writes = new Set();
279
+ const alsoRead = new Set();
210
280
  const declared = new Set();
211
281
  const shorthands = new Map();
212
282
  let enclosing = fileId;
@@ -228,19 +298,30 @@ function sitesOf(file, declarations, fileId) {
228
298
  }
229
299
  };
230
300
  const visit = (node) => {
301
+ if (unread.has(node)) {
302
+ return;
303
+ }
231
304
  if (isName(node)) {
232
305
  if (!declared.has(node.pos)) {
233
- found.push({
234
- node,
235
- shorthand: shorthands.get(node.pos),
236
- from: enclosing,
237
- use: writes.has(node.pos) ? "write" : "read",
238
- });
306
+ const site = { node, shorthand: shorthands.get(node.pos), from: enclosing };
307
+ if (writes.has(node.pos)) {
308
+ found.push({ ...site, use: "write" });
309
+ }
310
+ if (!writes.has(node.pos) || alsoRead.has(node.pos)) {
311
+ found.push({ ...site, use: "read" });
312
+ }
239
313
  }
240
314
  return;
241
315
  }
242
316
  markNames(node);
243
- markStores(node, writes);
317
+ markStores(node, writes, alsoRead);
318
+ if (isObjectLiteralExpression(node) && !isAssignmentPattern(node)) {
319
+ literals.push({ literal: node, from: enclosing });
320
+ }
321
+ // An assertion needs only comparability, so no member of its operand is read by it.
322
+ for (const written of isAsExpression(node) ? [] : handedOn(node)) {
323
+ flows.push(...valuesOf(written).map((value) => ({ value, from: enclosing })));
324
+ }
244
325
  for (const read of propertyReadsOf(node)) {
245
326
  reads.push({ ...read, from: enclosing });
246
327
  }
@@ -263,8 +344,18 @@ function sitesOf(file, declarations, fileId) {
263
344
  enclosing = outer;
264
345
  };
265
346
  file.forEachChild(visit);
266
- return { uses: found, reads, links, evaluations, decorated };
347
+ return { uses: found, literals, flows, reads, links, evaluations, decorated };
267
348
  }
349
+ /** The declaration kinds of a property an object literal declares. */
350
+ const LITERAL_PROPERTY_KINDS = new Set([
351
+ SyntaxKind.PropertyAssignment,
352
+ SyntaxKind.ShorthandPropertyAssignment,
353
+ SyntaxKind.MethodDeclaration,
354
+ SyntaxKind.GetAccessor,
355
+ SyntaxKind.SetAccessor,
356
+ ]);
357
+ /** The rule a file the analysis classified as test-support code is counted under. */
358
+ const TEST_SUPPORT_RULE = "test-support";
268
359
  /**
269
360
  * The name one test-file rule takes. A rule's name is a word list the report's own
270
361
  * vocabulary spells, which cannot carry a pattern, so a rule is named by the place of
@@ -277,17 +368,19 @@ function patternRule(index) {
277
368
  * The rules over the test files of several projects at once, ordered by rule: a file two
278
369
  * projects hold is one file, so each rule counts the distinct paths it matches.
279
370
  */
280
- export function testFileRulesOf(testFiles, paths) {
371
+ export function testFileRulesOf(testFiles, paths, supportFiles = []) {
281
372
  const distinct = [...new Set(paths)];
282
- return testFiles
283
- .map((pattern, index) => {
284
- const expression = globExpression(pattern);
285
- return {
286
- rule: patternRule(index),
287
- matched: distinct.filter((path) => expression.test(path)).length,
288
- };
289
- })
290
- .sort((a, b) => compare(a.rule, b.rule));
373
+ const support = new Set(supportFiles).size;
374
+ return [
375
+ ...testFiles.map((pattern, index) => {
376
+ const expression = globExpression(pattern);
377
+ return {
378
+ rule: patternRule(index),
379
+ matched: distinct.filter((path) => expression.test(path)).length,
380
+ };
381
+ }),
382
+ ...(support === 0 ? [] : [{ rule: TEST_SUPPORT_RULE, matched: support }]),
383
+ ].sort((a, b) => compare(a.rule, b.rule));
291
384
  }
292
385
  const importedCache = new WeakMap();
293
386
  /**
@@ -332,6 +425,90 @@ function importedNames(file) {
332
425
  importedCache.set(file, names);
333
426
  return names;
334
427
  }
428
+ /**
429
+ * The name node of every import binding one file's top level declares, and whether it
430
+ * binds a whole module.
431
+ */
432
+ function importBindings(file) {
433
+ const found = [];
434
+ for (const statement of file.statements) {
435
+ if (isImportEqualsDeclaration(statement)) {
436
+ found.push({ node: statement.name, whole: true });
437
+ continue;
438
+ }
439
+ const clause = isImportDeclaration(statement) ? statement.importClause : undefined;
440
+ if (clause === undefined) {
441
+ continue;
442
+ }
443
+ if (clause.name !== undefined) {
444
+ found.push({ node: clause.name, whole: false });
445
+ }
446
+ const bindings = clause.namedBindings;
447
+ if (bindings === undefined) {
448
+ continue;
449
+ }
450
+ if (bindings.kind === SyntaxKind.NamespaceImport) {
451
+ found.push({ node: bindings.name, whole: true });
452
+ }
453
+ else {
454
+ found.push(...bindings.elements.map((element) => ({ node: element.name, whole: false })));
455
+ }
456
+ }
457
+ return found;
458
+ }
459
+ /** The value one assignment stores through the name it writes, where it is a plain assignment. */
460
+ function storedValue(name) {
461
+ let target = name;
462
+ if (isPropertyAccessExpression(name.parent) && name.parent.name === name) {
463
+ target = name.parent;
464
+ }
465
+ while (isParenthesizedExpression(target.parent)) {
466
+ target = target.parent;
467
+ }
468
+ const parent = target.parent;
469
+ return isBinaryExpression(parent) &&
470
+ parent.operatorToken.kind === SyntaxKind.EqualsToken &&
471
+ parent.left === target
472
+ ? parent.right
473
+ : undefined;
474
+ }
475
+ /**
476
+ * The import bindings of one file that only the values written into one declaration
477
+ * use, for each write into it. A binding is named by an identifier that spells its
478
+ * local name and is not the member a property access names.
479
+ */
480
+ function aloneIn(file, uses, writes, found) {
481
+ const bindings = new Set(importBindings(file).map((binding) => binding.node.getText()));
482
+ if (bindings.size === 0) {
483
+ return [];
484
+ }
485
+ const occurrences = new Map();
486
+ for (const site of uses) {
487
+ const text = site.node.getText();
488
+ const parent = site.node.parent;
489
+ if (!bindings.has(text) || (isPropertyAccessExpression(parent) && parent.name === site.node)) {
490
+ continue;
491
+ }
492
+ if (!occurrences.get(text)?.includes(site.node)) {
493
+ occurrences.set(text, [...(occurrences.get(text) ?? []), site.node]);
494
+ }
495
+ }
496
+ const inside = (node, value) => node.pos >= value.pos && node.end <= value.end;
497
+ const result = [];
498
+ for (const write of writes) {
499
+ const to = found[write.index]?.to;
500
+ const sameTarget = writes.filter((one) => found[one.index]?.to === to);
501
+ const names = [...occurrences]
502
+ .filter(([, nodes]) => nodes.some((node) => inside(node, write.value)) &&
503
+ nodes.every((node) => sameTarget.some((one) => inside(node, one.value))))
504
+ .map(([name]) => name)
505
+ .sort(compare);
506
+ if (names.length > 0) {
507
+ result.push({ index: write.index, names });
508
+ }
509
+ }
510
+ return result;
511
+ }
335
512
  /** Two strings ordered bytewise, which is the order every set of a run is read in. */
336
513
  function compare(a, b) {
337
514
  if (a === b) {
@@ -362,7 +539,8 @@ export function references(project, held, targetRoot, options) {
362
539
  const found = [];
363
540
  const tests = [];
364
541
  const chains = aliasChains(project, held);
365
- const destructured = destructuring(project, cap);
542
+ const tables = propertyTables(project.queries);
543
+ const destructured = destructuring(project, cap, tables);
366
544
  let batched = 0;
367
545
  let fileBatches = 0;
368
546
  let residueFallbacks = 0;
@@ -375,7 +553,22 @@ export function references(project, held, targetRoot, options) {
375
553
  .ownSourceFiles()
376
554
  .filter((file) => relativePath(consumer.root, file.fileName) !== undefined &&
377
555
  relativePath(targetRoot, file.fileName) === undefined);
556
+ const ownPaths = project.ownPaths();
378
557
  const fileIds = new Set(held.symbols.filter((symbol) => symbol.kind === "file").map((symbol) => symbol.id));
558
+ let topLevel;
559
+ /** The declarations one file's top level holds. */
560
+ const declaredIn = (fileId) => {
561
+ if (topLevel === undefined) {
562
+ const byParent = new Map();
563
+ for (const symbol of held.symbols) {
564
+ if (fileIds.has(symbol.parent)) {
565
+ byParent.set(symbol.parent, [...(byParent.get(symbol.parent) ?? []), symbol]);
566
+ }
567
+ }
568
+ topLevel = byParent;
569
+ }
570
+ return topLevel.get(fileId) ?? [];
571
+ };
379
572
  /** The identifier of the declaration one node of this program is, where it is one. */
380
573
  const declaredAt = (node) => declarations.get(nodeKey(node.getSourceFile(), node));
381
574
  /**
@@ -405,6 +598,45 @@ export function references(project, held, targetRoot, options) {
405
598
  ]),
406
599
  ];
407
600
  };
601
+ // A type-query alias's own type names its export without using it; a use of the
602
+ // global is the use, so it names every link of the export's chain as well.
603
+ const aliases = project.ownSourceFiles().flatMap(typeQueryAliases);
604
+ const unread = new Set(aliases.map((alias) => alias.type));
605
+ const aliased = new Map();
606
+ if (aliases.length > 0) {
607
+ const exported = project.symbolsAt(aliases.map((alias) => project.handle(alias.exported)));
608
+ aliases.forEach((alias, index) => {
609
+ const id = declaredAt(alias.declaration);
610
+ const symbol = exported[index];
611
+ if (id === undefined || symbol === undefined) {
612
+ return;
613
+ }
614
+ aliased.set(id, symbol === UNANSWERED
615
+ ? (declarationsByName(held).get(alias.name) ?? []).map((one) => ({
616
+ id: one,
617
+ stepped: true,
618
+ guessed: true,
619
+ }))
620
+ : chains.chainOf(symbol).map((target) => ({ ...target, stepped: true })));
621
+ });
622
+ }
623
+ /** One use's targets, then every link an alias global among them stands for, each once. */
624
+ const throughAliases = (targets) => {
625
+ if (aliased.size === 0) {
626
+ return targets;
627
+ }
628
+ const all = [...targets];
629
+ const seen = new Set(targets.map((target) => target.id));
630
+ for (const target of targets) {
631
+ for (const forwarded of aliased.get(target.id) ?? []) {
632
+ if (!seen.has(forwarded.id)) {
633
+ seen.add(forwarded.id);
634
+ all.push(forwarded);
635
+ }
636
+ }
637
+ }
638
+ return all;
639
+ };
408
640
  /** The symbol one site resolves to, asking only the accessor the site's form names. */
409
641
  const symbolFor = (site, answered) => {
410
642
  if (site.shorthand !== undefined) {
@@ -418,13 +650,92 @@ export function references(project, held, targetRoot, options) {
418
650
  residueFallbacks += 1;
419
651
  return project.resolvedSymbolAt(project.handle(site.node));
420
652
  };
421
- for (const file of files) {
653
+ const walked = files.map((file) => {
654
+ const fileId = declaredAt(file) ?? "";
655
+ return { file, fileId, ...sitesOf(file, declarations, fileId, unread) };
656
+ });
657
+ const contextual = contextualUses(project, held, chains, walked.flatMap((one) => one.literals), walked.flatMap((one) => one.flows), cap, tables);
658
+ const writtenBy = new Map();
659
+ for (const write of contextual.writes) {
660
+ const property = write.at.parent;
661
+ writtenBy.set(nodeKey(property.getSourceFile(), property), write.ids);
662
+ }
663
+ const forwardedCache = new Map();
664
+ /**
665
+ * The members a literal property one symbol declares writes: a use of the property
666
+ * reaches them, because the literal's type carries the data the member describes.
667
+ */
668
+ const forwarded = (symbol) => {
669
+ const known = forwardedCache.get(symbol.id);
670
+ if (known !== undefined) {
671
+ return known;
672
+ }
673
+ const found = [];
674
+ if (writtenBy.size > 0) {
675
+ for (const handle of symbol.declarations) {
676
+ if (!LITERAL_PROPERTY_KINDS.has(handle.kind) || !ownPaths.has(handle.path)) {
677
+ continue;
678
+ }
679
+ const node = project.declarationAt(handle)?.node;
680
+ const ids = node === undefined ? undefined : writtenBy.get(nodeKey(node.getSourceFile(), node));
681
+ for (const id of ids ?? []) {
682
+ if (!found.some((target) => target.id === id)) {
683
+ found.push({ id, stepped: true, guessed: false });
684
+ }
685
+ }
686
+ }
687
+ }
688
+ forwardedCache.set(symbol.id, found);
689
+ return found;
690
+ };
691
+ /** One resolved symbol's declarations along its chain, and the members its literal properties write. */
692
+ const targetsOf = (symbol) => {
693
+ const through = forwarded(symbol);
694
+ return through.length === 0 ? chains.chainOf(symbol) : [...chains.chainOf(symbol), ...through];
695
+ };
696
+ const testFile = new Map();
697
+ for (const { file } of walked) {
422
698
  const path = renderPosition(file, root, 0).path;
423
699
  const test = patterns.some((pattern) => pattern.test(path));
700
+ testFile.set(file, test);
424
701
  if (test) {
425
702
  tests.push(path);
426
703
  }
427
- const { uses, reads, links, evaluations, decorated } = sitesOf(file, declarations, declaredAt(file) ?? "");
704
+ }
705
+ /** Each write reference's index in `found`, by its file, with the value it stores. */
706
+ const valued = new Map();
707
+ const holdsValue = (file, value) => {
708
+ if (value !== undefined) {
709
+ valued.set(file, [...(valued.get(file) ?? []), { index: found.length - 1, value }]);
710
+ }
711
+ };
712
+ for (const [use, sites] of [
713
+ ["write", contextual.writes],
714
+ ["read", contextual.reads],
715
+ ]) {
716
+ for (const site of sites) {
717
+ const file = site.at.getSourceFile();
718
+ const position = renderPosition(file, root, site.at.getStart());
719
+ for (const id of site.ids) {
720
+ found.push({
721
+ from: site.from,
722
+ to: id,
723
+ position,
724
+ use,
725
+ resolution: "contextual",
726
+ test: testFile.get(file) === true,
727
+ });
728
+ if (use === "write") {
729
+ holdsValue(file, site.at.parent);
730
+ }
731
+ }
732
+ }
733
+ }
734
+ for (const { file, fileId, uses, reads, links, evaluations, decorated } of walked) {
735
+ const test = testFile.get(file) === true;
736
+ // A component file's markup may use any binding its blocks declare at the top level.
737
+ const markup = isComponentFile(file);
738
+ const bindings = markup ? importBindings(file) : [];
428
739
  // A decorator is written inside the declaration it is attached to and receives it,
429
740
  // so the declaration is used where the decorator is written, whatever the
430
741
  // decorator does with it.
@@ -448,6 +759,7 @@ export function references(project, held, targetRoot, options) {
448
759
  ...uses.filter((site) => site.shorthand === undefined).map((site) => site.node),
449
760
  ...links.map((link) => link.node),
450
761
  ...evaluations.map((evaluation) => evaluation.node),
762
+ ...bindings.map((binding) => binding.node),
451
763
  ]),
452
764
  ];
453
765
  const answered = new Map();
@@ -504,6 +816,47 @@ export function references(project, held, targetRoot, options) {
504
816
  }
505
817
  }
506
818
  }
819
+ if (markup) {
820
+ for (const symbol of declaredIn(fileId)) {
821
+ found.push({
822
+ from: fileId,
823
+ to: symbol.id,
824
+ position: symbol.position,
825
+ use: "read",
826
+ resolution: "syntax",
827
+ test,
828
+ });
829
+ }
830
+ for (const binding of bindings) {
831
+ const symbol = answered.get(binding.node);
832
+ if (symbol === undefined) {
833
+ continue;
834
+ }
835
+ const position = renderPosition(file, root, binding.node.getStart());
836
+ const chained = symbol === UNANSWERED
837
+ ? named(binding.node).map((id) => ({ id, stepped: false, guessed: true }))
838
+ : chains.chainOf(symbol);
839
+ // The markup may name any export of a module a binding holds whole.
840
+ const targets = binding.whole
841
+ ? chained.flatMap((target) => [
842
+ target,
843
+ ...declaredIn(target.id)
844
+ .filter((one) => one.exported)
845
+ .map((one) => ({ ...target, id: one.id })),
846
+ ])
847
+ : chained;
848
+ for (const target of targets) {
849
+ found.push({
850
+ from: fileId,
851
+ to: target.id,
852
+ position,
853
+ use: "read",
854
+ resolution: target.guessed ? "by-name" : target.stepped ? "alias" : "batch",
855
+ test,
856
+ });
857
+ }
858
+ }
859
+ }
507
860
  // A pattern names a property of the value it destructures, which no name node of
508
861
  // the pattern resolves to: a shorthand's name is the local it binds.
509
862
  destructured.resolve(reads).forEach((symbol, index) => {
@@ -518,7 +871,7 @@ export function references(project, held, targetRoot, options) {
518
871
  stepped: false,
519
872
  guessed: true,
520
873
  }))
521
- : chains.chainOf(symbol);
874
+ : targetsOf(symbol);
522
875
  for (const target of targets) {
523
876
  found.push({
524
877
  from: read.from,
@@ -543,9 +896,9 @@ export function references(project, held, targetRoot, options) {
543
896
  const position = renderPosition(file, root, site.node.getStart());
544
897
  // A re-export something imports is used and so is the declaration behind it, so
545
898
  // the use names every link of the chain its name stands at the head of.
546
- const targets = symbol === UNANSWERED
899
+ const targets = throughAliases(symbol === UNANSWERED
547
900
  ? named(site.node).map((id) => ({ id, stepped: false, guessed: true }))
548
- : chains.chainOf(symbol);
901
+ : targetsOf(symbol));
549
902
  for (const target of targets) {
550
903
  found.push({
551
904
  from: site.from,
@@ -555,6 +908,19 @@ export function references(project, held, targetRoot, options) {
555
908
  resolution: target.guessed ? "by-name" : target.stepped ? "alias" : direct,
556
909
  test,
557
910
  });
911
+ if (site.use === "write") {
912
+ holdsValue(file, storedValue(site.node));
913
+ }
914
+ }
915
+ }
916
+ }
917
+ const usesOf = new Map(walked.map((one) => [one.file, one.uses]));
918
+ for (const [file, writes] of valued) {
919
+ const uses = usesOf.get(file) ?? [];
920
+ for (const { index, names } of aloneIn(file, uses, writes, found)) {
921
+ const reference = found[index];
922
+ if (reference !== undefined) {
923
+ found[index] = { ...reference, alone: names };
558
924
  }
559
925
  }
560
926
  }
@@ -576,6 +942,8 @@ export function references(project, held, targetRoot, options) {
576
942
  aliasSteps: chains.steps,
577
943
  patternBatches: destructured.cost.patternBatches,
578
944
  patternLookups: destructured.cost.patternLookups,
945
+ contextualBatches: contextual.batches,
946
+ contextualLookups: contextual.lookups,
579
947
  },
580
948
  };
581
949
  }
@@ -4,12 +4,9 @@
4
4
  * renders this document, so no two renderings disagree about which findings exist.
5
5
  */
6
6
  /** The version of the report schema this analyzer writes a report to. */
7
- const SCHEMA_VERSION = "6.1.0";
8
- /**
9
- * Every report schema version this analyzer reads: the schema versions the Contract it
10
- * implements admits, each an instance of the one it writes.
11
- */
12
- export const SCHEMA_VERSIONS_ACCEPTED = ["6.0.0", SCHEMA_VERSION];
7
+ const SCHEMA_VERSION = "7.0.0";
8
+ /** Every report schema version this analyzer reads: the one the Contract it implements admits. */
9
+ export const SCHEMA_VERSIONS_ACCEPTED = [SCHEMA_VERSION];
13
10
  /** The name this analyzer writes every document under and mints every identifier with. */
14
11
  export const ANALYZER_NAME = "deadset-ts";
15
12
  /** The language this analyzer claims. */
@@ -234,6 +231,29 @@ export function buildReport(input) {
234
231
  test_file_rules: [...input.testFileRules]
235
232
  .sort((a, b) => compare(a.rule, b.rule) || a.matched - b.matched)
236
233
  .map((one) => ({ rule: one.rule, matched: one.matched })),
234
+ type_error_skips: [
235
+ ...new Map(input.typeErrorSkips.map((one) => {
236
+ const skip = { path: one.path, line: one.line, message: one.message };
237
+ return [JSON.stringify(skip), skip];
238
+ })),
239
+ ]
240
+ .sort(([a, x], [b, y]) => compare(x.path, y.path) || x.line - y.line || compare(a, b))
241
+ .map(([, skip]) => skip),
242
+ notes: [],
243
+ unanswered_questions: [...input.unanswered]
244
+ .sort((a, b) => compare(a.configuration, b.configuration) ||
245
+ compare(JSON.stringify(a), JSON.stringify(b)))
246
+ .map((one) => ({
247
+ configuration: one.configuration,
248
+ questions: one.questions,
249
+ declarations: one.declarations,
250
+ })),
251
+ conventions_applied: input.conventionsApplied.map((one) => ({
252
+ name: one.name,
253
+ package: one.package,
254
+ version: one.version,
255
+ manifest: one.manifest,
256
+ })),
237
257
  totals: totalsOf(findings, input.result),
238
258
  };
239
259
  }
@@ -22,11 +22,15 @@ function kindName(code) {
22
22
  function command(level, at, endLine, title, message) {
23
23
  return `::${level} file=${escapeProperty(at.path)},line=${String(at.line)},col=${String(at.column)},endLine=${String(endLine)},title=${escapeProperty(title)}::${escapeData(message)}`;
24
24
  }
25
+ /** The title and the message prefix of a type-error skip's annotation. */
26
+ const SKIP_TITLE = "type error skipped";
27
+ const SKIP_MESSAGE = "the analysis did not evaluate the function or statement holding this type error: ";
25
28
  /**
26
29
  * One workflow annotation per finding, then one per stale suppression, each naming its
27
- * file, line, column and last line, titled by its code and kind. A finding at or above the
28
- * failing severity is an error and one below it a warning; a stale suppression is an error,
29
- * because its kind is fixed on at the failing severity.
30
+ * file, line, column and last line, titled by its code and kind, then one warning per
31
+ * type-error skip naming its file and line. A finding at or above the failing severity is
32
+ * an error and one below it a warning; a stale suppression is an error, because its kind
33
+ * is fixed on at the failing severity.
30
34
  */
31
35
  export function annotations(report, options) {
32
36
  return coalesced((function* lines() {
@@ -36,5 +40,8 @@ export function annotations(report, options) {
36
40
  for (const stale of report.stale_suppressions) {
37
41
  yield `${command("error", stale.position, stale.position.line, `${stale.code} ${kindName(stale.code)}`, stale.message)}\n`;
38
42
  }
43
+ for (const skip of report.type_error_skips) {
44
+ yield `::warning file=${escapeProperty(skip.path)},line=${String(skip.line)},title=${escapeProperty(SKIP_TITLE)}::${escapeData(SKIP_MESSAGE + skip.message)}\n`;
45
+ }
39
46
  })());
40
47
  }