@cplieger/deadset-ts 3.12.1 → 4.0.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 (120) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/node-host.js +10 -7
  3. package/dist/src/alias-chain.js +59 -7
  4. package/dist/src/analysis.js +217 -48
  5. package/dist/src/calls.js +23 -22
  6. package/dist/src/class-members.js +10 -11
  7. package/dist/src/components.js +4 -2
  8. package/dist/src/configured-declarations.js +56 -7
  9. package/dist/src/conformance-record.js +2 -2
  10. package/dist/src/dependencies.js +34 -17
  11. package/dist/src/deprecation.js +35 -24
  12. package/dist/src/destructuring.js +37 -29
  13. package/dist/src/discover.js +105 -28
  14. package/dist/src/edges.js +4 -1
  15. package/dist/src/entry-points.js +7 -0
  16. package/dist/src/enum-group.js +46 -40
  17. package/dist/src/exempt.js +42 -1
  18. package/dist/src/findings/completion.js +37 -8
  19. package/dist/src/findings/emitters.js +20 -6
  20. package/dist/src/findings/interfaces.js +7 -1
  21. package/dist/src/findings/intra-function.js +91 -2
  22. package/dist/src/findings/self-check.js +30 -7
  23. package/dist/src/findings/unused-declarations.js +15 -3
  24. package/dist/src/findings-pass.js +18 -11
  25. package/dist/src/framework-lifecycle.js +8 -6
  26. package/dist/src/implementations.js +45 -23
  27. package/dist/src/interface-satisfaction.js +30 -27
  28. package/dist/src/intra-function-parts.js +660 -0
  29. package/dist/src/inventory.js +85 -4
  30. package/dist/src/json-chunks.js +76 -0
  31. package/dist/src/kinds.js +337 -35
  32. package/dist/src/partial.js +23 -0
  33. package/dist/src/query.js +207 -0
  34. package/dist/src/references.js +92 -20
  35. package/dist/src/reflective-lookup.js +46 -4
  36. package/dist/src/report.js +14 -3
  37. package/dist/src/reporters/annotations.js +9 -5
  38. package/dist/src/reporters/json.js +3 -2
  39. package/dist/src/reporters/sarif.js +22 -16
  40. package/dist/src/reporters/template.js +112 -12
  41. package/dist/src/reporters/text.js +8 -6
  42. package/dist/src/roots.js +27 -11
  43. package/dist/src/serialization-contract.js +53 -32
  44. package/dist/src/session.js +79 -39
  45. package/dist/src/signatures.js +32 -0
  46. package/dist/src/suppress-file.js +29 -7
  47. package/dist/src/suppress-inline.js +8 -1
  48. package/dist/src/suppress.js +27 -0
  49. package/dist/src/sweep.js +4 -1
  50. package/dist/src/value-reach.js +44 -25
  51. package/dist/src/verbs/analyze.js +39 -14
  52. package/dist/src/verbs/describe.js +12 -8
  53. package/dist/src/verbs/explain.js +22 -9
  54. package/dist/src/verbs/print-projects.js +40 -7
  55. package/dist/src/verbs/print-retained.js +6 -2
  56. package/dist/src/verbs/print-roots.js +5 -1
  57. package/dist/src/version.js +1 -1
  58. package/jsr.json +1 -1
  59. package/package.json +3 -3
  60. package/src/alias-chain.ts +75 -11
  61. package/src/analysis.ts +385 -104
  62. package/src/calls.ts +10 -9
  63. package/src/class-members.ts +10 -11
  64. package/src/components.ts +4 -2
  65. package/src/configured-declarations.ts +80 -8
  66. package/src/conformance-record.ts +2 -2
  67. package/src/dependencies.ts +42 -20
  68. package/src/deprecation.ts +53 -27
  69. package/src/destructuring.ts +50 -39
  70. package/src/discover.ts +177 -33
  71. package/src/edges.ts +4 -1
  72. package/src/entry-points.ts +7 -0
  73. package/src/enum-group.ts +56 -42
  74. package/src/exempt.ts +50 -2
  75. package/src/finding.ts +5 -1
  76. package/src/findings/completion.ts +46 -10
  77. package/src/findings/emitter.ts +3 -0
  78. package/src/findings/emitters.ts +27 -6
  79. package/src/findings/interfaces.ts +7 -1
  80. package/src/findings/intra-function.ts +106 -3
  81. package/src/findings/self-check.ts +48 -7
  82. package/src/findings/unused-declarations.ts +18 -4
  83. package/src/findings-pass.ts +26 -11
  84. package/src/framework-lifecycle.ts +10 -6
  85. package/src/host.ts +5 -3
  86. package/src/implementations.ts +59 -23
  87. package/src/interface-satisfaction.ts +30 -27
  88. package/src/intra-function-parts.ts +889 -0
  89. package/src/inventory.ts +97 -4
  90. package/src/json-chunks.ts +87 -0
  91. package/src/kinds.ts +355 -41
  92. package/src/partial.ts +40 -0
  93. package/src/query.ts +522 -0
  94. package/src/references.ts +119 -38
  95. package/src/reflective-lookup.ts +56 -6
  96. package/src/report.ts +32 -4
  97. package/src/reporters/annotations.ts +23 -21
  98. package/src/reporters/json.ts +3 -2
  99. package/src/reporters/reporter.ts +2 -2
  100. package/src/reporters/sarif.ts +27 -18
  101. package/src/reporters/template.ts +119 -14
  102. package/src/reporters/text.ts +12 -12
  103. package/src/roots.ts +34 -12
  104. package/src/serialization-contract.ts +59 -40
  105. package/src/session.ts +146 -58
  106. package/src/signatures.ts +55 -0
  107. package/src/suppress-file.ts +40 -8
  108. package/src/suppress-inline.ts +8 -1
  109. package/src/suppress.ts +31 -0
  110. package/src/sweep.ts +9 -4
  111. package/src/value-reach.ts +66 -28
  112. package/src/verbs/analyze.ts +51 -14
  113. package/src/verbs/describe.ts +13 -9
  114. package/src/verbs/explain.ts +20 -9
  115. package/src/verbs/print-projects.ts +53 -7
  116. package/src/verbs/print-retained.ts +6 -2
  117. package/src/verbs/print-roots.ts +5 -1
  118. package/src/version.ts +1 -1
  119. package/dist/src/reporters/rules.js +0 -178
  120. package/src/reporters/rules.ts +0 -195
