@cplieger/deadset-ts 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +68 -50
  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 +230 -37
  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 +186 -0
  10. package/dist/src/conformance-record.js +2 -2
  11. package/dist/src/convention-rows.js +383 -0
  12. package/dist/src/conventions.js +456 -0
  13. package/dist/src/dependencies.js +30 -3
  14. package/dist/src/discover.js +11 -4
  15. package/dist/src/emit-map.js +72 -0
  16. package/dist/src/entry-point-gaps.js +81 -35
  17. package/dist/src/entry-points.js +47 -32
  18. package/dist/src/exempt.js +11 -3
  19. package/dist/src/findings/completion.js +19 -29
  20. package/dist/src/findings/emitters.js +22 -1
  21. package/dist/src/findings/unused-declarations.js +7 -2
  22. package/dist/src/graph.js +13 -5
  23. package/dist/src/inventory.js +4 -2
  24. package/dist/src/kinds.js +2 -2
  25. package/dist/src/manifest.js +37 -11
  26. package/dist/src/matrix.js +21 -13
  27. package/dist/src/partial.js +3 -1
  28. package/dist/src/pnpm-workspace.js +120 -0
  29. package/dist/src/position.js +17 -2
  30. package/dist/src/print.js +2 -0
  31. package/dist/src/references.js +209 -30
  32. package/dist/src/report.js +26 -6
  33. package/dist/src/reporters/annotations.js +10 -3
  34. package/dist/src/reporters/template.js +6 -3
  35. package/dist/src/resolve.js +31 -6
  36. package/dist/src/roots.js +37 -8
  37. package/dist/src/run.js +13 -3
  38. package/dist/src/schema.js +4 -2
  39. package/dist/src/session.js +141 -34
  40. package/dist/src/setup-failure.js +23 -0
  41. package/dist/src/source-files.js +4 -61
  42. package/dist/src/sweep.js +4 -2
  43. package/dist/src/template-field.js +38 -6
  44. package/dist/src/test-support.js +59 -0
  45. package/dist/src/type-errors.js +252 -0
  46. package/dist/src/type-query-alias.js +92 -0
  47. package/dist/src/verbs/analyze.js +24 -4
  48. package/dist/src/verbs/explain.js +4 -1
  49. package/dist/src/verbs/print-projects.js +17 -56
  50. package/dist/src/verbs/print-retained.js +7 -2
  51. package/dist/src/verbs/print-roots.js +7 -2
  52. package/dist/src/version-range.js +255 -0
  53. package/dist/src/version.js +1 -1
  54. package/dist/src/workspace-manifests.js +68 -0
  55. package/dist/src/workspace-programs.js +342 -0
  56. package/dist/src/workspace-resolution.js +307 -0
  57. package/dist/src/workspace-run.js +75 -0
  58. package/dist/src/workspace.js +358 -0
  59. package/jsr.json +1 -1
  60. package/package.json +2 -1
  61. package/src/analysis.ts +395 -71
  62. package/src/component-files.ts +718 -0
  63. package/src/component-layer.ts +160 -0
  64. package/src/config.ts +11 -2
  65. package/src/configuration-files.ts +235 -0
  66. package/src/conformance-record.ts +2 -2
  67. package/src/convention-rows.ts +440 -0
  68. package/src/conventions.ts +640 -0
  69. package/src/dependencies.ts +36 -2
  70. package/src/discover.ts +11 -7
  71. package/src/emit-map.ts +93 -0
  72. package/src/entry-point-gaps.ts +92 -38
  73. package/src/entry-points.ts +66 -36
  74. package/src/exempt.ts +21 -3
  75. package/src/findings/completion.ts +24 -33
  76. package/src/findings/emitters.ts +32 -1
  77. package/src/findings/unused-declarations.ts +9 -2
  78. package/src/graph.ts +15 -4
  79. package/src/host.ts +23 -0
  80. package/src/index.ts +1 -1
  81. package/src/inventory.ts +13 -2
  82. package/src/kinds.ts +2 -2
  83. package/src/manifest.ts +47 -14
  84. package/src/matrix.ts +43 -15
  85. package/src/partial.ts +6 -1
  86. package/src/pnpm-workspace.ts +135 -0
  87. package/src/position.ts +23 -2
  88. package/src/print.ts +2 -0
  89. package/src/references.ts +227 -28
  90. package/src/report.ts +56 -6
  91. package/src/reporters/annotations.ts +12 -3
  92. package/src/reporters/template.ts +6 -3
  93. package/src/resolve.ts +59 -6
  94. package/src/roots.ts +60 -9
  95. package/src/run.ts +15 -5
  96. package/src/schema.ts +4 -2
  97. package/src/session.ts +218 -37
  98. package/src/setup-failure.ts +39 -0
  99. package/src/source-files.ts +4 -66
  100. package/src/sweep.ts +9 -2
  101. package/src/template-field.ts +42 -7
  102. package/src/test-support.ts +77 -0
  103. package/src/type-errors.ts +340 -0
  104. package/src/type-query-alias.ts +133 -0
  105. package/src/verbs/analyze.ts +38 -4
  106. package/src/verbs/explain.ts +4 -1
  107. package/src/verbs/print-projects.ts +17 -69
  108. package/src/verbs/print-retained.ts +7 -2
  109. package/src/verbs/print-roots.ts +7 -2
  110. package/src/verbs/verb.ts +6 -3
  111. package/src/version-range.ts +309 -0
  112. package/src/version.ts +1 -1
  113. package/src/workspace-manifests.ts +100 -0
  114. package/src/workspace-programs.ts +486 -0
  115. package/src/workspace-resolution.ts +393 -0
  116. package/src/workspace-run.ts +148 -0
  117. package/src/workspace.ts +463 -0
