@cplieger/deadset-ts 8.0.0-dev.15 → 8.0.0-dev.17

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 (207) hide show
  1. package/dist/src/alias-chain.js +0 -5
  2. package/dist/src/amd.js +81 -0
  3. package/dist/src/analysis.js +94 -54
  4. package/dist/src/augmentations.js +23 -24
  5. package/dist/src/bindings.js +382 -0
  6. package/dist/src/calls.js +25 -8
  7. package/dist/src/commands/read.js +6 -7
  8. package/dist/src/commands/tools.js +5 -9
  9. package/dist/src/component-files.js +3 -2
  10. package/dist/src/conformance-record.js +1 -1
  11. package/dist/src/consumers.js +9 -10
  12. package/dist/src/contextual-uses.js +1 -17
  13. package/dist/src/contract/config.generated.js +5 -2
  14. package/dist/src/contract/contract.generated.js +119 -2
  15. package/dist/src/contract/report.generated.js +6 -2
  16. package/dist/src/dependencies.js +98 -71
  17. package/dist/src/destructuring.js +1 -19
  18. package/dist/src/element-access.js +179 -164
  19. package/dist/src/entries/build-scripts.js +222 -0
  20. package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
  21. package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
  22. package/dist/src/entries/conventions.js +739 -0
  23. package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
  24. package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
  25. package/dist/src/entries/run-directories.js +77 -0
  26. package/dist/src/entries/sources.js +144 -0
  27. package/dist/src/entries/static-module.js +670 -0
  28. package/dist/src/entries/test-runners.js +684 -0
  29. package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
  30. package/dist/src/extensions.js +37 -0
  31. package/dist/src/facts.js +38 -0
  32. package/dist/src/findings/completion.js +40 -4
  33. package/dist/src/findings/emitters.js +5 -8
  34. package/dist/src/{implementations.js → findings/implementations.js} +3 -3
  35. package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
  36. package/dist/src/findings/self-check.js +36 -20
  37. package/dist/src/findings-pass.js +5 -6
  38. package/dist/src/framework-lifecycle.js +42 -15
  39. package/dist/src/fs/manifest.js +15 -20
  40. package/dist/src/generated-file.js +172 -29
  41. package/dist/src/graph.js +14 -8
  42. package/dist/src/import-bindings.js +73 -28
  43. package/dist/src/index/sites.js +557 -0
  44. package/dist/src/index.js +1 -1
  45. package/dist/src/indexed-access.js +523 -478
  46. package/dist/src/injection-container.js +1 -1
  47. package/dist/src/interface-satisfaction.js +1 -1
  48. package/dist/src/inventory/commonjs.js +62 -17
  49. package/dist/src/inventory/enumerate.js +2 -1
  50. package/dist/src/javascript-file.js +26 -11
  51. package/dist/src/keyof-comparisons.js +124 -103
  52. package/dist/src/load/classify.js +28 -9
  53. package/dist/src/load/diagnostics.js +8 -1
  54. package/dist/src/load/discover.js +15 -7
  55. package/dist/src/load/expected-module.js +2 -2
  56. package/dist/src/load/plan.js +2 -2
  57. package/dist/src/load/specifiers.js +2 -4
  58. package/dist/src/load/synthesize.js +10 -5
  59. package/dist/src/load/workspace-manifests.js +1 -1
  60. package/dist/src/load/workspace-resolution.js +2 -2
  61. package/dist/src/matrix.js +12 -17
  62. package/dist/src/module-declarations.js +70 -85
  63. package/dist/src/overrides.js +134 -112
  64. package/dist/src/paths.js +56 -1
  65. package/dist/src/platform-calls.js +78 -0
  66. package/dist/src/platform-symbols.js +134 -0
  67. package/dist/src/references/read-rule.js +1 -0
  68. package/dist/src/references/read-rules.js +48 -0
  69. package/dist/src/references/sink.js +151 -0
  70. package/dist/src/references/sources.js +450 -0
  71. package/dist/src/references.js +111 -1116
  72. package/dist/src/resolve.js +1 -1
  73. package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
  74. package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
  75. package/dist/src/{manifest.js → roots/manifest.js} +4 -4
  76. package/dist/src/roots/published-reach.js +412 -0
  77. package/dist/src/roots/root-set.js +489 -0
  78. package/dist/src/roots/unseen-loads.js +236 -0
  79. package/dist/src/serialization-contract.js +9 -4
  80. package/dist/src/source-files.js +5 -9
  81. package/dist/src/stores.js +4 -2
  82. package/dist/src/suppress.js +30 -32
  83. package/dist/src/sweep.js +25 -13
  84. package/dist/src/symbol-keys.js +89 -83
  85. package/dist/src/tests/file-class.js +76 -0
  86. package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
  87. package/dist/src/{test-support.js → tests/support.js} +5 -12
  88. package/dist/src/type-parameter-reads.js +178 -139
  89. package/dist/src/unimplemented-callees.js +262 -242
  90. package/dist/src/verbs/analyze.js +15 -3
  91. package/dist/src/verbs/describe.js +5 -3
  92. package/dist/src/verbs/explain.js +50 -28
  93. package/dist/src/verbs/print-retained.js +2 -2
  94. package/jsr.json +1 -1
  95. package/package.json +3 -3
  96. package/src/alias-chain.ts +0 -7
  97. package/src/amd.ts +130 -0
  98. package/src/analysis.ts +156 -70
  99. package/src/augmentations.ts +41 -27
  100. package/src/automatic-types.ts +1 -1
  101. package/src/bindings.ts +460 -0
  102. package/src/calls.ts +25 -7
  103. package/src/commands/read.ts +6 -8
  104. package/src/commands/tools.ts +5 -11
  105. package/src/component-files.ts +4 -2
  106. package/src/conformance-record.ts +1 -1
  107. package/src/consumers.ts +12 -11
  108. package/src/contextual-uses.ts +2 -22
  109. package/src/contract/config.generated.ts +5 -2
  110. package/src/contract/contract.generated.ts +138 -2
  111. package/src/contract/report.generated.ts +6 -2
  112. package/src/dependencies.ts +117 -71
  113. package/src/destructuring.ts +1 -31
  114. package/src/element-access.ts +188 -182
  115. package/src/entries/build-scripts.ts +292 -0
  116. package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
  117. package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
  118. package/src/{conventions.ts → entries/conventions.ts} +349 -523
  119. package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
  120. package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
  121. package/src/entries/run-directories.ts +108 -0
  122. package/src/entries/sources.ts +266 -0
  123. package/src/entries/static-module.ts +858 -0
  124. package/src/entries/test-runners.ts +868 -0
  125. package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
  126. package/src/exempt.ts +3 -2
  127. package/src/extensions.ts +45 -0
  128. package/src/facts.ts +46 -0
  129. package/src/file-facts.ts +1 -1
  130. package/src/findings/boundary.ts +2 -0
  131. package/src/findings/completion.ts +43 -4
  132. package/src/findings/emitter.ts +1 -1
  133. package/src/findings/emitters.ts +14 -10
  134. package/src/{implementations.ts → findings/implementations.ts} +4 -4
  135. package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
  136. package/src/findings/self-check.ts +41 -22
  137. package/src/findings/visibility-narrowing.ts +1 -1
  138. package/src/findings-pass.ts +5 -12
  139. package/src/framework-lifecycle.ts +43 -15
  140. package/src/fs/manifest.ts +25 -21
  141. package/src/generated-file.ts +240 -36
  142. package/src/graph.ts +27 -11
  143. package/src/import-bindings.ts +98 -29
  144. package/src/index/sites.ts +781 -0
  145. package/src/index.ts +1 -1
  146. package/src/indexed-access.ts +570 -527
  147. package/src/injection-container.ts +1 -1
  148. package/src/interface-satisfaction.ts +1 -1
  149. package/src/inventory/commonjs.ts +102 -17
  150. package/src/inventory/enumerate.ts +2 -1
  151. package/src/javascript-file.ts +29 -12
  152. package/src/keyof-comparisons.ts +131 -116
  153. package/src/load/classify.ts +49 -13
  154. package/src/load/diagnostics.ts +11 -1
  155. package/src/load/discover.ts +27 -11
  156. package/src/load/expected-module.ts +8 -2
  157. package/src/load/plan.ts +11 -2
  158. package/src/load/specifiers.ts +8 -5
  159. package/src/load/synthesize.ts +12 -6
  160. package/src/load/test-globals.ts +1 -1
  161. package/src/load/workspace-manifests.ts +1 -1
  162. package/src/load/workspace-resolution.ts +9 -2
  163. package/src/load/workspace-run.ts +1 -1
  164. package/src/matrix.ts +13 -17
  165. package/src/module-declarations.ts +74 -98
  166. package/src/overrides.ts +162 -134
  167. package/src/paths.ts +65 -1
  168. package/src/platform-calls.ts +119 -0
  169. package/src/platform-symbols.ts +189 -0
  170. package/src/references/read-rule.ts +60 -0
  171. package/src/references/read-rules.ts +55 -0
  172. package/src/references/sink.ts +213 -0
  173. package/src/references/sources.ts +632 -0
  174. package/src/references.ts +174 -1492
  175. package/src/report.ts +2 -2
  176. package/src/resolve.ts +1 -1
  177. package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
  178. package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
  179. package/src/{manifest.ts → roots/manifest.ts} +5 -5
  180. package/src/roots/published-reach.ts +481 -0
  181. package/src/{roots.ts → roots/root-set.ts} +203 -630
  182. package/src/roots/unseen-loads.ts +345 -0
  183. package/src/serialization-contract.ts +9 -4
  184. package/src/source-files.ts +5 -10
  185. package/src/stores.ts +13 -2
  186. package/src/suppress.ts +48 -33
  187. package/src/sweep.ts +37 -14
  188. package/src/symbol-keys.ts +102 -102
  189. package/src/tests/file-class.ts +118 -0
  190. package/src/{outside-tests.ts → tests/outside.ts} +16 -10
  191. package/src/{test-support.ts → tests/support.ts} +15 -21
  192. package/src/type-parameter-reads.ts +187 -158
  193. package/src/unimplemented-callees.ts +297 -280
  194. package/src/verbs/analyze.ts +19 -5
  195. package/src/verbs/describe.ts +5 -2
  196. package/src/verbs/explain.ts +75 -32
  197. package/src/verbs/print-retained.ts +2 -2
  198. package/dist/src/build-scripts.js +0 -418
  199. package/dist/src/conventions.js +0 -907
  200. package/dist/src/entry-points.js +0 -444
  201. package/dist/src/own-body.js +0 -28
  202. package/dist/src/roots.js +0 -865
  203. package/dist/src/unseen-loads.js +0 -189
  204. package/src/build-scripts.ts +0 -536
  205. package/src/entry-points.ts +0 -584
  206. package/src/own-body.ts +0 -42
  207. package/src/unseen-loads.ts +0 -282