package/src/inventory.ts CHANGED
@@ -33,6 +33,7 @@ import type { Host } from "./host.ts";
33
33
  import { dirnamePath, joinPath, relativePath } from "./paths.ts";
34
34
  import { byPosition, positionKey, renderPosition, type Position } from "./position.ts";
35
35
  import { computedComponent, nameComponent, renderRef, type Component, type Module } from "./ref.ts";
36
+ import { isAnswered, UNANSWERED } from "./query.ts";
36
37
  import type { Handle, ProjectView } from "./session.ts";
37
38
 
38
39
  /**
@@ -160,6 +161,17 @@ export interface Inventory {
160
161
  * that the omission is a number rather than a silence.
161
162
  */
162
163
  readonly outsideOwnFiles: number;
164
+ /**
165
+ * The declarations whose module, namespace or container table the checker did not
166
+ * answer, in position order: whether each is exported, and which members a table
167
+ * would have added, are unknown.
168
+ */
169
+ readonly unanswered: readonly string[];
170
+ /**
171
+ * Every declaration other than a file by each name a use may spell it with: its
172
+ * component, its declared name where that differs, and a type parameter's own name.
173
+ */
174
+ readonly byName: ReadonlyMap<string, readonly string[]>;
163
175
  }
164
176
 
165
177
  /** A declaration under construction, before its reference is rendered. */
