@cplieger/deadset-ts 8.0.0-dev.7 → 8.0.0-dev.8
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/node-host.js +21 -3
- package/dist/src/analysis.js +13 -14
- package/dist/src/config.js +6 -83
- package/dist/src/configured-declarations.js +15 -20
- package/dist/src/consumers.js +2 -2
- package/dist/src/contract/config.generated.js +544 -0
- package/dist/src/contract/report.generated.js +3 -2
- package/dist/src/discover.js +1 -1
- package/dist/src/edges.js +36 -112
- package/dist/src/exempt-classes.js +0 -2
- package/dist/src/exempt.js +1 -1
- package/dist/src/findings/completion.js +5 -4
- package/dist/src/findings/emitters.js +1 -1
- package/dist/src/findings/intra-function.js +1 -1
- package/dist/src/findings/non-code-artifacts.js +2 -2
- package/dist/src/findings/unused-declarations.js +1 -1
- package/dist/src/findings/visibility-narrowing.js +2 -2
- package/dist/src/findings-pass.js +1 -1
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +59 -0
- package/dist/src/injection-container.js +1 -1
- package/dist/src/json-document.js +194 -67
- package/dist/src/kinds.js +0 -7
- package/dist/src/print.js +10 -79
- package/dist/src/report-schema.js +3 -224
- package/dist/src/resolve.js +245 -715
- package/dist/src/run.js +48 -55
- package/dist/src/schema.js +320 -344
- package/dist/src/scope.js +33 -101
- package/dist/src/serialization-contract.js +1 -1
- package/dist/src/suppress-file.js +15 -17
- package/dist/src/suppress.js +3 -3
- package/dist/src/template-field.js +4 -4
- package/dist/src/usage.js +10 -0
- package/dist/src/verbs/analyze.js +12 -15
- package/dist/src/verbs/explain.js +3 -3
- package/dist/src/verbs/print-projects.js +2 -2
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/analysis.ts +30 -26
- package/src/config.ts +31 -298
- package/src/configured-declarations.ts +18 -23
- package/src/consumers.ts +4 -4
- package/src/contract/config.generated.ts +798 -0
- package/src/contract/report.generated.ts +3 -2
- package/src/discover.ts +1 -1
- package/src/edges.ts +39 -124
- package/src/exempt-classes.ts +0 -5
- package/src/exempt.ts +4 -4
- package/src/findings/completion.ts +5 -6
- package/src/findings/emitter.ts +2 -2
- package/src/findings/emitters.ts +1 -1
- package/src/findings/intra-function.ts +1 -1
- package/src/findings/non-code-artifacts.ts +4 -4
- package/src/findings/unused-declarations.ts +1 -1
- package/src/findings/visibility-narrowing.ts +2 -2
- package/src/findings-pass.ts +3 -3
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +74 -0
- package/src/host.ts +12 -5
- package/src/injection-container.ts +1 -1
- package/src/json-document.ts +236 -76
- package/src/kinds.ts +0 -8
- package/src/print.ts +11 -92
- package/src/report-schema.ts +3 -299
- package/src/resolve.ts +281 -1105
- package/src/run.ts +50 -63
- package/src/schema.ts +391 -389
- package/src/scope.ts +39 -119
- package/src/serialization-contract.ts +1 -1
- package/src/suppress-file.ts +15 -20
- package/src/suppress.ts +3 -3
- package/src/template-field.ts +10 -7
- package/src/usage.ts +10 -0
- package/src/verbs/analyze.ts +12 -23
- package/src/verbs/explain.ts +3 -7
- package/src/verbs/print-projects.ts +2 -2
- package/src/verbs/verb.ts +2 -2
package/dist/src/edges.js
CHANGED
|
@@ -4,144 +4,68 @@
|
|
|
4
4
|
* never resolves the paired symbol: the other language's analyzer decides whether the
|
|
5
5
|
* pairing is a use, and the merge reads the two evaluations together.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { EDGES_SCHEMA } from "./contract/config.generated.js";
|
|
8
|
+
import { readTargetDocument } from "./fs/document.js";
|
|
9
|
+
import { dotted, placeIn, positionAt, readStrict, StrictJSONError } from "./json-document.js";
|
|
10
10
|
import { positionKey } from "./position.js";
|
|
11
|
+
import { firstRefusal } from "./schema.js";
|
|
11
12
|
import { isPartSubject } from "./suppress.js";
|
|
12
13
|
import { bytewise } from "./text/compare.js";
|
|
13
|
-
import {
|
|
14
|
+
import { UsageError } from "./usage.js";
|
|
14
15
|
/** The name and the location the Contract fixes for the edges document. */
|
|
15
16
|
export const EDGES_FILE = "deadset-edges.json";
|
|
16
17
|
/** The language prefix of every reference this analyzer evaluates. */
|
|
17
18
|
const OWN_PREFIX = "ts://";
|
|
18
|
-
/** The closed key list of the document and of one edge. */
|
|
19
|
-
const DOCUMENT_KEYS = new Set(["description", "edges"]);
|
|
20
|
-
const EDGE_KEYS = new Set(["id", "because", "provides", "used_by"]);
|
|
21
|
-
/** An edge identifier: solidus-separated segments of ASCII letters, digits, `_`, `.` and `-`. */
|
|
22
|
-
const ID_FORM = /^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/u;
|
|
23
|
-
/** A reference naming one symbol of some language: `<language>://<scope>#<fragment>`. */
|
|
24
|
-
const REFERENCE_FORM = /^[a-z][a-z0-9]*:\/\/[^ \t\r\n#]+#[^ \t\r\n]*$/u;
|
|
25
|
-
const DOCUMENT_WANT = "an object carrying the edges array, and an optional description";
|
|
26
|
-
const EDGE_WANT = "an edge naming id, provides and used_by, each a string, and an optional because";
|
|
27
|
-
const ID_WANT = "one or more solidus-separated segments of ASCII letters, digits, underscore, full stop and hyphen";
|
|
28
|
-
const REFERENCE_WANT = "a stable symbol reference naming one symbol exactly, <language>://<scope>#<fragment>";
|
|
29
19
|
/** An edges document the Contract refuses: a malformed input, answered with the usage code. */
|
|
30
|
-
class EdgesError extends
|
|
31
|
-
constructor(at,
|
|
32
|
-
super(
|
|
20
|
+
class EdgesError extends UsageError {
|
|
21
|
+
constructor(at, reason) {
|
|
22
|
+
super(`${at}: ${reason}`);
|
|
33
23
|
this.name = "EdgesError";
|
|
34
24
|
}
|
|
35
25
|
}
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
* would name a set of symbols rather than one.
|
|
39
|
-
*/
|
|
40
|
-
function carriesWildcard(ref) {
|
|
41
|
-
let quoted = false;
|
|
42
|
-
let computed = false;
|
|
43
|
-
let escaped = false;
|
|
44
|
-
for (const char of ref) {
|
|
45
|
-
if (escaped) {
|
|
46
|
-
escaped = false;
|
|
47
|
-
}
|
|
48
|
-
else if (quoted && char === "\\") {
|
|
49
|
-
escaped = true;
|
|
50
|
-
}
|
|
51
|
-
else if (quoted) {
|
|
52
|
-
quoted = char !== "'";
|
|
53
|
-
}
|
|
54
|
-
else if (computed) {
|
|
55
|
-
computed = char !== "]";
|
|
56
|
-
}
|
|
57
|
-
else if (char === "'") {
|
|
58
|
-
quoted = true;
|
|
59
|
-
}
|
|
60
|
-
else if (char === "[") {
|
|
61
|
-
computed = true;
|
|
62
|
-
}
|
|
63
|
-
else if (char === "*") {
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
/** One edge, checked: the closed key list, the value types and the value forms. */
|
|
70
|
-
function readEdge(value, at) {
|
|
71
|
-
const refuse = (text, want) => {
|
|
72
|
-
throw new EdgesError(at, text, want);
|
|
73
|
-
};
|
|
74
|
-
if (!isRecord(value)) {
|
|
75
|
-
return refuse(JSON.stringify(value), EDGE_WANT);
|
|
76
|
-
}
|
|
77
|
-
for (const [key, held] of Object.entries(value)) {
|
|
78
|
-
if (!EDGE_KEYS.has(key) || typeof held !== "string") {
|
|
79
|
-
refuse(`the member ${JSON.stringify(key)} holding ${JSON.stringify(held)}`, EDGE_WANT);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const edge = value;
|
|
83
|
-
const id = edge["id"] ?? refuse("an edge naming no id", EDGE_WANT);
|
|
84
|
-
const provides = edge["provides"] ?? refuse("an edge naming no provides side", EDGE_WANT);
|
|
85
|
-
const usedBy = edge["used_by"] ?? refuse("an edge naming no used_by side", EDGE_WANT);
|
|
86
|
-
if (!ID_FORM.test(id)) {
|
|
87
|
-
refuse(JSON.stringify(id), ID_WANT);
|
|
88
|
-
}
|
|
89
|
-
for (const ref of [provides, usedBy]) {
|
|
90
|
-
if (!REFERENCE_FORM.test(ref) || carriesWildcard(ref)) {
|
|
91
|
-
refuse(JSON.stringify(ref), REFERENCE_WANT);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return { id, provides, usedBy };
|
|
95
|
-
}
|
|
26
|
+
/** The sides of one edge, in the order a reader yields them. */
|
|
27
|
+
const SIDE_NAMES = ["provides", "used_by"];
|
|
96
28
|
/**
|
|
97
29
|
* Every side of a declared edge that names a symbol of this language, in document order,
|
|
98
30
|
* `provides` before `used_by` within an edge. An absent document declares no edge. The
|
|
99
|
-
* document is strict JSON
|
|
100
|
-
* {@link EdgesError}.
|
|
31
|
+
* document is strict JSON held to the Contract's edges schema, and a defect in it is an
|
|
32
|
+
* {@link EdgesError} at the line and column of the edge it is in, where it is in one.
|
|
101
33
|
*/
|
|
102
34
|
export function readEdgeSides(host, targetRoot) {
|
|
103
|
-
const
|
|
104
|
-
if (
|
|
35
|
+
const text = readTargetDocument(host, targetRoot, EDGES_FILE);
|
|
36
|
+
if (text === undefined) {
|
|
105
37
|
return [];
|
|
106
38
|
}
|
|
107
|
-
|
|
108
|
-
let parsed;
|
|
39
|
+
let read;
|
|
109
40
|
try {
|
|
110
|
-
|
|
41
|
+
read = readStrict(text, "edges");
|
|
111
42
|
}
|
|
112
43
|
catch (error) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
throw new EdgesError(EDGES_FILE,
|
|
117
|
-
}
|
|
118
|
-
const walk = walkDocument(text, "edges");
|
|
119
|
-
if (walk.twice !== undefined) {
|
|
120
|
-
throw new EdgesError(positionKey(positionAt(text, EDGES_FILE, walk.twice.offset)), `the member ${JSON.stringify(walk.twice.name)} is written twice, so neither value is chosen`, DOCUMENT_WANT);
|
|
44
|
+
if (!(error instanceof StrictJSONError)) {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
throw new EdgesError(positionKey(positionAt(text, EDGES_FILE, error.offset)), error.message);
|
|
121
48
|
}
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
49
|
+
const value = read.value;
|
|
50
|
+
const refused = firstRefusal(EDGES_SCHEMA, value);
|
|
51
|
+
if (refused !== undefined) {
|
|
52
|
+
const place = placeIn(value, refused.pointer, refused.member);
|
|
53
|
+
const offset = place.length > 1 ? read.starts.get(place[1] ?? "") : undefined;
|
|
54
|
+
const at = offset === undefined ? EDGES_FILE : positionKey(positionAt(text, EDGES_FILE, offset));
|
|
55
|
+
const where = dotted(place);
|
|
56
|
+
throw new EdgesError(at, where === "" ? refused.reason : `${where}: ${refused.reason}`);
|
|
129
57
|
}
|
|
58
|
+
const edges = value.edges;
|
|
130
59
|
const sides = [];
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
["used_by", edge.usedBy],
|
|
138
|
-
];
|
|
139
|
-
for (const [side, symbol] of named) {
|
|
140
|
-
if (symbol.startsWith(OWN_PREFIX)) {
|
|
141
|
-
sides.push({ edge: edge.id, side, symbol });
|
|
60
|
+
for (const id of read.starts.keys()) {
|
|
61
|
+
const edge = String(id);
|
|
62
|
+
for (const side of SIDE_NAMES) {
|
|
63
|
+
const symbol = edges[edge]?.[side];
|
|
64
|
+
if (symbol?.startsWith(OWN_PREFIX) === true) {
|
|
65
|
+
sides.push({ edge, side, symbol });
|
|
142
66
|
}
|
|
143
67
|
}
|
|
144
|
-
}
|
|
68
|
+
}
|
|
145
69
|
return sides;
|
|
146
70
|
}
|
|
147
71
|
/**
|
|
@@ -11,5 +11,3 @@ const VISIBILITY = new Map(TS_ROWS.map((row) => [row.class, row.typescriptVisibi
|
|
|
11
11
|
export function typescriptVisibilityOf(exemptionClass) {
|
|
12
12
|
return VISIBILITY.get(exemptionClass) ?? { private: false, privateName: false };
|
|
13
13
|
}
|
|
14
|
-
/** Every class of the vocabulary, whichever language it runs on, in the vocabulary's order. */
|
|
15
|
-
export const EXEMPTION_CLASS_NAMES = EXEMPTION_CLASSES.map((row) => row.class);
|
package/dist/src/exempt.js
CHANGED
|
@@ -17,7 +17,7 @@ import { bytewise } from "./text/compare.js";
|
|
|
17
17
|
* only switches nothing off here, so one configuration serves every analyzer.
|
|
18
18
|
*/
|
|
19
19
|
export function disabledClasses(config) {
|
|
20
|
-
return new Set(config.
|
|
20
|
+
return new Set(config.exemptions.disabled);
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Whether a class whose row states `allowed` may retain a member of one visibility. A
|
|
@@ -41,8 +41,9 @@ function everyDeclaredLoaded(boundary) {
|
|
|
41
41
|
* naming its family, else the code's default.
|
|
42
42
|
*/
|
|
43
43
|
function severityOf(input, code, fallback) {
|
|
44
|
-
const {
|
|
45
|
-
|
|
44
|
+
const { severity } = input.config;
|
|
45
|
+
const key = [code, code.slice(0, FAMILY_KEY_LENGTH)].find((one) => Object.hasOwn(severity, one));
|
|
46
|
+
return key === undefined ? fallback : (severity[key] ?? fallback);
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
49
|
* The declarations the roots name as a library's published API, each public member of a
|
|
@@ -165,7 +166,7 @@ function testUsedSubjects(input) {
|
|
|
165
166
|
function rootsCeiling(input, finding, candidate, configurations) {
|
|
166
167
|
const unreachable = candidate?.relation === "reachability" || finding.unreachableOnly === true;
|
|
167
168
|
const unseen = new Set((input.files.unseenLoads ?? []).map((load) => load.configuration));
|
|
168
|
-
const complete = input.config.ts.
|
|
169
|
+
const complete = input.config.ts.roots_complete && !configurations.some((one) => unseen.has(one));
|
|
169
170
|
return unreachable && !complete ? "probable" : "certain";
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
@@ -460,7 +461,7 @@ function dialed(finding, minConfidence) {
|
|
|
460
461
|
* root withholds itself alone.
|
|
461
462
|
*/
|
|
462
463
|
export function dialsOf(input, findings) {
|
|
463
|
-
const minConfidence = input.config.analysis.
|
|
464
|
+
const minConfidence = input.config.analysis.min_confidence;
|
|
464
465
|
const components = new Set(findings
|
|
465
466
|
.filter((finding) => finding.component.root &&
|
|
466
467
|
dialed({ ...finding, confidence: componentConfidenceOf(finding) }, minConfidence))
|
|
@@ -86,7 +86,7 @@ function atPossible(input) {
|
|
|
86
86
|
const { config } = input;
|
|
87
87
|
return {
|
|
88
88
|
...input,
|
|
89
|
-
config: { ...config, analysis: { ...config.analysis,
|
|
89
|
+
config: { ...config, analysis: { ...config.analysis, min_confidence: "possible" } },
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
@@ -98,7 +98,7 @@ export const intraFunction = (input) => {
|
|
|
98
98
|
return symbol === undefined ? [] : [symbol.id];
|
|
99
99
|
}));
|
|
100
100
|
const { declared, loaded } = input.boundary.consumers;
|
|
101
|
-
const closed = input.config.
|
|
101
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
102
102
|
const published = (id) => {
|
|
103
103
|
const symbol = byId.get(id);
|
|
104
104
|
return (symbol !== undefined &&
|
|
@@ -10,8 +10,8 @@ const FILE_START = 1;
|
|
|
10
10
|
* derived matrix is never complete.
|
|
11
11
|
*/
|
|
12
12
|
function completeMatrix(config) {
|
|
13
|
-
return (config.analysis.
|
|
14
|
-
config.analysis.configurations.some((entry) =>
|
|
13
|
+
return (config.analysis.matrix.complete &&
|
|
14
|
+
config.analysis.configurations.some((entry) => "project" in entry));
|
|
15
15
|
}
|
|
16
16
|
/** One finding about a whole file. */
|
|
17
17
|
function fileFinding(code, file, message) {
|
|
@@ -178,7 +178,7 @@ export const unusedDeclarations = (input) => [
|
|
|
178
178
|
* remove unless the run includes generated files.
|
|
179
179
|
*/
|
|
180
180
|
function unusedImports(input) {
|
|
181
|
-
const generated = input.config.analysis.
|
|
181
|
+
const generated = input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
182
182
|
return input.intraFunction.imports.flatMap((binding) => binding.used || generated?.has(binding.position.path) === true
|
|
183
183
|
? []
|
|
184
184
|
: [
|
|
@@ -193,10 +193,10 @@ function unreachable(facts, at, symbol, candidate, input) {
|
|
|
193
193
|
export function narrowings(input) {
|
|
194
194
|
const facts = factsOf(input.swept.matrix.union);
|
|
195
195
|
const { declared, loaded } = input.boundary.consumers;
|
|
196
|
-
const closed = input.config.
|
|
196
|
+
const closed = input.config.consumers.complete && declared.every((id) => loaded.includes(id));
|
|
197
197
|
const world = { closed, writeOnly: writeOnlyDeclarations(input) };
|
|
198
198
|
const candidates = new Map(input.swept.sweep.candidates.map((candidate) => [candidate.id, candidate]));
|
|
199
|
-
const generated = input.config.analysis.
|
|
199
|
+
const generated = input.config.analysis.generated_files === "include" ? undefined : input.files.generated;
|
|
200
200
|
const findings = [];
|
|
201
201
|
facts.graph.symbols.forEach((symbol, at) => {
|
|
202
202
|
if (generated?.has(symbol.position.path) === true) {
|
|
@@ -66,7 +66,7 @@ export function verdictOf(result, config) {
|
|
|
66
66
|
if (result.totals.staleSuppressions > 0) {
|
|
67
67
|
return EXIT_FINDINGS;
|
|
68
68
|
}
|
|
69
|
-
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));
|
|
70
70
|
return failing ? EXIT_FINDINGS : EXIT_CLEAN;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
@@ -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,59 @@
|
|
|
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 { joinPath, relativePath } from "../paths.js";
|
|
9
|
+
import { message } from "../product.js";
|
|
10
|
+
import { utf8Bytes } from "../reporters/sha256.js";
|
|
11
|
+
import { UsageError } from "../usage.js";
|
|
12
|
+
/**
|
|
13
|
+
* A document at the target root that is not one to read: its name resolves outside the
|
|
14
|
+
* target root or to anything but a regular file, or the read fails. Nothing is read from
|
|
15
|
+
* it, and the run ends with the failure code.
|
|
16
|
+
*/
|
|
17
|
+
export class TargetDocumentError extends Error {
|
|
18
|
+
constructor(label, reason) {
|
|
19
|
+
super(`${label}: ${reason}`);
|
|
20
|
+
this.name = "TargetDocumentError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Refuses a document past the Contract's bound with the usage code, naming it `label`. */
|
|
24
|
+
export function refuseOverBound(label, text) {
|
|
25
|
+
if (utf8Bytes(text).length > CONFIG_MAX_BYTES) {
|
|
26
|
+
throw new UsageError(`${label}: holds more than ${String(CONFIG_MAX_BYTES)} bytes`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** What `read` returns, or its throw as the document's {@link TargetDocumentError}. */
|
|
30
|
+
function attempt(label, read) {
|
|
31
|
+
try {
|
|
32
|
+
return read();
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new TargetDocumentError(label, message(error));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The text of the document `name` at the absolute `targetRoot`, undefined where nothing is
|
|
40
|
+
* there, each refusal naming it `label`. A document past the bound is read one byte past
|
|
41
|
+
* it and refused as {@link refuseOverBound} refuses it.
|
|
42
|
+
*/
|
|
43
|
+
export function readTargetDocument(host, targetRoot, name, label = name) {
|
|
44
|
+
const path = joinPath(targetRoot, name);
|
|
45
|
+
const kind = attempt(label, () => host.kindOf(path));
|
|
46
|
+
if (kind === "absent") {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const real = attempt(label, () => host.realPath(path));
|
|
50
|
+
if (relativePath(attempt(label, () => host.realPath(targetRoot)), real) === undefined) {
|
|
51
|
+
throw new TargetDocumentError(label, `resolves to ${real}, outside the target root`);
|
|
52
|
+
}
|
|
53
|
+
if (kind !== "file") {
|
|
54
|
+
throw new TargetDocumentError(label, "is not a regular file");
|
|
55
|
+
}
|
|
56
|
+
const text = attempt(label, () => host.readFile(real, CONFIG_MAX_BYTES + 1));
|
|
57
|
+
refuseOverBound(label, text);
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
@@ -24,7 +24,7 @@ function isInjected(node) {
|
|
|
24
24
|
/** The classes of the target a call whose callee an entry names passes, with that call's entry. */
|
|
25
25
|
function registrations(input) {
|
|
26
26
|
const { project, held } = input;
|
|
27
|
-
const resolved = resolveEntries(project, held, input.ts.
|
|
27
|
+
const resolved = resolveEntries(project, held, input.ts.injection_registrations);
|
|
28
28
|
if (!resolved.some(namesADeclaration)) {
|
|
29
29
|
return [];
|
|
30
30
|
}
|