@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/index.js CHANGED
@@ -41,13 +41,22 @@ function coerceNumber(value) {
41
41
  return Number(value);
42
42
  }
43
43
  var DURATION_UNITS = {
44
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
45
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
46
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
44
47
  ns: 1e-6,
48
+ nano: 1e-6,
49
+ nanos: 1e-6,
45
50
  nanosecond: 1e-6,
46
51
  nanoseconds: 1e-6,
47
52
  us: 1e-3,
53
+ micro: 1e-3,
54
+ micros: 1e-3,
48
55
  microsecond: 1e-3,
49
56
  microseconds: 1e-3,
50
57
  ms: 1,
58
+ milli: 1,
59
+ millis: 1,
51
60
  millisecond: 1,
52
61
  milliseconds: 1,
53
62
  s: 1e3,
@@ -99,37 +108,76 @@ function parseDuration(value, outputUnit = "ms") {
99
108
  return ms / divisor;
100
109
  }
101
110
  var BYTE_UNITS = {
111
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
112
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
113
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
114
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
115
+ // first letter, and long forms are lowercase only (`kilobyte`, never
116
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
117
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
118
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
102
119
  B: 1,
120
+ b: 1,
103
121
  byte: 1,
104
122
  bytes: 1,
105
- KB: 1e3,
123
+ kB: 1e3,
106
124
  kilobyte: 1e3,
107
125
  kilobytes: 1e3,
108
- KiB: 1024,
109
- kibibyte: 1024,
110
- kibibytes: 1024,
111
126
  MB: 1e6,
112
127
  megabyte: 1e6,
113
128
  megabytes: 1e6,
114
- MiB: 1048576,
115
- mebibyte: 1048576,
116
- mebibytes: 1048576,
117
129
  GB: 1e9,
118
130
  gigabyte: 1e9,
119
131
  gigabytes: 1e9,
120
- GiB: 1073741824,
121
- gibibyte: 1073741824,
122
- gibibytes: 1073741824,
123
132
  TB: 1e12,
124
133
  terabyte: 1e12,
125
134
  terabytes: 1e12,
135
+ PB: 1e15,
136
+ petabyte: 1e15,
137
+ petabytes: 1e15,
138
+ EB: 1e18,
139
+ exabyte: 1e18,
140
+ exabytes: 1e18,
141
+ ZB: 1e21,
142
+ zettabyte: 1e21,
143
+ zettabytes: 1e21,
144
+ YB: 1e24,
145
+ yottabyte: 1e24,
146
+ yottabytes: 1e24,
147
+ Ki: 1024,
148
+ KiB: 1024,
149
+ kibibyte: 1024,
150
+ kibibytes: 1024,
151
+ Mi: 1048576,
152
+ MiB: 1048576,
153
+ mebibyte: 1048576,
154
+ mebibytes: 1048576,
155
+ Gi: 1073741824,
156
+ GiB: 1073741824,
157
+ gibibyte: 1073741824,
158
+ gibibytes: 1073741824,
159
+ Ti: 1099511627776,
126
160
  TiB: 1099511627776,
127
161
  tebibyte: 1099511627776,
128
162
  tebibytes: 1099511627776,
129
- // S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
130
- // Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
131
- // Both upper- and lowercase accepted per Lightbend behaviour.
132
- // Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
163
+ Pi: 1024 ** 5,
164
+ PiB: 1024 ** 5,
165
+ pebibyte: 1024 ** 5,
166
+ pebibytes: 1024 ** 5,
167
+ Ei: 1024 ** 6,
168
+ EiB: 1024 ** 6,
169
+ exbibyte: 1024 ** 6,
170
+ exbibytes: 1024 ** 6,
171
+ Zi: 1024 ** 7,
172
+ ZiB: 1024 ** 7,
173
+ zebibyte: 1024 ** 7,
174
+ zebibytes: 1024 ** 7,
175
+ Yi: 1024 ** 8,
176
+ YiB: 1024 ** 8,
177
+ yobibyte: 1024 ** 8,
178
+ yobibytes: 1024 ** 8,
179
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
180
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
133
181
  K: 1024,
134
182
  k: 1024,
135
183
  M: 1024 ** 2,
@@ -142,16 +190,10 @@ var BYTE_UNITS = {
142
190
  p: 1024 ** 5,
143
191
  E: 1024 ** 6,
144
192
  e: 1024 ** 6,
145
- // lowercase short-form aliases
146
- b: 1,
147
- kb: 1e3,
148
- kib: 1024,
149
- mb: 1e6,
150
- mib: 1048576,
151
- gb: 1e9,
152
- gib: 1073741824,
153
- tb: 1e12,
154
- tib: 1099511627776
193
+ Z: 1024 ** 7,
194
+ z: 1024 ** 7,
195
+ Y: 1024 ** 8,
196
+ y: 1024 ** 8
155
197
  };
156
198
  var OUTPUT_BYTE_UNITS = {
157
199
  B: 1,
@@ -189,10 +231,7 @@ function parseBytes(value, outputUnit = "B") {
189
231
  }
190
232
  return bytes2 / divisor;
191
233
  }
192
- let mult = BYTE_UNITS[unit];
193
- if (mult === void 0) {
194
- mult = BYTE_UNITS[unit.toLowerCase()];
195
- }
234
+ const mult = BYTE_UNITS[unit];
196
235
  if (mult === void 0) return NaN;
197
236
  const bytes = num * mult;
198
237
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -256,35 +295,114 @@ function stringSegmentsToKey(segments) {
256
295
  return t;
257
296
  }).join(".");
258
297
  }
