@cplieger/deadset-ts 7.2.0-dev.4 → 8.0.0-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/mapper-connection.js +2 -1
  3. package/dist/bin/node-host.js +24 -5
  4. package/dist/src/analysis.js +245 -309
  5. package/dist/src/augmentations.js +124 -18
  6. package/dist/src/automatic-types.js +170 -0
  7. package/dist/src/build-scripts.js +425 -0
  8. package/dist/src/component-layer.js +4 -2
  9. package/dist/src/components.js +9 -9
  10. package/dist/src/config.js +6 -81
  11. package/dist/src/configuration-files.js +78 -188
  12. package/dist/src/configured-declarations.js +15 -20
  13. package/dist/src/conformance-record.js +2 -2
  14. package/dist/src/consumers.js +3 -30
  15. package/dist/src/contextual-uses.js +56 -9
  16. package/dist/src/contract/config.generated.js +558 -0
  17. package/dist/src/contract/contract.generated.js +1244 -0
  18. package/dist/src/contract/report.generated.js +862 -0
  19. package/dist/src/convention-rows.js +146 -1
  20. package/dist/src/conventions.js +136 -70
  21. package/dist/src/decorator.js +21 -18
  22. package/dist/src/dependencies.js +280 -62
  23. package/dist/src/edges.js +38 -122
  24. package/dist/src/element-access.js +4 -6
  25. package/dist/src/entry-point-gaps.js +9 -18
  26. package/dist/src/entry-points.js +106 -21
  27. package/dist/src/enum-group.js +3 -10
  28. package/dist/src/erased-stores.js +4 -6
  29. package/dist/src/exempt-classes.js +1 -63
  30. package/dist/src/exempt.js +21 -40
  31. package/dist/src/failure.js +36 -0
  32. package/dist/src/file-facts.js +2 -25
  33. package/dist/src/findings/completion.js +225 -63
  34. package/dist/src/findings/dependencies-and-module-machinery.js +72 -27
  35. package/dist/src/findings/emitters.js +6 -6
  36. package/dist/src/findings/interfaces.js +4 -5
  37. package/dist/src/findings/intra-function.js +53 -19
  38. package/dist/src/findings/non-code-artifacts.js +71 -39
  39. package/dist/src/findings/reads-and-writes.js +16 -13
  40. package/dist/src/findings/self-check.js +10 -12
  41. package/dist/src/findings/unused-declarations.js +57 -28
  42. package/dist/src/findings/visibility-narrowing.js +40 -18
  43. package/dist/src/findings-pass.js +8 -15
  44. package/dist/src/framework-lifecycle.js +7 -7
  45. package/dist/src/fs/document.js +52 -0
  46. package/dist/src/fs/manifest.js +72 -0
  47. package/dist/src/fs/tree.js +88 -0
  48. package/dist/src/generated-file.js +18 -4
  49. package/dist/src/git-ignore.js +99 -0
  50. package/dist/src/graph.js +9 -4
  51. package/dist/src/html-entries.js +6 -33
  52. package/dist/src/import-bindings.js +97 -0
  53. package/dist/src/in-operands.js +2 -1
  54. package/dist/src/index.js +1 -1
  55. package/dist/src/indexed-access.js +130 -28
  56. package/dist/src/injection-container.js +1 -1
  57. package/dist/src/interface-satisfaction.js +30 -12
  58. package/dist/src/intra-function-parts.js +341 -69
  59. package/dist/src/inventory.js +54 -29
  60. package/dist/src/javascript-file.js +33 -0
  61. package/dist/src/json-document.js +198 -67
  62. package/dist/src/keyof-comparisons.js +4 -6
  63. package/dist/src/kinds.js +3 -366
  64. package/dist/src/load/classify.js +270 -0
  65. package/dist/src/load/diagnostics.js +80 -0
  66. package/dist/src/load/discover.js +277 -0
  67. package/dist/src/{emit-map.js → load/emit-map.js} +1 -1
  68. package/dist/src/load/expected-module.js +136 -0
  69. package/dist/src/load/load.js +27 -0
  70. package/dist/src/load/module-lookup.js +37 -0
  71. package/dist/src/load/plan.js +313 -0
  72. package/dist/src/load/specifiers.js +115 -0
  73. package/dist/src/load/synthesize.js +140 -0
  74. package/dist/src/load/test-globals.js +207 -0
  75. package/dist/src/load/test-joins.js +82 -0
  76. package/dist/src/{workspace-manifests.js → load/workspace-manifests.js} +4 -4
  77. package/dist/src/{workspace-programs.js → load/workspace-programs.js} +88 -61
  78. package/dist/src/{workspace-resolution.js → load/workspace-resolution.js} +60 -59
  79. package/dist/src/{workspace-run.js → load/workspace-run.js} +7 -7
  80. package/dist/src/{workspace.js → load/workspace.js} +62 -124
  81. package/dist/src/manifest.js +49 -54
  82. package/dist/src/matrix.js +94 -13
  83. package/dist/src/mdx-entries.js +174 -0
  84. package/dist/src/module-declarations.js +9 -13
  85. package/dist/src/outside-tests.js +1 -1
  86. package/dist/src/overrides.js +41 -10
  87. package/dist/src/partial.js +2 -1
  88. package/dist/src/paths.js +26 -15
  89. package/dist/src/position.js +2 -1
  90. package/dist/src/print.js +11 -78
  91. package/dist/src/product.js +13 -0
  92. package/dist/src/query.js +1 -0
  93. package/dist/src/ref.js +11 -0
  94. package/dist/src/references.js +52 -31
  95. package/dist/src/report-schema.js +51 -0
  96. package/dist/src/report.js +59 -41
  97. package/dist/src/reporters/annotations.js +15 -8
  98. package/dist/src/reporters/sarif.js +16 -10
  99. package/dist/src/reporters/template.js +4 -19
  100. package/dist/src/resolve.js +245 -727
  101. package/dist/src/roots.js +374 -116
  102. package/dist/src/run.js +66 -77
  103. package/dist/src/schema.js +320 -342
  104. package/dist/src/scope.js +38 -115
  105. package/dist/src/serialization-contract.js +649 -29
  106. package/dist/src/session.js +41 -29
  107. package/dist/src/source-files.js +40 -15
  108. package/dist/src/suppress-file.js +34 -29
  109. package/dist/src/suppress-inline.js +21 -4
  110. package/dist/src/suppress.js +40 -56
  111. package/dist/src/sweep.js +32 -17
  112. package/dist/src/symbol-keys.js +105 -6
  113. package/dist/src/template-field.js +9 -19
  114. package/dist/src/test-support.js +119 -18
  115. package/dist/src/text/compare.js +26 -0
  116. package/dist/src/text/glob.js +194 -0
  117. package/dist/src/text/named-files.js +54 -0
  118. package/dist/src/text/shell.js +159 -0
  119. package/dist/src/text/yaml.js +393 -0
  120. package/dist/src/type-errors.js +27 -221
  121. package/dist/src/type-parameter-reads.js +172 -0
  122. package/dist/src/unseen-loads.js +100 -0
  123. package/dist/src/unwrap.js +23 -0
  124. package/dist/src/usage.js +10 -0
  125. package/dist/src/verbs/analyze.js +181 -125
  126. package/dist/src/verbs/describe.js +9 -16
  127. package/dist/src/verbs/explain.js +14 -9
  128. package/dist/src/verbs/print-config.js +2 -1
  129. package/dist/src/verbs/print-projects.js +6 -4
  130. package/dist/src/verbs/print-retained.js +4 -2
  131. package/dist/src/verbs/print-roots.js +4 -2
  132. package/dist/src/verbs/verb.js +1 -5
  133. package/dist/src/verbs/version.js +14 -3
  134. package/dist/src/workflow-steps.js +128 -33
  135. package/jsr.json +1 -1
  136. package/package.json +5 -4
  137. package/src/analysis.ts +333 -457
  138. package/src/augmentations.ts +152 -23
  139. package/src/automatic-types.ts +228 -0
  140. package/src/build-scripts.ts +543 -0
  141. package/src/component-layer.ts +4 -2
  142. package/src/components.ts +9 -10
  143. package/src/config.ts +31 -299
  144. package/src/configuration-files.ts +100 -191
  145. package/src/configured-declarations.ts +18 -23
  146. package/src/conformance-record.ts +2 -2
  147. package/src/consumers.ts +6 -57
  148. package/src/contextual-uses.ts +64 -12
  149. package/src/contract/config.generated.ts +818 -0
  150. package/src/contract/contract.generated.ts +1308 -0
  151. package/src/contract/report.generated.ts +1237 -0
  152. package/src/convention-rows.ts +186 -1
  153. package/src/conventions.ts +166 -90
  154. package/src/decorator.ts +28 -18
  155. package/src/dependencies.ts +356 -78
  156. package/src/edges.ts +41 -140
  157. package/src/element-access.ts +4 -7
  158. package/src/entry-point-gaps.ts +11 -20
  159. package/src/entry-points.ts +124 -28
  160. package/src/enum-group.ts +2 -11
  161. package/src/erased-stores.ts +3 -7
  162. package/src/exempt-classes.ts +1 -75
  163. package/src/exempt.ts +36 -50
  164. package/src/failure.ts +63 -0
  165. package/src/file-facts.ts +9 -24
  166. package/src/finding.ts +24 -9
  167. package/src/findings/boundary.ts +2 -0
  168. package/src/findings/completion.ts +297 -77
  169. package/src/findings/dependencies-and-module-machinery.ts +90 -31
  170. package/src/findings/emitter.ts +8 -9
  171. package/src/findings/emitters.ts +8 -8
  172. package/src/findings/interfaces.ts +4 -6
  173. package/src/findings/intra-function.ts +64 -20
  174. package/src/findings/non-code-artifacts.ts +88 -50
  175. package/src/findings/reads-and-writes.ts +20 -16
  176. package/src/findings/self-check.ts +11 -17
  177. package/src/findings/unused-declarations.ts +65 -28
  178. package/src/findings/visibility-narrowing.ts +51 -19
  179. package/src/findings-pass.ts +10 -19
  180. package/src/framework-lifecycle.ts +7 -7
  181. package/src/fs/document.ts +70 -0
  182. package/src/fs/manifest.ts +92 -0
  183. package/src/fs/tree.ts +110 -0
  184. package/src/generated-file.ts +23 -4
  185. package/src/git-ignore.ts +122 -0
  186. package/src/graph.ts +11 -3
  187. package/src/host.ts +17 -14
  188. package/src/html-entries.ts +6 -33
  189. package/src/import-bindings.ts +128 -0
  190. package/src/in-operands.ts +2 -1
  191. package/src/index.ts +1 -1
  192. package/src/indexed-access.ts +152 -30
  193. package/src/injection-container.ts +1 -1
  194. package/src/interface-satisfaction.ts +33 -13
  195. package/src/intra-function-parts.ts +449 -85
  196. package/src/inventory.ts +73 -27
  197. package/src/javascript-file.ts +37 -0
  198. package/src/json-document.ts +241 -76
  199. package/src/keyof-comparisons.ts +3 -7
  200. package/src/kinds.ts +5 -413
  201. package/src/load/classify.ts +397 -0
  202. package/src/load/diagnostics.ts +144 -0
  203. package/src/load/discover.ts +455 -0
  204. package/src/{emit-map.ts → load/emit-map.ts} +1 -1
  205. package/src/load/expected-module.ts +188 -0
  206. package/src/load/load.ts +73 -0
  207. package/src/load/module-lookup.ts +50 -0
  208. package/src/load/plan.ts +504 -0
  209. package/src/load/specifiers.ts +168 -0
  210. package/src/load/synthesize.ts +211 -0
  211. package/src/load/test-globals.ts +251 -0
  212. package/src/load/test-joins.ts +98 -0
  213. package/src/{workspace-manifests.ts → load/workspace-manifests.ts} +5 -5
  214. package/src/{workspace-programs.ts → load/workspace-programs.ts} +127 -80
  215. package/src/{workspace-resolution.ts → load/workspace-resolution.ts} +73 -64
  216. package/src/{workspace-run.ts → load/workspace-run.ts} +18 -16
  217. package/src/{workspace.ts → load/workspace.ts} +90 -134
  218. package/src/manifest.ts +81 -75
  219. package/src/matrix.ts +154 -18
  220. package/src/mdx-entries.ts +218 -0
  221. package/src/module-declarations.ts +11 -13
  222. package/src/outside-tests.ts +1 -1
  223. package/src/overrides.ts +51 -12
  224. package/src/partial.ts +3 -2
  225. package/src/paths.ts +27 -15
  226. package/src/position.ts +2 -1
  227. package/src/print.ts +12 -91
  228. package/src/product.ts +18 -0
  229. package/src/query.ts +11 -0
  230. package/src/ref.ts +13 -0
  231. package/src/references.ts +62 -32
  232. package/src/report-schema.ts +68 -0
  233. package/src/report.ts +105 -215
  234. package/src/reporters/annotations.ts +22 -11
  235. package/src/reporters/json.ts +1 -1
  236. package/src/reporters/reporter.ts +2 -4
  237. package/src/reporters/reporters.ts +1 -1
  238. package/src/reporters/sarif.ts +29 -16
  239. package/src/reporters/template.ts +4 -20
  240. package/src/reporters/text.ts +3 -3
  241. package/src/resolve.ts +283 -1111
  242. package/src/roots.ts +490 -137
  243. package/src/run.ts +75 -91
  244. package/src/schema.ts +391 -387
  245. package/src/scope.ts +46 -138
  246. package/src/serialization-contract.ts +818 -26
  247. package/src/session.ts +77 -54
  248. package/src/source-files.ts +67 -17
  249. package/src/suppress-file.ts +36 -34
  250. package/src/suppress-inline.ts +20 -3
  251. package/src/suppress.ts +52 -63
  252. package/src/sweep.ts +53 -18
  253. package/src/symbol-keys.ts +131 -7
  254. package/src/template-field.ts +15 -25
  255. package/src/test-support.ts +155 -18
  256. package/src/text/compare.ts +27 -0
  257. package/src/text/glob.ts +210 -0
  258. package/src/text/named-files.inputs.json +1522 -0
  259. package/src/text/named-files.ts +68 -0
  260. package/src/text/shell.ts +161 -0
  261. package/src/text/yaml.ts +423 -0
  262. package/src/type-errors.ts +35 -273
  263. package/src/type-parameter-reads.ts +227 -0
  264. package/src/unseen-loads.ts +134 -0
  265. package/src/unwrap.ts +34 -0
  266. package/src/usage.ts +10 -0
  267. package/src/verbs/analyze.ts +245 -161
  268. package/src/verbs/describe.ts +14 -15
  269. package/src/verbs/explain.ts +15 -13
  270. package/src/verbs/print-config.ts +2 -1
  271. package/src/verbs/print-projects.ts +6 -4
  272. package/src/verbs/print-retained.ts +4 -2
  273. package/src/verbs/print-roots.ts +4 -2
  274. package/src/verbs/verb.ts +2 -8
  275. package/src/verbs/version.ts +15 -3
  276. package/src/workflow-steps.ts +149 -34
  277. package/dist/src/discover.js +0 -347
  278. package/dist/src/glob.js +0 -65
  279. package/dist/src/pnpm-workspace.js +0 -120
  280. package/dist/src/setup-failure.js +0 -23
  281. package/dist/src/test-joins.js +0 -101
  282. package/dist/src/version.js +0 -7
  283. package/src/discover.ts +0 -510
  284. package/src/glob.ts +0 -67
  285. package/src/pnpm-workspace.ts +0 -135
  286. package/src/setup-failure.ts +0 -39
  287. package/src/test-joins.ts +0 -120
  288. package/src/version.ts +0 -7
