@o3co/ts.hocon 1.11.0 → 1.13.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/zod.cjs CHANGED
@@ -73,13 +73,22 @@ function coerceNumber(value) {
73
73
  return Number(value);
74
74
  }
75
75
  var DURATION_UNITS = {
76
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
77
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
78
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
76
79
  ns: 1e-6,
80
+ nano: 1e-6,
81
+ nanos: 1e-6,
77
82
  nanosecond: 1e-6,
78
83
  nanoseconds: 1e-6,
79
84
  us: 1e-3,
85
+ micro: 1e-3,
86
+ micros: 1e-3,
80
87
  microsecond: 1e-3,
81
88
  microseconds: 1e-3,
82
89
  ms: 1,
90
+ milli: 1,
91
+ millis: 1,
83
92
  millisecond: 1,
84
93
  milliseconds: 1,
85
94
  s: 1e3,
@@ -131,37 +140,76 @@ function parseDuration(value, outputUnit = "ms") {
131
140
  return ms / divisor;
132
141
  }
133
142
  var BYTE_UNITS = {
143
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
144
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
145
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
146
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
147
+ // first letter, and long forms are lowercase only (`kilobyte`, never
148
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
149
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
150
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
134
151
  B: 1,
152
+ b: 1,
135
153
  byte: 1,
136
154
  bytes: 1,
137
- KB: 1e3,
155
+ kB: 1e3,
138
156
  kilobyte: 1e3,
139
157
  kilobytes: 1e3,
140
- KiB: 1024,
141
- kibibyte: 1024,
142
- kibibytes: 1024,
143
158
  MB: 1e6,
144
159
  megabyte: 1e6,
145
160
  megabytes: 1e6,
146
- MiB: 1048576,
147
- mebibyte: 1048576,
148
- mebibytes: 1048576,
149
161
  GB: 1e9,
150
162
  gigabyte: 1e9,
151
163
  gigabytes: 1e9,
152
- GiB: 1073741824,
153
- gibibyte: 1073741824,
154
- gibibytes: 1073741824,
155
164
  TB: 1e12,
156
165
  terabyte: 1e12,
157
166
  terabytes: 1e12,
167
+ PB: 1e15,
168
+ petabyte: 1e15,
169
+ petabytes: 1e15,
170
+ EB: 1e18,
171
+ exabyte: 1e18,
172
+ exabytes: 1e18,
173
+ ZB: 1e21,
174
+ zettabyte: 1e21,
175
+ zettabytes: 1e21,
176
+ YB: 1e24,
177
+ yottabyte: 1e24,
178
+ yottabytes: 1e24,
179
+ Ki: 1024,
180
+ KiB: 1024,
181
+ kibibyte: 1024,
182
+ kibibytes: 1024,
183
+ Mi: 1048576,
184
+ MiB: 1048576,
185
+ mebibyte: 1048576,
186
+ mebibytes: 1048576,
187
+ Gi: 1073741824,
188
+ GiB: 1073741824,
189
+ gibibyte: 1073741824,
190
+ gibibytes: 1073741824,
191
+ Ti: 1099511627776,
158
192
  TiB: 1099511627776,
159
193
  tebibyte: 1099511627776,
160
194
  tebibytes: 1099511627776,
161
- // S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
162
- // Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
163
- // Both upper- and lowercase accepted per Lightbend behaviour.
164
- // Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
195
+ Pi: 1024 ** 5,
196
+ PiB: 1024 ** 5,
197
+ pebibyte: 1024 ** 5,
198
+ pebibytes: 1024 ** 5,
199
+ Ei: 1024 ** 6,
200
+ EiB: 1024 ** 6,
201
+ exbibyte: 1024 ** 6,
202
+ exbibytes: 1024 ** 6,
203
+ Zi: 1024 ** 7,
204
+ ZiB: 1024 ** 7,
205
+ zebibyte: 1024 ** 7,
206
+ zebibytes: 1024 ** 7,
207
+ Yi: 1024 ** 8,
208
+ YiB: 1024 ** 8,
209
+ yobibyte: 1024 ** 8,
210
+ yobibytes: 1024 ** 8,
211
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
212
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
165
213
  K: 1024,
166
214
  k: 1024,
167
215
  M: 1024 ** 2,
@@ -174,16 +222,10 @@ var BYTE_UNITS = {
174
222
  p: 1024 ** 5,
175
223
  E: 1024 ** 6,
176
224
  e: 1024 ** 6,
177
- // lowercase short-form aliases
178
- b: 1,
179
- kb: 1e3,
180
- kib: 1024,
181
- mb: 1e6,
182
- mib: 1048576,
183
- gb: 1e9,
184
- gib: 1073741824,
185
- tb: 1e12,
186
- tib: 1099511627776
225
+ Z: 1024 ** 7,
226
+ z: 1024 ** 7,
227
+ Y: 1024 ** 8,
228
+ y: 1024 ** 8
187
229
  };
188
230
  var OUTPUT_BYTE_UNITS = {
189
231
  B: 1,
@@ -221,10 +263,7 @@ function parseBytes(value, outputUnit = "B") {
221
263
  }
222
264
  return bytes2 / divisor;
223
265
  }
224
- let mult = BYTE_UNITS[unit];
225
- if (mult === void 0) {
226
- mult = BYTE_UNITS[unit.toLowerCase()];
227
- }
266
+ const mult = BYTE_UNITS[unit];
228
267
  if (mult === void 0) return NaN;
229
268
  const bytes = num * mult;
230
269
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -292,35 +331,114 @@ function stringSegmentsToKey(segments) {
292
331
  return t;
293
332
  }).join(".");
294
333
  }