298
+ function prefixSelfRefRemainder(s, fullKey) {
299
+ const texts = s.segments.map((seg) => seg.text);
300
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
301
+ for (let n = 1; n < texts.length; n++) {
302
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
303
+ }
304
+ return null;
305
+ }
306
+ function navigateResolverValue(v, remainder) {
307
+ let cur = v;
308
+ for (const seg of remainder) {
309
+ if (isSubst(cur) || isConcat(cur)) return null;
310
+ if (isResObj(cur)) {
311
+ const next = cur.fields.get(seg);
312
+ if (next === void 0) return void 0;
313
+ cur = next;
314
+ continue;
315
+ }
316
+ const hv = cur;
317
+ if (hv.kind === "object") {
318
+ const next = hv.fields.get(seg);
319
+ if (next === void 0) return void 0;
320
+ cur = next;
321
+ continue;
322
+ }
323
+ return void 0;
324
+ }
325
+ return cur;
326
+ }
327
+ function foldPrefixSelfRef(s, replacement, remainder) {
328
+ const navigated = navigateResolverValue(replacement, remainder);
329
+ if (navigated === null) return s;
330
+ if (navigated === void 0) {
331
+ return {
332
+ _kind: "subst-placeholder",
333
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
334
+ optional: s.optional,
335
+ knownAbsent: true,
336
+ listSuffix: s.listSuffix,
337
+ line: s.line,
338
+ col: s.col,
339
+ prefixLen: s.prefixLen
340
+ };
341
+ }
342
+ return cloneResolverValue(navigated);
343
+ }
344
+ function mergeResObjLayers(base, top) {
345
+ const fields = new Map(base.fields);
346
+ for (const [k, tv] of top.fields) {
347
+ const bv = fields.get(k);
348
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
349
+ fields.set(k, mergeResObjLayers(bv, tv));
350
+ } else {
351
+ fields.set(k, tv);
352
+ }
353
+ }
354
+ const priorValues = new Map(base.priorValues);
355
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
356
+ const resetKeys = new Set(base.resetKeys);
357
+ for (const k of top.resetKeys) resetKeys.add(k);
358
+ return {
359
+ _kind: "res-obj",
360
+ fields,
361
+ priorValues,
362
+ resetKeys
363
+ };
364
+ }
259
365
  function containsSelfRef(v, fullKey) {
260
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
261
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
366
+ return containsSelfRefInner(v, fullKey, true);
367
+ }
368
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
369
+ if (isSubst(v))
370
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
371
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
262
372
  if (isResObj(v)) {
263
373
  for (const f of v.fields.values()) {
264
- if (containsSelfRef(f, fullKey)) return true;
374
+ if (containsSelfRefInner(f, fullKey, false)) return true;
265
375
  }
266
376
  return false;
267
377
  }
