@cplieger/deadset-ts 8.0.0-dev.6 → 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/mapper-connection.js +2 -1
- package/dist/bin/node-host.js +24 -5
- package/dist/src/analysis.js +37 -56
- 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 +6 -82
- package/dist/src/configuration-files.js +27 -168
- 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/contract.generated.js +13 -0
- package/dist/src/contract/report.generated.js +3 -2
- package/dist/src/conventions.js +20 -60
- package/dist/src/dependencies.js +25 -43
- package/dist/src/discover.js +14 -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 +3 -10
- package/dist/src/enum-group.js +3 -10
- package/dist/src/erased-stores.js +4 -6
- package/dist/src/exempt-classes.js +0 -4
- package/dist/src/exempt.js +6 -22
- package/dist/src/file-facts.js +2 -25
- 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/reads-and-writes.js +2 -1
- package/dist/src/findings/self-check.js +3 -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 +7 -12
- package/dist/src/framework-lifecycle.js +7 -7
- package/dist/src/fs/document.js +59 -0
- 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/injection-container.js +1 -1
- package/dist/src/intra-function-parts.js +10 -13
- package/dist/src/inventory.js +2 -21
- package/dist/src/json-document.js +198 -67
- package/dist/src/keyof-comparisons.js +4 -6
- package/dist/src/kinds.js +0 -7
- 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 +11 -79
- package/dist/src/product.js +13 -0
- package/dist/src/references.js +14 -27
- package/dist/src/report-schema.js +3 -225
- package/dist/src/report.js +22 -31
- package/dist/src/reporters/sarif.js +4 -2
- package/dist/src/reporters/template.js +4 -19
- package/dist/src/resolve.js +245 -724
- package/dist/src/roots.js +7 -17
- package/dist/src/run.js +55 -61
- package/dist/src/schema.js +320 -343
- package/dist/src/scope.js +34 -103
- package/dist/src/serialization-contract.js +5 -9
- package/dist/src/session.js +2 -1
- package/dist/src/suppress-file.js +21 -25
- package/dist/src/suppress.js +3 -3
- package/dist/src/symbol-keys.js +3 -5
- package/dist/src/template-field.js +9 -19
- 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/usage.js +10 -0
- package/dist/src/verbs/analyze.js +25 -26
- package/dist/src/verbs/describe.js +4 -4
- package/dist/src/verbs/explain.js +8 -6
- package/dist/src/verbs/print-projects.js +4 -3
- package/dist/src/verbs/print-retained.js +2 -1
- package/dist/src/verbs/print-roots.js +2 -1
- package/dist/src/verbs/version.js +2 -1
- 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 +1 -1
- package/src/analysis.ts +55 -76
- 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 +31 -296
- package/src/configuration-files.ts +27 -170
- package/src/configured-declarations.ts +18 -23
- package/src/consumers.ts +4 -4
- package/src/contract/config.generated.ts +798 -0
- package/src/contract/contract.generated.ts +25 -0
- package/src/contract/report.generated.ts +3 -2
- package/src/conventions.ts +26 -74
- package/src/dependencies.ts +32 -50
- package/src/discover.ts +15 -40
- 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 +2 -13
- package/src/enum-group.ts +2 -11
- package/src/erased-stores.ts +3 -7
- package/src/exempt-classes.ts +0 -5
- package/src/exempt.ts +8 -30
- package/src/file-facts.ts +6 -24
- 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/reads-and-writes.ts +2 -1
- package/src/findings/self-check.ts +3 -2
- package/src/findings/unused-declarations.ts +1 -1
- package/src/findings/visibility-narrowing.ts +2 -2
- package/src/findings-pass.ts +9 -15
- package/src/framework-lifecycle.ts +7 -7
- package/src/fs/document.ts +74 -0
- package/src/fs/manifest.ts +52 -0
- package/src/fs/tree.ts +110 -0
- package/src/git-ignore.ts +38 -25
- package/src/host.ts +12 -5
- package/src/html-entries.ts +6 -33
- package/src/in-operands.ts +2 -1
- package/src/injection-container.ts +1 -1
- package/src/intra-function-parts.ts +10 -14
- package/src/inventory.ts +2 -20
- package/src/json-document.ts +241 -76
- package/src/keyof-comparisons.ts +3 -7
- package/src/kinds.ts +0 -8
- 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 +12 -92
- package/src/product.ts +18 -0
- package/src/references.ts +14 -29
- package/src/report-schema.ts +3 -301
- package/src/report.ts +24 -34
- package/src/reporters/sarif.ts +4 -4
- package/src/reporters/template.ts +4 -20
- package/src/resolve.ts +283 -1114
- package/src/roots.ts +7 -20
- package/src/run.ts +57 -75
- package/src/schema.ts +391 -388
- package/src/scope.ts +40 -126
- package/src/serialization-contract.ts +5 -10
- package/src/session.ts +2 -1
- package/src/suppress-file.ts +21 -29
- package/src/suppress.ts +3 -3
- package/src/symbol-keys.ts +2 -5
- package/src/template-field.ts +15 -25
- 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/usage.ts +10 -0
- package/src/verbs/analyze.ts +32 -42
- package/src/verbs/describe.ts +4 -4
- package/src/verbs/explain.ts +8 -10
- package/src/verbs/print-projects.ts +4 -3
- package/src/verbs/print-retained.ts +2 -1
- package/src/verbs/print-roots.ts +2 -1
- package/src/verbs/verb.ts +2 -2
- package/src/verbs/version.ts +2 -1
- 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/src/glob.ts +0 -67
- package/src/pnpm-workspace.ts +0 -135
package/dist/src/resolve.js
CHANGED
|
@@ -1,779 +1,300 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { SETTINGS } from "./contract/config.generated.js";
|
|
2
|
+
import { FAMILY_KEY_LENGTH } from "./contract/contract.generated.js";
|
|
3
|
+
import {} from "./config.js";
|
|
3
4
|
import { CONVENTION_ROWS } from "./convention-rows.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const PROVIDER_DIGEST = /^sha256:[0-9a-f]{64}$/u;
|
|
15
|
-
/** The members only an acquirable provider entry carries. */
|
|
16
|
-
const ACQUIRABLE_MEMBERS = ["source", "version", "digest"];
|
|
17
|
-
/** The members only a platform build configuration carries. */
|
|
18
|
-
const PLATFORM_MEMBERS = ["os", "arch", "tags"];
|
|
19
|
-
/** The member only a project build configuration carries. */
|
|
20
|
-
const PROJECT_MEMBER = "project";
|
|
5
|
+
import { refuseOverBound } from "./fs/document.js";
|
|
6
|
+
import { dotted, isRecord, locatedFault, placeIn, pointerOf, readStrict, StrictJSONError, } from "./json-document.js";
|
|
7
|
+
import { FIXED_SEVERITY_CODES } from "./kinds.js";
|
|
8
|
+
import { LANGUAGE } from "./product.js";
|
|
9
|
+
import { firstRefusal } from "./schema.js";
|
|
10
|
+
import { bytewise } from "./text/compare.js";
|
|
11
|
+
import { UsageError } from "./usage.js";
|
|
12
|
+
const ROWS = new Map(SETTINGS.map((row) => [row.key, row]));
|
|
13
|
+
/** The schema of a section or a keyed section's value, so the validator words its refusal. */
|
|
14
|
+
const OBJECT = { type: "object" };
|
|
21
15
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
16
|
+
* The settings whose entries name rows of this analyzer's own data, which the
|
|
17
|
+
* Contract's schema cannot list: a disabled convention names a convention row.
|
|
24
18
|
*/
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return parent[name];
|
|
54
|
-
}
|
|
55
|
-
function section(parent, name, path, label) {
|
|
56
|
-
const value = member(parent, name);
|
|
57
|
-
if (value === undefined) {
|
|
58
|
-
return undefined;
|
|
19
|
+
const OWN_VOCABULARIES = new Map([
|
|
20
|
+
["ts.disabled_conventions", new Set(CONVENTION_ROWS.map((row) => row.name))],
|
|
21
|
+
]);
|
|
22
|
+
function malformed(label, path, detail) {
|
|
23
|
+
return new UsageError(path === "" ? `${label}: ${detail}` : `${label}: ${path}: ${detail}`);
|
|
24
|
+
}
|
|
25
|
+
/** A key the closed key list does not declare, with the reason where there is more to say. */
|
|
26
|
+
function unimplemented(label, path, reason = "") {
|
|
27
|
+
const why = reason === "" ? "" : `: ${reason}`;
|
|
28
|
+
return new UsageError(`${label}: key ${JSON.stringify(path)} is not implemented${why}`);
|
|
29
|
+
}
|
|
30
|
+
/** Why a well-formed severity key the vocabulary does not hold names no setting. */
|
|
31
|
+
function severityKeyRefused(key) {
|
|
32
|
+
const fixed = FIXED_SEVERITY_CODES.filter((code) => code === key || (key.length === FAMILY_KEY_LENGTH && code.startsWith(key))).sort(bytewise);
|
|
33
|
+
if (fixed.length > 0) {
|
|
34
|
+
const named = fixed.length < 2
|
|
35
|
+
? fixed.join("")
|
|
36
|
+
: `${fixed.slice(0, -1).join(", ")} and ${String(fixed.at(-1))}`;
|
|
37
|
+
return `the Contract fixes the severity of ${named}, which this key names`;
|
|
38
|
+
}
|
|
39
|
+
return key.length === FAMILY_KEY_LENGTH
|
|
40
|
+
? `no issue kind carries a code of the family ${key}`
|
|
41
|
+
: `no issue kind carries the code ${key}`;
|
|
42
|
+
}
|
|
43
|
+
/** A value with every object's members in ascending order, so it prints one way. */
|
|
44
|
+
function canonical(value) {
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value.map(canonical);
|
|
59
47
|
}
|
|
60
48
|
if (!isRecord(value)) {
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
return value;
|
|
64
|
-
}
|
|
65
|
-
function spell(values) {
|
|
66
|
-
return values.map((value) => JSON.stringify(value)).join(", ");
|
|
67
|
-
}
|
|
68
|
-
function readEnum(parent, name, path, label, allowed) {
|
|
69
|
-
const value = member(parent, name);
|
|
70
|
-
if (value === undefined) {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
if (typeof value !== "string") {
|
|
74
|
-
throw malformed(label, path, "is not a string");
|
|
75
|
-
}
|
|
76
|
-
if (!allowed.includes(value)) {
|
|
77
|
-
throw malformed(label, path, `${JSON.stringify(value)} is not one of ${spell(allowed)}`);
|
|
78
|
-
}
|
|
79
|
-
return value;
|
|
80
|
-
}
|
|
81
|
-
function readString(parent, name, path, label) {
|
|
82
|
-
const value = member(parent, name);
|
|
83
|
-
if (value === undefined) {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
if (typeof value !== "string") {
|
|
87
|
-
throw malformed(label, path, "is not a string");
|
|
88
|
-
}
|
|
89
|
-
return value;
|
|
90
|
-
}
|
|
91
|
-
function readBoolean(parent, name, path, label) {
|
|
92
|
-
const value = member(parent, name);
|
|
93
|
-
if (value === undefined) {
|
|
94
|
-
return undefined;
|
|
95
|
-
}
|
|
96
|
-
if (typeof value !== "boolean") {
|
|
97
|
-
throw malformed(label, path, "is not a boolean");
|
|
98
|
-
}
|
|
99
|
-
return value;
|
|
100
|
-
}
|
|
101
|
-
function readCount(parent, name, path, label) {
|
|
102
|
-
const value = member(parent, name);
|
|
103
|
-
if (value === undefined) {
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
107
|
-
throw malformed(label, path, "is not an integer");
|
|
108
|
-
}
|
|
109
|
-
if (value < 0) {
|
|
110
|
-
throw malformed(label, path, `${String(value)} is below the minimum of 0`);
|
|
49
|
+
return value;
|
|
111
50
|
}
|
|
112
|
-
return value
|
|
51
|
+
return Object.fromEntries(Object.keys(value)
|
|
52
|
+
.sort(bytewise)
|
|
53
|
+
.map((name) => [name, canonical(value[name])]));
|
|
113
54
|
}
|
|
114
55
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
56
|
+
* The walk of one source over the closed key list. The members of an object are checked
|
|
57
|
+
* in ascending order and a key is checked whole before the next. A key this analyzer's
|
|
58
|
+
* language does not read is accepted unread, and of a key written per language, every
|
|
59
|
+
* other language's member.
|
|
118
60
|
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
61
|
+
class Walk {
|
|
62
|
+
label;
|
|
63
|
+
fractions;
|
|
64
|
+
into;
|
|
65
|
+
constructor(label, fractions, into) {
|
|
66
|
+
this.label = label;
|
|
67
|
+
this.fractions = fractions;
|
|
68
|
+
this.into = into;
|
|
69
|
+
}
|
|
70
|
+
section(at, members) {
|
|
71
|
+
for (const name of Object.keys(members).sort(bytewise)) {
|
|
72
|
+
const key = at === "" ? name : `${at}.${name}`;
|
|
73
|
+
const row = ROWS.get(key);
|
|
74
|
+
if (name.includes(".")) {
|
|
75
|
+
const reason = row === undefined ? "" : "it is one setting, written as nested objects";
|
|
76
|
+
throw unimplemented(this.label, key, reason);
|
|
77
|
+
}
|
|
78
|
+
if (row === undefined) {
|
|
79
|
+
throw unimplemented(this.label, key);
|
|
80
|
+
}
|
|
81
|
+
if (row.read) {
|
|
82
|
+
this.key(row, members[name]);
|
|
83
|
+
}
|
|
135
84
|
}
|
|
136
|
-
|
|
137
|
-
|
|
85
|
+
}
|
|
86
|
+
key(row, value) {
|
|
87
|
+
if (row.role === "section") {
|
|
88
|
+
this.refuse(row.key.split("."), value, firstRefusal(OBJECT, value));
|
|
89
|
+
this.section(row.key, value);
|
|
138
90
|
}
|
|
139
|
-
if (
|
|
140
|
-
|
|
91
|
+
else if (row.role === "setting") {
|
|
92
|
+
const read = row.perLanguage === undefined ? value : this.narrowed(row, value);
|
|
93
|
+
this.setting(row, read);
|
|
94
|
+
this.into.values.set(row.key, canonical(read));
|
|
141
95
|
}
|
|
142
|
-
|
|
143
|
-
|
|
96
|
+
else {
|
|
97
|
+
const members = this.keyed(row, value);
|
|
98
|
+
if (row.role === "keyed") {
|
|
99
|
+
this.into.keyed.set(row.key, members);
|
|
100
|
+
}
|
|
144
101
|
}
|
|
145
|
-
seen.add(entry);
|
|
146
|
-
}
|
|
147
|
-
return value;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* The target kind, a string or an object naming every language. The walk has
|
|
151
|
-
* already refused an undeclared member, so an object is refused for the first
|
|
152
|
-
* language it leaves out, then for the first language whose value is refused,
|
|
153
|
-
* each in the vocabulary's language order.
|
|
154
|
-
*/
|
|
155
|
-
function readTargetKind(target, label) {
|
|
156
|
-
const value = member(target, "kind");
|
|
157
|
-
const perLanguage = `an object naming the kind of each of ${spell(LANGUAGES)}`;
|
|
158
|
-
if (value === undefined) {
|
|
159
|
-
return undefined;
|
|
160
102
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
103
|
+
/** A per-language value as this language reads it: of the object form, its own member alone. */
|
|
104
|
+
narrowed(row, value) {
|
|
105
|
+
if (!isRecord(value)) {
|
|
106
|
+
return value;
|
|
164
107
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
for (const language of LANGUAGES) {
|
|
175
|
-
const kind = value[language];
|
|
176
|
-
if (typeof kind !== "string" || !TARGET_KINDS.includes(kind)) {
|
|
177
|
-
throw malformed(label, `target.kind.${language}`, `${JSON.stringify(kind)} is not one of ${spell(TARGET_KINDS)}`);
|
|
108
|
+
const others = row.perLanguage ?? [];
|
|
109
|
+
return Object.fromEntries(Object.entries(value).filter(([name]) => name === LANGUAGE || !others.includes(name)));
|
|
110
|
+
}
|
|
111
|
+
setting(row, value) {
|
|
112
|
+
this.refuse(row.key.split("."), value, firstRefusal(row.value ?? {}, value));
|
|
113
|
+
const text = this.fractions.get(pointerOf(row.key.split(".")));
|
|
114
|
+
if (typeof value === "number" && text !== undefined) {
|
|
115
|
+
throw malformed(this.label, row.key, `holds ${text}, want an integer written as an optional minus sign and digits alone`);
|
|
178
116
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const wrong = extensions?.findIndex((one) => !COMPONENT_EXTENSION.test(one)) ?? -1;
|
|
187
|
-
if (extensions !== undefined && wrong !== -1) {
|
|
188
|
-
throw malformed(label, `ts.component_extensions[${String(wrong)}]`, `${JSON.stringify(extensions[wrong])} is not a full stop followed by letters and digits`);
|
|
189
|
-
}
|
|
190
|
-
return extensions;
|
|
191
|
-
}
|
|
192
|
-
/** The names of the convention rows this analyzer's table carries. */
|
|
193
|
-
const CONVENTION_NAMES = new Set(CONVENTION_ROWS.map((row) => row.name));
|
|
194
|
-
function readDisabledConventions(ts, label) {
|
|
195
|
-
const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
|
|
196
|
-
const unknown = names?.findIndex((one) => !CONVENTION_NAMES.has(one)) ?? -1;
|
|
197
|
-
if (names !== undefined && unknown !== -1) {
|
|
198
|
-
throw malformed(label, `ts.disabled_conventions[${String(unknown)}]`, `${JSON.stringify(names[unknown])} names no convention row this analyzer carries`);
|
|
199
|
-
}
|
|
200
|
-
return names;
|
|
201
|
-
}
|
|
202
|
-
function requireMember(value, path, label) {
|
|
203
|
-
if (value === undefined || value === "") {
|
|
204
|
-
throw malformed(label, path, "is required and names nothing");
|
|
205
|
-
}
|
|
206
|
-
return value;
|
|
207
|
-
}
|
|
208
|
-
/** One build configuration entry as a platform: an identifier, an operating system and an architecture. */
|
|
209
|
-
function readPlatform(entry, at, label) {
|
|
210
|
-
return {
|
|
211
|
-
shape: "platform",
|
|
212
|
-
id: requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label),
|
|
213
|
-
os: requireMember(readString(entry, "os", `${at}.os`, label), `${at}.os`, label),
|
|
214
|
-
arch: requireMember(readString(entry, "arch", `${at}.arch`, label), `${at}.arch`, label),
|
|
215
|
-
tags: readStrings(entry, "tags", `${at}.tags`, label, 0) ?? [],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
/** One build configuration entry as a project: an identifier and a configuration file below the target root. */
|
|
219
|
-
function readProject(entry, at, label) {
|
|
220
|
-
const id = requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label);
|
|
221
|
-
const path = `${at}.${PROJECT_MEMBER}`;
|
|
222
|
-
const project = requireMember(readString(entry, PROJECT_MEMBER, path, label), path, label);
|
|
223
|
-
if (!PROJECT_PATH.test(project)) {
|
|
224
|
-
throw malformed(label, path, `${JSON.stringify(project)} is not a path below the target root: segments joined by /, ` +
|
|
225
|
-
"none empty, . or .., and no control character");
|
|
226
|
-
}
|
|
227
|
-
return { shape: "project", id, project };
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* One build configuration entry, read as the one shape its members name: a member
|
|
231
|
-
* only a platform carries makes it a platform, the member only a project carries
|
|
232
|
-
* makes it a project, and an entry naming members of both, or of neither, is
|
|
233
|
-
* refused at the entry, because the two shapes are exclusive.
|
|
234
|
-
*/
|
|
235
|
-
function readConfiguration(entry, at, label) {
|
|
236
|
-
if (!isRecord(entry)) {
|
|
237
|
-
throw malformed(label, at, "is not an object");
|
|
238
|
-
}
|
|
239
|
-
const platform = PLATFORM_MEMBERS.filter((name) => Object.hasOwn(entry, name));
|
|
240
|
-
const project = Object.hasOwn(entry, PROJECT_MEMBER);
|
|
241
|
-
if (platform.length > 0 && project) {
|
|
242
|
-
throw malformed(label, at, `carries ${spell(platform)} of a platform and ${JSON.stringify(PROJECT_MEMBER)} of a ` +
|
|
243
|
-
"project, and an entry is one shape or the other");
|
|
244
|
-
}
|
|
245
|
-
if (project) {
|
|
246
|
-
return readProject(entry, at, label);
|
|
247
|
-
}
|
|
248
|
-
if (platform.length === 0) {
|
|
249
|
-
throw malformed(label, at, `names neither a platform's "os" and "arch" nor a project's ${JSON.stringify(PROJECT_MEMBER)}`);
|
|
250
|
-
}
|
|
251
|
-
return readPlatform(entry, at, label);
|
|
252
|
-
}
|
|
253
|
-
/** The build matrix, one entry in one shape at a time. */
|
|
254
|
-
function readConfigurations(analysis, label) {
|
|
255
|
-
const value = member(analysis, "configurations");
|
|
256
|
-
if (value === undefined) {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
if (!Array.isArray(value)) {
|
|
260
|
-
throw malformed(label, "analysis.configurations", "is not an array");
|
|
261
|
-
}
|
|
262
|
-
return value.map((entry, index) => readConfiguration(entry, `analysis.configurations[${String(index)}]`, label));
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* The action delimiter pair: once a document names the object both members are
|
|
266
|
-
* required, because a pair carrying one member names no delimiters at all, so the
|
|
267
|
-
* refusal names the member the document left out rather than pairing the one it
|
|
268
|
-
* named with a default.
|
|
269
|
-
*/
|
|
270
|
-
function readDelimiters(analysis, label) {
|
|
271
|
-
const at = "analysis.template_delimiters";
|
|
272
|
-
const pair = section(analysis, "template_delimiters", at, label);
|
|
273
|
-
if (pair === undefined) {
|
|
274
|
-
return undefined;
|
|
275
|
-
}
|
|
276
|
-
return {
|
|
277
|
-
left: requireMember(readString(pair, "left", `${at}.left`, label), `${at}.left`, label),
|
|
278
|
-
right: requireMember(readString(pair, "right", `${at}.right`, label), `${at}.right`, label),
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* A severity key that names no setting: it names no live kind, or the Contract
|
|
283
|
-
* fixes the severity of the kind or kinds it names.
|
|
284
|
-
*/
|
|
285
|
-
function unimplementedSeverityKey(label, path, reason) {
|
|
286
|
-
return new ConfigError("unimplemented-key", path, `${label}: key ${JSON.stringify(path)} is not implemented: ${reason}`);
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Every code whose severity the Contract fixes that one severity key names, in
|
|
290
|
-
* ascending code order: the code itself, or every code of the family its
|
|
291
|
-
* two-digit prefix names. A family key covers every such code, so a refusal names
|
|
292
|
-
* them all rather than the first one found.
|
|
293
|
-
*/
|
|
294
|
-
function fixedByContract(code) {
|
|
295
|
-
return FIXED_SEVERITY_CODES.filter((candidate) => code === candidate || (code.length === FAMILY_KEY_LENGTH && candidate.startsWith(code))).sort();
|
|
296
|
-
}
|
|
297
|
-
/** The codes a refusal names: one on its own, and several with the last joined by and. */
|
|
298
|
-
function spellCodes(codes) {
|
|
299
|
-
if (codes.length < 2) {
|
|
300
|
-
return codes.join("");
|
|
301
|
-
}
|
|
302
|
-
return `${codes.slice(0, -1).join(", ")} and ${String(codes[codes.length - 1])}`;
|
|
303
|
-
}
|
|
304
|
-
/** Whether one severity key names a live kind: the code itself, or a family holding one. */
|
|
305
|
-
function namesLiveKind(code) {
|
|
306
|
-
return LIVE_CODES.some((candidate) => code === candidate || (code.length === FAMILY_KEY_LENGTH && candidate.startsWith(code)));
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* The severity object, one code at a time.
|
|
310
|
-
*
|
|
311
|
-
* A key is one issue-kind code or one two-digit family prefix. A key naming no
|
|
312
|
-
* live kind, and a key naming a kind whose severity the Contract fixes or a family
|
|
313
|
-
* prefix whose range holds one, is an unimplemented key rather than a setting.
|
|
314
|
-
*
|
|
315
|
-
* Whether a key names a kind this analyzer reports is not decided here: a live
|
|
316
|
-
* kind of another language is a setting the analyzer of that language reads.
|
|
317
|
-
*/
|
|
318
|
-
function readSeverity(document, label) {
|
|
319
|
-
const object = section(document, SEVERITY_SECTION, SEVERITY_SECTION, label);
|
|
320
|
-
if (object === undefined) {
|
|
321
|
-
return undefined;
|
|
322
|
-
}
|
|
323
|
-
const severity = new Map();
|
|
324
|
-
for (const code of Object.keys(object).sort()) {
|
|
325
|
-
const path = `${SEVERITY_SECTION}.${code}`;
|
|
326
|
-
if (!SEVERITY_KEY.test(code)) {
|
|
327
|
-
throw unimplementedSeverityKey(label, path, "a severity key is one issue-kind code or one two-digit family prefix");
|
|
117
|
+
const own = OWN_VOCABULARIES.get(row.key);
|
|
118
|
+
if (own !== undefined) {
|
|
119
|
+
const names = value;
|
|
120
|
+
const unknown = names.findIndex((name) => !own.has(name));
|
|
121
|
+
if (unknown !== -1) {
|
|
122
|
+
throw malformed(this.label, `${row.key}[${String(unknown)}]`, `${JSON.stringify(names[unknown])} names no row this analyzer carries`);
|
|
123
|
+
}
|
|
328
124
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
125
|
+
}
|
|
126
|
+
/** A keyed or an ignored section: an object whose member names its key pattern admits. */
|
|
127
|
+
keyed(row, value) {
|
|
128
|
+
this.refuse(row.key.split("."), value, firstRefusal(OBJECT, value));
|
|
129
|
+
const members = value;
|
|
130
|
+
const shape = new RegExp(row.keyPattern ?? "", "u");
|
|
131
|
+
for (const name of Object.keys(members).sort(bytewise)) {
|
|
132
|
+
const at = `${row.key}.${name}`;
|
|
133
|
+
if (!shape.test(name)) {
|
|
134
|
+
throw unimplemented(this.label, at, `the name does not match ${String(row.keyPattern)}`);
|
|
135
|
+
}
|
|
136
|
+
if (row.values !== undefined && !row.values.includes(name)) {
|
|
137
|
+
throw unimplemented(this.label, at, severityKeyRefused(name));
|
|
138
|
+
}
|
|
139
|
+
const member = members[name];
|
|
140
|
+
this.refuse([...row.key.split("."), name], member, firstRefusal(row.value ?? {}, member));
|
|
333
141
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
142
|
+
return members;
|
|
143
|
+
}
|
|
144
|
+
/** Throws the refusal the schema makes of `value`, at `at`, naming the place inside it. */
|
|
145
|
+
refuse(at, value, refused) {
|
|
146
|
+
if (refused === undefined) {
|
|
147
|
+
return;
|
|
337
148
|
}
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
149
|
+
const place = dotted([...at, ...placeIn(value, refused.pointer, refused.member)]);
|
|
150
|
+
switch (refused.keyword) {
|
|
151
|
+
case "additionalProperties":
|
|
152
|
+
throw unimplemented(this.label, place);
|
|
153
|
+
case "required":
|
|
154
|
+
throw malformed(this.label, place, "is required and absent");
|
|
341
155
|
}
|
|
156
|
+
throw malformed(this.label, place, refused.reason);
|
|
342
157
|
}
|
|
343
|
-
return severity;
|
|
344
158
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
159
|
+
const supplies = () => ({ values: new Map(), keyed: new Map() });
|
|
160
|
+
/** Reads and checks one configuration document. */
|
|
161
|
+
function documentSource(source, document) {
|
|
162
|
+
const { text, label } = document;
|
|
163
|
+
refuseOverBound(label, text);
|
|
164
|
+
let read;
|
|
165
|
+
try {
|
|
166
|
+
read = readStrict(text);
|
|
351
167
|
}
|
|
352
|
-
|
|
353
|
-
if (!
|
|
354
|
-
throw
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (!(error instanceof StrictJSONError)) {
|
|
170
|
+
throw error;
|
|
355
171
|
}
|
|
172
|
+
throw malformed(label, dotted(error.place), locatedFault(text, error));
|
|
356
173
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
/** The members that name a declaration, each belonging to exactly one shape. */
|
|
360
|
-
const SHAPE_MEMBERS = ["symbol", "module", "name", "global"];
|
|
361
|
-
/** The prefix of a stable symbol reference in the TypeScript form. */
|
|
362
|
-
const SYMBOL_PREFIX = "ts://";
|
|
363
|
-
/**
|
|
364
|
-
* A bare specifier: neither relative nor absolute, and not one the package's own
|
|
365
|
-
* imports field maps, each of which names a file of the analyzed program.
|
|
366
|
-
*/
|
|
367
|
-
const BARE_SPECIFIER = /^[^./#]/u;
|
|
368
|
-
/**
|
|
369
|
-
* One declaration entry, read as the one shape its members name. An entry carrying
|
|
370
|
-
* members of two shapes, or of none, is refused at the entry, as is a module named
|
|
371
|
-
* without the declaration's path or a path without its module.
|
|
372
|
-
*/
|
|
373
|
-
function readDeclaration(entry, at, label) {
|
|
374
|
-
if (!isRecord(entry)) {
|
|
375
|
-
throw malformed(label, at, "is not an object");
|
|
174
|
+
if (!isRecord(read.value)) {
|
|
175
|
+
throw malformed(label, "", "is not one JSON object");
|
|
376
176
|
}
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
177
|
+
const supplied = supplies();
|
|
178
|
+
new Walk(label, read.fractions, supplied).section("", read.value);
|
|
179
|
+
return { source, label, supplied };
|
|
180
|
+
}
|
|
181
|
+
/** The command line as a source: each flag checked as the one-setting document it supplies. */
|
|
182
|
+
function flagSource(flags) {
|
|
183
|
+
const supplied = supplies();
|
|
184
|
+
for (const key of [...flags.keys()].sort(bytewise)) {
|
|
185
|
+
const row = ROWS.get(key);
|
|
186
|
+
const text = flags.get(key) ?? "";
|
|
187
|
+
if (row?.option === undefined) {
|
|
188
|
+
throw unimplemented("the command line", key, "no flag supplies it");
|
|
382
189
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return {
|
|
388
|
-
shape: "global",
|
|
389
|
-
global: requireMember(readString(entry, "global", path, label), path, label),
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
if (named.length === 2 && named.includes("module") && named.includes("name")) {
|
|
393
|
-
const module = requireMember(readString(entry, "module", `${at}.module`, label), `${at}.module`, label);
|
|
394
|
-
if (!BARE_SPECIFIER.test(module)) {
|
|
395
|
-
throw malformed(label, `${at}.module`, `${JSON.stringify(module)} is not a bare specifier: a file of the analyzed program is named by its symbol`);
|
|
190
|
+
const integer = row.value?.["type"] === "integer" && /^-?[0-9]+$/u.test(text);
|
|
191
|
+
let document = integer ? Number(text) : text;
|
|
192
|
+
for (const name of key.split(".").reverse()) {
|
|
193
|
+
document = { [name]: document };
|
|
396
194
|
}
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
shape: "module",
|
|
400
|
-
module,
|
|
401
|
-
name: requireMember(readString(entry, "name", path, label), path, label),
|
|
402
|
-
};
|
|
195
|
+
new Walk(`--${row.option}`, new Map(), supplied).section("", document);
|
|
403
196
|
}
|
|
404
|
-
|
|
405
|
-
? 'names no declaration: an entry carries "symbol", "module" with "name", or "global"'
|
|
406
|
-
: `carries ${spell(named)}, which is not one of "symbol", "module" with "name", or "global"`);
|
|
197
|
+
return { source: "flag", label: "", supplied };
|
|
407
198
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (value === undefined) {
|
|
412
|
-
return undefined;
|
|
413
|
-
}
|
|
414
|
-
if (!Array.isArray(value)) {
|
|
415
|
-
throw malformed(label, path, "is not an array");
|
|
416
|
-
}
|
|
417
|
-
return value.map((entry, index) => readDeclaration(entry, `${path}[${String(index)}]`, label));
|
|
199
|
+
function originOf(held, key) {
|
|
200
|
+
const label = held.source === "flag" ? `--${String(ROWS.get(key)?.option)}` : held.label;
|
|
201
|
+
return { source: held.source, label };
|
|
418
202
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
* one component declaration or one base class, because a contract naming neither
|
|
422
|
-
* makes no class a component and so retains nothing.
|
|
423
|
-
*/
|
|
424
|
-
function readLifecycleContract(entry, at, label) {
|
|
425
|
-
if (!isRecord(entry)) {
|
|
426
|
-
throw malformed(label, at, "is not an object");
|
|
427
|
-
}
|
|
428
|
-
const components = readDeclarations(entry, "components", `${at}.components`, label) ?? [];
|
|
429
|
-
const bases = readDeclarations(entry, "bases", `${at}.bases`, label) ?? [];
|
|
430
|
-
const members = readStrings(entry, "members", `${at}.members`, label, 1);
|
|
431
|
-
if (members === undefined) {
|
|
432
|
-
throw malformed(label, `${at}.members`, "is required and names nothing");
|
|
433
|
-
}
|
|
434
|
-
if (components.length === 0 && bases.length === 0) {
|
|
435
|
-
throw malformed(label, at, 'names no declaration in "components" and no class in "bases", so it makes no class a component');
|
|
436
|
-
}
|
|
437
|
-
return { components, bases, members };
|
|
203
|
+
function searched(named, document) {
|
|
204
|
+
return `${named} (${document === undefined ? "not present" : document.label})`;
|
|
438
205
|
}
|
|
439
|
-
/**
|
|
440
|
-
function
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
206
|
+
/** Sets the value at one dotted key of the resolved tree, whose sections the table lists first. */
|
|
207
|
+
function setAt(tree, key, value) {
|
|
208
|
+
const names = key.split(".");
|
|
209
|
+
let into = tree;
|
|
210
|
+
for (const name of names.slice(0, -1)) {
|
|
211
|
+
into = into[name];
|
|
445
212
|
}
|
|
446
|
-
|
|
447
|
-
throw malformed(label, path, "is not an array");
|
|
448
|
-
}
|
|
449
|
-
return value.map((entry, index) => readLifecycleContract(entry, `${path}[${String(index)}]`, label));
|
|
450
|
-
}
|
|
451
|
-
/** One string member a provider entry requires, refused where it fails `pattern`. */
|
|
452
|
-
function readMatching(entry, name, at, label, pattern) {
|
|
453
|
-
const path = `${at}.${name}`;
|
|
454
|
-
const value = requireMember(readString(entry, name, path, label), path, label);
|
|
455
|
-
if (!pattern.test(value)) {
|
|
456
|
-
throw malformed(label, path, `${JSON.stringify(value)} does not match ${pattern.source}`);
|
|
457
|
-
}
|
|
458
|
-
return value;
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* One provider entry, read as the one shape its members name: a member only an
|
|
462
|
-
* acquirable analyzer carries makes it acquirable, and then it carries all three.
|
|
463
|
-
*/
|
|
464
|
-
function readProvider(entry, at, label) {
|
|
465
|
-
if (!isRecord(entry)) {
|
|
466
|
-
throw malformed(label, at, "is not an object");
|
|
467
|
-
}
|
|
468
|
-
const name = readMatching(entry, "name", at, label, ANALYZER_NAME);
|
|
469
|
-
const languages = readStrings(entry, "languages", `${at}.languages`, label, 1, LANGUAGES);
|
|
470
|
-
if (languages === undefined) {
|
|
471
|
-
throw malformed(label, `${at}.languages`, "is required and names nothing");
|
|
472
|
-
}
|
|
473
|
-
const commandPath = `${at}.command`;
|
|
474
|
-
const command = requireMember(readString(entry, "command", commandPath, label), commandPath, label);
|
|
475
|
-
if (!ACQUIRABLE_MEMBERS.some((member) => Object.hasOwn(entry, member))) {
|
|
476
|
-
return { shape: "installed", name, languages, command };
|
|
477
|
-
}
|
|
478
|
-
return {
|
|
479
|
-
shape: "acquirable",
|
|
480
|
-
name,
|
|
481
|
-
languages,
|
|
482
|
-
command,
|
|
483
|
-
source: readMatching(entry, "source", at, label, PROVIDER_SOURCE),
|
|
484
|
-
version: readMatching(entry, "version", at, label, PROVIDER_VERSION),
|
|
485
|
-
digest: readMatching(entry, "digest", at, label, PROVIDER_DIGEST),
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* The provider list. Every file a run writes for an analyzer is keyed by the
|
|
490
|
-
* entry's name, so a list naming one name twice is refused at the later entry.
|
|
491
|
-
*/
|
|
492
|
-
function readProviders(providers, label) {
|
|
493
|
-
const path = "providers.analyzers";
|
|
494
|
-
const value = member(providers, "analyzers");
|
|
495
|
-
if (value === undefined) {
|
|
496
|
-
return undefined;
|
|
497
|
-
}
|
|
498
|
-
if (!Array.isArray(value)) {
|
|
499
|
-
throw malformed(label, path, "is not an array");
|
|
500
|
-
}
|
|
501
|
-
const read = value.map((entry, index) => readProvider(entry, `${path}[${String(index)}]`, label));
|
|
502
|
-
const first = new Map();
|
|
503
|
-
read.forEach((entry, index) => {
|
|
504
|
-
const earlier = first.get(entry.name);
|
|
505
|
-
if (earlier !== undefined) {
|
|
506
|
-
throw malformed(label, `${path}[${String(index)}].name`, `${JSON.stringify(entry.name)} is already the name of ${path}[${String(earlier)}], ` +
|
|
507
|
-
"and every entry's name keys the files a run writes for it");
|
|
508
|
-
}
|
|
509
|
-
first.set(entry.name, index);
|
|
510
|
-
});
|
|
511
|
-
return read;
|
|
213
|
+
into[names.at(-1) ?? key] = value;
|
|
512
214
|
}
|
|
513
215
|
/**
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
216
|
+
* Every setting this analyzer reads, each from the highest-ranked source carrying it,
|
|
217
|
+
* else its default, in the closed key list's order. A setting with no default no
|
|
218
|
+
* source supplies is refused where its row is required and `enforce` holds, and left
|
|
219
|
+
* unset otherwise. A keyed section's members overlay its default's, member by member.
|
|
518
220
|
*/
|
|
519
|
-
function
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const analysis = section(value, "analysis", "analysis", label);
|
|
524
|
-
const matrix = section(analysis, "matrix", "analysis.matrix", label);
|
|
525
|
-
const target = section(value, "target", "target", label);
|
|
526
|
-
const consumers = section(value, "consumers", "consumers", label);
|
|
527
|
-
const roots = section(value, "roots", "roots", label);
|
|
528
|
-
const reporters = section(value, "reporters", "reporters", label);
|
|
529
|
-
const providers = section(value, "providers", "providers", label);
|
|
530
|
-
const ts = section(value, "ts", "ts", label);
|
|
531
|
-
section(value, "go", "go", label);
|
|
532
|
-
section(value, "provenance", "provenance", label);
|
|
533
|
-
const contractVersion = readString(value, "contract_version", "contract_version", label);
|
|
534
|
-
if (contractVersion !== undefined && !CONTRACT_VERSION_PATTERN.test(contractVersion)) {
|
|
535
|
-
throw malformed(label, "contract_version", `${JSON.stringify(contractVersion)} is not a semantic version`);
|
|
536
|
-
}
|
|
537
|
-
return {
|
|
538
|
-
contractVersion,
|
|
539
|
-
targetKind: readTargetKind(target, label),
|
|
540
|
-
languages: readStrings(analysis, "languages", "analysis.languages", label, 0, LANGUAGES),
|
|
541
|
-
minConfidence: readEnum(analysis, "min_confidence", "analysis.min_confidence", label, CONFIDENCES),
|
|
542
|
-
generatedFiles: readEnum(analysis, "generated_files", "analysis.generated_files", label, GENERATED_FILES),
|
|
543
|
-
consumerTests: readEnum(analysis, "consumer_tests", "analysis.consumer_tests", label, CONSUMER_TESTS),
|
|
544
|
-
configurations: readConfigurations(analysis, label),
|
|
545
|
-
matrixComplete: readBoolean(matrix, "complete", "analysis.matrix.complete", label),
|
|
546
|
-
templateDirs: readStrings(analysis, "template_dirs", "analysis.template_dirs", label, 0),
|
|
547
|
-
templateDelimiters: readDelimiters(analysis, label),
|
|
548
|
-
consumersComplete: readBoolean(consumers, "complete", "consumers.complete", label),
|
|
549
|
-
rootPatterns: readStrings(roots, "patterns", "roots.patterns", label, 0),
|
|
550
|
-
severity: readSeverity(value, label),
|
|
551
|
-
exemptionsDisabled: readExemptions(value, label),
|
|
552
|
-
formats: readStrings(reporters, "formats", "reporters.formats", label, 1, FORMATS),
|
|
553
|
-
sort: readEnum(reporters, "sort", "reporters.sort", label, SORTS),
|
|
554
|
-
cascade: readEnum(reporters, "cascade", "reporters.cascade", label, CASCADES),
|
|
555
|
-
maxFindings: readCount(reporters, "max_findings", "reporters.max_findings", label),
|
|
556
|
-
failOn: readEnum(reporters, "fail_on", "reporters.fail_on", label, SEVERITIES),
|
|
557
|
-
providers: readProviders(providers, label),
|
|
558
|
-
testFiles: readStrings(ts, "test_files", "ts.test_files", label, 1),
|
|
559
|
-
entryFiles: readStrings(ts, "entry_files", "ts.entry_files", label, 0),
|
|
560
|
-
componentExtensions: readComponentExtensions(ts, label),
|
|
561
|
-
disabledConventions: readDisabledConventions(ts, label),
|
|
562
|
-
injectionRegistrations: readDeclarations(ts, "injection_registrations", "ts.injection_registrations", label),
|
|
563
|
-
lifecycleContracts: readLifecycleContracts(ts, label),
|
|
564
|
-
serializers: readDeclarations(ts, "serializers", "ts.serializers", label),
|
|
565
|
-
rootsComplete: readBoolean(ts, "roots_complete", "ts.roots_complete", label),
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
/** Parses one document's text and walks it against the closed key list. */
|
|
569
|
-
function parseDocument(text, node, label) {
|
|
570
|
-
let value;
|
|
571
|
-
try {
|
|
572
|
-
value = JSON.parse(text);
|
|
221
|
+
function settle(inputs, enforce) {
|
|
222
|
+
const sources = [];
|
|
223
|
+
if (inputs.flags !== undefined && inputs.flags.size > 0) {
|
|
224
|
+
sources.push(flagSource(inputs.flags));
|
|
573
225
|
}
|
|
574
|
-
|
|
575
|
-
|
|
226
|
+
if (inputs.repository !== undefined) {
|
|
227
|
+
sources.push(documentSource("repository", inputs.repository));
|
|
576
228
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
/** Names a document in a refusal: the path the invocation passed, or what it is. */
|
|
581
|
-
function labelOr(label, named) {
|
|
582
|
-
return label === undefined || label === "" ? named : label;
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* Decodes the documents one invocation supplies, in the order they outrank each
|
|
586
|
-
* other, skipping every source it does not carry.
|
|
587
|
-
*/
|
|
588
|
-
function readSources(inputs) {
|
|
589
|
-
const sources = [];
|
|
590
|
-
const flagLabels = inputs.flagLabels ?? new Map();
|
|
591
|
-
if (inputs.flags !== undefined) {
|
|
592
|
-
const flat = parseDocument(inputs.flags, { kind: "map" }, FLAGS_LABEL);
|
|
593
|
-
if (!isRecord(flat)) {
|
|
594
|
-
throw malformed(FLAGS_LABEL, "", "is not one JSON object");
|
|
595
|
-
}
|
|
596
|
-
sources.push({
|
|
597
|
-
doc: readDoc(nestFlags(flat, FLAGS_LABEL), FLAGS_LABEL),
|
|
598
|
-
kind: "flag",
|
|
599
|
-
label: "",
|
|
600
|
-
flagLabels,
|
|
601
|
-
});
|
|
229
|
+
if (inputs.central !== undefined) {
|
|
230
|
+
sources.push(documentSource("central", inputs.central));
|
|
602
231
|
}
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
...(inputs.repository === undefined ? {} : { text: inputs.repository }),
|
|
608
|
-
...(inputs.repositoryLabel === undefined ? {} : { label: inputs.repositoryLabel }),
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
kind: "central",
|
|
612
|
-
named: "the central configuration",
|
|
613
|
-
...(inputs.central === undefined ? {} : { text: inputs.central }),
|
|
614
|
-
...(inputs.centralLabel === undefined ? {} : { label: inputs.centralLabel }),
|
|
615
|
-
},
|
|
616
|
-
];
|
|
617
|
-
for (const entry of held) {
|
|
618
|
-
if (entry.text === undefined) {
|
|
232
|
+
const tree = {};
|
|
233
|
+
const provenance = new Map();
|
|
234
|
+
for (const row of SETTINGS) {
|
|
235
|
+
if (!row.read || row.role === "ignored") {
|
|
619
236
|
continue;
|
|
620
237
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
doc: readDoc(parseDocument(entry.text, SCHEMA_ROOT, label), label),
|
|
624
|
-
kind: entry.kind,
|
|
625
|
-
label: entry.label ?? "",
|
|
626
|
-
flagLabels,
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
return sources;
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* The value the highest-ranked source carrying one supplies, with where it came
|
|
633
|
-
* from recorded, or the default.
|
|
634
|
-
*/
|
|
635
|
-
function settle(fallback, path, provenance, sources, read) {
|
|
636
|
-
for (const source of sources) {
|
|
637
|
-
const value = read(source.doc);
|
|
638
|
-
if (value !== undefined) {
|
|
639
|
-
provenance.set(path, originOf(source, path));
|
|
640
|
-
return value;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
provenance.set(path, { source: "default", label: "" });
|
|
644
|
-
return fallback;
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* The severity object, resolved one code at a time so a code only the central
|
|
648
|
-
* configuration names keeps its central value. An object that resolves empty
|
|
649
|
-
* carries one entry for the object itself.
|
|
650
|
-
*/
|
|
651
|
-
function settleSeverity(provenance, sources) {
|
|
652
|
-
const codes = new Set();
|
|
653
|
-
for (const source of sources) {
|
|
654
|
-
for (const code of source.doc.severity?.keys() ?? []) {
|
|
655
|
-
codes.add(code);
|
|
238
|
+
if (row.role === "section") {
|
|
239
|
+
setAt(tree, row.key, {});
|
|
656
240
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const value = source.doc.severity?.get(code);
|
|
663
|
-
if (value === undefined) {
|
|
664
|
-
continue;
|
|
241
|
+
else if (row.role === "keyed") {
|
|
242
|
+
const members = isRecord(row.default) ? { ...row.default } : {};
|
|
243
|
+
provenance.set(row.key, { source: "default", label: "" });
|
|
244
|
+
for (const name of Object.keys(members)) {
|
|
245
|
+
provenance.set(`${row.key}.${name}`, { source: "default", label: "" });
|
|
665
246
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
247
|
+
for (const held of sources.toReversed()) {
|
|
248
|
+
const named = held.supplied.keyed.get(row.key);
|
|
249
|
+
if (named === undefined) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
provenance.set(row.key, originOf(held, row.key));
|
|
253
|
+
for (const [name, value] of Object.entries(named)) {
|
|
254
|
+
members[name] = value;
|
|
255
|
+
provenance.set(`${row.key}.${name}`, originOf(held, row.key));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (Object.keys(members).length > 0) {
|
|
259
|
+
provenance.delete(row.key);
|
|
260
|
+
}
|
|
261
|
+
setAt(tree, row.key, canonical(members));
|
|
679
262
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
for (const path of [...provenance.keys()].sort()) {
|
|
696
|
-
const origin = provenance.get(path);
|
|
697
|
-
if (origin !== undefined && origin.source !== "default" && origin.label === "") {
|
|
698
|
-
throw malformed(origin.source === "flag" ? FLAGS_LABEL : origin.source, path, `came from ${origin.source}, which names no file or flag`);
|
|
263
|
+
else {
|
|
264
|
+
const held = sources.find((one) => one.supplied.values.has(row.key));
|
|
265
|
+
if (held !== undefined) {
|
|
266
|
+
setAt(tree, row.key, held.supplied.values.get(row.key));
|
|
267
|
+
provenance.set(row.key, originOf(held, row.key));
|
|
268
|
+
}
|
|
269
|
+
else if (row.default !== undefined) {
|
|
270
|
+
setAt(tree, row.key, canonical(row.default));
|
|
271
|
+
provenance.set(row.key, { source: "default", label: "" });
|
|
272
|
+
}
|
|
273
|
+
else if (enforce && row.required === true) {
|
|
274
|
+
throw new UsageError(`${row.key} is not set, it has no default and is never inferred: searched ` +
|
|
275
|
+
`${searched("the repository configuration", inputs.repository)} and ` +
|
|
276
|
+
searched("the central configuration", inputs.central));
|
|
277
|
+
}
|
|
699
278
|
}
|
|
700
279
|
}
|
|
280
|
+
// gen-config.ts marks a member optional exactly where this loop may leave it unset.
|
|
281
|
+
return { config: tree, provenance };
|
|
701
282
|
}
|
|
702
283
|
/**
|
|
703
|
-
* Applies
|
|
284
|
+
* Applies the command-line flags over the repository configuration over the central
|
|
704
285
|
* configuration over the default each key declares, and returns the resolved
|
|
705
|
-
* configuration with the origin of every setting
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
* list does not declare, a document that names one key twice, and a resolved
|
|
709
|
-
* configuration no source supplied a target kind for. Every one of them maps to
|
|
710
|
-
* the usage exit code.
|
|
286
|
+
* configuration with the origin of every setting: its keys in the closed key list's
|
|
287
|
+
* order, every object's members inside a value ascending. Every refusal is a
|
|
288
|
+
* {@link UsageError}.
|
|
711
289
|
*/
|
|
712
290
|
export function resolve(inputs) {
|
|
713
|
-
const { config, provenance } =
|
|
714
|
-
|
|
715
|
-
throw new ConfigError("missing-target-kind", "target.kind", "target.kind is not set, it has no default and is never inferred: searched " +
|
|
716
|
-
`${describeSource("the repository configuration", inputs.repository, inputs.repositoryLabel)} and ` +
|
|
717
|
-
describeSource("the central configuration", inputs.central, inputs.centralLabel));
|
|
718
|
-
}
|
|
719
|
-
checkLabels(provenance);
|
|
720
|
-
return { config, provenance };
|
|
291
|
+
const { config, provenance } = settle(inputs, true);
|
|
292
|
+
return { config: config, provenance };
|
|
721
293
|
}
|
|
722
294
|
/**
|
|
723
|
-
* The configuration the
|
|
724
|
-
*
|
|
725
|
-
* run analyzes does not depend on the kind, so a question about the projects asks
|
|
726
|
-
* none. The kind is empty where no document names one.
|
|
295
|
+
* The configuration the sources resolve to, refused as {@link resolve} refuses it but
|
|
296
|
+
* with no target kind required: which projects a run analyzes asks none.
|
|
727
297
|
*/
|
|
728
|
-
export function
|
|
729
|
-
return
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* Every setting the documents supply, each from the highest-ranked source carrying
|
|
733
|
-
* it, with the origin of each. The target kind is empty where no source names one.
|
|
734
|
-
*/
|
|
735
|
-
function settleAll(inputs) {
|
|
736
|
-
const sources = readSources(inputs);
|
|
737
|
-
const defaults = defaultConfig();
|
|
738
|
-
const provenance = new Map();
|
|
739
|
-
const at = (path, fallback, read) => settle(fallback, path, provenance, sources, read);
|
|
740
|
-
const config = {
|
|
741
|
-
contractVersion: at("contract_version", defaults.contractVersion, (doc) => doc.contractVersion),
|
|
742
|
-
targetKind: at("target.kind", "", (doc) => doc.targetKind),
|
|
743
|
-
analysis: {
|
|
744
|
-
languages: at("analysis.languages", defaults.analysis.languages, (doc) => doc.languages),
|
|
745
|
-
minConfidence: at("analysis.min_confidence", defaults.analysis.minConfidence, (doc) => doc.minConfidence),
|
|
746
|
-
generatedFiles: at("analysis.generated_files", defaults.analysis.generatedFiles, (doc) => doc.generatedFiles),
|
|
747
|
-
consumerTests: at("analysis.consumer_tests", defaults.analysis.consumerTests, (doc) => doc.consumerTests),
|
|
748
|
-
configurations: at("analysis.configurations", defaults.analysis.configurations, (doc) => doc.configurations),
|
|
749
|
-
matrixComplete: at("analysis.matrix.complete", defaults.analysis.matrixComplete, (doc) => doc.matrixComplete),
|
|
750
|
-
templateDirs: at("analysis.template_dirs", defaults.analysis.templateDirs, (doc) => doc.templateDirs),
|
|
751
|
-
templateDelimiters: at("analysis.template_delimiters", defaults.analysis.templateDelimiters, (doc) => doc.templateDelimiters),
|
|
752
|
-
},
|
|
753
|
-
consumersComplete: at("consumers.complete", defaults.consumersComplete, (doc) => doc.consumersComplete),
|
|
754
|
-
rootPatterns: at("roots.patterns", defaults.rootPatterns, (doc) => doc.rootPatterns),
|
|
755
|
-
severity: settleSeverity(provenance, sources),
|
|
756
|
-
exemptionsDisabled: at("exemptions.disabled", defaults.exemptionsDisabled, (doc) => doc.exemptionsDisabled),
|
|
757
|
-
reporters: {
|
|
758
|
-
formats: at("reporters.formats", defaults.reporters.formats, (doc) => doc.formats),
|
|
759
|
-
sort: at("reporters.sort", defaults.reporters.sort, (doc) => doc.sort),
|
|
760
|
-
cascade: at("reporters.cascade", defaults.reporters.cascade, (doc) => doc.cascade),
|
|
761
|
-
maxFindings: at("reporters.max_findings", defaults.reporters.maxFindings, (doc) => doc.maxFindings),
|
|
762
|
-
failOn: at("reporters.fail_on", defaults.reporters.failOn, (doc) => doc.failOn),
|
|
763
|
-
},
|
|
764
|
-
providers: {
|
|
765
|
-
analyzers: at("providers.analyzers", defaults.providers.analyzers, (doc) => doc.providers),
|
|
766
|
-
},
|
|
767
|
-
ts: {
|
|
768
|
-
testFiles: at("ts.test_files", defaults.ts.testFiles, (doc) => doc.testFiles),
|
|
769
|
-
entryFiles: at("ts.entry_files", defaults.ts.entryFiles, (doc) => doc.entryFiles),
|
|
770
|
-
componentExtensions: at("ts.component_extensions", defaults.ts.componentExtensions, (doc) => doc.componentExtensions),
|
|
771
|
-
disabledConventions: at("ts.disabled_conventions", defaults.ts.disabledConventions, (doc) => doc.disabledConventions),
|
|
772
|
-
injectionRegistrations: at("ts.injection_registrations", defaults.ts.injectionRegistrations, (doc) => doc.injectionRegistrations),
|
|
773
|
-
lifecycleContracts: at("ts.lifecycle_contracts", defaults.ts.lifecycleContracts, (doc) => doc.lifecycleContracts),
|
|
774
|
-
serializers: at("ts.serializers", defaults.ts.serializers, (doc) => doc.serializers),
|
|
775
|
-
rootsComplete: at("ts.roots_complete", defaults.ts.rootsComplete, (doc) => doc.rootsComplete),
|
|
776
|
-
},
|
|
777
|
-
};
|
|
778
|
-
return { config, provenance };
|
|
298
|
+
export function resolvePartial(inputs) {
|
|
299
|
+
return settle(inputs, false).config;
|
|
779
300
|
}
|