package/src/resolve.ts CHANGED
@@ -1,1198 +1,370 @@
1
+ import { SETTINGS, type SettingRow } from "./contract/config.generated.ts";
2
+ import { FAMILY_KEY_LENGTH } from "./contract/contract.generated.ts";
1
3
  import {
2
- ConfigError,
3
- defaultConfig,
4
- type BuildConfiguration,
5
- type Cascade,
6
- type Config,
7
- type Confidence,
8
- type ConsumerTests,
9
- type DeclarationEntry,
10
- type Format,
11
- type GeneratedFiles,
4
+ type Document,
12
5
  type Inputs,
13
- type Language,
14
- type LifecycleContract,
15
6
  type Origin,
7
+ type PartialConfig,
16
8
  type Provenance,
17
- type Provider,
18
- type Severity,
19
- type Sort,
9
+ type ResolvedConfig,
20
10
  type Source,
21
- type TargetKind,
22
- type TargetKindSetting,
23
- type TemplateDelimiters,
24
11
  } from "./config.ts";
25
12
  import { CONVENTION_ROWS } from "./convention-rows.ts";
26
- import { FIXED_SEVERITY_CODES, LIVE_CODES } from "./kinds.ts";
13
+ import { refuseOverBound } from "./fs/document.ts";
27
14
  import {
28
- checkDocument,
29
- malformed,
30
- nestFlags,
31
- SCHEMA_ROOT,
32
- SEVERITY_SECTION,
33
- type KeyNode,
34
- } from "./schema.ts";
35
-
36
- /** Names the command-line flags in a refusal, where a document is named by its path. */
37
- const FLAGS_LABEL = "the command-line flags";
38
-
39
- /** The length of a severity key naming a whole family: the prefix and two digits. */
40
- export const FAMILY_KEY_LENGTH = 4;
41
-
42
- const SEVERITY_KEY = /^DS[0-9]{2}([0-9]{2})?$/u;
43
- const EXEMPTION_CLASS = /^[a-z][a-z0-9-]*$/u;
44
- const CONTRACT_VERSION_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+$/u;
45
- const ANALYZER_NAME = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/u;
46
- const PROVIDER_SOURCE = /^(go|npm):[^ \t\r\n]+$/u;
47
- const PROVIDER_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/u;
48
- const PROVIDER_DIGEST = /^sha256:[0-9a-f]{64}$/u;
49
-
50
- /** The members only an acquirable provider entry carries. */
51
- const ACQUIRABLE_MEMBERS = ["source", "version", "digest"] as const;
52
-
53
- /** The members only a platform build configuration carries. */
54
- const PLATFORM_MEMBERS = ["os", "arch", "tags"] as const;
55
-
56
- /** The member only a project build configuration carries. */
57
- const PROJECT_MEMBER = "project";
15
+ dotted,
16
+ isRecord,
17
+ locatedFault,
18
+ type Place,
19
+ placeIn,
20
+ pointerOf,
21
+ readStrict,
22
+ StrictJSONError,
23
+ } from "./json-document.ts";
24
+ import { FIXED_SEVERITY_CODES } from "./kinds.ts";
25
+ import { LANGUAGE } from "./product.ts";
26
+ import { firstRefusal, type SchemaRefusal } from "./schema.ts";
27
+ import { bytewise } from "./text/compare.ts";
28
+ import { UsageError } from "./usage.ts";
29
+
30
+ const ROWS: ReadonlyMap<string, SettingRow> = new Map(SETTINGS.map((row) => [row.key, row]));
31
+
32
+ /** The schema of a section or a keyed section's value, so the validator words its refusal. */
33
+ const OBJECT = { type: "object" };
58
34
 
59
35
  /**
60
- * One segment of a project path: not empty, not `.` or `..`, and holding no
61
- * solidus or control character.
36
+ * The settings whose entries name rows of this analyzer's own data, which the
37
+ * Contract's schema cannot list: a disabled convention names a convention row.
62
38
  */
