@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.js
CHANGED
|
@@ -286,6 +286,252 @@ function mergeUnresolved(receiver, fallback) {
|
|
|
286
286
|
return result;
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
// src/internal/resolver/fold-self-ref.ts
|
|
290
|
+
function substFullKey(s) {
|
|
291
|
+
return stringSegmentsToKey(s.segments.map((seg) => seg.text));
|
|
292
|
+
}
|
|
293
|
+
function stringSegmentsToKey(segments) {
|
|
294
|
+
return segments.map((t) => {
|
|
295
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
296
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
297
|
+
}
|
|
298
|
+
return t;
|
|
299
|
+
}).join(".");
|
|
300
|
+
}
|
|
301
|
+
function containsSelfRef(v, fullKey) {
|
|
302
|
+
if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
|
|
303
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
|
|
304
|
+
if (isAppend(v)) {
|
|
305
|
+
return containsSelfRef(v.existing, fullKey) || containsSelfRef(v.elem, fullKey);
|
|
306
|
+
}
|
|
307
|
+
if (isResObj(v)) {
|
|
308
|
+
for (const f of v.fields.values()) {
|
|
309
|
+
if (containsSelfRef(f, fullKey)) return true;
|
|
310
|
+
}
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
const hv = v;
|
|
314
|
+
if (hv.kind === "array") {
|
|
315
|
+
return hv.items.some((item) => containsSelfRef(item, fullKey));
|
|
316
|
+
}
|
|
317
|
+
if (hv.kind === "object") {
|
|
318
|
+
for (const f of hv.fields.values()) {
|
|
319
|
+
if (containsSelfRef(f, fullKey)) return true;
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
function foldSelfRef(v, fullKey, replacement) {
|
|
326
|
+
if (isSubst(v)) {
|
|
327
|
+
return substFullKey(v) === fullKey ? replacement : v;
|
|
328
|
+
}
|
|
329
|
+
if (isConcat(v)) {
|
|
330
|
+
return {
|
|
331
|
+
_kind: "concat-placeholder",
|
|
332
|
+
nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
|
|
333
|
+
line: v.line,
|
|
334
|
+
col: v.col
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (isAppend(v)) {
|
|
338
|
+
return {
|
|
339
|
+
_kind: "append-placeholder",
|
|
340
|
+
existing: foldSelfRef(v.existing, fullKey, replacement),
|
|
341
|
+
elem: foldSelfRef(v.elem, fullKey, replacement)
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (isResObj(v)) {
|
|
345
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
346
|
+
for (const [k, val] of v.fields) {
|
|
347
|
+
newFields.set(k, foldSelfRef(val, fullKey, replacement));
|
|
348
|
+
}
|
|
349
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
|
|
350
|
+
}
|
|
351
|
+
const hv = v;
|
|
352
|
+
if (hv.kind === "array") {
|
|
353
|
+
return {
|
|
354
|
+
kind: "array",
|
|
355
|
+
items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
if (hv.kind === "object") {
|
|
359
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const [k, val] of hv.fields) {
|
|
361
|
+
newFields.set(k, foldSelfRef(val, fullKey, replacement));
|
|
362
|
+
}
|
|
363
|
+
return { kind: "object", fields: newFields };
|
|
364
|
+
}
|
|
365
|
+
return v;
|
|
366
|
+
}
|
|
367
|
+
function foldOrSkipPrior(prior, fullKey, old) {
|
|
368
|
+
if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
|
|
369
|
+
if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
|
|
370
|
+
return foldSelfRef(prior, fullKey, old);
|
|
371
|
+
}
|
|
372
|
+
function foldOptionalSelfRefAbsent(v, fullKey) {
|
|
373
|
+
if (isSubst(v) && substFullKey(v) === fullKey) {
|
|
374
|
+
if (!v.optional) return void 0;
|
|
375
|
+
return {
|
|
376
|
+
_kind: "subst-placeholder",
|
|
377
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
378
|
+
optional: v.optional,
|
|
379
|
+
knownAbsent: true,
|
|
380
|
+
listSuffix: v.listSuffix,
|
|
381
|
+
line: v.line,
|
|
382
|
+
col: v.col,
|
|
383
|
+
prefixLen: v.prefixLen
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (isConcat(v)) {
|
|
387
|
+
const nodes = [];
|
|
388
|
+
for (const node of v.nodes) {
|
|
389
|
+
const folded = foldOptionalSelfRefAbsent(node, fullKey);
|
|
390
|
+
if (folded === void 0) return void 0;
|
|
391
|
+
nodes.push(folded);
|
|
392
|
+
}
|
|
393
|
+
return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
|
|
394
|
+
}
|
|
395
|
+
if (isAppend(v)) {
|
|
396
|
+
const existing = foldOptionalSelfRefAbsent(v.existing, fullKey);
|
|
397
|
+
const elem = foldOptionalSelfRefAbsent(v.elem, fullKey);
|
|
398
|
+
if (existing === void 0 || elem === void 0) return void 0;
|
|
399
|
+
return { _kind: "append-placeholder", existing, elem };
|
|
400
|
+
}
|
|
401
|
+
if (isResObj(v)) {
|
|
402
|
+
const fields = /* @__PURE__ */ new Map();
|
|
403
|
+
for (const [key, value] of v.fields) {
|
|
404
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
405
|
+
if (folded === void 0) return void 0;
|
|
406
|
+
fields.set(key, folded);
|
|
407
|
+
}
|
|
408
|
+
return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues) };
|
|
409
|
+
}
|
|
410
|
+
const hv = v;
|
|
411
|
+
if (hv.kind === "array") {
|
|
412
|
+
const items = [];
|
|
413
|
+
for (const item of hv.items) {
|
|
414
|
+
const folded = foldOptionalSelfRefAbsent(item, fullKey);
|
|
415
|
+
if (folded === void 0) return void 0;
|
|
416
|
+
items.push(folded);
|
|
417
|
+
}
|
|
418
|
+
return { kind: "array", items };
|
|
419
|
+
}
|
|
420
|
+
if (hv.kind === "object") {
|
|
421
|
+
const fields = /* @__PURE__ */ new Map();
|
|
422
|
+
for (const [key, value] of hv.fields) {
|
|
423
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
424
|
+
if (folded === void 0) return void 0;
|
|
425
|
+
fields.set(key, folded);
|
|
426
|
+
}
|
|
427
|
+
return { kind: "object", fields };
|
|
428
|
+
}
|
|
429
|
+
return cloneResolverValue(v);
|
|
430
|
+
}
|
|
431
|
+
function cloneResolverValue(v) {
|
|
432
|
+
if (isSubst(v)) {
|
|
433
|
+
return {
|
|
434
|
+
_kind: "subst-placeholder",
|
|
435
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
436
|
+
optional: v.optional,
|
|
437
|
+
knownAbsent: v.knownAbsent,
|
|
438
|
+
listSuffix: v.listSuffix,
|
|
439
|
+
line: v.line,
|
|
440
|
+
col: v.col,
|
|
441
|
+
prefixLen: v.prefixLen
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (isConcat(v)) {
|
|
445
|
+
return {
|
|
446
|
+
_kind: "concat-placeholder",
|
|
447
|
+
nodes: v.nodes.map((n) => cloneResolverValue(n)),
|
|
448
|
+
line: v.line,
|
|
449
|
+
col: v.col
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
if (isResObj(v)) {
|
|
453
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
454
|
+
for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
|
|
455
|
+
const newPriors = /* @__PURE__ */ new Map();
|
|
456
|
+
for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
|
|
457
|
+
return { _kind: "res-obj", fields: newFields, priorValues: newPriors };
|
|
458
|
+
}
|
|
459
|
+
if (isAppend(v)) {
|
|
460
|
+
return {
|
|
461
|
+
_kind: "append-placeholder",
|
|
462
|
+
existing: cloneResolverValue(v.existing),
|
|
463
|
+
elem: cloneResolverValue(v.elem)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
const hv = v;
|
|
467
|
+
if (hv.kind === "array") {
|
|
468
|
+
return {
|
|
469
|
+
kind: "array",
|
|
470
|
+
items: hv.items.map((item) => cloneResolverValue(item))
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
if (hv.kind === "object") {
|
|
474
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
475
|
+
for (const [k, val] of hv.fields) {
|
|
476
|
+
newFields.set(k, cloneResolverValue(val));
|
|
477
|
+
}
|
|
478
|
+
return { kind: "object", fields: newFields };
|
|
479
|
+
}
|
|
480
|
+
return hv;
|
|
481
|
+
}
|
|
482
|
+
function foldNestedSelfRefs(v, pathPrefix) {
|
|
483
|
+
if (!isResObj(v)) return v;
|
|
484
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
485
|
+
for (const [k, fieldVal] of v.fields) {
|
|
486
|
+
const childPath = [...pathPrefix, k];
|
|
487
|
+
const fullKey = stringSegmentsToKey(childPath);
|
|
488
|
+
const folded = foldNestedSelfRefs(fieldVal, childPath);
|
|
489
|
+
let finalVal = folded;
|
|
490
|
+
if (containsSelfRef(folded, fullKey)) {
|
|
491
|
+
const leafPrior = v.priorValues.get(k);
|
|
492
|
+
if (leafPrior !== void 0) {
|
|
493
|
+
const leafPriorFolded = foldNestedSelfRefs(leafPrior, childPath);
|
|
494
|
+
finalVal = foldSelfRef(folded, fullKey, leafPriorFolded);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
newFields.set(k, finalVal);
|
|
498
|
+
}
|
|
499
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
|
|
500
|
+
}
|
|
501
|
+
function containsSubstByPath(v, target) {
|
|
502
|
+
if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
|
|
503
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
|
|
504
|
+
if (isAppend(v)) {
|
|
505
|
+
return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
|
|
506
|
+
}
|
|
507
|
+
if (isResObj(v)) {
|
|
508
|
+
for (const f of v.fields.values()) {
|
|
509
|
+
if (containsSubstByPath(f, target)) return true;
|
|
510
|
+
}
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
const hv = v;
|
|
514
|
+
if (hv.kind === "array") {
|
|
515
|
+
return hv.items.some((item) => containsSubstByPath(item, target));
|
|
516
|
+
}
|
|
517
|
+
if (hv.kind === "object") {
|
|
518
|
+
for (const f of hv.fields.values()) {
|
|
519
|
+
if (containsSubstByPath(f, target)) return true;
|
|
520
|
+
}
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
function segmentsTextEqual(a, b) {
|
|
526
|
+
if (a.length !== b.length) return false;
|
|
527
|
+
for (let i = 0; i < a.length; i++) {
|
|
528
|
+
const ai = a[i];
|
|
529
|
+
const bi = b[i];
|
|
530
|
+
if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
|
|
531
|
+
}
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
|
|
289
535
|
// src/internal/resolver/utils.ts
|
|
290
536
|
function segmentsToKey(segments) {
|
|
291
537
|
return segments.map((s) => {
|
|
@@ -327,13 +573,22 @@ function deepMergeHoconValues(base, overlay) {
|
|
|
327
573
|
}
|
|
328
574
|
return { kind: "object", fields: merged };
|
|
329
575
|
}
|
|
330
|
-
function deepMergeResObjInto(dst, src) {
|
|
576
|
+
function deepMergeResObjInto(dst, src, pathPrefix = []) {
|
|
331
577
|
for (const [k, srcVal] of src.fields) {
|
|
578
|
+
const childPrefix = [...pathPrefix, k];
|
|
579
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
332
580
|
const dstVal = dst.fields.get(k);
|
|
333
581
|
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
334
|
-
|
|
582
|
+
const priorExisting = dst.priorValues.get(k);
|
|
583
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
584
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
585
|
+
deepMergeResObjInto(dstVal, srcVal, childPrefix);
|
|
335
586
|
} else {
|
|
336
|
-
if (dstVal !== void 0)
|
|
587
|
+
if (dstVal !== void 0) {
|
|
588
|
+
const priorExisting = dst.priorValues.get(k);
|
|
589
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
590
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
591
|
+
}
|
|
337
592
|
dst.fields.set(k, srcVal);
|
|
338
593
|
}
|
|
339
594
|
}
|
|
@@ -411,6 +666,11 @@ var Lexer = class {
|
|
|
411
666
|
this.line = 1;
|
|
412
667
|
this.col = 1;
|
|
413
668
|
this.hadSpace = false;
|
|
669
|
+
// Accumulates literal whitespace chars consumed between tokens. Reset on every
|
|
670
|
+
// token push. Used so the parser can preserve path-expression whitespace
|
|
671
|
+
// (xx.hocon#42, E13) — e.g. tab vs space adjacent to dots in key paths.
|
|
672
|
+
// Comment text is NOT accumulated; only the actual WS chars.
|
|
673
|
+
this.whitespaceBuffer = "";
|
|
414
674
|
this.tokens = [];
|
|
415
675
|
if (input.charCodeAt(0) === 65279) this.input = input.slice(1);
|
|
416
676
|
}
|
|
@@ -426,6 +686,7 @@ var Lexer = class {
|
|
|
426
686
|
continue;
|
|
427
687
|
}
|
|
428
688
|
if (isHoconWhitespace(ch)) {
|
|
689
|
+
this.whitespaceBuffer += ch;
|
|
429
690
|
this.advance();
|
|
430
691
|
this.hadSpace = true;
|
|
431
692
|
continue;
|
|
@@ -517,7 +778,7 @@ var Lexer = class {
|
|
|
517
778
|
}
|
|
518
779
|
throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
|
|
519
780
|
}
|
|
520
|
-
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false });
|
|
781
|
+
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false, precedingWhitespace: "" });
|
|
521
782
|
return this.tokens;
|
|
522
783
|
}
|
|
523
784
|
peek(offset = 0) {
|
|
@@ -534,12 +795,14 @@ var Lexer = class {
|
|
|
534
795
|
return ch;
|
|
535
796
|
}
|
|
536
797
|
push(kind, value, l, c, isQuoted = false) {
|
|
537
|
-
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
798
|
+
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer });
|
|
538
799
|
this.hadSpace = false;
|
|
800
|
+
this.whitespaceBuffer = "";
|
|
539
801
|
}
|
|
540
802
|
pushSubst(payload, value, l, c) {
|
|
541
|
-
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
|
|
803
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer, subst: payload });
|
|
542
804
|
this.hadSpace = false;
|
|
805
|
+
this.whitespaceBuffer = "";
|
|
543
806
|
}
|
|
544
807
|
/**
|
|
545
808
|
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
@@ -785,7 +1048,7 @@ function isUnquotedContinue(ch, nextFn) {
|
|
|
785
1048
|
}
|
|
786
1049
|
|
|
787
1050
|
// src/internal/parser/parser.ts
|
|
788
|
-
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
1051
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, precedingWhitespace: "", subst: void 0 };
|
|
789
1052
|
var Parser = class {
|
|
790
1053
|
constructor(tokens) {
|
|
791
1054
|
this.tokens = tokens;
|
|
@@ -898,13 +1161,17 @@ var Parser = class {
|
|
|
898
1161
|
let trailingDot = false;
|
|
899
1162
|
let firstWasQuoted = false;
|
|
900
1163
|
let spaceConcat = false;
|
|
1164
|
+
let postDotPrefix = "";
|
|
901
1165
|
while (true) {
|
|
902
1166
|
const t = this.peek();
|
|
903
1167
|
if (t.kind === "string") {
|
|
904
1168
|
this.advance();
|
|
905
1169
|
if (segments.length === 0) firstWasQuoted = true;
|
|
906
1170
|
if (spaceConcat) {
|
|
907
|
-
segments[segments.length - 1] = `${segments[segments.length - 1]}
|
|
1171
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}${t.value}`;
|
|
1172
|
+
} else if (postDotPrefix !== "") {
|
|
1173
|
+
segments.push(`${postDotPrefix}${t.value}`);
|
|
1174
|
+
postDotPrefix = "";
|
|
908
1175
|
} else {
|
|
909
1176
|
segments.push(t.value);
|
|
910
1177
|
}
|
|
@@ -914,24 +1181,23 @@ var Parser = class {
|
|
|
914
1181
|
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
915
1182
|
const parts = raw.split(".");
|
|
916
1183
|
const filtered = parts.filter((s) => s.length > 0);
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
|
|
920
|
-
throw new ParseError(
|
|
921
|
-
`unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
|
|
922
|
-
t.line,
|
|
923
|
-
t.col
|
|
924
|
-
);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
if (spaceConcat && filtered.length > 0) {
|
|
1184
|
+
if (spaceConcat) {
|
|
1185
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
|
|
928
1186
|
if (raw.startsWith(".")) {
|
|
929
1187
|
segments.push(...filtered);
|
|
930
|
-
} else {
|
|
1188
|
+
} else if (filtered.length > 0) {
|
|
931
1189
|
const [head, ...tail] = filtered;
|
|
932
|
-
segments[segments.length - 1] = `${segments[segments.length - 1]}
|
|
1190
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${head}`;
|
|
933
1191
|
segments.push(...tail);
|
|
934
1192
|
}
|
|
1193
|
+
} else if (postDotPrefix !== "" && raw.startsWith(".")) {
|
|
1194
|
+
segments.push(postDotPrefix);
|
|
1195
|
+
postDotPrefix = "";
|
|
1196
|
+
segments.push(...filtered);
|
|
1197
|
+
} else if (postDotPrefix !== "" && filtered.length > 0) {
|
|
1198
|
+
const [head, ...tail] = filtered;
|
|
1199
|
+
segments.push(`${postDotPrefix}${head}`, ...tail);
|
|
1200
|
+
postDotPrefix = "";
|
|
935
1201
|
} else {
|
|
936
1202
|
segments.push(...filtered);
|
|
937
1203
|
}
|
|
@@ -941,11 +1207,23 @@ var Parser = class {
|
|
|
941
1207
|
break;
|
|
942
1208
|
}
|
|
943
1209
|
spaceConcat = false;
|
|
944
|
-
if (trailingDot)
|
|
1210
|
+
if (trailingDot) {
|
|
1211
|
+
const next2 = this.peek();
|
|
1212
|
+
if ((next2.kind === "unquoted" || next2.kind === "string") && next2.precedingWhitespace.length > 0) {
|
|
1213
|
+
postDotPrefix = next2.precedingWhitespace;
|
|
1214
|
+
} else {
|
|
1215
|
+
postDotPrefix = "";
|
|
1216
|
+
}
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
945
1219
|
const next = this.peek();
|
|
946
1220
|
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
947
1221
|
this.advance();
|
|
948
1222
|
trailingDot = true;
|
|
1223
|
+
const afterDot = this.peek();
|
|
1224
|
+
if ((afterDot.kind === "unquoted" || afterDot.kind === "string") && afterDot.precedingWhitespace.length > 0) {
|
|
1225
|
+
postDotPrefix = afterDot.precedingWhitespace;
|
|
1226
|
+
}
|
|
949
1227
|
continue;
|
|
950
1228
|
}
|
|
951
1229
|
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
@@ -958,6 +1236,14 @@ var Parser = class {
|
|
|
958
1236
|
}
|
|
959
1237
|
break;
|
|
960
1238
|
}
|
|
1239
|
+
if (trailingDot) {
|
|
1240
|
+
const here = this.peek();
|
|
1241
|
+
throw new ParseError(
|
|
1242
|
+
`path has a trailing period '.' \u2014 empty key segment not allowed (HOCON.md path rules)`,
|
|
1243
|
+
here.line,
|
|
1244
|
+
here.col
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
961
1247
|
return { segments, firstWasQuoted };
|
|
962
1248
|
}
|
|
963
1249
|
parseInclude() {
|
|
@@ -1130,7 +1416,8 @@ var Parser = class {
|
|
|
1130
1416
|
break;
|
|
1131
1417
|
}
|
|
1132
1418
|
if (hadSpace) {
|
|
1133
|
-
|
|
1419
|
+
const sep = t.precedingWhitespace.length > 0 ? t.precedingWhitespace : " ";
|
|
1420
|
+
parts.push({ kind: "scalar", raw: sep, valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
|
|
1134
1421
|
}
|
|
1135
1422
|
parts.push(node);
|
|
1136
1423
|
}
|
|
@@ -1343,7 +1630,7 @@ var IncludeLoader = class {
|
|
|
1343
1630
|
for (const ext of probeExts) {
|
|
1344
1631
|
const obj = this.loadSingle(`${absPath}${ext}`);
|
|
1345
1632
|
if (obj !== void 0) {
|
|
1346
|
-
deepMergeResObjInto(merged, obj);
|
|
1633
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1347
1634
|
foundAny = true;
|
|
1348
1635
|
}
|
|
1349
1636
|
}
|
|
@@ -1376,7 +1663,7 @@ var IncludeLoader = class {
|
|
|
1376
1663
|
for (const ext of probeExts) {
|
|
1377
1664
|
const obj = await this.loadSingleAsync(`${absPath}${ext}`);
|
|
1378
1665
|
if (obj !== void 0) {
|
|
1379
|
-
deepMergeResObjInto(merged, obj);
|
|
1666
|
+
deepMergeResObjInto(merged, obj, []);
|
|
1380
1667
|
foundAny = true;
|
|
1381
1668
|
}
|
|
1382
1669
|
}
|
|
@@ -1548,7 +1835,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1548
1835
|
if (pathPrefix.length > 0) {
|
|
1549
1836
|
this.relativizeResObj(included, pathPrefix);
|
|
1550
1837
|
}
|
|
1551
|
-
deepMergeResObjInto(obj, included);
|
|
1838
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1552
1839
|
return;
|
|
1553
1840
|
}
|
|
1554
1841
|
const [head, ...tail] = field.key;
|
|
@@ -1563,12 +1850,27 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1563
1850
|
return;
|
|
1564
1851
|
}
|
|
1565
1852
|
const childPrefix = [...pathPrefix, head];
|
|
1853
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1566
1854
|
if (field.append) {
|
|
1567
1855
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1568
|
-
obj.priorValues.
|
|
1856
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1857
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1858
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1569
1859
|
obj.fields.set(head, {
|
|
1570
1860
|
_kind: "append-placeholder",
|
|
1571
|
-
existing
|
|
1861
|
+
// Use the folded existing (self-ref-free) for the Append. When the
|
|
1862
|
+
// fold returned undefined (self-ref present but no old prior),
|
|
1863
|
+
// fall back to the original existing — resolveSubst will then
|
|
1864
|
+
// raise the "self-ref with no prior value" error at resolve time,
|
|
1865
|
+
// matching the pre-fix behaviour for that edge case.
|
|
1866
|
+
//
|
|
1867
|
+
// Clone `folded` again so the Append.existing and priorValues[head]
|
|
1868
|
+
// do not share the same Subst/Concat references — relativizeResObj
|
|
1869
|
+
// walks BOTH `fields` and `priorValues` and mutates Subst.segments
|
|
1870
|
+
// in place, so a shared reference would get its prefix applied
|
|
1871
|
+
// twice (resulting in e.g. `${outer.outer.base}` instead of
|
|
1872
|
+
// `${outer.base}`). Codex review on PR #131.
|
|
1873
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1572
1874
|
elem: this.astToResolverValue(field.value, childPrefix)
|
|
1573
1875
|
});
|
|
1574
1876
|
return;
|
|
@@ -1576,10 +1878,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1576
1878
|
const existing = obj.fields.get(head);
|
|
1577
1879
|
const newVal = this.astToResolverValue(field.value, childPrefix);
|
|
1578
1880
|
if (existing !== void 0) {
|
|
1579
|
-
obj.priorValues.
|
|
1881
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1882
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
1883
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
1884
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1580
1885
|
}
|
|
1581
1886
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1582
|
-
deepMergeResObjInto(existing, newVal);
|
|
1887
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1583
1888
|
return;
|
|
1584
1889
|
}
|
|
1585
1890
|
obj.fields.set(head, newVal);
|
|
@@ -1596,7 +1901,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1596
1901
|
if (pathPrefix.length > 0) {
|
|
1597
1902
|
this.relativizeResObj(included, pathPrefix);
|
|
1598
1903
|
}
|
|
1599
|
-
deepMergeResObjInto(obj, included);
|
|
1904
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
1600
1905
|
return;
|
|
1601
1906
|
}
|
|
1602
1907
|
const [head, ...tail] = field.key;
|
|
@@ -1611,12 +1916,15 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1611
1916
|
return;
|
|
1612
1917
|
}
|
|
1613
1918
|
const childPrefix = [...pathPrefix, head];
|
|
1919
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
1614
1920
|
if (field.append) {
|
|
1615
1921
|
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1616
|
-
obj.priorValues.
|
|
1922
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1923
|
+
const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
|
|
1924
|
+
if (folded !== void 0) obj.priorValues.set(head, folded);
|
|
1617
1925
|
obj.fields.set(head, {
|
|
1618
1926
|
_kind: "append-placeholder",
|
|
1619
|
-
existing: existing2,
|
|
1927
|
+
existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
|
|
1620
1928
|
elem: await this.astToResolverValueAsync(field.value, childPrefix)
|
|
1621
1929
|
});
|
|
1622
1930
|
return;
|
|
@@ -1624,10 +1932,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1624
1932
|
const existing = obj.fields.get(head);
|
|
1625
1933
|
const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
|
|
1626
1934
|
if (existing !== void 0) {
|
|
1627
|
-
obj.priorValues.
|
|
1935
|
+
const oldPrior = obj.priorValues.get(head);
|
|
1936
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
1937
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
1938
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
1628
1939
|
}
|
|
1629
1940
|
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1630
|
-
deepMergeResObjInto(existing, newVal);
|
|
1941
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
1631
1942
|
return;
|
|
1632
1943
|
}
|
|
1633
1944
|
obj.fields.set(head, newVal);
|
|
@@ -1646,7 +1957,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1646
1957
|
return inner;
|
|
1647
1958
|
}
|
|
1648
1959
|
case "subst":
|
|
1649
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1960
|
+
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 };
|
|
1650
1961
|
case "concat":
|
|
1651
1962
|
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
|
|
1652
1963
|
case "include":
|
|
@@ -1670,7 +1981,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1670
1981
|
case "object":
|
|
1671
1982
|
return await this.buildAsync(ast, pathPrefix);
|
|
1672
1983
|
case "subst":
|
|
1673
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1984
|
+
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 };
|
|
1674
1985
|
case "concat": {
|
|
1675
1986
|
const nodes = [];
|
|
1676
1987
|
for (const n of ast.nodes) {
|
|
@@ -1759,6 +2070,23 @@ var SubstitutionResolver = class {
|
|
|
1759
2070
|
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
1760
2071
|
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
1761
2072
|
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
2073
|
+
// Path stack tracking the full dotted path of the field currently being
|
|
2074
|
+
// assigned in `resolveResObj`. Leaf keys are pushed on entry to each
|
|
2075
|
+
// `(key, val)` iteration and popped on exit, so nested objects build up
|
|
2076
|
+
// the full path (e.g. `["o", "history"]` while resolving `o.history`).
|
|
2077
|
+
//
|
|
2078
|
+
// Used to tighten self-ref detection at the L192 found-lookup site:
|
|
2079
|
+
// a substitution `${X}` whose looked-up value contains a Subst pointing
|
|
2080
|
+
// at `X` is only a true self-reference when the field currently being
|
|
2081
|
+
// assigned is `X` (or a descendant of `X`). Without this guard, an
|
|
2082
|
+
// external lookup like `b = ${o}` (where o has a self-referential
|
|
2083
|
+
// field) would mis-fire and short-circuit to o's prior, returning the
|
|
2084
|
+
// wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
|
|
2085
|
+
// narrows the check to the owner case only.
|
|
2086
|
+
//
|
|
2087
|
+
// Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
|
|
2088
|
+
// same prefix-match semantics).
|
|
2089
|
+
this.resolvingFieldPath = [];
|
|
1762
2090
|
}
|
|
1763
2091
|
/** Returns a prefix for error messages, including origin info when set. */
|
|
1764
2092
|
originPrefix() {
|
|
@@ -1770,35 +2098,57 @@ var SubstitutionResolver = class {
|
|
|
1770
2098
|
resolveResObj(obj) {
|
|
1771
2099
|
const result = /* @__PURE__ */ new Map();
|
|
1772
2100
|
for (const [key, val] of obj.fields) {
|
|
1773
|
-
|
|
2101
|
+
this.resolvingFieldPath.push(key);
|
|
2102
|
+
const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
|
|
2103
|
+
this.cache.delete(fullCacheKey);
|
|
2104
|
+
let resolved;
|
|
2105
|
+
try {
|
|
2106
|
+
resolved = this.resolveVal(val, obj);
|
|
2107
|
+
} finally {
|
|
2108
|
+
this.resolvingFieldPath.pop();
|
|
2109
|
+
}
|
|
1774
2110
|
if (resolved !== void 0) {
|
|
1775
2111
|
if (resolved.kind === "object") {
|
|
1776
2112
|
const prior = obj.priorValues.get(key);
|
|
1777
2113
|
if (prior !== void 0) {
|
|
1778
2114
|
const priorResolved = this.resolveVal(prior, obj);
|
|
1779
2115
|
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
priorResolved,
|
|
1784
|
-
resolved
|
|
1785
|
-
)
|
|
2116
|
+
const finalValue = deepMergeHoconValues(
|
|
2117
|
+
priorResolved,
|
|
2118
|
+
resolved
|
|
1786
2119
|
);
|
|
2120
|
+
result.set(key, finalValue);
|
|
2121
|
+
this.cache.set(fullCacheKey, finalValue);
|
|
2122
|
+
this.cacheDescendants(fullCacheKey, finalValue);
|
|
1787
2123
|
continue;
|
|
1788
2124
|
}
|
|
1789
2125
|
}
|
|
1790
2126
|
}
|
|
1791
2127
|
result.set(key, resolved);
|
|
2128
|
+
this.cache.set(fullCacheKey, resolved);
|
|
2129
|
+
this.cacheDescendants(fullCacheKey, resolved);
|
|
1792
2130
|
} else {
|
|
1793
2131
|
const prior = obj.priorValues.get(key);
|
|
1794
2132
|
if (prior !== void 0) {
|
|
1795
2133
|
const priorResolved = this.resolveVal(prior, obj);
|
|
1796
|
-
if (priorResolved !== void 0)
|
|
2134
|
+
if (priorResolved !== void 0) {
|
|
2135
|
+
result.set(key, priorResolved);
|
|
2136
|
+
this.cache.set(fullCacheKey, priorResolved);
|
|
2137
|
+
this.cacheDescendants(fullCacheKey, priorResolved);
|
|
2138
|
+
}
|
|
1797
2139
|
}
|
|
1798
2140
|
}
|
|
1799
2141
|
}
|
|
1800
2142
|
return { kind: "object", fields: result };
|
|
1801
2143
|
}
|
|
2144
|
+
cacheDescendants(prefix, value) {
|
|
2145
|
+
if (value.kind !== "object") return;
|
|
2146
|
+
for (const [key, child] of value.fields) {
|
|
2147
|
+
const childKey = `${prefix}.${stringSegmentsToKey([key])}`;
|
|
2148
|
+
this.cache.set(childKey, child);
|
|
2149
|
+
this.cacheDescendants(childKey, child);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
1802
2152
|
resolveVal(v, scope) {
|
|
1803
2153
|
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
1804
2154
|
if (isConcat(v)) {
|
|
@@ -1824,6 +2174,7 @@ var SubstitutionResolver = class {
|
|
|
1824
2174
|
return hv;
|
|
1825
2175
|
}
|
|
1826
2176
|
resolveSubst(s, scope) {
|
|
2177
|
+
if (s.knownAbsent) return void 0;
|
|
1827
2178
|
const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
1828
2179
|
if (this.cache.has(key)) return this.cache.get(key);
|
|
1829
2180
|
if (this.resolving.has(key)) {
|
|
@@ -1860,7 +2211,10 @@ var SubstitutionResolver = class {
|
|
|
1860
2211
|
try {
|
|
1861
2212
|
const found = lookupPath(this.root, s.segments);
|
|
1862
2213
|
if (found !== void 0) {
|
|
1863
|
-
|
|
2214
|
+
const rfp = this.resolvingFieldPath;
|
|
2215
|
+
const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
|
|
2216
|
+
const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
|
|
2217
|
+
if (isSelfRef) {
|
|
1864
2218
|
let prior;
|
|
1865
2219
|
if (s.segments.length > 1) {
|
|
1866
2220
|
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
@@ -1913,6 +2267,28 @@ var SubstitutionResolver = class {
|
|
|
1913
2267
|
if (result !== void 0) this.cache.set(key, result);
|
|
1914
2268
|
return result;
|
|
1915
2269
|
}
|
|
2270
|
+
if (s.prefixLen > 0 && s.segments.length > s.prefixLen) {
|
|
2271
|
+
const originalSegments = s.segments.slice(s.prefixLen);
|
|
2272
|
+
const fallbackFound = lookupPath(this.root, originalSegments);
|
|
2273
|
+
if (fallbackFound !== void 0) {
|
|
2274
|
+
let result = this.resolveVal(fallbackFound, scope);
|
|
2275
|
+
if (originalSegments.length === 1 && result !== void 0 && result.kind === "object") {
|
|
2276
|
+
const rootSeg = originalSegments[0]?.text ?? "";
|
|
2277
|
+
const prior = this.root.priorValues.get(rootSeg);
|
|
2278
|
+
if (prior !== void 0) {
|
|
2279
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
2280
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
2281
|
+
result = deepMergeHoconValues(
|
|
2282
|
+
priorResolved,
|
|
2283
|
+
result
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
2289
|
+
return result;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
1916
2292
|
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
1917
2293
|
const result = this.resolveEnvList(s);
|
|
1918
2294
|
if (result !== void 0) {
|