@cplieger/deadset-ts 8.0.0-dev.15 → 8.0.0-dev.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/alias-chain.js +0 -5
- package/dist/src/amd.js +81 -0
- package/dist/src/analysis.js +94 -54
- package/dist/src/augmentations.js +23 -24
- package/dist/src/bindings.js +382 -0
- package/dist/src/calls.js +25 -8
- package/dist/src/commands/read.js +6 -7
- package/dist/src/commands/tools.js +5 -9
- package/dist/src/component-files.js +3 -2
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/consumers.js +9 -10
- package/dist/src/contextual-uses.js +1 -17
- package/dist/src/contract/config.generated.js +5 -2
- package/dist/src/contract/contract.generated.js +119 -2
- package/dist/src/contract/report.generated.js +6 -2
- package/dist/src/dependencies.js +98 -71
- package/dist/src/destructuring.js +1 -19
- package/dist/src/element-access.js +179 -164
- package/dist/src/entries/build-scripts.js +222 -0
- package/dist/src/{configuration-files.js → entries/configuration-files.js} +22 -64
- package/dist/src/{convention-rows.js → entries/convention-rows.js} +120 -27
- package/dist/src/entries/conventions.js +739 -0
- package/dist/src/{html-entries.js → entries/html-entries.js} +9 -12
- package/dist/src/{mdx-entries.js → entries/mdx-entries.js} +3 -3
- package/dist/src/entries/run-directories.js +77 -0
- package/dist/src/entries/sources.js +144 -0
- package/dist/src/entries/static-module.js +670 -0
- package/dist/src/entries/test-runners.js +684 -0
- package/dist/src/{workflow-steps.js → entries/workflow-steps.js} +18 -8
- package/dist/src/extensions.js +37 -0
- package/dist/src/facts.js +38 -0
- package/dist/src/findings/completion.js +40 -4
- package/dist/src/findings/emitters.js +5 -8
- package/dist/src/{implementations.js → findings/implementations.js} +3 -3
- package/dist/src/{in-operands.js → findings/in-operands.js} +2 -2
- package/dist/src/findings/self-check.js +36 -20
- package/dist/src/findings-pass.js +5 -6
- package/dist/src/framework-lifecycle.js +42 -15
- package/dist/src/fs/manifest.js +15 -20
- package/dist/src/generated-file.js +172 -29
- package/dist/src/graph.js +14 -8
- package/dist/src/import-bindings.js +73 -28
- package/dist/src/index/sites.js +557 -0
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +523 -478
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +1 -1
- package/dist/src/inventory/commonjs.js +62 -17
- package/dist/src/inventory/enumerate.js +2 -1
- package/dist/src/javascript-file.js +26 -11
- package/dist/src/keyof-comparisons.js +124 -103
- package/dist/src/load/classify.js +28 -9
- package/dist/src/load/diagnostics.js +8 -1
- package/dist/src/load/discover.js +15 -7
- package/dist/src/load/expected-module.js +2 -2
- package/dist/src/load/plan.js +2 -2
- package/dist/src/load/specifiers.js +2 -4
- package/dist/src/load/synthesize.js +10 -5
- package/dist/src/load/workspace-manifests.js +1 -1
- package/dist/src/load/workspace-resolution.js +2 -2
- package/dist/src/matrix.js +12 -17
- package/dist/src/module-declarations.js +70 -85
- package/dist/src/overrides.js +134 -112
- package/dist/src/paths.js +56 -1
- package/dist/src/platform-calls.js +78 -0
- package/dist/src/platform-symbols.js +134 -0
- package/dist/src/references/read-rule.js +1 -0
- package/dist/src/references/read-rules.js +48 -0
- package/dist/src/references/sink.js +151 -0
- package/dist/src/references/sources.js +450 -0
- package/dist/src/references.js +111 -1116
- package/dist/src/resolve.js +1 -1
- package/dist/src/{configured-declarations.js → roots/configured-declarations.js} +7 -4
- package/dist/src/{entry-point-gaps.js → roots/entry-point-gaps.js} +5 -8
- package/dist/src/{manifest.js → roots/manifest.js} +4 -4
- package/dist/src/roots/published-reach.js +412 -0
- package/dist/src/roots/root-set.js +489 -0
- package/dist/src/roots/unseen-loads.js +236 -0
- package/dist/src/serialization-contract.js +9 -4
- package/dist/src/source-files.js +5 -9
- package/dist/src/stores.js +4 -2
- package/dist/src/suppress.js +30 -32
- package/dist/src/sweep.js +25 -13
- package/dist/src/symbol-keys.js +89 -83
- package/dist/src/tests/file-class.js +76 -0
- package/dist/src/{outside-tests.js → tests/outside.js} +7 -7
- package/dist/src/{test-support.js → tests/support.js} +5 -12
- package/dist/src/type-parameter-reads.js +178 -139
- package/dist/src/unimplemented-callees.js +262 -242
- package/dist/src/verbs/analyze.js +15 -3
- package/dist/src/verbs/describe.js +5 -3
- package/dist/src/verbs/explain.js +50 -28
- package/dist/src/verbs/print-retained.js +2 -2
- package/jsr.json +1 -1
- package/package.json +3 -3
- package/src/alias-chain.ts +0 -7
- package/src/amd.ts +130 -0
- package/src/analysis.ts +156 -70
- package/src/augmentations.ts +41 -27
- package/src/automatic-types.ts +1 -1
- package/src/bindings.ts +460 -0
- package/src/calls.ts +25 -7
- package/src/commands/read.ts +6 -8
- package/src/commands/tools.ts +5 -11
- package/src/component-files.ts +4 -2
- package/src/conformance-record.ts +1 -1
- package/src/consumers.ts +12 -11
- package/src/contextual-uses.ts +2 -22
- package/src/contract/config.generated.ts +5 -2
- package/src/contract/contract.generated.ts +138 -2
- package/src/contract/report.generated.ts +6 -2
- package/src/dependencies.ts +117 -71
- package/src/destructuring.ts +1 -31
- package/src/element-access.ts +188 -182
- package/src/entries/build-scripts.ts +292 -0
- package/src/{configuration-files.ts → entries/configuration-files.ts} +33 -82
- package/src/{convention-rows.ts → entries/convention-rows.ts} +167 -28
- package/src/{conventions.ts → entries/conventions.ts} +349 -523
- package/src/{html-entries.ts → entries/html-entries.ts} +19 -18
- package/src/{mdx-entries.ts → entries/mdx-entries.ts} +5 -5
- package/src/entries/run-directories.ts +108 -0
- package/src/entries/sources.ts +266 -0
- package/src/entries/static-module.ts +858 -0
- package/src/entries/test-runners.ts +868 -0
- package/src/{workflow-steps.ts → entries/workflow-steps.ts} +29 -11
- package/src/exempt.ts +3 -2
- package/src/extensions.ts +45 -0
- package/src/facts.ts +46 -0
- package/src/file-facts.ts +1 -1
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +43 -4
- package/src/findings/emitter.ts +1 -1
- package/src/findings/emitters.ts +14 -10
- package/src/{implementations.ts → findings/implementations.ts} +4 -4
- package/src/{in-operands.ts → findings/in-operands.ts} +3 -3
- package/src/findings/self-check.ts +41 -22
- package/src/findings/visibility-narrowing.ts +1 -1
- package/src/findings-pass.ts +5 -12
- package/src/framework-lifecycle.ts +43 -15
- package/src/fs/manifest.ts +25 -21
- package/src/generated-file.ts +240 -36
- package/src/graph.ts +27 -11
- package/src/import-bindings.ts +98 -29
- package/src/index/sites.ts +781 -0
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +570 -527
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +1 -1
- package/src/inventory/commonjs.ts +102 -17
- package/src/inventory/enumerate.ts +2 -1
- package/src/javascript-file.ts +29 -12
- package/src/keyof-comparisons.ts +131 -116
- package/src/load/classify.ts +49 -13
- package/src/load/diagnostics.ts +11 -1
- package/src/load/discover.ts +27 -11
- package/src/load/expected-module.ts +8 -2
- package/src/load/plan.ts +11 -2
- package/src/load/specifiers.ts +8 -5
- package/src/load/synthesize.ts +12 -6
- package/src/load/test-globals.ts +1 -1
- package/src/load/workspace-manifests.ts +1 -1
- package/src/load/workspace-resolution.ts +9 -2
- package/src/load/workspace-run.ts +1 -1
- package/src/matrix.ts +13 -17
- package/src/module-declarations.ts +74 -98
- package/src/overrides.ts +162 -134
- package/src/paths.ts +65 -1
- package/src/platform-calls.ts +119 -0
- package/src/platform-symbols.ts +189 -0
- package/src/references/read-rule.ts +60 -0
- package/src/references/read-rules.ts +55 -0
- package/src/references/sink.ts +213 -0
- package/src/references/sources.ts +632 -0
- package/src/references.ts +174 -1492
- package/src/report.ts +2 -2
- package/src/resolve.ts +1 -1
- package/src/{configured-declarations.ts → roots/configured-declarations.ts} +10 -7
- package/src/{entry-point-gaps.ts → roots/entry-point-gaps.ts} +5 -8
- package/src/{manifest.ts → roots/manifest.ts} +5 -5
- package/src/roots/published-reach.ts +481 -0
- package/src/{roots.ts → roots/root-set.ts} +203 -630
- package/src/roots/unseen-loads.ts +345 -0
- package/src/serialization-contract.ts +9 -4
- package/src/source-files.ts +5 -10
- package/src/stores.ts +13 -2
- package/src/suppress.ts +48 -33
- package/src/sweep.ts +37 -14
- package/src/symbol-keys.ts +102 -102
- package/src/tests/file-class.ts +118 -0
- package/src/{outside-tests.ts → tests/outside.ts} +16 -10
- package/src/{test-support.ts → tests/support.ts} +15 -21
- package/src/type-parameter-reads.ts +187 -158
- package/src/unimplemented-callees.ts +297 -280
- package/src/verbs/analyze.ts +19 -5
- package/src/verbs/describe.ts +5 -2
- package/src/verbs/explain.ts +75 -32
- package/src/verbs/print-retained.ts +2 -2
- package/dist/src/build-scripts.js +0 -418
- package/dist/src/conventions.js +0 -907
- package/dist/src/entry-points.js +0 -444
- package/dist/src/own-body.js +0 -28
- package/dist/src/roots.js +0 -865
- package/dist/src/unseen-loads.js +0 -189
- package/src/build-scripts.ts +0 -536
- package/src/entry-points.ts +0 -584
- package/src/own-body.ts +0 -42
- package/src/unseen-loads.ts +0 -282
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* it runs in. A workflow or hook manager file is read by `text/yaml.ts`, and one it does not
|
|
5
5
|
* read gives no command; each command is read by `commands/read.ts`.
|
|
6
6
|
*/
|
|
7
|
-
import { pathsNamed, readCommands } from "
|
|
8
|
-
import { isRecord } from "
|
|
9
|
-
import { joinPath, resolvePath } from "
|
|
10
|
-
import { filesIn } from "
|
|
11
|
-
import { yamlDocument } from "
|
|
7
|
+
import { pathsNamed, readCommands } from "../commands/read.js";
|
|
8
|
+
import { isRecord } from "../json-document.js";
|
|
9
|
+
import { joinPath, resolvePath } from "../paths.js";
|
|
10
|
+
import { filesIn } from "../fs/tree.js";
|
|
11
|
+
import { yamlDocument } from "../text/yaml.js";
|
|
12
12
|
const WORKFLOWS = ".github/workflows";
|
|
13
13
|
/** The directories whose every file is a git hook script, run as the shell runs it. */
|
|
14
14
|
const HOOK_DIRS = [".husky", ".githooks"];
|
|
@@ -144,14 +144,24 @@ export function commandFileTexts(host, targetRoot) {
|
|
|
144
144
|
...[makefileAt(host, root) ?? []].flat(),
|
|
145
145
|
].flatMap((path) => readText(host, path) ?? []);
|
|
146
146
|
}
|
|
147
|
+
/** Reads the target root's workflow steps, git hooks, hook managers and makefile. */
|
|
148
|
+
export function readWorkflows(host, targetRoot) {
|
|
149
|
+
return {
|
|
150
|
+
readings: workflowCommands(host, targetRoot).map(({ text, dir, writtenDirectory }) => {
|
|
151
|
+
const reading = readCommands(text, dir);
|
|
152
|
+
return { ...reading, held: [reading.held, writtenDirectory ?? ""].join("\n") };
|
|
153
|
+
}),
|
|
154
|
+
texts: commandFileTexts(host, targetRoot),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
147
157
|
/**
|
|
148
158
|
* The own files the target root's workflow steps, git hooks and recipes run or name, as
|
|
149
159
|
* `pathsNamed` reads each command.
|
|
150
160
|
*/
|
|
151
|
-
export function workflowEntries(
|
|
161
|
+
export function workflowEntries(workflows, files) {
|
|
152
162
|
const found = [];
|
|
153
|
-
for (const
|
|
154
|
-
for (const { path, written } of pathsNamed(
|
|
163
|
+
for (const reading of workflows.readings) {
|
|
164
|
+
for (const { path, written } of pathsNamed(reading)) {
|
|
155
165
|
for (const file of files.named(path)) {
|
|
156
166
|
found.push({ file, source: written });
|
|
157
167
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** The extension lists the analysis reads files by, each written once. */
|
|
2
|
+
/**
|
|
3
|
+
* The extensions tried, in order, after a path written without one: a relative specifier
|
|
4
|
+
* of a configuration file (`roots.5`) and a path a command runs or loads (`roots.13`).
|
|
5
|
+
*/
|
|
6
|
+
export const APPENDED_EXTENSIONS = [
|
|
7
|
+
".ts",
|
|
8
|
+
".tsx",
|
|
9
|
+
".mts",
|
|
10
|
+
".cts",
|
|
11
|
+
".js",
|
|
12
|
+
".jsx",
|
|
13
|
+
".mjs",
|
|
14
|
+
".cjs",
|
|
15
|
+
];
|
|
16
|
+
/** The extensions of a module a tool runs or loads as its configuration (`roots.2`). */
|
|
17
|
+
export const MODULE_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"];
|
|
18
|
+
/** The extensions of a file a build writes from a source file. */
|
|
19
|
+
export const OUTPUT_EXTENSIONS = [
|
|
20
|
+
".js",
|
|
21
|
+
".mjs",
|
|
22
|
+
".cjs",
|
|
23
|
+
".jsx",
|
|
24
|
+
".d.ts",
|
|
25
|
+
".d.mts",
|
|
26
|
+
".d.cts",
|
|
27
|
+
];
|
|
28
|
+
/** One list as a glob's brace list, its dots dropped: `{js,mjs}`. */
|
|
29
|
+
export function braced(extensions) {
|
|
30
|
+
return `{${extensions.map((one) => one.slice(1)).join(",")}}`;
|
|
31
|
+
}
|
|
32
|
+
/** The extension of one list a name ends with, the longest where several do. */
|
|
33
|
+
export function extensionOf(name, extensions) {
|
|
34
|
+
return extensions
|
|
35
|
+
.filter((one) => name.endsWith(one))
|
|
36
|
+
.reduce((longest, one) => (one.length > (longest?.length ?? 0) ? one : longest), undefined);
|
|
37
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The facts document `--facts` writes, `bindings.1` and `bindings.2`: the run's generated
|
|
3
|
+
* files under the one decision the generated-file class reads, and the binding identities
|
|
4
|
+
* they publish. No binding state is computed, so it states `evaluated: false` (`bindings.10`).
|
|
5
|
+
*/
|
|
6
|
+
import { FACTS_VERSION } from "./contract/contract.generated.js";
|
|
7
|
+
import { LANGUAGE, PRODUCT } from "./product.js";
|
|
8
|
+
import { bytewise } from "./text/compare.js";
|
|
9
|
+
/** The facts document of one run, as the text written to the path `--facts` names. */
|
|
10
|
+
export function factsDocument(version, generated, bindings) {
|
|
11
|
+
const document = {
|
|
12
|
+
facts_version: FACTS_VERSION,
|
|
13
|
+
analyzer: { name: PRODUCT, version },
|
|
14
|
+
language: LANGUAGE,
|
|
15
|
+
evaluated: false,
|
|
16
|
+
generated: [...generated]
|
|
17
|
+
.sort(([a], [b]) => bytewise(a, b))
|
|
18
|
+
.map(([path, fact]) => ({
|
|
19
|
+
path,
|
|
20
|
+
line: fact.line,
|
|
21
|
+
evidence: fact.evidence,
|
|
22
|
+
detail: fact.detail,
|
|
23
|
+
...(fact.generator === undefined
|
|
24
|
+
? {}
|
|
25
|
+
: {
|
|
26
|
+
generator: {
|
|
27
|
+
row: fact.generator.row,
|
|
28
|
+
...(fact.generator.version === undefined
|
|
29
|
+
? {}
|
|
30
|
+
: { version: fact.generator.version }),
|
|
31
|
+
},
|
|
32
|
+
schema_files: fact.generator.schemaFiles,
|
|
33
|
+
}),
|
|
34
|
+
})),
|
|
35
|
+
bindings,
|
|
36
|
+
};
|
|
37
|
+
return `${JSON.stringify(document, null, 2)}\n`;
|
|
38
|
+
}
|
|
@@ -79,12 +79,13 @@ function testedByIn(input) {
|
|
|
79
79
|
* published API, a public member of a published type included, is `certain` where the
|
|
80
80
|
* run loaded every consumer the scope declared and declared one, `probable` where one did
|
|
81
81
|
* not load, and `possible` where it declared none, whatever the configuration says of the
|
|
82
|
-
* consumer set. A member an `in` expression tests by name,
|
|
83
|
-
* analysis fills, is `possible`.
|
|
82
|
+
* consumer set. A member an `in` expression tests by name, one a function outside the
|
|
83
|
+
* analysis fills, and one {@link possiblyLoaded} names, is `possible`.
|
|
84
84
|
*/
|
|
85
85
|
function reachabilityClasses(input) {
|
|
86
86
|
const tested = testedByIn(input);
|
|
87
87
|
const filled = input.boundary.filledOutside ?? new Set();
|
|
88
|
+
const possible = possiblyLoaded(input);
|
|
88
89
|
const published = publishedIds(input.swept.matrix.union);
|
|
89
90
|
const { declared } = input.boundary.consumers;
|
|
90
91
|
const outside = declared.length === 0
|
|
@@ -93,7 +94,7 @@ function reachabilityClasses(input) {
|
|
|
93
94
|
? "certain"
|
|
94
95
|
: "probable";
|
|
95
96
|
return (id) => {
|
|
96
|
-
if (tested.has(id) || filled.has(id)) {
|
|
97
|
+
if (tested.has(id) || filled.has(id) || possible.has(id)) {
|
|
97
98
|
return "possible";
|
|
98
99
|
}
|
|
99
100
|
return published.has(id) ? outside : "certain";
|
|
@@ -137,6 +138,35 @@ function testCeilings(input, classOf) {
|
|
|
137
138
|
? testTargets(union, id).reduce((lowest, target) => capped(lowest, classOf(target)), "certain")
|
|
138
139
|
: "certain";
|
|
139
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* What only a bundler's retry of an import Node leaves unresolved names (program.38), and each
|
|
143
|
+
* dead declaration that reaches through dead declarations, as confidence.20 reads what only
|
|
144
|
+
* tests reach: each is dead only while no loader the analysis does not see loads the file.
|
|
145
|
+
*/
|
|
146
|
+
function possiblyLoaded(input) {
|
|
147
|
+
const seeds = input.boundary.possiblyLoaded ?? new Set();
|
|
148
|
+
if (seeds.size === 0) {
|
|
149
|
+
return seeds;
|
|
150
|
+
}
|
|
151
|
+
const union = input.swept.matrix.union;
|
|
152
|
+
const dead = new Set(input.swept.sweep.candidates.map((one) => one.id));
|
|
153
|
+
const reached = new Set();
|
|
154
|
+
const pending = union.symbols.flatMap((symbol, at) => (seeds.has(symbol.id) ? [at] : []));
|
|
155
|
+
for (let at = pending.pop(); at !== undefined; at = pending.pop()) {
|
|
156
|
+
const symbol = union.symbols[at];
|
|
157
|
+
if (symbol === undefined || reached.has(symbol.id)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
reached.add(symbol.id);
|
|
161
|
+
for (const edge of union.out[at] ?? []) {
|
|
162
|
+
const to = union.symbols[edge.to];
|
|
163
|
+
if (to !== undefined && dead.has(to.id)) {
|
|
164
|
+
pending.push(edge.to);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return reached;
|
|
169
|
+
}
|
|
140
170
|
/**
|
|
141
171
|
* The subjects dead only because production code only tests use is: each declaration the
|
|
142
172
|
* sweep marks test-used, each `DS1602` file of such code, and each test of dead code every
|
|
@@ -281,6 +311,7 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
281
311
|
const union = swept.matrix.union;
|
|
282
312
|
const positions = new Map(union.symbols.map((symbol, at) => [declarationKey(symbol.ref, symbol.position.path), at]));
|
|
283
313
|
const candidates = new Map(swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
314
|
+
const fileIds = new Map(union.symbols.flatMap((one) => (one.kind === "file" ? [[one.position.path, one.id]] : [])));
|
|
284
315
|
const classOf = reachabilityClasses(input);
|
|
285
316
|
const ceilingOf = testCeilings(input, classOf);
|
|
286
317
|
const testUsed = testUsedSubjects(input);
|
|
@@ -365,7 +396,12 @@ export function completed(input, findings, kinds = KINDS) {
|
|
|
365
396
|
const counts = candidate === undefined
|
|
366
397
|
? union.made[at]
|
|
367
398
|
: { production: candidate.productionRefs, test: candidate.testRefs };
|
|
368
|
-
const ownClass = judged
|
|
399
|
+
const ownClass = judged !== undefined
|
|
400
|
+
? classOf(judged.id)
|
|
401
|
+
: (finding.reachabilityClass ??
|
|
402
|
+
(finding.code === CODE.fileNeverImported
|
|
403
|
+
? classOf(fileIds.get(finding.position.path) ?? "")
|
|
404
|
+
: "certain"));
|
|
369
405
|
const used = judged !== undefined
|
|
370
406
|
? candidate !== undefined && testUsed.declarations.has(judged.id)
|
|
371
407
|
: finding.code === CODE.fileNeverImported && testUsed.files.has(finding.position.path);
|
|
@@ -96,16 +96,13 @@ function atPossible(input) {
|
|
|
96
96
|
*/
|
|
97
97
|
function dialledOver(input, suppressed, found, unmarked) {
|
|
98
98
|
const would = [...unmarked, ...found];
|
|
99
|
-
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked));
|
|
100
99
|
const unanswered = heldKeys(input);
|
|
100
|
+
const heldIds = new Set(input.swept.heldByUnanswered);
|
|
101
|
+
const keyOf = (finding) => declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
|
|
102
|
+
const held = (finding) => unanswered.has(keyOf(finding)) || insideSkipped(input.swept.skipped, finding.position);
|
|
103
|
+
const ledger = ledgerOf(suppressed.records, would, dialsOver(input, found, suppressed, unmarked), { withholds: held, holdsRecord: (record) => heldIds.has(record.bound) });
|
|
101
104
|
const markup = markupKeys(input);
|
|
102
|
-
const candidates = found.filter((finding) =>
|
|
103
|
-
const key = declarationKey(finding.symbol.holder ?? finding.symbol.ref, finding.position.path);
|
|
104
|
-
return (!ledger.withheld(finding) &&
|
|
105
|
-
!unanswered.has(key) &&
|
|
106
|
-
!markup.has(key) &&
|
|
107
|
-
!insideSkipped(input.swept.skipped, finding.position));
|
|
108
|
-
});
|
|
105
|
+
const candidates = found.filter((finding) => !ledger.withheld(finding) && !held(finding) && !markup.has(keyOf(finding)));
|
|
109
106
|
const kept = reportable(input, candidates);
|
|
110
107
|
const evaluated = evaluateEdges(kept, input.boundary.edges, input.swept.matrix.union.symbols);
|
|
111
108
|
return {
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { isClassDeclaration, isHeritageClause, isInterfaceDeclaration, isMethodDeclaration, SyntaxKind, } from "@typescript/native/ast";
|
|
9
9
|
import { SymbolFlags } from "@typescript/native/sync";
|
|
10
|
-
import { aliasChains } from "
|
|
11
|
-
import { nodeKey } from "
|
|
12
|
-
import { must, Unanswerable } from "
|
|
10
|
+
import { aliasChains } from "../alias-chain.js";
|
|
11
|
+
import { nodeKey } from "../inventory.js";
|
|
12
|
+
import { must, Unanswerable } from "../query.js";
|
|
13
13
|
/** The interfaces, the classes and the `implements` entries of one project's own files. */
|
|
14
14
|
function walk(project, held) {
|
|
15
15
|
const interfaces = [];
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* links the read to a declaration, so only a text match can tell which member it reads.
|
|
5
5
|
*/
|
|
6
6
|
import { isBinaryExpression, isNoSubstitutionTemplateLiteral, isStringLiteral, SyntaxKind, } from "@typescript/native/ast";
|
|
7
|
-
import { sourceFilesOf } from "
|
|
8
|
-
import { bytewise } from "
|
|
7
|
+
import { sourceFilesOf } from "../source-files.js";
|
|
8
|
+
import { bytewise } from "../text/compare.js";
|
|
9
9
|
/**
|
|
10
10
|
* The text of each string literal, or template literal with no substitution, written as
|
|
11
11
|
* the left operand of an `in` expression in a file of the project that is no test file.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { CODE } from "../contract/contract.generated.js";
|
|
1
|
+
import { CODE, REMEDIES } from "../contract/contract.generated.js";
|
|
2
2
|
import { REPOSITORY_DOCUMENT } from "../config.js";
|
|
3
3
|
import { FIXED_SEVERITY_CODES } from "../kinds.js";
|
|
4
|
-
import {
|
|
4
|
+
import { names, subjectOfCode, } from "../suppress.js";
|
|
5
5
|
import { bytewise } from "../text/compare.js";
|
|
6
6
|
/** The subject kind of a finding about a configured declaration. */
|
|
7
7
|
const DECLARATION_SUBJECT = "configured-declaration";
|
|
@@ -24,6 +24,39 @@ export function fileRefsOf(symbols) {
|
|
|
24
24
|
.map((symbol) => [symbol.position.path, symbol.ref]));
|
|
25
25
|
return (path) => byPath.get(path);
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Why a record of `mechanism` naming `codes` can match nothing, one clause per subject whose
|
|
29
|
+
* findings kinds.json's remedies table lets no record of that mechanism name, in subject order.
|
|
30
|
+
*/
|
|
31
|
+
function unnameable(codes, mechanism) {
|
|
32
|
+
return Object.keys(REMEDIES).flatMap((subject) => {
|
|
33
|
+
const named = codes.filter((code) => subjectOfCode(code) === subject);
|
|
34
|
+
if (named.length === 0 || names(subject, mechanism)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const { names: what, suppressions } = REMEDIES[subject];
|
|
38
|
+
const reports = `${spelled(named)} ${named.length === 1 ? "reports" : "each report"} ${what}`;
|
|
39
|
+
if (suppressions.length > 0) {
|
|
40
|
+
const records = suppressions.map((one) => articled(WRITTEN[one]));
|
|
41
|
+
return [`${reports}, which only ${spelledOr(records)} can name`];
|
|
42
|
+
}
|
|
43
|
+
const settable = named.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
44
|
+
const remedy = settable.length === 0
|
|
45
|
+
? "fix what the finding names instead"
|
|
46
|
+
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
47
|
+
return [`${reports}, which no suppression can name; ${remedy}`];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** A record's written name with its indefinite article. */
|
|
51
|
+
function articled(name) {
|
|
52
|
+
return `${/^[aeiou]/u.test(name) ? "an" : "a"} ${name}`;
|
|
53
|
+
}
|
|
54
|
+
/** A list as a sentence names alternatives. */
|
|
55
|
+
function spelledOr(items) {
|
|
56
|
+
return items.length < 2
|
|
57
|
+
? items.join("")
|
|
58
|
+
: `${items.slice(0, -1).join(", ")} or ${items.at(-1) ?? ""}`;
|
|
59
|
+
}
|
|
27
60
|
/** A list of codes as a sentence names them. */
|
|
28
61
|
function spelled(codes) {
|
|
29
62
|
if (codes.length < 2) {
|
|
@@ -85,25 +118,8 @@ export function staleSuppressions(stale, files) {
|
|
|
85
118
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
86
119
|
.filter((code) => !codes.includes(code))
|
|
87
120
|
.sort(bytewise);
|
|
88
|
-
const rows = codes.filter(isUnsuppressibleCode);
|
|
89
|
-
const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
|
|
90
|
-
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
91
|
-
const remedy = settable.length === 0
|
|
92
|
-
? "fix what the finding names instead"
|
|
93
|
-
: `fix what the finding names or set the severity of ${spelled(settable)} instead`;
|
|
94
121
|
const declared = reports.length === 0 ? "" : `its declaration reports ${spelled(reports)}`;
|
|
95
|
-
const because =
|
|
96
|
-
...(rows.length > 0
|
|
97
|
-
? [
|
|
98
|
-
`${spelled(rows)} ${rows.length === 1 ? "reports a row" : "report rows"} of a document, which no suppression can name; ${remedy}`,
|
|
99
|
-
]
|
|
100
|
-
: []),
|
|
101
|
-
...(dependencies.length > 0
|
|
102
|
-
? [
|
|
103
|
-
`${spelled(dependencies)} ${dependencies.length === 1 ? "reports a dependency row" : "report dependency rows"}, which only an ignore entry or a baseline row can name`,
|
|
104
|
-
]
|
|
105
|
-
: []),
|
|
106
|
-
];
|
|
122
|
+
const because = unnameable(codes, first.mechanism);
|
|
107
123
|
const instead = because.length > 0
|
|
108
124
|
? `, because ${because.join("; ")}${declared === "" ? "" : `; ${declared}`}`
|
|
109
125
|
: declared === ""
|
|
@@ -7,7 +7,7 @@ import { EXIT_CLEAN, EXIT_FINDINGS, EXIT_PENDING } from "./contract/contract.gen
|
|
|
7
7
|
import { fails } from "./config.js";
|
|
8
8
|
import { decidedFindings } from "./findings/emitters.js";
|
|
9
9
|
import { fileRefsOf, staleSuppressions, } from "./findings/self-check.js";
|
|
10
|
-
import {
|
|
10
|
+
import { namesKind, pairs, suppressionCounts } from "./suppress.js";
|
|
11
11
|
import { bytewise } from "./text/compare.js";
|
|
12
12
|
import { PRODUCT } from "./product.js";
|
|
13
13
|
/** The canonical order: path, line, column, code, then the symbol reference. */
|
|
@@ -71,15 +71,14 @@ export function verdictOf(result, config) {
|
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* The rows one round of a baseline write records from its findings, in their order. A
|
|
74
|
-
* finding
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* once.
|
|
74
|
+
* finding no baseline row can name is not recorded: a row recording it would be stale on
|
|
75
|
+
* the first read back. Two findings that would record the same row, two parts of one
|
|
76
|
+
* declaration under one code, record it once.
|
|
78
77
|
*/
|
|
79
78
|
export function recordedFindings(findings) {
|
|
80
79
|
const rows = new Map();
|
|
81
80
|
for (const finding of findings) {
|
|
82
|
-
if (
|
|
81
|
+
if (!namesKind(finding.symbol.kind, "baseline")) {
|
|
83
82
|
continue;
|
|
84
83
|
}
|
|
85
84
|
const row = { code: finding.code, symbol: finding.symbol.ref, path: finding.position.path };
|
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The framework-lifecycle class: a member a
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* through a chain, a class its `bases` name.
|
|
6
|
-
*
|
|
2
|
+
* The framework-lifecycle class: a member a lifecycle contract lists is held back on each
|
|
3
|
+
* class that is the contract's component, being a class a decorator or a call the
|
|
4
|
+
* contract's `components` name is attached to or passes, or one extending, directly or
|
|
5
|
+
* through a chain, a class its `bases` name. The contracts are the platform's custom
|
|
6
|
+
* elements and each `ts.lifecycle_contracts` entry. A component's members are its type's,
|
|
7
|
+
* so a member a base class declares answers for every subclass.
|
|
7
8
|
*/
|
|
8
9
|
import { isClassDeclaration, isClassExpression, isHeritageClause, SyntaxKind, } from "@typescript/native/ast";
|
|
9
10
|
import { aliasChains } from "./alias-chain.js";
|
|
10
11
|
import { calleeName, callsOf, classesPassed, decoratedClasses, decoratorCallee } from "./calls.js";
|
|
11
12
|
import { classMembers, memberComponent, typesAt } from "./class-members.js";
|
|
12
|
-
import { entriesAt, names, namesADeclaration, resolveEntries, spelledEntry, } from "./configured-declarations.js";
|
|
13
|
+
import { entriesAt, names, namesADeclaration, resolveEntries, spelledEntry, } from "./roots/configured-declarations.js";
|
|
13
14
|
import { nodeKey } from "./inventory.js";
|
|
14
15
|
import { renderPosition } from "./position.js";
|
|
15
16
|
import { must } from "./query.js";
|
|
17
|
+
/**
|
|
18
|
+
* The platform's own contract (references-ts.38): `CustomElementRegistry.define` reads
|
|
19
|
+
* these members off the class it registers, its constructor's and its prototype's
|
|
20
|
+
* (https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define),
|
|
21
|
+
* and calls each callback as a custom element reaction.
|
|
22
|
+
*/
|
|
23
|
+
const CUSTOM_ELEMENTS = {
|
|
24
|
+
components: [{ global: "CustomElementRegistry.define" }],
|
|
25
|
+
members: [
|
|
26
|
+
"observedAttributes:static",
|
|
27
|
+
"disabledFeatures:static",
|
|
28
|
+
"formAssociated:static",
|
|
29
|
+
"connectedCallback",
|
|
30
|
+
"disconnectedCallback",
|
|
31
|
+
"connectedMoveCallback",
|
|
32
|
+
"adoptedCallback",
|
|
33
|
+
"attributeChangedCallback",
|
|
34
|
+
"formAssociatedCallback",
|
|
35
|
+
"formResetCallback",
|
|
36
|
+
"formDisabledCallback",
|
|
37
|
+
"formStateRestoreCallback",
|
|
38
|
+
],
|
|
39
|
+
};
|
|
16
40
|
/** Every contract's components and bases, resolved in one pass over the entries. */
|
|
17
41
|
function resolveContracts(project, held, contracts) {
|
|
18
42
|
const entries = contracts.flatMap((contract) => [
|
|
@@ -155,16 +179,17 @@ function byBases(project, held, chains, contracts) {
|
|
|
155
179
|
}
|
|
156
180
|
/**
|
|
157
181
|
* The framework-lifecycle detector: each member a contract lists, on each class that
|
|
158
|
-
* is a component of that contract's framework, recorded where the class
|
|
159
|
-
* be one.
|
|
182
|
+
* is a component of that contract's framework, recorded once per place where the class
|
|
183
|
+
* was found to be one.
|
|
160
184
|
*/
|
|
161
185
|
export function frameworkLifecycle(input) {
|
|
162
186
|
const { project, held, targetRoot } = input;
|
|
163
|
-
|
|
187
|
+
const declared = [CUSTOM_ELEMENTS, ...input.ts.lifecycle_contracts];
|
|
188
|
+
const contracts = resolveContracts(project, held, declared);
|
|
189
|
+
if (contracts.every((one) => one.components.length === 0 && one.bases.length === 0)) {
|
|
164
190
|
return [];
|
|
165
191
|
}
|
|
166
192
|
const chains = aliasChains(project, held);
|
|
167
|
-
const contracts = resolveContracts(project, held, input.ts.lifecycle_contracts);
|
|
168
193
|
const components = [
|
|
169
194
|
...byComponents(project, held, chains, contracts),
|
|
170
195
|
...byBases(project, held, chains, contracts),
|
|
@@ -173,22 +198,24 @@ export function frameworkLifecycle(input) {
|
|
|
173
198
|
return [];
|
|
174
199
|
}
|
|
175
200
|
const ids = new Set(components.map((one) => one.id));
|
|
176
|
-
const sides =
|
|
201
|
+
const sides = declared.some((contract) => contract.members.some((name) => name.endsWith(":static")))
|
|
177
202
|
? ["instance", "static"]
|
|
178
203
|
: ["instance"];
|
|
179
204
|
const members = sides.map((side) => classMembers(project, held, ids, side));
|
|
180
205
|
const byId = new Map(held.symbols.map((symbol) => [symbol.id, symbol]));
|
|
181
|
-
const found =
|
|
206
|
+
const found = new Map();
|
|
182
207
|
for (const component of components) {
|
|
183
208
|
const listed = new Set(component.contract.members);
|
|
184
209
|
const site = renderPosition(component.site.getSourceFile(), targetRoot, component.site.getStart());
|
|
185
210
|
for (const member of members.flatMap((side) => side.get(component.id) ?? [])) {
|
|
186
211
|
const symbol = byId.get(member.id);
|
|
187
212
|
const spelled = symbol === undefined ? undefined : memberComponent(symbol, byId);
|
|
188
|
-
|
|
189
|
-
|
|
213
|
+
// A configured contract may name the platform's registry too; one record answers both.
|
|
214
|
+
const key = `${member.id}\0${component.detail}\0${site.path}:${String(site.line)}:${String(site.column)}`;
|
|
215
|
+
if (spelled !== undefined && listed.has(spelled) && !found.has(key)) {
|
|
216
|
+
found.set(key, { id: member.id, detail: component.detail, site });
|
|
190
217
|
}
|
|
191
218
|
}
|
|
192
219
|
}
|
|
193
|
-
return found;
|
|
220
|
+
return [...found.values()];
|
|
194
221
|
}
|
package/dist/src/fs/manifest.js
CHANGED
|
@@ -16,22 +16,27 @@ export const DEPENDENCY_SECTIONS = [
|
|
|
16
16
|
export function inInstalledPackages(path) {
|
|
17
17
|
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
18
18
|
}
|
|
19
|
-
/** One JSON file
|
|
20
|
-
export function
|
|
19
|
+
/** One JSON file, read and parsed, as {@link JSONRead} tells its states apart. */
|
|
20
|
+
export function readJSONFile(host, path) {
|
|
21
21
|
let text;
|
|
22
22
|
try {
|
|
23
23
|
text = host.readFile(path);
|
|
24
24
|
}
|
|
25
|
-
catch {
|
|
26
|
-
return
|
|
25
|
+
catch (error) {
|
|
26
|
+
return { kind: "unread", reason: message(error) };
|
|
27
27
|
}
|
|
28
28
|
try {
|
|
29
|
-
return { text, value: JSON.parse(text) };
|
|
29
|
+
return { kind: "read", text, value: JSON.parse(text) };
|
|
30
30
|
}
|
|
31
31
|
catch {
|
|
32
|
-
return
|
|
32
|
+
return { kind: "malformed" };
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
36
|
+
export function readDocument(host, path) {
|
|
37
|
+
const read = readJSONFile(host, path);
|
|
38
|
+
return read.kind === "read" ? { text: read.text, value: read.value } : undefined;
|
|
39
|
+
}
|
|
35
40
|
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
36
41
|
export function readJSON(host, path) {
|
|
37
42
|
return readDocument(host, path)?.value;
|
|
@@ -50,21 +55,11 @@ export function readManifestAt(host, dir) {
|
|
|
50
55
|
if (!holdsManifest(host, dir)) {
|
|
51
56
|
return { kind: "absent" };
|
|
52
57
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
return { kind: "unread", reason: message(error) };
|
|
59
|
-
}
|
|
60
|
-
let value;
|
|
61
|
-
try {
|
|
62
|
-
value = JSON.parse(text);
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return { kind: "malformed" };
|
|
58
|
+
const read = readJSONFile(host, joinPath(dir, MANIFEST));
|
|
59
|
+
if (read.kind !== "read") {
|
|
60
|
+
return read;
|
|
66
61
|
}
|
|
67
|
-
return isRecord(value) ? { kind: "read", manifest: value } : { kind: "malformed" };
|
|
62
|
+
return isRecord(read.value) ? { kind: "read", manifest: read.value } : { kind: "malformed" };
|
|
68
63
|
}
|
|
69
64
|
/** Whether one directory holds a manifest file. */
|
|
70
65
|
export function holdsManifest(host, dir) {
|