@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10
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 +1 -1
- package/dist/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +245 -309
- package/dist/src/augmentations.js +124 -18
- package/dist/src/automatic-types.js +170 -0
- package/dist/src/build-scripts.js +425 -0
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +9 -9
- package/dist/src/config.js +6 -81
- package/dist/src/configuration-files.js +78 -188
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/conformance-record.js +2 -2
- package/dist/src/consumers.js +3 -30
- package/dist/src/contextual-uses.js +56 -9
- package/dist/src/contract/config.generated.js +558 -0
- package/dist/src/contract/contract.generated.js +1244 -0
- package/dist/src/contract/report.generated.js +862 -0
- package/dist/src/convention-rows.js +146 -1
- package/dist/src/conventions.js +136 -70
- package/dist/src/decorator.js +21 -18
- package/dist/src/dependencies.js +280 -62
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +9 -18
- package/dist/src/entry-points.js +106 -21
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +1 -63
- package/dist/src/exempt.js +21 -40
- package/dist/src/failure.js +36 -0
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +225 -63
- package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
- package/dist/src/findings/emitters.js +6 -6
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +53 -19
- package/dist/src/findings/non-code-artifacts.js +71 -39
- package/dist/src/findings/reads-and-writes.js +16 -13
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +57 -28
- package/dist/src/findings/visibility-narrowing.js +40 -18
- package/dist/src/findings-pass.js +8 -15
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +52 -0
- package/dist/src/fs/manifest.js +72 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/generated-file.js +18 -4
- package/dist/src/git-ignore.js +99 -0
- package/dist/src/graph.js +9 -4
- package/dist/src/html-entries.js +6 -33
- package/dist/src/import-bindings.js +97 -0
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/indexed-access.js +130 -28
- package/dist/src/injection-container.js +1 -1
- package/dist/src/interface-satisfaction.js +30 -12
- package/dist/src/intra-function-parts.js +341 -69
- package/dist/src/inventory.js +54 -29
- package/dist/src/javascript-file.js +33 -0
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +3 -366
- package/dist/src/load/classify.js +270 -0
- package/dist/src/load/diagnostics.js +80 -0
- package/dist/src/load/discover.js +277 -0
- package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
- package/dist/src/load/expected-module.js +136 -0
- package/dist/src/load/load.js +27 -0
- package/dist/src/load/module-lookup.js +37 -0
- package/dist/src/load/plan.js +313 -0
- package/dist/src/load/specifiers.js +115 -0
- package/dist/src/load/synthesize.js +140 -0
- package/dist/src/load/test-globals.js +207 -0
- package/dist/src/load/test-joins.js +82 -0
- package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
- package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
- package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
- package/dist/src/{workspace.js → load/workspace.js} +62 -124
- package/dist/src/manifest.js +49 -54
- package/dist/src/matrix.js +94 -13
- package/dist/src/mdx-entries.js +174 -0
- package/dist/src/module-declarations.js +9 -13
- package/dist/src/outside-tests.js +1 -1
- package/dist/src/overrides.js +41 -10
- package/dist/src/partial.js +2 -1
- package/dist/src/paths.js +26 -15
- package/dist/src/position.js +2 -1
- package/dist/src/print.js +11 -78
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +1 -0
- package/dist/src/ref.js +11 -0
- package/dist/src/references.js +52 -31
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +59 -41
- package/dist/src/reporters/annotations.js +15 -8
- package/dist/src/reporters/sarif.js +16 -10
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -727
- package/dist/src/roots.js +374 -116
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -342
- package/dist/src/scope.js +38 -115
- package/dist/src/serialization-contract.js +649 -29
- package/dist/src/session.js +41 -29
- package/dist/src/source-files.js +40 -15
- package/dist/src/suppress-file.js +34 -29
- package/dist/src/suppress-inline.js +21 -4
- package/dist/src/suppress.js +40 -56
- package/dist/src/sweep.js +32 -17
- package/dist/src/symbol-keys.js +105 -6
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-support.js +119 -18
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/named-files.js +54 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/text/yaml.js +393 -0
- package/dist/src/type-errors.js +27 -221
- package/dist/src/type-parameter-reads.js +172 -0
- package/dist/src/unseen-loads.js +100 -0
- package/dist/src/unwrap.js +23 -0
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +181 -125
- package/dist/src/verbs/describe.js +9 -16
- package/dist/src/verbs/explain.js +14 -9
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +6 -4
- package/dist/src/verbs/print-retained.js +4 -2
- package/dist/src/verbs/print-roots.js +4 -2
- package/dist/src/verbs/verb.js +1 -5
- package/dist/src/verbs/version.js +14 -3
- package/dist/src/workflow-steps.js +128 -33
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/analysis.ts +333 -457
- package/src/augmentations.ts +152 -23
- package/src/automatic-types.ts +228 -0
- package/src/build-scripts.ts +543 -0
- package/src/component-layer.ts +4 -2
- package/src/components.ts +9 -10
- package/src/config.ts +31 -299
- package/src/configuration-files.ts +100 -191
- package/src/configured-declarations.ts +18 -23
- package/src/conformance-record.ts +2 -2
- package/src/consumers.ts +6 -57
- package/src/contextual-uses.ts +64 -12
- package/src/contract/config.generated.ts +818 -0
- package/src/contract/contract.generated.ts +1308 -0
- package/src/contract/report.generated.ts +1237 -0
- package/src/convention-rows.ts +186 -1
- package/src/conventions.ts +166 -90
- package/src/decorator.ts +28 -18
- package/src/dependencies.ts +356 -78
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +11 -20
- package/src/entry-points.ts +124 -28
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +1 -75
- package/src/exempt.ts +36 -50
- package/src/failure.ts +63 -0
- package/src/file-facts.ts +9 -24
- package/src/finding.ts +24 -9
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +297 -77
- package/src/findings/dependencies-and-module-machinery.ts +90 -31
- package/src/findings/emitter.ts +8 -9
- package/src/findings/emitters.ts +8 -8
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +64 -20
- package/src/findings/non-code-artifacts.ts +88 -50
- package/src/findings/reads-and-writes.ts +20 -16
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +65 -28
- package/src/findings/visibility-narrowing.ts +51 -19
- package/src/findings-pass.ts +10 -19
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +70 -0
- package/src/fs/manifest.ts +92 -0
- package/src/fs/tree.ts +110 -0
- package/src/generated-file.ts +23 -4
- package/src/git-ignore.ts +122 -0
- package/src/graph.ts +11 -3
- package/src/host.ts +17 -14
- package/src/html-entries.ts +6 -33
- package/src/import-bindings.ts +128 -0
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/indexed-access.ts +152 -30
- package/src/injection-container.ts +1 -1
- package/src/interface-satisfaction.ts +33 -13
- package/src/intra-function-parts.ts +449 -85
- package/src/inventory.ts +73 -27
- package/src/javascript-file.ts +37 -0
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +5 -413
- package/src/load/classify.ts +397 -0
- package/src/load/diagnostics.ts +144 -0
- package/src/load/discover.ts +455 -0
- package/src/{emit-map.ts → load/emit-map.ts} +1 -1
- package/src/load/expected-module.ts +188 -0
- package/src/load/load.ts +73 -0
- package/src/load/module-lookup.ts +50 -0
- package/src/load/plan.ts +504 -0
- package/src/load/specifiers.ts +168 -0
- package/src/load/synthesize.ts +211 -0
- package/src/load/test-globals.ts +251 -0
- package/src/load/test-joins.ts +98 -0
- package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
- package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
- package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
- package/src/{workspace.ts → load/workspace.ts} +90 -134
- package/src/manifest.ts +81 -75
- package/src/matrix.ts +154 -18
- package/src/mdx-entries.ts +218 -0
- package/src/module-declarations.ts +11 -13
- package/src/outside-tests.ts +1 -1
- package/src/overrides.ts +51 -12
- package/src/partial.ts +3 -2
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -91
- package/src/product.ts +18 -0
- package/src/query.ts +11 -0
- package/src/ref.ts +13 -0
- package/src/references.ts +62 -32
- package/src/report-schema.ts +68 -0
- package/src/report.ts +105 -215
- package/src/reporters/annotations.ts +22 -11
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -4
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +29 -16
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +283 -1111
- package/src/roots.ts +490 -137
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -387
- package/src/scope.ts +46 -138
- package/src/serialization-contract.ts +818 -26
- package/src/session.ts +77 -54
- package/src/source-files.ts +67 -17
- package/src/suppress-file.ts +36 -34
- package/src/suppress-inline.ts +20 -3
- package/src/suppress.ts +52 -63
- package/src/sweep.ts +53 -18
- package/src/symbol-keys.ts +131 -7
- package/src/template-field.ts +15 -25
- package/src/test-support.ts +155 -18
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/named-files.inputs.json +1522 -0
- package/src/text/named-files.ts +68 -0
- package/src/text/shell.ts +161 -0
- package/src/text/yaml.ts +423 -0
- package/src/type-errors.ts +35 -273
- package/src/type-parameter-reads.ts +227 -0
- package/src/unseen-loads.ts +134 -0
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +245 -161
- package/src/verbs/describe.ts +14 -15
- package/src/verbs/explain.ts +15 -13
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +6 -4
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +2 -8
- package/src/verbs/version.ts +15 -3
- package/src/workflow-steps.ts +149 -34
- package/dist/src/discover.js +0 -347
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/setup-failure.js +0 -23
- package/dist/src/test-joins.js +0 -101
- package/dist/src/version.js +0 -7
- package/src/discover.ts +0 -510
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/setup-failure.ts +0 -39
- package/src/test-joins.ts +0 -120
- package/src/version.ts +0 -7
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.ts";
|
|
1
2
|
import { REPOSITORY_DOCUMENT } from "../config.ts";
|
|
2
3
|
import type { Finding, FindingDetails, SuppressionEntry } from "../finding.ts";
|
|
3
4
|
import type { InventorySymbol } from "../inventory.ts";
|
|
@@ -6,19 +7,12 @@ import type { Position } from "../position.ts";
|
|
|
6
7
|
import {
|
|
7
8
|
isDependencyCode,
|
|
8
9
|
isUnsuppressibleCode,
|
|
9
|
-
STALE_SUPPRESSION,
|
|
10
|
-
SUPPRESSION_WITHOUT_REASON,
|
|
11
10
|
type Mechanism,
|
|
12
11
|
type Refusal,
|
|
13
12
|
type SuppressionRecord,
|
|
14
13
|
} from "../suppress.ts";
|
|
15
14
|
import type { Emitter } from "./emitter.ts";
|
|
16
|
-
|
|
17
|
-
/** The code a configured root that matches nothing is reported under. */
|
|
18
|
-
const UNMATCHED_ROOT = "DS1704";
|
|
19
|
-
|
|
20
|
-
/** The code a configured declaration that names nothing is reported under. */
|
|
21
|
-
const UNMATCHED_DECLARATION = "DS1706";
|
|
15
|
+
import { bytewise } from "../text/compare.ts";
|
|
22
16
|
|
|
23
17
|
/** The subject kind of a finding about a configured declaration. */
|
|
24
18
|
const DECLARATION_SUBJECT = "configured-declaration";
|
|
@@ -39,7 +33,7 @@ export interface StaleRecord {
|
|
|
39
33
|
readonly reports: readonly string[];
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
/** What the
|
|
36
|
+
/** What the configuration family reads: the run's refused suppressions and its configured roots. */
|
|
43
37
|
export interface SelfCheckFacts {
|
|
44
38
|
/** The suppressions the grammar refused with a finding, in reading order. */
|
|
45
39
|
readonly refusals: readonly Refusal[];
|
|
@@ -135,7 +129,7 @@ function refusedSuppressions(refusals: readonly Refusal[], files: FileRefs): rea
|
|
|
135
129
|
suppressionRef(refused.symbol, refused.path, files),
|
|
136
130
|
refused.site,
|
|
137
131
|
`${WRITTEN[refused.mechanism]} for ${refused.code} ${
|
|
138
|
-
refused.reported ===
|
|
132
|
+
refused.reported === CODE.suppressionWithoutReason
|
|
139
133
|
? "carries no reason"
|
|
140
134
|
: "names a symbol and no path"
|
|
141
135
|
}`,
|
|
@@ -146,7 +140,7 @@ function refusedSuppressions(refusals: readonly Refusal[], files: FileRefs): rea
|
|
|
146
140
|
|
|
147
141
|
/** One stale suppression as the report's stale-suppression record carries it. */
|
|
148
142
|
export interface StaleSuppression {
|
|
149
|
-
readonly code: typeof
|
|
143
|
+
readonly code: typeof CODE.staleSuppression;
|
|
150
144
|
readonly mechanism: Mechanism;
|
|
151
145
|
readonly entry: {
|
|
152
146
|
readonly code: string;
|
|
@@ -178,10 +172,10 @@ export function staleSuppressions(
|
|
|
178
172
|
bySite.set(key, held);
|
|
179
173
|
}
|
|
180
174
|
return [...bySite.values()].map(({ first, records }) => {
|
|
181
|
-
const codes = [...new Set(records.map((one) => one.record.code))].sort();
|
|
175
|
+
const codes = [...new Set(records.map((one) => one.record.code))].sort(bytewise);
|
|
182
176
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
183
177
|
.filter((code) => !codes.includes(code))
|
|
184
|
-
.sort();
|
|
178
|
+
.sort(bytewise);
|
|
185
179
|
const rows = codes.filter(isUnsuppressibleCode);
|
|
186
180
|
const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
|
|
187
181
|
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
@@ -209,7 +203,7 @@ export function staleSuppressions(
|
|
|
209
203
|
? ""
|
|
210
204
|
: `, and ${declared}`;
|
|
211
205
|
return {
|
|
212
|
-
code:
|
|
206
|
+
code: CODE.staleSuppression,
|
|
213
207
|
mechanism: first.mechanism,
|
|
214
208
|
entry: {
|
|
215
209
|
code: first.code,
|
|
@@ -242,7 +236,7 @@ function unmatchedDeclarationFindings(
|
|
|
242
236
|
unmatched: readonly UnmatchedDeclaration[],
|
|
243
237
|
): readonly Finding[] {
|
|
244
238
|
return unmatched.map((one) => ({
|
|
245
|
-
code:
|
|
239
|
+
code: CODE.unmatchedConfiguredDeclaration,
|
|
246
240
|
position: documentPosition(one.document),
|
|
247
241
|
symbol: { ref: one.ref, kind: DECLARATION_SUBJECT, name: one.ref, sizeLines: 1 },
|
|
248
242
|
message: `${one.key} entry names no declaration in any project`,
|
|
@@ -262,7 +256,7 @@ export function unmatchedRootFindings(
|
|
|
262
256
|
): readonly Finding[] {
|
|
263
257
|
const position = documentPosition(document);
|
|
264
258
|
return sources.map((source) => ({
|
|
265
|
-
code:
|
|
259
|
+
code: CODE.unmatchedRoot,
|
|
266
260
|
position,
|
|
267
261
|
symbol: { ref: source, kind: ROOT_SUBJECT, name: source, sizeLines: 1 },
|
|
268
262
|
message:
|
|
@@ -273,7 +267,7 @@ export function unmatchedRootFindings(
|
|
|
273
267
|
}
|
|
274
268
|
|
|
275
269
|
/**
|
|
276
|
-
* The findings of the
|
|
270
|
+
* The findings of the configuration family, `DS9000` to `DS9099`: a suppression refused for
|
|
277
271
|
* carrying no reason or naming no path, and a configured root or declaration that named
|
|
278
272
|
* nothing. A
|
|
279
273
|
* suppression that matched nothing is a record of the report's stale suppressions, which
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.ts";
|
|
1
2
|
import type { Finding } from "../finding.ts";
|
|
2
3
|
import type { InventorySymbol, SymbolKind } from "../inventory.ts";
|
|
3
4
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
4
5
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
6
|
+
import { testsUnseenAPI } from "./completion.ts";
|
|
5
7
|
import { unreachableExports } from "./visibility-narrowing.ts";
|
|
6
8
|
|
|
7
9
|
/** The kinds of declaration that are members of a class, an interface, a type or an enum. */
|
|
@@ -13,18 +15,18 @@ const MEMBERS: ReadonlySet<SymbolKind> = new Set([
|
|
|
13
15
|
]);
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
|
-
* The kinds of declaration no code of this
|
|
18
|
+
* The kinds of declaration no code of this emitter reports: an interface and a method an
|
|
17
19
|
* interface declares are the interface kinds' subjects, and a type parameter is the
|
|
18
20
|
* subject of the unused-type-parameter kind or of none.
|
|
19
21
|
*/
|
|
20
|
-
const
|
|
22
|
+
const NOT_REPORTED_HERE: ReadonlySet<SymbolKind> = new Set([
|
|
21
23
|
"file",
|
|
22
24
|
"interface",
|
|
23
25
|
"interface-method",
|
|
24
26
|
"type-parameter",
|
|
25
27
|
]);
|
|
26
28
|
|
|
27
|
-
/** The reader's word for each kind of declaration a finding of
|
|
29
|
+
/** The reader's word for each kind of declaration a finding of this emitter names. */
|
|
28
30
|
const WORDS: Readonly<Record<SymbolKind, string>> = {
|
|
29
31
|
file: "file",
|
|
30
32
|
function: "function",
|
|
@@ -55,21 +57,24 @@ interface Judged {
|
|
|
55
57
|
readonly deprecated: boolean;
|
|
56
58
|
/** Whether the unreachable-export kind reports it. */
|
|
57
59
|
readonly unreachable: boolean;
|
|
60
|
+
/** Whether it is a test with a target that is published API no declared consumer judges. */
|
|
61
|
+
readonly testsUnseenAPI: boolean;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
/**
|
|
61
65
|
* The code one candidate is reported under, or none, each symbol once under the most
|
|
62
66
|
* specific code: a test of dead code first; no other test-file declaration or enum
|
|
63
67
|
* member nothing names; a test-support declaration test code references as test-only
|
|
64
|
-
* use; then deprecation, a test reference,
|
|
65
|
-
* unused export being the
|
|
68
|
+
* use; then deprecation, a test reference, or only dead production references beside a
|
|
69
|
+
* test that stays, and the unreferenced codes, an unimportable unused export being the
|
|
70
|
+
* unreachable kind's.
|
|
66
71
|
*/
|
|
67
72
|
function codeOf(judged: Judged): string | undefined {
|
|
68
73
|
const { candidate, symbol } = judged;
|
|
69
74
|
if (candidate.testOfDeadCode) {
|
|
70
|
-
return
|
|
75
|
+
return judged.testsUnseenAPI ? undefined : CODE.testOfDeadCode;
|
|
71
76
|
}
|
|
72
|
-
if ((judged.test && !candidate.unreferencedTest) ||
|
|
77
|
+
if ((judged.test && !candidate.unreferencedTest) || NOT_REPORTED_HERE.has(symbol.kind)) {
|
|
73
78
|
return undefined;
|
|
74
79
|
}
|
|
75
80
|
const referenced = candidate.productionRefs + candidate.testRefs > 0;
|
|
@@ -80,29 +85,33 @@ function codeOf(judged: Judged): string | undefined {
|
|
|
80
85
|
return unreferencedCode(symbol);
|
|
81
86
|
}
|
|
82
87
|
if (candidate.heldByTests === candidate.id) {
|
|
83
|
-
return
|
|
88
|
+
return CODE.testOnlyUse;
|
|
84
89
|
}
|
|
85
90
|
if (candidate.productionRefs === 0 && judged.deprecated) {
|
|
86
|
-
return
|
|
91
|
+
return CODE.deprecatedUnused;
|
|
87
92
|
}
|
|
88
|
-
if (
|
|
89
|
-
|
|
93
|
+
if (
|
|
94
|
+
candidate.testRefs > 0 &&
|
|
95
|
+
(candidate.productionRefs === 0 ||
|
|
96
|
+
(candidate.relation === "reachability" && candidate.keptByTests))
|
|
97
|
+
) {
|
|
98
|
+
return CODE.testOnlyUse;
|
|
90
99
|
}
|
|
91
100
|
if (MEMBERS.has(symbol.kind)) {
|
|
92
|
-
return
|
|
101
|
+
return CODE.unusedMember;
|
|
93
102
|
}
|
|
94
103
|
if (!symbol.exported) {
|
|
95
|
-
return
|
|
104
|
+
return CODE.unusedUnexported;
|
|
96
105
|
}
|
|
97
|
-
return judged.unreachable ? undefined :
|
|
106
|
+
return judged.unreachable ? undefined : CODE.unusedExported;
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
/** The code a declaration nothing references carries, by its shape and its visibility. */
|
|
101
110
|
function unreferencedCode(symbol: InventorySymbol): string {
|
|
102
111
|
if (MEMBERS.has(symbol.kind)) {
|
|
103
|
-
return
|
|
112
|
+
return CODE.unusedMember;
|
|
104
113
|
}
|
|
105
|
-
return symbol.exported ?
|
|
114
|
+
return symbol.exported ? CODE.unusedExported : CODE.unusedUnexported;
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
/** The visibility a member's message names, where it narrows the member's reach. */
|
|
@@ -127,15 +136,17 @@ function messageOf(code: string, judged: Judged): string {
|
|
|
127
136
|
const { candidate, symbol } = judged;
|
|
128
137
|
const word = WORDS[symbol.kind];
|
|
129
138
|
switch (code) {
|
|
130
|
-
case
|
|
139
|
+
case CODE.testOfDeadCode:
|
|
131
140
|
return TEST_OF_DEAD_CODE;
|
|
132
|
-
case
|
|
141
|
+
case CODE.deprecatedUnused:
|
|
133
142
|
return `deprecated ${word} has no production reference`;
|
|
134
|
-
case
|
|
135
|
-
return
|
|
143
|
+
case CODE.testOnlyUse:
|
|
144
|
+
return candidate.productionRefs === 0
|
|
145
|
+
? `${word} is referenced only from test files and never from production code`
|
|
146
|
+
: `${word} is referenced only from test files and from declarations that are themselves dead`;
|
|
136
147
|
}
|
|
137
148
|
const subject =
|
|
138
|
-
code ===
|
|
149
|
+
code === CODE.unusedMember
|
|
139
150
|
? `${visibilityWord(symbol)}${word}`
|
|
140
151
|
: symbol.kind === "export-alias"
|
|
141
152
|
? word
|
|
@@ -143,7 +154,7 @@ function messageOf(code: string, judged: Judged): string {
|
|
|
143
154
|
if (candidate.relation === "reachability") {
|
|
144
155
|
return `${subject} is referenced only from declarations that are themselves dead`;
|
|
145
156
|
}
|
|
146
|
-
if (code ===
|
|
157
|
+
if (code === CODE.unusedExported) {
|
|
147
158
|
return `${subject} has no reference in the target and none from any loaded consumer`;
|
|
148
159
|
}
|
|
149
160
|
return `${subject} has no reference in the target`;
|
|
@@ -195,21 +206,46 @@ function brandMembers(input: EmitterInput): ReadonlySet<string> {
|
|
|
195
206
|
/** The kinds of member a brand is: a data member of a type or of a class. */
|
|
196
207
|
const BRANDS: ReadonlySet<SymbolKind> = new Set(["type-member", "class-member"]);
|
|
197
208
|
|
|
198
|
-
/** The findings of the unused-
|
|
199
|
-
export const unusedDeclarations: Emitter = (input) =>
|
|
209
|
+
/** The findings of the unused-declaration kinds, `DS1001` to `DS1004` and `DS1102` to `DS1104`. */
|
|
210
|
+
export const unusedDeclarations: Emitter = (input) => [
|
|
211
|
+
...emitterFindings(input, false),
|
|
212
|
+
...unusedImports(input),
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The import bindings their file never reads. A generated file's are the generator's to
|
|
217
|
+
* remove unless the run includes generated files.
|
|
218
|
+
*/
|
|
219
|
+
function unusedImports(input: EmitterInput): Finding[] {
|
|
220
|
+
const generated =
|
|
221
|
+
input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
222
|
+
return input.intraFunction.imports.flatMap((binding) =>
|
|
223
|
+
binding.used || generated?.has(binding.position.path) === true
|
|
224
|
+
? []
|
|
225
|
+
: [
|
|
226
|
+
{
|
|
227
|
+
code: CODE.unusedImport,
|
|
228
|
+
position: binding.position,
|
|
229
|
+
symbol: { ref: binding.ref, kind: "import", name: binding.name, sizeLines: 1 },
|
|
230
|
+
message: `import binding ${binding.name} is never read in its file`,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
);
|
|
234
|
+
}
|
|
200
235
|
|
|
201
236
|
/**
|
|
202
237
|
* The finding each member of a dead container would carry were its container live: what
|
|
203
238
|
* a record on such a member is matched against, since the report carries the container's
|
|
204
239
|
* finding rather than one of the member's own.
|
|
205
240
|
*/
|
|
206
|
-
export const fallenMembers: Emitter = (input) =>
|
|
241
|
+
export const fallenMembers: Emitter = (input) => emitterFindings(input, true);
|
|
207
242
|
|
|
208
|
-
/**
|
|
209
|
-
function
|
|
243
|
+
/** This emitter's findings, or, where `fallen` is set, its dead containers' members' alone. */
|
|
244
|
+
function emitterFindings(input: EmitterInput, fallen: boolean): Finding[] {
|
|
210
245
|
const union = input.swept.matrix.union;
|
|
211
246
|
const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
|
|
212
247
|
const unreachable = unreachableExports(input);
|
|
248
|
+
const unseenAPI = testsUnseenAPI(input);
|
|
213
249
|
let brands: ReadonlySet<string> | undefined;
|
|
214
250
|
|
|
215
251
|
const found: Finding[] = [];
|
|
@@ -229,11 +265,12 @@ function familyFindings(input: EmitterInput, fallen: boolean): Finding[] {
|
|
|
229
265
|
test: union.test[at] === true,
|
|
230
266
|
deprecated: input.deprecated.has(symbol.id),
|
|
231
267
|
unreachable: unreachable.has(symbol.id),
|
|
268
|
+
testsUnseenAPI: candidate.testOfDeadCode && unseenAPI(candidate.id),
|
|
232
269
|
};
|
|
233
270
|
const code = codeOf(judged);
|
|
234
271
|
if (
|
|
235
272
|
code === undefined ||
|
|
236
|
-
(code ===
|
|
273
|
+
(code === CODE.unusedMember && (brands ??= brandMembers(input)).has(symbol.id))
|
|
237
274
|
) {
|
|
238
275
|
continue;
|
|
239
276
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.ts";
|
|
1
2
|
import type { Finding } from "../finding.ts";
|
|
2
3
|
import { OUTSIDE, type Graph } from "../graph.ts";
|
|
3
4
|
import type { InventorySymbol, SymbolKind } from "../inventory.ts";
|
|
4
5
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
5
6
|
import { declarationKey } from "../ref.ts";
|
|
6
7
|
import type { RootKind } from "../roots.ts";
|
|
8
|
+
import { runnerCalls } from "../sweep.ts";
|
|
7
9
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
8
10
|
import { writeOnlyDeclarations } from "./reads-and-writes.ts";
|
|
9
11
|
|
|
10
|
-
const UNNECESSARY_EXPORT = "DS1101";
|
|
11
|
-
const UNREACHABLE_EXPORT = "DS1103";
|
|
12
|
-
const REDUNDANT_EXPORT_KEYWORD = "DS1104";
|
|
13
|
-
|
|
14
12
|
/** How far the references to one declaration reach, from the narrowest. */
|
|
15
13
|
type Reach = "file" | "package" | "outside";
|
|
16
14
|
|
|
@@ -21,7 +19,7 @@ type Reach = "file" | "package" | "outside";
|
|
|
21
19
|
*/
|
|
22
20
|
const PUBLISHING: ReadonlySet<RootKind> = new Set<RootKind>(["manifest-entry", "published-api"]);
|
|
23
21
|
|
|
24
|
-
/** The reader's word for each kind of declaration this
|
|
22
|
+
/** The reader's word for each kind of declaration this emitter reports. */
|
|
25
23
|
const WORDS: Partial<Readonly<Record<SymbolKind, string>>> = {
|
|
26
24
|
function: "function",
|
|
27
25
|
class: "class",
|
|
@@ -51,8 +49,37 @@ function publishes(kinds: ReadonlySet<RootKind> | undefined): boolean {
|
|
|
51
49
|
return [...(kinds ?? [])].some((kind) => PUBLISHING.has(kind));
|
|
52
50
|
}
|
|
53
51
|
|
|
52
|
+
/**
|
|
53
|
+
* The root kinds that name a file without entering it by its exports: a configuration
|
|
54
|
+
* string or a script token runs the file, and no tool reads a name it exports.
|
|
55
|
+
*/
|
|
56
|
+
const RUN_ONLY: ReadonlySet<RootKind> = new Set<RootKind>(["configuration-string", "script"]);
|
|
57
|
+
|
|
58
|
+
/** Whether a root makes its file an entry file, or its declaration an entry, which a tool may read by name. */
|
|
54
59
|
function entersByName(kinds: ReadonlySet<RootKind> | undefined): boolean {
|
|
55
|
-
return [...(kinds ?? [])].some((kind) => !PUBLISHING.has(kind));
|
|
60
|
+
return [...(kinds ?? [])].some((kind) => !PUBLISHING.has(kind) && !RUN_ONLY.has(kind));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The root kinds whose root of a file enters no export by name: a document's import roots
|
|
65
|
+
* each export it imports, so an export it does not import is judged as in any file.
|
|
66
|
+
*/
|
|
67
|
+
const PER_NAME: ReadonlySet<RootKind> = new Set<RootKind>(["document-import"]);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether the roots of a declaration's file enter it by name: a test runner's root
|
|
71
|
+
* enters only an export the runner calls, so another export of a test file is judged.
|
|
72
|
+
*/
|
|
73
|
+
function fileEntersByName(
|
|
74
|
+
kinds: ReadonlySet<RootKind> | undefined,
|
|
75
|
+
symbol: InventorySymbol,
|
|
76
|
+
): boolean {
|
|
77
|
+
const entering = [...(kinds ?? [])].filter(
|
|
78
|
+
(kind) => !PUBLISHING.has(kind) && !RUN_ONLY.has(kind) && !PER_NAME.has(kind),
|
|
79
|
+
);
|
|
80
|
+
return (
|
|
81
|
+
entering.some((kind) => kind !== "test-runner") || (entering.length > 0 && runnerCalls(symbol))
|
|
82
|
+
);
|
|
56
83
|
}
|
|
57
84
|
|
|
58
85
|
/**
|
|
@@ -121,8 +148,8 @@ function wordOf(symbol: InventorySymbol): string {
|
|
|
121
148
|
|
|
122
149
|
/** The visibility each narrowing code names, which only the narrowing codes carry. */
|
|
123
150
|
const NARROWER: Readonly<Record<string, "file" | "package">> = {
|
|
124
|
-
[
|
|
125
|
-
[
|
|
151
|
+
[CODE.redundantExportKeyword]: "file",
|
|
152
|
+
[CODE.unnecessaryExport]: "package",
|
|
126
153
|
};
|
|
127
154
|
|
|
128
155
|
function findingOf(symbol: InventorySymbol, code: string, message: string): Finding {
|
|
@@ -163,7 +190,7 @@ function narrowing(
|
|
|
163
190
|
world.writeOnly.has(declarationKey(symbol.ref, symbol.position.path)) ||
|
|
164
191
|
symbol.kind === "export-alias" ||
|
|
165
192
|
entersByName(facts.rootedBy[at]) ||
|
|
166
|
-
|
|
193
|
+
fileEntersByName(facts.rootedBy[file], symbol) ||
|
|
167
194
|
(facts.named[at] ?? 0) === 0
|
|
168
195
|
) {
|
|
169
196
|
return undefined;
|
|
@@ -176,7 +203,7 @@ function narrowing(
|
|
|
176
203
|
}
|
|
177
204
|
return findingOf(
|
|
178
205
|
symbol,
|
|
179
|
-
|
|
206
|
+
CODE.redundantExportKeyword,
|
|
180
207
|
`exported ${word} is referenced only inside the file that declares it`,
|
|
181
208
|
);
|
|
182
209
|
case "package":
|
|
@@ -185,7 +212,7 @@ function narrowing(
|
|
|
185
212
|
}
|
|
186
213
|
return findingOf(
|
|
187
214
|
symbol,
|
|
188
|
-
|
|
215
|
+
CODE.unnecessaryExport,
|
|
189
216
|
`exported ${word} is referenced only inside the package that declares it`,
|
|
190
217
|
);
|
|
191
218
|
default:
|
|
@@ -198,7 +225,7 @@ function narrowing(
|
|
|
198
225
|
* export no code outside the target can import, because the manifest declares
|
|
199
226
|
* `exports` and no root reaches it. Each arm yields to a more specific kind, so one
|
|
200
227
|
* declaration is reported once: a test whose every subject is dead, an interface, a
|
|
201
|
-
* declaration only a test file references, and a deprecated one.
|
|
228
|
+
* declaration only a test file references or a test that stays keeps, and a deprecated one.
|
|
202
229
|
*/
|
|
203
230
|
function unreachable(
|
|
204
231
|
facts: Facts,
|
|
@@ -213,7 +240,7 @@ function unreachable(
|
|
|
213
240
|
(facts.rootedBy[at]?.size ?? 0) > 0 ||
|
|
214
241
|
candidate.testOfDeadCode ||
|
|
215
242
|
symbol.kind === "interface" ||
|
|
216
|
-
(candidate.
|
|
243
|
+
(candidate.testRefs > 0 && (candidate.productionRefs === 0 || candidate.keptByTests)) ||
|
|
217
244
|
(candidate.productionRefs === 0 && input.deprecated.has(symbol.id))
|
|
218
245
|
) {
|
|
219
246
|
return undefined;
|
|
@@ -224,13 +251,13 @@ function unreachable(
|
|
|
224
251
|
: "has no reference in the target";
|
|
225
252
|
return findingOf(
|
|
226
253
|
symbol,
|
|
227
|
-
|
|
254
|
+
CODE.unreachableExport,
|
|
228
255
|
`exported ${wordOf(symbol)} ${found}, and nothing outside can import the file that declares it`,
|
|
229
256
|
);
|
|
230
257
|
}
|
|
231
258
|
|
|
232
259
|
/**
|
|
233
|
-
* The visibility-narrowing
|
|
260
|
+
* The visibility-narrowing kinds over one run: `DS1501`, `DS1101` and `DS1503`. A finding
|
|
234
261
|
* about a symbol a declared cross-language edge names is reported here like any other, and
|
|
235
262
|
* the edge's evaluation then holds it pending, because the edge stands for a reference from
|
|
236
263
|
* outside the file and the package whose use is decided in the other language. A
|
|
@@ -239,13 +266,13 @@ function unreachable(
|
|
|
239
266
|
export function narrowings(input: EmitterInput): readonly Finding[] {
|
|
240
267
|
const facts = factsOf(input.swept.matrix.union);
|
|
241
268
|
const { declared, loaded } = input.boundary.consumers;
|
|
242
|
-
const closed = input.config.
|
|
269
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
243
270
|
const world = { closed, writeOnly: writeOnlyDeclarations(input) };
|
|
244
271
|
const candidates = new Map(
|
|
245
272
|
input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]),
|
|
246
273
|
);
|
|
247
274
|
const generated =
|
|
248
|
-
input.config.analysis.
|
|
275
|
+
input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
249
276
|
|
|
250
277
|
const findings: Finding[] = [];
|
|
251
278
|
facts.graph.symbols.forEach((symbol, at) => {
|
|
@@ -253,8 +280,13 @@ export function narrowings(input: EmitterInput): readonly Finding[] {
|
|
|
253
280
|
return;
|
|
254
281
|
}
|
|
255
282
|
const candidate = candidates.get(symbol.id);
|
|
283
|
+
// A declaration of test code, a test file's or test-support code's, that only test code
|
|
284
|
+
// references is a candidate, live in the run that counts test references.
|
|
285
|
+
const live =
|
|
286
|
+
candidate === undefined ||
|
|
287
|
+
(facts.graph.test[at] === true && !candidate.unreferencedTest && !candidate.testOfDeadCode);
|
|
256
288
|
const found =
|
|
257
|
-
candidate === undefined
|
|
289
|
+
candidate === undefined || live
|
|
258
290
|
? narrowing(facts, at, symbol, world)
|
|
259
291
|
: unreachable(facts, at, symbol, candidate, input);
|
|
260
292
|
if (found !== undefined) {
|
|
@@ -281,5 +313,5 @@ export function unreachableExports(input: EmitterInput): ReadonlySet<string> {
|
|
|
281
313
|
return found;
|
|
282
314
|
}
|
|
283
315
|
|
|
284
|
-
/** The findings of the visibility-narrowing
|
|
316
|
+
/** The findings of the visibility-narrowing kinds, `DS1501`, `DS1101` and `DS1503`. */
|
|
285
317
|
export const visibilityNarrowing: Emitter = narrowings;
|
package/src/findings-pass.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* verdict a report carries beside them.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { EXIT_CLEAN, EXIT_FINDINGS, EXIT_PENDING } from "./contract/contract.generated.ts";
|
|
7
8
|
import type { AnalysisInputs } from "./analysis.ts";
|
|
8
|
-
import { fails, type
|
|
9
|
+
import { fails, type ResolvedConfig } from "./config.ts";
|
|
9
10
|
import type { EdgeEvaluation } from "./edges.ts";
|
|
10
11
|
import type { CompletedFinding, Finding } from "./finding.ts";
|
|
11
12
|
import { decidedFindings, type Withheld } from "./findings/emitters.ts";
|
|
@@ -23,7 +24,8 @@ import {
|
|
|
23
24
|
type Ledger,
|
|
24
25
|
} from "./suppress.ts";
|
|
25
26
|
import type { Provenance, Recorded } from "./suppress-file.ts";
|
|
26
|
-
import {
|
|
27
|
+
import { bytewise } from "./text/compare.ts";
|
|
28
|
+
import { PRODUCT } from "./product.ts";
|
|
27
29
|
|
|
28
30
|
/** The counts a report's totals carry beside its findings. */
|
|
29
31
|
interface PassTotals {
|
|
@@ -49,25 +51,14 @@ export interface PassResult {
|
|
|
49
51
|
readonly ledger: Ledger;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
/** The exit code of a report holding at least one pending finding. */
|
|
53
|
-
export const EXIT_PENDING = 4;
|
|
54
|
-
|
|
55
|
-
/** Two strings ordered bytewise. */
|
|
56
|
-
function compare(a: string, b: string): number {
|
|
57
|
-
if (a === b) {
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
return a < b ? -1 : 1;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
54
|
/** The canonical order: path, line, column, code, then the symbol reference. */
|
|
64
55
|
function canonical(a: Finding, b: Finding): number {
|
|
65
56
|
return (
|
|
66
|
-
|
|
57
|
+
bytewise(a.position.path, b.position.path) ||
|
|
67
58
|
a.position.line - b.position.line ||
|
|
68
59
|
a.position.column - b.position.column ||
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
bytewise(a.code, b.code) ||
|
|
61
|
+
bytewise(a.symbol.ref, b.symbol.ref)
|
|
71
62
|
);
|
|
72
63
|
}
|
|
73
64
|
|
|
@@ -118,7 +109,7 @@ export function findingsPass(inputs: AnalysisInputs): PassResult {
|
|
|
118
109
|
* suppression is stale or a finding's severity reaches the configured failing severity;
|
|
119
110
|
* clean otherwise.
|
|
120
111
|
*/
|
|
121
|
-
export function verdictOf(result: PassResult, config:
|
|
112
|
+
export function verdictOf(result: PassResult, config: ResolvedConfig): number {
|
|
122
113
|
if (result.totals.pending > 0) {
|
|
123
114
|
return EXIT_PENDING;
|
|
124
115
|
}
|
|
@@ -126,7 +117,7 @@ export function verdictOf(result: PassResult, config: Config): number {
|
|
|
126
117
|
return EXIT_FINDINGS;
|
|
127
118
|
}
|
|
128
119
|
const failing = result.findings.some((finding) =>
|
|
129
|
-
fails(finding.severity, config.reporters.
|
|
120
|
+
fails(finding.severity, config.reporters.fail_on),
|
|
130
121
|
);
|
|
131
122
|
return failing ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
132
123
|
}
|
|
@@ -164,5 +155,5 @@ export function rowKey(row: Recorded): string {
|
|
|
164
155
|
|
|
165
156
|
/** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
|
|
166
157
|
export function analyzerProvenance(version: string): Provenance {
|
|
167
|
-
return { analyzer:
|
|
158
|
+
return { analyzer: PRODUCT, version };
|
|
168
159
|
}
|
|
@@ -55,8 +55,8 @@ function resolveContracts<Brand>(
|
|
|
55
55
|
contracts: readonly LifecycleContract[],
|
|
56
56
|
): readonly ResolvedContract[] {
|
|
57
57
|
const entries: DeclarationEntry[] = contracts.flatMap((contract) => [
|
|
58
|
-
...contract.components,
|
|
59
|
-
...contract.bases,
|
|
58
|
+
...(contract.components ?? []),
|
|
59
|
+
...(contract.bases ?? []),
|
|
60
60
|
]);
|
|
61
61
|
const resolved = resolveEntries(project, held, entries);
|
|
62
62
|
let next = 0;
|
|
@@ -67,8 +67,8 @@ function resolveContracts<Brand>(
|
|
|
67
67
|
};
|
|
68
68
|
return contracts.map((contract) => ({
|
|
69
69
|
contract,
|
|
70
|
-
components: take(contract.components
|
|
71
|
-
bases: take(contract.bases
|
|
70
|
+
components: take(contract.components?.length ?? 0),
|
|
71
|
+
bases: take(contract.bases?.length ?? 0),
|
|
72
72
|
}));
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -243,11 +243,11 @@ function byBases<Brand>(
|
|
|
243
243
|
*/
|
|
244
244
|
export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly Evidence[] {
|
|
245
245
|
const { project, held, targetRoot } = input;
|
|
246
|
-
if (input.ts.
|
|
246
|
+
if (input.ts.lifecycle_contracts.length === 0) {
|
|
247
247
|
return [];
|
|
248
248
|
}
|
|
249
249
|
const chains = aliasChains(project, held);
|
|
250
|
-
const contracts = resolveContracts(project, held, input.ts.
|
|
250
|
+
const contracts = resolveContracts(project, held, input.ts.lifecycle_contracts);
|
|
251
251
|
const components = [
|
|
252
252
|
...byComponents(project, held, chains, contracts),
|
|
253
253
|
...byBases(project, held, chains, contracts),
|
|
@@ -256,7 +256,7 @@ export function frameworkLifecycle<Brand>(input: DetectorInput<Brand>): readonly
|
|
|
256
256
|
return [];
|
|
257
257
|
}
|
|
258
258
|
const ids = new Set(components.map((one) => one.id));
|
|
259
|
-
const sides: readonly ClassSide[] = input.ts.
|
|
259
|
+
const sides: readonly ClassSide[] = input.ts.lifecycle_contracts.some((contract) =>
|
|
260
260
|
contract.members.some((name) => name.endsWith(":static")),
|
|
261
261
|
)
|
|
262
262
|
? ["instance", "static"]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one read of a document the target holds at its root, which a checkout under analysis
|
|
3
|
+
* controls: the repository configuration, the ignore file, the baseline and the edges
|
|
4
|
+
* document. The read stays inside the target root, takes a regular file alone, and holds no
|
|
5
|
+
* more of it than the Contract's document bound.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CONFIG_MAX_BYTES } from "../contract/config.generated.ts";
|
|
9
|
+
import type { Host } from "../host.ts";
|
|
10
|
+
import { failureExit } from "../failure.ts";
|
|
11
|
+
import { joinPath, relativePath } from "../paths.ts";
|
|
12
|
+
import { message } from "../product.ts";
|
|
13
|
+
import { utf8Bytes } from "../reporters/sha256.ts";
|
|
14
|
+
import { UsageError } from "../usage.ts";
|
|
15
|
+
|
|
16
|
+
function unreadable(label: string, reason: string): Error {
|
|
17
|
+
return failureExit(
|
|
18
|
+
"unreadable-document",
|
|
19
|
+
`${label}: ${reason}: make it a regular file inside the target root that the run can read, or remove it`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Refuses a document past the Contract's bound with the usage code, naming it `label`. */
|
|
24
|
+
export function refuseOverBound(label: string, text: string): void {
|
|
25
|
+
if (utf8Bytes(text).length > CONFIG_MAX_BYTES) {
|
|
26
|
+
throw new UsageError(`${label}: holds more than ${String(CONFIG_MAX_BYTES)} bytes`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The text of the document `name` at the absolute `targetRoot`, undefined where nothing is
|
|
32
|
+
* there. One whose name resolves outside the target root or to anything but a regular file,
|
|
33
|
+
* or whose read fails, is refused by `refuse` naming it `label`, nothing read from it. A
|
|
34
|
+
* document past the bound is read one byte past it and refused as {@link refuseOverBound} does.
|
|
35
|
+
*/
|
|
36
|
+
export function readTargetDocument(
|
|
37
|
+
host: Host,
|
|
38
|
+
targetRoot: string,
|
|
39
|
+
name: string,
|
|
40
|
+
label = name,
|
|
41
|
+
refuse: (label: string, reason: string) => Error = unreadable,
|
|
42
|
+
): string | undefined {
|
|
43
|
+
const attempt = <T>(read: () => T): T => {
|
|
44
|
+
try {
|
|
45
|
+
return read();
|
|
46
|
+
} catch (error: unknown) {
|
|
47
|
+
throw refuse(label, message(error));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const path = joinPath(targetRoot, name);
|
|
51
|
+
const kind = attempt(() => host.kindOf(path));
|
|
52
|
+
if (kind === "absent") {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const real = attempt(() => host.realPath(path));
|
|
56
|
+
if (
|
|
57
|
+
relativePath(
|
|
58
|
+
attempt(() => host.realPath(targetRoot)),
|
|
59
|
+
real,
|
|
60
|
+
) === undefined
|
|
61
|
+
) {
|
|
62
|
+
throw refuse(label, `resolves to ${real}, outside the target root`);
|
|
63
|
+
}
|
|
64
|
+
if (kind !== "file") {
|
|
65
|
+
throw refuse(label, "is not a regular file");
|
|
66
|
+
}
|
|
67
|
+
const text = attempt(() => host.readFile(real, CONFIG_MAX_BYTES + 1));
|
|
68
|
+
refuseOverBound(label, text);
|
|
69
|
+
return text;
|
|
70
|
+
}
|