@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.js
CHANGED
|
@@ -290,6 +290,252 @@ 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 !v.knownAbsent && 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 foldOptionalSelfRefAbsent(prior, fullKey);
|
|
374
|
+
return foldSelfRef(prior, fullKey, old);
|
|
375
|
+
}
|
|
376
|
+
function foldOptionalSelfRefAbsent(v, fullKey) {
|
|
377
|
+
if (isSubst(v) && substFullKey(v) === fullKey) {
|
|
378
|
+
if (!v.optional) return void 0;
|
|
379
|
+
return {
|
|
380
|
+
_kind: "subst-placeholder",
|
|
381
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
382
|
+
optional: v.optional,
|
|
383
|
+
knownAbsent: true,
|
|
384
|
+
listSuffix: v.listSuffix,
|
|
385
|
+
line: v.line,
|
|
386
|
+
col: v.col,
|
|
387
|
+
prefixLen: v.prefixLen
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (isConcat(v)) {
|
|
391
|
+
const nodes = [];
|
|
392
|
+
for (const node of v.nodes) {
|
|
393
|
+
const folded = foldOptionalSelfRefAbsent(node, fullKey);
|
|
394
|
+
if (folded === void 0) return void 0;
|
|
395
|
+
nodes.push(folded);
|
|
396
|
+
}
|
|
397
|
+
return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
|
|
398
|
+
}
|
|
399
|
+
if (isAppend(v)) {
|
|
400
|
+
const existing = foldOptionalSelfRefAbsent(v.existing, fullKey);
|
|
401
|
+
const elem = foldOptionalSelfRefAbsent(v.elem, fullKey);
|
|
402
|
+
if (existing === void 0 || elem === void 0) return void 0;
|
|
403
|
+
return { _kind: "append-placeholder", existing, elem };
|
|
404
|
+
}
|
|
405
|
+
if (isResObj(v)) {
|
|
406
|
+
const fields = /* @__PURE__ */ new Map();
|
|
407
|
+
for (const [key, value] of v.fields) {
|
|
408
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
409
|
+
if (folded === void 0) return void 0;
|
|
410
|
+
fields.set(key, folded);
|
|
411
|
+
}
|
|
412
|
+
return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues) };
|
|
413
|
+
}
|
|
414
|
+
const hv = v;
|
|
415
|
+
if (hv.kind === "array") {
|
|
416
|
+
const items = [];
|
|
417
|
+
for (const item of hv.items) {
|
|
418
|
+
const folded = foldOptionalSelfRefAbsent(item, fullKey);
|
|
419
|
+
if (folded === void 0) return void 0;
|
|
420
|
+
items.push(folded);
|
|
421
|
+
}
|
|
422
|
+
return { kind: "array", items };
|
|
423
|
+
}
|
|
424
|
+
if (hv.kind === "object") {
|
|
425
|
+
const fields = /* @__PURE__ */ new Map();
|
|
426
|
+
for (const [key, value] of hv.fields) {
|
|
427
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
428
|
+
if (folded === void 0) return void 0;
|
|
429
|
+
fields.set(key, folded);
|
|
430
|
+
}
|
|
431
|
+
return { kind: "object", fields };
|
|
432
|
+
}
|
|
433
|
+
return cloneResolverValue(v);
|
|
434
|
+
}
|
|
435
|
+
function cloneResolverValue(v) {
|
|
436
|
+
if (isSubst(v)) {
|
|
437
|
+
return {
|
|
438
|
+
_kind: "subst-placeholder",
|
|
439
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
440
|
+
optional: v.optional,
|
|
441
|
+
knownAbsent: v.knownAbsent,
|
|
442
|
+
listSuffix: v.listSuffix,
|
|
443
|
+
line: v.line,
|
|
444
|
+
col: v.col,
|
|
445
|
+
prefixLen: v.prefixLen
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (isConcat(v)) {
|
|
449
|
+
return {
|
|
450
|
+
_kind: "concat-placeholder",
|
|
451
|
+
nodes: v.nodes.map((n) => cloneResolverValue(n)),
|
|
452
|
+
line: v.line,
|
|
453
|
+
col: v.col
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (isResObj(v)) {
|
|
457
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
458
|
+
for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
|
|
459
|
+
const newPriors = /* @__PURE__ */ new Map();
|
|
460
|
+
for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
|
|
461
|
+
return { _kind: "res-obj", fields: newFields, priorValues: newPriors };
|
|
462
|
+
}
|
|
463
|
+
if (isAppend(v)) {
|
|
464
|
+
return {
|
|
465
|
+
_kind: "append-placeholder",
|
|
466
|
+
existing: cloneResolverValue(v.existing),
|
|
467
|
+
elem: cloneResolverValue(v.elem)
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
const hv = v;
|
|
471
|
+
if (hv.kind === "array") {
|
|
472
|
+
return {
|
|
473
|
+
kind: "array",
|
|
474
|
+
items: hv.items.map((item) => cloneResolverValue(item))
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
if (hv.kind === "object") {
|
|
478
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
479
|
+
for (const [k, val] of hv.fields) {
|
|
480
|
+
newFields.set(k, cloneResolverValue(val));
|
|
481
|
+
}
|
|
482
|
+
return { kind: "object", fields: newFields };
|
|
483
|
+
}
|
|
484
|
+
return hv;
|
|
485
|
+
}
|
|
486
|
+
function foldNestedSelfRefs(v, pathPrefix) {
|
|
487
|
+
if (!isResObj(v)) return v;
|
|
488
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
489
|
+
for (const [k, fieldVal] of v.fields) {
|
|
490
|
+
const childPath = [...pathPrefix, k];
|
|
491
|
+
const fullKey = stringSegmentsToKey(childPath);
|
|
492
|
+
const folded = foldNestedSelfRefs(fieldVal, childPath);
|
|
493
|
+
let finalVal = folded;
|
|
494
|
+
if (containsSelfRef(folded, fullKey)) {
|
|
495
|
+
const leafPrior = v.priorValues.get(k);
|
|
496
|
+
if (leafPrior !== void 0) {
|
|
497
|
+
const leafPriorFolded = foldNestedSelfRefs(leafPrior, childPath);
|
|
498
|
+
finalVal = foldSelfRef(folded, fullKey, leafPriorFolded);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
newFields.set(k, finalVal);
|
|
502
|
+
}
|
|
503
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
|
|
504
|
+
}
|
|
505
|
+
function containsSubstByPath(v, target) {
|
|
506
|
+
if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
|
|
507
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
|
|
508
|
+
if (isAppend(v)) {
|
|
509
|
+
return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
|
|
510
|
+
}
|
|
511
|
+
if (isResObj(v)) {
|
|
512
|
+
for (const f of v.fields.values()) {
|
|
513
|
+
if (containsSubstByPath(f, target)) return true;
|
|
514
|
+
}
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
const hv = v;
|
|
518
|
+
if (hv.kind === "array") {
|
|
519
|
+
return hv.items.some((item) => containsSubstByPath(item, target));
|
|
520
|
+
}
|
|
521
|
+
if (hv.kind === "object") {
|
|
522
|
+
for (const f of hv.fields.values()) {
|
|
523
|
+
if (containsSubstByPath(f, target)) return true;
|
|
524
|
+
}
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
function segmentsTextEqual(a, b) {
|
|
530
|
+
if (a.length !== b.length) return false;
|
|
531
|
+
for (let i = 0; i < a.length; i++) {
|
|
532
|
+
const ai = a[i];
|
|
533
|
+
const bi = b[i];
|
|
534
|
+
if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
|
|
535
|
+
}
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
|
|
293
539
|
// src/internal/resolver/utils.ts
|
|
294
540
|
function segmentsToKey(segments) {
|
|
295
541
|
return segments.map((s) => {
|
|
@@ -331,13 +577,22 @@ function deepMergeHoconValues(base, overlay) {
|
|
|
331
577
|
}
|
|
332
578
|
return { kind: "object", fields: merged };
|
|
333
579
|
}
|
|
334
|
-
function deepMergeResObjInto(dst, src) {
|
|
580
|
+
function deepMergeResObjInto(dst, src, pathPrefix = []) {
|
|
335
581
|
for (const [k, srcVal] of src.fields) {
|
|
582
|
+
const childPrefix = [...pathPrefix, k];
|
|
583
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
336
584
|
const dstVal = dst.fields.get(k);
|
|
337
585
|
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
338
|
-
|
|
586
|
+
const priorExisting = dst.priorValues.get(k);
|
|
587
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
588
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
589
|
+
deepMergeResObjInto(dstVal, srcVal, childPrefix);
|
|
339
590
|
} else {
|
|
340
|
-
if (dstVal !== void 0)
|
|
591
|
+
if (dstVal !== void 0) {
|
|
592
|
+
const priorExisting = dst.priorValues.get(k);
|
|
593
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
594
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
595
|
+
}
|
|
341
596
|
dst.fields.set(k, srcVal);
|
|
342
597
|
}
|
|
343
598
|
}
|
|
@@ -415,6 +670,11 @@ var Lexer = class {
|
|
|
415
670
|
this.line = 1;
|
|
416
671
|
this.col = 1;
|
|
417
672
|
this.hadSpace = false;
|
|
673
|
+
// Accumulates literal whitespace chars consumed between tokens. Reset on every
|
|
674
|
+
// token push. Used so the parser can preserve path-expression whitespace
|
|
675
|
+
// (xx.hocon#42, E13) — e.g. tab vs space adjacent to dots in key paths.
|
|
676
|
+
// Comment text is NOT accumulated; only the actual WS chars.
|
|
677
|
+
this.whitespaceBuffer = "";
|
|
418
678
|
this.tokens = [];
|
|
419
679
|
if (input.charCodeAt(0) === 65279) this.input = input.slice(1);
|
|
420
680
|
}
|
|
@@ -430,6 +690,7 @@ var Lexer = class {
|
|
|
430
690
|
continue;
|
|
431
691
|
}
|
|
432
692
|
if (isHoconWhitespace(ch)) {
|
|
693
|
+
this.whitespaceBuffer += ch;
|
|
433
694
|
this.advance();
|
|
434
695
|
this.hadSpace = true;
|
|
435
696
|
continue;
|
|
@@ -521,7 +782,7 @@ var Lexer = class {
|
|
|
521
782
|
}
|
|
522
783
|
throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
|
|
523
784
|
}
|
|
524
|
-
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false });
|
|
785
|
+
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false, precedingWhitespace: "" });
|
|
525
786
|
return this.tokens;
|
|
526
787
|
}
|
|
527
788
|
peek(offset = 0) {
|
|
@@ -538,12 +799,14 @@ var Lexer = class {
|
|
|
538
799
|
return ch;
|
|
539
800
|
}
|
|
540
801
|
push(kind, value, l, c, isQuoted = false) {
|
|
541
|
-
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
802
|
+
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer });
|
|
542
803
|
this.hadSpace = false;
|
|
804
|
+
this.whitespaceBuffer = "";
|
|
543
805
|
}
|
|
544
806
|
pushSubst(payload, value, l, c) {
|
|
545
|
-
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
|
|
807
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer, subst: payload });
|
|
546
808
|
this.hadSpace = false;
|
|
809
|
+
this.whitespaceBuffer = "";
|
|
547
810
|
}
|
|
548
811
|
/**
|
|
549
812
|
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
@@ -789,7 +1052,7 @@ function isUnquotedContinue(ch, nextFn) {
|
|
|
789
1052
|
}
|
|
790
1053
|
|
|
791
1054
|
// src/internal/parser/parser.ts
|
|
792
|
-
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
1055
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, precedingWhitespace: "", subst: void 0 };
|
|
793
1056
|
var Parser = class {
|
|
794
1057
|
constructor(tokens) {
|
|
795
1058
|
this.tokens = tokens;
|
|
@@ -902,13 +1165,17 @@ var Parser = class {
|
|
|
902
1165
|
let trailingDot = false;
|
|
903
1166
|
let firstWasQuoted = false;
|
|
904
1167
|
let spaceConcat = false;
|
|
1168
|
+
let postDotPrefix = "";
|
|
905
1169
|
while (true) {
|
|
906
1170
|
const t = this.peek();
|
|
907
1171
|
if (t.kind === "string") {
|
|
908
1172
|
this.advance();
|
|
909
1173
|
if (segments.length === 0) firstWasQuoted = true;
|
|
910
1174
|
if (spaceConcat) {
|
|
911
|
-
segments[segments.length - 1] = `${segments[segments.length - 1]}
|
|
1175
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}${t.value}`;
|
|
1176
|
+
} else if (postDotPrefix !== "") {
|
|
1177
|
+
segments.push(`${postDotPrefix}${t.value}`);
|
|
1178
|
+
postDotPrefix = "";
|
|
912
1179
|
} else {
|
|
913
1180
|
segments.push(t.value);
|
|
914
1181
|
}
|
|
@@ -918,24 +1185,23 @@ var Parser = class {
|
|
|
918
1185
|
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
919
1186
|
const parts = raw.split(".");
|
|
920
1187
|
const filtered = parts.filter((s) => s.length > 0);
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
|
|
924
|
-
throw new ParseError(
|
|
925
|
-
`unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
|
|
926
|
-
t.line,
|
|
927
|
-
t.col
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
if (spaceConcat && filtered.length > 0) {
|
|
1188
|
+
if (spaceConcat) {
|
|
1189
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
|
|
932
1190
|
if (raw.startsWith(".")) {
|
|
933
1191
|
segments.push(...filtered);
|
|
934
|
-
} else {
|
|
1192
|
+
} else if (filtered.length > 0) {
|
|
935
1193
|
const [head, ...tail] = filtered;
|
|
936
|
-
segments[segments.length - 1] = `${segments[segments.length - 1]}
|
|
1194
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${head}`;
|
|
937
1195
|
segments.push(...tail);
|
|
938
1196
|
}
|
|
1197
|
+
} else if (postDotPrefix !== "" && raw.startsWith(".")) {
|
|
1198
|
+
segments.push(postDotPrefix);
|
|
1199
|
+
postDotPrefix = "";
|
|
1200
|
+
segments.push(...filtered);
|
|
1201
|
+
} else if (postDotPrefix !== "" && filtered.length > 0) {
|
|
1202
|
+
const [head, ...tail] = filtered;
|
|
1203
|
+
segments.push(`${postDotPrefix}${head}`, ...tail);
|
|
1204
|
+
postDotPrefix = "";
|
|
939
1205
|
} else {
|
|
940
1206
|
segments.push(...filtered);
|
|
941
1207
|
}
|
|
@@ -945,11 +1211,23 @@ var Parser = class {
|
|
|
945
1211
|
break;
|
|
946
1212
|
}
|
|
947
1213
|
spaceConcat = false;
|
|
948
|
-
if (trailingDot)
|
|
1214
|
+
if (trailingDot) {
|
|
1215
|
+
const next2 = this.peek();
|
|
1216
|
+
if ((next2.kind === "unquoted" || next2.kind === "string") && next2.precedingWhitespace.length > 0) {
|
|
1217
|
+
postDotPrefix = next2.precedingWhitespace;
|
|
1218
|
+
} else {
|
|
1219
|
+
postDotPrefix = "";
|
|
1220
|
+
}
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
949
1223
|
const next = this.peek();
|
|
950
1224
|
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
951
1225
|
this.advance();
|
|
952
1226
|
trailingDot = true;
|
|
1227
|
+
const afterDot = this.peek();
|
|
1228
|
+
if ((afterDot.kind === "unquoted" || afterDot.kind === "string") && afterDot.precedingWhitespace.length > 0) {
|
|
1229
|
+
postDotPrefix = afterDot.precedingWhitespace;
|
|
1230
|
+
}
|
|
953
1231
|
continue;
|
|
954
1232
|
}
|
|
955
1233
|
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
@@ -962,6 +1240,14 @@ var Parser = class {
|
|
|
962
1240
|
}
|
|
963
1241
|
break;
|
|
964
1242
|
}
|
|
1243
|
+
if (trailingDot) {
|
|
1244
|
+
const here = this.peek();
|
|
1245
|
+
throw new ParseError(
|
|
1246
|
+
`path has a trailing period '.' \u2014 empty key segment not allowed (HOCON.md path rules)`,
|
|
1247
|
+
here.line,
|
|
1248
|
+
here.col
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
965
1251
|
return { segments, firstWasQuoted };
|
|
966
1252
|
}
|
|
967
1253
|
parseInclude() {
|
|
@@ -1134,7 +1420,8 @@ var Parser = class {
|
|
|
1134
1420
|
break;
|
|
1135
1421
|
}
|
|
1136
1422
|
if (hadSpace) {
|
|
1137
|
-
|
|
1423
|
+
const sep = t.precedingWhitespace.length > 0 ? t.precedingWhitespace : " ";
|
|
1424
|
+
parts.push({ kind: "scalar", raw: sep, valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
|
|
1138
1425
|
}
|
|
1139
1426
|
parts.push(node);
|
|
1140
1427
|
}
|
|
@@ -1347,7 +1634,7 @@ var IncludeLoader = class {
|
|
|
1347
1634
|
for (const ext of probeExts) {
|
|
1348
1635
|
const obj = this.loadSingle(`${absPath}${ext}`);
|
|
1349
1636
|
if (obj !== void 0) {
|
|
1350
|
-
deepMergeResObjInto(merged, obj);
|
|
1637
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1351
1638
|
foundAny = true;
|
|
1352
1639
|
}
|
|
1353
1640
|
}
|
|
@@ -1380,7 +1667,7 @@ var IncludeLoader = class {
|
|
|
1380
1667
|
for (const ext of probeExts) {
|
|
1381
1668
|
const obj = await this.loadSingleAsync(`${absPath}${ext}`);
|
|
1382
1669
|
if (obj !== void 0) {
|
|
1383
|
-
deepMergeResObjInto(merged, obj);
|
|
1670
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1384
1671
|
foundAny = true;
|
|
1385
1672
|
}
|
|
1386
1673
|
}
|
|
@@ -1552,7 +1839,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1552
1839
|
if (pathPrefix.length > 0) {
|
|
1553
1840
|
this.relativizeResObj(included, pathPrefix);
|
|
1554
1841
|
}
|
|
1555
|
-
deepMergeResObjInto(obj, included);
|
|
1842
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1556
1843
|
return;
|
|
1557
1844
|
}
|
|
1558
1845
|
const [head, ...tail] = field.key;
|
|
@@ -1567,12 +1854,27 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1567
1854
|
return;
|
|
1568
1855
|
}
|
|
1569
1856
|
const childPrefix = [...pathPrefix, head];
|
|
1857
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1570
1858
|
if (field.append) {
|
|
1571
1859
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1572
|
-
obj.priorValues.
|
|
1860
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1861
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1862
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1573
1863
|
obj.fields.set(head, {
|
|
1574
1864
|
_kind: "append-placeholder",
|
|
1575
|
-
existing
|
|
1865
|
+
// Use the folded existing (self-ref-free) for the Append. When the
|
|
1866
|
+
// fold returned undefined (self-ref present but no old prior),
|
|
1867
|
+
// fall back to the original existing — resolveSubst will then
|
|
1868
|
+
// raise the "self-ref with no prior value" error at resolve time,
|
|
1869
|
+
// matching the pre-fix behaviour for that edge case.
|
|
1870
|
+
//
|
|
1871
|
+
// Clone `folded` again so the Append.existing and priorValues[head]
|
|
1872
|
+
// do not share the same Subst/Concat references — relativizeResObj
|
|
1873
|
+
// walks BOTH `fields` and `priorValues` and mutates Subst.segments
|
|
1874
|
+
// in place, so a shared reference would get its prefix applied
|
|
1875
|
+
// twice (resulting in e.g. `${outer.outer.base}` instead of
|
|
1876
|
+
// `${outer.base}`). Codex review on PR #131.
|
|
1877
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1576
1878
|
elem: this.astToResolverValue(field.value, childPrefix)
|
|
1577
1879
|
});
|
|
1578
1880
|
return;
|
|
@@ -1580,10 +1882,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1580
1882
|
const existing = obj.fields.get(head);
|
|
1581
1883
|
const newVal = this.astToResolverValue(field.value, childPrefix);
|
|
1582
1884
|
if (existing !== void 0) {
|
|
1583
|
-
obj.priorValues.
|
|
1885
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1886
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
1887
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
1888
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1584
1889
|
}
|
|
1585
1890
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1586
|
-
deepMergeResObjInto(existing, newVal);
|
|
1891
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1587
1892
|
return;
|
|
1588
1893
|
}
|
|
1589
1894
|
obj.fields.set(head, newVal);
|
|
@@ -1600,7 +1905,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1600
1905
|
if (pathPrefix.length > 0) {
|
|
1601
1906
|
this.relativizeResObj(included, pathPrefix);
|
|
1602
1907
|
}
|
|
1603
|
-
deepMergeResObjInto(obj, included);
|
|
1908
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1604
1909
|
return;
|
|
1605
1910
|
}
|
|
1606
1911
|
const [head, ...tail] = field.key;
|
|
@@ -1615,12 +1920,15 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1615
1920
|
return;
|
|
1616
1921
|
}
|
|
1617
1922
|
const childPrefix = [...pathPrefix, head];
|
|
1923
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1618
1924
|
if (field.append) {
|
|
1619
1925
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1620
|
-
obj.priorValues.
|
|
1926
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1927
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1928
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1621
1929
|
obj.fields.set(head, {
|
|
1622
1930
|
_kind: "append-placeholder",
|
|
1623
|
-
existing: existing2,
|
|
1931
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1624
1932
|
elem: await this.astToResolverValueAsync(field.value, childPrefix)
|
|
1625
1933
|
});
|
|
1626
1934
|
return;
|
|
@@ -1628,10 +1936,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1628
1936
|
const existing = obj.fields.get(head);
|
|
1629
1937
|
const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
|
|
1630
1938
|
if (existing !== void 0) {
|
|
1631
|
-
obj.priorValues.
|
|
1939
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1940
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
1941
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
1942
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1632
1943
|
}
|
|
1633
1944
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1634
|
-
deepMergeResObjInto(existing, newVal);
|
|
1945
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1635
1946
|
return;
|
|
1636
1947
|
}
|
|
1637
1948
|
obj.fields.set(head, newVal);
|
|
@@ -1650,7 +1961,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1650
1961
|
return inner;
|
|
1651
1962
|
}
|
|
1652
1963
|
case "subst":
|
|
1653
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1964
|
+
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 };
|
|
1654
1965
|
case "concat":
|
|
1655
1966
|
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
|
|
1656
1967
|
case "include":
|
|
@@ -1674,7 +1985,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1674
1985
|
case "object":
|
|
1675
1986
|
return await this.buildAsync(ast, pathPrefix);
|
|
1676
1987
|
case "subst":
|
|
1677
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1988
|
+
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 };
|
|
1678
1989
|
case "concat": {
|
|
1679
1990
|
const nodes = [];
|
|
1680
1991
|
for (const n of ast.nodes) {
|
|
@@ -1763,6 +2074,23 @@ var SubstitutionResolver = class {
|
|
|
1763
2074
|
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
1764
2075
|
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
1765
2076
|
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
2077
|
+
// Path stack tracking the full dotted path of the field currently being
|
|
2078
|
+
// assigned in `resolveResObj`. Leaf keys are pushed on entry to each
|
|
2079
|
+
// `(key, val)` iteration and popped on exit, so nested objects build up
|
|
2080
|
+
// the full path (e.g. `["o", "history"]` while resolving `o.history`).
|
|
2081
|
+
//
|
|
2082
|
+
// Used to tighten self-ref detection at the L192 found-lookup site:
|
|
2083
|
+
// a substitution `${X}` whose looked-up value contains a Subst pointing
|
|
2084
|
+
// at `X` is only a true self-reference when the field currently being
|
|
2085
|
+
// assigned is `X` (or a descendant of `X`). Without this guard, an
|
|
2086
|
+
// external lookup like `b = ${o}` (where o has a self-referential
|
|
2087
|
+
// field) would mis-fire and short-circuit to o's prior, returning the
|
|
2088
|
+
// wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
|
|
2089
|
+
// narrows the check to the owner case only.
|
|
2090
|
+
//
|
|
2091
|
+
// Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
|
|
2092
|
+
// same prefix-match semantics).
|
|
2093
|
+
this.resolvingFieldPath = [];
|
|
1766
2094
|
}
|
|
1767
2095
|
/** Returns a prefix for error messages, including origin info when set. */
|
|
1768
2096
|
originPrefix() {
|
|
@@ -1774,35 +2102,57 @@ var SubstitutionResolver = class {
|
|
|
1774
2102
|
resolveResObj(obj) {
|
|
1775
2103
|
const result = /* @__PURE__ */ new Map();
|
|
1776
2104
|
for (const [key, val] of obj.fields) {
|
|
1777
|
-
|
|
2105
|
+
this.resolvingFieldPath.push(key);
|
|
2106
|
+
const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
|
|
2107
|
+
this.cache.delete(fullCacheKey);
|
|
2108
|
+
let resolved;
|
|
2109
|
+
try {
|
|
2110
|
+
resolved = this.resolveVal(val, obj);
|
|
2111
|
+
} finally {
|
|
2112
|
+
this.resolvingFieldPath.pop();
|
|
2113
|
+
}
|
|
1778
2114
|
if (resolved !== void 0) {
|
|
1779
2115
|
if (resolved.kind === "object") {
|
|
1780
2116
|
const prior = obj.priorValues.get(key);
|
|
1781
2117
|
if (prior !== void 0) {
|
|
1782
2118
|
const priorResolved = this.resolveVal(prior, obj);
|
|
1783
2119
|
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
priorResolved,
|
|
1788
|
-
resolved
|
|
1789
|
-
)
|
|
2120
|
+
const finalValue = deepMergeHoconValues(
|
|
2121
|
+
priorResolved,
|
|
2122
|
+
resolved
|
|
1790
2123
|
);
|
|
2124
|
+
result.set(key, finalValue);
|
|
2125
|
+
this.cache.set(fullCacheKey, finalValue);
|
|
2126
|
+
this.cacheDescendants(fullCacheKey, finalValue);
|
|
1791
2127
|
continue;
|
|
1792
2128
|
}
|
|
1793
2129
|
}
|
|
1794
2130
|
}
|
|
1795
2131
|
result.set(key, resolved);
|
|
2132
|
+
this.cache.set(fullCacheKey, resolved);
|
|
2133
|
+
this.cacheDescendants(fullCacheKey, resolved);
|
|
1796
2134
|
} else {
|
|
1797
2135
|
const prior = obj.priorValues.get(key);
|
|
1798
2136
|
if (prior !== void 0) {
|
|
1799
2137
|
const priorResolved = this.resolveVal(prior, obj);
|
|
1800
|
-
if (priorResolved !== void 0)
|
|
2138
|
+
if (priorResolved !== void 0) {
|
|
2139
|
+
result.set(key, priorResolved);
|
|
2140
|
+
this.cache.set(fullCacheKey, priorResolved);
|
|
2141
|
+
this.cacheDescendants(fullCacheKey, priorResolved);
|
|
2142
|
+
}
|
|
1801
2143
|
}
|
|
1802
2144
|
}
|
|
1803
2145
|
}
|
|
1804
2146
|
return { kind: "object", fields: result };
|
|
1805
2147
|
}
|
|
2148
|
+
cacheDescendants(prefix, value) {
|
|
2149
|
+
if (value.kind !== "object") return;
|
|
2150
|
+
for (const [key, child] of value.fields) {
|
|
2151
|
+
const childKey = `${prefix}.${stringSegmentsToKey([key])}`;
|
|
2152
|
+
this.cache.set(childKey, child);
|
|
2153
|
+
this.cacheDescendants(childKey, child);
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
1806
2156
|
resolveVal(v, scope) {
|
|
1807
2157
|
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
1808
2158
|
if (isConcat(v)) {
|
|
@@ -1828,6 +2178,7 @@ var SubstitutionResolver = class {
|
|
|
1828
2178
|
return hv;
|
|
1829
2179
|
}
|
|
1830
2180
|
resolveSubst(s, scope) {
|
|
2181
|
+
if (s.knownAbsent) return void 0;
|
|
1831
2182
|
const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
1832
2183
|
if (this.cache.has(key)) return this.cache.get(key);
|
|
1833
2184
|
if (this.resolving.has(key)) {
|
|
@@ -1864,7 +2215,10 @@ var SubstitutionResolver = class {
|
|
|
1864
2215
|
try {
|
|
1865
2216
|
const found = lookupPath(this.root, s.segments);
|
|
1866
2217
|
if (found !== void 0) {
|
|
1867
|
-
|
|
2218
|
+
const rfp = this.resolvingFieldPath;
|
|
2219
|
+
const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
|
|
2220
|
+
const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
|
|
2221
|
+
if (isSelfRef) {
|
|
1868
2222
|
let prior;
|
|
1869
2223
|
if (s.segments.length > 1) {
|
|
1870
2224
|
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
@@ -1917,6 +2271,28 @@ var SubstitutionResolver = class {
|
|
|
1917
2271
|
if (result !== void 0) this.cache.set(key, result);
|
|
1918
2272
|
return result;
|
|
1919
2273
|
}
|
|
2274
|
+
if (s.prefixLen > 0 && s.segments.length > s.prefixLen) {
|
|
2275
|
+
const originalSegments = s.segments.slice(s.prefixLen);
|
|
2276
|
+
const fallbackFound = lookupPath(this.root, originalSegments);
|
|
2277
|
+
if (fallbackFound !== void 0) {
|
|
2278
|
+
let result = this.resolveVal(fallbackFound, scope);
|
|
2279
|
+
if (originalSegments.length === 1 && result !== void 0 && result.kind === "object") {
|
|
2280
|
+
const rootSeg = originalSegments[0]?.text ?? "";
|
|
2281
|
+
const prior = this.root.priorValues.get(rootSeg);
|
|
2282
|
+
if (prior !== void 0) {
|
|
2283
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
2284
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
2285
|
+
result = deepMergeHoconValues(
|
|
2286
|
+
priorResolved,
|
|
2287
|
+
result
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
2293
|
+
return result;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
1920
2296
|
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
1921
2297
|
const result = this.resolveEnvList(s);
|
|
1922
2298
|
if (result !== void 0) {
|