@cplieger/deadset-ts 8.0.0-dev.5 → 8.0.0-dev.7
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/bin/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +3 -2
- package/dist/src/analysis.js +28 -46
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +17 -29
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +3 -2
- package/dist/src/configuration-files.js +27 -168
- package/dist/src/conformance-record.js +1 -1
- package/dist/src/contract/contract.generated.js +1084 -0
- package/dist/src/contract/report.generated.js +796 -0
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +13 -39
- package/dist/src/edges.js +7 -15
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +3 -10
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +3 -63
- package/dist/src/exempt.js +13 -40
- package/dist/src/file-facts.js +2 -25
- package/dist/src/findings/completion.js +11 -20
- package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
- package/dist/src/findings/emitters.js +4 -4
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +8 -7
- package/dist/src/findings/non-code-artifacts.js +5 -6
- package/dist/src/findings/reads-and-writes.js +11 -9
- package/dist/src/findings/self-check.js +10 -12
- package/dist/src/findings/unused-declarations.js +28 -27
- package/dist/src/findings/visibility-narrowing.js +9 -11
- package/dist/src/findings-pass.js +7 -14
- package/dist/src/fs/manifest.js +43 -0
- package/dist/src/fs/tree.js +88 -0
- package/dist/src/git-ignore.js +34 -25
- package/dist/src/html-entries.js +6 -33
- package/dist/src/in-operands.js +2 -1
- package/dist/src/index.js +1 -1
- package/dist/src/intra-function-parts.js +18 -26
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +4 -0
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +4 -360
- package/dist/src/manifest.js +5 -43
- package/dist/src/mdx-entries.js +3 -26
- 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 +3 -2
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +272 -0
- package/dist/src/report.js +39 -42
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +12 -26
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +10 -8
- package/dist/src/schema.js +4 -3
- package/dist/src/scope.js +4 -5
- package/dist/src/serialization-contract.js +4 -8
- package/dist/src/session.js +2 -1
- package/dist/src/setup-failure.js +2 -6
- package/dist/src/suppress-file.js +12 -13
- package/dist/src/suppress-inline.js +3 -2
- package/dist/src/suppress.js +29 -48
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +5 -15
- package/dist/src/test-globals.js +6 -28
- package/dist/src/test-joins.js +6 -6
- package/dist/src/text/compare.js +26 -0
- package/dist/src/text/glob.js +194 -0
- package/dist/src/text/shell.js +159 -0
- package/dist/src/{yaml-document.js → text/yaml.js} +38 -0
- package/dist/src/type-errors.js +14 -38
- package/dist/src/unseen-loads.js +2 -9
- package/dist/src/unwrap.js +23 -0
- package/dist/src/verbs/analyze.js +22 -15
- package/dist/src/verbs/describe.js +7 -7
- package/dist/src/verbs/explain.js +7 -4
- package/dist/src/verbs/print-config.js +2 -1
- package/dist/src/verbs/print-projects.js +4 -2
- 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 +4 -3
- package/dist/src/workflow-steps.js +9 -46
- package/dist/src/workspace-manifests.js +2 -2
- package/dist/src/workspace-programs.js +5 -3
- package/dist/src/workspace-resolution.js +13 -17
- package/dist/src/workspace.js +28 -116
- package/jsr.json +1 -1
- package/package.json +2 -1
- package/src/analysis.ts +29 -54
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +17 -38
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +11 -15
- package/src/configuration-files.ts +27 -170
- package/src/conformance-record.ts +1 -1
- package/src/contract/contract.generated.ts +1148 -0
- package/src/contract/report.generated.ts +1157 -0
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +14 -39
- package/src/edges.ts +7 -21
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +2 -13
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +5 -74
- package/src/exempt.ts +17 -47
- package/src/file-facts.ts +6 -24
- package/src/finding.ts +9 -9
- package/src/findings/completion.ts +22 -23
- package/src/findings/dependencies-and-module-machinery.ts +4 -7
- package/src/findings/emitter.ts +6 -7
- package/src/findings/emitters.ts +6 -6
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +8 -8
- package/src/findings/non-code-artifacts.ts +5 -7
- package/src/findings/reads-and-writes.ts +15 -12
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +28 -27
- package/src/findings/visibility-narrowing.ts +9 -12
- package/src/findings-pass.ts +7 -16
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/intra-function-parts.ts +18 -28
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +5 -0
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +6 -406
- package/src/manifest.ts +5 -46
- package/src/mdx-entries.ts +3 -26
- package/src/partial.ts +2 -1
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +3 -2
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +364 -0
- package/src/report.ts +66 -229
- package/src/reporters/annotations.ts +2 -2
- package/src/reporters/json.ts +1 -1
- package/src/reporters/reporter.ts +2 -2
- package/src/reporters/reporters.ts +1 -1
- package/src/reporters/sarif.ts +17 -12
- package/src/reporters/template.ts +4 -20
- package/src/reporters/text.ts +3 -3
- package/src/resolve.ts +31 -38
- package/src/roots.ts +7 -20
- package/src/run.ts +10 -14
- package/src/schema.ts +4 -3
- package/src/scope.ts +4 -10
- package/src/serialization-contract.ts +4 -9
- package/src/session.ts +2 -1
- package/src/setup-failure.ts +5 -7
- package/src/suppress-file.ts +11 -15
- package/src/suppress-inline.ts +2 -2
- package/src/suppress.ts +39 -55
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +5 -18
- package/src/test-globals.ts +9 -27
- package/src/test-joins.ts +6 -8
- package/src/text/compare.ts +27 -0
- package/src/text/glob.ts +210 -0
- package/src/text/shell.ts +161 -0
- package/src/{yaml-document.ts → text/yaml.ts} +46 -0
- package/src/type-errors.ts +25 -36
- package/src/unseen-loads.ts +1 -12
- package/src/unwrap.ts +34 -0
- package/src/verbs/analyze.ts +38 -25
- package/src/verbs/describe.ts +13 -7
- package/src/verbs/explain.ts +7 -4
- package/src/verbs/print-config.ts +2 -1
- package/src/verbs/print-projects.ts +4 -2
- package/src/verbs/print-retained.ts +4 -2
- package/src/verbs/print-roots.ts +4 -2
- package/src/verbs/verb.ts +0 -6
- package/src/verbs/version.ts +4 -3
- package/src/workflow-steps.ts +9 -44
- package/src/workspace-manifests.ts +2 -2
- package/src/workspace-programs.ts +5 -3
- package/src/workspace-resolution.ts +21 -17
- package/src/workspace.ts +35 -121
- package/dist/src/glob.js +0 -65
- package/dist/src/pnpm-workspace.js +0 -120
- package/dist/src/version.js +0 -7
- package/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
- package/src/version.ts +0 -7
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.ts";
|
|
1
2
|
import type { Finding, FindingPosition, PositionedSymbol } from "../finding.ts";
|
|
2
3
|
import type { InventorySymbol } from "../inventory.ts";
|
|
3
4
|
import { byPosition } from "../position.ts";
|
|
4
5
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
5
6
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
6
7
|
|
|
7
|
-
const UNUSED_INTERFACE = "DS1201";
|
|
8
|
-
const UNCALLED_INTERFACE_METHOD = "DS1203";
|
|
9
|
-
|
|
10
8
|
/** What each finding says, by what the sweep found about the subject's references. */
|
|
11
9
|
const UNUSED_MESSAGE = "nothing in the target names the interface as a type";
|
|
12
10
|
const UNUSED_DEAD_MESSAGE =
|
|
@@ -69,7 +67,7 @@ function findingOf(
|
|
|
69
67
|
|
|
70
68
|
/**
|
|
71
69
|
* The findings of the interfaces family, `DS1200` to `DS1299`: `DS1201`, a dead
|
|
72
|
-
* interface, whose members fall with it unreported; and `
|
|
70
|
+
* interface, whose members fall with it unreported; and `DS1202`, a method of a live
|
|
73
71
|
* interface no reference names, unless it is a marker, a method whose every
|
|
74
72
|
* implementation has an empty body. A test file's declaration is neither kind's subject,
|
|
75
73
|
* because a production sweep counts none of the references it can have, save an interface
|
|
@@ -86,7 +84,7 @@ export const interfaces: Emitter = (input) => {
|
|
|
86
84
|
if (symbol.kind === "interface") {
|
|
87
85
|
found.push(
|
|
88
86
|
findingOf(
|
|
89
|
-
|
|
87
|
+
CODE.unusedInterface,
|
|
90
88
|
symbol,
|
|
91
89
|
unusedMessage(candidate),
|
|
92
90
|
run.implementationsOf(candidate.id),
|
|
@@ -104,7 +102,7 @@ export const interfaces: Emitter = (input) => {
|
|
|
104
102
|
}
|
|
105
103
|
found.push(
|
|
106
104
|
findingOf(
|
|
107
|
-
|
|
105
|
+
CODE.uncalledInterfaceMethod,
|
|
108
106
|
symbol,
|
|
109
107
|
UNCALLED_MESSAGE,
|
|
110
108
|
run.implementationsOf(symbol.parent),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { CODE, type Confidence } from "../contract/contract.generated.ts";
|
|
2
2
|
import type { Finding } from "../finding.ts";
|
|
3
3
|
import { KINDS } from "../kinds.ts";
|
|
4
4
|
import { parameterRef } from "../ref.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { type PartFact } from "../intra-function-parts.ts";
|
|
6
6
|
import { reachedWithTheTests } from "../sweep.ts";
|
|
7
7
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
8
8
|
|
|
@@ -85,9 +85,9 @@ function reported(
|
|
|
85
85
|
}
|
|
86
86
|
const { free, freeButValued, discardedEverywhere } = input.intraFunction;
|
|
87
87
|
switch (part.code) {
|
|
88
|
-
case
|
|
88
|
+
case CODE.unusedParameter:
|
|
89
89
|
return parameterVerdict(part, free, freeButValued) !== undefined;
|
|
90
|
-
case
|
|
90
|
+
case CODE.unusedResult:
|
|
91
91
|
return (
|
|
92
92
|
free.has(part.declaration) &&
|
|
93
93
|
discardedEverywhere.has(part.declaration) &&
|
|
@@ -99,7 +99,7 @@ function reported(
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* The findings of the
|
|
102
|
+
* The findings of the inside-functions family, `DS1401` to `DS1406`, each about a part of
|
|
103
103
|
* the declaration its reference names, at the part's own position, whatever the sweep
|
|
104
104
|
* decided about a production declaration. A part of test code is reported only where the
|
|
105
105
|
* run that counts test references reaches its declaration and that is no test of dead
|
|
@@ -146,12 +146,12 @@ export const intraFunction: Emitter = (input) => {
|
|
|
146
146
|
const found: Finding[] = [];
|
|
147
147
|
for (const part of input.intraFunction.parts) {
|
|
148
148
|
const declaration = byId.get(part.declaration);
|
|
149
|
-
const overlap = KINDS.get(part.code)?.overlap ?? [];
|
|
149
|
+
const overlap = KINDS.get(part.code)?.overlap?.ts ?? [];
|
|
150
150
|
if (declaration === undefined || !judged(declaration.id) || !reported(input, part, unknown)) {
|
|
151
151
|
continue;
|
|
152
152
|
}
|
|
153
153
|
const verdict =
|
|
154
|
-
part.code ===
|
|
154
|
+
part.code === CODE.unusedParameter
|
|
155
155
|
? parameterVerdict(part, input.intraFunction.free, input.intraFunction.freeButValued)
|
|
156
156
|
: undefined;
|
|
157
157
|
const message =
|
|
@@ -162,7 +162,7 @@ export const intraFunction: Emitter = (input) => {
|
|
|
162
162
|
code: part.code,
|
|
163
163
|
position: part.position,
|
|
164
164
|
symbol: {
|
|
165
|
-
...(part.code ===
|
|
165
|
+
...(part.code === CODE.unusedParameter
|
|
166
166
|
? { ref: parameterRef(declaration.ref, part.name), holder: declaration.ref }
|
|
167
167
|
: { ref: declaration.ref }),
|
|
168
168
|
kind: part.kind,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.ts";
|
|
1
2
|
import type { Config } from "../config.ts";
|
|
2
3
|
import type { Finding } from "../finding.ts";
|
|
3
4
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
4
5
|
|
|
5
|
-
const FILE_NEVER_BUILT = "DS1501";
|
|
6
|
-
const FILE_NEVER_IMPORTED = "DS1502";
|
|
7
|
-
|
|
8
6
|
/** The subject kind of a finding about a source file, from the finding schema's vocabulary. */
|
|
9
7
|
const FILE_SUBJECT = "file";
|
|
10
8
|
|
|
@@ -57,7 +55,7 @@ function fileNeverBuilt({ config, swept, files }: EmitterInput): readonly Findin
|
|
|
57
55
|
const message = `no project of the build matrix includes this file (${swept.matrix.configurations.join(", ")})`;
|
|
58
56
|
return files.tree
|
|
59
57
|
.filter((file) => !built.has(file.path))
|
|
60
|
-
.map((file) => fileFinding(
|
|
58
|
+
.map((file) => fileFinding(CODE.fileNeverBuilt, file, message));
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
/** The files of the run, each with whether a reference written in another file reaches it. */
|
|
@@ -152,21 +150,21 @@ function fileNeverImported(input: EmitterInput): readonly Finding[] {
|
|
|
152
150
|
return referenced.has(symbol.position.path)
|
|
153
151
|
? {
|
|
154
152
|
...fileFinding(
|
|
155
|
-
|
|
153
|
+
CODE.fileNeverImported,
|
|
156
154
|
file,
|
|
157
155
|
"only code that is itself dead imports this file, and no root names it or a declaration in it",
|
|
158
156
|
),
|
|
159
157
|
unreachableOnly: true as const,
|
|
160
158
|
}
|
|
161
159
|
: fileFinding(
|
|
162
|
-
|
|
160
|
+
CODE.fileNeverImported,
|
|
163
161
|
file,
|
|
164
162
|
"no import reaches this file and no root names it or a declaration in it",
|
|
165
163
|
);
|
|
166
164
|
});
|
|
167
165
|
}
|
|
168
166
|
|
|
169
|
-
/** The findings of the
|
|
167
|
+
/** The findings of the files family, `DS1600` to `DS1699`. */
|
|
170
168
|
export const nonCodeArtifacts: Emitter = (input) => [
|
|
171
169
|
...fileNeverBuilt(input),
|
|
172
170
|
...fileNeverImported(input),
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.ts";
|
|
1
2
|
import type { Finding, FindingDetails, FindingPosition } from "../finding.ts";
|
|
2
3
|
import type { InventorySymbol } from "../inventory.ts";
|
|
3
4
|
import type { MatrixCandidate } from "../matrix.ts";
|
|
4
5
|
import { positionKey } from "../position.ts";
|
|
5
6
|
import { declarationKey } from "../ref.ts";
|
|
6
7
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
8
|
+
import { bytewise } from "../text/compare.ts";
|
|
7
9
|
|
|
8
|
-
/** The codes
|
|
9
|
-
type ReadsAndWritesCode =
|
|
10
|
+
/** The codes this emitter reports. */
|
|
11
|
+
type ReadsAndWritesCode =
|
|
12
|
+
typeof CODE.writeOnlySymbol | typeof CODE.unusedEnumMember | typeof CODE.unusedTypeParameter;
|
|
10
13
|
|
|
11
|
-
/** The containers whose type parameters `
|
|
14
|
+
/** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
|
|
12
15
|
const SIGNATURES: ReadonlySet<string> = new Set(["function", "method", "type"]);
|
|
13
16
|
|
|
14
|
-
/** What
|
|
17
|
+
/** What this emitter reads of one run, indexed by declaration. */
|
|
15
18
|
interface Run {
|
|
16
19
|
readonly input: EmitterInput;
|
|
17
20
|
readonly symbols: ReadonlyMap<string, InventorySymbol>;
|
|
@@ -121,7 +124,7 @@ function writeOnlyMessage(
|
|
|
121
124
|
: "never read";
|
|
122
125
|
const cascade = [
|
|
123
126
|
...parametersOnlyNamedBy(run, namedBy, symbol).map((name) => `type parameter ${name}`),
|
|
124
|
-
...[...held.alone].sort().map((name) => `import ${name}`),
|
|
127
|
+
...[...held.alone].sort(bytewise).map((name) => `import ${name}`),
|
|
125
128
|
];
|
|
126
129
|
const word = SUBJECT_WORDS[symbol.kind] ?? "member";
|
|
127
130
|
const message = `${word} ${symbol.name} is written ${writes} and ${read}`;
|
|
@@ -185,7 +188,7 @@ function writeOnlySymbols(run: Run): readonly Finding[] {
|
|
|
185
188
|
continue;
|
|
186
189
|
}
|
|
187
190
|
found.push(
|
|
188
|
-
findingAbout(symbol,
|
|
191
|
+
findingAbout(symbol, CODE.writeOnlySymbol, writeOnlyMessage(run, namedBy, symbol, held), {
|
|
189
192
|
writePositions: held.writes,
|
|
190
193
|
}),
|
|
191
194
|
);
|
|
@@ -214,18 +217,18 @@ function unnamed(run: Run, kind: InventorySymbol["kind"]): readonly InventorySym
|
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
/**
|
|
217
|
-
* `
|
|
220
|
+
* `DS1005`: a member of an enum declaration that nothing names. A member an enum-group
|
|
218
221
|
* record holds back is no candidate, so a member a conversion can produce never reaches
|
|
219
222
|
* this kind.
|
|
220
223
|
*/
|
|
221
224
|
function unusedEnumMembers(run: Run): readonly Finding[] {
|
|
222
225
|
return unnamed(run, "enum-member").map((symbol) =>
|
|
223
|
-
findingAbout(symbol,
|
|
226
|
+
findingAbout(symbol, CODE.unusedEnumMember, `enum member ${symbol.name} is named nowhere`),
|
|
224
227
|
);
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
/**
|
|
228
|
-
* `
|
|
231
|
+
* `DS1006`: a type parameter of a function, a class method or a type alias that neither
|
|
229
232
|
* the declaration's signature nor its body names. An alias names no type of its own, so
|
|
230
233
|
* a parameter its type never names makes no two instantiations distinct. One of a class,
|
|
231
234
|
* an interface or a method signature is never reported: a class or an interface merges
|
|
@@ -237,7 +240,7 @@ function unusedTypeParameters(run: Run): readonly Finding[] {
|
|
|
237
240
|
.map((symbol) =>
|
|
238
241
|
findingAbout(
|
|
239
242
|
symbol,
|
|
240
|
-
|
|
243
|
+
CODE.unusedTypeParameter,
|
|
241
244
|
`type parameter ${symbol.name} is named in neither its signature nor its body`,
|
|
242
245
|
),
|
|
243
246
|
);
|
|
@@ -257,8 +260,8 @@ export function writeOnlyDeclarations(input: EmitterInput): ReadonlySet<string>
|
|
|
257
260
|
}
|
|
258
261
|
|
|
259
262
|
/**
|
|
260
|
-
* The findings of the reads-and-writes
|
|
261
|
-
* each code's in site order.
|
|
263
|
+
* The findings of the reads-and-writes kinds, `DS1301`, `DS1005` and `DS1006`, in that
|
|
264
|
+
* order and each code's in site order.
|
|
262
265
|
*/
|
|
263
266
|
export const readsAndWrites: Emitter = (input) => {
|
|
264
267
|
const run = indexed(input);
|
|
@@ -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,3 +1,4 @@
|
|
|
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";
|
|
@@ -14,18 +15,18 @@ const MEMBERS: ReadonlySet<SymbolKind> = new Set([
|
|
|
14
15
|
]);
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
* 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
|
|
18
19
|
* interface declares are the interface kinds' subjects, and a type parameter is the
|
|
19
20
|
* subject of the unused-type-parameter kind or of none.
|
|
20
21
|
*/
|
|
21
|
-
const
|
|
22
|
+
const NOT_REPORTED_HERE: ReadonlySet<SymbolKind> = new Set([
|
|
22
23
|
"file",
|
|
23
24
|
"interface",
|
|
24
25
|
"interface-method",
|
|
25
26
|
"type-parameter",
|
|
26
27
|
]);
|
|
27
28
|
|
|
28
|
-
/** 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. */
|
|
29
30
|
const WORDS: Readonly<Record<SymbolKind, string>> = {
|
|
30
31
|
file: "file",
|
|
31
32
|
function: "function",
|
|
@@ -71,9 +72,9 @@ interface Judged {
|
|
|
71
72
|
function codeOf(judged: Judged): string | undefined {
|
|
72
73
|
const { candidate, symbol } = judged;
|
|
73
74
|
if (candidate.testOfDeadCode) {
|
|
74
|
-
return judged.testsUnseenAPI ? undefined :
|
|
75
|
+
return judged.testsUnseenAPI ? undefined : CODE.testOfDeadCode;
|
|
75
76
|
}
|
|
76
|
-
if ((judged.test && !candidate.unreferencedTest) ||
|
|
77
|
+
if ((judged.test && !candidate.unreferencedTest) || NOT_REPORTED_HERE.has(symbol.kind)) {
|
|
77
78
|
return undefined;
|
|
78
79
|
}
|
|
79
80
|
const referenced = candidate.productionRefs + candidate.testRefs > 0;
|
|
@@ -84,33 +85,33 @@ function codeOf(judged: Judged): string | undefined {
|
|
|
84
85
|
return unreferencedCode(symbol);
|
|
85
86
|
}
|
|
86
87
|
if (candidate.heldByTests === candidate.id) {
|
|
87
|
-
return
|
|
88
|
+
return CODE.testOnlyUse;
|
|
88
89
|
}
|
|
89
90
|
if (candidate.productionRefs === 0 && judged.deprecated) {
|
|
90
|
-
return
|
|
91
|
+
return CODE.deprecatedUnused;
|
|
91
92
|
}
|
|
92
93
|
if (
|
|
93
94
|
candidate.testRefs > 0 &&
|
|
94
95
|
(candidate.productionRefs === 0 ||
|
|
95
96
|
(candidate.relation === "reachability" && candidate.keptByTests))
|
|
96
97
|
) {
|
|
97
|
-
return
|
|
98
|
+
return CODE.testOnlyUse;
|
|
98
99
|
}
|
|
99
100
|
if (MEMBERS.has(symbol.kind)) {
|
|
100
|
-
return
|
|
101
|
+
return CODE.unusedMember;
|
|
101
102
|
}
|
|
102
103
|
if (!symbol.exported) {
|
|
103
|
-
return
|
|
104
|
+
return CODE.unusedUnexported;
|
|
104
105
|
}
|
|
105
|
-
return judged.unreachable ? undefined :
|
|
106
|
+
return judged.unreachable ? undefined : CODE.unusedExported;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
/** The code a declaration nothing references carries, by its shape and its visibility. */
|
|
109
110
|
function unreferencedCode(symbol: InventorySymbol): string {
|
|
110
111
|
if (MEMBERS.has(symbol.kind)) {
|
|
111
|
-
return
|
|
112
|
+
return CODE.unusedMember;
|
|
112
113
|
}
|
|
113
|
-
return symbol.exported ?
|
|
114
|
+
return symbol.exported ? CODE.unusedExported : CODE.unusedUnexported;
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
/** The visibility a member's message names, where it narrows the member's reach. */
|
|
@@ -135,17 +136,17 @@ function messageOf(code: string, judged: Judged): string {
|
|
|
135
136
|
const { candidate, symbol } = judged;
|
|
136
137
|
const word = WORDS[symbol.kind];
|
|
137
138
|
switch (code) {
|
|
138
|
-
case
|
|
139
|
+
case CODE.testOfDeadCode:
|
|
139
140
|
return TEST_OF_DEAD_CODE;
|
|
140
|
-
case
|
|
141
|
+
case CODE.deprecatedUnused:
|
|
141
142
|
return `deprecated ${word} has no production reference`;
|
|
142
|
-
case
|
|
143
|
+
case CODE.testOnlyUse:
|
|
143
144
|
return candidate.productionRefs === 0
|
|
144
145
|
? `${word} is referenced only from test files and never from production code`
|
|
145
146
|
: `${word} is referenced only from test files and from declarations that are themselves dead`;
|
|
146
147
|
}
|
|
147
148
|
const subject =
|
|
148
|
-
code ===
|
|
149
|
+
code === CODE.unusedMember
|
|
149
150
|
? `${visibilityWord(symbol)}${word}`
|
|
150
151
|
: symbol.kind === "export-alias"
|
|
151
152
|
? word
|
|
@@ -153,7 +154,7 @@ function messageOf(code: string, judged: Judged): string {
|
|
|
153
154
|
if (candidate.relation === "reachability") {
|
|
154
155
|
return `${subject} is referenced only from declarations that are themselves dead`;
|
|
155
156
|
}
|
|
156
|
-
if (code ===
|
|
157
|
+
if (code === CODE.unusedExported) {
|
|
157
158
|
return `${subject} has no reference in the target and none from any loaded consumer`;
|
|
158
159
|
}
|
|
159
160
|
return `${subject} has no reference in the target`;
|
|
@@ -205,15 +206,15 @@ function brandMembers(input: EmitterInput): ReadonlySet<string> {
|
|
|
205
206
|
/** The kinds of member a brand is: a data member of a type or of a class. */
|
|
206
207
|
const BRANDS: ReadonlySet<SymbolKind> = new Set(["type-member", "class-member"]);
|
|
207
208
|
|
|
208
|
-
/** The findings of the unused-
|
|
209
|
+
/** The findings of the unused-declaration kinds, `DS1001` to `DS1004` and `DS1102` to `DS1104`. */
|
|
209
210
|
export const unusedDeclarations: Emitter = (input) => [
|
|
210
|
-
...
|
|
211
|
+
...emitterFindings(input, false),
|
|
211
212
|
...unusedImports(input),
|
|
212
213
|
];
|
|
213
214
|
|
|
214
215
|
/**
|
|
215
|
-
* The import bindings their file never reads
|
|
216
|
-
*
|
|
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.
|
|
217
218
|
*/
|
|
218
219
|
function unusedImports(input: EmitterInput): Finding[] {
|
|
219
220
|
const generated =
|
|
@@ -223,7 +224,7 @@ function unusedImports(input: EmitterInput): Finding[] {
|
|
|
223
224
|
? []
|
|
224
225
|
: [
|
|
225
226
|
{
|
|
226
|
-
code:
|
|
227
|
+
code: CODE.unusedImport,
|
|
227
228
|
position: binding.position,
|
|
228
229
|
symbol: { ref: binding.ref, kind: "import", name: binding.name, sizeLines: 1 },
|
|
229
230
|
message: `import binding ${binding.name} is never read in its file`,
|
|
@@ -237,10 +238,10 @@ function unusedImports(input: EmitterInput): Finding[] {
|
|
|
237
238
|
* a record on such a member is matched against, since the report carries the container's
|
|
238
239
|
* finding rather than one of the member's own.
|
|
239
240
|
*/
|
|
240
|
-
export const fallenMembers: Emitter = (input) =>
|
|
241
|
+
export const fallenMembers: Emitter = (input) => emitterFindings(input, true);
|
|
241
242
|
|
|
242
|
-
/**
|
|
243
|
-
function
|
|
243
|
+
/** This emitter's findings, or, where `fallen` is set, its dead containers' members' alone. */
|
|
244
|
+
function emitterFindings(input: EmitterInput, fallen: boolean): Finding[] {
|
|
244
245
|
const union = input.swept.matrix.union;
|
|
245
246
|
const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
|
|
246
247
|
const unreachable = unreachableExports(input);
|
|
@@ -269,7 +270,7 @@ function familyFindings(input: EmitterInput, fallen: boolean): Finding[] {
|
|
|
269
270
|
const code = codeOf(judged);
|
|
270
271
|
if (
|
|
271
272
|
code === undefined ||
|
|
272
|
-
(code ===
|
|
273
|
+
(code === CODE.unusedMember && (brands ??= brandMembers(input)).has(symbol.id))
|
|
273
274
|
) {
|
|
274
275
|
continue;
|
|
275
276
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
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";
|
|
@@ -8,10 +9,6 @@ import { runnerCalls } from "../sweep.ts";
|
|
|
8
9
|
import type { Emitter, EmitterInput } from "./emitter.ts";
|
|
9
10
|
import { writeOnlyDeclarations } from "./reads-and-writes.ts";
|
|
10
11
|
|
|
11
|
-
const UNNECESSARY_EXPORT = "DS1101";
|
|
12
|
-
const UNREACHABLE_EXPORT = "DS1103";
|
|
13
|
-
const REDUNDANT_EXPORT_KEYWORD = "DS1104";
|
|
14
|
-
|
|
15
12
|
/** How far the references to one declaration reach, from the narrowest. */
|
|
16
13
|
type Reach = "file" | "package" | "outside";
|
|
17
14
|
|
|
@@ -22,7 +19,7 @@ type Reach = "file" | "package" | "outside";
|
|
|
22
19
|
*/
|
|
23
20
|
const PUBLISHING: ReadonlySet<RootKind> = new Set<RootKind>(["manifest-entry", "published-api"]);
|
|
24
21
|
|
|
25
|
-
/** The reader's word for each kind of declaration this
|
|
22
|
+
/** The reader's word for each kind of declaration this emitter reports. */
|
|
26
23
|
const WORDS: Partial<Readonly<Record<SymbolKind, string>>> = {
|
|
27
24
|
function: "function",
|
|
28
25
|
class: "class",
|
|
@@ -151,8 +148,8 @@ function wordOf(symbol: InventorySymbol): string {
|
|
|
151
148
|
|
|
152
149
|
/** The visibility each narrowing code names, which only the narrowing codes carry. */
|
|
153
150
|
const NARROWER: Readonly<Record<string, "file" | "package">> = {
|
|
154
|
-
[
|
|
155
|
-
[
|
|
151
|
+
[CODE.redundantExportKeyword]: "file",
|
|
152
|
+
[CODE.unnecessaryExport]: "package",
|
|
156
153
|
};
|
|
157
154
|
|
|
158
155
|
function findingOf(symbol: InventorySymbol, code: string, message: string): Finding {
|
|
@@ -206,7 +203,7 @@ function narrowing(
|
|
|
206
203
|
}
|
|
207
204
|
return findingOf(
|
|
208
205
|
symbol,
|
|
209
|
-
|
|
206
|
+
CODE.redundantExportKeyword,
|
|
210
207
|
`exported ${word} is referenced only inside the file that declares it`,
|
|
211
208
|
);
|
|
212
209
|
case "package":
|
|
@@ -215,7 +212,7 @@ function narrowing(
|
|
|
215
212
|
}
|
|
216
213
|
return findingOf(
|
|
217
214
|
symbol,
|
|
218
|
-
|
|
215
|
+
CODE.unnecessaryExport,
|
|
219
216
|
`exported ${word} is referenced only inside the package that declares it`,
|
|
220
217
|
);
|
|
221
218
|
default:
|
|
@@ -254,13 +251,13 @@ function unreachable(
|
|
|
254
251
|
: "has no reference in the target";
|
|
255
252
|
return findingOf(
|
|
256
253
|
symbol,
|
|
257
|
-
|
|
254
|
+
CODE.unreachableExport,
|
|
258
255
|
`exported ${wordOf(symbol)} ${found}, and nothing outside can import the file that declares it`,
|
|
259
256
|
);
|
|
260
257
|
}
|
|
261
258
|
|
|
262
259
|
/**
|
|
263
|
-
* The visibility-narrowing
|
|
260
|
+
* The visibility-narrowing kinds over one run: `DS1501`, `DS1101` and `DS1503`. A finding
|
|
264
261
|
* about a symbol a declared cross-language edge names is reported here like any other, and
|
|
265
262
|
* the edge's evaluation then holds it pending, because the edge stands for a reference from
|
|
266
263
|
* outside the file and the package whose use is decided in the other language. A
|
|
@@ -316,5 +313,5 @@ export function unreachableExports(input: EmitterInput): ReadonlySet<string> {
|
|
|
316
313
|
return found;
|
|
317
314
|
}
|
|
318
315
|
|
|
319
|
-
/** The findings of the visibility-narrowing
|
|
316
|
+
/** The findings of the visibility-narrowing kinds, `DS1501`, `DS1101` and `DS1503`. */
|
|
320
317
|
export const visibilityNarrowing: Emitter = narrowings;
|
package/src/findings-pass.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
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
9
|
import { fails, type Config } from "./config.ts";
|
|
9
10
|
import type { EdgeEvaluation } from "./edges.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
|
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Host } from "../host.ts";
|
|
2
|
+
import { isRecord } from "../json-document.ts";
|
|
3
|
+
import { joinPath } from "../paths.ts";
|
|
4
|
+
|
|
5
|
+
/** The file a package's manifest is read from. */
|
|
6
|
+
export const MANIFEST = "package.json";
|
|
7
|
+
|
|
8
|
+
/** The directory a package manager installs packages into, whose files are no project's own. */
|
|
9
|
+
export const INSTALLED_PACKAGES = "node_modules";
|
|
10
|
+
|
|
11
|
+
/** Whether one path passes through an installed-packages directory. */
|
|
12
|
+
export function inInstalledPackages(path: string): boolean {
|
|
13
|
+
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
17
|
+
export function readDocument(
|
|
18
|
+
host: Host,
|
|
19
|
+
path: string,
|
|
20
|
+
): { text: string; value: unknown } | undefined {
|
|
21
|
+
let text: string;
|
|
22
|
+
try {
|
|
23
|
+
text = host.readFile(path);
|
|
24
|
+
} catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return { text, value: JSON.parse(text) as unknown };
|
|
29
|
+
} catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
35
|
+
export function readJSON(host: Host, path: string): unknown {
|
|
36
|
+
return readDocument(host, path)?.value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The manifest in one directory, decoded, or undefined where none reads as an object. A
|
|
41
|
+
* manifest this program cannot read declares nothing, the same answer as an absent one:
|
|
42
|
+
* the package manager owns the refusal of a manifest it rejects.
|
|
43
|
+
*/
|
|
44
|
+
export function manifestAt(host: Host, dir: string): Record<string, unknown> | undefined {
|
|
45
|
+
const value = readJSON(host, joinPath(dir, MANIFEST));
|
|
46
|
+
return isRecord(value) ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Whether one directory holds a manifest file. */
|
|
50
|
+
export function holdsManifest(host: Host, dir: string): boolean {
|
|
51
|
+
return host.kindOf(joinPath(dir, MANIFEST)) === "file";
|
|
52
|
+
}
|