@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.
- package/README.md +68 -50
- package/dist/bin/component-mapper.js +5 -0
- package/dist/bin/mapper-connection.js +101 -0
- package/dist/bin/node-host.js +24 -2
- package/dist/src/analysis.js +230 -37
- package/dist/src/component-files.js +612 -0
- package/dist/src/component-layer.js +132 -0
- package/dist/src/config.js +9 -2
- package/dist/src/configuration-files.js +186 -0
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/convention-rows.js +383 -0
- package/dist/src/conventions.js +456 -0
- package/dist/src/dependencies.js +30 -3
- package/dist/src/discover.js +11 -4
- package/dist/src/emit-map.js +72 -0
- package/dist/src/entry-point-gaps.js +81 -35
- package/dist/src/entry-points.js +47 -32
- package/dist/src/exempt.js +11 -3
- package/dist/src/findings/completion.js +19 -29
- package/dist/src/findings/emitters.js +22 -1
- package/dist/src/findings/unused-declarations.js +7 -2
- package/dist/src/graph.js +13 -5
- package/dist/src/inventory.js +4 -2
- package/dist/src/kinds.js +2 -2
- package/dist/src/manifest.js +37 -11
- package/dist/src/matrix.js +21 -13
- package/dist/src/partial.js +3 -1
- package/dist/src/pnpm-workspace.js +120 -0
- package/dist/src/position.js +17 -2
- package/dist/src/print.js +2 -0
- package/dist/src/references.js +209 -30
- package/dist/src/report.js +26 -6
- package/dist/src/reporters/annotations.js +10 -3
- package/dist/src/reporters/template.js +6 -3
- package/dist/src/resolve.js +31 -6
- package/dist/src/roots.js +37 -8
- package/dist/src/run.js +13 -3
- package/dist/src/schema.js +4 -2
- package/dist/src/session.js +141 -34
- package/dist/src/setup-failure.js +23 -0
- package/dist/src/source-files.js +4 -61
- package/dist/src/sweep.js +4 -2
- package/dist/src/template-field.js +38 -6
- package/dist/src/test-support.js +59 -0
- package/dist/src/type-errors.js +252 -0
- package/dist/src/type-query-alias.js +92 -0
- package/dist/src/verbs/analyze.js +24 -4
- package/dist/src/verbs/explain.js +4 -1
- package/dist/src/verbs/print-projects.js +17 -56
- package/dist/src/verbs/print-retained.js +7 -2
- package/dist/src/verbs/print-roots.js +7 -2
- package/dist/src/version-range.js +255 -0
- package/dist/src/version.js +1 -1
- package/dist/src/workspace-manifests.js +68 -0
- package/dist/src/workspace-programs.js +342 -0
- package/dist/src/workspace-resolution.js +307 -0
- package/dist/src/workspace-run.js +75 -0
- package/dist/src/workspace.js +358 -0
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +395 -71
- package/src/component-files.ts +718 -0
- package/src/component-layer.ts +160 -0
- package/src/config.ts +11 -2
- package/src/configuration-files.ts +235 -0
- package/src/conformance-record.ts +2 -2
- package/src/convention-rows.ts +440 -0
- package/src/conventions.ts +640 -0
- package/src/dependencies.ts +36 -2
- package/src/discover.ts +11 -7
- package/src/emit-map.ts +93 -0
- package/src/entry-point-gaps.ts +92 -38
- package/src/entry-points.ts +66 -36
- package/src/exempt.ts +21 -3
- package/src/findings/completion.ts +24 -33
- package/src/findings/emitters.ts +32 -1
- package/src/findings/unused-declarations.ts +9 -2
- package/src/graph.ts +15 -4
- package/src/host.ts +23 -0
- package/src/index.ts +1 -1
- package/src/inventory.ts +13 -2
- package/src/kinds.ts +2 -2
- package/src/manifest.ts +47 -14
- package/src/matrix.ts +43 -15
- package/src/partial.ts +6 -1
- package/src/pnpm-workspace.ts +135 -0
- package/src/position.ts +23 -2
- package/src/print.ts +2 -0
- package/src/references.ts +227 -28
- package/src/report.ts +56 -6
- package/src/reporters/annotations.ts +12 -3
- package/src/reporters/template.ts +6 -3
- package/src/resolve.ts +59 -6
- package/src/roots.ts +60 -9
- package/src/run.ts +15 -5
- package/src/schema.ts +4 -2
- package/src/session.ts +218 -37
- package/src/setup-failure.ts +39 -0
- package/src/source-files.ts +4 -66
- package/src/sweep.ts +9 -2
- package/src/template-field.ts +42 -7
- package/src/test-support.ts +77 -0
- package/src/type-errors.ts +340 -0
- package/src/type-query-alias.ts +133 -0
- package/src/verbs/analyze.ts +38 -4
- package/src/verbs/explain.ts +4 -1
- package/src/verbs/print-projects.ts +17 -69
- package/src/verbs/print-retained.ts +7 -2
- package/src/verbs/print-roots.ts +7 -2
- package/src/verbs/verb.ts +6 -3
- package/src/version-range.ts +309 -0
- package/src/version.ts +1 -1
- package/src/workspace-manifests.ts +100 -0
- package/src/workspace-programs.ts +486 -0
- package/src/workspace-resolution.ts +393 -0
- package/src/workspace-run.ts +148 -0
- 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
|
|
11
|
-
const EVERY_CONVENTION = "
|
|
12
|
-
/** Why a tool
|
|
13
|
-
const UNREAD = "No rule reads it
|
|
14
|
-
"ts.entry_files or roots.patterns
|
|
15
|
-
|
|
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: "
|
|
205
|
-
reason: "Only
|
|
206
|
-
"
|
|
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: "
|
|
217
|
-
reason:
|
|
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
|
|
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
|
|
233
|
-
reason:
|
|
234
|
-
|
|
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
|
|
245
|
-
reason: "
|
|
246
|
-
"
|
|
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: "
|
|
251
|
-
reason:
|
|
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
|
|
263
|
-
reason:
|
|
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. */
|
package/dist/src/entry-points.js
CHANGED
|
@@ -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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
364
|
-
*
|
|
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
|
|
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
|
-
|
|
381
|
-
|
|
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)) {
|
package/dist/src/exempt.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
51
|
-
*
|
|
52
|
-
* declared
|
|
53
|
-
*
|
|
54
|
-
* Every other declaration has every reference
|
|
55
|
-
* and a `#private` name among them, and is
|
|
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
|
|
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
|
|
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
|
|
44
|
-
*
|
|
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
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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,
|
package/dist/src/inventory.js
CHANGED
|
@@ -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
|
-
|
|
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",
|