63
- const PROJECT_SEGMENT = String.raw`(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+)`;
64
-
65
- /**
66
- * A project path: segments joined by a solidus, so the path is relative, below
67
- * the root it is read against, and spelled one way on every machine.
68
- */
69
- const PROJECT_PATH = new RegExp(`^${PROJECT_SEGMENT}(?:/${PROJECT_SEGMENT})*$`, "u");
70
-
71
- const TARGET_KINDS: readonly TargetKind[] = ["application", "library"];
72
- const CONFIDENCES: readonly Confidence[] = ["certain", "probable", "possible"];
73
- const GENERATED_FILES: readonly GeneratedFiles[] = ["exclude", "include"];
74
- const CONSUMER_TESTS: readonly ConsumerTests[] = ["test", "production"];
75
- const LANGUAGES: readonly Language[] = ["go", "ts"];
76
- const SEVERITIES: readonly Severity[] = ["allow", "warn", "deny"];
77
- const FORMATS: readonly Format[] = ["text", "json", "github", "sarif", "template"];
78
- const SORTS: readonly Sort[] = ["position", "size"];
79
- const CASCADES: readonly Cascade[] = ["roots", "full"];
80
-
81
- /**
82
- * One configuration document, read as the settings it supplies. Every member is
83
- * possibly undefined, so a key the document omits is distinguishable from one it
84
- * sets to a value equal to the default, which is what makes resolution per
85
- * setting rather than per document.
86
- */
87
- interface Doc {
88
- readonly contractVersion: string | undefined;
89
- readonly targetKind: TargetKindSetting | undefined;
90
- readonly languages: readonly Language[] | undefined;
91
- readonly minConfidence: Confidence | undefined;
92
- readonly generatedFiles: GeneratedFiles | undefined;
93
- readonly consumerTests: ConsumerTests | undefined;
94
- readonly configurations: readonly BuildConfiguration[] | undefined;
95
- readonly matrixComplete: boolean | undefined;
96
- readonly templateDirs: readonly string[] | undefined;
97
- readonly templateDelimiters: TemplateDelimiters | undefined;
98
- readonly consumersComplete: boolean | undefined;
99
- readonly rootPatterns: readonly string[] | undefined;
100
- readonly severity: ReadonlyMap<string, Severity> | undefined;
101
- readonly exemptionsDisabled: readonly string[] | undefined;
102
- readonly formats: readonly Format[] | undefined;
103
- readonly sort: Sort | undefined;
104
- readonly cascade: Cascade | undefined;
105
- readonly maxFindings: number | undefined;
106
- readonly failOn: Severity | undefined;
107
- readonly providers: readonly Provider[] | undefined;
108
- readonly testFiles: readonly string[] | undefined;
109
- readonly entryFiles: readonly string[] | undefined;
110
- readonly componentExtensions: readonly string[] | undefined;
111
- readonly disabledConventions: readonly string[] | undefined;
112
- readonly injectionRegistrations: readonly DeclarationEntry[] | undefined;
113
- readonly lifecycleContracts: readonly LifecycleContract[] | undefined;
114
- readonly serializers: readonly DeclarationEntry[] | undefined;
115
- }
116
-
117
- /** One document the resolution reads, with the origin its settings carry. */
118
- interface DocSource {
119
- readonly doc: Doc;
120
- readonly kind: Source;
121
- readonly label: string;
122
- readonly flagLabels: ReadonlyMap<string, string>;
123
- }
39
+ const OWN_VOCABULARIES: ReadonlyMap<string, ReadonlySet<string>> = new Map([
40
+ ["ts.disabled_conventions", new Set(CONVENTION_ROWS.map((row) => row.name))],
41
+ ]);
124
42
 
