@cplieger/deadset-ts 4.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/component-mapper.js +5 -0
  3. package/dist/bin/mapper-connection.js +101 -0
  4. package/dist/bin/node-host.js +24 -2
  5. package/dist/src/analysis.js +301 -41
  6. package/dist/src/component-files.js +612 -0
  7. package/dist/src/component-layer.js +132 -0
  8. package/dist/src/config.js +9 -2
  9. package/dist/src/configuration-files.js +290 -0
  10. package/dist/src/conformance-record.js +3 -9
  11. package/dist/src/contextual-uses.js +240 -0
  12. package/dist/src/convention-rows.js +389 -0
  13. package/dist/src/conventions.js +565 -0
  14. package/dist/src/dependencies.js +36 -3
  15. package/dist/src/destructuring.js +6 -6
  16. package/dist/src/discover.js +10 -3
  17. package/dist/src/emit-map.js +72 -0
  18. package/dist/src/entry-point-gaps.js +81 -35
  19. package/dist/src/entry-points.js +50 -32
  20. package/dist/src/exempt-classes.js +6 -1
  21. package/dist/src/exempt.js +32 -20
  22. package/dist/src/findings/completion.js +46 -32
  23. package/dist/src/findings/emitters.js +22 -1
  24. package/dist/src/findings/interfaces.js +6 -2
  25. package/dist/src/findings/reads-and-writes.js +53 -9
  26. package/dist/src/findings/unused-declarations.js +47 -3
  27. package/dist/src/generated-file.js +24 -0
  28. package/dist/src/graph.js +13 -5
  29. package/dist/src/interface-satisfaction.js +2 -68
  30. package/dist/src/inventory.js +8 -3
  31. package/dist/src/kinds.js +6 -5
  32. package/dist/src/manifest.js +37 -11
  33. package/dist/src/matrix.js +21 -13
  34. package/dist/src/partial.js +3 -1
  35. package/dist/src/pnpm-workspace.js +120 -0
  36. package/dist/src/position.js +17 -2
  37. package/dist/src/print.js +2 -0
  38. package/dist/src/query.js +15 -0
  39. package/dist/src/references.js +404 -36
  40. package/dist/src/report.js +26 -6
  41. package/dist/src/reporters/annotations.js +10 -3
  42. package/dist/src/reporters/template.js +3 -0
  43. package/dist/src/resolve.js +37 -11
  44. package/dist/src/roots.js +37 -8
  45. package/dist/src/run.js +13 -3
  46. package/dist/src/schema.js +2 -0
  47. package/dist/src/session.js +141 -34
  48. package/dist/src/setup-failure.js +23 -0
  49. package/dist/src/source-files.js +4 -61
  50. package/dist/src/stores.js +3 -0
  51. package/dist/src/sweep.js +4 -2
  52. package/dist/src/template-field.js +38 -6
  53. package/dist/src/test-support.js +59 -0
  54. package/dist/src/type-errors.js +252 -0
  55. package/dist/src/type-query-alias.js +92 -0
  56. package/dist/src/value-flow.js +74 -0
  57. package/dist/src/verbs/analyze.js +24 -4
  58. package/dist/src/verbs/explain.js +4 -1
  59. package/dist/src/verbs/print-projects.js +17 -56
  60. package/dist/src/verbs/print-retained.js +7 -2
  61. package/dist/src/verbs/print-roots.js +7 -2
  62. package/dist/src/version-range.js +255 -0
  63. package/dist/src/version.js +1 -1
  64. package/dist/src/workspace-manifests.js +68 -0
  65. package/dist/src/workspace-programs.js +342 -0
  66. package/dist/src/workspace-resolution.js +307 -0
  67. package/dist/src/workspace-run.js +75 -0
  68. package/dist/src/workspace.js +358 -0
  69. package/jsr.json +1 -1
  70. package/package.json +3 -2
  71. package/src/analysis.ts +475 -72
  72. package/src/component-files.ts +718 -0
  73. package/src/component-layer.ts +160 -0
  74. package/src/config.ts +11 -2
  75. package/src/configuration-files.ts +370 -0
  76. package/src/conformance-record.ts +3 -10
  77. package/src/contextual-uses.ts +354 -0
  78. package/src/convention-rows.ts +448 -0
  79. package/src/conventions.ts +788 -0
  80. package/src/dependencies.ts +49 -2
  81. package/src/destructuring.ts +16 -6
  82. package/src/discover.ts +10 -6
  83. package/src/emit-map.ts +93 -0
  84. package/src/entry-point-gaps.ts +92 -38
  85. package/src/entry-points.ts +79 -36
  86. package/src/exempt-classes.ts +6 -1
  87. package/src/exempt.ts +69 -20
  88. package/src/file-facts.ts +2 -0
  89. package/src/findings/completion.ts +55 -36
  90. package/src/findings/emitters.ts +32 -1
  91. package/src/findings/interfaces.ts +9 -2
  92. package/src/findings/reads-and-writes.ts +70 -9
  93. package/src/findings/unused-declarations.ts +60 -3
  94. package/src/generated-file.ts +27 -0
  95. package/src/graph.ts +15 -4
  96. package/src/host.ts +23 -0
  97. package/src/index.ts +1 -1
  98. package/src/interface-satisfaction.ts +1 -92
  99. package/src/inventory.ts +17 -3
  100. package/src/kinds.ts +7 -5
  101. package/src/manifest.ts +47 -14
  102. package/src/matrix.ts +43 -15
  103. package/src/partial.ts +6 -1
  104. package/src/pnpm-workspace.ts +135 -0
  105. package/src/position.ts +23 -2
  106. package/src/print.ts +2 -0
  107. package/src/query.ts +26 -0
  108. package/src/references.ts +472 -40
  109. package/src/report.ts +56 -6
  110. package/src/reporters/annotations.ts +12 -3
  111. package/src/reporters/template.ts +3 -0
  112. package/src/resolve.ts +65 -11
  113. package/src/roots.ts +70 -9
  114. package/src/run.ts +15 -5
  115. package/src/schema.ts +2 -0
  116. package/src/session.ts +218 -37
  117. package/src/setup-failure.ts +39 -0
  118. package/src/source-files.ts +4 -66
  119. package/src/stores.ts +9 -0
  120. package/src/sweep.ts +9 -2
  121. package/src/template-field.ts +42 -7
  122. package/src/test-support.ts +77 -0
  123. package/src/type-errors.ts +340 -0
  124. package/src/type-query-alias.ts +133 -0
  125. package/src/value-flow.ts +104 -0
  126. package/src/verbs/analyze.ts +38 -4
  127. package/src/verbs/explain.ts +4 -1
  128. package/src/verbs/print-projects.ts +17 -69
  129. package/src/verbs/print-retained.ts +7 -2
  130. package/src/verbs/print-roots.ts +7 -2
  131. package/src/verbs/verb.ts +6 -3
  132. package/src/version-range.ts +309 -0
  133. package/src/version.ts +1 -1
  134. package/src/workspace-manifests.ts +100 -0
  135. package/src/workspace-programs.ts +486 -0
  136. package/src/workspace-resolution.ts +393 -0
  137. package/src/workspace-run.ts +148 -0
  138. package/src/workspace.ts +463 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Where the compiler writes what it compiles: the output extension each source
