@o3co/ts.hocon 1.5.0 → 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
  }
@@ -1387,7 +1582,7 @@ var IncludeLoader = class {
1387
1582
  for (const ext of probeExts) {
1388
1583
  const obj = this.loadSingle(`${absPath}${ext}`);
1389
1584
  if (obj !== void 0) {
1390
- deepMergeResObjInto(merged, obj);
1585
+ deepMergeResObjInto(merged, obj, []);
1391
1586
  foundAny = true;
1392
1587
  }
1393
1588
  }
@@ -1420,7 +1615,7 @@ var IncludeLoader = class {
1420
1615
  for (const ext of probeExts) {
1421
1616
  const obj = await this.loadSingleAsync(`${absPath}${ext}`);
1422
1617
  if (obj !== void 0) {
1423
- deepMergeResObjInto(merged, obj);
1618
+ deepMergeResObjInto(merged, obj, []);
1424
1619
  foundAny = true;
1425
1620
  }
1426
1621
  }
@@ -1592,7 +1787,7 @@ var StructureBuilder = class _StructureBuilder {
1592
1787
  if (pathPrefix.length > 0) {
1593
1788
  this.relativizeResObj(included, pathPrefix);
1594
1789
  }
1595
- deepMergeResObjInto(obj, included);
1790
+ deepMergeResObjInto(obj, included, pathPrefix);
1596
1791
  return;
1597
1792
  }
1598
1793
  const [head, ...tail] = field.key;
@@ -1607,12 +1802,27 @@ var StructureBuilder = class _StructureBuilder {
1607
1802
  return;
1608
1803
  }
1609
1804
  const childPrefix = [...pathPrefix, head];
1805
+ const fullKey = stringSegmentsToKey(childPrefix);
1610
1806
  if (field.append) {
1611
1807
  const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1612
- 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);
1613
1811
  obj.fields.set(head, {
1614
1812
  _kind: "append-placeholder",
1615
- 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,
1616
1826
  elem: this.astToResolverValue(field.value, childPrefix)
1617
1827
  });
1618
1828
  return;
@@ -1620,10 +1830,13 @@ var StructureBuilder = class _StructureBuilder {
1620
1830
  const existing = obj.fields.get(head);
1621
1831
  const newVal = this.astToResolverValue(field.value, childPrefix);
1622
1832
  if (existing !== void 0) {
1623
- 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);
1624
1837
  }
1625
1838
  if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
1626
- deepMergeResObjInto(existing, newVal);
1839
+ deepMergeResObjInto(existing, newVal, childPrefix);
1627
1840
  return;
1628
1841
  }
1629
1842
  obj.fields.set(head, newVal);
@@ -1640,7 +1853,7 @@ var StructureBuilder = class _StructureBuilder {
1640
1853
  if (pathPrefix.length > 0) {
1641
1854
  this.relativizeResObj(included, pathPrefix);
1642
1855
  }
1643
- deepMergeResObjInto(obj, included);
1856
+ deepMergeResObjInto(obj, included, pathPrefix);
1644
1857
  return;
1645
1858
  }
1646
1859
  const [head, ...tail] = field.key;
@@ -1655,12 +1868,15 @@ var StructureBuilder = class _StructureBuilder {
1655
1868
  return;
1656
1869
  }
1657
1870
  const childPrefix = [...pathPrefix, head];
1871
+ const fullKey = stringSegmentsToKey(childPrefix);
1658
1872
  if (field.append) {
1659
1873
  const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1660
- 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);
1661
1877
  obj.fields.set(head, {
1662
1878
  _kind: "append-placeholder",
1663
- existing: existing2,
1879
+ existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
1664
1880
  elem: await this.astToResolverValueAsync(field.value, childPrefix)
1665
1881
  });
1666
1882
  return;
@@ -1668,10 +1884,13 @@ var StructureBuilder = class _StructureBuilder {
1668
1884
  const existing = obj.fields.get(head);
1669
1885
  const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
1670
1886
  if (existing !== void 0) {
1671
- 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);
1672
1891
  }
1673
1892
  if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
1674
- deepMergeResObjInto(existing, newVal);
1893
+ deepMergeResObjInto(existing, newVal, childPrefix);
1675
1894
  return;
1676
1895
  }
1677
1896
  obj.fields.set(head, newVal);
@@ -1803,6 +2022,23 @@ var SubstitutionResolver = class {
1803
2022
  // narrower than path-equality. Spec amendment deferred to a follow-up
1804
2023
  // xx.hocon PR (see Phase 6 #3f close-out notes).
1805
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 = [];
1806
2042
  }
1807
2043
  /** Returns a prefix for error messages, including origin info when set. */
1808
2044
  originPrefix() {
@@ -1814,7 +2050,13 @@ var SubstitutionResolver = class {
1814
2050
  resolveResObj(obj) {
1815
2051
  const result = /* @__PURE__ */ new Map();
1816
2052
  for (const [key, val] of obj.fields) {
1817
- 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
+ }
1818
2060
  if (resolved !== void 0) {
1819
2061
  if (resolved.kind === "object") {
1820
2062
  const prior = obj.priorValues.get(key);
@@ -1904,7 +2146,10 @@ var SubstitutionResolver = class {
1904
2146
  try {
1905
2147
  const found = lookupPath(this.root, s.segments);
1906
2148
  if (found !== void 0) {
1907
- 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) {
1908
2153
  let prior;
1909
2154
  if (s.segments.length > 1) {
1910
2155
  const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";