@o3co/ts.hocon 1.4.1 → 1.5.2

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/zod.cjs CHANGED
@@ -329,6 +329,192 @@ function mergeUnresolved(receiver, fallback) {
329
329
  return result;
330
330
  }
331
331
 
332
+ // src/internal/resolver/fold-self-ref.ts
333
+ function substFullKey(s) {
334
+ return stringSegmentsToKey(s.segments.map((seg) => seg.text));
335
+ }
336
+ function stringSegmentsToKey(segments) {
337
+ return segments.map((t) => {
338
+ if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
339
+ return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
340
+ }
341
+ return t;
342
+ }).join(".");
343
+ }
344
+ function containsSelfRef(v, fullKey) {
345
+ if (isSubst(v)) return substFullKey(v) === fullKey;
346
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
347
+ if (isAppend(v)) {
348
+ return containsSelfRef(v.existing, fullKey) || containsSelfRef(v.elem, fullKey);
349
+ }
350
+ if (isResObj(v)) {
351
+ for (const f of v.fields.values()) {
352
+ if (containsSelfRef(f, fullKey)) return true;
353
+ }
354
+ return false;
355
+ }
356
+ const hv = v;
357
+ if (hv.kind === "array") {
358
+ return hv.items.some((item) => containsSelfRef(item, fullKey));
359
+ }
360
+ if (hv.kind === "object") {
361
+ for (const f of hv.fields.values()) {
362
+ if (containsSelfRef(f, fullKey)) return true;
363
+ }
364
+ return false;
365
+ }
366
+ return false;
367
+ }
368
+ function foldSelfRef(v, fullKey, replacement) {
369
+ if (isSubst(v)) {
370
+ return substFullKey(v) === fullKey ? replacement : v;
371
+ }
372
+ if (isConcat(v)) {
373
+ return {
374
+ _kind: "concat-placeholder",
375
+ nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
376
+ line: v.line,
377
+ col: v.col
378
+ };
379
+ }
380
+ if (isAppend(v)) {
381
+ return {
382
+ _kind: "append-placeholder",
383
+ existing: foldSelfRef(v.existing, fullKey, replacement),
384
+ elem: foldSelfRef(v.elem, fullKey, replacement)
385
+ };
386
+ }
387
+ if (isResObj(v)) {
388
+ const newFields = /* @__PURE__ */ new Map();
389
+ for (const [k, val] of v.fields) {
390
+ newFields.set(k, foldSelfRef(val, fullKey, replacement));
391
+ }
392
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
393
+ }
394
+ const hv = v;
395
+ if (hv.kind === "array") {
396
+ return {
397
+ kind: "array",
398
+ items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
399
+ };
400
+ }
401
+ if (hv.kind === "object") {
402
+ const newFields = /* @__PURE__ */ new Map();
403
+ for (const [k, val] of hv.fields) {
404
+ newFields.set(k, foldSelfRef(val, fullKey, replacement));
405
+ }
406
+ return { kind: "object", fields: newFields };
407
+ }
408
+ return v;
409
+ }
410
+ function foldOrSkipPrior(prior, fullKey, old) {
411
+ if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
412
+ if (old === void 0) return void 0;
413
+ return foldSelfRef(prior, fullKey, old);
414
+ }
415
+ function cloneResolverValue(v) {
416
+ if (isSubst(v)) {
417
+ return {
418
+ _kind: "subst-placeholder",
419
+ segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
420
+ optional: v.optional,
421
+ listSuffix: v.listSuffix,
422
+ line: v.line,
423
+ col: v.col,
424
+ prefixLen: v.prefixLen
425
+ };
426
+ }
427
+ if (isConcat(v)) {
428
+ return {
429
+ _kind: "concat-placeholder",
430
+ nodes: v.nodes.map((n) => cloneResolverValue(n)),
431
+ line: v.line,
432
+ col: v.col
433
+ };
434
+ }
435
+ if (isResObj(v)) {
436
+ const newFields = /* @__PURE__ */ new Map();
437
+ for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
438
+ const newPriors = /* @__PURE__ */ new Map();
439
+ for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
440
+ return { _kind: "res-obj", fields: newFields, priorValues: newPriors };
441
+ }
442
+ if (isAppend(v)) {
443
+ return {
444
+ _kind: "append-placeholder",
445
+ existing: cloneResolverValue(v.existing),
446
+ elem: cloneResolverValue(v.elem)
447
+ };
448
+ }
449
+ const hv = v;
450
+ if (hv.kind === "array") {
451
+ return {
452
+ kind: "array",
453
+ items: hv.items.map((item) => cloneResolverValue(item))
454
+ };
455
+ }
456
+ if (hv.kind === "object") {
457
+ const newFields = /* @__PURE__ */ new Map();
458
+ for (const [k, val] of hv.fields) {
459
+ newFields.set(k, cloneResolverValue(val));
460
+ }
461
+ return { kind: "object", fields: newFields };
462
+ }
463
+ return hv;
464
+ }
465
+ function foldNestedSelfRefs(v, pathPrefix) {
466
+ if (!isResObj(v)) return v;
467
+ const newFields = /* @__PURE__ */ new Map();
468
+ for (const [k, fieldVal] of v.fields) {
469
+ const childPath = [...pathPrefix, k];
470
+ const fullKey = stringSegmentsToKey(childPath);
471
+ const folded = foldNestedSelfRefs(fieldVal, childPath);
472
+ let finalVal = folded;
473
+ if (containsSelfRef(folded, fullKey)) {
474
+ const leafPrior = v.priorValues.get(k);
475
+ if (leafPrior !== void 0) {
476
+ const leafPriorFolded = foldNestedSelfRefs(leafPrior, childPath);
477
+ finalVal = foldSelfRef(folded, fullKey, leafPriorFolded);
478
+ }
479
+ }
480
+ newFields.set(k, finalVal);
481
+ }
482
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
483
+ }
484
+ function containsSubstByPath(v, target) {
485
+ if (isSubst(v)) return segmentsTextEqual(v.segments, target);
486
+ if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
487
+ if (isAppend(v)) {
488
+ return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
489
+ }
490
+ if (isResObj(v)) {
491
+ for (const f of v.fields.values()) {
492
+ if (containsSubstByPath(f, target)) return true;
493
+ }
494
+ return false;
495
+ }
496
+ const hv = v;
497
+ if (hv.kind === "array") {
498
+ return hv.items.some((item) => containsSubstByPath(item, target));
499
+ }
500
+ if (hv.kind === "object") {
501
+ for (const f of hv.fields.values()) {
502
+ if (containsSubstByPath(f, target)) return true;
503
+ }
504
+ return false;
505
+ }
506
+ return false;
507
+ }
508
+ function segmentsTextEqual(a, b) {
509
+ if (a.length !== b.length) return false;
510
+ for (let i = 0; i < a.length; i++) {
511
+ const ai = a[i];
512
+ const bi = b[i];
513
+ if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
514
+ }
515
+ return true;
516
+ }
517
+
332
518
  // src/internal/resolver/utils.ts
