@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/index.cjs +313 -20
- package/dist/index.js +313 -20
- package/dist/zod.cjs +313 -20
- package/dist/zod.js +313 -20
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -338,6 +338,192 @@ function mergeUnresolved(receiver, fallback) {
|
|
|
338
338
|
return result;
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
// src/internal/resolver/fold-self-ref.ts
|
|
342
|
+
function substFullKey(s) {
|
|
343
|
+
return stringSegmentsToKey(s.segments.map((seg) => seg.text));
|
|
344
|
+
}
|
|
345
|
+
function stringSegmentsToKey(segments) {
|
|
346
|
+
return segments.map((t) => {
|
|
347
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
348
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
349
|
+
}
|
|
350
|
+
return t;
|
|
351
|
+
}).join(".");
|
|
352
|
+
}
|
|
353
|
+
function containsSelfRef(v, fullKey) {
|
|
354
|
+
if (isSubst(v)) return substFullKey(v) === fullKey;
|
|
355
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
|
|
356
|
+
if (isAppend(v)) {
|
|
357
|
+
return containsSelfRef(v.existing, fullKey) || containsSelfRef(v.elem, fullKey);
|
|
358
|
+
}
|
|
359
|
+
if (isResObj(v)) {
|
|
360
|
+
for (const f of v.fields.values()) {
|
|
361
|
+
if (containsSelfRef(f, fullKey)) return true;
|
|
362
|
+
}
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
const hv = v;
|
|
366
|
+
if (hv.kind === "array") {
|
|
367
|
+
return hv.items.some((item) => containsSelfRef(item, fullKey));
|
|
368
|
+
}
|
|
369
|
+
if (hv.kind === "object") {
|
|
370
|
+
for (const f of hv.fields.values()) {
|
|
371
|
+
if (containsSelfRef(f, fullKey)) return true;
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
function foldSelfRef(v, fullKey, replacement) {
|
|
378
|
+
if (isSubst(v)) {
|
|
379
|
+
return substFullKey(v) === fullKey ? replacement : v;
|
|
380
|
+
}
|
|
381
|
+
if (isConcat(v)) {
|
|
382
|
+
return {
|
|
383
|
+
_kind: "concat-placeholder",
|
|
384
|
+
nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
|
|
385
|
+
line: v.line,
|
|
386
|
+
col: v.col
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (isAppend(v)) {
|
|
390
|
+
return {
|
|
391
|
+
_kind: "append-placeholder",
|
|
392
|
+
existing: foldSelfRef(v.existing, fullKey, replacement),
|
|
393
|
+
elem: foldSelfRef(v.elem, fullKey, replacement)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if (isResObj(v)) {
|
|
397
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
398
|
+
for (const [k, val] of v.fields) {
|
|
399
|
+
newFields.set(k, foldSelfRef(val, fullKey, replacement));
|
|
400
|
+
}
|
|
401
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
|
|
402
|
+
}
|
|
403
|
+
const hv = v;
|
|
404
|
+
if (hv.kind === "array") {
|
|
405
|
+
return {
|
|
406
|
+
kind: "array",
|
|
407
|
+
items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
if (hv.kind === "object") {
|
|
411
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
412
|
+
for (const [k, val] of hv.fields) {
|
|
413
|
+
newFields.set(k, foldSelfRef(val, fullKey, replacement));
|
|
414
|
+
}
|
|
415
|
+
return { kind: "object", fields: newFields };
|
|
416
|
+
}
|
|
417
|
+
return v;
|
|
418
|
+
}
|
|
419
|
+
function foldOrSkipPrior(prior, fullKey, old) {
|
|
420
|
+
if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
|
|
421
|
+
if (old === void 0) return void 0;
|
|
422
|
+
return foldSelfRef(prior, fullKey, old);
|
|
423
|
+
}
|
|
424
|
+
function cloneResolverValue(v) {
|
|
425
|
+
if (isSubst(v)) {
|
|
426
|
+
return {
|
|
427
|
+
_kind: "subst-placeholder",
|
|
428
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
429
|
+
optional: v.optional,
|
|
430
|
+
listSuffix: v.listSuffix,
|
|
431
|
+
line: v.line,
|
|
432
|
+
col: v.col,
|
|
433
|
+
prefixLen: v.prefixLen
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
if (isConcat(v)) {
|
|
437
|
+
return {
|
|
438
|
+
_kind: "concat-placeholder",
|
|
439
|
+
nodes: v.nodes.map((n) => cloneResolverValue(n)),
|
|
440
|
+
line: v.line,
|
|
441
|
+
col: v.col
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (isResObj(v)) {
|
|
445
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
446
|
+
for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
|
|
447
|
+
const newPriors = /* @__PURE__ */ new Map();
|
|
448
|
+
for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
|
|
449
|
+
return { _kind: "res-obj", fields: newFields, priorValues: newPriors };
|
|
450
|
+
}
|
|
451
|
+
if (isAppend(v)) {
|
|
452
|
+
return {
|
|
453
|
+
_kind: "append-placeholder",
|
|
454
|
+
existing: cloneResolverValue(v.existing),
|
|
455
|
+
elem: cloneResolverValue(v.elem)
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const hv = v;
|
|
459
|
+
if (hv.kind === "array") {
|
|
460
|
+
return {
|
|
461
|
+
kind: "array",
|
|
462
|
+
items: hv.items.map((item) => cloneResolverValue(item))
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
if (hv.kind === "object") {
|
|
466
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
467
|
+
for (const [k, val] of hv.fields) {
|
|
468
|
+
newFields.set(k, cloneResolverValue(val));
|
|
469
|
+
}
|
|
470
|
+
return { kind: "object", fields: newFields };
|
|
471
|
+
}
|
|
472
|
+
return hv;
|
|
473
|
+
}
|
|
474
|
+
function foldNestedSelfRefs(v, pathPrefix) {
|
|
475
|
+
if (!isResObj(v)) return v;
|
|
476
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
477
|
+
for (const [k, fieldVal] of v.fields) {
|
|
478
|
+
const childPath = [...pathPrefix, k];
|
|
479
|
+
const fullKey = stringSegmentsToKey(childPath);
|
|
480
|
+
const folded = foldNestedSelfRefs(fieldVal, childPath);
|
|
481
|
+
let finalVal = folded;
|
|
482
|
+
if (containsSelfRef(folded, fullKey)) {
|
|
483
|
+
const leafPrior = v.priorValues.get(k);
|
|
484
|
+
if (leafPrior !== void 0) {
|
|
485
|
+
const leafPriorFolded = foldNestedSelfRefs(leafPrior, childPath);
|
|
486
|
+
finalVal = foldSelfRef(folded, fullKey, leafPriorFolded);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
newFields.set(k, finalVal);
|
|
490
|
+
}
|
|
491
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
|
|
492
|
+
}
|
|
493
|
+
function containsSubstByPath(v, target) {
|
|
494
|
+
if (isSubst(v)) return segmentsTextEqual(v.segments, target);
|
|
495
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
|
|
496
|
+
if (isAppend(v)) {
|
|
497
|
+
return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
|
|
498
|
+
}
|
|
499
|
+
if (isResObj(v)) {
|
|
500
|
+
for (const f of v.fields.values()) {
|
|
501
|
+
if (containsSubstByPath(f, target)) return true;
|
|
502
|
+
}
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
const hv = v;
|
|
506
|
+
if (hv.kind === "array") {
|
|
507
|
+
return hv.items.some((item) => containsSubstByPath(item, target));
|
|
508
|
+
}
|
|
509
|
+
if (hv.kind === "object") {
|
|
510
|
+
for (const f of hv.fields.values()) {
|
|
511
|
+
if (containsSubstByPath(f, target)) return true;
|
|
512
|
+
}
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
function segmentsTextEqual(a, b) {
|
|
518
|
+
if (a.length !== b.length) return false;
|
|
519
|
+
for (let i = 0; i < a.length; i++) {
|
|
520
|
+
const ai = a[i];
|
|
521
|
+
const bi = b[i];
|
|
522
|
+
if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
|
|
523
|
+
}
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
|
|
341
527
|
// src/internal/resolver/utils.ts
|
|
342
528
|
function segmentsToKey(segments) {
|
|
343
529
|
return segments.map((s) => {
|
|
@@ -379,13 +565,22 @@ function deepMergeHoconValues(base, overlay) {
|
|
|
379
565
|
}
|
|
380
566
|
return { kind: "object", fields: merged };
|
|
381
567
|
}
|
|
382
|
-
function deepMergeResObjInto(dst, src) {
|
|
568
|
+
function deepMergeResObjInto(dst, src, pathPrefix = []) {
|
|
383
569
|
for (const [k, srcVal] of src.fields) {
|
|
570
|
+
const childPrefix = [...pathPrefix, k];
|
|
571
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
384
572
|
const dstVal = dst.fields.get(k);
|
|
385
573
|
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
386
|
-
|
|
574
|
+
const priorExisting = dst.priorValues.get(k);
|
|
575
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
576
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
577
|
+
deepMergeResObjInto(dstVal, srcVal, childPrefix);
|
|
387
578
|
} else {
|
|
388
|
-
if (dstVal !== void 0)
|
|
579
|
+
if (dstVal !== void 0) {
|
|
580
|
+
const priorExisting = dst.priorValues.get(k);
|
|
581
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
582
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
583
|
+
}
|
|
389
584
|
dst.fields.set(k, srcVal);
|
|
390
585
|
}
|
|
391
586
|
}
|
|
@@ -949,12 +1144,17 @@ var Parser = class {
|
|
|
949
1144
|
const segments = [];
|
|
950
1145
|
let trailingDot = false;
|
|
951
1146
|
let firstWasQuoted = false;
|
|
1147
|
+
let spaceConcat = false;
|
|
952
1148
|
while (true) {
|
|
953
1149
|
const t = this.peek();
|
|
954
1150
|
if (t.kind === "string") {
|
|
955
1151
|
this.advance();
|
|
956
1152
|
if (segments.length === 0) firstWasQuoted = true;
|
|
957
|
-
|
|
1153
|
+
if (spaceConcat) {
|
|
1154
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]} ${t.value}`;
|
|
1155
|
+
} else {
|
|
1156
|
+
segments.push(t.value);
|
|
1157
|
+
}
|
|
958
1158
|
trailingDot = false;
|
|
959
1159
|
} else if (t.kind === "unquoted") {
|
|
960
1160
|
this.advance();
|
|
@@ -971,12 +1171,23 @@ var Parser = class {
|
|
|
971
1171
|
);
|
|
972
1172
|
}
|
|
973
1173
|
}
|
|
974
|
-
|
|
1174
|
+
if (spaceConcat && filtered.length > 0) {
|
|
1175
|
+
if (raw.startsWith(".")) {
|
|
1176
|
+
segments.push(...filtered);
|
|
1177
|
+
} else {
|
|
1178
|
+
const [head, ...tail] = filtered;
|
|
1179
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]} ${head}`;
|
|
1180
|
+
segments.push(...tail);
|
|
1181
|
+
}
|
|
1182
|
+
} else {
|
|
1183
|
+
segments.push(...filtered);
|
|
1184
|
+
}
|
|
975
1185
|
trailingDot = raw.endsWith(".");
|
|
976
1186
|
} else {
|
|
977
1187
|
if (segments.length === 0) throw new ParseError(`expected key, got ${t.kind}`, t.line, t.col);
|
|
978
1188
|
break;
|
|
979
1189
|
}
|
|
1190
|
+
spaceConcat = false;
|
|
980
1191
|
if (trailingDot) continue;
|
|
981
1192
|
const next = this.peek();
|
|
982
1193
|
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
@@ -988,6 +1199,10 @@ var Parser = class {
|
|
|
988
1199
|
trailingDot = true;
|
|
989
1200
|
continue;
|
|
990
1201
|
}
|
|
1202
|
+
if ((next.kind === "unquoted" || next.kind === "string") && next.precedingSpace) {
|
|
1203
|
+
spaceConcat = true;
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
991
1206
|
break;
|
|
992
1207
|
}
|
|
993
1208
|
return { segments, firstWasQuoted };
|
|
@@ -1376,7 +1591,7 @@ var IncludeLoader = class {
|
|
|
1376
1591
|
for (const ext of probeExts) {
|
|
1377
1592
|
const obj = this.loadSingle(`${absPath}${ext}`);
|
|
1378
1593
|
if (obj !== void 0) {
|
|
1379
|
-
deepMergeResObjInto(merged, obj);
|
|
1594
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1380
1595
|
foundAny = true;
|
|
1381
1596
|
}
|
|
1382
1597
|
}
|
|
@@ -1409,7 +1624,7 @@ var IncludeLoader = class {
|
|
|
1409
1624
|
for (const ext of probeExts) {
|
|
1410
1625
|
const obj = await this.loadSingleAsync(`${absPath}${ext}`);
|
|
1411
1626
|
if (obj !== void 0) {
|
|
1412
|
-
deepMergeResObjInto(merged, obj);
|
|
1627
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1413
1628
|
foundAny = true;
|
|
1414
1629
|
}
|
|
1415
1630
|
}
|
|
@@ -1581,7 +1796,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1581
1796
|
if (pathPrefix.length > 0) {
|
|
1582
1797
|
this.relativizeResObj(included, pathPrefix);
|
|
1583
1798
|
}
|
|
1584
|
-
deepMergeResObjInto(obj, included);
|
|
1799
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1585
1800
|
return;
|
|
1586
1801
|
}
|
|
1587
1802
|
const [head, ...tail] = field.key;
|
|
@@ -1596,12 +1811,27 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1596
1811
|
return;
|
|
1597
1812
|
}
|
|
1598
1813
|
const childPrefix = [...pathPrefix, head];
|
|
1814
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1599
1815
|
if (field.append) {
|
|
1600
1816
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1601
|
-
obj.priorValues.
|
|
1817
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1818
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1819
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1602
1820
|
obj.fields.set(head, {
|
|
1603
1821
|
_kind: "append-placeholder",
|
|
1604
|
-
existing
|
|
1822
|
+
// Use the folded existing (self-ref-free) for the Append. When the
|
|
1823
|
+
// fold returned undefined (self-ref present but no old prior),
|
|
1824
|
+
// fall back to the original existing — resolveSubst will then
|
|
1825
|
+
// raise the "self-ref with no prior value" error at resolve time,
|
|
1826
|
+
// matching the pre-fix behaviour for that edge case.
|
|
1827
|
+
//
|
|
1828
|
+
// Clone `folded` again so the Append.existing and priorValues[head]
|
|
1829
|
+
// do not share the same Subst/Concat references — relativizeResObj
|
|
1830
|
+
// walks BOTH `fields` and `priorValues` and mutates Subst.segments
|
|
1831
|
+
// in place, so a shared reference would get its prefix applied
|
|
1832
|
+
// twice (resulting in e.g. `${outer.outer.base}` instead of
|
|
1833
|
+
// `${outer.base}`). Codex review on PR #131.
|
|
1834
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1605
1835
|
elem: this.astToResolverValue(field.value, childPrefix)
|
|
1606
1836
|
});
|
|
1607
1837
|
return;
|
|
@@ -1609,10 +1839,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1609
1839
|
const existing = obj.fields.get(head);
|
|
1610
1840
|
const newVal = this.astToResolverValue(field.value, childPrefix);
|
|
1611
1841
|
if (existing !== void 0) {
|
|
1612
|
-
|
|
1842
|
+
const exFolded = foldNestedSelfRefs(existing, childPrefix);
|
|
1843
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1844
|
+
const prior = foldOrSkipPrior(exFolded, fullKey, oldPrior);
|
|
1845
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1613
1846
|
}
|
|
1614
1847
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1615
|
-
deepMergeResObjInto(existing, newVal);
|
|
1848
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1616
1849
|
return;
|
|
1617
1850
|
}
|
|
1618
1851
|
obj.fields.set(head, newVal);
|
|
@@ -1629,7 +1862,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1629
1862
|
if (pathPrefix.length > 0) {
|
|
1630
1863
|
this.relativizeResObj(included, pathPrefix);
|
|
1631
1864
|
}
|
|
1632
|
-
deepMergeResObjInto(obj, included);
|
|
1865
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1633
1866
|
return;
|
|
1634
1867
|
}
|
|
1635
1868
|
const [head, ...tail] = field.key;
|
|
@@ -1644,12 +1877,15 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1644
1877
|
return;
|
|
1645
1878
|
}
|
|
1646
1879
|
const childPrefix = [...pathPrefix, head];
|
|
1880
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1647
1881
|
if (field.append) {
|
|
1648
1882
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1649
|
-
obj.priorValues.
|
|
1883
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1884
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1885
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1650
1886
|
obj.fields.set(head, {
|
|
1651
1887
|
_kind: "append-placeholder",
|
|
1652
|
-
existing: existing2,
|
|
1888
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1653
1889
|
elem: await this.astToResolverValueAsync(field.value, childPrefix)
|
|
1654
1890
|
});
|
|
1655
1891
|
return;
|
|
@@ -1657,10 +1893,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1657
1893
|
const existing = obj.fields.get(head);
|
|
1658
1894
|
const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
|
|
1659
1895
|
if (existing !== void 0) {
|
|
1660
|
-
|
|
1896
|
+
const exFolded = foldNestedSelfRefs(existing, childPrefix);
|
|
1897
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1898
|
+
const prior = foldOrSkipPrior(exFolded, fullKey, oldPrior);
|
|
1899
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1661
1900
|
}
|
|
1662
1901
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1663
|
-
deepMergeResObjInto(existing, newVal);
|
|
1902
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1664
1903
|
return;
|
|
1665
1904
|
}
|
|
1666
1905
|
obj.fields.set(head, newVal);
|
|
@@ -1792,6 +2031,23 @@ var SubstitutionResolver = class {
|
|
|
1792
2031
|
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
1793
2032
|
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
1794
2033
|
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
2034
|
+
// Path stack tracking the full dotted path of the field currently being
|
|
2035
|
+
// assigned in `resolveResObj`. Leaf keys are pushed on entry to each
|
|
2036
|
+
// `(key, val)` iteration and popped on exit, so nested objects build up
|
|
2037
|
+
// the full path (e.g. `["o", "history"]` while resolving `o.history`).
|
|
2038
|
+
//
|
|
2039
|
+
// Used to tighten self-ref detection at the L192 found-lookup site:
|
|
2040
|
+
// a substitution `${X}` whose looked-up value contains a Subst pointing
|
|
2041
|
+
// at `X` is only a true self-reference when the field currently being
|
|
2042
|
+
// assigned is `X` (or a descendant of `X`). Without this guard, an
|
|
2043
|
+
// external lookup like `b = ${o}` (where o has a self-referential
|
|
2044
|
+
// field) would mis-fire and short-circuit to o's prior, returning the
|
|
2045
|
+
// wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
|
|
2046
|
+
// narrows the check to the owner case only.
|
|
2047
|
+
//
|
|
2048
|
+
// Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
|
|
2049
|
+
// same prefix-match semantics).
|
|
2050
|
+
this.resolvingFieldPath = [];
|
|
1795
2051
|
}
|
|
1796
2052
|
/** Returns a prefix for error messages, including origin info when set. */
|
|
1797
2053
|
originPrefix() {
|
|
@@ -1803,7 +2059,13 @@ var SubstitutionResolver = class {
|
|
|
1803
2059
|
resolveResObj(obj) {
|
|
1804
2060
|
const result = /* @__PURE__ */ new Map();
|
|
1805
2061
|
for (const [key, val] of obj.fields) {
|
|
1806
|
-
|
|
2062
|
+
this.resolvingFieldPath.push(key);
|
|
2063
|
+
let resolved;
|
|
2064
|
+
try {
|
|
2065
|
+
resolved = this.resolveVal(val, obj);
|
|
2066
|
+
} finally {
|
|
2067
|
+
this.resolvingFieldPath.pop();
|
|
2068
|
+
}
|
|
1807
2069
|
if (resolved !== void 0) {
|
|
1808
2070
|
if (resolved.kind === "object") {
|
|
1809
2071
|
const prior = obj.priorValues.get(key);
|
|
@@ -1893,7 +2155,10 @@ var SubstitutionResolver = class {
|
|
|
1893
2155
|
try {
|
|
1894
2156
|
const found = lookupPath(this.root, s.segments);
|
|
1895
2157
|
if (found !== void 0) {
|
|
1896
|
-
|
|
2158
|
+
const rfp = this.resolvingFieldPath;
|
|
2159
|
+
const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
|
|
2160
|
+
const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
|
|
2161
|
+
if (isSelfRef) {
|
|
1897
2162
|
let prior;
|
|
1898
2163
|
if (s.segments.length > 1) {
|
|
1899
2164
|
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
@@ -2104,8 +2369,33 @@ var SubstitutionResolver = class {
|
|
|
2104
2369
|
}
|
|
2105
2370
|
resolveAppend(a, scope) {
|
|
2106
2371
|
const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
|
|
2372
|
+
if (this.opts.allowUnresolved && (isSubst(existing) || isConcat(existing) || isAppend(existing))) {
|
|
2373
|
+
return a;
|
|
2374
|
+
}
|
|
2107
2375
|
const elem = this.resolveVal(a.elem, scope);
|
|
2108
|
-
|
|
2376
|
+
let priorItems;
|
|
2377
|
+
if (existing.kind === "array") {
|
|
2378
|
+
priorItems = existing.items;
|
|
2379
|
+
} else if (existing.kind === "object") {
|
|
2380
|
+
const converted = numericObjectToArray(existing);
|
|
2381
|
+
if (converted === null) {
|
|
2382
|
+
throw new ResolveError(
|
|
2383
|
+
`'+=' on non-array value: prior value is object with non-numeric keys (spec L732)`,
|
|
2384
|
+
"",
|
|
2385
|
+
0,
|
|
2386
|
+
0
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
priorItems = converted;
|
|
2390
|
+
} else {
|
|
2391
|
+
throw new ResolveError(
|
|
2392
|
+
`'+=' on non-array value: prior value is ${existing.kind} (spec L732)`,
|
|
2393
|
+
"",
|
|
2394
|
+
0,
|
|
2395
|
+
0
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
const items = [...priorItems];
|
|
2109
2399
|
if (elem !== void 0) items.push(elem);
|
|
2110
2400
|
return { kind: "array", items };
|
|
2111
2401
|
}
|
|
@@ -2207,6 +2497,9 @@ var Config = class _Config {
|
|
|
2207
2497
|
}
|
|
2208
2498
|
getString(path2) {
|
|
2209
2499
|
const v = this.requireScalar(path2);
|
|
2500
|
+
if (v.valueType === "null") {
|
|
2501
|
+
throw new ConfigError(`expected string at ${path2}, got ${v.valueType}`, path2);
|
|
2502
|
+
}
|
|
2210
2503
|
return v.raw;
|
|
2211
2504
|
}
|
|
2212
2505
|
getNumber(path2) {
|