@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/zod.cjs
CHANGED
|
@@ -329,6 +329,252 @@ 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 !v.knownAbsent && 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 foldOptionalSelfRefAbsent(prior, fullKey);
|
|
413
|
+
return foldSelfRef(prior, fullKey, old);
|
|
414
|
+
}
|
|
415
|
+
function foldOptionalSelfRefAbsent(v, fullKey) {
|
|
416
|
+
if (isSubst(v) && substFullKey(v) === fullKey) {
|
|
417
|
+
if (!v.optional) return void 0;
|
|
418
|
+
return {
|
|
419
|
+
_kind: "subst-placeholder",
|
|
420
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
421
|
+
optional: v.optional,
|
|
422
|
+
knownAbsent: true,
|
|
423
|
+
listSuffix: v.listSuffix,
|
|
424
|
+
line: v.line,
|
|
425
|
+
col: v.col,
|
|
426
|
+
prefixLen: v.prefixLen
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
if (isConcat(v)) {
|
|
430
|
+
const nodes = [];
|
|
431
|
+
for (const node of v.nodes) {
|
|
432
|
+
const folded = foldOptionalSelfRefAbsent(node, fullKey);
|
|
433
|
+
if (folded === void 0) return void 0;
|
|
434
|
+
nodes.push(folded);
|
|
435
|
+
}
|
|
436
|
+
return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
|
|
437
|
+
}
|
|
438
|
+
if (isAppend(v)) {
|
|
439
|
+
const existing = foldOptionalSelfRefAbsent(v.existing, fullKey);
|
|
440
|
+
const elem = foldOptionalSelfRefAbsent(v.elem, fullKey);
|
|
441
|
+
if (existing === void 0 || elem === void 0) return void 0;
|
|
442
|
+
return { _kind: "append-placeholder", existing, elem };
|
|
443
|
+
}
|
|
444
|
+
if (isResObj(v)) {
|
|
445
|
+
const fields = /* @__PURE__ */ new Map();
|
|
446
|
+
for (const [key, value] of v.fields) {
|
|
447
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
448
|
+
if (folded === void 0) return void 0;
|
|
449
|
+
fields.set(key, folded);
|
|
450
|
+
}
|
|
451
|
+
return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues) };
|
|
452
|
+
}
|
|
453
|
+
const hv = v;
|
|
454
|
+
if (hv.kind === "array") {
|
|
455
|
+
const items = [];
|
|
456
|
+
for (const item of hv.items) {
|
|
457
|
+
const folded = foldOptionalSelfRefAbsent(item, fullKey);
|
|
458
|
+
if (folded === void 0) return void 0;
|
|
459
|
+
items.push(folded);
|
|
460
|
+
}
|
|
461
|
+
return { kind: "array", items };
|
|
462
|
+
}
|
|
463
|
+
if (hv.kind === "object") {
|
|
464
|
+
const fields = /* @__PURE__ */ new Map();
|
|
465
|
+
for (const [key, value] of hv.fields) {
|
|
466
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
467
|
+
if (folded === void 0) return void 0;
|
|
468
|
+
fields.set(key, folded);
|
|
469
|
+
}
|
|
470
|
+
return { kind: "object", fields };
|
|
471
|
+
}
|
|
472
|
+
return cloneResolverValue(v);
|
|
473
|
+
}
|
|
474
|
+
function cloneResolverValue(v) {
|
|
475
|
+
if (isSubst(v)) {
|
|
476
|
+
return {
|
|
477
|
+
_kind: "subst-placeholder",
|
|
478
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
479
|
+
optional: v.optional,
|
|
480
|
+
knownAbsent: v.knownAbsent,
|
|
481
|
+
listSuffix: v.listSuffix,
|
|
482
|
+
line: v.line,
|
|
483
|
+
col: v.col,
|
|
484
|
+
prefixLen: v.prefixLen
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
if (isConcat(v)) {
|
|
488
|
+
return {
|
|
489
|
+
_kind: "concat-placeholder",
|
|
490
|
+
nodes: v.nodes.map((n) => cloneResolverValue(n)),
|
|
491
|
+
line: v.line,
|
|
492
|
+
col: v.col
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
if (isResObj(v)) {
|
|
496
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
497
|
+
for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
|
|
498
|
+
const newPriors = /* @__PURE__ */ new Map();
|
|
499
|
+
for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
|
|
500
|
+
return { _kind: "res-obj", fields: newFields, priorValues: newPriors };
|
|
501
|
+
}
|
|
502
|
+
if (isAppend(v)) {
|
|
503
|
+
return {
|
|
504
|
+
_kind: "append-placeholder",
|
|
505
|
+
existing: cloneResolverValue(v.existing),
|
|
506
|
+
elem: cloneResolverValue(v.elem)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
const hv = v;
|
|
510
|
+
if (hv.kind === "array") {
|
|
511
|
+
return {
|
|
512
|
+
kind: "array",
|
|
513
|
+
items: hv.items.map((item) => cloneResolverValue(item))
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
if (hv.kind === "object") {
|
|
517
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
518
|
+
for (const [k, val] of hv.fields) {
|
|
519
|
+
newFields.set(k, cloneResolverValue(val));
|
|
520
|
+
}
|
|
521
|
+
return { kind: "object", fields: newFields };
|
|
522
|
+
}
|
|
523
|
+
return hv;
|
|
524
|
+
}
|
|
525
|
+
function foldNestedSelfRefs(v, pathPrefix) {
|
|
526
|
+
if (!isResObj(v)) return v;
|
|
527
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
528
|
+
for (const [k, fieldVal] of v.fields) {
|
|
529
|
+
const childPath = [...pathPrefix, k];
|
|
530
|
+
const fullKey = stringSegmentsToKey(childPath);
|
|
531
|
+
const folded = foldNestedSelfRefs(fieldVal, childPath);
|
|
532
|
+
let finalVal = folded;
|
|
533
|
+
if (containsSelfRef(folded, fullKey)) {
|
|
534
|
+
const leafPrior = v.priorValues.get(k);
|
|
535
|
+
if (leafPrior !== void 0) {
|
|
536
|
+
const leafPriorFolded = foldNestedSelfRefs(leafPrior, childPath);
|
|
537
|
+
finalVal = foldSelfRef(folded, fullKey, leafPriorFolded);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
newFields.set(k, finalVal);
|
|
541
|
+
}
|
|
542
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
|
|
543
|
+
}
|
|
544
|
+
function containsSubstByPath(v, target) {
|
|
545
|
+
if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
|
|
546
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
|
|
547
|
+
if (isAppend(v)) {
|
|
548
|
+
return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
|
|
549
|
+
}
|
|
550
|
+
if (isResObj(v)) {
|
|
551
|
+
for (const f of v.fields.values()) {
|
|
552
|
+
if (containsSubstByPath(f, target)) return true;
|
|
553
|
+
}
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
const hv = v;
|
|
557
|
+
if (hv.kind === "array") {
|
|
558
|
+
return hv.items.some((item) => containsSubstByPath(item, target));
|
|
559
|
+
}
|
|
560
|
+
if (hv.kind === "object") {
|
|
561
|
+
for (const f of hv.fields.values()) {
|
|
562
|
+
if (containsSubstByPath(f, target)) return true;
|
|
563
|
+
}
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
function segmentsTextEqual(a, b) {
|
|
569
|
+
if (a.length !== b.length) return false;
|
|
570
|
+
for (let i = 0; i < a.length; i++) {
|
|
571
|
+
const ai = a[i];
|
|
572
|
+
const bi = b[i];
|
|
573
|
+
if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
|
|
574
|
+
}
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
|
|
332
578
|
// src/internal/resolver/utils.ts
|
|
333
579
|
function segmentsToKey(segments) {
|
|
334
580
|
return segments.map((s) => {
|
|
@@ -370,13 +616,22 @@ function deepMergeHoconValues(base, overlay) {
|
|
|
370
616
|
}
|
|
371
617
|
return { kind: "object", fields: merged };
|
|
372
618
|
}
|
|
373
|
-
function deepMergeResObjInto(dst, src) {
|
|
619
|
+
function deepMergeResObjInto(dst, src, pathPrefix = []) {
|
|
374
620
|
for (const [k, srcVal] of src.fields) {
|
|
621
|
+
const childPrefix = [...pathPrefix, k];
|
|
622
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
375
623
|
const dstVal = dst.fields.get(k);
|
|
376
624
|
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
377
|
-
|
|
625
|
+
const priorExisting = dst.priorValues.get(k);
|
|
626
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
627
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
628
|
+
deepMergeResObjInto(dstVal, srcVal, childPrefix);
|
|
378
629
|
} else {
|
|
379
|
-
if (dstVal !== void 0)
|
|
630
|
+
if (dstVal !== void 0) {
|
|
631
|
+
const priorExisting = dst.priorValues.get(k);
|
|
632
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
633
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
634
|
+
}
|
|
380
635
|
dst.fields.set(k, srcVal);
|
|
381
636
|
}
|
|
382
637
|
}
|
|
@@ -454,6 +709,11 @@ var Lexer = class {
|
|
|
454
709
|
this.line = 1;
|
|
455
710
|
this.col = 1;
|
|
456
711
|
this.hadSpace = false;
|
|
712
|
+
// Accumulates literal whitespace chars consumed between tokens. Reset on every
|
|
713
|
+
// token push. Used so the parser can preserve path-expression whitespace
|
|
714
|
+
// (xx.hocon#42, E13) — e.g. tab vs space adjacent to dots in key paths.
|
|
715
|
+
// Comment text is NOT accumulated; only the actual WS chars.
|
|
716
|
+
this.whitespaceBuffer = "";
|
|
457
717
|
this.tokens = [];
|
|
458
718
|
if (input.charCodeAt(0) === 65279) this.input = input.slice(1);
|
|
459
719
|
}
|
|
@@ -469,6 +729,7 @@ var Lexer = class {
|
|
|
469
729
|
continue;
|
|
470
730
|
}
|
|
471
731
|
if (isHoconWhitespace(ch)) {
|
|
732
|
+
this.whitespaceBuffer += ch;
|
|
472
733
|
this.advance();
|
|
473
734
|
this.hadSpace = true;
|
|
474
735
|
continue;
|
|
@@ -560,7 +821,7 @@ var Lexer = class {
|
|
|
560
821
|
}
|
|
561
822
|
throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
|
|
562
823
|
}
|
|
563
|
-
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false });
|
|
824
|
+
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false, precedingWhitespace: "" });
|
|
564
825
|
return this.tokens;
|
|
565
826
|
}
|
|
566
827
|
peek(offset = 0) {
|
|
@@ -577,12 +838,14 @@ var Lexer = class {
|
|
|
577
838
|
return ch;
|
|
578
839
|
}
|
|
579
840
|
push(kind, value, l, c, isQuoted = false) {
|
|
580
|
-
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
841
|
+
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer });
|
|
581
842
|
this.hadSpace = false;
|
|
843
|
+
this.whitespaceBuffer = "";
|
|
582
844
|
}
|
|
583
845
|
pushSubst(payload, value, l, c) {
|
|
584
|
-
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
|
|
846
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer, subst: payload });
|
|
585
847
|
this.hadSpace = false;
|
|
848
|
+
this.whitespaceBuffer = "";
|
|
586
849
|
}
|
|
587
850
|
/**
|
|
588
851
|
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
@@ -828,7 +1091,7 @@ function isUnquotedContinue(ch, nextFn) {
|
|
|
828
1091
|
}
|
|
829
1092
|
|
|
830
1093
|
// src/internal/parser/parser.ts
|
|
831
|
-
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
1094
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, precedingWhitespace: "", subst: void 0 };
|
|
832
1095
|
var Parser = class {
|
|
833
1096
|
constructor(tokens) {
|
|
834
1097
|
this.tokens = tokens;
|
|
@@ -941,13 +1204,17 @@ var Parser = class {
|
|
|
941
1204
|
let trailingDot = false;
|
|
942
1205
|
let firstWasQuoted = false;
|
|
943
1206
|
let spaceConcat = false;
|
|
1207
|
+
let postDotPrefix = "";
|
|
944
1208
|
while (true) {
|
|
945
1209
|
const t = this.peek();
|
|
946
1210
|
if (t.kind === "string") {
|
|
947
1211
|
this.advance();
|
|
948
1212
|
if (segments.length === 0) firstWasQuoted = true;
|
|
949
1213
|
if (spaceConcat) {
|
|
950
|
-
segments[segments.length - 1] = `${segments[segments.length - 1]}
|
|
1214
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}${t.value}`;
|
|
1215
|
+
} else if (postDotPrefix !== "") {
|
|
1216
|
+
segments.push(`${postDotPrefix}${t.value}`);
|
|
1217
|
+
postDotPrefix = "";
|
|
951
1218
|
} else {
|
|
952
1219
|
segments.push(t.value);
|
|
953
1220
|
}
|
|
@@ -957,24 +1224,23 @@ var Parser = class {
|
|
|
957
1224
|
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
958
1225
|
const parts = raw.split(".");
|
|
959
1226
|
const filtered = parts.filter((s) => s.length > 0);
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
|
|
963
|
-
throw new ParseError(
|
|
964
|
-
`unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
|
|
965
|
-
t.line,
|
|
966
|
-
t.col
|
|
967
|
-
);
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
if (spaceConcat && filtered.length > 0) {
|
|
1227
|
+
if (spaceConcat) {
|
|
1228
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
|
|
971
1229
|
if (raw.startsWith(".")) {
|
|
972
1230
|
segments.push(...filtered);
|
|
973
|
-
} else {
|
|
1231
|
+
} else if (filtered.length > 0) {
|
|
974
1232
|
const [head, ...tail] = filtered;
|
|
975
|
-
segments[segments.length - 1] = `${segments[segments.length - 1]}
|
|
1233
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${head}`;
|
|
976
1234
|
segments.push(...tail);
|
|
977
1235
|
}
|
|
1236
|
+
} else if (postDotPrefix !== "" && raw.startsWith(".")) {
|
|
1237
|
+
segments.push(postDotPrefix);
|
|
1238
|
+
postDotPrefix = "";
|
|
1239
|
+
segments.push(...filtered);
|
|
1240
|
+
} else if (postDotPrefix !== "" && filtered.length > 0) {
|
|
1241
|
+
const [head, ...tail] = filtered;
|
|
1242
|
+
segments.push(`${postDotPrefix}${head}`, ...tail);
|
|
1243
|
+
postDotPrefix = "";
|
|
978
1244
|
} else {
|
|
979
1245
|
segments.push(...filtered);
|
|
980
1246
|
}
|
|
@@ -984,11 +1250,23 @@ var Parser = class {
|
|
|
984
1250
|
break;
|
|
985
1251
|
}
|
|
986
1252
|
spaceConcat = false;
|
|
987
|
-
if (trailingDot)
|
|
1253
|
+
if (trailingDot) {
|
|
1254
|
+
const next2 = this.peek();
|
|
1255
|
+
if ((next2.kind === "unquoted" || next2.kind === "string") && next2.precedingWhitespace.length > 0) {
|
|
1256
|
+
postDotPrefix = next2.precedingWhitespace;
|
|
1257
|
+
} else {
|
|
1258
|
+
postDotPrefix = "";
|
|
1259
|
+
}
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
988
1262
|
const next = this.peek();
|
|
989
1263
|
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
990
1264
|
this.advance();
|
|
991
1265
|
trailingDot = true;
|
|
1266
|
+
const afterDot = this.peek();
|
|
1267
|
+
if ((afterDot.kind === "unquoted" || afterDot.kind === "string") && afterDot.precedingWhitespace.length > 0) {
|
|
1268
|
+
postDotPrefix = afterDot.precedingWhitespace;
|
|
1269
|
+
}
|
|
992
1270
|
continue;
|
|
993
1271
|
}
|
|
994
1272
|
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
@@ -1001,6 +1279,14 @@ var Parser = class {
|
|
|
1001
1279
|
}
|
|
1002
1280
|
break;
|
|
1003
1281
|
}
|
|
1282
|
+
if (trailingDot) {
|
|
1283
|
+
const here = this.peek();
|
|
1284
|
+
throw new ParseError(
|
|
1285
|
+
`path has a trailing period '.' \u2014 empty key segment not allowed (HOCON.md path rules)`,
|
|
1286
|
+
here.line,
|
|
1287
|
+
here.col
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1004
1290
|
return { segments, firstWasQuoted };
|
|
1005
1291
|
}
|
|
1006
1292
|
parseInclude() {
|
|
@@ -1173,7 +1459,8 @@ var Parser = class {
|
|
|
1173
1459
|
break;
|
|
1174
1460
|
}
|
|
1175
1461
|
if (hadSpace) {
|
|
1176
|
-
|
|
1462
|
+
const sep = t.precedingWhitespace.length > 0 ? t.precedingWhitespace : " ";
|
|
1463
|
+
parts.push({ kind: "scalar", raw: sep, valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
|
|
1177
1464
|
}
|
|
1178
1465
|
parts.push(node);
|
|
1179
1466
|
}
|
|
@@ -1387,7 +1674,7 @@ var IncludeLoader = class {
|
|
|
1387
1674
|
for (const ext of probeExts) {
|
|
1388
1675
|
const obj = this.loadSingle(`${absPath}${ext}`);
|
|
1389
1676
|
if (obj !== void 0) {
|
|
1390
|
-
deepMergeResObjInto(merged, obj);
|
|
1677
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1391
1678
|
foundAny = true;
|
|
1392
1679
|
}
|
|
1393
1680
|
}
|
|
@@ -1420,7 +1707,7 @@ var IncludeLoader = class {
|
|
|
1420
1707
|
for (const ext of probeExts) {
|
|
1421
1708
|
const obj = await this.loadSingleAsync(`${absPath}${ext}`);
|
|
1422
1709
|
if (obj !== void 0) {
|
|
1423
|
-
deepMergeResObjInto(merged, obj);
|
|
1710
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1424
1711
|
foundAny = true;
|
|
1425
1712
|
}
|
|
1426
1713
|
}
|
|
@@ -1592,7 +1879,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1592
1879
|
if (pathPrefix.length > 0) {
|
|
1593
1880
|
this.relativizeResObj(included, pathPrefix);
|
|
1594
1881
|
}
|
|
1595
|
-
deepMergeResObjInto(obj, included);
|
|
1882
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1596
1883
|
return;
|
|
1597
1884
|
}
|
|
1598
1885
|
const [head, ...tail] = field.key;
|
|
@@ -1607,12 +1894,27 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1607
1894
|
return;
|
|
1608
1895
|
}
|
|
1609
1896
|
const childPrefix = [...pathPrefix, head];
|
|
1897
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1610
1898
|
if (field.append) {
|
|
1611
1899
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1612
|
-
obj.priorValues.
|
|
1900
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1901
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1902
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1613
1903
|
obj.fields.set(head, {
|
|
1614
1904
|
_kind: "append-placeholder",
|
|
1615
|
-
existing
|
|
1905
|
+
// Use the folded existing (self-ref-free) for the Append. When the
|
|
1906
|
+
// fold returned undefined (self-ref present but no old prior),
|
|
1907
|
+
// fall back to the original existing — resolveSubst will then
|
|
1908
|
+
// raise the "self-ref with no prior value" error at resolve time,
|
|
1909
|
+
// matching the pre-fix behaviour for that edge case.
|
|
1910
|
+
//
|
|
1911
|
+
// Clone `folded` again so the Append.existing and priorValues[head]
|
|
1912
|
+
// do not share the same Subst/Concat references — relativizeResObj
|
|
1913
|
+
// walks BOTH `fields` and `priorValues` and mutates Subst.segments
|
|
1914
|
+
// in place, so a shared reference would get its prefix applied
|
|
1915
|
+
// twice (resulting in e.g. `${outer.outer.base}` instead of
|
|
1916
|
+
// `${outer.base}`). Codex review on PR #131.
|
|
1917
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1616
1918
|
elem: this.astToResolverValue(field.value, childPrefix)
|
|
1617
1919
|
});
|
|
1618
1920
|
return;
|
|
@@ -1620,10 +1922,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1620
1922
|
const existing = obj.fields.get(head);
|
|
1621
1923
|
const newVal = this.astToResolverValue(field.value, childPrefix);
|
|
1622
1924
|
if (existing !== void 0) {
|
|
1623
|
-
obj.priorValues.
|
|
1925
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1926
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
1927
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
1928
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1624
1929
|
}
|
|
1625
1930
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1626
|
-
deepMergeResObjInto(existing, newVal);
|
|
1931
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1627
1932
|
return;
|
|
1628
1933
|
}
|
|
1629
1934
|
obj.fields.set(head, newVal);
|
|
@@ -1640,7 +1945,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1640
1945
|
if (pathPrefix.length > 0) {
|
|
1641
1946
|
this.relativizeResObj(included, pathPrefix);
|
|
1642
1947
|
}
|
|
1643
|
-
deepMergeResObjInto(obj, included);
|
|
1948
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1644
1949
|
return;
|
|
1645
1950
|
}
|
|
1646
1951
|
const [head, ...tail] = field.key;
|
|
@@ -1655,12 +1960,15 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1655
1960
|
return;
|
|
1656
1961
|
}
|
|
1657
1962
|
const childPrefix = [...pathPrefix, head];
|
|
1963
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1658
1964
|
if (field.append) {
|
|
1659
1965
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1660
|
-
obj.priorValues.
|
|
1966
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1967
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1968
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1661
1969
|
obj.fields.set(head, {
|
|
1662
1970
|
_kind: "append-placeholder",
|
|
1663
|
-
existing: existing2,
|
|
1971
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1664
1972
|
elem: await this.astToResolverValueAsync(field.value, childPrefix)
|
|
1665
1973
|
});
|
|
1666
1974
|
return;
|
|
@@ -1668,10 +1976,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1668
1976
|
const existing = obj.fields.get(head);
|
|
1669
1977
|
const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
|
|
1670
1978
|
if (existing !== void 0) {
|
|
1671
|
-
obj.priorValues.
|
|
1979
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1980
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
1981
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
1982
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1672
1983
|
}
|
|
1673
1984
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1674
|
-
deepMergeResObjInto(existing, newVal);
|
|
1985
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1675
1986
|
return;
|
|
1676
1987
|
}
|
|
1677
1988
|
obj.fields.set(head, newVal);
|
|
@@ -1690,7 +2001,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1690
2001
|
return inner;
|
|
1691
2002
|
}
|
|
1692
2003
|
case "subst":
|
|
1693
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
2004
|
+
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 };
|
|
1694
2005
|
case "concat":
|
|
1695
2006
|
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
|
|
1696
2007
|
case "include":
|
|
@@ -1714,7 +2025,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1714
2025
|
case "object":
|
|
1715
2026
|
return await this.buildAsync(ast, pathPrefix);
|
|
1716
2027
|
case "subst":
|
|
1717
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
2028
|
+
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 };
|
|
1718
2029
|
case "concat": {
|
|
1719
2030
|
const nodes = [];
|
|
1720
2031
|
for (const n of ast.nodes) {
|
|
@@ -1803,6 +2114,23 @@ var SubstitutionResolver = class {
|
|
|
1803
2114
|
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
1804
2115
|
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
1805
2116
|
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
2117
|
+
// Path stack tracking the full dotted path of the field currently being
|
|
2118
|
+
// assigned in `resolveResObj`. Leaf keys are pushed on entry to each
|
|
2119
|
+
// `(key, val)` iteration and popped on exit, so nested objects build up
|
|
2120
|
+
// the full path (e.g. `["o", "history"]` while resolving `o.history`).
|
|
2121
|
+
//
|
|
2122
|
+
// Used to tighten self-ref detection at the L192 found-lookup site:
|
|
2123
|
+
// a substitution `${X}` whose looked-up value contains a Subst pointing
|
|
2124
|
+
// at `X` is only a true self-reference when the field currently being
|
|
2125
|
+
// assigned is `X` (or a descendant of `X`). Without this guard, an
|
|
2126
|
+
// external lookup like `b = ${o}` (where o has a self-referential
|
|
2127
|
+
// field) would mis-fire and short-circuit to o's prior, returning the
|
|
2128
|
+
// wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
|
|
2129
|
+
// narrows the check to the owner case only.
|
|
2130
|
+
//
|
|
2131
|
+
// Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
|
|
2132
|
+
// same prefix-match semantics).
|
|
2133
|
+
this.resolvingFieldPath = [];
|
|
1806
2134
|
}
|
|
1807
2135
|
/** Returns a prefix for error messages, including origin info when set. */
|
|
1808
2136
|
originPrefix() {
|
|
@@ -1814,35 +2142,57 @@ var SubstitutionResolver = class {
|
|
|
1814
2142
|
resolveResObj(obj) {
|
|
1815
2143
|
const result = /* @__PURE__ */ new Map();
|
|
1816
2144
|
for (const [key, val] of obj.fields) {
|
|
1817
|
-
|
|
2145
|
+
this.resolvingFieldPath.push(key);
|
|
2146
|
+
const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
|
|
2147
|
+
this.cache.delete(fullCacheKey);
|
|
2148
|
+
let resolved;
|
|
2149
|
+
try {
|
|
2150
|
+
resolved = this.resolveVal(val, obj);
|
|
2151
|
+
} finally {
|
|
2152
|
+
this.resolvingFieldPath.pop();
|
|
2153
|
+
}
|
|
1818
2154
|
if (resolved !== void 0) {
|
|
1819
2155
|
if (resolved.kind === "object") {
|
|
1820
2156
|
const prior = obj.priorValues.get(key);
|
|
1821
2157
|
if (prior !== void 0) {
|
|
1822
2158
|
const priorResolved = this.resolveVal(prior, obj);
|
|
1823
2159
|
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
priorResolved,
|
|
1828
|
-
resolved
|
|
1829
|
-
)
|
|
2160
|
+
const finalValue = deepMergeHoconValues(
|
|
2161
|
+
priorResolved,
|
|
2162
|
+
resolved
|
|
1830
2163
|
);
|
|
2164
|
+
result.set(key, finalValue);
|
|
2165
|
+
this.cache.set(fullCacheKey, finalValue);
|
|
2166
|
+
this.cacheDescendants(fullCacheKey, finalValue);
|
|
1831
2167
|
continue;
|
|
1832
2168
|
}
|
|
1833
2169
|
}
|
|
1834
2170
|
}
|
|
1835
2171
|
result.set(key, resolved);
|
|
2172
|
+
this.cache.set(fullCacheKey, resolved);
|
|
2173
|
+
this.cacheDescendants(fullCacheKey, resolved);
|
|
1836
2174
|
} else {
|
|
1837
2175
|
const prior = obj.priorValues.get(key);
|
|
1838
2176
|
if (prior !== void 0) {
|
|
1839
2177
|
const priorResolved = this.resolveVal(prior, obj);
|
|
1840
|
-
if (priorResolved !== void 0)
|
|
2178
|
+
if (priorResolved !== void 0) {
|
|
2179
|
+
result.set(key, priorResolved);
|
|
2180
|
+
this.cache.set(fullCacheKey, priorResolved);
|
|
2181
|
+
this.cacheDescendants(fullCacheKey, priorResolved);
|
|
2182
|
+
}
|
|
1841
2183
|
}
|
|
1842
2184
|
}
|
|
1843
2185
|
}
|
|
1844
2186
|
return { kind: "object", fields: result };
|
|
1845
2187
|
}
|
|
2188
|
+
cacheDescendants(prefix, value) {
|
|
2189
|
+
if (value.kind !== "object") return;
|
|
2190
|
+
for (const [key, child] of value.fields) {
|
|
2191
|
+
const childKey = `${prefix}.${stringSegmentsToKey([key])}`;
|
|
2192
|
+
this.cache.set(childKey, child);
|
|
2193
|
+
this.cacheDescendants(childKey, child);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
1846
2196
|
resolveVal(v, scope) {
|
|
1847
2197
|
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
1848
2198
|
if (isConcat(v)) {
|
|
@@ -1868,6 +2218,7 @@ var SubstitutionResolver = class {
|
|
|
1868
2218
|
return hv;
|
|
1869
2219
|
}
|
|
1870
2220
|
resolveSubst(s, scope) {
|
|
2221
|
+
if (s.knownAbsent) return void 0;
|
|
1871
2222
|
const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
1872
2223
|
if (this.cache.has(key)) return this.cache.get(key);
|
|
1873
2224
|
if (this.resolving.has(key)) {
|
|
@@ -1904,7 +2255,10 @@ var SubstitutionResolver = class {
|
|
|
1904
2255
|
try {
|
|
1905
2256
|
const found = lookupPath(this.root, s.segments);
|
|
1906
2257
|
if (found !== void 0) {
|
|
1907
|
-
|
|
2258
|
+
const rfp = this.resolvingFieldPath;
|
|
2259
|
+
const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
|
|
2260
|
+
const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
|
|
2261
|
+
if (isSelfRef) {
|
|
1908
2262
|
let prior;
|
|
1909
2263
|
if (s.segments.length > 1) {
|
|
1910
2264
|
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
@@ -1957,6 +2311,28 @@ var SubstitutionResolver = class {
|
|
|
1957
2311
|
if (result !== void 0) this.cache.set(key, result);
|
|
1958
2312
|
return result;
|
|
1959
2313
|
}
|
|
2314
|
+
if (s.prefixLen > 0 && s.segments.length > s.prefixLen) {
|
|
2315
|
+
const originalSegments = s.segments.slice(s.prefixLen);
|
|
2316
|
+
const fallbackFound = lookupPath(this.root, originalSegments);
|
|
2317
|
+
if (fallbackFound !== void 0) {
|
|
2318
|
+
let result = this.resolveVal(fallbackFound, scope);
|
|
2319
|
+
if (originalSegments.length === 1 && result !== void 0 && result.kind === "object") {
|
|
2320
|
+
const rootSeg = originalSegments[0]?.text ?? "";
|
|
2321
|
+
const prior = this.root.priorValues.get(rootSeg);
|
|
2322
|
+
if (prior !== void 0) {
|
|
2323
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
2324
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
2325
|
+
result = deepMergeHoconValues(
|
|
2326
|
+
priorResolved,
|
|
2327
|
+
result
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
2333
|
+
return result;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
1960
2336
|
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
1961
2337
|
const result = this.resolveEnvList(s);
|
|
1962
2338
|
if (result !== void 0) {
|