@@ -0,0 +1,345 @@
1
+ /**
2
+ * The loads by a computed path: those whose files the analysis resolves, and those it
3
+ * cannot, whose files are roots the analysis does not see, so the configuration's roots
4
+ * are never complete.
5
+ */
6
+
7
+ import {
8
+ isBinaryExpression,
9
+ isCallExpression,
10
+ isIdentifier,
11
+ isMetaProperty,
12
+ isNoSubstitutionTemplateLiteral,
13
+ isPropertyAccessExpression,
14
+ isStringLiteral,
15
+ isTemplateExpression,
16
+ SyntaxKind,
17
+ type CallExpression,
18
+ type Identifier,
19
+ type Node,
20
+ type SourceFile,
21
+ } from "@typescript/native/ast";
22
+ import { amdLoadOf, amdUnresolved } from "../amd.ts";
23
+ import { membersNamed, textsOf, writtenValue, type Value } from "../entries/static-module.ts";
24
+ import { writtenName } from "../platform-calls.ts";
25
+ import {
26
+ cannotStartPackageName,
27
+ dirnamePath,
28
+ hasURLScheme,
29
+ isAbsolutePath,
30
+ relativePath,
31
+ resolvePath,
32
+ startsRelativePath,
33
+ } from "../paths.ts";
34
+ import type { Position } from "../position.ts";
35
+ import { unwrapped } from "../unwrap.ts";
36
+ import { globExpression } from "../text/glob.ts";
37
+
38
+ /** One load the analysis cannot resolve, in the configuration whose production file writes it. */
39
+ export interface UnseenLoad {
40
+ readonly configuration: string;
41
+ /** The position of the call. */
42
+ readonly position: Position;
43
+ }
44
+
45
+ /** The literal text a computed path starts with: a template's head, or a concatenation's first literal. */
46
+ function leadingLiteral(node: Node): string | undefined {
47
+ const at = unwrapped(node);
48
+ if (isTemplateExpression(at)) {
49
+ return at.head.text;
50
+ }
51
+ if (isBinaryExpression(at) && at.operatorToken.kind === SyntaxKind.PlusToken) {
52
+ const left = unwrapped(at.left);
53
+ return isStringLiteral(left) || isNoSubstitutionTemplateLiteral(left)
54
+ ? left.text
55
+ : leadingLiteral(at.left);
56
+ }
57
+ return undefined;
58
+ }
59
+
60
+ /**
61
+ * The directory below `dir` a computed load's leading literal names, every file of which
62
+ * the load reaches, or none where it names a package, `dir` itself or a directory holding
63
+ * it, each of which would name every sibling.
64
+ */
65
+ function computedLoadDirectory(dir: string, prefix: string): string | undefined {
66
+ if (!startsRelativePath(prefix) || !prefix.endsWith("/")) {
67
+ return undefined;
68
+ }
69
+ const target = resolvePath(dir, prefix);
70
+ return target === dir || relativePath(target, dir) !== undefined ? undefined : target;
71
+ }
72
+
73
+ /** One `import()`, `require()` or `import.meta.glob()` call, and the declaration whose code writes it. */
74
+ export interface LoadCall {
75
+ readonly call: CallExpression;
76
+ readonly from: string;
77
+ }
78
+
79
+ /** One file a computed load may load, by its id. */
80
+ interface Loadable {
81
+ readonly file: SourceFile;
82
+ readonly fileId: string;
83
+ }
84
+
85
+ /** One load by a computed path whose files the analysis resolves. */
86
+ export interface ComputedLoad {
87
+ readonly call: CallExpression;
88
+ readonly from: string;
89
+ /** The name a `require()` call is written with; {@link callsPlatform} owns which one loads. */
90
+ readonly require: Identifier | undefined;
91
+ /** Whether the call is an AMD `define` or `require` (references-ts.37). */
92
+ readonly amd: boolean;
93
+ readonly files: readonly Loadable[];
94
+ /** Whether the load reads each file as text or as a URL, which references no declaration of it. */
95
+ readonly asset: boolean;
96
+ /**
97
+ * Whether the analysis names every file the load may load. An unresolved load names none,
98
+ * and leaves the configuration's roots incomplete (confidence.26).
99
+ */
100
+ readonly resolved: boolean;
101
+ /**
102
+ * Whether the load is an eager glob, which Vite turns into static imports at the top of
103
+ * the calling module (https://vite.dev/guide/features.html#glob-import), so the file
104
+ * evaluates what it loads wherever the call is written.
105
+ */
106
+ readonly eager: boolean;
107
+ }
108
+
109
+ /** Whether one node is a call that may load a module by a computed path. */
110
+ export function isLoadCall(node: Node): node is CallExpression {
111
+ if (!isCallExpression(node)) {
112
+ return false;
113
+ }
114
+ return (
115
+ node.expression.kind === SyntaxKind.ImportKeyword ||
116
+ writtenName(node.expression) !== undefined ||
117
+ isMetaGlob(node)
118
+ );
119
+ }
120
+
121
+ /**
122
+ * The files one `import.meta.glob` call loads, as Vite's `toAbsoluteGlob` reads its globs
123
+ * (https://vite.dev/guide/features.html#glob-import): `/` against the project root, `./`
124
+ * and `../` against a literal `base` (read against the root where it starts with `/`) or the
125
+ * calling file's directory, `**` as written, and `!` removing what it matches. Undefined
126
+ * where Vite reads a glob through an alias, the root is unknown, or a glob or base is computed.
127
+ */
128
+ function globbed(
129
+ file: SourceFile,
130
+ first: Node,
131
+ options: Node | undefined,
132
+ others: readonly Loadable[],
133
+ rootOf: (fileName: string) => string | undefined,
134
+ ): readonly Loadable[] | undefined {
135
+ const patterns = textsOf(writtenValue(first), true);
136
+ const bases = globOption(options, "base");
137
+ const [written, ...more] = bases ?? [];
138
+ const base = written === undefined ? undefined : textsOf(written, false);
139
+ if (
140
+ patterns === undefined ||
141
+ bases === undefined ||
142
+ more.length > 0 ||
143
+ (written !== undefined && base === undefined)
144
+ ) {
145
+ return undefined;
146
+ }
147
+ const dir = dirnamePath(file.fileName);
148
+ const root = (): string | undefined => rootOf(file.fileName);
149
+ const against = (at: string): string | undefined => {
150
+ if (!isAbsolutePath(at)) {
151
+ return resolvePath(dir, at);
152
+ }
153
+ const top = root();
154
+ return top === undefined ? undefined : resolvePath(top, `.${at}`);
155
+ };
156
+ const from = base?.[0] === undefined ? dir : against(base[0]);
157
+ const keep: RegExp[] = [];
158
+ const drop: RegExp[] = [];
159
+ for (const written of patterns) {
160
+ const negated = written.startsWith("!");
161
+ const pattern = negated ? written.slice(1) : written;
162
+ let path: string | undefined;
163
+ if (isAbsolutePath(pattern)) {
164
+ path = against(pattern);
165
+ } else if (startsRelativePath(pattern)) {
166
+ path = from === undefined ? undefined : resolvePath(from, pattern);
167
+ } else if (pattern.startsWith("**")) {
168
+ path = pattern;
169
+ }
170
+ if (path === undefined) {
171
+ return undefined;
172
+ }
173
+ (negated ? drop : keep).push(globExpression(path));
174
+ }
175
+ return others.filter(
176
+ (candidate) =>
177
+ keep.some((glob) => glob.test(candidate.file.fileName)) &&
178
+ !drop.some((glob) => glob.test(candidate.file.fileName)),
179
+ );
180
+ }
181
+
182
+ /**
183
+ * The computed loads among one file's load calls, each loading the `candidates` but the
184
+ * file itself: an `import()` or `require()` whose path starts with a relative literal naming
185
+ * a directory below the file's loads every candidate below it, as a bundler's context module
186
+ * reads it, and an `import.meta.glob` every candidate its globs match ({@link globbed}). A
187
+ * call of a computed path that names no such directory and no package is an unresolved load,
188
+ * and so is an AMD call one of whose dependencies names no path ({@link amdUnresolved}).
189
+ */
190
+ export function computedLoadsOf(
191
+ file: SourceFile,
192
+ calls: readonly LoadCall[],
193
+ candidates: readonly Loadable[],
194
+ rootOf: (fileName: string) => string | undefined,
195
+ ): readonly ComputedLoad[] {
196
+ const dir = dirnamePath(file.fileName);
197
+ const others = candidates.filter((candidate) => candidate.file !== file);
198
+ return calls.flatMap(({ call, from }): ComputedLoad[] => {
199
+ const [first, options] = call.arguments;
200
+ if (first === undefined) {
201
+ return [];
202
+ }
203
+ if (isMetaGlob(call)) {
204
+ const files = globbed(file, first, options, others, rootOf);
205
+ const asset = readsAsAsset(options);
206
+ return [
207
+ {
208
+ call,
209
+ from,
210
+ require: undefined,
211
+ amd: false,
212
+ files: files ?? [],
213
+ resolved: files !== undefined,
214
+ asset,
215
+ eager: isEager(options),
216
+ },
217
+ ];
218
+ }
219
+ const amd = amdLoadOf(call);
220
+ if (amd !== undefined) {
221
+ return amdUnresolved(amd)
222
+ ? [
223
+ {
224
+ call,
225
+ from,
226
+ require: amd.callee,
227
+ amd: true,
228
+ files: [],
229
+ resolved: false,
230
+ asset: false,
231
+ eager: false,
232
+ },
233
+ ]
234
+ : [];
235
+ }
236
+ const argument = unwrapped(first);
237
+ const require =
238
+ call.expression.kind === SyntaxKind.ImportKeyword ? undefined : writtenName(call.expression);
239
+ if (!mayNamePath(argument)) {
240
+ return [];
241
+ }
242
+ const prefix = leadingLiteral(argument);
243
+ if (namesPackage(prefix)) {
244
+ return [];
245
+ }
246
+ const target = prefix === undefined ? undefined : computedLoadDirectory(dir, prefix);
247
+ const files =
248
+ target === undefined
249
+ ? []
250
+ : others.filter((candidate) => relativePath(target, candidate.file.fileName) !== undefined);
251
+ return [
252
+ {
253
+ call,
254
+ from,
255
+ require,
256
+ amd: false,
257
+ files,
258
+ resolved: target !== undefined,
259
+ asset: false,
260
+ eager: false,
261
+ },
262
+ ];
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Whether a computed argument may be a module path: anything but a string, which names its
268
+ * module, and a value no path is, a number, a boolean, `null`, an object, an array, a
269
+ * function or a class.
270
+ */
271
+ function mayNamePath(argument: Node): boolean {
272
+ return !NO_PATH.has(argument.kind);
273
+ }
274
+
275
+ const NO_PATH: ReadonlySet<SyntaxKind> = new Set([
276
+ SyntaxKind.StringLiteral,
277
+ SyntaxKind.NoSubstitutionTemplateLiteral,
278
+ SyntaxKind.NumericLiteral,
279
+ SyntaxKind.BigIntLiteral,
280
+ SyntaxKind.TrueKeyword,
281
+ SyntaxKind.FalseKeyword,
282
+ SyntaxKind.NullKeyword,
283
+ SyntaxKind.ObjectLiteralExpression,
284
+ SyntaxKind.ArrayLiteralExpression,
285
+ SyntaxKind.ArrowFunction,
286
+ SyntaxKind.FunctionExpression,
287
+ SyntaxKind.ClassExpression,
288
+ SyntaxKind.RegularExpressionLiteral,
289
+ ]);
290
+
291
+ /** Whether one call is `import.meta.glob(...)`. */
292
+ function isMetaGlob(node: CallExpression): boolean {
293
+ if (!isPropertyAccessExpression(node.expression)) {
294
+ return false;
295
+ }
296
+ const { expression, name } = node.expression;
297
+ return (
298
+ isIdentifier(name) &&
299
+ name.text === "glob" &&
300
+ isMetaProperty(expression) &&
301
+ expression.keywordToken === SyntaxKind.ImportKeyword
302
+ );
303
+ }
304
+
305
+ /** The values the options of an `import.meta.glob` write for one key, undefined where a spread may hide one. */
306
+ function globOption(options: Node | undefined, key: string): readonly Value[] | undefined {
307
+ return options === undefined ? [] : membersNamed(writtenValue(options), key);
308
+ }
309
+
310
+ /** Whether the options of an `import.meta.glob` set `eager` to `true`. */
311
+ function isEager(options: Node | undefined): boolean {
312
+ return (
313
+ globOption(options, "eager")?.some((value) => value.kind === "boolean" && value.value) === true
314
+ );
315
+ }
316
+
317
+ /** The query parameters that make Vite load a file as its text or its URL, each written bare. */
318
+ const ASSET_QUERY = /(?:^\?|&)(?:raw|url|inline)(?:&|$)/u;
319
+
320
+ /**
321
+ * Whether the options of an `import.meta.glob` read each file as text or as a URL: a
322
+ * `query` string holding a bare `raw`, `url` or `inline`, or an `as` of `raw` or `url`,
323
+ * which Vite reads as that query (https://vite.dev/guide/features.html#glob-import). Any
324
+ * other query is read as loading the module, which keeps more than it may.
325
+ */
326
+ function readsAsAsset(options: Node | undefined): boolean {
327
+ const texts = (key: string): readonly string[] =>
328
+ (globOption(options, key) ?? []).flatMap((value) =>
329
+ value.kind === "text" ? [value.text] : [],
330
+ );
331
+ return (
332
+ texts("as").some((text) => text === "raw" || text === "url") ||
333
+ texts("query").some((text) => ASSET_QUERY.test(text.startsWith("?") ? text : `?${text}`))
334
+ );
335
+ }
336
+
337
+ /** Whether a computed path's leading literal names a package, whose files are none of the target's. */
338
+ function namesPackage(prefix: string | undefined): boolean {
339
+ return (
340
+ prefix !== undefined &&
341
+ prefix !== "" &&
342
+ !cannotStartPackageName(prefix) &&
343
+ !hasURLScheme(prefix)
344
+ );
345
+ }
@@ -72,7 +72,7 @@ import {
72
72
  namesADeclaration,
73
73
  resolveEntries,
74
74
  spelledEntry,
75
- } from "./configured-declarations.ts";
75
+ } from "./roots/configured-declarations.ts";
76
76
  import type { DetectorInput, Evidence } from "./exempt.ts";
77
77
  import { nodeKey, type Inventory } from "./inventory.ts";
78
78
  import { renderPosition, type Position } from "./position.ts";
@@ -1220,7 +1220,8 @@ export interface Decoded {
1220
1220
 
1221
1221
  /**
1222
1222
  * The data members the decodes and copies of `files` fill, as {@link Decoded} names them.
1223
- * A generic declared outside the analysis, or only ambiently, fills the type that a type
1223
+ * A generic declared outside the analysis, or only ambiently, or one that reads a value
1224
+ * {@link readTypes} finds untyped as its own type parameter, fills the type that a type
1224
1225
  * parameter of its own no parameter names binds, where the awaited result carries that
1225
1226
  * type through its data members rather than only naming it, as a store's methods do: no
1226
1227
  * argument hands the value over, so it fills it where the analysis cannot see, and may
@@ -1282,6 +1283,10 @@ function decodedOf<Brand>(
1282
1283
  for (const file of files) {
1283
1284
  file.forEachChild(visit);
1284
1285
  }
1286
+ const read = readTypes(project, untyped);
1287
+ // A generic of the analysis that reads an untyped value as its own type parameter is a
1288
+ // client of what made the value, and fills what that parameter binds as an outside one does.
1289
+ const clients = new Set(read.made.flatMap((type) => (type.isTypeParameter() ? [type.id] : [])));
1285
1290
  const filled: { readonly carried: Type; readonly types: readonly Type[] }[] = [];
1286
1291
  const resolved = queries.resolvedSignatures(calls).map(must);
1287
1292
  for (const signature of resolved) {
@@ -1296,7 +1301,8 @@ function decodedOf<Brand>(
1296
1301
  if (
1297
1302
  declaration === undefined ||
1298
1303
  (inAnalysis(declaration.path) &&
1299
- (node === undefined || (node.flags & NodeFlags.Ambient) === 0))
1304
+ (node === undefined || (node.flags & NodeFlags.Ambient) === 0) &&
1305
+ !generic.some((id) => clients.has(id)))
1300
1306
  ) {
1301
1307
  continue;
1302
1308
  }
@@ -1318,7 +1324,6 @@ function decodedOf<Brand>(
1318
1324
  filled.push({ carried: awaited(project, given), types });
1319
1325
  }
1320
1326
  }
1321
- const read = readTypes(project, untyped);
1322
1327
  const copies = copiedNames(project, spreads, calls);
1323
1328
  if (
1324
1329
  filled.length === 0 &&
@@ -8,6 +8,7 @@ import type { SourceFile } from "@typescript/native/ast";
8
8
  import { commonDirectory, emittedFrom } from "./load/emit-map.ts";
9
9
  import { dirnamePath, joinPath, relativePath, resolvePath } from "./paths.ts";
10
10
  import type { ProjectView } from "./session.ts";
11
+ import { OUTPUT_EXTENSIONS } from "./extensions.ts";
11
12
 
12
13
  /** The one character a subpath pattern of a manifest's `exports` holds. */
13
14
  const SUBPATH_WILDCARD = "*";
@@ -15,9 +16,6 @@ const SUBPATH_WILDCARD = "*";
15
16
  /** The extension a source file's name drops when the project writes it out. */
16
17
  const SOURCE_EXTENSION = /\.[cm]?[jt]sx?$/u;
17
18
 
18
- /** The extensions the project may write a source file's output under. */
19
- const OUTPUT_EXTENSIONS = [".js", ".mjs", ".cjs", ".jsx", ".d.ts", ".d.mts", ".d.cts"] as const;
20
-
21
19
  /**
22
20
  * Whether one path is what a subpath pattern stands for: the text before the
23
21
  * wildcard, any run of characters including the solidus, then the text after it.
@@ -90,13 +88,10 @@ function layoutOf<Brand>(
90
88
  }
91
89
 
92
90
  /**
93
- * The lookups over one project's own files.
94
- *
95
- * A path that names an emitted file is read back to the source the project would
96
- * emit it from through the project's own emit mapping and nothing else: the
97
- * extension is one the compiler emits from a source extension, and a path below the
98
- * output directory is read below the directory the compiler emits from, which the
99
- * configuration declares or which is the directory every input file shares.
91
+ * The lookups over one project's own files. A path naming an emitted file is read back to its
92
+ * source through the project's own emit mapping alone: the extension is one the compiler emits
93
+ * from a source extension, and a path below the output directory is read below the directory
94
+ * the compiler emits from, which the configuration declares or every input file shares.
100
95
  */
101
96
  export function sourceFilesOf<Brand>(project: ProjectView<Brand>, targetRoot: string): SourceFiles {
102
97
  const byName = new Map<string, SourceFile>();
package/src/stores.ts CHANGED
@@ -13,6 +13,7 @@ import { nodeKey, type Inventory } from "./inventory.ts";
13
13
  import type { Reference } from "./references.ts";
14
14
  import type { ProjectView } from "./session.ts";
15
15
  import type { Exemption, Mode } from "./sweep.ts";
16
+ import { classedAsTest, madeByTestCode, withheldBy } from "./tests/file-class.ts";
16
17
 
17
18
  /** One run's stores and reads, over every configuration. */
18
19
  export interface Stores {
@@ -39,6 +40,8 @@ export interface Stores {
39
40
  /** What one configuration contributes to the run's stores. */
40
41
  interface ConfigurationStores {
41
42
  readonly references: readonly Reference[];
43
+ /** The configuration's test-support files, whose references are test code's. */
44
+ readonly support: ReadonlySet<string>;
42
45
  readonly accessors: readonly string[];
43
46
  }
44
47
 
@@ -71,12 +74,20 @@ export function storesOf(
71
74
  ): Stores {
72
75
  return {
73
76
  references: per.flatMap((one) =>
74
- one.references.filter((reference) => !mode.production || !reference.test),
77
+ one.references.filter((reference) => !withheldBy(mode, reference, one.support)),
75
78
  ),
76
79
  readInTests: new Set(
77
80
  per.flatMap((one) =>
78
81
  one.references
79
- .filter((reference) => reference.test && reference.use !== "write")
82
+ .filter(
83
+ (reference) =>
84
+ reference.use !== "write" &&
85
+ classedAsTest(
86
+ madeByTestCode(reference, one.support),
87
+ reference.consumer !== undefined,
88
+ mode,
89
+ ),
90
+ )
80
91
  .map((reference) => reference.to),
81
92
  ),
82
93
  ),
package/src/suppress.ts CHANGED
@@ -4,12 +4,13 @@
4
4
  * ignore entry or a baseline row. A bound record marks its declaration live before the
5
5
  * sweep, unless its code reports a part or a dependency row, and is in effect when it
6
6
  * withholds a finding its code would have produced there, dormant when the configuration
7
- * withholds every such finding anyway, and stale otherwise.
7
+ * or a hold withholds every such finding anyway, and stale otherwise.
8
8
  */
9
9
 
10
10
  import {
11
11
  type CODE,
12
12
  KIND_ROWS,
13
+ REMEDIES,
13
14
  SYMBOL_SUBJECTS,
14
15
  type Subject,
15
16
  } from "./contract/contract.generated.ts";
@@ -147,6 +148,20 @@ export interface Dials<F extends Finding = Finding> {
147
148
  readonly withholds: (finding: F) => boolean;
148
149
  }
149
150
 
151
+ /**
152
+ * What the analysis holds because it cannot decide it, as the ledger reads it: a hold is a
153
+ * gap, not evidence that the code changed, so a record it touches is dormant.
154
+ */
155
+ export interface Holds<F extends Finding = Finding> {
156
+ /** Whether a hold withholds one finding: a type error's skip, or a hold of its declaration. */
157
+ readonly withholds: (finding: F) => boolean;
158
+ /** Whether a hold keeps the declaration one record is bound to live, so no finding about it is computed. */
159
+ readonly holdsRecord: (record: SuppressionRecord) => boolean;
160
+ }
161
+
162
+ /** No hold. */
163
+ const NO_HOLDS: Holds = { withholds: () => false, holdsRecord: () => false };
164
+
150
165
  /** What every record did, and the findings the records withhold. */
151
166
  export interface Ledger {
152
167
  /** One verdict per record, in reading order. */
@@ -172,36 +187,30 @@ function rowOf(subject: Subject | undefined): boolean {
172
187
  return subject === "file" || subject === "dependency" || subject === "record";
173
188
  }
174
189
 
175
- /**
176
- * Whether a finding's subject is a row of a document rather than a declaration. Apart
177
- * from a dependency row, nothing withholds a finding about one.
178
- */
190
+ /** Whether a finding's subject is a row of a document rather than a declaration. */
179
191
  export function isRowSubject(kind: string): boolean {
180
192
  return rowOf(subjectOf(kind));
181
193
  }
182
194
 
183
- /** Whether a code reports a row of a document. */
184
- export function isRowCode(code: string): boolean {
185
- return rowOf(SUBJECT_OF_CODE.get(code));
195
+ /** The subject a code reports, which kinds.json's remedies table answers for. */
196
+ export function subjectOfCode(code: string): Subject | undefined {
197
+ return SUBJECT_OF_CODE.get(code);
186
198
  }
187
199
 
188
200
  /**
189
- * Whether a finding's subject is a dependency row. An ignore entry or a baseline row
190
- * naming the row's reference and file withholds its finding and marks nothing, and an
191
- * inline directive never binds one.
201
+ * Whether a record of `mechanism` can name a finding about `subject`, as kinds.json's
202
+ * remedies table lists the suppressions of each subject. A subject the table does not
203
+ * know is a declaration's.
192
204
  */
193
- export function isDependencySubject(kind: string): boolean {
194
- return subjectOf(kind) === "dependency";
195
- }
196
-
197
- /** Whether a code reports a dependency row, which only an ignore entry or a baseline row names. */
198
- export function isDependencyCode(code: string): boolean {
199
- return SUBJECT_OF_CODE.get(code) === "dependency";
205
+ export function names(subject: Subject | undefined, mechanism: Mechanism): boolean {
206
+ return (REMEDIES[subject ?? "declaration"].suppressions as readonly Mechanism[]).includes(
207
+ mechanism,
208
+ );
200
209
  }
201
210
 
202
- /** Whether a code reports a row of a document that no record of any mechanism can name. */
203
- export function isUnsuppressibleCode(code: string): boolean {
204
- return isRowCode(code) && !isDependencyCode(code);
211
+ /** Whether a finding about a symbol of `kind` is one a record of `mechanism` can name. */
212
+ export function namesKind(kind: string, mechanism: Mechanism): boolean {
213
+ return names(subjectOf(kind), mechanism);
205
214
  }
206
215
 
207
216
  /**
@@ -238,16 +247,13 @@ export function pairs(record: SuppressionRecord, finding: Finding): boolean {
238
247
  if (record.code !== finding.code) {
239
248
  return false;
240
249
  }
241
- if (isDependencySubject(finding.symbol.kind)) {
242
- return (
243
- record.mechanism !== "inline" &&
244
- record.symbol === finding.symbol.ref &&
245
- record.path === finding.position.path
246
- );
247
- }
248
- if (isRowSubject(finding.symbol.kind)) {
250
+ const subject = subjectOf(finding.symbol.kind);
251
+ if (!names(subject, record.mechanism)) {
249
252
  return false;
250
253
  }
254
+ if (subject === "dependency") {
255
+ return record.symbol === finding.symbol.ref && record.path === finding.position.path;
256
+ }
251
257
  if (record.bound !== "" && record.bound === positionKey(finding.position)) {
252
258
  return true;
253
259
  }
@@ -262,13 +268,14 @@ export function pairs(record: SuppressionRecord, finding: Finding): boolean {
262
268
  * there: swept with no mark for a liveness kind, and as swept otherwise. The first record
263
269
  * in reading order claims a finding, so a second naming it is stale; a part-kind record
264
270
  * claims every finding of its code about a part of its declaration. A record is dormant
265
- * when the dials withhold every finding it claims, read from those findings alone, or when
266
- * it claims none and its code's severity is `allow`.
271
+ * when the dials or `holds` withhold every finding it claims, or when it claims none and
272
+ * its code is at `allow` or a hold keeps its declaration live.
267
273
  */
268
274
  export function ledgerOf<F extends Finding>(
269
275
  records: readonly SuppressionRecord[],
270
276
  would: readonly F[],
271
277
  dials: Dials<F>,
278
+ holds: Holds<F> = NO_HOLDS,
272
279
  ): Ledger {
273
280
  const taken = new Set<string>();
274
281
  const verdicts: Verdict[] = [];
@@ -278,13 +285,21 @@ export function ledgerOf<F extends Finding>(
278
285
  const claimed =
279
286
  first === undefined ? [] : isPartKind(record.code) ? would.filter(open) : [first];
280
287
  if (claimed.length === 0) {
281
- verdicts.push(record.bound !== "" && dials.allows(record.code) ? "dormant" : "stale");
288
+ verdicts.push(
289
+ record.bound !== "" && (dials.allows(record.code) || holds.holdsRecord(record))
290
+ ? "dormant"
291
+ : "stale",
292
+ );
282
293
  continue;
283
294
  }
284
295
  for (const finding of claimed) {
285
296
  taken.add(claimKey(finding));
286
297
  }
287
- verdicts.push(claimed.every((finding) => dials.withholds(finding)) ? "dormant" : "in-effect");
298
+ verdicts.push(
299
+ claimed.every((finding) => dials.withholds(finding) || holds.withholds(finding))
300
+ ? "dormant"
301
+ : "in-effect",
302
+ );
288
303
  }
289
304
  return {
290
305
  verdicts,