334
+ function prefixSelfRefRemainder(s, fullKey) {
335
+ const texts = s.segments.map((seg) => seg.text);
336
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
337
+ for (let n = 1; n < texts.length; n++) {
338
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
339
+ }
340
+ return null;
341
+ }
342
+ function navigateResolverValue(v, remainder) {
343
+ let cur = v;
344
+ for (const seg of remainder) {
345
+ if (isSubst(cur) || isConcat(cur)) return null;
346
+ if (isResObj(cur)) {
347
+ const next = cur.fields.get(seg);
348
+ if (next === void 0) return void 0;
349
+ cur = next;
350
+ continue;
351
+ }
352
+ const hv = cur;
353
+ if (hv.kind === "object") {
354
+ const next = hv.fields.get(seg);
355
+ if (next === void 0) return void 0;
356
+ cur = next;
357
+ continue;
358
+ }
359
+ return void 0;
360
+ }
361
+ return cur;
362
+ }
363
+ function foldPrefixSelfRef(s, replacement, remainder) {
364
+ const navigated = navigateResolverValue(replacement, remainder);
365
+ if (navigated === null) return s;
366
+ if (navigated === void 0) {
367
+ return {
368
+ _kind: "subst-placeholder",
369
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
370
+ optional: s.optional,
371
+ knownAbsent: true,
372
+ listSuffix: s.listSuffix,
373
+ line: s.line,
374
+ col: s.col,
375
+ prefixLen: s.prefixLen
376
+ };
377
+ }
378
+ return cloneResolverValue(navigated);
379
+ }
380
+ function mergeResObjLayers(base, top) {
381
+ const fields = new Map(base.fields);
382
+ for (const [k, tv] of top.fields) {
383
+ const bv = fields.get(k);
384
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
385
+ fields.set(k, mergeResObjLayers(bv, tv));
386
+ } else {
387
+ fields.set(k, tv);
388
+ }
389
+ }
390
+ const priorValues = new Map(base.priorValues);
391
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
392
+ const resetKeys = new Set(base.resetKeys);
393
+ for (const k of top.resetKeys) resetKeys.add(k);
394
+ return {
395
+ _kind: "res-obj",
396
+ fields,
397
+ priorValues,
398
+ resetKeys
399
+ };
400
+ }
295
401
  function containsSelfRef(v, fullKey) {
296
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
297
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
402
+ return containsSelfRefInner(v, fullKey, true);
403
+ }
404
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
405
+ if (isSubst(v))
406
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
407
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
298
408
  if (isResObj(v)) {
299
409
  for (const f of v.fields.values()) {
300
- if (containsSelfRef(f, fullKey)) return true;
410
+ if (containsSelfRefInner(f, fullKey, false)) return true;
301
411
  }
302
412
  return false;
303
413
  }
304
414
  const hv = v;
305
415
  if (hv.kind === "array") {
306
- return hv.items.some((item) => containsSelfRef(item, fullKey));
416
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
307
417
  }
