@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10

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 (288) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
package/dist/src/edges.js CHANGED
@@ -4,153 +4,69 @@
4
4
  * never resolves the paired symbol: the other language's analyzer decides whether the
5
5
  * pairing is a use, and the merge reads the two evaluations together.
6
6
  */
7
- import { ConfigError } from "./config.js";
8
- import { positionAt, walkDocument } from "./json-document.js";
9
- import { joinPath } from "./paths.js";
7
+ import { EDGES_SCHEMA } from "./contract/config.generated.js";
8
+ import { readTargetDocument } from "./fs/document.js";
9
+ import { dotted, placeIn, positionAt, readStrict, StrictJSONError } from "./json-document.js";
10
10
  import { positionKey } from "./position.js";
11
+ import { firstRefusal } from "./schema.js";
11
12
  import { isPartSubject } from "./suppress.js";
13
+ import { bytewise } from "./text/compare.js";
14
+ import { UsageError } from "./usage.js";
12
15
  /** The name and the location the Contract fixes for the edges document. */
13
16
  export const EDGES_FILE = "deadset-edges.json";
14
17
  /** The language prefix of every reference this analyzer evaluates. */
15
18
  const OWN_PREFIX = "ts://";
16
- /** The closed key list of the document and of one edge. */
17
- const DOCUMENT_KEYS = new Set(["description", "edges"]);
18
- const EDGE_KEYS = new Set(["id", "because", "provides", "used_by"]);
19
- /** An edge identifier: solidus-separated segments of ASCII letters, digits, `_`, `.` and `-`. */
20
- const ID_FORM = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/u;
21
- /** A reference naming one symbol of some language: `<language>://<scope>#<fragment>`. */
22
- const REFERENCE_FORM = /^[a-z][a-z0-9]*:\/\/[^ \t\r\n#]+#[^ \t\r\n]*$/u;
23
- const DOCUMENT_WANT = "an object carrying the edges array, and an optional description";
24
- const EDGE_WANT = "an edge naming id, provides and used_by, each a string, and an optional because";
25
- const ID_WANT = "one or more solidus-separated segments of ASCII letters, digits, underscore, full stop and hyphen";
26
- const REFERENCE_WANT = "a stable symbol reference naming one symbol exactly, <language>://<scope>#<fragment>";
27
19
  /** An edges document the Contract refuses: a malformed input, answered with the usage code. */
28
- class EdgesError extends ConfigError {
29
- constructor(at, text, want) {
30
- super("malformed", EDGES_FILE, `${at}: ${text}: want ${want}`);
20
+ class EdgesError extends UsageError {
21
+ constructor(at, reason) {
22
+ super(`${at}: ${reason}`);
31
23
  this.name = "EdgesError";
32
24
  }
33
25
  }
34
- /**
35
- * Whether a reference carries a wildcard outside a quoted or a computed component, which
36
- * would name a set of symbols rather than one.
37
- */
38
- function carriesWildcard(ref) {
39
- let quoted = false;
40
- let computed = false;
41
- let escaped = false;
42
- for (const char of ref) {
43
- if (escaped) {
44
- escaped = false;
45
- }
46
- else if (quoted && char === "\\") {
47
- escaped = true;
48
- }
49
- else if (quoted) {
50
- quoted = char !== "'";
51
- }
52
- else if (computed) {
53
- computed = char !== "]";
54
- }
55
- else if (char === "'") {
56
- quoted = true;
57
- }
58
- else if (char === "[") {
59
- computed = true;
60
- }
61
- else if (char === "*") {
62
- return true;
63
- }
64
- }
65
- return false;
66
- }
67
- function isObject(value) {
68
- return typeof value === "object" && value !== null && !Array.isArray(value);
69
- }
70
- /** One edge, checked: the closed key list, the value types and the value forms. */
71
- function readEdge(value, at) {
72
- const refuse = (text, want) => {
73
- throw new EdgesError(at, text, want);
74
- };
75
- if (!isObject(value)) {
76
- return refuse(JSON.stringify(value), EDGE_WANT);
77
- }
78
- for (const [key, held] of Object.entries(value)) {
79
- if (!EDGE_KEYS.has(key) || typeof held !== "string") {
80
- refuse(`the member ${JSON.stringify(key)} holding ${JSON.stringify(held)}`, EDGE_WANT);
81
- }
82
- }
83
- const edge = value;
84
- const id = edge["id"] ?? refuse("an edge naming no id", EDGE_WANT);
85
- const provides = edge["provides"] ?? refuse("an edge naming no provides side", EDGE_WANT);
86
- const usedBy = edge["used_by"] ?? refuse("an edge naming no used_by side", EDGE_WANT);
87
- if (!ID_FORM.test(id)) {
88
- refuse(JSON.stringify(id), ID_WANT);
89
- }
90
- for (const ref of [provides, usedBy]) {
91
- if (!REFERENCE_FORM.test(ref) || carriesWildcard(ref)) {
92
- refuse(JSON.stringify(ref), REFERENCE_WANT);
93
- }
94
- }
95
- return { id, provides, usedBy };
96
- }
26
+ /** The sides of one edge, in the order a reader yields them. */
27
+ const SIDE_NAMES = ["provides", "used_by"];
97
28
  /**
98
29
  * Every side of a declared edge that names a symbol of this language, in document order,
99
30
  * `provides` before `used_by` within an edge. An absent document declares no edge. The
100
- * document is strict JSON with a closed key list at both levels, and a defect in it is an
101
- * {@link EdgesError}.
31
+ * document is strict JSON held to the Contract's edges schema, and a defect in it is an
32
+ * {@link EdgesError} at the line and column of the edge it is in, where it is in one.
102
33
  */
103
34
  export function readEdgeSides(host, targetRoot) {
104
- const file = joinPath(targetRoot, EDGES_FILE);
105
- if (host.kindOf(file) === "absent") {
35
+ const text = readTargetDocument(host, targetRoot, EDGES_FILE);
36
+ if (text === undefined) {
106
37
  return [];
107
38
  }
108
- const text = host.readFile(file);
109
- let parsed;
39
+ let read;
110
40
  try {
111
- parsed = JSON.parse(text);
41
+ read = readStrict(text, "edges");
112
42
  }
113
43
  catch (error) {
114
- throw new EdgesError(EDGES_FILE, error instanceof Error ? error.message : String(error), DOCUMENT_WANT);
115
- }
116
- if (!isObject(parsed)) {
117
- throw new EdgesError(EDGES_FILE, JSON.stringify(parsed), DOCUMENT_WANT);
118
- }
119
- const walk = walkDocument(text, "edges");
120
- if (walk.twice !== undefined) {
121
- throw new EdgesError(positionKey(positionAt(text, EDGES_FILE, walk.twice.offset)), `the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`, DOCUMENT_WANT);
44
+ if (!(error instanceof StrictJSONError)) {
45
+ throw error;
46
+ }
47
+ throw new EdgesError(positionKey(positionAt(text, EDGES_FILE, error.offset)), error.message);
122
48
  }
123
- const unknown = Object.keys(parsed).find((key) => !DOCUMENT_KEYS.has(key));
124
- const description = parsed["description"];
125
- const edges = parsed["edges"];
126
- if (unknown !== undefined ||
127
- (description !== undefined && typeof description !== "string") ||
128
- !Array.isArray(edges)) {
129
- throw new EdgesError(EDGES_FILE, unknown === undefined ? EDGES_FILE : `the member ${JSON.stringify(unknown)}`, DOCUMENT_WANT);
49
+ const value = read.value;
50
+ const refused = firstRefusal(EDGES_SCHEMA, value);
51
+ if (refused !== undefined) {
52
+ const place = placeIn(value, refused.pointer, refused.member);
53
+ const offset = place.length > 1 ? read.starts.get(place[1] ?? "") : undefined;
54
+ const at = offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
55
+ const where = dotted(place);
56
+ throw new EdgesError(at, where === "" ? refused.reason : `${where}: ${refused.reason}`);
130
57
  }
58
+ const edges = value.edges;
131
59
  const sides = [];
132
- edges.forEach((value, index) => {
133
- const offset = walk.starts[index];
134
- const at = offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
135
- const edge = readEdge(value, at);
136
- const named = [
137
- ["provides", edge.provides],
138
- ["used_by", edge.usedBy],
139
- ];
140
- for (const [side, symbol] of named) {
141
- if (symbol.startsWith(OWN_PREFIX)) {
142
- sides.push({ edge: edge.id, side, symbol });
60
+ for (const id of read.starts.keys()) {
61
+ const edge = String(id);
62
+ for (const side of SIDE_NAMES) {
63
+ const symbol = edges[edge]?.[side];
64
+ if (symbol?.startsWith(OWN_PREFIX) === true) {
65
+ sides.push({ edge, side, symbol });
143
66
  }
144
67
  }
145
- });
146
- return sides;
147
- }
148
- /** Two strings ordered bytewise. */
149
- function compare(a, b) {
150
- if (a === b) {
151
- return 0;
152
68
  }
153
- return a < b ? -1 : 1;
69
+ return sides;
154
70
  }
155
71
  /**
156
72
  * Evaluates every declared side of this language over the run's findings. A side naming a
@@ -191,6 +107,6 @@ export function evaluateEdges(findings, sides, symbols) {
191
107
  return { ...side, state: "dead", finding };
192
108
  });
193
109
  });
194
- evaluations.sort((a, b) => compare(a.edge, b.edge) || compare(a.side, b.side));
110
+ evaluations.sort((a, b) => bytewise(a.edge, b.edge) || bytewise(a.side, b.side));
195
111
  return { findings: findings.filter((_finding, at) => !pending.has(at)), evaluations };
196
112
  }
@@ -8,6 +8,7 @@ import { isArrayLiteralExpression, isArrowFunction, isBinaryExpression, isCallEx
8
8
  import { nodeKey } from "./inventory.js";
9
9
  import { renderPosition } from "./position.js";
10
10
  import { UNANSWERED } from "./query.js";
11
+ import { parenthesesRemoved } from "./unwrap.js";
11
12
  /** The assignment operators that read their target before they write it. */
12
13
  const COMPOUND = new Set([
13
14
  SyntaxKind.PlusEqualsToken,
@@ -47,12 +48,9 @@ function useOf(node) {
47
48
  }
48
49
  return "read";
49
50
  }
50
- function unwrapped(node) {
51
- return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
52
- }
53
51
  /** Whether an index is a literal the checker resolves to a member by its own text. */
54
52
  function isLiteralIndex(node) {
55
- const at = unwrapped(node);
53
+ const at = parenthesesRemoved(node);
56
54
  return isStringLiteral(at) || isNumericLiteral(at) || isNoSubstitutionTemplateLiteral(at);
57
55
  }
58
56
  /**
@@ -195,7 +193,7 @@ export function elementAccessReferences(project, held, chains, root, testFiles)
195
193
  const parameterOf = new Map();
196
194
  const byCallable = new Map();
197
195
  for (const candidate of candidates) {
198
- const index = unwrapped(candidate.access.argumentExpression);
196
+ const index = parenthesesRemoved(candidate.access.argumentExpression);
199
197
  const fn = isIdentifier(index) ? enclosingFunction(candidate.access) : undefined;
200
198
  const callable = fn === undefined ? undefined : callableOf(fn);
201
199
  if (callable === undefined || !isIdentifier(index)) {
@@ -252,7 +250,7 @@ export function elementAccessReferences(project, held, chains, root, testFiles)
252
250
  continue;
253
251
  }
254
252
  // An object that is a parameter too is read as the argument each call passes there.
255
- const object = unwrapped(candidate.access.expression);
253
+ const object = parenthesesRemoved(candidate.access.expression);
256
254
  const objectAt = isIdentifier(object)
257
255
  ? deferred.callable.parameters.findIndex((parameter) => isIdentifier(parameter.name) && parameter.name.text === object.text)
258
256
  : -1;
@@ -7,6 +7,7 @@
7
7
  * Every row carries a reason, because the list is read where the rows are shown
8
8
  * one at a time: beside a finding, or in a report's declared gaps.
9
9
  */
10
+ import { bytewise } from "./text/compare.js";
10
11
  /** What is declined for a tool whose own conventions are entered only through its configuration files. */
11
12
  const EVERY_CONVENTION = "every file it loads by its own convention beyond its configuration files";
12
13
  /** Why a tool whose own conventions are not read declines them. */
@@ -43,7 +44,6 @@ const UNREAD_TOOLS = [
43
44
  "cypress",
44
45
  "danger",
45
46
  "dependency-cruiser",
46
- "docusaurus",
47
47
  "dotenv",
48
48
  "drizzle",
49
49
  "electron-vite",
@@ -97,14 +97,12 @@ const UNREAD_TOOLS = [
97
97
  "nuxtjs-i18n",
98
98
  "nx",
99
99
  "nyc",
100
- "oclif",
101
100
  "openapi-ts",
102
101
  "openclaw",
103
102
  "orval",
104
103
  "oxfmt",
105
104
  "oxlint",
106
105
  "panda-css",
107
- "parcel",
108
106
  "payload",
109
107
  "pino",
110
108
  "playwright-ct",
@@ -149,7 +147,6 @@ const UNREAD_TOOLS = [
149
147
  "swc",
150
148
  "syncpack",
151
149
  "tailwind",
152
- "tanstack-router",
153
150
  "taskfile",
154
151
  "tauri",
155
152
  "temporal",
@@ -193,18 +190,19 @@ const UNREAD_TOOLS = [
193
190
  ];
194
191
  /** The conventions declined for a tool some of whose conventions are entered. */
195
192
  const PARTLY_ENTERED = [
193
+ {
194
+ tool: "docusaurus",
195
+ convention: "a page or theme directory a plugin's configuration moves, and the docs",
196
+ reason: "The convention row reads src/pages and src/theme only, and a document's imports are " +
197
+ "read from every Markdown and MDX file, so a module only a moved directory holds is " +
198
+ "reported as never imported unless ts.entry_files names it.",
199
+ },
196
200
  {
197
201
  tool: "eslint",
198
202
  convention: "a legacy configuration written in YAML or in JSON with comments",
199
203
  reason: "Only a configuration module and a strict JSON configuration file are read, so a " +
200
204
  "dependency only such a file names is reported as unused.",
201
205
  },
202
- {
203
- tool: "eslint",
204
- convention: "a plugin or shareable configuration named without its package's prefix",
205
- reason: "A string uses a dependency only where it spells the dependency's name, so a " +
206
- "dependency only a shortened name loads is reported as unused.",
207
- },
208
206
  {
209
207
  tool: "playwright",
210
208
  convention: "a testMatch written as a regular expression",
@@ -294,15 +292,8 @@ const PARTLY_ENTERED = [
294
292
  reason: NOT_LITERAL,
295
293
  },
296
294
  ];
297
- /** Two strings ordered bytewise. */
298
- function compare(a, b) {
299
- if (a === b) {
300
- return 0;
301
- }
302
- return a < b ? -1 : 1;
303
- }
304
295
  /** Every declined entry-point convention, ordered by tool and then by convention. */
305
296
  export const DECLINED_CONVENTIONS = [
306
297
  ...UNREAD_TOOLS.map((tool) => ({ tool, convention: EVERY_CONVENTION, reason: UNREAD })),
307
298
  ...PARTLY_ENTERED,
308
- ].sort((a, b) => compare(a.tool, b.tool) || compare(a.convention, b.convention));
299
+ ].sort((a, b) => bytewise(a.tool, b.tool) || bytewise(a.convention, b.convention));
@@ -10,14 +10,18 @@
10
10
  * value that is not a literal is not evaluated and not guessed, so a file it would
11
11
  * have named is entered by nothing here.
12
12
  */
13
- import { isArrayLiteralExpression, isAsExpression, isCallExpression, isExportAssignment, isIdentifier, isMetaProperty, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, } from "@typescript/native/ast";
13
+ import { isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isMetaProperty, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
14
14
  import { SymbolFlags } from "@typescript/native/sync";
15
15
  import { configurationFiles, filesNamedBy, moduleStrings, } from "./configuration-files.js";
16
- import { globExpression } from "./glob.js";
16
+ import { buildScriptEntries } from "./build-scripts.js";
17
17
  import { htmlEntries } from "./html-entries.js";
18
+ import { documentEntries } from "./mdx-entries.js";
18
19
  import { dirnamePath, relativePath, resolvePath } from "./paths.js";
19
20
  import { UNANSWERED } from "./query.js";
21
+ import { computedLoadDirectory, leadingLiteral } from "./unseen-loads.js";
20
22
  import { workflowEntries } from "./workflow-steps.js";
23
+ import { unwrapped } from "./unwrap.js";
24
+ import { globExpression } from "./text/glob.js";
21
25
  /** The extensions the test runner and the browser-test runner load a configuration from. */
22
26
  const MODULE_EXTENSIONS = ["js", "mjs", "cjs", "ts", "mts", "cts"];
23
27
  /** The tool file names the configuration form does not cover. */
@@ -37,8 +41,11 @@ const BROWSER_TEST_MATCH = "**/*.{spec,test}.{js,ts,jsx,tsx,cjs,cts,cjsx,ctsx,mj
37
41
  const SETUP_MEMBERS = ["setupFiles", "globalSetup"];
38
42
  /** A member that moves the directory every path of a test-runner configuration is read from. */
39
43
  const ROOT_MEMBER = "root";
40
- /** The prefixes a literal is relative with, in a module specifier and a URL reference alike. */
41
- const RELATIVE_PREFIXES = ["./", "../"];
44
+ /**
45
+ * A URL reference's scheme. A reference with none that does not start with a solidus is
46
+ * relative, the bare name included: https://url.spec.whatwg.org/#relative-url-string
47
+ */
48
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/iu;
42
49
  /** The constructors whose first argument addresses a worker script. */
43
50
  const WORKER_CONSTRUCTORS = new Set(["Worker", "SharedWorker"]);
44
51
  /** The file's name, the last segment of its path. */
@@ -63,14 +70,6 @@ function literalOf(node) {
63
70
  }
64
71
  return undefined;
65
72
  }
66
- /** The expression a wrapper that changes no value holds: parentheses and type assertions. */
67
- function unwrapped(node) {
68
- let at = node;
69
- while (isParenthesizedExpression(at) || isAsExpression(at) || isSatisfiesExpression(at)) {
70
- at = at.expression;
71
- }
72
- return at;
73
- }
74
73
  /**
75
74
  * The object literals one configuration file's default export is written as: the
76
75
  * literal itself, or each literal argument of the call it is, at any depth of calls.
@@ -138,9 +137,16 @@ function literalsOf(value) {
138
137
  }
139
138
  return found;
140
139
  }
141
- /** Whether a literal is relative in the sense a module specifier and a URL reference share. */
140
+ /**
141
+ * Whether a literal is a relative URL reference, which is also how the test runner reads a
142
+ * setup file's path against its root.
143
+ */
142
144
  function isRelative(text) {
143
- return RELATIVE_PREFIXES.some((prefix) => text.startsWith(prefix));
145
+ return text !== "" && !text.startsWith("/") && !URL_SCHEME.test(text);
146
+ }
147
+ /** Whether a module specifier is relative, which a bare name is not: it names a package. */
148
+ function isModuleRelative(text) {
149
+ return text.startsWith("./") || text.startsWith("../");
144
150
  }
145
151
  /** A URL reference with its query and its fragment removed, which name no part of the file. */
146
152
  function pathPart(text) {
@@ -199,7 +205,7 @@ function configurationEntries(host, targetRoot, files, parse, selected) {
199
205
  strings(dirnamePath(document.path), unselectedIn(document.path, document.strings));
200
206
  }
201
207
  for (const { file } of outside) {
202
- strings(dirnamePath(file.fileName), moduleStrings(file).map((one) => one.text));
208
+ strings(dirnamePath(file.fileName), unselected(file));
203
209
  }
204
210
  return found;
205
211
  }
@@ -348,10 +354,10 @@ function workerSitesOf(file) {
348
354
  return found;
349
355
  }
350
356
  /**
351
- * The workers one file addresses. A call's names must resolve to the platform's own
352
- * declarations, which no file of the target declares and no import brings in; a
353
- * name the target binds itself is the target's own constructor or object, which
354
- * addresses no worker this rule knows. One batch per file that writes such a call.
357
+ * The workers one file addresses. A call's names must resolve to a symbol the platform
358
+ * declares, which a file of the target may augment and no import brings in; a name only
359
+ * the target declares is the target's own constructor or object, which addresses no
360
+ * worker this rule knows. One batch per file that writes such a call.
355
361
  */
356
362
  function workersOf(project, file, files) {
357
363
  const sites = workerSitesOf(file);
@@ -371,8 +377,7 @@ function workersOf(project, file, files) {
371
377
  }
372
378
  if (symbol !== undefined &&
373
379
  (symbol.flags & SymbolFlags.Alias) === 0 &&
374
- symbol.declarations.length > 0 &&
375
- symbol.declarations.every((handle) => !files.byName.has(handle.path))) {
380
+ symbol.declarations.some((handle) => !files.byName.has(handle.path))) {
376
381
  platform.add(name);
377
382
  }
378
383
  });
@@ -388,6 +393,73 @@ function workersOf(project, file, files) {
388
393
  }
389
394
  return found;
390
395
  }
396
+ /** Whether one call is `import.meta.glob(...)`. */
397
+ function isMetaGlob(node) {
398
+ if (!isCallExpression(node) || !isPropertyAccessExpression(node.expression)) {
399
+ return false;
400
+ }
401
+ const { expression, name } = node.expression;
402
+ return (isIdentifier(name) &&
403
+ name.text === "glob" &&
404
+ isMetaProperty(expression) &&
405
+ expression.keywordToken === SyntaxKind.ImportKeyword);
406
+ }
407
+ /**
408
+ * The files one file loads by a computed path, each with what exports: every own file
409
+ * below the directory a relative literal start of an `import()` or `require()` argument
410
+ * names, other than the calling file's own directory or one holding it, which would name
411
+ * every sibling, and
412
+ * every own file a relative literal glob of `import.meta.glob` matches, both read against
413
+ * the calling file's directory, as a bundler's context module reads them.
414
+ */
415
+ function computedImportsOf(file, files) {
416
+ const dir = dirnamePath(file.fileName);
417
+ const found = [];
418
+ const below = (prefix) => {
419
+ const target = computedLoadDirectory(dir, prefix);
420
+ if (target === undefined) {
421
+ return;
422
+ }
423
+ for (const candidate of files.byName.values()) {
424
+ if (candidate !== file && relativePath(target, candidate.fileName) !== undefined) {
425
+ found.push({ file: candidate, rule: "computed-import", source: prefix, exports: true });
426
+ }
427
+ }
428
+ };
429
+ const globbed = (pattern) => {
430
+ if (!isModuleRelative(pattern)) {
431
+ return;
432
+ }
433
+ const expression = globExpression(resolvePath(dir, pattern));
434
+ for (const candidate of files.byName.values()) {
435
+ if (candidate !== file && expression.test(candidate.fileName)) {
436
+ found.push({ file: candidate, rule: "computed-import", source: pattern, exports: true });
437
+ }
438
+ }
439
+ };
440
+ const visit = (node) => {
441
+ if (isCallExpression(node)) {
442
+ const [first] = node.arguments;
443
+ const callee = node.expression;
444
+ const loads = callee.kind === SyntaxKind.ImportKeyword ||
445
+ (isIdentifier(callee) && callee.text === "require");
446
+ if (loads && first !== undefined) {
447
+ const prefix = leadingLiteral(first);
448
+ if (prefix !== undefined) {
449
+ below(prefix);
450
+ }
451
+ }
452
+ else if (isMetaGlob(node) && first !== undefined) {
453
+ for (const pattern of literalsOf(first) ?? []) {
454
+ globbed(pattern);
455
+ }
456
+ }
457
+ }
458
+ node.forEachChild(visit);
459
+ };
460
+ file.forEachChild(visit);
461
+ return found;
462
+ }
391
463
  /**
392
464
  * Every file of one project the rules enter, in no particular order and possibly more
393
465
  * than once: a file two rules enter appears once per rule.
@@ -417,10 +489,23 @@ export function entryPoints(project, files, testFiles, host, targetRoot, parse,
417
489
  }
418
490
  }
419
491
  found.push(...workersOf(project, file, files));
492
+ found.push(...computedImportsOf(file, files));
420
493
  }
421
494
  for (const { file, source } of htmlEntries(host, targetRoot, files, parse)) {
422
495
  found.push({ file, rule: "html-entry", source, exports: true });
423
496
  }
497
+ for (const { file, source, names } of documentEntries(host, targetRoot, files)) {
498
+ found.push({
499
+ file,
500
+ rule: "document-import",
501
+ source,
502
+ exports: true,
503
+ ...(names === undefined ? {} : { names }),
504
+ });
505
+ }
506
+ for (const { file, source } of buildScriptEntries(host, targetRoot, files, parse)) {
507
+ found.push({ file, rule: "bundler-entry", source, exports: true });
508
+ }
424
509
  for (const { file, source } of workflowEntries(host, targetRoot, files)) {
425
510
  found.push({ file, rule: "workflow-step", source, exports: true });
426
511
  }
@@ -6,7 +6,8 @@
6
6
  * or `unknown` expression asserted to it, or bound to a variable or returned from a
7
7
  * function it annotates, where a number counts too. An enum's own value converts nothing.
8
8
  */
9
- import { isArrowFunction, isAsExpression, isElementAccessExpression, isEnumDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isParenthesizedExpression, isParenthesizedTypeNode, isPropertyAccessExpression, isReturnStatement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
9
+ import { isArrowFunction, isAsExpression, isElementAccessExpression, isEnumDeclaration, isFunctionDeclaration, isFunctionExpression, isGetAccessorDeclaration, isIdentifier, isMethodDeclaration, isNoSubstitutionTemplateLiteral, isParenthesizedTypeNode, isPropertyAccessExpression, isReturnStatement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
10
+ import { parenthesesRemoved } from "./unwrap.js";
10
11
  import { TypeFlags } from "@typescript/native/sync";
11
12
  import { aliasChains } from "./alias-chain.js";
12
13
  import { nodeKey } from "./inventory.js";
@@ -16,14 +17,6 @@ import { isAnswered, UNANSWERED } from "./query.js";
16
17
  const NUMBER = TypeFlags.NumberLike;
17
18
  const STRING = TypeFlags.StringLike;
18
19
  const DECODED = TypeFlags.Any | TypeFlags.Unknown;
19
- /** The expression one node is, through however many parentheses are written around it. */
20
- function unparenthesized(node) {
21
- let held = node;
22
- while (isParenthesizedExpression(held)) {
23
- held = held.expression;
24
- }
25
- return held;
26
- }
27
20
  /**
28
21
  * The name node a type node names its declaration by: the identifier of a type
29
22
  * reference, the right side of a qualified one, through parentheses. A type that is no
@@ -45,7 +38,7 @@ function typeName(type) {
45
38
  }
46
39
  /** The name node an expression names an object by: an identifier, or a property access's name. */
47
40
  function objectName(expression) {
48
- const held = unparenthesized(expression);
41
+ const held = parenthesesRemoved(expression);
49
42
  if (isIdentifier(held)) {
50
43
  return held;
51
44
  }
@@ -6,10 +6,11 @@
6
6
  * either stores. A store into an erased property of a type the analyzed program does not
7
7
  * declare hands the value to the code that declares it.
8
8
  */
9
- import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadElement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
9
+ import { isArrayLiteralExpression, isBinaryExpression, isCallExpression, isElementAccessExpression, isIdentifier, isNoSubstitutionTemplateLiteral, isNumericLiteral, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isShorthandPropertyAssignment, isSpreadElement, isStringLiteral, isVariableDeclaration, SyntaxKind, } from "@typescript/native/ast";
10
10
  import { SymbolFlags, TypeFlags } from "@typescript/native/sync";
11
11
  import { must } from "./query.js";
12
12
  import { checkedTypes } from "./value-reach.js";
13
+ import { parenthesesRemoved } from "./unwrap.js";
13
14
  /** The types that keep nothing of the value they are given. */
14
15
  const ERASED = TypeFlags.Any | TypeFlags.Unknown;
15
16
  /** The array methods whose arguments become elements of the array they are called on. */
@@ -20,9 +21,6 @@ function literalKey(node) {
20
21
  ? node.text
21
22
  : undefined;
22
23
  }
23
- function unwrapped(node) {
24
- return isParenthesizedExpression(node) ? unwrapped(node.expression) : node;
25
- }
26
24
  /**
27
25
  * Whether a variable declaration's type can differ from what is stored in it: it carries an
28
26
  * annotation, or it has neither an annotation nor an initializer, so its type evolves.
@@ -76,8 +74,8 @@ function propertyAt(source, name) {
76
74
  * here, and a default value is stored in its target too.
77
75
  */
78
76
  function patternTargets(pattern, from, out) {
79
- const target = unwrapped(pattern);
80
- const source = from.path.length === 0 ? unwrapped(from.value) : from.value;
77
+ const target = parenthesesRemoved(pattern);
78
+ const source = from.path.length === 0 ? parenthesesRemoved(from.value) : from.value;
81
79
  const into = (element, step, literal) => {
82
80
  patternTargets(element, literal === undefined
83
81
  ? { value: from.value, path: [...from.path, step] }
@@ -1,62 +1,4 @@
1
- /**
2
- * The exemption classes of the Contract's vocabulary, in its order: each class's name, its
3
- * languages and, for a TypeScript class, which private members it may retain. The rows are
4
- * written out, as the fixed severity codes are, and a test holds them equal to the Contract.
5
- * A class name is a type, so a table or a record naming a class outside the vocabulary, or
6
- * one of another language only, does not compile.
7
- */
8
- /** Every class of the vocabulary, in the order the Contract lists them. */
9
- export const EXEMPTION_CLASSES = [
10
- {
11
- class: "interface-satisfaction",
12
- languages: ["go", "ts"],
13
- typescriptVisibility: { private: false, privateName: false },
14
- },
15
- { class: "encoding-reflection", languages: ["go"] },
16
- { class: "format-verb-contract", languages: ["go"] },
17
- { class: "errors-duck-typing", languages: ["go"] },
18
- {
19
- class: "enum-group",
20
- languages: ["go", "ts"],
21
- typescriptVisibility: { private: false, privateName: false },
22
- },
23
- {
24
- class: "generated-file",
25
- languages: ["go", "ts"],
26
- typescriptVisibility: { private: true, privateName: false },
27
- },
28
- { class: "linkname-cgo-asm-plugin", languages: ["go"] },
29
- {
30
- class: "template-field",
31
- languages: ["go", "ts"],
32
- typescriptVisibility: { private: true, privateName: false },
33
- },
34
- {
35
- class: "reflective-lookup",
36
- languages: ["go", "ts"],
37
- typescriptVisibility: { private: true, privateName: false },
38
- },
39
- {
40
- class: "decorator",
41
- languages: ["ts"],
42
- typescriptVisibility: { private: true, privateName: false },
43
- },
44
- {
45
- class: "injection-container",
46
- languages: ["ts"],
47
- typescriptVisibility: { private: true, privateName: false },
48
- },
49
- {
50
- class: "framework-lifecycle",
51
- languages: ["ts"],
52
- typescriptVisibility: { private: true, privateName: false },
53
- },
54
- {
55
- class: "serialization-contract",
56
- languages: ["ts"],
57
- typescriptVisibility: { private: true, privateName: false },
58
- },
59
- ];
1
+ import { EXEMPTION_CLASSES } from "./contract/contract.generated.js";
60
2
  function runsOnTS(row) {
61
3
  const languages = row.languages;
62
4
  return languages.includes("ts");
@@ -69,7 +11,3 @@ const VISIBILITY = new Map(TS_ROWS.map((row) => [row.class, row.typescriptVisibi
69
11
  export function typescriptVisibilityOf(exemptionClass) {
70
12
  return VISIBILITY.get(exemptionClass) ?? { private: false, privateName: false };
71
13
  }
72
- /** Whether one name is a class of the vocabulary, whichever language it runs on. */
73
- export function isExemptionClass(name) {
74
- return EXEMPTION_CLASSES.some((row) => row.class === name);
75
- }