125
- /**
126
- * The origin one setting of this source carries. A flag names itself per setting,
127
- * because one invocation carries many.
128
- */
129
- function originOf(source: DocSource, path: string): Origin {
130
- if (source.kind === "flag") {
131
- return { source: "flag", label: source.flagLabels.get(path) ?? "" };
132
- }
133
- return { source: source.kind, label: source.label };
43
+ function malformed(label: string, path: string, detail: string): UsageError {
44
+ return new UsageError(path === "" ? `${label}: ${detail}` : `${label}: ${path}: ${detail}`);
134
45
  }
135
46
 
136
- function isRecord(value: unknown): value is Record<string, unknown> {
137
- return typeof value === "object" && value !== null && !Array.isArray(value);
47
+ /** A key the closed key list does not declare, with the reason where there is more to say. */
48
+ function unimplemented(label: string, path: string, reason = ""): UsageError {
49
+ const why = reason === "" ? "" : `: ${reason}`;
50
+ return new UsageError(`${label}: key ${JSON.stringify(path)} is not implemented${why}`);
138
51
  }
139
52
 
140
- function member(parent: Record<string, unknown> | undefined, name: string): unknown {
141
- if (parent === undefined || !Object.hasOwn(parent, name)) {
142
- return undefined;
53
+ /** Why a well-formed severity key the vocabulary does not hold names no setting. */
54
+ function severityKeyRefused(key: string): string {
55
+ const fixed = FIXED_SEVERITY_CODES.filter(
56
+ (code) => code === key || (key.length === FAMILY_KEY_LENGTH && code.startsWith(key)),
57
+ ).sort(bytewise);
58
+ if (fixed.length > 0) {
59
+ const named =
60
+ fixed.length < 2
61
+ ? fixed.join("")
62
+ : `${fixed.slice(0, -1).join(", ")} and ${String(fixed.at(-1))}`;
63
+ return `the Contract fixes the severity of ${named}, which this key names`;
143
64
  }
144
- return parent[name];
65
+ return key.length === FAMILY_KEY_LENGTH
66
+ ? `no issue kind carries a code of the family ${key}`
67
+ : `no issue kind carries the code ${key}`;
145
68
  }
146
69
 
147
- function section(
148
- parent: Record<string, unknown> | undefined,
149
- name: string,
150
- path: string,
151
- label: string,
152
- ): Record<string, unknown> | undefined {
153
- const value = member(parent, name);
154
- if (value === undefined) {
155
- return undefined;
70
+ /** A value with every object's members in ascending order, so it prints one way. */
71
+ function canonical(value: unknown): unknown {
72
+ if (Array.isArray(value)) {
73
+ return value.map(canonical);
156
74
  }
157
75
  if (!isRecord(value)) {
158
- throw malformed(label, path, "is not an object");
159
- }
160
- return value;
161
- }
162
-
163
- function spell(values: readonly string[]): string {
164
- return values.map((value) => JSON.stringify(value)).join(", ");
165
- }
166
-
167
- function readEnum<T extends string>(
168
- parent: Record<string, unknown> | undefined,
169
- name: string,
170
- path: string,
171
- label: string,
172
- allowed: readonly T[],
173
- ): T | undefined {
174
- const value = member(parent, name);
175
- if (value === undefined) {
176
- return undefined;
177
- }
178
- if (typeof value !== "string") {
179
- throw malformed(label, path, "is not a string");
180
- }
181
- if (!(allowed as readonly string[]).includes(value)) {
182
- throw malformed(label, path, `${JSON.stringify(value)} is not one of ${spell(allowed)}`);
183
- }
184
- return value as T;
185
- }
186
-
187
- function readString(
188
- parent: Record<string, unknown> | undefined,
189
- name: string,
190
- path: string,
191
- label: string,
192
- ): string | undefined {
193
- const value = member(parent, name);
194
- if (value === undefined) {
195
- return undefined;
76
+ return value;
196
77
  }
197
- if (typeof value !== "string") {
198
- throw malformed(label, path, "is not a string");
199
- }
200
- return value;
78
+ return Object.fromEntries(
79
+ Object.keys(value)
80
+ .sort(bytewise)
81
+ .map((name) => [name, canonical(value[name])]),
82
+ );
201
83
  }
202
84
 
203
- function readBoolean(
204
- parent: Record<string, unknown> | undefined,
205
- name: string,
206
- path: string,
207
- label: string,
208
- ): boolean | undefined {
209
- const value = member(parent, name);
210
- if (value === undefined) {
211
- return undefined;
212
- }
213
- if (typeof value !== "boolean") {
214
- throw malformed(label, path, "is not a boolean");
215
- }
216
- return value;
217
- }
218
-
219
- function readCount(
220
- parent: Record<string, unknown> | undefined,
221
- name: string,
222
- path: string,
223
- label: string,
224
- ): number | undefined {
225
- const value = member(parent, name);
226
- if (value === undefined) {
227
- return undefined;
228
- }
229
- if (typeof value !== "number" || !Number.isInteger(value)) {
230
- throw malformed(label, path, "is not an integer");
231
- }
232
- if (value < 0) {
233
- throw malformed(label, path, `${String(value)} is below the minimum of 0`);
234
- }
235
- return value;
85
+ /** What one checked source supplies: each setting's value and each keyed section's members. */
86
+ interface Supplied {
87
+ readonly values: Map<string, unknown>;
88
+ readonly keyed: Map<string, Readonly<Record<string, unknown>>>;
236
89
  }
237
90
 
238
91
  /**
239
- * One array of strings, refusing an array shorter than its minimum, one holding an
240
- * empty entry, one naming an entry twice, and, where a closed set is given, one
241
- * holding an entry outside it.
92
+ * The walk of one source over the closed key list. The members of an object are checked
93
+ * in ascending order and a key is checked whole before the next. A key this analyzer's
94
+ * language does not read is accepted unread, and of a key written per language, every
95
+ * other language's member.
242
96
  */
243
- function readStrings<T extends string>(
244
- parent: Record<string, unknown> | undefined,
245
- name: string,
246
- path: string,
247
- label: string,
248
- minItems: number,
249
- allowed?: readonly T[],
250
- ): readonly T[] | undefined {
251
- const value = member(parent, name);
252
- if (value === undefined) {
253
- return undefined;
254
- }
255
- if (!Array.isArray(value)) {
256
- throw malformed(label, path, "is not an array");
257
- }
258
- if (value.length < minItems) {
259
- throw malformed(
260
- label,
261
- path,
262
- `holds ${String(value.length)} entries, want at least ${String(minItems)}`,
263
- );
264
- }
265
- const seen = new Set<string>();
266
- for (const [index, entry] of (value as unknown[]).entries()) {
267
- const at = `${path}[${String(index)}]`;
268
- if (typeof entry !== "string") {
269
- throw malformed(label, at, "is not a string");
270
- }
271
- if (entry === "") {
272
- throw malformed(label, at, "is empty");
273
- }
274
- if (seen.has(entry)) {
275
- throw malformed(label, at, `names ${JSON.stringify(entry)} again`);
276
- }
277
- if (allowed !== undefined && !(allowed as readonly string[]).includes(entry)) {
278
- throw malformed(label, at, `${JSON.stringify(entry)} is not one of ${spell(allowed)}`);
97
+ class Walk {
98
+ private readonly label: string;
99
+ private readonly fractions: ReadonlyMap<string, string>;
100
+ private readonly into: Supplied;
101
+
102
+ constructor(label: string, fractions: ReadonlyMap<string, string>, into: Supplied) {
103
+ this.label = label;
104
+ this.fractions = fractions;
105
+ this.into = into;
106
+ }
107
+
108
+ section(at: string, members: Readonly<Record<string, unknown>>): void {
109
+ for (const name of Object.keys(members).sort(bytewise)) {
110
+ const key = at === "" ? name : `${at}.${name}`;
111
+ const row = ROWS.get(key);
112
+ if (name.includes(".")) {
113
+ const reason = row === undefined ? "" : "it is one setting, written as nested objects";
114
+ throw unimplemented(this.label, key, reason);
115
+ }
116
+ if (row === undefined) {
117
+ throw unimplemented(this.label, key);
118
+ }
119
+ if (row.read) {
120
+ this.key(row, members[name]);
121
+ }
279
122
  }
280
- seen.add(entry);
281
123
  }
282
- return value as readonly T[];
283
- }
284
124
 
285
- /**
286
- * The target kind, a string or an object naming every language. The walk has
287
- * already refused an undeclared member, so an object is refused for the first
288
- * language it leaves out, then for the first language whose value is refused,
289
- * each in the vocabulary's language order.
290
- */
291
- function readTargetKind(
292
- target: Record<string, unknown> | undefined,
293
- label: string,
294
- ): TargetKindSetting | undefined {
295
- const value = member(target, "kind");
296
- const perLanguage = `an object naming the kind of each of ${spell(LANGUAGES)}`;
297
- if (value === undefined) {
298
- return undefined;
299
- }
300
- if (typeof value === "string") {
301
- if (!(TARGET_KINDS as readonly string[]).includes(value)) {
302
- throw malformed(
303
- label,
304
- "target.kind",
305
- `${JSON.stringify(value)} is not one of ${spell(TARGET_KINDS)}, nor ${perLanguage}`,
306
- );
307
- }
308
- return value as TargetKind;
309
- }
310
- if (!isRecord(value)) {
311
- throw malformed(
312
- label,
313
- "target.kind",
314
- `${JSON.stringify(value)} is neither one of ${spell(TARGET_KINDS)} nor ${perLanguage}`,
315
- );
316
- }
317
- const missing = LANGUAGES.find((language) => !Object.hasOwn(value, language));
318
- if (missing !== undefined) {
319
- throw malformed(label, `target.kind.${missing}`, `is not set, and ${perLanguage} names each`);
320
- }
321
- for (const language of LANGUAGES) {
322
- const kind = value[language];
323
- if (typeof kind !== "string" || !(TARGET_KINDS as readonly string[]).includes(kind)) {
324
- throw malformed(
325
- label,
326
- `target.kind.${language}`,
327
- `${JSON.stringify(kind)} is not one of ${spell(TARGET_KINDS)}`,
328
- );
125
+ private key(row: SettingRow, value: unknown): void {
126
+ if (row.role === "section") {
127
+ this.refuse(row.key.split("."), value, firstRefusal(OBJECT, value));
128
+ this.section(row.key, value as Record<string, unknown>);
129
+ } else if (row.role === "setting") {
130
+ const read = row.perLanguage === undefined ? value : this.narrowed(row, value);
131
+ this.setting(row, read);
132
+ this.into.values.set(row.key, canonical(read));
133
+ } else {
134
+ const members = this.keyed(row, value);
135
+ if (row.role === "keyed") {
136
+ this.into.keyed.set(row.key, members);
137
+ }
329
138
  }
330
139
  }
331
- return { go: value["go"] as TargetKind, ts: value["ts"] as TargetKind };
332
- }
333
-
334
- /** A component file's extension: a full stop followed by letters and digits. */
335
- const COMPONENT_EXTENSION = /^\.[A-Za-z0-9]+$/u;
336
-
337
- function readComponentExtensions(
338
- ts: Record<string, unknown> | undefined,
339
- label: string,
340
- ): readonly string[] | undefined {
341
- const extensions = readStrings(ts, "component_extensions", "ts.component_extensions", label, 0);
342
- const wrong = extensions?.findIndex((one) => !COMPONENT_EXTENSION.test(one)) ?? -1;
343
- if (extensions !== undefined && wrong !== -1) {
344
- throw malformed(
345
- label,
346
- `ts.component_extensions[${String(wrong)}]`,
347
- `${JSON.stringify(extensions[wrong])} is not a full stop followed by letters and digits`,
348
- );
349
- }
350
- return extensions;
351
- }
352
-
353
- /** The names of the convention rows this analyzer's table carries. */
354
- const CONVENTION_NAMES: ReadonlySet<string> = new Set(CONVENTION_ROWS.map((row) => row.name));
355
-
356
- function readDisabledConventions(
357
- ts: Record<string, unknown> | undefined,
358
- label: string,
359
- ): readonly string[] | undefined {
360
- const names = readStrings(ts, "disabled_conventions", "ts.disabled_conventions", label, 0);
361
- const unknown = names?.findIndex((one) => !CONVENTION_NAMES.has(one)) ?? -1;
362
- if (names !== undefined && unknown !== -1) {
363
- throw malformed(
364
- label,
365
- `ts.disabled_conventions[${String(unknown)}]`,
366
- `${JSON.stringify(names[unknown])} names no convention row this analyzer carries`,
367
- );
368
- }
369
- return names;
370
- }
371
-
372
- function requireMember(value: string | undefined, path: string, label: string): string {
373
- if (value === undefined || value === "") {
374
- throw malformed(label, path, "is required and names nothing");
375
- }
376
- return value;
377
- }
378
-
379
- /** One build configuration entry as a platform: an identifier, an operating system and an architecture. */
380
- function readPlatform(
381
- entry: Record<string, unknown>,
382
- at: string,
383
- label: string,
384
- ): BuildConfiguration {
385
- return {
386
- shape: "platform",
387
- id: requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label),
388
- os: requireMember(readString(entry, "os", `${at}.os`, label), `${at}.os`, label),
389
- arch: requireMember(readString(entry, "arch", `${at}.arch`, label), `${at}.arch`, label),
390
- tags: readStrings(entry, "tags", `${at}.tags`, label, 0) ?? [],
391
- };
392
- }
393
140
 
394
- /** One build configuration entry as a project: an identifier and a configuration file below the target root. */
395
- function readProject(
396
- entry: Record<string, unknown>,
397
- at: string,
398
- label: string,
399
- ): BuildConfiguration {
400
- const id = requireMember(readString(entry, "id", `${at}.id`, label), `${at}.id`, label);
401
- const path = `${at}.${PROJECT_MEMBER}`;
402
- const project = requireMember(readString(entry, PROJECT_MEMBER, path, label), path, label);
403
- if (!PROJECT_PATH.test(project)) {
404
- throw malformed(
405
- label,
406
- path,
407
- `${JSON.stringify(project)} is not a path below the target root: segments joined by /, ` +
408
- "none empty, . or .., and no control character",
409
- );
410
- }
411
- return { shape: "project", id, project };
412
- }
413
-
414
- /**
415
- * One build configuration entry, read as the one shape its members name: a member
416
- * only a platform carries makes it a platform, the member only a project carries
417
- * makes it a project, and an entry naming members of both, or of neither, is
418
- * refused at the entry, because the two shapes are exclusive.
419
- */
420
- function readConfiguration(entry: unknown, at: string, label: string): BuildConfiguration {
421
- if (!isRecord(entry)) {
422
- throw malformed(label, at, "is not an object");
423
- }
424
- const platform = PLATFORM_MEMBERS.filter((name) => Object.hasOwn(entry, name));
425
- const project = Object.hasOwn(entry, PROJECT_MEMBER);
426
- if (platform.length > 0 && project) {
427
- throw malformed(
428
- label,
429
- at,
430
- `carries ${spell(platform)} of a platform and ${JSON.stringify(PROJECT_MEMBER)} of a ` +
431
- "project, and an entry is one shape or the other",
432
- );
433
- }
434
- if (project) {
435
- return readProject(entry, at, label);
436
- }
437
- if (platform.length === 0) {
438
- throw malformed(
439
- label,
440
- at,
441
- `names neither a platform's "os" and "arch" nor a project's ${JSON.stringify(PROJECT_MEMBER)}`,
141
+ /** A per-language value as this language reads it: of the object form, its own member alone. */
142
+ private narrowed(row: SettingRow, value: unknown): unknown {
143
+ if (!isRecord(value)) {
144
+ return value;
145
+ }
146
+ const others: readonly string[] = row.perLanguage ?? [];
147
+ return Object.fromEntries(
148
+ Object.entries(value).filter(([name]) => name === LANGUAGE || !others.includes(name)),
442
149
  );
443
150
  }
444
- return readPlatform(entry, at, label);
445
- }
446
-
447
- /** The build matrix, one entry in one shape at a time. */
448
- function readConfigurations(
449
- analysis: Record<string, unknown> | undefined,
450
- label: string,
451
- ): readonly BuildConfiguration[] | undefined {
452
- const value = member(analysis, "configurations");
453
- if (value === undefined) {
454
- return undefined;
455
- }
456
- if (!Array.isArray(value)) {
457
- throw malformed(label, "analysis.configurations", "is not an array");
458
- }
459
- return (value as unknown[]).map((entry, index) =>
460
- readConfiguration(entry, `analysis.configurations[${String(index)}]`, label),
461
- );
462
- }
463
151
 
464
- /**
465
- * The action delimiter pair: once a document names the object both members are
466
- * required, because a pair carrying one member names no delimiters at all, so the
467
- * refusal names the member the document left out rather than pairing the one it
468
- * named with a default.
469
- */
470
- function readDelimiters(
471
- analysis: Record<string, unknown> | undefined,
472
- label: string,
473
- ): TemplateDelimiters | undefined {
474
- const at = "analysis.template_delimiters";
475
- const pair = section(analysis, "template_delimiters", at, label);
476
- if (pair === undefined) {
477
- return undefined;
478
- }
479
- return {
480
- left: requireMember(readString(pair, "left", `${at}.left`, label), `${at}.left`, label),
481
- right: requireMember(readString(pair, "right", `${at}.right`, label), `${at}.right`, label),
482
- };
483
- }
484
-
485
- /**
486
- * A severity key that names no setting: it names no live kind, or the Contract
487
- * fixes the severity of the kind or kinds it names.
488
- */
489
- function unimplementedSeverityKey(label: string, path: string, reason: string): ConfigError {
490
- return new ConfigError(
491
- "unimplemented-key",
492
- path,
493
- `${label}: key ${JSON.stringify(path)} is not implemented: ${reason}`,
494
- );
495
- }
496
-
497
- /**
498
- * Every code whose severity the Contract fixes that one severity key names, in
499
- * ascending code order: the code itself, or every code of the family its
500
- * two-digit prefix names. A family key covers every such code, so a refusal names
501
- * them all rather than the first one found.
502
- */
503
- function fixedByContract(code: string): string[] {
504
- return FIXED_SEVERITY_CODES.filter(
505
- (candidate) =>
506
- code === candidate || (code.length === FAMILY_KEY_LENGTH && candidate.startsWith(code)),
507
- ).sort();
508
- }
509
-
510
- /** The codes a refusal names: one on its own, and several with the last joined by and. */
511
- function spellCodes(codes: readonly string[]): string {
512
- if (codes.length < 2) {
513
- return codes.join("");
514
- }
515
- return `${codes.slice(0, -1).join(", ")} and ${String(codes[codes.length - 1])}`;
516
- }
517
-
518
- /** Whether one severity key names a live kind: the code itself, or a family holding one. */
519
- function namesLiveKind(code: string): boolean {
520
- return LIVE_CODES.some(
521
- (candidate) =>
522
- code === candidate || (code.length === FAMILY_KEY_LENGTH && candidate.startsWith(code)),
523
- );
524
- }
525
-
526
- /**
527
- * The severity object, one code at a time.
528
- *
529
- * A key is one issue-kind code or one two-digit family prefix. A key naming no
530
- * live kind, and a key naming a kind whose severity the Contract fixes or a family
531
- * prefix whose range holds one, is an unimplemented key rather than a setting.
532
- *
533
- * Whether a key names a kind this analyzer reports is not decided here: a live
534
- * kind of another language is a setting the analyzer of that language reads.
535
- */
536
- function readSeverity(
537
- document: Record<string, unknown> | undefined,
538
- label: string,
539
- ): ReadonlyMap<string, Severity> | undefined {
540
- const object = section(document, SEVERITY_SECTION, SEVERITY_SECTION, label);
541
- if (object === undefined) {
542
- return undefined;
543
- }
544
- const severity = new Map<string, Severity>();
545
- for (const code of Object.keys(object).sort()) {
546
- const path = `${SEVERITY_SECTION}.${code}`;
547
- if (!SEVERITY_KEY.test(code)) {
548
- throw unimplementedSeverityKey(
549
- label,
550
- path,
551
- "a severity key is one issue-kind code or one two-digit family prefix",
552
- );
553
- }
554
- if (!namesLiveKind(code)) {
555
- throw unimplementedSeverityKey(
556
- label,
557
- path,
558
- code.length === FAMILY_KEY_LENGTH
559
- ? "the family this prefix names holds no live issue kind"
560
- : "the code names no live issue kind",
561
- );
562
- }
563
- const fixed = fixedByContract(code);
564
- if (fixed.length > 0) {
565
- throw unimplementedSeverityKey(
566
- label,
567
- path,
568
- `the Contract fixes the severity of ${spellCodes(fixed)}, which this key names`,
152
+ private setting(row: SettingRow, value: unknown): void {
153
+ this.refuse(row.key.split("."), value, firstRefusal(row.value ?? {}, value));
154
+ const text = this.fractions.get(pointerOf(row.key.split(".")));
155
+ if (typeof value === "number" && text !== undefined) {
156
+ throw malformed(
157
+ this.label,
158
+ row.key,
159
+ `holds ${text}, want an integer written as an optional minus sign and digits alone`,
569
160
  );
570
161
  }
571
- const value = readEnum(object, code, path, label, SEVERITIES);
572
- if (value !== undefined) {
573
- severity.set(code, value);
162
+ const own = OWN_VOCABULARIES.get(row.key);
163
+ if (own !== undefined) {
164
+ const names = value as readonly string[];
165
+ const unknown = names.findIndex((name) => !own.has(name));
166
+ if (unknown !== -1) {
167
+ throw malformed(
168
+ this.label,
169
+ `${row.key}[${String(unknown)}]`,
170
+ `${JSON.stringify(names[unknown])} names no row this analyzer carries`,
171
+ );
172
+ }
574
173
  }
575
174
  }
576
- return severity;
577
- }
578
175
 
579
- /** The exemption classes named for switching off. */
580
- function readExemptions(
581
- document: Record<string, unknown> | undefined,
582
- label: string,
583
- ): readonly string[] | undefined {
584
- const exemptions = section(document, "exemptions", "exemptions", label);
585
- const disabled = readStrings(exemptions, "disabled", "exemptions.disabled", label, 0);
586
- if (disabled === undefined) {
587
- return undefined;
588
- }
589
- for (const entry of disabled) {
590
- if (!EXEMPTION_CLASS.test(entry)) {
591
- throw malformed(
592
- label,
593
- "exemptions.disabled",
594
- `${JSON.stringify(entry)} is not an exemption class name`,
595
- );
176
+ /** A keyed or an ignored section: an object whose member names its key pattern admits. */
177
+ private keyed(row: SettingRow, value: unknown): Readonly<Record<string, unknown>> {
178
+ this.refuse(row.key.split("."), value, firstRefusal(OBJECT, value));
179
+ const members = value as Readonly<Record<string, unknown>>;
180
+ const shape = new RegExp(row.keyPattern ?? "", "u");
181
+ for (const name of Object.keys(members).sort(bytewise)) {
182
+ const at = `${row.key}.${name}`;
183
+ if (!shape.test(name)) {
184
+ throw unimplemented(this.label, at, `the name does not match ${String(row.keyPattern)}`);
185
+ }
186
+ if (row.values !== undefined && !row.values.includes(name)) {
187
+ throw unimplemented(this.label, at, severityKeyRefused(name));
188
+ }
189
+ const member = members[name];
190
+ this.refuse([...row.key.split("."), name], member, firstRefusal(row.value ?? {}, member));
596
191
  }
192
+ return members;
597
193
  }
598
- return disabled;
599
- }
600
194
 
601
- /** The members that name a declaration, each belonging to exactly one shape. */
602
- const SHAPE_MEMBERS = ["symbol", "module", "name", "global"] as const;
603
-
604
- /** The prefix of a stable symbol reference in the TypeScript form. */
605
- const SYMBOL_PREFIX = "ts://";
606
-
607
- /**
608
- * A bare specifier: neither relative nor absolute, and not one the package's own
609
- * imports field maps, each of which names a file of the analyzed program.
610
- */
611
- const BARE_SPECIFIER = /^[^./#]/u;
612
-
613
- /**
614
- * One declaration entry, read as the one shape its members name. An entry carrying
615
- * members of two shapes, or of none, is refused at the entry, as is a module named
616
- * without the declaration's path or a path without its module.
617
- */
618
- function readDeclaration(entry: unknown, at: string, label: string): DeclarationEntry {
619
- if (!isRecord(entry)) {
620
- throw malformed(label, at, "is not an object");
621
- }
622
- const named = SHAPE_MEMBERS.filter((name) => Object.hasOwn(entry, name));
623
- if (named.length === 1 && named[0] === "symbol") {
624
- const symbol = requireMember(
625
- readString(entry, "symbol", `${at}.symbol`, label),
626
- `${at}.symbol`,
627
- label,
628
- );
629
- if (!symbol.startsWith(SYMBOL_PREFIX)) {
630
- throw malformed(label, `${at}.symbol`, `${JSON.stringify(symbol)} is not a ts:// reference`);
195
+ /** Throws the refusal the schema makes of `value`, at `at`, naming the place inside it. */
196
+ private refuse(at: Place, value: unknown, refused: SchemaRefusal | undefined): void {
197
+ if (refused === undefined) {
198
+ return;
631
199
  }
632
- return { shape: "symbol", symbol };
633
- }
634
- if (named.length === 1 && named[0] === "global") {
635
- const path = `${at}.global`;
636
- return {
637
- shape: "global",
638
- global: requireMember(readString(entry, "global", path, label), path, label),
639
- };
640
- }
641
- if (named.length === 2 && named.includes("module") && named.includes("name")) {
642
- const module = requireMember(
643
- readString(entry, "module", `${at}.module`, label),
644
- `${at}.module`,
645
- label,
646
- );
647
- if (!BARE_SPECIFIER.test(module)) {
648
- throw malformed(
649
- label,
650
- `${at}.module`,
651
- `${JSON.stringify(module)} is not a bare specifier: a file of the analyzed program is named by its symbol`,
652
- );
200
+ const place = dotted([...at, ...placeIn(value, refused.pointer, refused.member)]);
201
+ switch (refused.keyword) {
202
+ case "additionalProperties":
203
+ throw unimplemented(this.label, place);
204
+ case "required":
205
+ throw malformed(this.label, place, "is required and absent");
653
206
  }
654
- const path = `${at}.name`;
655
- return {
656
- shape: "module",
657
- module,
658
- name: requireMember(readString(entry, "name", path, label), path, label),
659
- };
207
+ throw malformed(this.label, place, refused.reason);
660
208
  }
661
- throw malformed(
662
- label,
663
- at,
664
- named.length === 0
665
- ? 'names no declaration: an entry carries "symbol", "module" with "name", or "global"'
666
- : `carries ${spell(named)}, which is not one of "symbol", "module" with "name", or "global"`,
667
- );
668
209
  }
669
210
 
670
- /** One array of declaration entries, absent where the document does not write it. */
671
- function readDeclarations(
672
- parent: Record<string, unknown> | undefined,
673
- name: string,
674
- path: string,
675
- label: string,
676
- ): readonly DeclarationEntry[] | undefined {
677
- const value = member(parent, name);
678
- if (value === undefined) {
679
- return undefined;
680
- }
681
- if (!Array.isArray(value)) {
682
- throw malformed(label, path, "is not an array");
683
- }
684
- return (value as unknown[]).map((entry, index) =>
685
- readDeclaration(entry, `${path}[${String(index)}]`, label),
686
- );
687
- }
688
-
689
- /**
690
- * One lifecycle contract. The member names are required, and the entry names at least
691
- * one component declaration or one base class, because a contract naming neither
692
- * makes no class a component and so retains nothing.
693
- */
694
- function readLifecycleContract(entry: unknown, at: string, label: string): LifecycleContract {
695
- if (!isRecord(entry)) {
696
- throw malformed(label, at, "is not an object");
697
- }
698
- const components = readDeclarations(entry, "components", `${at}.components`, label) ?? [];
699
- const bases = readDeclarations(entry, "bases", `${at}.bases`, label) ?? [];
700
- const members = readStrings(entry, "members", `${at}.members`, label, 1);
701
- if (members === undefined) {
702
- throw malformed(label, `${at}.members`, "is required and names nothing");
703
- }
704
- if (components.length === 0 && bases.length === 0) {
705
- throw malformed(
706
- label,
707
- at,
708
- 'names no declaration in "components" and no class in "bases", so it makes no class a component',
709
- );
710
- }
711
- return { components, bases, members };
712
- }
713
-
714
- /** The lifecycle contracts, one per framework the project declares. */
715
- function readLifecycleContracts(
716
- ts: Record<string, unknown> | undefined,
717
- label: string,
718
- ): readonly LifecycleContract[] | undefined {
719
- const path = "ts.lifecycle_contracts";
720
- const value = member(ts, "lifecycle_contracts");
721
- if (value === undefined) {
722
- return undefined;
723
- }
724
- if (!Array.isArray(value)) {
725
- throw malformed(label, path, "is not an array");
726
- }
727
- return (value as unknown[]).map((entry, index) =>
728
- readLifecycleContract(entry, `${path}[${String(index)}]`, label),
729
- );
211
+ /** One source a resolution reads, with what it supplies. */
212
+ interface Held {
213
+ readonly source: Source;
214
+ readonly label: string;
215
+ readonly supplied: Supplied;
730
216
  }
731
217
 
732
- /** One string member a provider entry requires, refused where it fails `pattern`. */
733
- function readMatching(
734
- entry: Record<string, unknown>,
735
- name: string,
736
- at: string,
737
- label: string,
738
- pattern: RegExp,
739
- ): string {
740
- const path = `${at}.${name}`;
741
- const value = requireMember(readString(entry, name, path, label), path, label);
742
- if (!pattern.test(value)) {
743
- throw malformed(label, path, `${JSON.stringify(value)} does not match ${pattern.source}`);
744
- }
745
- return value;
746
- }
218
+ const supplies = (): Supplied => ({ values: new Map(), keyed: new Map() });
747
219
 
748
- /**
749
- * One provider entry, read as the one shape its members name: a member only an
750
- * acquirable analyzer carries makes it acquirable, and then it carries all three.
751
- */
752
- function readProvider(entry: unknown, at: string, label: string): Provider {
753
- if (!isRecord(entry)) {
754
- throw malformed(label, at, "is not an object");
755
- }
756
- const name = readMatching(entry, "name", at, label, ANALYZER_NAME);
757
- const languages = readStrings(entry, "languages", `${at}.languages`, label, 1, LANGUAGES);
758
- if (languages === undefined) {
759
- throw malformed(label, `${at}.languages`, "is required and names nothing");
220
+ /** Reads and checks one configuration document. */
221
+ function documentSource(source: Source, document: Document): Held {
222
+ const { text, label } = document;
223
+ refuseOverBound(label, text);
224
+ let read;
225
+ try {
226
+ read = readStrict(text);
227
+ } catch (error: unknown) {
228
+ if (!(error instanceof StrictJSONError)) {
229
+ throw error;
230
+ }
231
+ throw malformed(label, dotted(error.place), locatedFault(text, error));
760
232
  }
761
- const commandPath = `${at}.command`;
762
- const command = requireMember(
763
- readString(entry, "command", commandPath, label),
764
- commandPath,
765
- label,
766
- );
767
- if (!ACQUIRABLE_MEMBERS.some((member) => Object.hasOwn(entry, member))) {
768
- return { shape: "installed", name, languages, command };
233
+ if (!isRecord(read.value)) {
234
+ throw malformed(label, "", "is not one JSON object");
769
235
  }
770
- return {
771
- shape: "acquirable",
772
- name,
773
- languages,
774
- command,
775
- source: readMatching(entry, "source", at, label, PROVIDER_SOURCE),
776
- version: readMatching(entry, "version", at, label, PROVIDER_VERSION),
777
- digest: readMatching(entry, "digest", at, label, PROVIDER_DIGEST),
778
- };
236
+ const supplied = supplies();
237
+ new Walk(label, read.fractions, supplied).section("", read.value);
238
+ return { source, label, supplied };
779
239
  }
780
240
 
781
- /**
782
- * The provider list. Every file a run writes for an analyzer is keyed by the
783
- * entry's name, so a list naming one name twice is refused at the later entry.
784
- */
785
- function readProviders(
786
- providers: Record<string, unknown> | undefined,
787
- label: string,
788
- ): readonly Provider[] | undefined {
789
- const path = "providers.analyzers";
790
- const value = member(providers, "analyzers");
791
- if (value === undefined) {
792
- return undefined;
793
- }
794
- if (!Array.isArray(value)) {
795
- throw malformed(label, path, "is not an array");
796
- }
797
- const read = (value as unknown[]).map((entry, index) =>
798
- readProvider(entry, `${path}[${String(index)}]`, label),
799
- );
800
- const first = new Map<string, number>();
801
- read.forEach((entry, index) => {
802
- const earlier = first.get(entry.name);
803
- if (earlier !== undefined) {
804
- throw malformed(
805
- label,
806
- `${path}[${String(index)}].name`,
807
- `${JSON.stringify(entry.name)} is already the name of ${path}[${String(earlier)}], ` +
808
- "and every entry's name keys the files a run writes for it",
809
- );
241
+ /** The command line as a source: each flag checked as the one-setting document it supplies. */
242
+ function flagSource(flags: ReadonlyMap<string, string>): Held {
243
+ const supplied = supplies();
244
+ for (const key of [...flags.keys()].sort(bytewise)) {
245
+ const row = ROWS.get(key);
246
+ const text = flags.get(key) ?? "";
247
+ if (row?.option === undefined) {
248
+ throw unimplemented("the command line", key, "no flag supplies it");
810
249
  }
811
- first.set(entry.name, index);
812
- });
813
- return read;
814
- }
815
-
816
- /**
817
- * Reads one already-parsed configuration document as the settings it supplies,
818
- * refusing every constraint the closed key list declares that a parse cannot
819
- * express: a value of the wrong type, a value outside a closed set, an array that
820
- * repeats an entry or holds too few, and a count below its minimum.
821
- */
822
- function readDoc(value: unknown, label: string): Doc {
823
- if (!isRecord(value)) {
824
- throw malformed(label, "", "is not one JSON object");
825
- }
826
- const analysis = section(value, "analysis", "analysis", label);
827
- const matrix = section(analysis, "matrix", "analysis.matrix", label);
828
- const target = section(value, "target", "target", label);
829
- const consumers = section(value, "consumers", "consumers", label);
830
- const roots = section(value, "roots", "roots", label);
831
- const reporters = section(value, "reporters", "reporters", label);
832
- const providers = section(value, "providers", "providers", label);
833
- const ts = section(value, "ts", "ts", label);
834
- section(value, "go", "go", label);
835
- section(value, "provenance", "provenance", label);
836
-
837
- const contractVersion = readString(value, "contract_version", "contract_version", label);
838
- if (contractVersion !== undefined && !CONTRACT_VERSION_PATTERN.test(contractVersion)) {
839
- throw malformed(
840
- label,
841
- "contract_version",
842
- `${JSON.stringify(contractVersion)} is not a semantic version`,
250
+ const integer = row.value?.["type"] === "integer" && /^-?[0-9]+$/u.test(text);
251
+ let document: unknown = integer ? Number(text) : text;
252
+ for (const name of key.split(".").reverse()) {
253
+ document = { [name]: document };
254
+ }
255
+ new Walk(`--${row.option}`, new Map(), supplied).section(
256
+ "",
257
+ document as Record<string, unknown>,
843
258
  );
844
259
  }
845
-
846
- return {
847
- contractVersion,
848
- targetKind: readTargetKind(target, label),
849
- languages: readStrings(analysis, "languages", "analysis.languages", label, 0, LANGUAGES),
850
- minConfidence: readEnum(
851
- analysis,
852
- "min_confidence",
853
- "analysis.min_confidence",
854
- label,
855
- CONFIDENCES,
856
- ),
857
- generatedFiles: readEnum(
858
- analysis,
859
- "generated_files",
860
- "analysis.generated_files",
861
- label,
862
- GENERATED_FILES,
863
- ),
864
- consumerTests: readEnum(
865
- analysis,
866
- "consumer_tests",
867
- "analysis.consumer_tests",
868
- label,
869
- CONSUMER_TESTS,
870
- ),
871
- configurations: readConfigurations(analysis, label),
872
- matrixComplete: readBoolean(matrix, "complete", "analysis.matrix.complete", label),
873
- templateDirs: readStrings(analysis, "template_dirs", "analysis.template_dirs", label, 0),
874
- templateDelimiters: readDelimiters(analysis, label),
875
- consumersComplete: readBoolean(consumers, "complete", "consumers.complete", label),
876
- rootPatterns: readStrings(roots, "patterns", "roots.patterns", label, 0),
877
- severity: readSeverity(value, label),
878
- exemptionsDisabled: readExemptions(value, label),
879
- formats: readStrings(reporters, "formats", "reporters.formats", label, 1, FORMATS),
880
- sort: readEnum(reporters, "sort", "reporters.sort", label, SORTS),
881
- cascade: readEnum(reporters, "cascade", "reporters.cascade", label, CASCADES),
882
- maxFindings: readCount(reporters, "max_findings", "reporters.max_findings", label),
883
- failOn: readEnum(reporters, "fail_on", "reporters.fail_on", label, SEVERITIES),
884
- providers: readProviders(providers, label),
885
- testFiles: readStrings(ts, "test_files", "ts.test_files", label, 1),
886
- entryFiles: readStrings(ts, "entry_files", "ts.entry_files", label, 0),
887
- componentExtensions: readComponentExtensions(ts, label),
888
- disabledConventions: readDisabledConventions(ts, label),
889
- injectionRegistrations: readDeclarations(
890
- ts,
891
- "injection_registrations",
892
- "ts.injection_registrations",
893
- label,
894
- ),
895
- lifecycleContracts: readLifecycleContracts(ts, label),
896
- serializers: readDeclarations(ts, "serializers", "ts.serializers", label),
897
- };
260
+ return { source: "flag", label: "", supplied };
898
261
  }
899
262
 
900
- /** Parses one document's text and walks it against the closed key list. */
901
- function parseDocument(text: string, node: KeyNode, label: string): unknown {
902
- let value: unknown;
903
- try {
904
- value = JSON.parse(text);
905
- } catch (error: unknown) {
906
- throw malformed(label, "", error instanceof Error ? error.message : String(error));
907
- }
908
- checkDocument(text, node, label);
909
- return value;
263
+ function originOf(held: Held, key: string): Origin {
264
+ const label = held.source === "flag" ? `--${String(ROWS.get(key)?.option)}` : held.label;
265
+ return { source: held.source, label };
910
266
  }
911
267
 
912
- /** Names a document in a refusal: the path the invocation passed, or what it is. */
913
- function labelOr(label: string | undefined, named: string): string {
914
- return label === undefined || label === "" ? named : label;
268
+ function searched(named: string, document: Document | undefined): string {
269
+ return `${named} (${document === undefined ? "not present" : document.label})`;
915
270
  }
916
271
 
917
- /**
918
- * Decodes the documents one invocation supplies, in the order they outrank each
919
- * other, skipping every source it does not carry.
920
- */
921
- function readSources(inputs: Inputs): DocSource[] {
922
- const sources: DocSource[] = [];
923
- const flagLabels = inputs.flagLabels ?? new Map<string, string>();
924
- if (inputs.flags !== undefined) {
925
- const flat = parseDocument(inputs.flags, { kind: "map" }, FLAGS_LABEL);
926
- if (!isRecord(flat)) {
927
- throw malformed(FLAGS_LABEL, "", "is not one JSON object");
928
- }
929
- sources.push({
930
- doc: readDoc(nestFlags(flat, FLAGS_LABEL), FLAGS_LABEL),
931
- kind: "flag",
932
- label: "",
933
- flagLabels,
934
- });
272
+ /** Sets the value at one dotted key of the resolved tree, whose sections the table lists first. */
273
+ function setAt(tree: Record<string, unknown>, key: string, value: unknown): void {
274
+ const names = key.split(".");
275
+ let into = tree;
276
+ for (const name of names.slice(0, -1)) {
277
+ into = into[name] as Record<string, unknown>;
935
278
  }
936
- const held: readonly { kind: Source; named: string; text?: string; label?: string }[] = [
937
- {
938
- kind: "repository",
939
- named: "the repository configuration",
940
- ...(inputs.repository === undefined ? {} : { text: inputs.repository }),
941
- ...(inputs.repositoryLabel === undefined ? {} : { label: inputs.repositoryLabel }),
942
- },
943
- {
944
- kind: "central",
945
- named: "the central configuration",
946
- ...(inputs.central === undefined ? {} : { text: inputs.central }),
947
- ...(inputs.centralLabel === undefined ? {} : { label: inputs.centralLabel }),
948
- },
949
- ];
950
- for (const entry of held) {
951
- if (entry.text === undefined) {
952
- continue;
953
- }
954
- const label = labelOr(entry.label, entry.named);
955
- sources.push({
956
- doc: readDoc(parseDocument(entry.text, SCHEMA_ROOT, label), label),
957
- kind: entry.kind,
958
- label: entry.label ?? "",
959
- flagLabels,
960
- });
961
- }
962
- return sources;
279
+ into[names.at(-1) ?? key] = value;
963
280
  }
964
281
 
965
282
  /**
966
- * The value the highest-ranked source carrying one supplies, with where it came
967
- * from recorded, or the default.
283
+ * Every setting this analyzer reads, each from the highest-ranked source carrying it,
284
+ * else its default, in the closed key list's order. A setting with no default no
285
+ * source supplies is refused where its row is required and `enforce` holds, and left
286
+ * unset otherwise. A keyed section's members overlay its default's, member by member.
968
287
  */
969
- function settle<T>(
970
- fallback: T,
971
- path: string,
972
- provenance: Map<string, Origin>,
973
- sources: readonly DocSource[],
974
- read: (doc: Doc) => T | undefined,
975
- ): T {
976
- for (const source of sources) {
977
- const value = read(source.doc);
978
- if (value !== undefined) {
979
- provenance.set(path, originOf(source, path));
980
- return value;
981
- }
288
+ function settle(
289
+ inputs: Inputs,
290
+ enforce: boolean,
291
+ ): { config: PartialConfig; provenance: Provenance } {
292
+ const sources: Held[] = [];
293
+ if (inputs.flags !== undefined && inputs.flags.size > 0) {
294
+ sources.push(flagSource(inputs.flags));
982
295
  }
983
- provenance.set(path, { source: "default", label: "" });
984
- return fallback;
985
- }
986
-
987
- /**
988
- * The severity object, resolved one code at a time so a code only the central
989
- * configuration names keeps its central value. An object that resolves empty
990
- * carries one entry for the object itself.
991
- */
992
- function settleSeverity(
993
- provenance: Map<string, Origin>,
994
- sources: readonly DocSource[],
995
- ): ReadonlyMap<string, Severity> {
996
- const codes = new Set<string>();
997
- for (const source of sources) {
998
- for (const code of source.doc.severity?.keys() ?? []) {
999
- codes.add(code);
1000
- }
1001
- }
1002
- const severity = new Map<string, Severity>();
1003
- for (const code of [...codes].sort()) {
1004
- const path = `${SEVERITY_SECTION}.${code}`;
1005
- for (const source of sources) {
1006
- const value = source.doc.severity?.get(code);
1007
- if (value === undefined) {
1008
- continue;
1009
- }
1010
- severity.set(code, value);
1011
- provenance.set(path, originOf(source, path));
1012
- break;
1013
- }
296
+ if (inputs.repository !== undefined) {
297
+ sources.push(documentSource("repository", inputs.repository));
1014
298
  }
1015
- if (codes.size > 0) {
1016
- return severity;
299
+ if (inputs.central !== undefined) {
300
+ sources.push(documentSource("central", inputs.central));
1017
301
  }
1018
- provenance.set(SEVERITY_SECTION, { source: "default", label: "" });
1019
- for (const source of sources) {
1020
- if (source.doc.severity !== undefined) {
1021
- provenance.set(SEVERITY_SECTION, originOf(source, SEVERITY_SECTION));
1022
- break;
302
+ const tree: Record<string, unknown> = {};
303
+ const provenance = new Map<string, Origin>();
304
+ for (const row of SETTINGS) {
305
+ if (!row.read || row.role === "ignored") {
306
+ continue;
1023
307
  }
1024
- }
1025
- return severity;
1026
- }
1027
-
1028
- /** Names one configuration source and whether the invocation carried it. */
1029
- function describeSource(
1030
- named: string,
1031
- text: string | undefined,
1032
- label: string | undefined,
1033
- ): string {
1034
- if (text === undefined) {
1035
- return `${named} (not present)`;
1036
- }
1037
- return `${named} (${labelOr(label, "unnamed")})`;
1038
- }
1039
-
1040
- /**
1041
- * Refuses a provenance entry whose source names no file or flag, which would
1042
- * print a provenance value the closed key list refuses to read back.
1043
- */
1044
- function checkLabels(provenance: ReadonlyMap<string, Origin>): void {
1045
- for (const path of [...provenance.keys()].sort()) {
1046
- const origin = provenance.get(path);
1047
- if (origin !== undefined && origin.source !== "default" && origin.label === "") {
1048
- throw malformed(
1049
- origin.source === "flag" ? FLAGS_LABEL : origin.source,
1050
- path,
1051
- `came from ${origin.source}, which names no file or flag`,
1052
- );
308
+ if (row.role === "section") {
309
+ setAt(tree, row.key, {});
310
+ } else if (row.role === "keyed") {
311
+ const members: Record<string, unknown> = isRecord(row.default) ? { ...row.default } : {};
312
+ provenance.set(row.key, { source: "default", label: "" });
313
+ for (const name of Object.keys(members)) {
314
+ provenance.set(`${row.key}.${name}`, { source: "default", label: "" });
315
+ }
316
+ for (const held of sources.toReversed()) {
317
+ const named = held.supplied.keyed.get(row.key);
318
+ if (named === undefined) {
319
+ continue;
320
+ }
321
+ provenance.set(row.key, originOf(held, row.key));
322
+ for (const [name, value] of Object.entries(named)) {
323
+ members[name] = value;
324
+ provenance.set(`${row.key}.${name}`, originOf(held, row.key));
325
+ }
326
+ }
327
+ if (Object.keys(members).length > 0) {
328
+ provenance.delete(row.key);
329
+ }
330
+ setAt(tree, row.key, canonical(members));
331
+ } else {
332
+ const held = sources.find((one) => one.supplied.values.has(row.key));
333
+ if (held !== undefined) {
334
+ setAt(tree, row.key, held.supplied.values.get(row.key));
335
+ provenance.set(row.key, originOf(held, row.key));
336
+ } else if (row.default !== undefined) {
337
+ setAt(tree, row.key, canonical(row.default));
338
+ provenance.set(row.key, { source: "default", label: "" });
339
+ } else if (enforce && row.required === true) {
340
+ throw new UsageError(
341
+ `${row.key} is not set, it has no default and is never inferred: searched ` +
342
+ `${searched("the repository configuration", inputs.repository)} and ` +
343
+ searched("the central configuration", inputs.central),
344
+ );
345
+ }
1053
346
  }
1054
347
  }
348
+ // gen-config.ts marks a member optional exactly where this loop may leave it unset.
349
+ return { config: tree as unknown as PartialConfig, provenance };
1055
350
  }
1056
351
 
1057
352
  /**
1058
- * Applies a command-line flag over the repository configuration over the central
353
+ * Applies the command-line flags over the repository configuration over the central
1059
354
  * configuration over the default each key declares, and returns the resolved
1060
- * configuration with the origin of every setting.
1061
- *
1062
- * It throws a {@link ConfigError} for a document that names a key the closed key
1063
- * list does not declare, a document that names one key twice, and a resolved
1064
- * configuration no source supplied a target kind for. Every one of them maps to
1065
- * the usage exit code.
1066
- */
1067
- export function resolve(inputs: Inputs): { config: Config; provenance: Provenance } {
1068
- const { config, provenance } = settleAll(inputs);
1069
- if (config.targetKind === "") {
1070
- throw new ConfigError(
1071
- "missing-target-kind",
1072
- "target.kind",
1073
- "target.kind is not set, it has no default and is never inferred: searched " +
1074
- `${describeSource("the repository configuration", inputs.repository, inputs.repositoryLabel)} and ` +
1075
- describeSource("the central configuration", inputs.central, inputs.centralLabel),
1076
- );
1077
- }
1078
- checkLabels(provenance);
1079
- return { config, provenance };
1080
- }
1081
-
1082
- /**
1083
- * The configuration the documents resolve to, every document read and refused as
1084
- * {@link resolve} refuses it, with no target kind required of them: which projects a
1085
- * run analyzes does not depend on the kind, so a question about the projects asks
1086
- * none. The kind is empty where no document names one.
355
+ * configuration with the origin of every setting: its keys in the closed key list's
356
+ * order, every object's members inside a value ascending. Every refusal is a
357
+ * {@link UsageError}.
1087
358
  */
1088
- export function resolveUnkinded(inputs: Inputs): Config {
1089
- return settleAll(inputs).config;
359
+ export function resolve(inputs: Inputs): { config: ResolvedConfig; provenance: Provenance } {
360
+ const { config, provenance } = settle(inputs, true);
361
+ return { config: config as ResolvedConfig, provenance };
1090
362
  }
1091
363
 
1092
364
  /**
1093
- * Every setting the documents supply, each from the highest-ranked source carrying
1094
- * it, with the origin of each. The target kind is empty where no source names one.
365
+ * The configuration the sources resolve to, refused as {@link resolve} refuses it but
366
+ * with no target kind required: which projects a run analyzes asks none.
1095
367
  */
1096
- function settleAll(inputs: Inputs): { config: Config; provenance: Map<string, Origin> } {
1097
- const sources = readSources(inputs);
1098
- const defaults = defaultConfig();
1099
- const provenance = new Map<string, Origin>();
1100
- const at = <T>(path: string, fallback: T, read: (doc: Doc) => T | undefined): T =>
1101
- settle(fallback, path, provenance, sources, read);
1102
-
1103
- const config: Config = {
1104
- contractVersion: at("contract_version", defaults.contractVersion, (doc) => doc.contractVersion),
1105
- targetKind: at<TargetKindSetting | "">("target.kind", "", (doc) => doc.targetKind),
1106
- analysis: {
1107
- languages: at("analysis.languages", defaults.analysis.languages, (doc) => doc.languages),
1108
- minConfidence: at(
1109
- "analysis.min_confidence",
1110
- defaults.analysis.minConfidence,
1111
- (doc) => doc.minConfidence,
1112
- ),
1113
- generatedFiles: at(
1114
- "analysis.generated_files",
1115
- defaults.analysis.generatedFiles,
1116
- (doc) => doc.generatedFiles,
1117
- ),
1118
- consumerTests: at(
1119
- "analysis.consumer_tests",
1120
- defaults.analysis.consumerTests,
1121
- (doc) => doc.consumerTests,
1122
- ),
1123
- configurations: at(
1124
- "analysis.configurations",
1125
- defaults.analysis.configurations,
1126
- (doc) => doc.configurations,
1127
- ),
1128
- matrixComplete: at(
1129
- "analysis.matrix.complete",
1130
- defaults.analysis.matrixComplete,
1131
- (doc) => doc.matrixComplete,
1132
- ),
1133
- templateDirs: at(
1134
- "analysis.template_dirs",
1135
- defaults.analysis.templateDirs,
1136
- (doc) => doc.templateDirs,
1137
- ),
1138
- templateDelimiters: at(
1139
- "analysis.template_delimiters",
1140
- defaults.analysis.templateDelimiters,
1141
- (doc) => doc.templateDelimiters,
1142
- ),
1143
- },
1144
- consumersComplete: at(
1145
- "consumers.complete",
1146
- defaults.consumersComplete,
1147
- (doc) => doc.consumersComplete,
1148
- ),
1149
- rootPatterns: at("roots.patterns", defaults.rootPatterns, (doc) => doc.rootPatterns),
1150
- severity: settleSeverity(provenance, sources),
1151
- exemptionsDisabled: at(
1152
- "exemptions.disabled",
1153
- defaults.exemptionsDisabled,
1154
- (doc) => doc.exemptionsDisabled,
1155
- ),
1156
- reporters: {
1157
- formats: at("reporters.formats", defaults.reporters.formats, (doc) => doc.formats),
1158
- sort: at("reporters.sort", defaults.reporters.sort, (doc) => doc.sort),
1159
- cascade: at("reporters.cascade", defaults.reporters.cascade, (doc) => doc.cascade),
1160
- maxFindings: at(
1161
- "reporters.max_findings",
1162
- defaults.reporters.maxFindings,
1163
- (doc) => doc.maxFindings,
1164
- ),
1165
- failOn: at("reporters.fail_on", defaults.reporters.failOn, (doc) => doc.failOn),
1166
- },
1167
- providers: {
1168
- analyzers: at("providers.analyzers", defaults.providers.analyzers, (doc) => doc.providers),
1169
- },
1170
- ts: {
1171
- testFiles: at("ts.test_files", defaults.ts.testFiles, (doc) => doc.testFiles),
1172
- entryFiles: at("ts.entry_files", defaults.ts.entryFiles, (doc) => doc.entryFiles),
1173
- componentExtensions: at(
1174
- "ts.component_extensions",
1175
- defaults.ts.componentExtensions,
1176
- (doc) => doc.componentExtensions,
1177
- ),
1178
- disabledConventions: at(
1179
- "ts.disabled_conventions",
1180
- defaults.ts.disabledConventions,
1181
- (doc) => doc.disabledConventions,
1182
- ),
1183
- injectionRegistrations: at(
1184
- "ts.injection_registrations",
1185
- defaults.ts.injectionRegistrations,
1186
- (doc) => doc.injectionRegistrations,
1187
- ),
1188
- lifecycleContracts: at(
1189
- "ts.lifecycle_contracts",
1190
- defaults.ts.lifecycleContracts,
1191
- (doc) => doc.lifecycleContracts,
1192
- ),
1193
- serializers: at("ts.serializers", defaults.ts.serializers, (doc) => doc.serializers),
1194
- },
1195
- };
1196
-
1197
- return { config, provenance };
368
+ export function resolvePartial(inputs: Inputs): PartialConfig {
369
+ return settle(inputs, false).config;
1198
370
  }