@cplieger/deadset-ts 8.0.0-dev.1 → 8.0.0-dev.11
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 +181 -341
- package/dist/src/augmentations.js +106 -13
- package/dist/src/automatic-types.js +10 -29
- package/dist/src/build-scripts.js +425 -0
- package/dist/src/component-layer.js +4 -2
- package/dist/src/components.js +2 -3
- package/dist/src/config.js +6 -82
- package/dist/src/configuration-files.js +28 -176
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/conformance-record.js +1 -1
- 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 +73 -3
- package/dist/src/conventions.js +36 -66
- package/dist/src/dependencies.js +103 -40
- package/dist/src/edges.js +38 -122
- package/dist/src/element-access.js +4 -6
- package/dist/src/entry-point-gaps.js +2 -8
- package/dist/src/entry-points.js +11 -27
- 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 +139 -72
- package/dist/src/findings/dependencies-and-module-machinery.js +4 -5
- package/dist/src/findings/emitters.js +5 -5
- package/dist/src/findings/interfaces.js +4 -5
- package/dist/src/findings/intra-function.js +13 -10
- package/dist/src/findings/non-code-artifacts.js +7 -8
- 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 +41 -32
- package/dist/src/findings/visibility-narrowing.js +19 -16
- 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 +7 -4
- package/dist/src/git-ignore.js +39 -26
- package/dist/src/graph.js +5 -3
- 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/injection-container.js +1 -1
- package/dist/src/intra-function-parts.js +63 -43
- package/dist/src/inventory.js +2 -21
- 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} +89 -66
- package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +23 -65
- 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 +76 -4
- package/dist/src/mdx-entries.js +3 -26
- package/dist/src/outside-tests.js +1 -1
- 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 -79
- package/dist/src/product.js +13 -0
- package/dist/src/query.js +1 -0
- package/dist/src/ref.js +1 -0
- package/dist/src/references.js +48 -30
- package/dist/src/report-schema.js +51 -0
- package/dist/src/report.js +57 -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 -729
- package/dist/src/roots.js +273 -87
- package/dist/src/run.js +66 -77
- package/dist/src/schema.js +320 -343
- package/dist/src/scope.js +38 -115
- package/dist/src/serialization-contract.js +595 -21
- package/dist/src/session.js +34 -29
- package/dist/src/source-files.js +1 -1
- package/dist/src/suppress-file.js +25 -28
- package/dist/src/suppress-inline.js +3 -2
- package/dist/src/suppress.js +33 -51
- package/dist/src/sweep.js +7 -9
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +9 -19
- package/dist/src/test-support.js +8 -11
- 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 +11 -232
- 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 +166 -126
- package/dist/src/verbs/describe.js +9 -16
- package/dist/src/verbs/explain.js +10 -7
- 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 +62 -64
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/analysis.ts +245 -502
- package/src/augmentations.ts +135 -17
- package/src/automatic-types.ts +11 -29
- package/src/build-scripts.ts +543 -0
- package/src/component-layer.ts +4 -2
- package/src/components.ts +2 -4
- package/src/config.ts +31 -302
- package/src/configuration-files.ts +34 -179
- package/src/configured-declarations.ts +18 -23
- package/src/conformance-record.ts +1 -1
- 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 +85 -3
- package/src/conventions.ts +46 -84
- package/src/dependencies.ts +118 -46
- package/src/edges.ts +41 -140
- package/src/element-access.ts +4 -7
- package/src/entry-point-gaps.ts +3 -9
- package/src/entry-points.ts +12 -38
- 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 +14 -9
- package/src/findings/boundary.ts +2 -0
- package/src/findings/completion.ts +181 -84
- package/src/findings/dependencies-and-module-machinery.ts +4 -7
- package/src/findings/emitter.ts +8 -9
- package/src/findings/emitters.ts +7 -7
- package/src/findings/interfaces.ts +4 -6
- package/src/findings/intra-function.ts +13 -11
- package/src/findings/non-code-artifacts.ts +9 -11
- package/src/findings/reads-and-writes.ts +20 -16
- package/src/findings/self-check.ts +11 -17
- package/src/findings/unused-declarations.ts +45 -32
- package/src/findings/visibility-narrowing.ts +20 -17
- 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 +7 -4
- package/src/git-ignore.ts +50 -26
- package/src/graph.ts +5 -3
- package/src/host.ts +12 -5
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/index.ts +1 -1
- package/src/injection-container.ts +1 -1
- package/src/intra-function-parts.ts +73 -44
- package/src/inventory.ts +2 -20
- 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} +128 -85
- package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +33 -76
- 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 +122 -4
- package/src/mdx-entries.ts +3 -26
- package/src/outside-tests.ts +1 -1
- package/src/partial.ts +3 -2
- package/src/paths.ts +27 -15
- package/src/position.ts +2 -1
- package/src/print.ts +12 -92
- package/src/product.ts +18 -0
- package/src/query.ts +11 -0
- package/src/ref.ts +1 -0
- package/src/references.ts +52 -31
- package/src/report-schema.ts +68 -0
- package/src/report.ts +101 -224
- 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 -1114
- package/src/roots.ts +339 -104
- package/src/run.ts +75 -91
- package/src/schema.ts +391 -388
- package/src/scope.ts +46 -138
- package/src/serialization-contract.ts +763 -22
- package/src/session.ts +70 -54
- package/src/source-files.ts +1 -1
- package/src/suppress-file.ts +24 -33
- package/src/suppress-inline.ts +2 -2
- package/src/suppress.ts +43 -58
- package/src/sweep.ts +23 -9
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +15 -25
- package/src/test-support.ts +17 -13
- 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 +18 -288
- package/src/unseen-loads.ts +134 -0
- package/src/unwrap.ts +34 -0
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +224 -162
- package/src/verbs/describe.ts +14 -15
- package/src/verbs/explain.ts +10 -11
- 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 +74 -64
- package/dist/src/base-url.js +0 -18
- package/dist/src/discover.js +0 -468
- 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/base-url.ts +0 -24
- package/src/discover.ts +0 -653
- 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,7 +1,9 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.js";
|
|
1
2
|
import { positionKey } from "../position.js";
|
|
2
3
|
import { declarationKey } from "../ref.js";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
import { bytewise } from "../text/compare.js";
|
|
5
|
+
/** The containers whose type parameters `DS1006` reports: a function, a class method and a type alias. */
|
|
6
|
+
const SIGNATURES = new Set(["function", "method", "type"]);
|
|
5
7
|
function indexed(input) {
|
|
6
8
|
const { matrix, sweep } = input.swept;
|
|
7
9
|
return {
|
|
@@ -75,7 +77,7 @@ function writeOnlyMessage(run, namedBy, symbol, held) {
|
|
|
75
77
|
: "never read";
|
|
76
78
|
const cascade = [
|
|
77
79
|
...parametersOnlyNamedBy(run, namedBy, symbol).map((name) => `type parameter ${name}`),
|
|
78
|
-
...[...held.alone].sort().map((name) => `import ${name}`),
|
|
80
|
+
...[...held.alone].sort(bytewise).map((name) => `import ${name}`),
|
|
79
81
|
];
|
|
80
82
|
const word = SUBJECT_WORDS[symbol.kind] ?? "member";
|
|
81
83
|
const message = `${word} ${symbol.name} is written ${writes} and ${read}`;
|
|
@@ -133,7 +135,7 @@ function writeOnlySymbols(run) {
|
|
|
133
135
|
run.candidates.has(symbol.id)) {
|
|
134
136
|
continue;
|
|
135
137
|
}
|
|
136
|
-
found.push(findingAbout(symbol,
|
|
138
|
+
found.push(findingAbout(symbol, CODE.writeOnlySymbol, writeOnlyMessage(run, namedBy, symbol, held), {
|
|
137
139
|
writePositions: held.writes,
|
|
138
140
|
}));
|
|
139
141
|
}
|
|
@@ -157,23 +159,24 @@ function unnamed(run, kind) {
|
|
|
157
159
|
});
|
|
158
160
|
}
|
|
159
161
|
/**
|
|
160
|
-
* `
|
|
162
|
+
* `DS1005`: a member of an enum declaration that nothing names. A member an enum-group
|
|
161
163
|
* record holds back is no candidate, so a member a conversion can produce never reaches
|
|
162
164
|
* this kind.
|
|
163
165
|
*/
|
|
164
166
|
function unusedEnumMembers(run) {
|
|
165
|
-
return unnamed(run, "enum-member").map((symbol) => findingAbout(symbol,
|
|
167
|
+
return unnamed(run, "enum-member").map((symbol) => findingAbout(symbol, CODE.unusedEnumMember, `enum member ${symbol.name} is named nowhere`));
|
|
166
168
|
}
|
|
167
169
|
/**
|
|
168
|
-
* `
|
|
169
|
-
* declaration's signature nor its body names.
|
|
170
|
-
* a
|
|
171
|
-
*
|
|
170
|
+
* `DS1006`: a type parameter of a function, a class method or a type alias that neither
|
|
171
|
+
* the declaration's signature nor its body names. An alias names no type of its own, so
|
|
172
|
+
* a parameter its type never names makes no two instantiations distinct. One of a class,
|
|
173
|
+
* an interface or a method signature is never reported: a class or an interface merges
|
|
174
|
+
* only with declarations of the same parameters.
|
|
172
175
|
*/
|
|
173
176
|
function unusedTypeParameters(run) {
|
|
174
177
|
return unnamed(run, "type-parameter")
|
|
175
178
|
.filter((symbol) => SIGNATURES.has(run.symbols.get(symbol.parent)?.kind ?? ""))
|
|
176
|
-
.map((symbol) => findingAbout(symbol,
|
|
179
|
+
.map((symbol) => findingAbout(symbol, CODE.unusedTypeParameter, `type parameter ${symbol.name} is named in neither its signature nor its body`));
|
|
177
180
|
}
|
|
178
181
|
/**
|
|
179
182
|
* The keys of the declarations the write-only kind reports, whatever the
|
|
@@ -184,8 +187,8 @@ export function writeOnlyDeclarations(input) {
|
|
|
184
187
|
return new Set(writeOnlySymbols(indexed(input)).map((finding) => declarationKey(finding.symbol.ref, finding.position.path)));
|
|
185
188
|
}
|
|
186
189
|
/**
|
|
187
|
-
* The findings of the reads-and-writes
|
|
188
|
-
* each code's in site order.
|
|
190
|
+
* The findings of the reads-and-writes kinds, `DS1301`, `DS1005` and `DS1006`, in that
|
|
191
|
+
* order and each code's in site order.
|
|
189
192
|
*/
|
|
190
193
|
export const readsAndWrites = (input) => {
|
|
191
194
|
const run = indexed(input);
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.js";
|
|
1
2
|
import { REPOSITORY_DOCUMENT } from "../config.js";
|
|
2
3
|
import { FIXED_SEVERITY_CODES } from "../kinds.js";
|
|
3
|
-
import { isDependencyCode, isUnsuppressibleCode,
|
|
4
|
-
|
|
5
|
-
const UNMATCHED_ROOT = "DS1704";
|
|
6
|
-
/** The code a configured declaration that names nothing is reported under. */
|
|
7
|
-
const UNMATCHED_DECLARATION = "DS1706";
|
|
4
|
+
import { isDependencyCode, isUnsuppressibleCode, } from "../suppress.js";
|
|
5
|
+
import { bytewise } from "../text/compare.js";
|
|
8
6
|
/** The subject kind of a finding about a configured declaration. */
|
|
9
7
|
const DECLARATION_SUBJECT = "configured-declaration";
|
|
10
8
|
/** The subject kind of a finding about a suppression record. */
|
|
@@ -63,7 +61,7 @@ function suppressionRef(symbol, path, files) {
|
|
|
63
61
|
}
|
|
64
62
|
/** One finding per refused suppression: the grammar's reason refusal and its scope refusal. */
|
|
65
63
|
function refusedSuppressions(refusals, files) {
|
|
66
|
-
return refusals.map((refused) => aboutSuppression(refused.reported, suppressionRef(refused.symbol, refused.path, files), refused.site, `${WRITTEN[refused.mechanism]} for ${refused.code} ${refused.reported ===
|
|
64
|
+
return refusals.map((refused) => aboutSuppression(refused.reported, suppressionRef(refused.symbol, refused.path, files), refused.site, `${WRITTEN[refused.mechanism]} for ${refused.code} ${refused.reported === CODE.suppressionWithoutReason
|
|
67
65
|
? "carries no reason"
|
|
68
66
|
: "names a symbol and no path"}`, { mechanism: refused.mechanism, entry: writtenEntry(refused) }));
|
|
69
67
|
}
|
|
@@ -83,10 +81,10 @@ export function staleSuppressions(stale, files) {
|
|
|
83
81
|
bySite.set(key, held);
|
|
84
82
|
}
|
|
85
83
|
return [...bySite.values()].map(({ first, records }) => {
|
|
86
|
-
const codes = [...new Set(records.map((one) => one.record.code))].sort();
|
|
84
|
+
const codes = [...new Set(records.map((one) => one.record.code))].sort(bytewise);
|
|
87
85
|
const reports = [...new Set(records.flatMap((one) => one.reports))]
|
|
88
86
|
.filter((code) => !codes.includes(code))
|
|
89
|
-
.sort();
|
|
87
|
+
.sort(bytewise);
|
|
90
88
|
const rows = codes.filter(isUnsuppressibleCode);
|
|
91
89
|
const dependencies = first.mechanism === "inline" ? codes.filter(isDependencyCode) : [];
|
|
92
90
|
const settable = rows.filter((code) => !FIXED_SEVERITY_CODES.includes(code));
|
|
@@ -112,7 +110,7 @@ export function staleSuppressions(stale, files) {
|
|
|
112
110
|
? ""
|
|
113
111
|
: `, and ${declared}`;
|
|
114
112
|
return {
|
|
115
|
-
code:
|
|
113
|
+
code: CODE.staleSuppression,
|
|
116
114
|
mechanism: first.mechanism,
|
|
117
115
|
entry: {
|
|
118
116
|
code: first.code,
|
|
@@ -141,7 +139,7 @@ function documentPosition(document) {
|
|
|
141
139
|
*/
|
|
142
140
|
function unmatchedDeclarationFindings(unmatched) {
|
|
143
141
|
return unmatched.map((one) => ({
|
|
144
|
-
code:
|
|
142
|
+
code: CODE.unmatchedConfiguredDeclaration,
|
|
145
143
|
position: documentPosition(one.document),
|
|
146
144
|
symbol: { ref: one.ref, kind: DECLARATION_SUBJECT, name: one.ref, sizeLines: 1 },
|
|
147
145
|
message: `${one.key} entry names no declaration in any project`,
|
|
@@ -157,7 +155,7 @@ function unmatchedDeclarationFindings(unmatched) {
|
|
|
157
155
|
export function unmatchedRootFindings(sources, document) {
|
|
158
156
|
const position = documentPosition(document);
|
|
159
157
|
return sources.map((source) => ({
|
|
160
|
-
code:
|
|
158
|
+
code: CODE.unmatchedRoot,
|
|
161
159
|
position,
|
|
162
160
|
symbol: { ref: source, kind: ROOT_SUBJECT, name: source, sizeLines: 1 },
|
|
163
161
|
message: source.includes("*") || source.includes("?")
|
|
@@ -166,7 +164,7 @@ export function unmatchedRootFindings(sources, document) {
|
|
|
166
164
|
}));
|
|
167
165
|
}
|
|
168
166
|
/**
|
|
169
|
-
* The findings of the
|
|
167
|
+
* The findings of the configuration family, `DS9000` to `DS9099`: a suppression refused for
|
|
170
168
|
* carrying no reason or naming no path, and a configured root or declaration that named
|
|
171
169
|
* nothing. A
|
|
172
170
|
* suppression that matched nothing is a record of the report's stale suppressions, which
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.js";
|
|
2
|
+
import { testsUnseenAPI } from "./completion.js";
|
|
1
3
|
import { unreachableExports } from "./visibility-narrowing.js";
|
|
2
4
|
/** The kinds of declaration that are members of a class, an interface, a type or an enum. */
|
|
3
5
|
const MEMBERS = new Set([
|
|
@@ -7,17 +9,17 @@ const MEMBERS = new Set([
|
|
|
7
9
|
"enum-member",
|
|
8
10
|
]);
|
|
9
11
|
/**
|
|
10
|
-
* The kinds of declaration no code of this
|
|
12
|
+
* The kinds of declaration no code of this emitter reports: an interface and a method an
|
|
11
13
|
* interface declares are the interface kinds' subjects, and a type parameter is the
|
|
12
14
|
* subject of the unused-type-parameter kind or of none.
|
|
13
15
|
*/
|
|
14
|
-
const
|
|
16
|
+
const NOT_REPORTED_HERE = new Set([
|
|
15
17
|
"file",
|
|
16
18
|
"interface",
|
|
17
19
|
"interface-method",
|
|
18
20
|
"type-parameter",
|
|
19
21
|
]);
|
|
20
|
-
/** The reader's word for each kind of declaration a finding of
|
|
22
|
+
/** The reader's word for each kind of declaration a finding of this emitter names. */
|
|
21
23
|
const WORDS = {
|
|
22
24
|
file: "file",
|
|
23
25
|
function: "function",
|
|
@@ -41,15 +43,16 @@ const TEST_OF_DEAD_CODE = "every target symbol this test references is reported
|
|
|
41
43
|
* The code one candidate is reported under, or none, each symbol once under the most
|
|
42
44
|
* specific code: a test of dead code first; no other test-file declaration or enum
|
|
43
45
|
* member nothing names; a test-support declaration test code references as test-only
|
|
44
|
-
* use; then deprecation, a test reference,
|
|
45
|
-
* unused export being the
|
|
46
|
+
* use; then deprecation, a test reference, or only dead production references beside a
|
|
47
|
+
* test that stays, and the unreferenced codes, an unimportable unused export being the
|
|
48
|
+
* unreachable kind's.
|
|
46
49
|
*/
|
|
47
50
|
function codeOf(judged) {
|
|
48
51
|
const { candidate, symbol } = judged;
|
|
49
52
|
if (candidate.testOfDeadCode) {
|
|
50
|
-
return
|
|
53
|
+
return judged.testsUnseenAPI ? undefined : CODE.testOfDeadCode;
|
|
51
54
|
}
|
|
52
|
-
if ((judged.test && !candidate.unreferencedTest) ||
|
|
55
|
+
if ((judged.test && !candidate.unreferencedTest) || NOT_REPORTED_HERE.has(symbol.kind)) {
|
|
53
56
|
return undefined;
|
|
54
57
|
}
|
|
55
58
|
const referenced = candidate.productionRefs + candidate.testRefs > 0;
|
|
@@ -60,28 +63,30 @@ function codeOf(judged) {
|
|
|
60
63
|
return unreferencedCode(symbol);
|
|
61
64
|
}
|
|
62
65
|
if (candidate.heldByTests === candidate.id) {
|
|
63
|
-
return
|
|
66
|
+
return CODE.testOnlyUse;
|
|
64
67
|
}
|
|
65
68
|
if (candidate.productionRefs === 0 && judged.deprecated) {
|
|
66
|
-
return
|
|
69
|
+
return CODE.deprecatedUnused;
|
|
67
70
|
}
|
|
68
|
-
if (candidate.
|
|
69
|
-
|
|
71
|
+
if (candidate.testRefs > 0 &&
|
|
72
|
+
(candidate.productionRefs === 0 ||
|
|
73
|
+
(candidate.relation === "reachability" && candidate.keptByTests))) {
|
|
74
|
+
return CODE.testOnlyUse;
|
|
70
75
|
}
|
|
71
76
|
if (MEMBERS.has(symbol.kind)) {
|
|
72
|
-
return
|
|
77
|
+
return CODE.unusedMember;
|
|
73
78
|
}
|
|
74
79
|
if (!symbol.exported) {
|
|
75
|
-
return
|
|
80
|
+
return CODE.unusedUnexported;
|
|
76
81
|
}
|
|
77
|
-
return judged.unreachable ? undefined :
|
|
82
|
+
return judged.unreachable ? undefined : CODE.unusedExported;
|
|
78
83
|
}
|
|
79
84
|
/** The code a declaration nothing references carries, by its shape and its visibility. */
|
|
80
85
|
function unreferencedCode(symbol) {
|
|
81
86
|
if (MEMBERS.has(symbol.kind)) {
|
|
82
|
-
return
|
|
87
|
+
return CODE.unusedMember;
|
|
83
88
|
}
|
|
84
|
-
return symbol.exported ?
|
|
89
|
+
return symbol.exported ? CODE.unusedExported : CODE.unusedUnexported;
|
|
85
90
|
}
|
|
86
91
|
/** The visibility a member's message names, where it narrows the member's reach. */
|
|
87
92
|
function visibilityWord(symbol) {
|
|
@@ -104,14 +109,16 @@ function messageOf(code, judged) {
|
|
|
104
109
|
const { candidate, symbol } = judged;
|
|
105
110
|
const word = WORDS[symbol.kind];
|
|
106
111
|
switch (code) {
|
|
107
|
-
case
|
|
112
|
+
case CODE.testOfDeadCode:
|
|
108
113
|
return TEST_OF_DEAD_CODE;
|
|
109
|
-
case
|
|
114
|
+
case CODE.deprecatedUnused:
|
|
110
115
|
return `deprecated ${word} has no production reference`;
|
|
111
|
-
case
|
|
112
|
-
return
|
|
116
|
+
case CODE.testOnlyUse:
|
|
117
|
+
return candidate.productionRefs === 0
|
|
118
|
+
? `${word} is referenced only from test files and never from production code`
|
|
119
|
+
: `${word} is referenced only from test files and from declarations that are themselves dead`;
|
|
113
120
|
}
|
|
114
|
-
const subject = code ===
|
|
121
|
+
const subject = code === CODE.unusedMember
|
|
115
122
|
? `${visibilityWord(symbol)}${word}`
|
|
116
123
|
: symbol.kind === "export-alias"
|
|
117
124
|
? word
|
|
@@ -119,7 +126,7 @@ function messageOf(code, judged) {
|
|
|
119
126
|
if (candidate.relation === "reachability") {
|
|
120
127
|
return `${subject} is referenced only from declarations that are themselves dead`;
|
|
121
128
|
}
|
|
122
|
-
if (code ===
|
|
129
|
+
if (code === CODE.unusedExported) {
|
|
123
130
|
return `${subject} has no reference in the target and none from any loaded consumer`;
|
|
124
131
|
}
|
|
125
132
|
return `${subject} has no reference in the target`;
|
|
@@ -161,22 +168,22 @@ function brandMembers(input) {
|
|
|
161
168
|
}
|
|
162
169
|
/** The kinds of member a brand is: a data member of a type or of a class. */
|
|
163
170
|
const BRANDS = new Set(["type-member", "class-member"]);
|
|
164
|
-
/** The findings of the unused-
|
|
171
|
+
/** The findings of the unused-declaration kinds, `DS1001` to `DS1004` and `DS1102` to `DS1104`. */
|
|
165
172
|
export const unusedDeclarations = (input) => [
|
|
166
|
-
...
|
|
173
|
+
...emitterFindings(input, false),
|
|
167
174
|
...unusedImports(input),
|
|
168
175
|
];
|
|
169
176
|
/**
|
|
170
|
-
* The import bindings their file never reads
|
|
171
|
-
*
|
|
177
|
+
* The import bindings their file never reads. A generated file's are the generator's to
|
|
178
|
+
* remove unless the run includes generated files.
|
|
172
179
|
*/
|
|
173
180
|
function unusedImports(input) {
|
|
174
|
-
const generated = input.config.analysis.
|
|
181
|
+
const generated = input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
175
182
|
return input.intraFunction.imports.flatMap((binding) => binding.used || generated?.has(binding.position.path) === true
|
|
176
183
|
? []
|
|
177
184
|
: [
|
|
178
185
|
{
|
|
179
|
-
code:
|
|
186
|
+
code: CODE.unusedImport,
|
|
180
187
|
position: binding.position,
|
|
181
188
|
symbol: { ref: binding.ref, kind: "import", name: binding.name, sizeLines: 1 },
|
|
182
189
|
message: `import binding ${binding.name} is never read in its file`,
|
|
@@ -188,12 +195,13 @@ function unusedImports(input) {
|
|
|
188
195
|
* a record on such a member is matched against, since the report carries the container's
|
|
189
196
|
* finding rather than one of the member's own.
|
|
190
197
|
*/
|
|
191
|
-
export const fallenMembers = (input) =>
|
|
192
|
-
/**
|
|
193
|
-
function
|
|
198
|
+
export const fallenMembers = (input) => emitterFindings(input, true);
|
|
199
|
+
/** This emitter's findings, or, where `fallen` is set, its dead containers' members' alone. */
|
|
200
|
+
function emitterFindings(input, fallen) {
|
|
194
201
|
const union = input.swept.matrix.union;
|
|
195
202
|
const dead = new Set(input.swept.sweep.candidates.map((candidate) => candidate.id));
|
|
196
203
|
const unreachable = unreachableExports(input);
|
|
204
|
+
const unseenAPI = testsUnseenAPI(input);
|
|
197
205
|
let brands;
|
|
198
206
|
const found = [];
|
|
199
207
|
for (const candidate of input.swept.sweep.candidates) {
|
|
@@ -212,10 +220,11 @@ function familyFindings(input, fallen) {
|
|
|
212
220
|
test: union.test[at] === true,
|
|
213
221
|
deprecated: input.deprecated.has(symbol.id),
|
|
214
222
|
unreachable: unreachable.has(symbol.id),
|
|
223
|
+
testsUnseenAPI: candidate.testOfDeadCode && unseenAPI(candidate.id),
|
|
215
224
|
};
|
|
216
225
|
const code = codeOf(judged);
|
|
217
226
|
if (code === undefined ||
|
|
218
|
-
(code ===
|
|
227
|
+
(code === CODE.unusedMember && (brands ??= brandMembers(input)).has(symbol.id))) {
|
|
219
228
|
continue;
|
|
220
229
|
}
|
|
221
230
|
const endLine = Math.max(symbol.position.line, symbol.endLine);
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
+
import { CODE } from "../contract/contract.generated.js";
|
|
1
2
|
import { OUTSIDE } from "../graph.js";
|
|
2
3
|
import { declarationKey } from "../ref.js";
|
|
3
4
|
import { runnerCalls } from "../sweep.js";
|
|
4
5
|
import { writeOnlyDeclarations } from "./reads-and-writes.js";
|
|
5
|
-
const UNNECESSARY_EXPORT = "DS1101";
|
|
6
|
-
const UNREACHABLE_EXPORT = "DS1103";
|
|
7
|
-
const REDUNDANT_EXPORT_KEYWORD = "DS1104";
|
|
8
6
|
/**
|
|
9
7
|
* The root kinds by which a consumer importing the package reaches a declaration. A
|
|
10
8
|
* declaration or a file rooted by any other kind is entered by something that names it
|
|
11
9
|
* from outside the reference graph: a runtime, a tool, a framework or the configuration.
|
|
12
10
|
*/
|
|
13
11
|
const PUBLISHING = new Set(["manifest-entry", "published-api"]);
|
|
14
|
-
/** The reader's word for each kind of declaration this
|
|
12
|
+
/** The reader's word for each kind of declaration this emitter reports. */
|
|
15
13
|
const WORDS = {
|
|
16
14
|
function: "function",
|
|
17
15
|
class: "class",
|
|
@@ -34,12 +32,17 @@ const RUN_ONLY = new Set(["configuration-string", "script"]);
|
|
|
34
32
|
function entersByName(kinds) {
|
|
35
33
|
return [...(kinds ?? [])].some((kind) => !PUBLISHING.has(kind) && !RUN_ONLY.has(kind));
|
|
36
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The root kinds whose root of a file enters no export by name: a document's import roots
|
|
37
|
+
* each export it imports, so an export it does not import is judged as in any file.
|
|
38
|
+
*/
|
|
39
|
+
const PER_NAME = new Set(["document-import"]);
|
|
37
40
|
/**
|
|
38
41
|
* Whether the roots of a declaration's file enter it by name: a test runner's root
|
|
39
42
|
* enters only an export the runner calls, so another export of a test file is judged.
|
|
40
43
|
*/
|
|
41
44
|
function fileEntersByName(kinds, symbol) {
|
|
42
|
-
const entering = [...(kinds ?? [])].filter((kind) => !PUBLISHING.has(kind) && !RUN_ONLY.has(kind));
|
|
45
|
+
const entering = [...(kinds ?? [])].filter((kind) => !PUBLISHING.has(kind) && !RUN_ONLY.has(kind) && !PER_NAME.has(kind));
|
|
43
46
|
return (entering.some((kind) => kind !== "test-runner") || (entering.length > 0 && runnerCalls(symbol)));
|
|
44
47
|
}
|
|
45
48
|
/**
|
|
@@ -104,8 +107,8 @@ function wordOf(symbol) {
|
|
|
104
107
|
}
|
|
105
108
|
/** The visibility each narrowing code names, which only the narrowing codes carry. */
|
|
106
109
|
const NARROWER = {
|
|
107
|
-
[
|
|
108
|
-
[
|
|
110
|
+
[CODE.redundantExportKeyword]: "file",
|
|
111
|
+
[CODE.unnecessaryExport]: "package",
|
|
109
112
|
};
|
|
110
113
|
function findingOf(symbol, code, message) {
|
|
111
114
|
const { path, line, column } = symbol.position;
|
|
@@ -148,12 +151,12 @@ function narrowing(facts, at, symbol, world) {
|
|
|
148
151
|
if (facts.published[file] === true && !closed) {
|
|
149
152
|
return undefined;
|
|
150
153
|
}
|
|
151
|
-
return findingOf(symbol,
|
|
154
|
+
return findingOf(symbol, CODE.redundantExportKeyword, `exported ${word} is referenced only inside the file that declares it`);
|
|
152
155
|
case "package":
|
|
153
156
|
if (!closed || !publishes(facts.rootedBy[at])) {
|
|
154
157
|
return undefined;
|
|
155
158
|
}
|
|
156
|
-
return findingOf(symbol,
|
|
159
|
+
return findingOf(symbol, CODE.unnecessaryExport, `exported ${word} is referenced only inside the package that declares it`);
|
|
157
160
|
default:
|
|
158
161
|
return undefined;
|
|
159
162
|
}
|
|
@@ -163,7 +166,7 @@ function narrowing(facts, at, symbol, world) {
|
|
|
163
166
|
* export no code outside the target can import, because the manifest declares
|
|
164
167
|
* `exports` and no root reaches it. Each arm yields to a more specific kind, so one
|
|
165
168
|
* declaration is reported once: a test whose every subject is dead, an interface, a
|
|
166
|
-
* declaration only a test file references, and a deprecated one.
|
|
169
|
+
* declaration only a test file references or a test that stays keeps, and a deprecated one.
|
|
167
170
|
*/
|
|
168
171
|
function unreachable(facts, at, symbol, candidate, input) {
|
|
169
172
|
if (!input.boundary.encapsulated ||
|
|
@@ -171,17 +174,17 @@ function unreachable(facts, at, symbol, candidate, input) {
|
|
|
171
174
|
(facts.rootedBy[at]?.size ?? 0) > 0 ||
|
|
172
175
|
candidate.testOfDeadCode ||
|
|
173
176
|
symbol.kind === "interface" ||
|
|
174
|
-
(candidate.
|
|
177
|
+
(candidate.testRefs > 0 && (candidate.productionRefs === 0 || candidate.keptByTests)) ||
|
|
175
178
|
(candidate.productionRefs === 0 && input.deprecated.has(symbol.id))) {
|
|
176
179
|
return undefined;
|
|
177
180
|
}
|
|
178
181
|
const found = candidate.relation === "reachability"
|
|
179
182
|
? "is referenced only from declarations that are themselves dead"
|
|
180
183
|
: "has no reference in the target";
|
|
181
|
-
return findingOf(symbol,
|
|
184
|
+
return findingOf(symbol, CODE.unreachableExport, `exported ${wordOf(symbol)} ${found}, and nothing outside can import the file that declares it`);
|
|
182
185
|
}
|
|
183
186
|
/**
|
|
184
|
-
* The visibility-narrowing
|
|
187
|
+
* The visibility-narrowing kinds over one run: `DS1501`, `DS1101` and `DS1503`. A finding
|
|
185
188
|
* about a symbol a declared cross-language edge names is reported here like any other, and
|
|
186
189
|
* the edge's evaluation then holds it pending, because the edge stands for a reference from
|
|
187
190
|
* outside the file and the package whose use is decided in the other language. A
|
|
@@ -190,10 +193,10 @@ function unreachable(facts, at, symbol, candidate, input) {
|
|
|
190
193
|
export function narrowings(input) {
|
|
191
194
|
const facts = factsOf(input.swept.matrix.union);
|
|
192
195
|
const { declared, loaded } = input.boundary.consumers;
|
|
193
|
-
const closed = input.config.
|
|
196
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
194
197
|
const world = { closed, writeOnly: writeOnlyDeclarations(input) };
|
|
195
198
|
const candidates = new Map(input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
196
|
-
const generated = input.config.analysis.
|
|
199
|
+
const generated = input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
197
200
|
const findings = [];
|
|
198
201
|
facts.graph.symbols.forEach((symbol, at) => {
|
|
199
202
|
if (generated?.has(symbol.position.path) === true) {
|
|
@@ -229,5 +232,5 @@ export function unreachableExports(input) {
|
|
|
229
232
|
}
|
|
230
233
|
return found;
|
|
231
234
|
}
|
|
232
|
-
/** The findings of the visibility-narrowing
|
|
235
|
+
/** The findings of the visibility-narrowing kinds, `DS1501`, `DS1101` and `DS1503`. */
|
|
233
236
|
export const visibilityNarrowing = narrowings;
|
|
@@ -3,27 +3,20 @@
|
|
|
3
3
|
* applied and the declared edges evaluated, and the stale suppressions, the totals and the
|
|
4
4
|
* verdict a report carries beside them.
|
|
5
5
|
*/
|
|
6
|
+
import { EXIT_CLEAN, EXIT_FINDINGS, EXIT_PENDING } from "./contract/contract.generated.js";
|
|
6
7
|
import { fails } from "./config.js";
|
|
7
8
|
import { decidedFindings } from "./findings/emitters.js";
|
|
8
9
|
import { fileRefsOf, staleSuppressions, } from "./findings/self-check.js";
|
|
9
10
|
import { isDependencySubject, isRowSubject, pairs, suppressionCounts, } from "./suppress.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
export const EXIT_PENDING = 4;
|
|
13
|
-
/** Two strings ordered bytewise. */
|
|
14
|
-
function compare(a, b) {
|
|
15
|
-
if (a === b) {
|
|
16
|
-
return 0;
|
|
17
|
-
}
|
|
18
|
-
return a < b ? -1 : 1;
|
|
19
|
-
}
|
|
11
|
+
import { bytewise } from "./text/compare.js";
|
|
12
|
+
import { PRODUCT } from "./product.js";
|
|
20
13
|
/** The canonical order: path, line, column, code, then the symbol reference. */
|
|
21
14
|
function canonical(a, b) {
|
|
22
|
-
return (
|
|
15
|
+
return (bytewise(a.position.path, b.position.path) ||
|
|
23
16
|
a.position.line - b.position.line ||
|
|
24
17
|
a.position.column - b.position.column ||
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
bytewise(a.code, b.code) ||
|
|
19
|
+
bytewise(a.symbol.ref, b.symbol.ref));
|
|
27
20
|
}
|
|
28
21
|
/**
|
|
29
22
|
* The pass over one run. The emitter table decides every record against every finding the
|
|
@@ -73,7 +66,7 @@ export function verdictOf(result, config) {
|
|
|
73
66
|
if (result.totals.staleSuppressions > 0) {
|
|
74
67
|
return EXIT_FINDINGS;
|
|
75
68
|
}
|
|
76
|
-
const failing = result.findings.some((finding) => fails(finding.severity, config.reporters.
|
|
69
|
+
const failing = result.findings.some((finding) => fails(finding.severity, config.reporters.fail_on));
|
|
77
70
|
return failing ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
78
71
|
}
|
|
79
72
|
/**
|
|
@@ -100,5 +93,5 @@ export function rowKey(row) {
|
|
|
100
93
|
}
|
|
101
94
|
/** The analyzer identity a baseline this analyzer writes names as its rows' provenance. */
|
|
102
95
|
export function analyzerProvenance(version) {
|
|
103
|
-
return { analyzer:
|
|
96
|
+
return { analyzer: PRODUCT, version };
|
|
104
97
|
}
|
|
@@ -16,8 +16,8 @@ import { must } from "./query.js";
|
|
|
16
16
|
/** Every contract's components and bases, resolved in one pass over the entries. */
|
|
17
17
|
function resolveContracts(project, held, contracts) {
|
|
18
18
|
const entries = contracts.flatMap((contract) => [
|
|
19
|
-
...contract.components,
|
|
20
|
-
...contract.bases,
|
|
19
|
+
...(contract.components ?? []),
|
|
20
|
+
...(contract.bases ?? []),
|
|
21
21
|
]);
|
|
22
22
|
const resolved = resolveEntries(project, held, entries);
|
|
23
23
|
let next = 0;
|
|
@@ -28,8 +28,8 @@ function resolveContracts(project, held, contracts) {
|
|
|
28
28
|
};
|
|
29
29
|
return contracts.map((contract) => ({
|
|
30
30
|
contract,
|
|
31
|
-
components: take(contract.components
|
|
32
|
-
bases: take(contract.bases
|
|
31
|
+
components: take(contract.components?.length ?? 0),
|
|
32
|
+
bases: take(contract.bases?.length ?? 0),
|
|
33
33
|
}));
|
|
34
34
|
}
|
|
35
35
|
/** The classes a decorator or a call the contracts' components name makes a component. */
|
|
@@ -160,11 +160,11 @@ function byBases(project, held, chains, contracts) {
|
|
|
160
160
|
*/
|
|
161
161
|
export function frameworkLifecycle(input) {
|
|
162
162
|
const { project, held, targetRoot } = input;
|
|
163
|
-
if (input.ts.
|
|
163
|
+
if (input.ts.lifecycle_contracts.length === 0) {
|
|
164
164
|
return [];
|
|
165
165
|
}
|
|
166
166
|
const chains = aliasChains(project, held);
|
|
167
|
-
const contracts = resolveContracts(project, held, input.ts.
|
|
167
|
+
const contracts = resolveContracts(project, held, input.ts.lifecycle_contracts);
|
|
168
168
|
const components = [
|
|
169
169
|
...byComponents(project, held, chains, contracts),
|
|
170
170
|
...byBases(project, held, chains, contracts),
|
|
@@ -173,7 +173,7 @@ export function frameworkLifecycle(input) {
|
|
|
173
173
|
return [];
|
|
174
174
|
}
|
|
175
175
|
const ids = new Set(components.map((one) => one.id));
|
|
176
|
-
const sides = input.ts.
|
|
176
|
+
const sides = input.ts.lifecycle_contracts.some((contract) => contract.members.some((name) => name.endsWith(":static")))
|
|
177
177
|
? ["instance", "static"]
|
|
178
178
|
: ["instance"];
|
|
179
179
|
const members = sides.map((side) => classMembers(project, held, ids, side));
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
import { CONFIG_MAX_BYTES } from "../contract/config.generated.js";
|
|
8
|
+
import { failureExit } from "../failure.js";
|
|
9
|
+
import { joinPath, relativePath } from "../paths.js";
|
|
10
|
+
import { message } from "../product.js";
|
|
11
|
+
import { utf8Bytes } from "../reporters/sha256.js";
|
|
12
|
+
import { UsageError } from "../usage.js";
|
|
13
|
+
function unreadable(label, reason) {
|
|
14
|
+
return failureExit("unreadable-document", `${label}: ${reason}: make it a regular file inside the target root that the run can read, or remove it`);
|
|
15
|
+
}
|
|
16
|
+
/** Refuses a document past the Contract's bound with the usage code, naming it `label`. */
|
|
17
|
+
export function refuseOverBound(label, text) {
|
|
18
|
+
if (utf8Bytes(text).length > CONFIG_MAX_BYTES) {
|
|
19
|
+
throw new UsageError(`${label}: holds more than ${String(CONFIG_MAX_BYTES)} bytes`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The text of the document `name` at the absolute `targetRoot`, undefined where nothing is
|
|
24
|
+
* there. One whose name resolves outside the target root or to anything but a regular file,
|
|
25
|
+
* or whose read fails, is refused by `refuse` naming it `label`, nothing read from it. A
|
|
26
|
+
* document past the bound is read one byte past it and refused as {@link refuseOverBound} does.
|
|
27
|
+
*/
|
|
28
|
+
export function readTargetDocument(host, targetRoot, name, label = name, refuse = unreadable) {
|
|
29
|
+
const attempt = (read) => {
|
|
30
|
+
try {
|
|
31
|
+
return read();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw refuse(label, message(error));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const path = joinPath(targetRoot, name);
|
|
38
|
+
const kind = attempt(() => host.kindOf(path));
|
|
39
|
+
if (kind === "absent") {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const real = attempt(() => host.realPath(path));
|
|
43
|
+
if (relativePath(attempt(() => host.realPath(targetRoot)), real) === undefined) {
|
|
44
|
+
throw refuse(label, `resolves to ${real}, outside the target root`);
|
|
45
|
+
}
|
|
46
|
+
if (kind !== "file") {
|
|
47
|
+
throw refuse(label, "is not a regular file");
|
|
48
|
+
}
|
|
49
|
+
const text = attempt(() => host.readFile(real, CONFIG_MAX_BYTES + 1));
|
|
50
|
+
refuseOverBound(label, text);
|
|
51
|
+
return text;
|
|
52
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isRecord } from "../json-document.js";
|
|
2
|
+
import { joinPath } from "../paths.js";
|
|
3
|
+
import { message } from "../product.js";
|
|
4
|
+
/** The file a package's manifest is read from. */
|
|
5
|
+
export const MANIFEST = "package.json";
|
|
6
|
+
/** The directory a package manager installs packages into, whose files are no project's own. */
|
|
7
|
+
export const INSTALLED_PACKAGES = "node_modules";
|
|
8
|
+
/** The manifest members a package declares the packages it depends on under. */
|
|
9
|
+
export const DEPENDENCY_SECTIONS = [
|
|
10
|
+
"dependencies",
|
|
11
|
+
"devDependencies",
|
|
12
|
+
"peerDependencies",
|
|
13
|
+
"optionalDependencies",
|
|
14
|
+
];
|
|
15
|
+
/** Whether one path passes through an installed-packages directory. */
|
|
16
|
+
export function inInstalledPackages(path) {
|
|
17
|
+
return path.split("/").includes(INSTALLED_PACKAGES);
|
|
18
|
+
}
|
|
19
|
+
/** One JSON file's text and decoded value, or undefined where it cannot be read or parsed. */
|
|
20
|
+
export function readDocument(host, path) {
|
|
21
|
+
let text;
|
|
22
|
+
try {
|
|
23
|
+
text = host.readFile(path);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return { text, value: JSON.parse(text) };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** One JSON file's decoded value, or undefined where it cannot be read or parsed. */
|
|
36
|
+
export function readJSON(host, path) {
|
|
37
|
+
return readDocument(host, path)?.value;
|
|
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, dir) {
|
|
45
|
+
const value = readJSON(host, joinPath(dir, MANIFEST));
|
|
46
|
+
return isRecord(value) ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
/** The manifest in one directory, as {@link ManifestRead} tells its states apart. */
|
|
49
|
+
export function readManifestAt(host, dir) {
|
|
50
|
+
if (!holdsManifest(host, dir)) {
|
|
51
|
+
return { kind: "absent" };
|
|
52
|
+
}
|
|
53
|
+
let text;
|
|
54
|
+
try {
|
|
55
|
+
text = host.readFile(joinPath(dir, MANIFEST));
|
|
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" };
|
|
66
|
+
}
|
|
67
|
+
return isRecord(value) ? { kind: "read", manifest: value } : { kind: "malformed" };
|
|
68
|
+
}
|
|
69
|
+
/** Whether one directory holds a manifest file. */
|
|
70
|
+
export function holdsManifest(host, dir) {
|
|
71
|
+
return host.kindOf(joinPath(dir, MANIFEST)) === "file";
|
|
72
|
+
}
|