308
418
  if (hv.kind === "object") {
309
419
  for (const f of hv.fields.values()) {
310
- if (containsSelfRef(f, fullKey)) return true;
420
+ if (containsSelfRefInner(f, fullKey, false)) return true;
311
421
  }
312
422
  return false;
313
423
  }
314
424
  return false;
315
425
  }
316
426
  function foldSelfRef(v, fullKey, replacement) {
427
+ return foldSelfRefInner(v, fullKey, replacement, true);
428
+ }
429
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
317
430
  if (isSubst(v)) {
318
- return substFullKey(v) === fullKey ? replacement : v;
431
+ if (substFullKey(v) === fullKey) return replacement;
432
+ if (allowPrefix) {
433
+ const remainder = prefixSelfRefRemainder(v, fullKey);
434
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
435
+ }
436
+ return v;
319
437
  }
320
438
  if (isConcat(v)) {
321
439
  return {
322
440
  _kind: "concat-placeholder",
323
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
441
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
324
442
  line: v.line,
325
443
  col: v.col
326
444
  };
@@ -328,7 +446,7 @@ function foldSelfRef(v, fullKey, replacement) {
328
446
  if (isResObj(v)) {
329
447
  const newFields = /* @__PURE__ */ new Map();
330
448
  for (const [k, val] of v.fields) {
331
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
449
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
332
450
  }
333
451
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
334
452
  }
@@ -336,13 +454,15 @@ function foldSelfRef(v, fullKey, replacement) {
336
454
  if (hv.kind === "array") {
337
455
  return {
338
456
  kind: "array",
339
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
457
+ items: hv.items.map(
458
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
459
+ )
340
460
  };
341
461
  }
342
462
  if (hv.kind === "object") {
343
463
  const newFields = /* @__PURE__ */ new Map();
344
464
  for (const [k, val] of hv.fields) {
345
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
465
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
346
466
  }
347
467
  return { kind: "object", fields: newFields };
348
468
  }
@@ -386,10 +506,26 @@ function foldKnownAbsentSelfRef(v, fullKey, replacement) {
386
506
  function foldOrSkipPrior(prior, fullKey, old) {
387
507
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
388
508
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
509
+ if (isSubst(prior)) {
510
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
511
+ if (remainder !== null) {
512
+ const navigated = navigateResolverValue(old, remainder);
513
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
514
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
515
+ return mergeResObjLayers(
516
+ cloneResolverValue(old),
517
+ cloneResolverValue(navigated)
518
+ );
519
+ }
520
+ }
521
+ }
389
522
  return foldSelfRef(prior, fullKey, old);
390
523
  }
391
524
  function foldOptionalSelfRefAbsent(v, fullKey) {
392
- if (isSubst(v) && substFullKey(v) === fullKey) {
525
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
526
+ }
527
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
528
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
393
529
  if (!v.optional) return void 0;
394
530
  return {
395
531
  _kind: "subst-placeholder",
@@ -405,7 +541,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
405
541
  if (isConcat(v)) {
406
542
  const nodes = [];
407
543
  for (const node of v.nodes) {
408
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
544
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
409
545
  if (folded === void 0) return void 0;
410
546
  nodes.push(folded);
411
547
  }
@@ -414,7 +550,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
414
550
  if (isResObj(v)) {
415
551
  const fields = /* @__PURE__ */ new Map();
416
552
  for (const [key, value] of v.fields) {
417
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
553
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
418
554
  if (folded === void 0) return void 0;
419
555
  fields.set(key, folded);
420
556
  }
@@ -424,7 +560,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
424
560
  if (hv.kind === "array") {
425
561
  const items = [];
426
562
  for (const item of hv.items) {
427
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
563
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
428
564
  if (folded === void 0) return void 0;
429
565
  items.push(folded);
430
566
  }
@@ -433,7 +569,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
433
569
  if (hv.kind === "object") {
434
570
  const fields = /* @__PURE__ */ new Map();
435
571
  for (const [key, value] of hv.fields) {
436
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
572
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
437
573
  if (folded === void 0) return void 0;
438
574
  fields.set(key, folded);
439
575
  }
@@ -806,7 +942,6 @@ var Lexer = class {
806
942
  if (!closed) {
807
943
  throw new ParseError("unterminated triple-quoted string", sl, sc);
808
944
  }
809
- if (value.startsWith("\n")) value = value.slice(1);
810
945
  this.push("triple_string", value, sl, sc, true);
811
946
  continue;
812
947
  }
@@ -1123,9 +1258,9 @@ var Parser = class {
1123
1258
  }
1124
1259
  }
1125
1260
  this.skip("newline");
1126
- const remaining = this.peek();
1127
- if (remaining.kind !== "eof") {
1128
- throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
1261
+ const remaining2 = this.peek();
1262
+ if (remaining2.kind !== "eof") {
1263
+ throw new ParseError(`Unexpected token '${remaining2.value}' after closing brace`, remaining2.line, remaining2.col);
1129
1264
  }
1130
1265
  return { kind: "object", fields: allFields, pos: first.pos };
1131
1266
  }
@@ -1133,13 +1268,19 @@ var Parser = class {
1133
1268
  this.advance();
1134
1269
  const arr = this.parseArray();
1135
1270
  this.skip("newline");
1136
- const remaining = this.peek();
1137
- if (remaining.kind !== "eof") {
1138
- throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1271
+ const remaining2 = this.peek();
1272
+ if (remaining2.kind !== "eof") {
1273
+ throw new ParseError(`Unexpected token '${remaining2.value}' after root array`, remaining2.line, remaining2.col);
1139
1274
  }
1140
1275
  return { ...arr, pos: { line: t.line, col: t.col } };
1141
1276
  }
1142
- return this.parseObject(false);
1277
+ const root = this.parseObject(false);
1278
+ this.skip("newline");
1279
+ const remaining = this.peek();
1280
+ if (remaining.kind !== "eof") {
1281
+ throw new ParseError(`Unexpected token '${remaining.value}' after root content`, remaining.line, remaining.col);
1282
+ }
1283
+ return root;
1143
1284
  }
1144
1285
  peek(offset = 0) {
1145
1286
  return this.tokens[this.pos + offset] ?? EOF_TOKEN;
@@ -1530,6 +1671,28 @@ function stripBom(input) {
1530
1671
  return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
1531
1672
  }
1532
1673
 
1674
+ // src/internal/depth.ts
1675
+ var MAX_PATH_SEGMENTS = 64;
1676
+ function tooDeep(segments) {
1677
+ return segments > MAX_PATH_SEGMENTS;
1678
+ }
1679
+ function guardStackDepth(fn, wrap) {
1680
+ try {
1681
+ return fn();
1682
+ } catch (e) {
1683
+ if (!isStackOverflow(e)) throw e;
1684
+ throw wrap(STACK_MESSAGE);
1685
+ }
1686
+ }
1687
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
1688
+ function isStackOverflow(e) {
1689
+ if (!(e instanceof RangeError)) return false;
1690
+ const m = e.message.toLowerCase();
1691
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
1692
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
1693
+ m.includes("stack overflow");
1694
+ }
1695
+
1533
1696
  // src/internal/properties/properties.ts
1534
1697
  function parseProperties(input) {
1535
1698
  const pairs = [];
@@ -1537,7 +1700,15 @@ function parseProperties(input) {
1537
1700
  const [rawKey, rawValue] = splitKeyValue(text);
1538
1701
  const key = unescapeProps(rawKey, line);
1539
1702
  if (key === "") continue;
1540
- pairs.push([key.split("."), unescapeProps(rawValue, line)]);
1703
+ const segments = key.split(".");
1704
+ if (tooDeep(segments.length)) {
1705
+ throw new ParseError(
1706
+ `key maps to a path ${segments.length} segments deep, over the limit of ${MAX_PATH_SEGMENTS}`,
1707
+ line,
1708
+ 1
1709
+ );
1710
+ }
1711
+ pairs.push([segments, unescapeProps(rawValue, line)]);
1541
1712
  }
1542
1713
  return nestPairs(pairs);
1543
1714
  }
@@ -2256,6 +2427,12 @@ var SubstitutionResolver = class {
2256
2427
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
2257
2428
  // same prefix-match semantics).
2258
2429
  this.resolvingFieldPath = [];
2430
+ // S13a.12 recursion guard: field keys whose prior is currently being
2431
+ // resolved through the prefix-self-ref branch. Saved priors are
2432
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
2433
+ // a shape the fold could not see through (a live subst mid-navigation) —
2434
+ // report it as a cycle instead of recursing forever.
2435
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
2259
2436
  }
2260
2437
  /** Returns a prefix for error messages, including origin info when set. */
2261
2438
  originPrefix() {
@@ -2270,46 +2447,57 @@ var SubstitutionResolver = class {
2270
2447
  this.resolvingFieldPath.push(key);
2271
2448
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
2272
2449
  this.cache.delete(fullCacheKey);
2273
- let resolved;
2274
2450
  try {
2275
- resolved = this.resolveVal(val, obj);
2276
- } finally {
2277
- this.resolvingFieldPath.pop();
2278
- }
2279
- if (resolved !== void 0) {
2280
- if (resolved.kind === "object") {
2451
+ const resolved = this.resolveVal(val, obj);
2452
+ if (resolved !== void 0) {
2453
+ if (resolved.kind === "object") {
2454
+ const prior = obj.priorValues.get(key);
2455
+ if (prior !== void 0) {
2456
+ const priorResolved = this.resolveVal(prior, obj);
2457
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
2458
+ const finalValue = deepMergeHoconValues(
2459
+ priorResolved,
2460
+ resolved
2461
+ );
2462
+ result.set(key, finalValue);
2463
+ this.cache.set(fullCacheKey, finalValue);
2464
+ this.cacheDescendants(fullCacheKey, finalValue);
2465
+ continue;
2466
+ }
2467
+ }
2468
+ }
2469
+ result.set(key, resolved);
2470
+ this.cache.set(fullCacheKey, resolved);
2471
+ this.cacheDescendants(fullCacheKey, resolved);
2472
+ } else {
2281
2473
  const prior = obj.priorValues.get(key);
2282
2474
  if (prior !== void 0) {
2283
2475
  const priorResolved = this.resolveVal(prior, obj);
2284
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
2285
- const finalValue = deepMergeHoconValues(
2286
- priorResolved,
2287
- resolved
2288
- );
2289
- result.set(key, finalValue);
2290
- this.cache.set(fullCacheKey, finalValue);
2291
- this.cacheDescendants(fullCacheKey, finalValue);
2292
- continue;
2476
+ if (priorResolved !== void 0) {
2477
+ result.set(key, priorResolved);
2478
+ this.cache.set(fullCacheKey, priorResolved);
2479
+ this.cacheDescendants(fullCacheKey, priorResolved);
2293
2480
  }
2294
2481
  }
2295
2482
  }
2296
- result.set(key, resolved);
2297
- this.cache.set(fullCacheKey, resolved);
2298
- this.cacheDescendants(fullCacheKey, resolved);
2299
- } else {
2300
- const prior = obj.priorValues.get(key);
2301
- if (prior !== void 0) {
2302
- const priorResolved = this.resolveVal(prior, obj);
2303
- if (priorResolved !== void 0) {
2304
- result.set(key, priorResolved);
2305
- this.cache.set(fullCacheKey, priorResolved);
2306
- this.cacheDescendants(fullCacheKey, priorResolved);
2307
- }
2308
- }
2483
+ } finally {
2484
+ this.resolvingFieldPath.pop();
2309
2485
  }
2310
2486
  }
2311
2487
  return { kind: "object", fields: result };
2312
2488
  }
2489
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
2490
+ * or a walk into a scalar/array is path-absent → undefined. */
2491
+ navigateResolved(v, remainder) {
2492
+ let cur = v;
2493
+ for (const seg of remainder) {
2494
+ if (cur.kind !== "object") return void 0;
2495
+ const next = cur.fields.get(seg);
2496
+ if (next === void 0) return void 0;
2497
+ cur = next;
2498
+ }
2499
+ return cur;
2500
+ }
2313
2501
  cacheDescendants(prefix, value) {
2314
2502
  if (value.kind !== "object") return;
2315
2503
  for (const [key, child] of value.fields) {
@@ -2332,17 +2520,59 @@ var SubstitutionResolver = class {
2332
2520
  if (isResObj(v)) return this.resolveResObj(v);
2333
2521
  const hv = v;
2334
2522
  if (hv.kind === "array") {
2335
- return {
2336
- kind: "array",
2337
- items: hv.items.map(
2338
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
2339
- )
2340
- };
2523
+ const items = [];
2524
+ for (const item of hv.items) {
2525
+ const resolved = this.resolveVal(item, scope);
2526
+ if (resolved === void 0) continue;
2527
+ items.push(resolved);
2528
+ }
2529
+ return { kind: "array", items };
2341
2530
  }
2342
2531
  return hv;
2343
2532
  }
2344
2533
  resolveSubst(s, scope) {
2345
- if (s.knownAbsent) return void 0;
2534
+ if (s.knownAbsent) {
2535
+ if (!s.optional) {
2536
+ if (this.opts.allowUnresolved) return s;
2537
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2538
+ throw new ResolveError(
2539
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2540
+ k,
2541
+ s.line,
2542
+ s.col
2543
+ );
2544
+ }
2545
+ return void 0;
2546
+ }
2547
+ {
2548
+ const rfp = this.resolvingFieldPath;
2549
+ const sTexts = s.segments.map((seg) => seg.text);
2550
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
2551
+ const guardKey = stringSegmentsToKey(rfp);
2552
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
2553
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
2554
+ this.resolvingPrefixPriors.add(guardKey);
2555
+ try {
2556
+ const priorResolved = this.resolveVal(prior, scope);
2557
+ if (priorResolved !== void 0) {
2558
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
2559
+ if (navigated !== void 0) return navigated;
2560
+ }
2561
+ } finally {
2562
+ this.resolvingPrefixPriors.delete(guardKey);
2563
+ }
2564
+ }
2565
+ if (s.optional) return void 0;
2566
+ if (this.opts.allowUnresolved) return s;
2567
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2568
+ throw new ResolveError(
2569
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2570
+ k,
2571
+ s.line,
2572
+ s.col
2573
+ );
2574
+ }
2575
+ }
2346
2576
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2347
2577
  if (this.cache.has(key)) return this.cache.get(key);
2348
2578
  if (this.resolving.has(key)) {
@@ -2669,6 +2899,121 @@ function resolve2(ast, opts) {
2669
2899
  return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
2670
2900
  }
2671
2901
 
2902
+ // src/render-hocon.ts
2903
+ function renderHoconFromRoot(root) {
2904
+ const out = [];
2905
+ renderObjectBody(out, root, 0);
2906
+ return out.join("");
2907
+ }
2908
+ function renderObjectBody(out, o, depth) {
2909
+ const indent = " ".repeat(depth);
2910
+ for (const [k, v] of o.fields) {
2911
+ out.push(indent, renderKey(k));
2912
+ if (v.kind === "object") {
2913
+ out.push(" {");
2914
+ if (v.fields.size === 0) {
2915
+ out.push("}\n");
2916
+ continue;
2917
+ }
2918
+ out.push("\n");
2919
+ renderObjectBody(out, v, depth + 1);
2920
+ out.push(indent, "}\n");
2921
+ } else {
2922
+ out.push(" = ");
2923
+ renderValue(out, v, depth);
2924
+ out.push("\n");
2925
+ }
2926
+ }
2927
+ }
2928
+ function renderValue(out, v, depth) {
2929
+ switch (v.kind) {
2930
+ case "object": {
2931
+ if (v.fields.size === 0) {
2932
+ out.push("{}");
2933
+ return;
2934
+ }
2935
+ out.push("{\n");
2936
+ renderObjectBody(out, v, depth + 1);
2937
+ out.push(" ".repeat(depth), "}");
2938
+ return;
2939
+ }
2940
+ case "array":
2941
+ renderArray(out, v, depth);
2942
+ return;
2943
+ case "scalar":
2944
+ out.push(renderScalar(v));
2945
+ return;
2946
+ default:
2947
+ throw new ConfigError(
2948
+ "renderHocon: unrenderable value (config must be resolved data)",
2949
+ ""
2950
+ );
2951
+ }
2952
+ }
2953
+ function renderArray(out, a, depth) {
2954
+ if (a.items.length === 0) {
2955
+ out.push("[]");
2956
+ return;
2957
+ }
2958
+ const inner = " ".repeat(depth + 1);
2959
+ out.push("[\n");
2960
+ for (const e of a.items) {
2961
+ out.push(inner);
2962
+ renderValue(out, e, depth + 1);
2963
+ out.push("\n");
2964
+ }
2965
+ out.push(" ".repeat(depth), "]");
2966
+ }
2967
+ function renderScalar(s) {
2968
+ switch (s.valueType) {
2969
+ case "null":
2970
+ return "null";
2971
+ case "boolean":
2972
+ case "number":
2973
+ return s.raw;
2974
+ case "string":
2975
+ return renderString(s.raw);
2976
+ }
2977
+ }
2978
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
2979
+ function renderKey(k) {
2980
+ if (k === "include") return quoteString(k);
2981
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
2982
+ }
2983
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
2984
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
2985
+ function renderString(s) {
2986
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
2987
+ }
2988
+ function quoteString(s) {
2989
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
2990
+ return `"""${s}"""`;
2991
+ }
2992
+ let b = '"';
2993
+ for (const ch of s) {
2994
+ switch (ch) {
2995
+ case '"':
2996
+ b += '\\"';
2997
+ break;
2998
+ case "\\":
2999
+ b += "\\\\";
3000
+ break;
3001
+ case "\n":
3002
+ b += "\\n";
3003
+ break;
3004
+ case "\r":
3005
+ b += "\\r";
3006
+ break;
3007
+ case " ":
3008
+ b += "\\t";
3009
+ break;
3010
+ default:
3011
+ b += ch;
3012
+ }
3013
+ }
3014
+ return b + '"';
3015
+ }
3016
+
2672
3017
  // src/config.ts
2673
3018
  var Config = class _Config {
2674
3019
  constructor(root, opts) {
@@ -2974,6 +3319,36 @@ var Config = class _Config {
2974
3319
  toObject(opts) {
2975
3320
  return hoconToJs(this.root, opts?.nullPrototype === true);
2976
3321
  }
3322
+ /**
3323
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
3324
+ * `Config.RenderHOCON`, v1.11.0).
3325
+ *
3326
+ * The output round-trips: parsing it back yields the same value tree. That
3327
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
3328
+ * quoted whenever leaving it bare would re-parse as a different type (a
3329
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
3330
+ * provably cannot.
3331
+ *
3332
+ * The Config must be resolved and hold only data (objects, arrays, string /
3333
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
3334
+ * format adapters produce. An unresolved placeholder throws
3335
+ * {@link ConfigError}; substitutions have no textual round trip through a
3336
+ * value tree.
3337
+ *
3338
+ * The root object's fields are emitted without enclosing braces, nested
3339
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
3340
+ * spaces. Source comments are not represented — a value tree does not carry
3341
+ * them.
3342
+ */
3343
+ renderHocon() {
3344
+ if (!this._resolved) {
3345
+ throw new ConfigError(
3346
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
3347
+ ""
3348
+ );
3349
+ }
3350
+ return renderHoconFromRoot(this.root);
3351
+ }
2977
3352
  /**
2978
3353
  * @internal Test-only: renders this resolved Config's value tree as canonical
2979
3354
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
@@ -3216,10 +3591,30 @@ function buildResolveContext(input, opts) {
3216
3591
  return { ast, resolveOpts };
3217
3592
  }
3218
3593
  function parse(input, opts = {}) {
3594
+ return parseStringWithOptions(input, opts);
3595
+ }
3596
+ function parseStringWithOptions(input, opts = {}) {
3597
+ return guardStackDepth(
3598
+ () => parseStringInner(input, opts),
3599
+ (msg) => new ParseError(msg, 1, 1, opts.originDescription)
3600
+ );
3601
+ }
3602
+ function parseStringInner(input, opts) {
3603
+ const resolveSubstitutions = opts.resolveSubstitutions ?? true;
3219
3604
  const { ast, resolveOpts } = buildResolveContext(input, opts);
3220
- const value = resolve2(ast, resolveOpts);
3221
- if (value.kind !== "object") throw new Error("resolved value is not an object");
3222
- return new Config(value, { resolved: true, originDescription: opts.originDescription });
3605
+ if (resolveSubstitutions) {
3606
+ const value = resolve2(ast, resolveOpts);
3607
+ if (value.kind !== "object") throw new Error("resolved value is not an object");
3608
+ return new Config(value, { resolved: true, originDescription: opts.originDescription });
3609
+ }
3610
+ const tree = buildTree(ast, resolveOpts);
3611
+ const hasPlaceholders = containsPlaceholders(tree);
3612
+ return Config._fromUnresolvedResObj(tree, {
3613
+ parseBaseDir: opts.baseDir,
3614
+ originDescription: opts.originDescription,
3615
+ resolved: !hasPlaceholders,
3616
+ resolveOpts
3617
+ });
3223
3618
  }
3224
3619
  function parseFile(filePath, opts = {}) {
3225
3620
  if (typeof process === "undefined" && !opts.readFileSync) {