3
+ * extension becomes, and the output path one source path is written to. A path a
4
+ * manifest or a document names is read back to its source through this one rule,
5
+ * whether the path is the target's own or a workspace package's.
6
+ */
7
+ import { dirnamePath, joinPath, relativePath } from "./paths.js";
8
+ /** The source extensions the compiler emits each output extension from. */
9
+ const EMITTED = [
10
+ { emitted: ".d.ts", sources: [".ts", ".tsx"] },
11
+ { emitted: ".d.mts", sources: [".mts"] },
12
+ { emitted: ".d.cts", sources: [".cts"] },
13
+ { emitted: ".js", sources: [".ts", ".tsx"] },
14
+ { emitted: ".jsx", sources: [".tsx"] },
15
+ { emitted: ".mjs", sources: [".mts"] },
16
+ { emitted: ".cjs", sources: [".cts"] },
17
+ ];
18
+ /** One path with its extension replaced, where it carries the one named. */
19
+ function reExtended(path, emitted, source) {
20
+ return path.endsWith(emitted) ? path.slice(0, path.length - emitted.length) + source : undefined;
21
+ }
22
+ /**
23
+ * The longest directory every path shares, which is where the compiler roots its
24
+ * output when the configuration declares no root directory and its directory is not
25
+ * the root.
26
+ */
27
+ export function commonDirectory(paths) {
28
+ const first = paths[0];
29
+ if (first === undefined) {
30
+ return "";
31
+ }
32
+ let common = dirnamePath(first).split("/");
33
+ for (const path of paths.slice(1)) {
34
+ const segments = dirnamePath(path).split("/");
35
+ let shared = 0;
36
+ while (shared < common.length &&
37
+ shared < segments.length &&
38
+ common[shared] === segments[shared]) {
39
+ shared += 1;
40
+ }
41
+ common = common.slice(0, shared);
42
+ }
43
+ return common.join("/");
44
+ }
45
+ /**
46
+ * The source paths one written path may be compiled from, in the order they are
47
+ * tried: for each output extension the path carries, the path re-extended where it
48
+ * stands, then read below each source directory where it is below the directory that
49
+ * output is written to.
50
+ */
51
+ export function emittedFrom(path, layout) {
52
+ const found = [];
53
+ for (const { emitted, sources } of EMITTED) {
54
+ if (!path.endsWith(emitted)) {
55
+ continue;
56
+ }
57
+ const emitDir = emitted.startsWith(".d.")
58
+ ? (layout.declarationDir ?? layout.outDir)
59
+ : layout.outDir;
60
+ const below = emitDir === undefined ? undefined : relativePath(emitDir, path);
61
+ const mapped = below === undefined ? [] : layout.sourceDirs.map((dir) => joinPath(dir, below));
62
+ for (const source of sources) {
63
+ for (const from of [path, ...mapped]) {
64
+ const candidate = reExtended(from, emitted, source);
65
+ if (candidate !== undefined) {
66
+ found.push(candidate);
67
+ }
68
+ }
69
+ }
70
+ }
71
+ return found;
72
+ }
@@ -7,15 +7,18 @@
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
- /** What is declined for a tool none of whose conventions is entered. */
11
- const EVERY_CONVENTION = "its configuration and every file it loads by its own convention";
12
- /** Why a tool none of whose conventions is entered declines them. */
13
- const UNREAD = "No rule reads it, so a file only it loads is reported as never imported unless " +
14
- "ts.entry_files or roots.patterns names it.";
15
- /** The tools none of whose conventions is entered. */
10
+ /** What is declined for a tool whose own conventions are entered only through its configuration files. */
11
+ const EVERY_CONVENTION = "every file it loads by its own convention beyond its configuration files";
12
+ /** Why a tool whose own conventions are not read declines them. */
13
+ const UNREAD = "No rule reads it beyond a configuration file the file-name form or a string enters, so a " +
14
+ "file only it loads is reported as never imported unless ts.entry_files or roots.patterns " +
15
+ "names it.";
16
+ /** Why a configuration's file names that are not relative literals are declined. */
17
+ const NOT_RELATIVE = "A string of a configuration file names a file only where it is a literal starting with ./ " +
18
+ "or ../, so a file named otherwise is reported as never imported.";
19
+ /** The tools whose own conventions are not read. */
16
20
  const UNREAD_TOOLS = [
17
21
  "angular",
18
- "astro",
19
22
  "astro-db",
20
23
  "astro-markdoc",
21
24
  "astro-og-canvas",
@@ -48,7 +51,6 @@ const UNREAD_TOOLS = [
48
51
  "esbuild",
49
52
  "eve",
50
53
  "execa",
51
- "expo",
52
54
  "expressive-code",
53
55
  "fast",
54
56
  "fumadocs",
@@ -85,7 +87,6 @@ const UNREAD_TOOLS = [
85
87
  "nano-staged",
86
88
  "nest",
87
89
  "netlify",
88
- "next",
89
90
  "next-intl",
90
91
  "next-mdx",
91
92
  "nitro",
@@ -93,7 +94,6 @@ const UNREAD_TOOLS = [
93
94
  "node-modules-inspector",
94
95
  "nodemon",
95
96
  "npm-package-json-lint",
96
- "nuxt",
97
97
  "nuxtjs-i18n",
98
98
  "nx",
99
99
  "nyc",
@@ -118,17 +118,14 @@ const UNREAD_TOOLS = [
118
118
  "prettier",
119
119
  "prisma",
120
120
  "quasar",
121
- "qwik",
122
121
  "railway",
123
122
  "raycast",
124
123
  "react-cosmos",
125
124
  "react-email",
126
125
  "react-native",
127
- "react-router",
128
126
  "relay",
129
127
  "release-it",
130
128
  "remark",
131
- "remix",
132
129
  "rolldown",
133
130
  "rollup",
134
131
  "rsbuild",
@@ -144,11 +141,9 @@ const UNREAD_TOOLS = [
144
141
  "sst",
145
142
  "starlight",
146
143
  "stencil",
147
- "storybook",
148
144
  "stylelint",
149
145
  "svelte",
150
146
  "sveltejs-package",
151
- "sveltekit",
152
147
  "svgo",
153
148
  "svgr",
154
149
  "swc",
@@ -171,7 +166,6 @@ const UNREAD_TOOLS = [
171
166
  "typescript-content-mapper",
172
167
  "unbuild",
173
168
  "unocss",
174
- "unplugin-auto-import",
175
169
  "unplugin-icons",
176
170
  "unplugin-vue-components",
177
171
  "unplugin-vue-i18n",
@@ -201,9 +195,15 @@ const UNREAD_TOOLS = [
201
195
  const PARTLY_ENTERED = [
202
196
  {
203
197
  tool: "eslint",
204
- convention: "the legacy .eslintrc configuration files",
205
- reason: "Only the flat configuration's file names are entered, so a legacy configuration " +
206
- "file the program holds is reported as never imported.",
198
+ convention: "a legacy configuration written in YAML or in JSON with comments",
199
+ reason: "Only a configuration module and a strict JSON configuration file are read, so a " +
200
+ "dependency only such a file names is reported as unused.",
201
+ },
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
207
  },
208
208
  {
209
209
  tool: "playwright",
@@ -213,8 +213,8 @@ const PARTLY_ENTERED = [
213
213
  },
214
214
  {
215
215
  tool: "playwright",
216
- convention: "the globalSetup and globalTeardown files",
217
- reason: "No rule enters them, so a setup file only the runner loads is reported as never imported.",
216
+ convention: "a globalSetup or globalTeardown file named without a leading ./ or ../",
217
+ reason: NOT_RELATIVE,
218
218
  },
219
219
  {
220
220
  tool: "playwright",
@@ -222,16 +222,64 @@ const PARTLY_ENTERED = [
222
222
  reason: "Only the configuration object's own testDir and testMatch are read, so a test " +
223
223
  "directory a project entry moves is not entered.",
224
224
  },
225
+ {
226
+ tool: "expo",
227
+ convention: "an app directory the expo-router entry of an app configuration moves",
228
+ reason: "The convention row reads the app directory at its default places only, so a route " +
229
+ "below a moved directory is reported as never imported unless ts.entry_files names it.",
230
+ },
231
+ {
232
+ tool: "next",
233
+ convention: "a page extension next.config adds through pageExtensions",
234
+ reason: "The convention row roots the default extensions only, so a route file of another " +
235
+ "extension is reported as never imported unless ts.entry_files names it.",
236
+ },
237
+ {
238
+ tool: "nuxt",
239
+ convention: "the files of a layer below layers/ beyond its nuxt.config",
240
+ reason: "The convention row reads the project's own directories, so a layer's pages, plugins " +
241
+ "and server routes are reported as never imported unless ts.entry_files names them.",
242
+ },
243
+ {
244
+ tool: "qwik",
245
+ convention: "the routes of @qwik.dev/router",
246
+ reason: "No convention row holds for that package, so its route files are reported as " +
247
+ "never imported unless ts.entry_files names them.",
248
+ },
249
+ {
250
+ tool: "react-router",
251
+ convention: "a route module app/routes.ts names outside the routes directory",
252
+ reason: "The convention row roots the routes directory, and a string of routes.ts names no " +
253
+ "file, so a route module elsewhere is reported as never imported unless " +
254
+ "ts.entry_files names it.",
255
+ },
256
+ {
257
+ tool: "storybook",
258
+ convention: "a story the stories member of .storybook/main names outside *.stories.* files",
259
+ reason: "The convention row roots *.stories.* files, so a story file named otherwise is " +
260
+ "reported as never imported unless ts.entry_files names it.",
261
+ },
225
262
  {
226
263
  tool: "stryker",
227
- convention: "a configuration file named on the command line",
228
- reason: "Only the file names the tool looks for by default are entered.",
264
+ convention: "a configuration file named on a command line no package.json script writes",
265
+ reason: "Only the file names the tool looks for by default and the files a script names are entered.",
266
+ },
267
+ {
268
+ tool: "unplugin-auto-import",
269
+ convention: "an import its generated declarations add for a component's template alone",
270
+ reason: "Only a global declared as an alias of an export is read, so an export the generated " +
271
+ "declarations name only for templates is reported as unused.",
229
272
  },
230
273
  {
231
274
  tool: "vite",
232
- convention: "its configuration and every entry point it names",
233
- reason: "Only a worker a call addresses by a literal is entered. No rule reads the " +
234
- "configuration, so a file only it names is reported as never imported.",
275
+ convention: "an entry point its configuration names without a leading ./ or ../, or computes",
276
+ reason: NOT_RELATIVE,
277
+ },
278
+ {
279
+ tool: "vite",
280
+ convention: "the module scripts an index.html loads",
281
+ reason: "No rule reads an HTML file, so a script only index.html loads is reported as never " +
282
+ "imported unless ts.entry_files names it.",
235
283
  },
236
284
  {
237
285
  tool: "vitest",
@@ -241,15 +289,14 @@ const PARTLY_ENTERED = [
241
289
  },
242
290
  {
243
291
  tool: "vitest",
244
- convention: "the mock files below a __mocks__ directory",
245
- reason: "No rule enters a file by its directory, so a mock only the runner's module mocking " +
246
- "loads is reported as never imported.",
292
+ convention: "the setup files of a configuration that moves its root",
293
+ reason: "A string is read against the configuration file's directory, so a setup file named " +
294
+ "against a root the configuration moves is not entered.",
247
295
  },
248
296
  {
249
297
  tool: "vitest",
250
- convention: "the setup files of a configuration that moves its root",
251
- reason: "A configuration writing root reads its setup files against a directory its text " +
252
- "may not state, so none of them is entered.",
298
+ convention: "a setup file named without a leading ./ or ../",
299
+ reason: NOT_RELATIVE,
253
300
  },
254
301
  {
255
302
  tool: "vitest",
@@ -259,9 +306,8 @@ const PARTLY_ENTERED = [
259
306
  },
260
307
  {
261
308
  tool: "webpack",
262
- convention: "its configuration and every entry point it names",
263
- reason: "Only a worker a call addresses by a literal is entered. No rule reads the " +
264
- "configuration, so a file only it names is reported as never imported.",
309
+ convention: "an entry point its configuration names without a leading ./ or ../, or computes",
310
+ reason: NOT_RELATIVE,
265
311
  },
266
312
  ];
267
313
  /** Two strings ordered bytewise. */
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The files a tool or the platform enters without any import naming them: the
3
- * configuration files the test runner, the linter, the mutation tester and the
4
- * browser-test runner load by their file name, the test files the runner executes,
5
- * and a worker a call addresses by a string literal.
3
+ * configuration files a tool loads by their file name and the files their strings
4
+ * name, the test files the runner executes, and a worker a call addresses by a string
5
+ * literal.
6
6
  *
7
7
  * Each rule is read from the program and nothing else: a file name the tool
8
8
  * itself looks for, a literal written in a configuration file, or a literal
@@ -12,45 +12,31 @@
12
12
  */
13
13
  import { isArrayLiteralExpression, isAsExpression, isCallExpression, isExportAssignment, isIdentifier, isMetaProperty, isNewExpression, isNoSubstitutionTemplateLiteral, isObjectLiteralExpression, isParenthesizedExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, } from "@typescript/native/unstable/ast";
14
14
  import { SymbolFlags } from "@typescript/native/unstable/sync";
15
+ import { configurationFiles, filesNamedBy, moduleStrings, } from "./configuration-files.js";
15
16
  import { globExpression } from "./glob.js";
16
17
  import { dirnamePath, relativePath, resolvePath } from "./paths.js";
17
18
  import { UNANSWERED } from "./query.js";
18
19
  /** The extensions the test runner and the browser-test runner load a configuration from. */
19
20
  const MODULE_EXTENSIONS = ["js", "mjs", "cjs", "ts", "mts", "cts"];
20
- /** The extensions the linter's flat configuration and the mutation tester's are loaded from. */
21
+ /** The extensions the mutation tester's configuration is loaded from. */
21
22
  const SCRIPT_EXTENSIONS = ["js", "mjs", "cjs"];
23
+ /** The tool file names the configuration form does not cover. */
22
24
  const CONVENTIONS = [
23
25
  {
24
26
  rule: "test-runner",
25
- stems: ["vitest.config", "vitest.workspace", "vitest.projects"],
27
+ stems: ["vitest.workspace", "vitest.projects"],
26
28
  extensions: MODULE_EXTENSIONS,
27
29
  testRunner: true,
28
30
  },
29
- {
30
- rule: "lint-configuration",
31
- stems: ["eslint.config"],
32
- extensions: MODULE_EXTENSIONS,
33
- testRunner: false,
34
- },
35
31
  {
36
32
  rule: "mutation-testing",
37
- stems: ["stryker.config", "stryker.conf", ".stryker.config", ".stryker.conf"],
33
+ stems: ["stryker.conf", ".stryker.config", ".stryker.conf"],
38
34
  extensions: SCRIPT_EXTENSIONS,
39
35
  testRunner: false,
40
36
  },
41
- {
42
- rule: "mutation-testing",
43
- stems: ["vitest.stryker.config"],
44
- extensions: MODULE_EXTENSIONS,
45
- testRunner: true,
46
- },
47
- {
48
- rule: "browser-tests",
49
- stems: ["playwright.config"],
50
- extensions: MODULE_EXTENSIONS,
51
- testRunner: false,
52
- },
53
37
  ];
38
+ /** The stem of the browser-test runner's configuration, whose test directory is read. */
39
+ const BROWSER_TEST_CONFIG = "playwright.config";
54
40
  /** The browser-test runner's own test-file pattern, which holds where a configuration names none. */
55
41
  const BROWSER_TEST_MATCH = "**/*.{spec,test}.{js,ts,jsx,tsx,cjs,cts,cjsx,ctsx,mjs,mts,mjsx,mtsx}";
56
42
  /** The members of a test-runner configuration's `test` object that name setup files. */
@@ -174,6 +160,37 @@ function filesNamed(files, dir, text) {
174
160
  }
175
161
  return files.named(resolvePath(dir, pathPart(text)));
176
162
  }
163
+ /** Whether one file name is the browser-test runner's configuration. */
164
+ function isBrowserTestConfig(name) {
165
+ return MODULE_EXTENSIONS.some((ext) => name === `${BROWSER_TEST_CONFIG}.${ext}`);
166
+ }
167
+ /**
168
+ * The files the configuration files of one project enter: each configuration module,
169
+ * with what it exports, and each own file a relative string of a configuration module
170
+ * or a JSON configuration file names, read against that file's directory.
171
+ */
172
+ function configurationEntries(host, targetRoot, files, parse) {
173
+ const { modules, documents, outside } = configurationFiles(host, targetRoot, files, parse);
174
+ const found = [];
175
+ const strings = (dir, texts) => {
176
+ for (const text of texts) {
177
+ for (const file of filesNamedBy(files, dir, text)) {
178
+ found.push({ file, rule: "configuration-string", source: text, exports: true });
179
+ }
180
+ }
181
+ };
182
+ for (const { file, form } of modules) {
183
+ found.push({ file, rule: "configuration-file", source: form, exports: true });
184
+ strings(dirnamePath(file.fileName), moduleStrings(file).map((one) => one.text));
185
+ }
186
+ for (const document of documents) {
187
+ strings(dirnamePath(document.path), document.strings);
188
+ }
189
+ for (const { file } of outside) {
190
+ strings(dirnamePath(file.fileName), moduleStrings(file).map((one) => one.text));
191
+ }
192
+ return found;
193
+ }
177
194
  /**
178
195
  * The setup files one test-runner configuration names: every literal under
179
196
  * `test.setupFiles` and `test.globalSetup`, read against the configuration's own
@@ -360,26 +377,27 @@ function workersOf(project, file, files) {
360
377
  return found;
361
378
  }
362
379
  /**
363
- * Every file of one project the lifted rules enter, in no particular order and
364
- * possibly more than once: a file two rules enter appears once per rule.
380
+ * Every file of one project the rules enter, in no particular order and possibly more
381
+ * than once: a file two rules enter appears once per rule.
365
382
  *
366
383
  * `testFiles` is the configuration's test-file patterns, matched against each
367
384
  * file's path below the target root; a file one of them matches is a test file the
368
385
  * runner executes.
369
386
  */
370
- export function entryPoints(project, files, testFiles) {
371
- const found = [];
387
+ export function entryPoints(project, files, testFiles, host, targetRoot, parse) {
388
+ const found = [...configurationEntries(host, targetRoot, files, parse)];
372
389
  const tests = testFiles.map((pattern) => ({ pattern, expression: globExpression(pattern) }));
373
390
  for (const [path, file] of files.byPath) {
374
- const named = conventionOf(basename(path));
391
+ const name = basename(path);
392
+ const named = conventionOf(name);
375
393
  if (named !== undefined) {
376
394
  found.push({ file, rule: named.convention.rule, source: named.spelled, exports: true });
377
395
  if (named.convention.testRunner) {
378
396
  found.push(...setupFilesOf(file, files));
379
397
  }
380
- if (named.convention.rule === "browser-tests") {
381
- found.push(...browserTestsOf(file, files));
382
- }
398
+ }
399
+ if (isBrowserTestConfig(name)) {
400
+ found.push(...browserTestsOf(file, files));
383
401
  }
384
402
  for (const { pattern, expression } of tests) {
385
403
  if (expression.test(path)) {
@@ -26,7 +26,12 @@ export const EXEMPTION_CLASSES = [
26
26
  confidence: "certain",
27
27
  typescriptVisibility: { private: false, privateName: false },
28
28
  },
29
- { class: "generated-file", languages: ["go"], confidence: "certain" },
29
+ {
30
+ class: "generated-file",
31
+ languages: ["go", "ts"],
32
+ confidence: "certain",
33
+ typescriptVisibility: { private: true, privateName: false },
34
+ },
30
35
  { class: "linkname-cgo-asm-plugin", languages: ["go"], confidence: "certain" },
31
36
  {
32
37
  class: "template-field",
@@ -88,8 +88,13 @@ function detected(exemptionClass, detect, input) {
88
88
  * test file makes is none there.
89
89
  */
90
90
  export function computeExemptions(input, detectors, holding) {
91
+ return detectExemptions(input, detectors, holding).records;
92
+ }
93
+ /** The records of {@link computeExemptions}, beside the test-file evidence it set aside. */
94
+ export function detectExemptions(input, detectors, holding) {
91
95
  const visibility = new Map(input.held.symbols.map((symbol) => [symbol.id, symbol.visibility]));
92
96
  const found = [];
97
+ const inTestFiles = [];
93
98
  for (const exemptionClass of TS_EXEMPTION_CLASSES) {
94
99
  const detect = detectors.get(exemptionClass);
95
100
  if (detect === undefined || holding.disabled.has(exemptionClass)) {
@@ -100,18 +105,21 @@ export function computeExemptions(input, detectors, holding) {
100
105
  if (!retainable(allowed, visibility.get(evidence.id))) {
101
106
  continue;
102
107
  }
103
- if (holding.mode.production && holding.testFiles.has(evidence.site.path)) {
104
- continue;
105
- }
106
- found.push({
108
+ const record = {
107
109
  id: evidence.id,
108
110
  class: exemptionClass,
109
111
  detail: evidence.detail,
110
112
  site: evidence.site,
111
- });
113
+ ...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
114
+ };
115
+ if (holding.mode.production && holding.testFiles.has(evidence.site.path)) {
116
+ inTestFiles.push(record);
117
+ continue;
118
+ }
119
+ found.push(record);
112
120
  }
113
121
  }
114
- return exemptionsOf(found);
122
+ return { records: exemptionsOf(found), inTestFiles };
115
123
  }
116
124
  /** Two strings ordered bytewise. */
117
125
  function compare(a, b) {
@@ -122,14 +130,14 @@ function compare(a, b) {
122
130
  }
123
131
  /**
124
132
  * Records as a run holds them: ordered by site, then class, then declaration, and one
125
- * per declaration, class and detail, the one at the first site. A fact found at many
126
- * sites, or by several projects, is one record.
133
+ * per declaration, class, detail and file it holds while live, the one at the first
134
+ * site. A fact found at many sites, or by several projects, is one record.
127
135
  */
128
136
  export function exemptionsOf(found) {
129
137
  const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || compare(a.class, b.class) || compare(a.id, b.id));
130
138
  const seen = new Set();
131
139
  return ordered.filter((record) => {
132
- const fact = [record.id, record.class, record.detail].join("\u0000");
140
+ const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
133
141
  if (seen.has(fact)) {
134
142
  return false;
135
143
  }
@@ -137,6 +145,13 @@ export function exemptionsOf(found) {
137
145
  return true;
138
146
  });
139
147
  }
148
+ /**
149
+ * The records that hold while the component files at `live` are live: every record that
150
+ * holds whatever is live, and each one a live file's markup holds, as a record of the run.
151
+ */
152
+ export function holdingWhile(records, live) {
153
+ return exemptionsOf(records.flatMap(({ whileLive, ...record }) => whileLive === undefined || live.has(whileLive) ? [record] : []));
154
+ }
140
155
  /** The records per declaration, each declaration's in the order given. */
141
156
  function byDeclaration(records) {
142
157
  const grouped = new Map();
@@ -152,24 +167,21 @@ function byDeclaration(records) {
152
167
  return grouped;
153
168
  }
154
169
  /**
155
- * The records that held back a declaration some configuration of the run would
156
- * otherwise judge dead: each configuration's graph is swept once more with no
157
- * exemption and the marks it was given, and a record is retained when one of those
158
- * sweeps judged its declaration a candidate. A record that holds a declaration back in
159
- * one configuration is retained whatever another configuration answers about it, as a
160
- * run of that configuration alone would report the declaration without it. A record
161
- * on a declaration that a relation or a mark holds live in every configuration held
162
- * nothing back. The order is the declarations' site order, and for one declaration
163
- * the input's.
170
+ * The records that held back a declaration: one some configuration's graph, swept
171
+ * again with no exemption and its marks, judges a candidate, whatever another
172
+ * configuration answers, or one in `writeOnly`, which the run would report as written
173
+ * and never read without the record's read. A declaration a relation or a mark holds
174
+ * live in every configuration is held back by no record. The order is the
175
+ * declarations' site order, and for one declaration the input's.
164
176
  */
165
- export function retainedIn(matrix, input) {
177
+ export function retainedIn(matrix, input, writeOnly = new Set()) {
166
178
  const exempt = input.exempt ?? [];
167
179
  if (exempt.length === 0) {
168
180
  return [];
169
181
  }
170
182
  const bare = { marked: input.marked, mode: input.mode };
171
183
  const dead = new Set(matrix.graphs.flatMap((graph) => sweep(graph, bare).candidates.map((candidate) => candidate.id)));
172
- const held = byDeclaration(exempt.filter((record) => dead.has(record.id)));
184
+ const held = byDeclaration(exempt.filter((record) => dead.has(record.id) || writeOnly.has(record.id)));
173
185
  return matrix.union.symbols.flatMap((symbol) => held.get(symbol.id) ?? []);
174
186
  }
175
187
  /**