@@ -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,34 @@ 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) {
173
+ const { modules, documents } = configurationFiles(host, targetRoot, files);
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
+ return found;
190
+ }
177
191
  /**
178
192
  * The setup files one test-runner configuration names: every literal under
179
193
  * `test.setupFiles` and `test.globalSetup`, read against the configuration's own
@@ -360,26 +374,27 @@ function workersOf(project, file, files) {
360
374
  return found;
361
375
  }
362
376
  /**
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.
377
+ * Every file of one project the rules enter, in no particular order and possibly more
378
+ * than once: a file two rules enter appears once per rule.
365
379
  *
366
380
  * `testFiles` is the configuration's test-file patterns, matched against each
367
381
  * file's path below the target root; a file one of them matches is a test file the
368
382
  * runner executes.
369
383
  */
370
- export function entryPoints(project, files, testFiles) {
371
- const found = [];
384
+ export function entryPoints(project, files, testFiles, host, targetRoot) {
385
+ const found = [...configurationEntries(host, targetRoot, files)];
372
386
  const tests = testFiles.map((pattern) => ({ pattern, expression: globExpression(pattern) }));
373
387
  for (const [path, file] of files.byPath) {
374
- const named = conventionOf(basename(path));
388
+ const name = basename(path);
389
+ const named = conventionOf(name);
375
390
  if (named !== undefined) {
376
391
  found.push({ file, rule: named.convention.rule, source: named.spelled, exports: true });
377
392
  if (named.convention.testRunner) {
378
393
  found.push(...setupFilesOf(file, files));
379
394
  }
380
- if (named.convention.rule === "browser-tests") {
381
- found.push(...browserTestsOf(file, files));
382
- }
395
+ }
396
+ if (isBrowserTestConfig(name)) {
397
+ found.push(...browserTestsOf(file, files));
383
398
  }
384
399
  for (const { pattern, expression } of tests) {
385
400
  if (expression.test(path)) {
@@ -108,6 +108,7 @@ export function computeExemptions(input, detectors, holding) {
108
108
  class: exemptionClass,
109
109
  detail: evidence.detail,
110
110
  site: evidence.site,
111
+ ...(evidence.whileLive === undefined ? {} : { whileLive: evidence.whileLive }),
111
112
  });
112
113
  }
113
114
  }
@@ -122,14 +123,14 @@ function compare(a, b) {
122
123
  }
123
124
  /**
124
125
  * 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.
126
+ * per declaration, class, detail and file it holds while live, the one at the first
127
+ * site. A fact found at many sites, or by several projects, is one record.
127
128
  */
128
129
  export function exemptionsOf(found) {
129
130
  const ordered = [...found].sort((a, b) => byPosition(a.site, b.site) || compare(a.class, b.class) || compare(a.id, b.id));
130
131
  const seen = new Set();
131
132
  return ordered.filter((record) => {
132
- const fact = [record.id, record.class, record.detail].join("\u0000");
133
+ const fact = [record.id, record.class, record.detail, record.whileLive ?? ""].join("\u0000");
133
134
  if (seen.has(fact)) {
134
135
  return false;
135
136
  }
@@ -137,6 +138,13 @@ export function exemptionsOf(found) {
137
138
  return true;
138
139
  });
139
140
  }
141
+ /**
142
+ * The records that hold while the component files at `live` are live: every record that
143
+ * holds whatever is live, and each one a live file's markup holds, as a record of the run.
144
+ */
145
+ export function holdingWhile(records, live) {
146
+ return exemptionsOf(records.flatMap(({ whileLive, ...record }) => whileLive === undefined || live.has(whileLive) ? [record] : []));
147
+ }
140
148
  /** The records per declaration, each declaration's in the order given. */
141
149
  function byDeclaration(records) {
142
150
  const grouped = new Map();
@@ -15,50 +15,40 @@ const RANK = { certain: 3, probable: 2, possible: 1 };
15
15
  function capped(found, ceiling) {
16
16
  return RANK[found] <= RANK[ceiling] ? found : ceiling;
17
17
  }
18
- /** The kind whose default turns on what the run knows about the target's consumers. */
19
- const UNUSED_EXPORTED = "DS1001";
20
18
  /** Whether the run loaded every consumer the scope declared, which holds of a scope declaring none. */
21
19
  function everyDeclaredLoaded(boundary) {
22
20
  const { declared, loaded } = boundary.consumers;
23
21
  return declared.every((id) => loaded.includes(id));
24
22
  }
25
- /**
26
- * Whether a library's published API may have a caller the run cannot see: the target is
27
- * a library, and the run either holds no consumer information or did not load a consumer
28
- * the scope declared. A configuration declaring the consumer set complete holds consumer
29
- * information whether or not it declares a consumer, because it says no other exists.
30
- */
31
- function openWorld(input) {
32
- const known = input.config.consumersComplete || input.boundary.consumers.declared.length > 0;
33
- return input.config.targetKind === "library" && !(known && everyDeclaredLoaded(input.boundary));
34
- }
35
23
  /**
36
24
  * The severity the configuration gives one code: the key naming the code, else the key
37
- * naming its family, else the code's default. The unused-exported kind defaults to
38
- * `allow` while a library's published API is open to callers the run cannot see.
25
+ * naming its family, else the code's default.
39
26
  */
40
27
  function severityOf(input, code, fallback) {
41
28
  const { config } = input;
42
- const named = config.severity.get(code) ?? config.severity.get(code.slice(0, FAMILY_KEY_LENGTH));
43
- if (named !== undefined) {
44
- return named;
45
- }
46
- return code === UNUSED_EXPORTED && openWorld(input) ? "allow" : fallback;
29
+ return (config.severity.get(code) ?? config.severity.get(code.slice(0, FAMILY_KEY_LENGTH)) ?? fallback);
30
+ }
31
+ /**
32
+ * The declarations the roots name as a library's published API, each public member of a
33
+ * published type included.
34
+ */
35
+ function publishedIds(union) {
36
+ return new Set(union.rooted
37
+ .filter((root) => root.kind === "published-api")
38
+ .flatMap((root) => union.symbols[root.at]?.id ?? []));
47
39
  }
48
40
  /**
49
41
  * The reachability class of one declaration of the run. A declaration of a library's
50
- * published API has callers outside the target: it is `certain` where the run loaded
51
- * every consumer the scope declared and declared at least one, `probable` where it
52
- * declared consumers and did not load them all, and `possible` where it declared none.
53
- * Whether the configuration declares the consumer set complete decides nothing here.
54
- * Every other declaration has every reference in the loaded program, a private member
55
- * and a `#private` name among them, and is `certain`.
42
+ * published API, a public member of a published type included, has callers outside the
43
+ * target: it is `certain` where the run loaded every consumer the scope declared and
44
+ * declared at least one, `probable` where it declared consumers and did not load them
45
+ * all, and `possible` where it declared none. Whether the configuration declares the
46
+ * consumer set complete decides nothing here. Every other declaration has every reference
47
+ * in the loaded program, a private member and a `#private` name among them, and is
48
+ * `certain`.
56
49
  */
57
50
  function reachabilityClasses(input) {
58
- const union = input.swept.matrix.union;
59
- const published = new Set(union.rooted
60
- .filter((root) => root.kind === "published-api")
61
- .map((root) => union.symbols[root.at]?.id));
51
+ const published = publishedIds(input.swept.matrix.union);
62
52
  const { declared } = input.boundary.consumers;
63
53
  const outside = declared.length === 0
64
54
  ? "possible"
@@ -1,5 +1,7 @@
1
1
  import { evaluateEdges } from "../edges.js";
2
+ import { liveFilesAt } from "../matrix.js";
2
3
  import { ledgerOf } from "../suppress.js";
4
+ import { insideSkipped } from "../type-errors.js";
3
5
  import { completed, dialsOf, reportable } from "./completion.js";
4
6
  import { dependenciesAndModuleMachinery } from "./dependencies-and-module-machinery.js";
5
7
  import { interfaces } from "./interfaces.js";
@@ -33,6 +35,21 @@ function heldRefs(input) {
33
35
  .filter((symbol) => held.has(symbol.id))
34
36
  .map((symbol) => symbol.ref));
35
37
  }
38
+ /**
39
+ * The references of every top-level declaration of a live component file. The file's
40
+ * markup may use each of them, which no analysis of the markup can rule out, so the run
41
+ * reports nothing about one while the file is live.
42
+ */
43
+ function markupRefs(input) {
44
+ const { swept } = input;
45
+ if (swept.componentFiles.length === 0) {
46
+ return new Set();
47
+ }
48
+ const live = new Set(liveFilesAt(swept.matrix, swept.sweep, new Set(swept.componentFiles)).map((symbol) => symbol.id));
49
+ return new Set(swept.matrix.union.symbols
50
+ .filter((symbol) => live.has(symbol.parent))
51
+ .map((symbol) => symbol.ref));
52
+ }
36
53
  /** Every family's findings over one sweep, and those of any further emitter, completed by one step. */
37
54
  function completedOver(input, ...more) {
38
55
  return completed(input, [...EMITTERS.values(), ...more].flatMap((emit) => emit(input)));
@@ -64,7 +81,11 @@ export function decidedFindings(input, suppressed = { unmarked: input, records:
64
81
  const would = [...unmarked, ...found];
65
82
  const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
66
83
  const unanswered = heldRefs(input);
67
- const kept = reportable(input, found.filter((finding) => !ledger.withheld(finding) && !unanswered.has(finding.symbol.ref)));
84
+ const markup = markupRefs(input);
85
+ const kept = reportable(input, found.filter((finding) => !ledger.withheld(finding) &&
86
+ !unanswered.has(finding.symbol.ref) &&
87
+ !markup.has(finding.symbol.ref) &&
88
+ !insideSkipped(input.swept.skipped, finding.position)));
68
89
  const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
69
90
  return {
70
91
  findings: evaluated.findings,
@@ -40,8 +40,9 @@ const TEST_OF_DEAD_CODE = "every target symbol this test references is reported
40
40
  /**
41
41
  * The code one candidate is reported under, or none, each symbol once under the most
42
42
  * specific code: a test of dead code first; no other test-file declaration, member of a
43
- * dead container or enum member nothing names; then deprecation, a test reference, and
44
- * the unreferenced codes, an unimportable unused export being the unreachable kind's.
43
+ * dead container or enum member nothing names, nor a test-support declaration test code
44
+ * references; then deprecation, a test reference, and the unreferenced codes, an
45
+ * unimportable unused export being the unreachable kind's.
45
46
  */
46
47
  function codeOf(judged) {
47
48
  const { candidate, symbol } = judged;
@@ -55,6 +56,9 @@ function codeOf(judged) {
55
56
  if (symbol.kind === "enum-member" && !referenced) {
56
57
  return undefined;
57
58
  }
59
+ if (judged.support && candidate.testRefs > 0) {
60
+ return undefined;
61
+ }
58
62
  if (candidate.productionRefs === 0 && judged.deprecated) {
59
63
  return "DS1006";
60
64
  }
@@ -138,6 +142,7 @@ function familyFindings(input, fallen) {
138
142
  candidate,
139
143
  symbol,
140
144
  test: union.test[at] === true,
145
+ support: union.support[at] === true,
141
146
  containerDead: containerDead && !fallen,
142
147
  deprecated: input.deprecated.has(symbol.id),
143
148
  unreachable: unreachable.has(symbol.id),
package/dist/src/graph.js CHANGED
@@ -32,20 +32,26 @@ const SWEPT = {
32
32
  };
33
33
  /**
34
34
  * Whether a root of each kind names a caller, which holds it live under both relations: a
35
- * tool reads the configuration it loads, a runtime runs a test file, a setup file, a
36
- * worker and a command, the manifest names what enters the package, and an entry file, a
37
- * configured root and a pattern assert a caller. A library's published API only supposes
35
+ * tool reads the configuration it loads and the files its strings name, a runtime runs a
36
+ * test file, a setup file, a worker, a command and a script, the manifest names what
37
+ * enters the package, an alias global stands for the export it names, and an entry file,
38
+ * a configured root and a pattern assert a caller. A library's published API only supposes
38
39
  * a consumer the run may not see, so it is live under reachability alone.
39
40
  */
40
41
  const NAMES_A_CALLER = {
41
42
  "entry-file": true,
43
+ convention: true,
42
44
  "manifest-entry": true,
43
45
  "manifest-binary": true,
46
+ script: true,
44
47
  "published-api": false,
45
48
  configured: true,
46
49
  pattern: true,
50
+ "type-error": true,
51
+ "type-query-alias": true,
52
+ "configuration-file": true,
53
+ "configuration-string": true,
47
54
  "test-runner": true,
48
- "lint-configuration": true,
49
55
  "mutation-testing": true,
50
56
  "browser-tests": true,
51
57
  worker: true,
@@ -65,7 +71,8 @@ export function namesACaller(kind) {
65
71
  * from such a declaration still counts toward its target, because reference counting
66
72
  * asks whether any declaration names the target. A root naming nothing is dropped.
67
73
  */
68
- export function graphOf(symbols, references, roots, testFiles) {
74
+ export function graphOf(symbols, references, roots, testFiles, supportFiles = []) {
75
+ const supports = new Set(supportFiles);
69
76
  const index = new Map(symbols.map((symbol, at) => [symbol.id, at]));
70
77
  const at = (id) => index.get(id) ?? OUTSIDE;
71
78
  const tests = new Set(testFiles);
@@ -114,6 +121,7 @@ export function graphOf(symbols, references, roots, testFiles) {
114
121
  made,
115
122
  parent,
116
123
  test: symbols.map((symbol) => tests.has(symbol.position.path)),
124
+ support: symbols.map((symbol) => supports.has(symbol.position.path)),
117
125
  subject: symbols.map((symbol) => swept(symbol.kind)),
118
126
  exportsOf,
119
127
  rooted,
@@ -1,6 +1,6 @@
1
1
  import { isClassDeclaration, isComputedPropertyName, isEnumDeclaration, isEnumMember, isExportAssignment, isExportDeclaration, isFunctionDeclaration, isGetAccessorDeclaration, isIdentifier, isInterfaceDeclaration, isIntersectionTypeNode, isMethodDeclaration, isMethodSignatureDeclaration, isModuleBlock, isModuleDeclaration, isNamespaceExport, isParenthesizedTypeNode, isPrivateIdentifier, isPropertyDeclaration, isPropertySignatureDeclaration, isSetAccessorDeclaration, isTypeAliasDeclaration, isTypeLiteralNode, isUnionTypeNode, isVariableStatement, ModifierFlags, } from "@typescript/native/unstable/ast";
2
2
  import { dirnamePath, joinPath, relativePath } from "./paths.js";
3
- import { byPosition, positionKey, renderPosition } from "./position.js";
3
+ import { byPosition, positionKey, renderPosition, writtenLength, } from "./position.js";
4
4
  import { computedComponent, nameComponent, renderRef } from "./ref.js";
5
5
  import { isAnswered, UNANSWERED } from "./query.js";
6
6
  const MANIFEST = "package.json";
@@ -273,7 +273,9 @@ export function inventory(project, host, targetRoot) {
273
273
  typeParameter: undefined,
274
274
  });
275
275
  exportScopes.push({ owner: fileRecord, nameNode: file });
276
- walkStatements(file.statements, fileRecord);
276
+ // A statement a component file's module appends is not written in the file.
277
+ const written = writtenLength(file);
278
+ walkStatements(file.statements.filter((statement) => statement.end <= written), fileRecord);
277
279
  }
278
280
  /** One declaration of a module, a namespace or a container body. */
279
281
  function walkStatements(statements, owner) {
package/dist/src/kinds.js CHANGED
@@ -39,7 +39,7 @@ const ROWS = [
39
39
  code: "DS1004",
40
40
  name: "test-only-use",
41
41
  languages: ["go", "ts"],
42
- rule: "A symbol with zero production references and at least one test reference: an unused-exported, unused-unexported or unused-member candidate whose test reference count is not zero, reported once under this code. A reference from a consumer's test files is a test reference unless the configuration counts consumer tests as production.",
42
+ rule: "A symbol with zero production references and at least one test reference: an unused-exported, unused-unexported or unused-member candidate whose test reference count is not zero, reported once under this code. A reference from a consumer's test files is a test reference unless the configuration counts consumer tests as production. A reference from test-support code, a package or file that only test code imports, is a test reference, and a declaration of test-support code is never reported under this code.",
43
43
  defaultSeverity: "deny",
44
44
  maxClass: "certain",
45
45
  fixability: "deletable",
@@ -278,7 +278,7 @@ const ROWS = [
278
278
  name: "unused-parameter",
279
279
  languages: ["go", "ts"],
280
280
  rule: "A parameter with no reference inside its function body, on a function whose signature is free to change.",
281
- precondition: "The signature must be free, defined as follows: the function is not a method retained by interface satisfaction, is not used as a value, is not a go:linkname or cgo target, and is not a stub whose body is empty or only panics. A parameter its body never names is dead whatever the callers, so a published declaration of a library is reported too, with the fixability the vocabulary gives the kind: the signature change is a breaking change.",
281
+ precondition: "The signature must be free, defined as follows: the function is not a method retained by interface satisfaction, is not used as a value, is not a go:linkname or cgo target, is not a function the Go test driver runs (a test, benchmark or fuzz test of a test file, or TestMain), and is not a stub whose body is empty or only panics. A parameter its body never names is dead whatever the callers, so a published declaration of a library is reported too, with the fixability the vocabulary gives the kind: the signature change is a breaking change.",
282
282
  defaultSeverity: "warn",
283
283
  maxClass: "certain",
284
284
  fixability: "manual",