333
519
  function segmentsToKey(segments) {
334
520
  return segments.map((s) => {
@@ -370,13 +556,22 @@ function deepMergeHoconValues(base, overlay) {
370
556
  }
371
557
  return { kind: "object", fields: merged };
372
558
  }
373
- function deepMergeResObjInto(dst, src) {
559
+ function deepMergeResObjInto(dst, src, pathPrefix = []) {
374
560
  for (const [k, srcVal] of src.fields) {
561
+ const childPrefix = [...pathPrefix, k];
562
+ const fullKey = stringSegmentsToKey(childPrefix);
375
563
  const dstVal = dst.fields.get(k);
376
564
  if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
377
- deepMergeResObjInto(dstVal, srcVal);
565
+ const priorExisting = dst.priorValues.get(k);
566
+ const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
567
+ if (prior !== void 0) dst.priorValues.set(k, prior);
568
+ deepMergeResObjInto(dstVal, srcVal, childPrefix);
378
569
  } else {
379
- if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
570
+ if (dstVal !== void 0) {
571
+ const priorExisting = dst.priorValues.get(k);
572
+ const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
573
+ if (prior !== void 0) dst.priorValues.set(k, prior);
574
+ }
380
575
  dst.fields.set(k, srcVal);
381
576
  }
382
577
  }
@@ -940,12 +1135,17 @@ var Parser = class {
940
1135
  const segments = [];
941
1136
  let trailingDot = false;
942
1137
  let firstWasQuoted = false;
1138
+ let spaceConcat = false;
943
1139
  while (true) {
944
1140
  const t = this.peek();
945
1141
  if (t.kind === "string") {
946
1142
  this.advance();
947
1143
  if (segments.length === 0) firstWasQuoted = true;
948
- segments.push(t.value);
1144
+ if (spaceConcat) {
1145
+ segments[segments.length - 1] = `${segments[segments.length - 1]} ${t.value}`;
1146
+ } else {
1147
+ segments.push(t.value);
1148
+ }
949
1149
  trailingDot = false;
950
1150
  } else if (t.kind === "unquoted") {
951
1151
  this.advance();
@@ -962,12 +1162,23 @@ var Parser = class {
962
1162
  );
963
1163
  }
964
1164
  }
965
- segments.push(...filtered);
1165
+ if (spaceConcat && filtered.length > 0) {
1166
+ if (raw.startsWith(".")) {
1167
+ segments.push(...filtered);
1168
+ } else {
1169
+ const [head, ...tail] = filtered;
1170
+ segments[segments.length - 1] = `${segments[segments.length - 1]} ${head}`;
1171
+ segments.push(...tail);
1172
+ }
1173
+ } else {
1174
+ segments.push(...filtered);
1175
+ }
966
1176
  trailingDot = raw.endsWith(".");
967
1177
  } else {
968
1178
  if (segments.length === 0) throw new ParseError(`expected key, got ${t.kind}`, t.line, t.col);
969
1179
  break;
970
1180
  }
1181
+ spaceConcat = false;
971
1182
  if (trailingDot) continue;
972
1183
  const next = this.peek();
973
1184
  if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
@@ -979,6 +1190,10 @@ var Parser = class {
979
1190
  trailingDot = true;
980
1191
  continue;
981
1192
  }
1193
+ if ((next.kind === "unquoted" || next.kind === "string") && next.precedingSpace) {
1194
+ spaceConcat = true;
1195
+ continue;
1196
+ }
982
1197
  break;
983
1198
  }
984
1199
  return { segments, firstWasQuoted };
@@ -1367,7 +1582,7 @@ var IncludeLoader = class {
1367
1582
  for (const ext of probeExts) {
1368
1583
  const obj = this.loadSingle(`${absPath}${ext}`);
1369
1584
  if (obj !== void 0) {
1370
- deepMergeResObjInto(merged, obj);
1585
+ deepMergeResObjInto(merged, obj, []);
1371
1586
  foundAny = true;
1372
1587
  }
1373
1588
  }
@@ -1400,7 +1615,7 @@ var IncludeLoader = class {
1400
1615
  for (const ext of probeExts) {
1401
1616
  const obj = await this.loadSingleAsync(`${absPath}${ext}`);
1402
1617
  if (obj !== void 0) {
1403
- deepMergeResObjInto(merged, obj);
1618
+ deepMergeResObjInto(merged, obj, []);
1404
1619
  foundAny = true;
1405
1620
  }
1406
1621
  }
@@ -1572,7 +1787,7 @@ var StructureBuilder = class _StructureBuilder {
1572
1787
  if (pathPrefix.length > 0) {
1573
1788
  this.relativizeResObj(included, pathPrefix);
1574
1789
  }
1575
- deepMergeResObjInto(obj, included);
1790
+ deepMergeResObjInto(obj, included, pathPrefix);
1576
1791
  return;
1577
1792
  }
1578
1793
  const [head, ...tail] = field.key;
@@ -1587,12 +1802,27 @@ var StructureBuilder = class _StructureBuilder {
1587
1802
  return;
1588
1803
  }
1589
1804
  const childPrefix = [...pathPrefix, head];
1805
+ const fullKey = stringSegmentsToKey(childPrefix);
1590
1806
  if (field.append) {
1591
1807
  const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1592
- obj.priorValues.set(head, existing2);
1808
+ const oldPrior = obj.priorValues.get(head);
1809
+ const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
1810
+ if (folded !== void 0) obj.priorValues.set(head, folded);
1593
1811
  obj.fields.set(head, {
1594
1812
  _kind: "append-placeholder",
1595
- existing: existing2,
1813
+ // Use the folded existing (self-ref-free) for the Append. When the
1814
+ // fold returned undefined (self-ref present but no old prior),
1815
+ // fall back to the original existing — resolveSubst will then
1816
+ // raise the "self-ref with no prior value" error at resolve time,
1817
+ // matching the pre-fix behaviour for that edge case.
1818
+ //
1819
+ // Clone `folded` again so the Append.existing and priorValues[head]
1820
+ // do not share the same Subst/Concat references — relativizeResObj
1821
+ // walks BOTH `fields` and `priorValues` and mutates Subst.segments
1822
+ // in place, so a shared reference would get its prefix applied
1823
+ // twice (resulting in e.g. `${outer.outer.base}` instead of
1824
+ // `${outer.base}`). Codex review on PR #131.
1825
+ existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
1596
1826
  elem: this.astToResolverValue(field.value, childPrefix)
1597
1827
  });
1598
1828
  return;
@@ -1600,10 +1830,13 @@ var StructureBuilder = class _StructureBuilder {
1600
1830
  const existing = obj.fields.get(head);
1601
1831
  const newVal = this.astToResolverValue(field.value, childPrefix);
1602
1832
  if (existing !== void 0) {
1603
- obj.priorValues.set(head, existing);
1833
+ const exFolded = foldNestedSelfRefs(existing, childPrefix);
1834
+ const oldPrior = obj.priorValues.get(head);
1835
+ const prior = foldOrSkipPrior(exFolded, fullKey, oldPrior);
1836
+ if (prior !== void 0) obj.priorValues.set(head, prior);
1604
1837
  }
1605
1838
  if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
1606
- deepMergeResObjInto(existing, newVal);
1839
+ deepMergeResObjInto(existing, newVal, childPrefix);
1607
1840
  return;
1608
1841
  }
1609
1842
  obj.fields.set(head, newVal);
@@ -1620,7 +1853,7 @@ var StructureBuilder = class _StructureBuilder {
1620
1853
  if (pathPrefix.length > 0) {
1621
1854
  this.relativizeResObj(included, pathPrefix);
1622
1855
  }
1623
- deepMergeResObjInto(obj, included);
1856
+ deepMergeResObjInto(obj, included, pathPrefix);
1624
1857
  return;
1625
1858
  }
1626
1859
  const [head, ...tail] = field.key;
@@ -1635,12 +1868,15 @@ var StructureBuilder = class _StructureBuilder {
1635
1868
  return;
1636
1869
  }
1637
1870
  const childPrefix = [...pathPrefix, head];
1871
+ const fullKey = stringSegmentsToKey(childPrefix);
1638
1872
  if (field.append) {
1639
1873
  const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1640
- obj.priorValues.set(head, existing2);
1874
+ const oldPrior = obj.priorValues.get(head);
1875
+ const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
1876
+ if (folded !== void 0) obj.priorValues.set(head, folded);
1641
1877
  obj.fields.set(head, {
1642
1878
  _kind: "append-placeholder",
1643
- existing: existing2,
1879
+ existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
1644
1880
  elem: await this.astToResolverValueAsync(field.value, childPrefix)
1645
1881
  });
1646
1882
  return;
@@ -1648,10 +1884,13 @@ var StructureBuilder = class _StructureBuilder {
1648
1884
  const existing = obj.fields.get(head);
1649
1885
  const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
1650
1886
  if (existing !== void 0) {
1651
- obj.priorValues.set(head, existing);
1887
+ const exFolded = foldNestedSelfRefs(existing, childPrefix);
1888
+ const oldPrior = obj.priorValues.get(head);
1889
+ const prior = foldOrSkipPrior(exFolded, fullKey, oldPrior);
1890
+ if (prior !== void 0) obj.priorValues.set(head, prior);
1652
1891
  }
1653
1892
  if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
1654
- deepMergeResObjInto(existing, newVal);
1893
+ deepMergeResObjInto(existing, newVal, childPrefix);
1655
1894
  return;
1656
1895
  }
1657
1896
  obj.fields.set(head, newVal);
@@ -1783,6 +2022,23 @@ var SubstitutionResolver = class {
1783
2022
  // narrower than path-equality. Spec amendment deferred to a follow-up
1784
2023
  // xx.hocon PR (see Phase 6 #3f close-out notes).
1785
2024
  this.resolvingConcats = /* @__PURE__ */ new WeakSet();
2025
+ // Path stack tracking the full dotted path of the field currently being
2026
+ // assigned in `resolveResObj`. Leaf keys are pushed on entry to each
2027
+ // `(key, val)` iteration and popped on exit, so nested objects build up
2028
+ // the full path (e.g. `["o", "history"]` while resolving `o.history`).
2029
+ //
2030
+ // Used to tighten self-ref detection at the L192 found-lookup site:
2031
+ // a substitution `${X}` whose looked-up value contains a Subst pointing
2032
+ // at `X` is only a true self-reference when the field currently being
2033
+ // assigned is `X` (or a descendant of `X`). Without this guard, an
2034
+ // external lookup like `b = ${o}` (where o has a self-referential
2035
+ // field) would mis-fire and short-circuit to o's prior, returning the
2036
+ // wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
2037
+ // narrows the check to the owner case only.
2038
+ //
2039
+ // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
2040
+ // same prefix-match semantics).
2041
+ this.resolvingFieldPath = [];
1786
2042
  }
1787
2043
  /** Returns a prefix for error messages, including origin info when set. */
1788
2044
  originPrefix() {
@@ -1794,7 +2050,13 @@ var SubstitutionResolver = class {
1794
2050
  resolveResObj(obj) {
1795
2051
  const result = /* @__PURE__ */ new Map();
1796
2052
  for (const [key, val] of obj.fields) {
1797
- const resolved = this.resolveVal(val, obj);
2053
+ this.resolvingFieldPath.push(key);
2054
+ let resolved;
2055
+ try {
2056
+ resolved = this.resolveVal(val, obj);
2057
+ } finally {
2058
+ this.resolvingFieldPath.pop();
2059
+ }
1798
2060
  if (resolved !== void 0) {
1799
2061
  if (resolved.kind === "object") {
1800
2062
  const prior = obj.priorValues.get(key);
@@ -1884,7 +2146,10 @@ var SubstitutionResolver = class {
1884
2146
  try {
1885
2147
  const found = lookupPath(this.root, s.segments);
1886
2148
  if (found !== void 0) {
1887
- if (isConcat(found) && this.resolvingConcats.has(found)) {
2149
+ const rfp = this.resolvingFieldPath;
2150
+ const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
2151
+ const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
2152
+ if (isSelfRef) {
1888
2153
  let prior;
1889
2154
  if (s.segments.length > 1) {
1890
2155
  const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
@@ -2095,8 +2360,33 @@ var SubstitutionResolver = class {
2095
2360
  }
2096
2361
  resolveAppend(a, scope) {
2097
2362
  const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
2363
+ if (this.opts.allowUnresolved && (isSubst(existing) || isConcat(existing) || isAppend(existing))) {
2364
+ return a;
2365
+ }
2098
2366
  const elem = this.resolveVal(a.elem, scope);
2099
- const items = existing.kind === "array" ? [...existing.items] : [existing];
2367
+ let priorItems;
2368
+ if (existing.kind === "array") {
2369
+ priorItems = existing.items;
2370
+ } else if (existing.kind === "object") {
2371
+ const converted = numericObjectToArray(existing);
2372
+ if (converted === null) {
2373
+ throw new ResolveError(
2374
+ `'+=' on non-array value: prior value is object with non-numeric keys (spec L732)`,
2375
+ "",
2376
+ 0,
2377
+ 0
2378
+ );
2379
+ }
2380
+ priorItems = converted;
2381
+ } else {
2382
+ throw new ResolveError(
2383
+ `'+=' on non-array value: prior value is ${existing.kind} (spec L732)`,
2384
+ "",
2385
+ 0,
2386
+ 0
2387
+ );
2388
+ }
2389
+ const items = [...priorItems];
2100
2390
  if (elem !== void 0) items.push(elem);
2101
2391
  return { kind: "array", items };
2102
2392
  }
@@ -2194,6 +2484,9 @@ var Config = class _Config {
2194
2484
  }
2195
2485
  getString(path2) {
2196
2486
  const v = this.requireScalar(path2);
2487
+ if (v.valueType === "null") {
2488
+ throw new ConfigError(`expected string at ${path2}, got ${v.valueType}`, path2);
2489
+ }
2197
2490
  return v.raw;
2198
2491
  }
2199
2492
  getNumber(path2) {