@@ -758,12 +770,28 @@ export function inventory<Brand>(
758
770
  const handles: Handle<Brand>[] = batched.map((node) => project.handle(node));
759
771
  const resolved = project.symbolsAt(handles);
760
772
  const symbolOf = new Map<Node, TSSymbol>();
773
+ // A scope or a container whose symbol or table went unanswered keeps what its syntax
774
+ // declares, and everything under it is recorded unanswered.
775
+ const unansweredOwners = new Set<Building>();
761
776
  batched.forEach((node, index) => {
762
777
  const symbol = resolved[index];
763
- if (symbol !== undefined) {
778
+ if (typeof symbol === "object") {
764
779
  symbolOf.set(node, symbol);
765
780
  }
766
781
  });
782
+ resolved.slice(0, exportScopes.length).forEach((symbol, index) => {
783
+ const scope = exportScopes[index];
784
+ if (symbol === UNANSWERED && scope !== undefined) {
785
+ unansweredOwners.add(scope.owner);
786
+ }
787
+ });
788
+ containers
789
+ .filter((container) => container.nameNode !== undefined)
790
+ .forEach((container, index) => {
791
+ if (resolved[exportScopes.length + index] === UNANSWERED) {
792
+ unansweredOwners.add(container.owner);
793
+ }
794
+ });
767
795
 
768
796
  let exportTables = 0;
769
797
  for (const scope of exportScopes) {
@@ -772,7 +800,11 @@ export function inventory<Brand>(
772
800
  continue;
773
801
  }
774
802
  exportTables += 1;
775
- for (const [, exported] of symbol.getExports()) {
803
+ const table = project.queries.exportsOf(symbol);
804
+ if (!isAnswered(table)) {
805
+ unansweredOwners.add(scope.owner);
806
+ }
807
+ for (const [, exported] of isAnswered(table) ? table : []) {
776
808
  for (const declaration of exported.declarations) {
777
809
  const held = project.declarationAt(declaration);
778
810
  if (held === undefined) {
@@ -794,11 +826,21 @@ export function inventory<Brand>(
794
826
  if (symbol !== undefined) {
795
827
  if (!container.membersAreExports) {
796
828
  memberTables += 1;
797
- outsideOwnFiles += readTable(container, symbol.getMembers(), seen, false);
829
+ const table = project.queries.membersOf(symbol);
830
+ if (isAnswered(table)) {
831
+ outsideOwnFiles += readTable(container, table, seen, false);
832
+ } else {
833
+ unansweredOwners.add(container.owner);
834
+ }
798
835
  }
799
836
  if (container.membersAreExports || container.isClass) {
800
837
  memberTables += 1;
801
- outsideOwnFiles += readTable(container, symbol.getExports(), seen, container.isClass);
838
+ const table = project.queries.exportsOf(symbol);
839
+ if (isAnswered(table)) {
840
+ outsideOwnFiles += readTable(container, table, seen, container.isClass);
841
+ } else {
842
+ unansweredOwners.add(container.owner);
843
+ }
802
844
  }
803
845
  }
804
846
  completeFromTree(container, seen);
@@ -919,6 +961,7 @@ export function inventory<Brand>(
919
961
  return undefined;
920
962
  }
921
963
 
964
+ const byId = new Map(building.map((record) => [positionKey(record.position), record]));
922
965
  const symbols = building
923
966
  .map(render)
924
967
  .sort((a, b) => byPosition(a.position, b.position) || (a.ref < b.ref ? -1 : 1));
@@ -932,9 +975,50 @@ export function inventory<Brand>(
932
975
  cost: { batches: 1, exportTables, memberTables },
933
976
  starReExports,
934
977
  outsideOwnFiles,
978
+ unanswered: symbols
979
+ .filter((symbol) => underUnanswered(byId.get(symbol.id), unansweredOwners))
980
+ .map((symbol) => symbol.id),
981
+ byName: namesOf(building),
935
982
  };
936
983
  }
937
984
 
985
+ /** Whether one declaration is, or is declared under, a scope or container left unanswered. */
986
+ function underUnanswered(record: Building | undefined, owners: ReadonlySet<Building>): boolean {
987
+ for (let at = record; at !== undefined; at = at.parent) {
988
+ if (owners.has(at)) {
989
+ return true;
990
+ }
991
+ }
992
+ return false;
993
+ }
994
+
995
+ /** Every declaration but a file, by each name a use may spell it with. */
996
+ function namesOf(building: readonly Building[]): ReadonlyMap<string, readonly string[]> {
997
+ const byName = new Map<string, string[]>();
998
+ for (const record of building) {
999
+ if (record.kind === "file") {
1000
+ continue;
1001
+ }
1002
+ const id = positionKey(record.position);
1003
+ const names =
1004
+ record.typeParameter === undefined
1005
+ ? [record.component.text, record.localName]
1006
+ : [record.typeParameter];
1007
+ for (const name of new Set(names)) {
1008
+ if (name === undefined) {
1009
+ continue;
1010
+ }
1011
+ const ids = byName.get(name);
1012
+ if (ids === undefined) {
1013
+ byName.set(name, [id]);
1014
+ } else if (!ids.includes(id)) {
1015
+ ids.push(id);
1016
+ }
1017
+ }
1018
+ }
1019
+ return byName;
1020
+ }
1021
+
938
1022
  /** The chain of components that names one declaration, read from the module inward. */
939
1023
  function chainOf(record: Building): readonly Component[] {
940
1024
  return [...record.parentChain, record.component];
@@ -984,3 +1068,12 @@ function refOf(record: Building, chain: readonly Component[]): string {
984
1068
  ...(record.typeParameter === undefined ? {} : { typeParameter: record.typeParameter }),
985
1069
  });
986
1070
  }
1071
+
1072
+ /**
1073
+ * Every declaration of an inventory other than a file, by each name a use may spell it
1074
+ * with. It is how a reference the checker could not resolve is still counted, as a
1075
+ * reference to every declaration that bears the name it spells.
1076
+ */
1077
+ export function declarationsByName(held: Inventory): ReadonlyMap<string, readonly string[]> {
1078
+ return held.byName;
1079
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * A document written in pieces, so no stage of writing it holds the whole document as
3
+ * one string: a string has a length limit far below what a large report reaches.
4
+ */
5
+
6
+ /** The length a piece grows to before it is handed on. */
7
+ export const CHUNK_LENGTH = 1 << 16;
8
+
9
+ /**
10
+ * The pieces of `pieces` joined into runs of about {@link CHUNK_LENGTH}, the last one
11
+ * shorter. A piece longer than that is handed on whole.
12
+ */
13
+ export function* coalesced(pieces: Iterable<string>): Generator<string, void, undefined> {
14
+ let held: string[] = [];
15
+ let length = 0;
16
+ for (const piece of pieces) {
17
+ held.push(piece);
18
+ length += piece.length;
19
+ if (length >= CHUNK_LENGTH) {
20
+ yield held.join("");
21
+ held = [];
22
+ length = 0;
23
+ }
24
+ }
25
+ if (length > 0) {
26
+ yield held.join("");
27
+ }
28
+ }
29
+
30
+ /** Whether `JSON.stringify` writes nothing for a value where an object member holds it. */
31
+ function omitted(value: unknown): boolean {
32
+ return value === undefined || typeof value === "function" || typeof value === "symbol";
33
+ }
34
+
35
+ function* valueOf(
36
+ value: unknown,
37
+ prefix: string,
38
+ step: string,
39
+ ): Generator<string, void, undefined> {
40
+ // With no indentation the document is compact: no line breaks, and no space after a key.
41
+ const nl = step === "" ? "" : "\n";
42
+ if (Array.isArray(value)) {
43
+ if (value.length === 0) {
44
+ yield "[]";
45
+ return;
46
+ }
47
+ const inner = prefix + step;
48
+ yield "[";
49
+ for (let at = 0; at < value.length; at += 1) {
50
+ yield at === 0 ? `${nl}${inner}` : `,${nl}${inner}`;
51
+ const element: unknown = value[at];
52
+ yield* omitted(element) ? ["null"] : valueOf(element, inner, step);
53
+ }
54
+ yield `${nl}${prefix}]`;
55
+ return;
56
+ }
57
+ if (typeof value === "object" && value !== null) {
58
+ const inner = prefix + step;
59
+ let first = true;
60
+ for (const [key, member] of Object.entries(value)) {
61
+ if (omitted(member)) {
62
+ continue;
63
+ }
64
+ yield `${first ? "{" : ","}${nl}${inner}${JSON.stringify(key)}:${step === "" ? "" : " "}`;
65
+ first = false;
66
+ yield* valueOf(member, inner, step);
67
+ }
68
+ yield first ? "{}" : `${nl}${prefix}}`;
69
+ return;
70
+ }
71
+ yield JSON.stringify(value);
72
+ }
73
+
74
+ /**
75
+ * One JSON document in pieces whose concatenation is the value as `JSON.stringify`
76
+ * writes it at `indent` spaces, followed by a newline. The value is plain data: objects,
77
+ * arrays, strings, finite numbers, booleans and null, an object member holding nothing
78
+ * being left out as `JSON.stringify` leaves it out.
79
+ */
80
+ export function jsonChunks(value: unknown, indent: number): Generator<string, void, undefined> {
81
+ return coalesced(
82
+ (function* document(): Generator<string, void, undefined> {
83
+ yield* valueOf(value, "", " ".repeat(indent));
84
+ yield "\n";
85
+ })(),
86
+ );
87
+ }