@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.js CHANGED
@@ -290,6 +290,192 @@ function mergeUnresolved(receiver, fallback) {
290
290
  return result;
291
291
  }
292
292
 
293
+ // src/internal/resolver/fold-self-ref.ts
294
+ function substFullKey(s) {
295
+ return stringSegmentsToKey(s.segments.map((seg) => seg.text));
296
+ }
297
+ function stringSegmentsToKey(segments) {
298
+ return segments.map((t) => {
299
+ if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
300
+ return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
301
+ }
302
+ return t;
303
+ }).join(".");
304
+ }
305
+ function containsSelfRef(v, fullKey) {
306
+ if (isSubst(v)) return substFullKey(v) === fullKey;
307
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
308
+ if (isAppend(v)) {
309
+ return containsSelfRef(v.existing, fullKey) || containsSelfRef(v.elem, fullKey);
310
+ }
311
+ if (isResObj(v)) {
312
+ for (const f of v.fields.values()) {
313
+ if (containsSelfRef(f, fullKey)) return true;
314
+ }
315
+ return false;
316
+ }
317
+ const hv = v;
318
+ if (hv.kind === "array") {
319
+ return hv.items.some((item) => containsSelfRef(item, fullKey));
320
+ }
321
+ if (hv.kind === "object") {
322
+ for (const f of hv.fields.values()) {
323
+ if (containsSelfRef(f, fullKey)) return true;
324
+ }
325
+ return false;
326
+ }
327
+ return false;
328
+ }
329
+ function foldSelfRef(v, fullKey, replacement) {
330
+ if (isSubst(v)) {
331
+ return substFullKey(v) === fullKey ? replacement : v;
332
+ }
333
+ if (isConcat(v)) {
334
+ return {
335
+ _kind: "concat-placeholder",
336
+ nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
337
+ line: v.line,
338
+ col: v.col
339
+ };
340
+ }
341
+ if (isAppend(v)) {
342
+ return {
343
+ _kind: "append-placeholder",
344
+ existing: foldSelfRef(v.existing, fullKey, replacement),
345
+ elem: foldSelfRef(v.elem, fullKey, replacement)
346
+ };
347
+ }
348
+ if (isResObj(v)) {
349
+ const newFields = /* @__PURE__ */ new Map();
350
+ for (const [k, val] of v.fields) {
351
+ newFields.set(k, foldSelfRef(val, fullKey, replacement));
352
+ }
353
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
354
+ }
355
+ const hv = v;
356
+ if (hv.kind === "array") {
357
+ return {
358
+ kind: "array",
359
+ items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
360
+ };
361
+ }
362
+ if (hv.kind === "object") {
363
+ const newFields = /* @__PURE__ */ new Map();
364
+ for (const [k, val] of hv.fields) {
365
+ newFields.set(k, foldSelfRef(val, fullKey, replacement));
366
+ }
367
+ return { kind: "object", fields: newFields };
368
+ }
369
+ return v;
370
+ }
371
+ function foldOrSkipPrior(prior, fullKey, old) {
372
+ if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
373
+ if (old === void 0) return void 0;
374
+ return foldSelfRef(prior, fullKey, old);
375
+ }
376
+ function cloneResolverValue(v) {
377
+ if (isSubst(v)) {
378
+ return {
379
+ _kind: "subst-placeholder",
380
+ segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
381
+ optional: v.optional,
382
+ listSuffix: v.listSuffix,
383
+ line: v.line,
384
+ col: v.col,
385
+ prefixLen: v.prefixLen
386
+ };
387
+ }
388
+ if (isConcat(v)) {
389
+ return {
390
+ _kind: "concat-placeholder",
391
+ nodes: v.nodes.map((n) => cloneResolverValue(n)),
392
+ line: v.line,
393
+ col: v.col
394
+ };
395
+ }
396
+ if (isResObj(v)) {
397
+ const newFields = /* @__PURE__ */ new Map();
398
+ for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
399
+ const newPriors = /* @__PURE__ */ new Map();
400
+ for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
401
+ return { _kind: "res-obj", fields: newFields, priorValues: newPriors };
402
+ }
403
+ if (isAppend(v)) {
404
+ return {
405
+ _kind: "append-placeholder",
406
+ existing: cloneResolverValue(v.existing),
407
+ elem: cloneResolverValue(v.elem)
408
+ };
409
+ }
410
+ const hv = v;
411
+ if (hv.kind === "array") {
412
+ return {
413
+ kind: "array",
414
+ items: hv.items.map((item) => cloneResolverValue(item))
415
+ };
416
+ }
417
+ if (hv.kind === "object") {
418
+ const newFields = /* @__PURE__ */ new Map();
419
+ for (const [k, val] of hv.fields) {
420
+ newFields.set(k, cloneResolverValue(val));
421
+ }
422
+ return { kind: "object", fields: newFields };
423
+ }
424
+ return hv;
425
+ }
426
+ function foldNestedSelfRefs(v, pathPrefix) {
427
+ if (!isResObj(v)) return v;
428
+ const newFields = /* @__PURE__ */ new Map();
429
+ for (const [k, fieldVal] of v.fields) {
430
+ const childPath = [...pathPrefix, k];
431
+ const fullKey = stringSegmentsToKey(childPath);
432
+ const folded = foldNestedSelfRefs(fieldVal, childPath);
433
+ let finalVal = folded;
434
+ if (containsSelfRef(folded, fullKey)) {
435
+ const leafPrior = v.priorValues.get(k);
436
+ if (leafPrior !== void 0) {
437
+ const leafPriorFolded = foldNestedSelfRefs(leafPrior, childPath);
438
+ finalVal = foldSelfRef(folded, fullKey, leafPriorFolded);
439
+ }
440
+ }
441
+ newFields.set(k, finalVal);
442
+ }
443
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
444
+ }
445
+ function containsSubstByPath(v, target) {
446
+ if (isSubst(v)) return segmentsTextEqual(v.segments, target);
447
+ if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
448
+ if (isAppend(v)) {
449
+ return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
450
+ }
451
+ if (isResObj(v)) {
452
+ for (const f of v.fields.values()) {
453
+ if (containsSubstByPath(f, target)) return true;
454
+ }
455
+ return false;
456
+ }
457
+ const hv = v;
458
+ if (hv.kind === "array") {
459
+ return hv.items.some((item) => containsSubstByPath(item, target));
460
+ }
461
+ if (hv.kind === "object") {
462
+ for (const f of hv.fields.values()) {
463
+ if (containsSubstByPath(f, target)) return true;
464
+ }
465
+ return false;
466
+ }
467
+ return false;
468
+ }
469
+ function segmentsTextEqual(a, b) {
470
+ if (a.length !== b.length) return false;
471
+ for (let i = 0; i < a.length; i++) {
472
+ const ai = a[i];
473
+ const bi = b[i];
474
+ if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
475
+ }
476
+ return true;
477
+ }
478
+
293
479
  // src/internal/resolver/utils.ts
