yarramate 1.15.0 → 1.15.1

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/compiler.js CHANGED
@@ -362,9 +362,22 @@ function compileWorkspaceResolved(parsed) {
362
362
  }
363
363
  const alreadyDeclaresPolicy = pendingProfiles.some(({ identity }) => identity === shippedPolicyIdentity);
364
364
  if (!alreadyDeclaresPolicy) {
365
+ // This probe runs BEFORE the document gate that rejects a source whose
366
+ // schema check failed, so it has to hold its own precondition: a source
367
+ // that composes to anything but a mapping - an empty file, a comment-only
368
+ // one, a bare scalar - selects no profile at all. It used to read
369
+ // `.profile` through an `as` cast, which is what hid the null from the
370
+ // typechecker, and an empty document crashed the whole compile with a
371
+ // `TypeError` instead of the `YM201 must be object` its schema already
372
+ // produces. Every other consumer of a parsed entry checks its diagnostics
373
+ // first (the profile walk above, the pattern walk below); this one could
374
+ // not, because it runs before that gate exists, so it narrows instead.
365
375
  const selected = documentInputs.some(({ entry }) => {
366
376
  const value = entry.value;
367
- return value.profile === shippedPolicyIdentity;
377
+ return (typeof value === 'object' &&
378
+ value !== null &&
379
+ value.profile ===
380
+ shippedPolicyIdentity);
368
381
  });
369
382
  const extended = pendingProfiles.some(({ value }) => value.extends === shippedPolicyIdentity);
370
383
  if (selected || extended) {
@@ -18,7 +18,13 @@ export type SubjectReferenceGroup = 'document' | 'projection' | 'evidence' | 'ad
18
18
  export type SubjectReferenceForm = 'declaration' | 'reference' | 'qualified';
19
19
  export interface SubjectReferencePosition {
20
20
  readonly group: SubjectReferenceGroup;
21
- /** Key path from the document root; `*` matches every sequence index. */
21
+ /**
22
+ * Key path from the document root; `*` matches every element of a
23
+ * collection, which is every index of a sequence and every value of a
24
+ * mapping whose keys are open. Both spell `*` because the schema, not the
25
+ * position, is what decides which one a path lands on: `items` and
26
+ * `patternProperties` are the same shape of "and now, each of these".
27
+ */
22
28
  readonly path: readonly string[];
23
29
  readonly form: SubjectReferenceForm;
24
30
  }
@@ -25,6 +25,15 @@ export const SUBJECT_REFERENCE_POSITIONS = [
25
25
  path: ['concepts', '*', 'constraints', '*', 'ref'],
26
26
  form: 'reference',
27
27
  },
28
+ // The subjects bound into a pattern instance's slots (ADR 0123). Keyed by
29
+ // slot name rather than indexed, which is why it went missing: this is the
30
+ // only address in any of the four schemas that lives in a mapping, and the
31
+ // completeness walker below derived only sequences.
32
+ {
33
+ group: 'document',
34
+ path: ['concepts', '*', 'parts', '*'],
35
+ form: 'reference',
36
+ },
28
37
  {
29
38
  group: 'document',
30
39
  path: ['concepts', '*', 'references', '*', 'ref'],
@@ -150,10 +159,22 @@ const collect = (node, path, pointer, form, documentId, source, hits, aliases) =
150
159
  }
151
160
  const [segment, ...rest] = path;
152
161
  if (segment === '*') {
153
- if (!isSeq(node))
162
+ if (isSeq(node)) {
163
+ for (const [index, item] of node.items.entries()) {
164
+ collect(item, rest, `${pointer}/${index}`, form, documentId, source, hits, aliases);
165
+ }
154
166
  return;
155
- for (const [index, item] of node.items.entries()) {
156
- collect(item, rest, `${pointer}/${index}`, form, documentId, source, hits, aliases);
167
+ }
168
+ // A mapping with open keys, which today is `parts` alone. The pointer
169
+ // segment is the authored key, so a diagnostic names the SLOT ("/parts/
170
+ // interface") rather than a position the reader would have to count out.
171
+ if (isMap(node)) {
172
+ for (const item of node.items) {
173
+ const key = isScalar(item.key) ? String(item.key.value) : undefined;
174
+ if (key === undefined)
175
+ continue;
176
+ collect(item.value, rest, `${pointer}/${key}`, form, documentId, source, hits, aliases);
177
+ }
157
178
  }
158
179
  return;
159
180
  }