@o3co/ts.hocon 1.5.0 → 1.6.0
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/{config-BwjvKeZ5.d.cts → config-CjSkQ4_l.d.cts} +2 -0
- package/dist/{config-BwjvKeZ5.d.ts → config-CjSkQ4_l.d.ts} +2 -0
- package/dist/index.cjs +422 -46
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +422 -46
- package/dist/zod.cjs +422 -46
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +422 -46
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -338,6 +338,252 @@ 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 !v.knownAbsent && 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 foldOptionalSelfRefAbsent(prior, fullKey);
|
|
422
|
+
return foldSelfRef(prior, fullKey, old);
|
|
423
|
+
}
|
|
424
|
+
function foldOptionalSelfRefAbsent(v, fullKey) {
|
|
425
|
+
if (isSubst(v) && substFullKey(v) === fullKey) {
|
|
426
|
+
if (!v.optional) return void 0;
|
|
427
|
+
return {
|
|
428
|
+
_kind: "subst-placeholder",
|
|
429
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
430
|
+
optional: v.optional,
|
|
431
|
+
knownAbsent: true,
|
|
432
|
+
listSuffix: v.listSuffix,
|
|
433
|
+
line: v.line,
|
|
434
|
+
col: v.col,
|
|
435
|
+
prefixLen: v.prefixLen
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
if (isConcat(v)) {
|
|
439
|
+
const nodes = [];
|
|
440
|
+
for (const node of v.nodes) {
|
|
441
|
+
const folded = foldOptionalSelfRefAbsent(node, fullKey);
|
|
442
|
+
if (folded === void 0) return void 0;
|
|
443
|
+
nodes.push(folded);
|
|
444
|
+
}
|
|
445
|
+
return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
|
|
446
|
+
}
|
|
447
|
+
if (isAppend(v)) {
|
|
448
|
+
const existing = foldOptionalSelfRefAbsent(v.existing, fullKey);
|
|
449
|
+
const elem = foldOptionalSelfRefAbsent(v.elem, fullKey);
|
|
450
|
+
if (existing === void 0 || elem === void 0) return void 0;
|
|
451
|
+
return { _kind: "append-placeholder", existing, elem };
|
|
452
|
+
}
|
|
453
|
+
if (isResObj(v)) {
|
|
454
|
+
const fields = /* @__PURE__ */ new Map();
|
|
455
|
+
for (const [key, value] of v.fields) {
|
|
456
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
457
|
+
if (folded === void 0) return void 0;
|
|
458
|
+
fields.set(key, folded);
|
|
459
|
+
}
|
|
460
|
+
return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues) };
|
|
461
|
+
}
|
|
462
|
+
const hv = v;
|
|
463
|
+
if (hv.kind === "array") {
|
|
464
|
+
const items = [];
|
|
465
|
+
for (const item of hv.items) {
|
|
466
|
+
const folded = foldOptionalSelfRefAbsent(item, fullKey);
|
|
467
|
+
if (folded === void 0) return void 0;
|
|
468
|
+
items.push(folded);
|
|
469
|
+
}
|
|
470
|
+
return { kind: "array", items };
|
|
471
|
+
}
|
|
472
|
+
if (hv.kind === "object") {
|
|
473
|
+
const fields = /* @__PURE__ */ new Map();
|
|
474
|
+
for (const [key, value] of hv.fields) {
|
|
475
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
476
|
+
if (folded === void 0) return void 0;
|
|
477
|
+
fields.set(key, folded);
|
|
478
|
+
}
|
|
479
|
+
return { kind: "object", fields };
|
|
480
|
+
}
|
|
481
|
+
return cloneResolverValue(v);
|
|
482
|
+
}
|
|
483
|
+
function cloneResolverValue(v) {
|
|
484
|
+
if (isSubst(v)) {
|
|
485
|
+
return {
|
|
486
|
+
_kind: "subst-placeholder",
|
|
487
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
488
|
+
optional: v.optional,
|
|
489
|
+
knownAbsent: v.knownAbsent,
|
|
490
|
+
listSuffix: v.listSuffix,
|
|
491
|
+
line: v.line,
|
|
492
|
+
col: v.col,
|
|
493
|
+
prefixLen: v.prefixLen
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
if (isConcat(v)) {
|
|
497
|
+
return {
|
|
498
|
+
_kind: "concat-placeholder",
|
|
499
|
+
nodes: v.nodes.map((n) => cloneResolverValue(n)),
|
|
500
|
+
line: v.line,
|
|
501
|
+
col: v.col
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
if (isResObj(v)) {
|
|
505
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
506
|
+
for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
|
|
507
|
+
const newPriors = /* @__PURE__ */ new Map();
|
|
508
|
+
for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
|
|
509
|
+
return { _kind: "res-obj", fields: newFields, priorValues: newPriors };
|
|
510
|
+
}
|
|
511
|
+
if (isAppend(v)) {
|
|
512
|
+
return {
|
|
513
|
+
_kind: "append-placeholder",
|
|
514
|
+
existing: cloneResolverValue(v.existing),
|
|
515
|
+
elem: cloneResolverValue(v.elem)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const hv = v;
|
|
519
|
+
if (hv.kind === "array") {
|
|
520
|
+
return {
|
|
521
|
+
kind: "array",
|
|
522
|
+
items: hv.items.map((item) => cloneResolverValue(item))
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (hv.kind === "object") {
|
|
526
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const [k, val] of hv.fields) {
|
|
528
|
+
newFields.set(k, cloneResolverValue(val));
|
|
529
|
+
}
|
|
530
|
+
return { kind: "object", fields: newFields };
|
|
531
|
+
}
|
|
532
|
+
return hv;
|
|
533
|
+
}
|
|
534
|
+
function foldNestedSelfRefs(v, pathPrefix) {
|
|
535
|
+
if (!isResObj(v)) return v;
|
|
536
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const [k, fieldVal] of v.fields) {
|
|
538
|
+
const childPath = [...pathPrefix, k];
|
|
539
|
+
const fullKey = stringSegmentsToKey(childPath);
|
|
540
|
+
const folded = foldNestedSelfRefs(fieldVal, childPath);
|
|
541
|
+
let finalVal = folded;
|
|
542
|
+
if (containsSelfRef(folded, fullKey)) {
|
|
543
|
+
const leafPrior = v.priorValues.get(k);
|
|
544
|
+
if (leafPrior !== void 0) {
|
|
545
|
+
const leafPriorFolded = foldNestedSelfRefs(leafPrior, childPath);
|
|
546
|
+
finalVal = foldSelfRef(folded, fullKey, leafPriorFolded);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
newFields.set(k, finalVal);
|
|
550
|
+
}
|
|
551
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
|
|
552
|
+
}
|
|
553
|
+
function containsSubstByPath(v, target) {
|
|
554
|
+
if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
|
|
555
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
|
|
556
|
+
if (isAppend(v)) {
|
|
557
|
+
return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
|
|
558
|
+
}
|
|
559
|
+
if (isResObj(v)) {
|
|
560
|
+
for (const f of v.fields.values()) {
|
|
561
|
+
if (containsSubstByPath(f, target)) return true;
|
|
562
|
+
}
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
const hv = v;
|
|
566
|
+
if (hv.kind === "array") {
|
|
567
|
+
return hv.items.some((item) => containsSubstByPath(item, target));
|
|
568
|
+
}
|
|
569
|
+
if (hv.kind === "object") {
|
|
570
|
+
for (const f of hv.fields.values()) {
|
|
571
|
+
if (containsSubstByPath(f, target)) return true;
|
|
572
|
+
}
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
function segmentsTextEqual(a, b) {
|
|
578
|
+
if (a.length !== b.length) return false;
|
|
579
|
+
for (let i = 0; i < a.length; i++) {
|
|
580
|
+
const ai = a[i];
|
|
581
|
+
const bi = b[i];
|
|
582
|
+
if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
|
|
583
|
+
}
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
|
|
341
587
|
// src/internal/resolver/utils.ts
|
|
342
588
|
function segmentsToKey(segments) {
|
|
343
589
|
return segments.map((s) => {
|
|
@@ -379,13 +625,22 @@ function deepMergeHoconValues(base, overlay) {
|
|
|
379
625
|
}
|
|
380
626
|
return { kind: "object", fields: merged };
|
|
381
627
|
}
|
|
382
|
-
function deepMergeResObjInto(dst, src) {
|
|
628
|
+
function deepMergeResObjInto(dst, src, pathPrefix = []) {
|
|
383
629
|
for (const [k, srcVal] of src.fields) {
|
|
630
|
+
const childPrefix = [...pathPrefix, k];
|
|
631
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
384
632
|
const dstVal = dst.fields.get(k);
|
|
385
633
|
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
386
|
-
|
|
634
|
+
const priorExisting = dst.priorValues.get(k);
|
|
635
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
636
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
637
|
+
deepMergeResObjInto(dstVal, srcVal, childPrefix);
|
|
387
638
|
} else {
|
|
388
|
-
if (dstVal !== void 0)
|
|
639
|
+
if (dstVal !== void 0) {
|
|
640
|
+
const priorExisting = dst.priorValues.get(k);
|
|
641
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
642
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
643
|
+
}
|
|
389
644
|
dst.fields.set(k, srcVal);
|
|
390
645
|
}
|
|
391
646
|
}
|
|
@@ -463,6 +718,11 @@ var Lexer = class {
|
|
|
463
718
|
this.line = 1;
|
|
464
719
|
this.col = 1;
|
|
465
720
|
this.hadSpace = false;
|
|
721
|
+
// Accumulates literal whitespace chars consumed between tokens. Reset on every
|
|
722
|
+
// token push. Used so the parser can preserve path-expression whitespace
|
|
723
|
+
// (xx.hocon#42, E13) — e.g. tab vs space adjacent to dots in key paths.
|
|
724
|
+
// Comment text is NOT accumulated; only the actual WS chars.
|
|
725
|
+
this.whitespaceBuffer = "";
|
|
466
726
|
this.tokens = [];
|
|
467
727
|
if (input.charCodeAt(0) === 65279) this.input = input.slice(1);
|
|
468
728
|
}
|
|
@@ -478,6 +738,7 @@ var Lexer = class {
|
|
|
478
738
|
continue;
|
|
479
739
|
}
|
|
480
740
|
if (isHoconWhitespace(ch)) {
|
|
741
|
+
this.whitespaceBuffer += ch;
|
|
481
742
|
this.advance();
|
|
482
743
|
this.hadSpace = true;
|
|
483
744
|
continue;
|
|
@@ -569,7 +830,7 @@ var Lexer = class {
|
|
|
569
830
|
}
|
|
570
831
|
throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
|
|
571
832
|
}
|
|
572
|
-
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false });
|
|
833
|
+
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false, precedingWhitespace: "" });
|
|
573
834
|
return this.tokens;
|
|
574
835
|
}
|
|
575
836
|
peek(offset = 0) {
|
|
@@ -586,12 +847,14 @@ var Lexer = class {
|
|
|
586
847
|
return ch;
|
|
587
848
|
}
|
|
588
849
|
push(kind, value, l, c, isQuoted = false) {
|
|
589
|
-
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
850
|
+
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer });
|
|
590
851
|
this.hadSpace = false;
|
|
852
|
+
this.whitespaceBuffer = "";
|
|
591
853
|
}
|
|
592
854
|
pushSubst(payload, value, l, c) {
|
|
593
|
-
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
|
|
855
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer, subst: payload });
|
|
594
856
|
this.hadSpace = false;
|
|
857
|
+
this.whitespaceBuffer = "";
|
|
595
858
|
}
|
|
596
859
|
/**
|
|
597
860
|
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
@@ -837,7 +1100,7 @@ function isUnquotedContinue(ch, nextFn) {
|
|
|
837
1100
|
}
|
|
838
1101
|
|
|
839
1102
|
// src/internal/parser/parser.ts
|
|
840
|
-
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
1103
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, precedingWhitespace: "", subst: void 0 };
|
|
841
1104
|
var Parser = class {
|
|
842
1105
|
constructor(tokens) {
|
|
843
1106
|
this.tokens = tokens;
|
|
@@ -950,13 +1213,17 @@ var Parser = class {
|
|
|
950
1213
|
let trailingDot = false;
|
|
951
1214
|
let firstWasQuoted = false;
|
|
952
1215
|
let spaceConcat = false;
|
|
1216
|
+
let postDotPrefix = "";
|
|
953
1217
|
while (true) {
|
|
954
1218
|
const t = this.peek();
|
|
955
1219
|
if (t.kind === "string") {
|
|
956
1220
|
this.advance();
|
|
957
1221
|
if (segments.length === 0) firstWasQuoted = true;
|
|
958
1222
|
if (spaceConcat) {
|
|
959
|
-
segments[segments.length - 1] = `${segments[segments.length - 1]}
|
|
1223
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}${t.value}`;
|
|
1224
|
+
} else if (postDotPrefix !== "") {
|
|
1225
|
+
segments.push(`${postDotPrefix}${t.value}`);
|
|
1226
|
+
postDotPrefix = "";
|
|
960
1227
|
} else {
|
|
961
1228
|
segments.push(t.value);
|
|
962
1229
|
}
|
|
@@ -966,24 +1233,23 @@ var Parser = class {
|
|
|
966
1233
|
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
967
1234
|
const parts = raw.split(".");
|
|
968
1235
|
const filtered = parts.filter((s) => s.length > 0);
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
|
|
972
|
-
throw new ParseError(
|
|
973
|
-
`unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
|
|
974
|
-
t.line,
|
|
975
|
-
t.col
|
|
976
|
-
);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
if (spaceConcat && filtered.length > 0) {
|
|
1236
|
+
if (spaceConcat) {
|
|
1237
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
|
|
980
1238
|
if (raw.startsWith(".")) {
|
|
981
1239
|
segments.push(...filtered);
|
|
982
|
-
} else {
|
|
1240
|
+
} else if (filtered.length > 0) {
|
|
983
1241
|
const [head, ...tail] = filtered;
|
|
984
|
-
segments[segments.length - 1] = `${segments[segments.length - 1]}
|
|
1242
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${head}`;
|
|
985
1243
|
segments.push(...tail);
|
|
986
1244
|
}
|
|
1245
|
+
} else if (postDotPrefix !== "" && raw.startsWith(".")) {
|
|
1246
|
+
segments.push(postDotPrefix);
|
|
1247
|
+
postDotPrefix = "";
|
|
1248
|
+
segments.push(...filtered);
|
|
1249
|
+
} else if (postDotPrefix !== "" && filtered.length > 0) {
|
|
1250
|
+
const [head, ...tail] = filtered;
|
|
1251
|
+
segments.push(`${postDotPrefix}${head}`, ...tail);
|
|
1252
|
+
postDotPrefix = "";
|
|
987
1253
|
} else {
|
|
988
1254
|
segments.push(...filtered);
|
|
989
1255
|
}
|
|
@@ -993,11 +1259,23 @@ var Parser = class {
|
|
|
993
1259
|
break;
|
|
994
1260
|
}
|
|
995
1261
|
spaceConcat = false;
|
|
996
|
-
if (trailingDot)
|
|
1262
|
+
if (trailingDot) {
|
|
1263
|
+
const next2 = this.peek();
|
|
1264
|
+
if ((next2.kind === "unquoted" || next2.kind === "string") && next2.precedingWhitespace.length > 0) {
|
|
1265
|
+
postDotPrefix = next2.precedingWhitespace;
|
|
1266
|
+
} else {
|
|
1267
|
+
postDotPrefix = "";
|
|
1268
|
+
}
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
997
1271
|
const next = this.peek();
|
|
998
1272
|
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
999
1273
|
this.advance();
|
|
1000
1274
|
trailingDot = true;
|
|
1275
|
+
const afterDot = this.peek();
|
|
1276
|
+
if ((afterDot.kind === "unquoted" || afterDot.kind === "string") && afterDot.precedingWhitespace.length > 0) {
|
|
1277
|
+
postDotPrefix = afterDot.precedingWhitespace;
|
|
1278
|
+
}
|
|
1001
1279
|
continue;
|
|
1002
1280
|
}
|
|
1003
1281
|
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
@@ -1010,6 +1288,14 @@ var Parser = class {
|
|
|
1010
1288
|
}
|
|
1011
1289
|
break;
|
|
1012
1290
|
}
|
|
1291
|
+
if (trailingDot) {
|
|
1292
|
+
const here = this.peek();
|
|
1293
|
+
throw new ParseError(
|
|
1294
|
+
`path has a trailing period '.' \u2014 empty key segment not allowed (HOCON.md path rules)`,
|
|
1295
|
+
here.line,
|
|
1296
|
+
here.col
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1013
1299
|
return { segments, firstWasQuoted };
|
|
1014
1300
|
}
|
|
1015
1301
|
parseInclude() {
|
|
@@ -1182,7 +1468,8 @@ var Parser = class {
|
|
|
1182
1468
|
break;
|
|
1183
1469
|
}
|
|
1184
1470
|
if (hadSpace) {
|
|
1185
|
-
|
|
1471
|
+
const sep = t.precedingWhitespace.length > 0 ? t.precedingWhitespace : " ";
|
|
1472
|
+
parts.push({ kind: "scalar", raw: sep, valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
|
|
1186
1473
|
}
|
|
1187
1474
|
parts.push(node);
|
|
1188
1475
|
}
|
|
@@ -1396,7 +1683,7 @@ var IncludeLoader = class {
|
|
|
1396
1683
|
for (const ext of probeExts) {
|
|
1397
1684
|
const obj = this.loadSingle(`${absPath}${ext}`);
|
|
1398
1685
|
if (obj !== void 0) {
|
|
1399
|
-
deepMergeResObjInto(merged, obj);
|
|
1686
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1400
1687
|
foundAny = true;
|
|
1401
1688
|
}
|
|
1402
1689
|
}
|
|
@@ -1429,7 +1716,7 @@ var IncludeLoader = class {
|
|
|
1429
1716
|
for (const ext of probeExts) {
|
|
1430
1717
|
const obj = await this.loadSingleAsync(`${absPath}${ext}`);
|
|
1431
1718
|
if (obj !== void 0) {
|
|
1432
|
-
deepMergeResObjInto(merged, obj);
|
|
1719
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1433
1720
|
foundAny = true;
|
|
1434
1721
|
}
|
|
1435
1722
|
}
|
|
@@ -1601,7 +1888,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1601
1888
|
if (pathPrefix.length > 0) {
|
|
1602
1889
|
this.relativizeResObj(included, pathPrefix);
|
|
1603
1890
|
}
|
|
1604
|
-
deepMergeResObjInto(obj, included);
|
|
1891
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1605
1892
|
return;
|
|
1606
1893
|
}
|
|
1607
1894
|
const [head, ...tail] = field.key;
|
|
@@ -1616,12 +1903,27 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1616
1903
|
return;
|
|
1617
1904
|
}
|
|
1618
1905
|
const childPrefix = [...pathPrefix, head];
|
|
1906
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1619
1907
|
if (field.append) {
|
|
1620
1908
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1621
|
-
obj.priorValues.
|
|
1909
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1910
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1911
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1622
1912
|
obj.fields.set(head, {
|
|
1623
1913
|
_kind: "append-placeholder",
|
|
1624
|
-
existing
|
|
1914
|
+
// Use the folded existing (self-ref-free) for the Append. When the
|
|
1915
|
+
// fold returned undefined (self-ref present but no old prior),
|
|
1916
|
+
// fall back to the original existing — resolveSubst will then
|
|
1917
|
+
// raise the "self-ref with no prior value" error at resolve time,
|
|
1918
|
+
// matching the pre-fix behaviour for that edge case.
|
|
1919
|
+
//
|
|
1920
|
+
// Clone `folded` again so the Append.existing and priorValues[head]
|
|
1921
|
+
// do not share the same Subst/Concat references — relativizeResObj
|
|
1922
|
+
// walks BOTH `fields` and `priorValues` and mutates Subst.segments
|
|
1923
|
+
// in place, so a shared reference would get its prefix applied
|
|
1924
|
+
// twice (resulting in e.g. `${outer.outer.base}` instead of
|
|
1925
|
+
// `${outer.base}`). Codex review on PR #131.
|
|
1926
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1625
1927
|
elem: this.astToResolverValue(field.value, childPrefix)
|
|
1626
1928
|
});
|
|
1627
1929
|
return;
|
|
@@ -1629,10 +1931,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1629
1931
|
const existing = obj.fields.get(head);
|
|
1630
1932
|
const newVal = this.astToResolverValue(field.value, childPrefix);
|
|
1631
1933
|
if (existing !== void 0) {
|
|
1632
|
-
obj.priorValues.
|
|
1934
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1935
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
1936
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
1937
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1633
1938
|
}
|
|
1634
1939
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1635
|
-
deepMergeResObjInto(existing, newVal);
|
|
1940
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1636
1941
|
return;
|
|
1637
1942
|
}
|
|
1638
1943
|
obj.fields.set(head, newVal);
|
|
@@ -1649,7 +1954,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1649
1954
|
if (pathPrefix.length > 0) {
|
|
1650
1955
|
this.relativizeResObj(included, pathPrefix);
|
|
1651
1956
|
}
|
|
1652
|
-
deepMergeResObjInto(obj, included);
|
|
1957
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1653
1958
|
return;
|
|
1654
1959
|
}
|
|
1655
1960
|
const [head, ...tail] = field.key;
|
|
@@ -1664,12 +1969,15 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1664
1969
|
return;
|
|
1665
1970
|
}
|
|
1666
1971
|
const childPrefix = [...pathPrefix, head];
|
|
1972
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1667
1973
|
if (field.append) {
|
|
1668
1974
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1669
|
-
obj.priorValues.
|
|
1975
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1976
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1977
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1670
1978
|
obj.fields.set(head, {
|
|
1671
1979
|
_kind: "append-placeholder",
|
|
1672
|
-
existing: existing2,
|
|
1980
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1673
1981
|
elem: await this.astToResolverValueAsync(field.value, childPrefix)
|
|
1674
1982
|
});
|
|
1675
1983
|
return;
|
|
@@ -1677,10 +1985,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1677
1985
|
const existing = obj.fields.get(head);
|
|
1678
1986
|
const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
|
|
1679
1987
|
if (existing !== void 0) {
|
|
1680
|
-
obj.priorValues.
|
|
1988
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1989
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
1990
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
1991
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1681
1992
|
}
|
|
1682
1993
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1683
|
-
deepMergeResObjInto(existing, newVal);
|
|
1994
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1684
1995
|
return;
|
|
1685
1996
|
}
|
|
1686
1997
|
obj.fields.set(head, newVal);
|
|
@@ -1699,7 +2010,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1699
2010
|
return inner;
|
|
1700
2011
|
}
|
|
1701
2012
|
case "subst":
|
|
1702
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
2013
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, knownAbsent: false, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1703
2014
|
case "concat":
|
|
1704
2015
|
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
|
|
1705
2016
|
case "include":
|
|
@@ -1723,7 +2034,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1723
2034
|
case "object":
|
|
1724
2035
|
return await this.buildAsync(ast, pathPrefix);
|
|
1725
2036
|
case "subst":
|
|
1726
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
2037
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, knownAbsent: false, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1727
2038
|
case "concat": {
|
|
1728
2039
|
const nodes = [];
|
|
1729
2040
|
for (const n of ast.nodes) {
|
|
@@ -1812,6 +2123,23 @@ var SubstitutionResolver = class {
|
|
|
1812
2123
|
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
1813
2124
|
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
1814
2125
|
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
2126
|
+
// Path stack tracking the full dotted path of the field currently being
|
|
2127
|
+
// assigned in `resolveResObj`. Leaf keys are pushed on entry to each
|
|
2128
|
+
// `(key, val)` iteration and popped on exit, so nested objects build up
|
|
2129
|
+
// the full path (e.g. `["o", "history"]` while resolving `o.history`).
|
|
2130
|
+
//
|
|
2131
|
+
// Used to tighten self-ref detection at the L192 found-lookup site:
|
|
2132
|
+
// a substitution `${X}` whose looked-up value contains a Subst pointing
|
|
2133
|
+
// at `X` is only a true self-reference when the field currently being
|
|
2134
|
+
// assigned is `X` (or a descendant of `X`). Without this guard, an
|
|
2135
|
+
// external lookup like `b = ${o}` (where o has a self-referential
|
|
2136
|
+
// field) would mis-fire and short-circuit to o's prior, returning the
|
|
2137
|
+
// wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
|
|
2138
|
+
// narrows the check to the owner case only.
|
|
2139
|
+
//
|
|
2140
|
+
// Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
|
|
2141
|
+
// same prefix-match semantics).
|
|
2142
|
+
this.resolvingFieldPath = [];
|
|
1815
2143
|
}
|
|
1816
2144
|
/** Returns a prefix for error messages, including origin info when set. */
|
|
1817
2145
|
originPrefix() {
|
|
@@ -1823,35 +2151,57 @@ var SubstitutionResolver = class {
|
|
|
1823
2151
|
resolveResObj(obj) {
|
|
1824
2152
|
const result = /* @__PURE__ */ new Map();
|
|
1825
2153
|
for (const [key, val] of obj.fields) {
|
|
1826
|
-
|
|
2154
|
+
this.resolvingFieldPath.push(key);
|
|
2155
|
+
const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
|
|
2156
|
+
this.cache.delete(fullCacheKey);
|
|
2157
|
+
let resolved;
|
|
2158
|
+
try {
|
|
2159
|
+
resolved = this.resolveVal(val, obj);
|
|
2160
|
+
} finally {
|
|
2161
|
+
this.resolvingFieldPath.pop();
|
|
2162
|
+
}
|
|
1827
2163
|
if (resolved !== void 0) {
|
|
1828
2164
|
if (resolved.kind === "object") {
|
|
1829
2165
|
const prior = obj.priorValues.get(key);
|
|
1830
2166
|
if (prior !== void 0) {
|
|
1831
2167
|
const priorResolved = this.resolveVal(prior, obj);
|
|
1832
2168
|
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
priorResolved,
|
|
1837
|
-
resolved
|
|
1838
|
-
)
|
|
2169
|
+
const finalValue = deepMergeHoconValues(
|
|
2170
|
+
priorResolved,
|
|
2171
|
+
resolved
|
|
1839
2172
|
);
|
|
2173
|
+
result.set(key, finalValue);
|
|
2174
|
+
this.cache.set(fullCacheKey, finalValue);
|
|
2175
|
+
this.cacheDescendants(fullCacheKey, finalValue);
|
|
1840
2176
|
continue;
|
|
1841
2177
|
}
|
|
1842
2178
|
}
|
|
1843
2179
|
}
|
|
1844
2180
|
result.set(key, resolved);
|
|
2181
|
+
this.cache.set(fullCacheKey, resolved);
|
|
2182
|
+
this.cacheDescendants(fullCacheKey, resolved);
|
|
1845
2183
|
} else {
|
|
1846
2184
|
const prior = obj.priorValues.get(key);
|
|
1847
2185
|
if (prior !== void 0) {
|
|
1848
2186
|
const priorResolved = this.resolveVal(prior, obj);
|
|
1849
|
-
if (priorResolved !== void 0)
|
|
2187
|
+
if (priorResolved !== void 0) {
|
|
2188
|
+
result.set(key, priorResolved);
|
|
2189
|
+
this.cache.set(fullCacheKey, priorResolved);
|
|
2190
|
+
this.cacheDescendants(fullCacheKey, priorResolved);
|
|
2191
|
+
}
|
|
1850
2192
|
}
|
|
1851
2193
|
}
|
|
1852
2194
|
}
|
|
1853
2195
|
return { kind: "object", fields: result };
|
|
1854
2196
|
}
|
|
2197
|
+
cacheDescendants(prefix, value) {
|
|
2198
|
+
if (value.kind !== "object") return;
|
|
2199
|
+
for (const [key, child] of value.fields) {
|
|
2200
|
+
const childKey = `${prefix}.${stringSegmentsToKey([key])}`;
|
|
2201
|
+
this.cache.set(childKey, child);
|
|
2202
|
+
this.cacheDescendants(childKey, child);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
1855
2205
|
resolveVal(v, scope) {
|
|
1856
2206
|
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
1857
2207
|
if (isConcat(v)) {
|
|
@@ -1877,6 +2227,7 @@ var SubstitutionResolver = class {
|
|
|
1877
2227
|
return hv;
|
|
1878
2228
|
}
|
|
1879
2229
|
resolveSubst(s, scope) {
|
|
2230
|
+
if (s.knownAbsent) return void 0;
|
|
1880
2231
|
const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
1881
2232
|
if (this.cache.has(key)) return this.cache.get(key);
|
|
1882
2233
|
if (this.resolving.has(key)) {
|
|
@@ -1913,7 +2264,10 @@ var SubstitutionResolver = class {
|
|
|
1913
2264
|
try {
|
|
1914
2265
|
const found = lookupPath(this.root, s.segments);
|
|
1915
2266
|
if (found !== void 0) {
|
|
1916
|
-
|
|
2267
|
+
const rfp = this.resolvingFieldPath;
|
|
2268
|
+
const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
|
|
2269
|
+
const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
|
|
2270
|
+
if (isSelfRef) {
|
|
1917
2271
|
let prior;
|
|
1918
2272
|
if (s.segments.length > 1) {
|
|
1919
2273
|
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
@@ -1966,6 +2320,28 @@ var SubstitutionResolver = class {
|
|
|
1966
2320
|
if (result !== void 0) this.cache.set(key, result);
|
|
1967
2321
|
return result;
|
|
1968
2322
|
}
|
|
2323
|
+
if (s.prefixLen > 0 && s.segments.length > s.prefixLen) {
|
|
2324
|
+
const originalSegments = s.segments.slice(s.prefixLen);
|
|
2325
|
+
const fallbackFound = lookupPath(this.root, originalSegments);
|
|
2326
|
+
if (fallbackFound !== void 0) {
|
|
2327
|
+
let result = this.resolveVal(fallbackFound, scope);
|
|
2328
|
+
if (originalSegments.length === 1 && result !== void 0 && result.kind === "object") {
|
|
2329
|
+
const rootSeg = originalSegments[0]?.text ?? "";
|
|
2330
|
+
const prior = this.root.priorValues.get(rootSeg);
|
|
2331
|
+
if (prior !== void 0) {
|
|
2332
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
2333
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
2334
|
+
result = deepMergeHoconValues(
|
|
2335
|
+
priorResolved,
|
|
2336
|
+
result
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
2342
|
+
return result;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
1969
2345
|
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
1970
2346
|
const result = this.resolveEnvList(s);
|
|
1971
2347
|
if (result !== void 0) {
|