294
480
  function segmentsToKey(segments) {
295
481
  return segments.map((s) => {
@@ -331,13 +517,22 @@ function deepMergeHoconValues(base, overlay) {
331
517
  }
332
518
  return { kind: "object", fields: merged };
333
519
  }
334
- function deepMergeResObjInto(dst, src) {
520
+ function deepMergeResObjInto(dst, src, pathPrefix = []) {
335
521
  for (const [k, srcVal] of src.fields) {
522
+ const childPrefix = [...pathPrefix, k];
523
+ const fullKey = stringSegmentsToKey(childPrefix);
336
524
  const dstVal = dst.fields.get(k);
337
525
  if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
338
- deepMergeResObjInto(dstVal, srcVal);
526
+ const priorExisting = dst.priorValues.get(k);
527
+ const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
528
+ if (prior !== void 0) dst.priorValues.set(k, prior);
529
+ deepMergeResObjInto(dstVal, srcVal, childPrefix);
339
530
  } else {
340
- if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
531
+ if (dstVal !== void 0) {
532
+ const priorExisting = dst.priorValues.get(k);
533
+ const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
534
+ if (prior !== void 0) dst.priorValues.set(k, prior);
535
+ }
341
536
  dst.fields.set(k, srcVal);
342
537
  }
343
538
  }
@@ -1347,7 +1542,7 @@ var IncludeLoader = class {
1347
1542
  for (const ext of probeExts) {
1348
1543
  const obj = this.loadSingle(`${absPath}${ext}`);
1349
1544
  if (obj !== void 0) {
1350
- deepMergeResObjInto(merged, obj);
1545
+ deepMergeResObjInto(merged, obj, []);
1351
1546
  foundAny = true;
1352
1547
  }
1353
1548
  }
@@ -1380,7 +1575,7 @@ var IncludeLoader = class {
1380
1575
  for (const ext of probeExts) {
1381
1576
  const obj = await this.loadSingleAsync(`${absPath}${ext}`);
1382
1577
  if (obj !== void 0) {
1383
- deepMergeResObjInto(merged, obj);
1578
+ deepMergeResObjInto(merged, obj, []);
1384
1579
  foundAny = true;
1385
1580
  }
1386
1581
  }
@@ -1552,7 +1747,7 @@ var StructureBuilder = class _StructureBuilder {
1552
1747
  if (pathPrefix.length > 0) {
1553
1748
  this.relativizeResObj(included, pathPrefix);
1554
1749
  }
1555
- deepMergeResObjInto(obj, included);
1750
+ deepMergeResObjInto(obj, included, pathPrefix);
1556
1751
  return;
1557
1752
  }
1558
1753
  const [head, ...tail] = field.key;
@@ -1567,12 +1762,27 @@ var StructureBuilder = class _StructureBuilder {
1567
1762
  return;
1568
1763
  }
1569
1764
  const childPrefix = [...pathPrefix, head];
1765
+ const fullKey = stringSegmentsToKey(childPrefix);
1570
1766
  if (field.append) {
1571
1767
  const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1572
- obj.priorValues.set(head, existing2);
1768
+ const oldPrior = obj.priorValues.get(head);
1769
+ const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
1770
+ if (folded !== void 0) obj.priorValues.set(head, folded);
1573
1771
  obj.fields.set(head, {
1574
1772
  _kind: "append-placeholder",
1575
- existing: existing2,
1773
+ // Use the folded existing (self-ref-free) for the Append. When the
1774
+ // fold returned undefined (self-ref present but no old prior),
1775
+ // fall back to the original existing — resolveSubst will then
1776
+ // raise the "self-ref with no prior value" error at resolve time,
1777
+ // matching the pre-fix behaviour for that edge case.
1778
+ //
1779
+ // Clone `folded` again so the Append.existing and priorValues[head]
1780
+ // do not share the same Subst/Concat references — relativizeResObj
1781
+ // walks BOTH `fields` and `priorValues` and mutates Subst.segments
1782
+ // in place, so a shared reference would get its prefix applied
1783
+ // twice (resulting in e.g. `${outer.outer.base}` instead of
1784
+ // `${outer.base}`). Codex review on PR #131.
1785
+ existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
1576
1786
  elem: this.astToResolverValue(field.value, childPrefix)
1577
1787
  });
1578
1788
  return;
@@ -1580,10 +1790,13 @@ var StructureBuilder = class _StructureBuilder {
1580
1790
  const existing = obj.fields.get(head);
1581
1791
  const newVal = this.astToResolverValue(field.value, childPrefix);
1582
1792
  if (existing !== void 0) {
1583
- obj.priorValues.set(head, existing);
1793
+ const exFolded = foldNestedSelfRefs(existing, childPrefix);
1794
+ const oldPrior = obj.priorValues.get(head);
1795
+ const prior = foldOrSkipPrior(exFolded, fullKey, oldPrior);
1796
+ if (prior !== void 0) obj.priorValues.set(head, prior);
1584
1797
  }
1585
1798
  if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
1586
- deepMergeResObjInto(existing, newVal);
1799
+ deepMergeResObjInto(existing, newVal, childPrefix);
1587
1800
  return;
1588
1801
  }
1589
1802
  obj.fields.set(head, newVal);
@@ -1600,7 +1813,7 @@ var StructureBuilder = class _StructureBuilder {
1600
1813
  if (pathPrefix.length > 0) {
1601
1814
  this.relativizeResObj(included, pathPrefix);
1602
1815
  }
1603
- deepMergeResObjInto(obj, included);
1816
+ deepMergeResObjInto(obj, included, pathPrefix);
1604
1817
  return;
1605
1818
  }
1606
1819
  const [head, ...tail] = field.key;
@@ -1615,12 +1828,15 @@ var StructureBuilder = class _StructureBuilder {
1615
1828
  return;
1616
1829
  }
1617
1830
  const childPrefix = [...pathPrefix, head];
1831
+ const fullKey = stringSegmentsToKey(childPrefix);
1618
1832
  if (field.append) {
1619
1833
  const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1620
- obj.priorValues.set(head, existing2);
1834
+ const oldPrior = obj.priorValues.get(head);
1835
+ const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
1836
+ if (folded !== void 0) obj.priorValues.set(head, folded);
1621
1837
  obj.fields.set(head, {
1622
1838
  _kind: "append-placeholder",
1623
- existing: existing2,
1839
+ existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
1624
1840
  elem: await this.astToResolverValueAsync(field.value, childPrefix)
1625
1841
  });
1626
1842
  return;
@@ -1628,10 +1844,13 @@ var StructureBuilder = class _StructureBuilder {
1628
1844
  const existing = obj.fields.get(head);
1629
1845
  const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
1630
1846
  if (existing !== void 0) {
1631
- obj.priorValues.set(head, existing);
1847
+ const exFolded = foldNestedSelfRefs(existing, childPrefix);
1848
+ const oldPrior = obj.priorValues.get(head);
1849
+ const prior = foldOrSkipPrior(exFolded, fullKey, oldPrior);
1850
+ if (prior !== void 0) obj.priorValues.set(head, prior);
1632
1851
  }
1633
1852
  if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
1634
- deepMergeResObjInto(existing, newVal);
1853
+ deepMergeResObjInto(existing, newVal, childPrefix);
1635
1854
  return;
1636
1855
  }
1637
1856
  obj.fields.set(head, newVal);
@@ -1763,6 +1982,23 @@ var SubstitutionResolver = class {
1763
1982
  // narrower than path-equality. Spec amendment deferred to a follow-up
1764
1983
  // xx.hocon PR (see Phase 6 #3f close-out notes).
1765
1984
  this.resolvingConcats = /* @__PURE__ */ new WeakSet();
1985
+ // Path stack tracking the full dotted path of the field currently being
1986
+ // assigned in `resolveResObj`. Leaf keys are pushed on entry to each
1987
+ // `(key, val)` iteration and popped on exit, so nested objects build up
1988
+ // the full path (e.g. `["o", "history"]` while resolving `o.history`).
1989
+ //
1990
+ // Used to tighten self-ref detection at the L192 found-lookup site:
1991
+ // a substitution `${X}` whose looked-up value contains a Subst pointing
1992
+ // at `X` is only a true self-reference when the field currently being
1993
+ // assigned is `X` (or a descendant of `X`). Without this guard, an
1994
+ // external lookup like `b = ${o}` (where o has a self-referential
1995
+ // field) would mis-fire and short-circuit to o's prior, returning the
1996
+ // wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
1997
+ // narrows the check to the owner case only.
1998
+ //
1999
+ // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
2000
+ // same prefix-match semantics).
2001
+ this.resolvingFieldPath = [];
1766
2002
  }
1767
2003
  /** Returns a prefix for error messages, including origin info when set. */
1768
2004
  originPrefix() {
@@ -1774,7 +2010,13 @@ var SubstitutionResolver = class {
1774
2010
  resolveResObj(obj) {
1775
2011
  const result = /* @__PURE__ */ new Map();
1776
2012
  for (const [key, val] of obj.fields) {
1777
- const resolved = this.resolveVal(val, obj);
2013
+ this.resolvingFieldPath.push(key);
2014
+ let resolved;
2015
+ try {
2016
+ resolved = this.resolveVal(val, obj);
2017
+ } finally {
2018
+ this.resolvingFieldPath.pop();
2019
+ }
1778
2020
  if (resolved !== void 0) {
1779
2021
  if (resolved.kind === "object") {
1780
2022
  const prior = obj.priorValues.get(key);
@@ -1864,7 +2106,10 @@ var SubstitutionResolver = class {
1864
2106
  try {
1865
2107
  const found = lookupPath(this.root, s.segments);
1866
2108
  if (found !== void 0) {
1867
- if (isConcat(found) && this.resolvingConcats.has(found)) {
2109
+ const rfp = this.resolvingFieldPath;
2110
+ const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
2111
+ const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
2112
+ if (isSelfRef) {
1868
2113
  let prior;
1869
2114
  if (s.segments.length > 1) {
1870
2115
  const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o3co/ts.hocon",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Full Lightbend HOCON spec-compliant parser and config library for TypeScript",
5
5
  "keywords": [
6
6
  "hocon",