268
378
  const hv = v;
269
379
  if (hv.kind === "array") {
270
- return hv.items.some((item) => containsSelfRef(item, fullKey));
380
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
271
381
  }
272
382
  if (hv.kind === "object") {
273
383
  for (const f of hv.fields.values()) {
274
- if (containsSelfRef(f, fullKey)) return true;
384
+ if (containsSelfRefInner(f, fullKey, false)) return true;
275
385
  }
276
386
  return false;
277
387
  }
278
388
  return false;
279
389
  }
280
390
  function foldSelfRef(v, fullKey, replacement) {
391
+ return foldSelfRefInner(v, fullKey, replacement, true);
392
+ }
393
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
281
394
  if (isSubst(v)) {
282
- return substFullKey(v) === fullKey ? replacement : v;
395
+ if (substFullKey(v) === fullKey) return replacement;
396
+ if (allowPrefix) {
397
+ const remainder = prefixSelfRefRemainder(v, fullKey);
398
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
399
+ }
400
+ return v;
283
401
  }
284
402
  if (isConcat(v)) {
285
403
  return {
286
404
  _kind: "concat-placeholder",
287
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
405
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
288
406
  line: v.line,
289
407
  col: v.col
290
408
  };
@@ -292,7 +410,7 @@ function foldSelfRef(v, fullKey, replacement) {
292
410
  if (isResObj(v)) {
293
411
  const newFields = /* @__PURE__ */ new Map();
294
412
  for (const [k, val] of v.fields) {
295
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
413
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
296
414
  }
297
415
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
298
416
  }
@@ -300,13 +418,15 @@ function foldSelfRef(v, fullKey, replacement) {
300
418
  if (hv.kind === "array") {
301
419
  return {
302
420
  kind: "array",
303
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
421
+ items: hv.items.map(
422
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
423
+ )
304
424
  };
305
425
  }
306
426
  if (hv.kind === "object") {
307
427
  const newFields = /* @__PURE__ */ new Map();
308
428
  for (const [k, val] of hv.fields) {
309
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
429
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
310
430
  }
311
431
  return { kind: "object", fields: newFields };
312
432
  }
@@ -350,10 +470,26 @@ function foldKnownAbsentSelfRef(v, fullKey, replacement) {
350
470
  function foldOrSkipPrior(prior, fullKey, old) {
351
471
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
352
472
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
473
+ if (isSubst(prior)) {
474
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
475
+ if (remainder !== null) {
476
+ const navigated = navigateResolverValue(old, remainder);
477
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
478
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
479
+ return mergeResObjLayers(
480
+ cloneResolverValue(old),
481
+ cloneResolverValue(navigated)
482
+ );
483
+ }
484
+ }
485
+ }
353
486
  return foldSelfRef(prior, fullKey, old);
354
487
  }
355
488
  function foldOptionalSelfRefAbsent(v, fullKey) {
356
- if (isSubst(v) && substFullKey(v) === fullKey) {
489
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
490
+ }
491
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
492
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
357
493
  if (!v.optional) return void 0;
358
494
  return {
359
495
  _kind: "subst-placeholder",
@@ -369,7 +505,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
369
505
  if (isConcat(v)) {
370
506
  const nodes = [];
371
507
  for (const node of v.nodes) {
372
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
508
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
373
509
  if (folded === void 0) return void 0;
374
510
  nodes.push(folded);
375
511
  }
@@ -378,7 +514,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
378
514
  if (isResObj(v)) {
379
515
  const fields = /* @__PURE__ */ new Map();
380
516
  for (const [key, value] of v.fields) {
381
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
517
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
382
518
  if (folded === void 0) return void 0;
383
519
  fields.set(key, folded);
384
520
  }
@@ -388,7 +524,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
388
524
  if (hv.kind === "array") {
389
525
  const items = [];
390
526
  for (const item of hv.items) {
391
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
527
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
392
528
  if (folded === void 0) return void 0;
393
529
  items.push(folded);
394
530
  }
@@ -397,7 +533,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
397
533
  if (hv.kind === "object") {
398
534
  const fields = /* @__PURE__ */ new Map();
399
535
  for (const [key, value] of hv.fields) {
400
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
536
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
401
537
  if (folded === void 0) return void 0;
402
538
  fields.set(key, folded);
403
539
  }
@@ -770,7 +906,6 @@ var Lexer = class {
770
906
  if (!closed) {
771
907
  throw new ParseError("unterminated triple-quoted string", sl, sc);
772
908
  }
773
- if (value.startsWith("\n")) value = value.slice(1);
774
909
  this.push("triple_string", value, sl, sc, true);
775
910
  continue;
776
911
  }
@@ -1087,9 +1222,9 @@ var Parser = class {
1087
1222
  }
1088
1223
  }
1089
1224
  this.skip("newline");
1090
- const remaining = this.peek();
1091
- if (remaining.kind !== "eof") {
1092
- throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
1225
+ const remaining2 = this.peek();
1226
+ if (remaining2.kind !== "eof") {
1227
+ throw new ParseError(`Unexpected token '${remaining2.value}' after closing brace`, remaining2.line, remaining2.col);
1093
1228
  }
1094
1229
  return { kind: "object", fields: allFields, pos: first.pos };
1095
1230
  }
@@ -1097,13 +1232,19 @@ var Parser = class {
1097
1232
  this.advance();
1098
1233
  const arr = this.parseArray();
1099
1234
  this.skip("newline");
1100
- const remaining = this.peek();
1101
- if (remaining.kind !== "eof") {
1102
- throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1235
+ const remaining2 = this.peek();
1236
+ if (remaining2.kind !== "eof") {
1237
+ throw new ParseError(`Unexpected token '${remaining2.value}' after root array`, remaining2.line, remaining2.col);
1103
1238
  }
1104
1239
  return { ...arr, pos: { line: t.line, col: t.col } };
1105
1240
  }
1106
- return this.parseObject(false);
1241
+ const root = this.parseObject(false);
1242
+ this.skip("newline");
1243
+ const remaining = this.peek();
1244
+ if (remaining.kind !== "eof") {
1245
+ throw new ParseError(`Unexpected token '${remaining.value}' after root content`, remaining.line, remaining.col);
1246
+ }
1247
+ return root;
1107
1248
  }
1108
1249
  peek(offset = 0) {
1109
1250
  return this.tokens[this.pos + offset] ?? EOF_TOKEN;
@@ -1494,6 +1635,39 @@ function stripBom(input) {
1494
1635
  return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
1495
1636
  }
1496
1637
 
1638
+ // src/internal/depth.ts
1639
+ var MAX_PATH_SEGMENTS = 64;
1640
+ function tooDeep(segments) {
1641
+ return segments > MAX_PATH_SEGMENTS;
1642
+ }
1643
+ function guardStackDepth(fn, wrap) {
1644
+ try {
1645
+ return fn();
1646
+ } catch (e) {
1647
+ if (!isStackOverflow(e)) throw e;
1648
+ throw wrap(STACK_MESSAGE);
1649
+ }
1650
+ }
1651
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
1652
+ function isStackOverflow(e) {
1653
+ if (!(e instanceof RangeError)) return false;
1654
+ const m = e.message.toLowerCase();
1655
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
1656
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
1657
+ m.includes("stack overflow");
1658
+ }
1659
+ function depthError(message) {
1660
+ return new ConfigError(message, "");
1661
+ }
1662
+ async function guardStackDepthAsync(fn, wrap) {
1663
+ try {
1664
+ return await fn();
1665
+ } catch (e) {
1666
+ if (!isStackOverflow(e)) throw e;
1667
+ throw wrap(STACK_MESSAGE);
1668
+ }
1669
+ }
1670
+
1497
1671
  // src/internal/properties/properties.ts
1498
1672
  function parseProperties(input) {
1499
1673
  const pairs = [];
@@ -1501,7 +1675,15 @@ function parseProperties(input) {
1501
1675
  const [rawKey, rawValue] = splitKeyValue(text);
1502
1676
  const key = unescapeProps(rawKey, line);
1503
1677
  if (key === "") continue;
1504
- pairs.push([key.split("."), unescapeProps(rawValue, line)]);
1678
+ const segments = key.split(".");
1679
+ if (tooDeep(segments.length)) {
1680
+ throw new ParseError(
1681
+ `key maps to a path ${segments.length} segments deep, over the limit of ${MAX_PATH_SEGMENTS}`,
1682
+ line,
1683
+ 1
1684
+ );
1685
+ }
1686
+ pairs.push([segments, unescapeProps(rawValue, line)]);
1505
1687
  }
1506
1688
  return nestPairs(pairs);
1507
1689
  }
@@ -2219,6 +2401,12 @@ var SubstitutionResolver = class {
2219
2401
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
2220
2402
  // same prefix-match semantics).
2221
2403
  this.resolvingFieldPath = [];
2404
+ // S13a.12 recursion guard: field keys whose prior is currently being
2405
+ // resolved through the prefix-self-ref branch. Saved priors are
2406
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
2407
+ // a shape the fold could not see through (a live subst mid-navigation) —
2408
+ // report it as a cycle instead of recursing forever.
2409
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
2222
2410
  }
2223
2411
  /** Returns a prefix for error messages, including origin info when set. */
2224
2412
  originPrefix() {
@@ -2233,46 +2421,57 @@ var SubstitutionResolver = class {
2233
2421
  this.resolvingFieldPath.push(key);
2234
2422
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
2235
2423
  this.cache.delete(fullCacheKey);
2236
- let resolved;
2237
2424
  try {
2238
- resolved = this.resolveVal(val, obj);
2239
- } finally {
2240
- this.resolvingFieldPath.pop();
2241
- }
2242
- if (resolved !== void 0) {
2243
- if (resolved.kind === "object") {
2425
+ const resolved = this.resolveVal(val, obj);
2426
+ if (resolved !== void 0) {
2427
+ if (resolved.kind === "object") {
2428
+ const prior = obj.priorValues.get(key);
2429
+ if (prior !== void 0) {
2430
+ const priorResolved = this.resolveVal(prior, obj);
2431
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
2432
+ const finalValue = deepMergeHoconValues(
2433
+ priorResolved,
2434
+ resolved
2435
+ );
2436
+ result.set(key, finalValue);
2437
+ this.cache.set(fullCacheKey, finalValue);
2438
+ this.cacheDescendants(fullCacheKey, finalValue);
2439
+ continue;
2440
+ }
2441
+ }
2442
+ }
2443
+ result.set(key, resolved);
2444
+ this.cache.set(fullCacheKey, resolved);
2445
+ this.cacheDescendants(fullCacheKey, resolved);
2446
+ } else {
2244
2447
  const prior = obj.priorValues.get(key);
2245
2448
  if (prior !== void 0) {
2246
2449
  const priorResolved = this.resolveVal(prior, obj);
2247
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
2248
- const finalValue = deepMergeHoconValues(
2249
- priorResolved,
2250
- resolved
2251
- );
2252
- result.set(key, finalValue);
2253
- this.cache.set(fullCacheKey, finalValue);
2254
- this.cacheDescendants(fullCacheKey, finalValue);
2255
- continue;
2450
+ if (priorResolved !== void 0) {
2451
+ result.set(key, priorResolved);
2452
+ this.cache.set(fullCacheKey, priorResolved);
2453
+ this.cacheDescendants(fullCacheKey, priorResolved);
2256
2454
  }
2257
2455
  }
2258
2456
  }
2259
- result.set(key, resolved);
2260
- this.cache.set(fullCacheKey, resolved);
2261
- this.cacheDescendants(fullCacheKey, resolved);
2262
- } else {
2263
- const prior = obj.priorValues.get(key);
2264
- if (prior !== void 0) {
2265
- const priorResolved = this.resolveVal(prior, obj);
2266
- if (priorResolved !== void 0) {
2267
- result.set(key, priorResolved);
2268
- this.cache.set(fullCacheKey, priorResolved);
2269
- this.cacheDescendants(fullCacheKey, priorResolved);
2270
- }
2271
- }
2457
+ } finally {
2458
+ this.resolvingFieldPath.pop();
2272
2459
  }
2273
2460
  }
2274
2461
  return { kind: "object", fields: result };
2275
2462
  }
2463
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
2464
+ * or a walk into a scalar/array is path-absent → undefined. */
2465
+ navigateResolved(v, remainder) {
2466
+ let cur = v;
2467
+ for (const seg of remainder) {
2468
+ if (cur.kind !== "object") return void 0;
2469
+ const next = cur.fields.get(seg);
2470
+ if (next === void 0) return void 0;
2471
+ cur = next;
2472
+ }
2473
+ return cur;
2474
+ }
2276
2475
  cacheDescendants(prefix, value) {
2277
2476
  if (value.kind !== "object") return;
2278
2477
  for (const [key, child] of value.fields) {
@@ -2295,17 +2494,59 @@ var SubstitutionResolver = class {
2295
2494
  if (isResObj(v)) return this.resolveResObj(v);
2296
2495
  const hv = v;
2297
2496
  if (hv.kind === "array") {
2298
- return {
2299
- kind: "array",
2300
- items: hv.items.map(
2301
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
2302
- )
2303
- };
2497
+ const items = [];
2498
+ for (const item of hv.items) {
2499
+ const resolved = this.resolveVal(item, scope);
2500
+ if (resolved === void 0) continue;
2501
+ items.push(resolved);
2502
+ }
2503
+ return { kind: "array", items };
2304
2504
  }
2305
2505
  return hv;
2306
2506
  }
2307
2507
  resolveSubst(s, scope) {
2308
- if (s.knownAbsent) return void 0;
2508
+ if (s.knownAbsent) {
2509
+ if (!s.optional) {
2510
+ if (this.opts.allowUnresolved) return s;
2511
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2512
+ throw new ResolveError(
2513
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2514
+ k,
2515
+ s.line,
2516
+ s.col
2517
+ );
2518
+ }
2519
+ return void 0;
2520
+ }
2521
+ {
2522
+ const rfp = this.resolvingFieldPath;
2523
+ const sTexts = s.segments.map((seg) => seg.text);
2524
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
2525
+ const guardKey = stringSegmentsToKey(rfp);
2526
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
2527
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
2528
+ this.resolvingPrefixPriors.add(guardKey);
2529
+ try {
2530
+ const priorResolved = this.resolveVal(prior, scope);
2531
+ if (priorResolved !== void 0) {
2532
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
2533
+ if (navigated !== void 0) return navigated;
2534
+ }
2535
+ } finally {
2536
+ this.resolvingPrefixPriors.delete(guardKey);
2537
+ }
2538
+ }
2539
+ if (s.optional) return void 0;
2540
+ if (this.opts.allowUnresolved) return s;
2541
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2542
+ throw new ResolveError(
2543
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2544
+ k,
2545
+ s.line,
2546
+ s.col
2547
+ );
2548
+ }
2549
+ }
2309
2550
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2310
2551
  if (this.cache.has(key)) return this.cache.get(key);
2311
2552
  if (this.resolving.has(key)) {
@@ -2636,6 +2877,121 @@ async function resolveAsync(ast, opts) {
2636
2877
  return resolveTree(root, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
2637
2878
  }
2638
2879
 
2880
+ // src/render-hocon.ts
2881
+ function renderHoconFromRoot(root) {
2882
+ const out = [];
2883
+ renderObjectBody(out, root, 0);
2884
+ return out.join("");
2885
+ }
2886
+ function renderObjectBody(out, o, depth) {
2887
+ const indent = " ".repeat(depth);
2888
+ for (const [k, v] of o.fields) {
2889
+ out.push(indent, renderKey(k));
2890
+ if (v.kind === "object") {
2891
+ out.push(" {");
2892
+ if (v.fields.size === 0) {
2893
+ out.push("}\n");
2894
+ continue;
2895
+ }
2896
+ out.push("\n");
2897
+ renderObjectBody(out, v, depth + 1);
2898
+ out.push(indent, "}\n");
2899
+ } else {
2900
+ out.push(" = ");
2901
+ renderValue(out, v, depth);
2902
+ out.push("\n");
2903
+ }
2904
+ }
2905
+ }
2906
+ function renderValue(out, v, depth) {
2907
+ switch (v.kind) {
2908
+ case "object": {
2909
+ if (v.fields.size === 0) {
2910
+ out.push("{}");
2911
+ return;
2912
+ }
2913
+ out.push("{\n");
2914
+ renderObjectBody(out, v, depth + 1);
2915
+ out.push(" ".repeat(depth), "}");
2916
+ return;
2917
+ }
2918
+ case "array":
2919
+ renderArray(out, v, depth);
2920
+ return;
2921
+ case "scalar":
2922
+ out.push(renderScalar(v));
2923
+ return;
2924
+ default:
2925
+ throw new ConfigError(
2926
+ "renderHocon: unrenderable value (config must be resolved data)",
2927
+ ""
2928
+ );
2929
+ }
2930
+ }
2931
+ function renderArray(out, a, depth) {
2932
+ if (a.items.length === 0) {
2933
+ out.push("[]");
2934
+ return;
2935
+ }
2936
+ const inner = " ".repeat(depth + 1);
2937
+ out.push("[\n");
2938
+ for (const e of a.items) {
2939
+ out.push(inner);
2940
+ renderValue(out, e, depth + 1);
2941
+ out.push("\n");
2942
+ }
2943
+ out.push(" ".repeat(depth), "]");
2944
+ }
2945
+ function renderScalar(s) {
2946
+ switch (s.valueType) {
2947
+ case "null":
2948
+ return "null";
2949
+ case "boolean":
2950
+ case "number":
2951
+ return s.raw;
2952
+ case "string":
2953
+ return renderString(s.raw);
2954
+ }
2955
+ }
2956
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
2957
+ function renderKey(k) {
2958
+ if (k === "include") return quoteString(k);
2959
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
2960
+ }
2961
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
2962
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
2963
+ function renderString(s) {
2964
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
2965
+ }
2966
+ function quoteString(s) {
2967
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
2968
+ return `"""${s}"""`;
2969
+ }
2970
+ let b = '"';
2971
+ for (const ch of s) {
2972
+ switch (ch) {
2973
+ case '"':
2974
+ b += '\\"';
2975
+ break;
2976
+ case "\\":
2977
+ b += "\\\\";
2978
+ break;
2979
+ case "\n":
2980
+ b += "\\n";
2981
+ break;
2982
+ case "\r":
2983
+ b += "\\r";
2984
+ break;
2985
+ case " ":
2986
+ b += "\\t";
2987
+ break;
2988
+ default:
2989
+ b += ch;
2990
+ }
2991
+ }
2992
+ return b + '"';
2993
+ }
2994
+
2639
2995
  // src/config.ts
2640
2996
  var Config = class _Config {
2641
2997
  constructor(root, opts) {
@@ -2941,6 +3297,36 @@ var Config = class _Config {
2941
3297
  toObject(opts) {
2942
3298
  return hoconToJs(this.root, opts?.nullPrototype === true);
2943
3299
  }
3300
+ /**
3301
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
3302
+ * `Config.RenderHOCON`, v1.11.0).
3303
+ *
3304
+ * The output round-trips: parsing it back yields the same value tree. That
3305
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
3306
+ * quoted whenever leaving it bare would re-parse as a different type (a
3307
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
3308
+ * provably cannot.
3309
+ *
3310
+ * The Config must be resolved and hold only data (objects, arrays, string /
3311
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
3312
+ * format adapters produce. An unresolved placeholder throws
3313
+ * {@link ConfigError}; substitutions have no textual round trip through a
3314
+ * value tree.
3315
+ *
3316
+ * The root object's fields are emitted without enclosing braces, nested
3317
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
3318
+ * spaces. Source comments are not represented — a value tree does not carry
3319
+ * them.
3320
+ */
3321
+ renderHocon() {
3322
+ if (!this._resolved) {
3323
+ throw new ConfigError(
3324
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
3325
+ ""
3326
+ );
3327
+ }
3328
+ return renderHoconFromRoot(this.root);
3329
+ }
2944
3330
  /**
2945
3331
  * @internal Test-only: renders this resolved Config's value tree as canonical
2946
3332
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
@@ -3194,13 +3580,16 @@ function buildResolveContext(input, opts) {
3194
3580
  return { ast, resolveOpts };
3195
3581
  }
3196
3582
  function parse(input, opts = {}) {
3197
- const { ast, resolveOpts } = buildResolveContext(input, opts);
3198
- const value = resolve2(ast, resolveOpts);
3199
- if (value.kind !== "object") throw new Error("resolved value is not an object");
3200
- return new Config(value, { resolved: true, originDescription: opts.originDescription });
3583
+ return parseStringWithOptions(input, opts);
3201
3584
  }
3202
3585
  var parseString = parse;
3203
3586
  function parseStringWithOptions(input, opts = {}) {
3587
+ return guardStackDepth(
3588
+ () => parseStringInner(input, opts),
3589
+ (msg) => new ParseError(msg, 1, 1, opts.originDescription)
3590
+ );
3591
+ }
3592
+ function parseStringInner(input, opts) {
3204
3593
  const resolveSubstitutions = opts.resolveSubstitutions ?? true;
3205
3594
  const { ast, resolveOpts } = buildResolveContext(input, opts);
3206
3595
  if (resolveSubstitutions) {
@@ -3231,10 +3620,12 @@ function parseFileWithOptions(filePath, opts = {}) {
3231
3620
  });
3232
3621
  }
3233
3622
  async function parseAsync(input, opts = {}) {
3234
- const { ast, resolveOpts } = buildResolveContext(input, opts);
3235
- const value = await resolveAsync(ast, resolveOpts);
3236
- if (value.kind !== "object") throw new Error("resolved value is not an object");
3237
- return new Config(value);
3623
+ return guardStackDepthAsync(async () => {
3624
+ const { ast, resolveOpts } = buildResolveContext(input, opts);
3625
+ const value = await resolveAsync(ast, resolveOpts);
3626
+ if (value.kind !== "object") throw new Error("resolved value is not an object");
3627
+ return new Config(value);
3628
+ }, (msg) => new ParseError(msg, 1, 1, opts.originDescription));
3238
3629
  }
3239
3630
  function parseFile(filePath, opts = {}) {
3240
3631
  if (typeof process === "undefined" && !opts.readFileSync) {
@@ -3295,7 +3686,10 @@ function isNull(v) {
3295
3686
 
3296
3687
  // src/value-factory.ts
3297
3688
  function fromMap(values, originDescription) {
3298
- const fields = coerceObject(values, "");
3689
+ const fields = guardStackDepth(
3690
+ () => coerceObject(values, ""),
3691
+ (msg) => depthError(`fromMap: ${msg}`)
3692
+ );
3299
3693
  const root = { kind: "object", fields };
3300
3694
  return Config._fromResolvedValue(root, { originDescription });
3301
3695
  }