@o3co/ts.hocon 1.6.0 → 1.6.1

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
@@ -237,55 +237,6 @@ var NotResolvedError = class extends ConfigError {
237
237
  }
238
238
  };
239
239
 
240
- // src/internal/resolver/types.ts
241
- var separatorValues = /* @__PURE__ */ new WeakSet();
242
- function isSubst(v) {
243
- return v._kind === "subst-placeholder";
244
- }
245
- function isConcat(v) {
246
- return v._kind === "concat-placeholder";
247
- }
248
- function isAppend(v) {
249
- return v._kind === "append-placeholder";
250
- }
251
- function isResObj(v) {
252
- return v._kind === "res-obj";
253
- }
254
- function makeResObj() {
255
- return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
256
- }
257
- function mergeUnresolved(receiver, fallback) {
258
- const result = makeResObj();
259
- for (const [k, v] of fallback.fields) {
260
- result.fields.set(k, v);
261
- }
262
- for (const [k, v] of fallback.priorValues) {
263
- result.priorValues.set(k, v);
264
- }
265
- for (const [k, rv] of receiver.fields) {
266
- const existing = result.fields.get(k);
267
- if (existing !== void 0) {
268
- const recPrior = receiver.priorValues.get(k);
269
- if (recPrior !== void 0 && !isResObj(recPrior)) {
270
- result.fields.set(k, rv);
271
- continue;
272
- }
273
- if (isResObj(rv) && isResObj(existing)) {
274
- result.fields.set(k, mergeUnresolved(rv, existing));
275
- continue;
276
- }
277
- result.priorValues.set(k, existing);
278
- }
279
- result.fields.set(k, rv);
280
- }
281
- for (const [k, v] of receiver.priorValues) {
282
- if (receiver.fields.has(k)) {
283
- result.priorValues.set(k, v);
284
- }
285
- }
286
- return result;
287
- }
288
-
289
240
  // src/internal/resolver/fold-self-ref.ts
290
241
  function substFullKey(s) {
291
242
  return stringSegmentsToKey(s.segments.map((seg) => seg.text));
@@ -301,9 +252,6 @@ function stringSegmentsToKey(segments) {
301
252
  function containsSelfRef(v, fullKey) {
302
253
  if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
303
254
  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
255
  if (isResObj(v)) {
308
256
  for (const f of v.fields.values()) {
309
257
  if (containsSelfRef(f, fullKey)) return true;
@@ -334,19 +282,12 @@ function foldSelfRef(v, fullKey, replacement) {
334
282
  col: v.col
335
283
  };
336
284
  }
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
285
  if (isResObj(v)) {
345
286
  const newFields = /* @__PURE__ */ new Map();
346
287
  for (const [k, val] of v.fields) {
347
288
  newFields.set(k, foldSelfRef(val, fullKey, replacement));
348
289
  }
349
- return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
290
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
350
291
  }
351
292
  const hv = v;
352
293
  if (hv.kind === "array") {
@@ -364,6 +305,41 @@ function foldSelfRef(v, fullKey, replacement) {
364
305
  }
365
306
  return v;
366
307
  }
308
+ function foldKnownAbsentSelfRef(v, fullKey, replacement) {
309
+ if (isSubst(v)) {
310
+ return v.knownAbsent && substFullKey(v) === fullKey ? replacement : v;
311
+ }
312
+ if (isConcat(v)) {
313
+ return {
314
+ _kind: "concat-placeholder",
315
+ nodes: v.nodes.map((n) => foldKnownAbsentSelfRef(n, fullKey, replacement)),
316
+ line: v.line,
317
+ col: v.col
318
+ };
319
+ }
320
+ if (isResObj(v)) {
321
+ const newFields = /* @__PURE__ */ new Map();
322
+ for (const [k, val] of v.fields) {
323
+ newFields.set(k, foldKnownAbsentSelfRef(val, fullKey, replacement));
324
+ }
325
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
326
+ }
327
+ const hv = v;
328
+ if (hv.kind === "array") {
329
+ return {
330
+ kind: "array",
331
+ items: hv.items.map((item) => foldKnownAbsentSelfRef(item, fullKey, replacement))
332
+ };
333
+ }
334
+ if (hv.kind === "object") {
335
+ const newFields = /* @__PURE__ */ new Map();
336
+ for (const [k, val] of hv.fields) {
337
+ newFields.set(k, foldKnownAbsentSelfRef(val, fullKey, replacement));
338
+ }
339
+ return { kind: "object", fields: newFields };
340
+ }
341
+ return v;
342
+ }
367
343
  function foldOrSkipPrior(prior, fullKey, old) {
368
344
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
369
345
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
@@ -392,12 +368,6 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
392
368
  }
393
369
  return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
394
370
  }
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
371
  if (isResObj(v)) {
402
372
  const fields = /* @__PURE__ */ new Map();
403
373
  for (const [key, value] of v.fields) {
@@ -405,7 +375,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
405
375
  if (folded === void 0) return void 0;
406
376
  fields.set(key, folded);
407
377
  }
408
- return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues) };
378
+ return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
409
379
  }
410
380
  const hv = v;
411
381
  if (hv.kind === "array") {
@@ -454,14 +424,7 @@ function cloneResolverValue(v) {
454
424
  for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
455
425
  const newPriors = /* @__PURE__ */ new Map();
456
426
  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
- };
427
+ return { _kind: "res-obj", fields: newFields, priorValues: newPriors, resetKeys: new Set(v.resetKeys) };
465
428
  }
466
429
  const hv = v;
467
430
  if (hv.kind === "array") {
@@ -496,14 +459,11 @@ function foldNestedSelfRefs(v, pathPrefix) {
496
459
  }
497
460
  newFields.set(k, finalVal);
498
461
  }
499
- return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
462
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
500
463
  }
501
464
  function containsSubstByPath(v, target) {
502
465
  if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
503
466
  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
467
  if (isResObj(v)) {
508
468
  for (const f of v.fields.values()) {
509
469
  if (containsSubstByPath(f, target)) return true;
@@ -532,6 +492,53 @@ function segmentsTextEqual(a, b) {
532
492
  return true;
533
493
  }
534
494
 
495
+ // src/internal/resolver/types.ts
496
+ var separatorValues = /* @__PURE__ */ new WeakSet();
497
+ function isSubst(v) {
498
+ return v._kind === "subst-placeholder";
499
+ }
500
+ function isConcat(v) {
501
+ return v._kind === "concat-placeholder";
502
+ }
503
+ function isResObj(v) {
504
+ return v._kind === "res-obj";
505
+ }
506
+ function makeResObj() {
507
+ return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map(), resetKeys: /* @__PURE__ */ new Set() };
508
+ }
509
+ function mergeUnresolved(receiver, fallback) {
510
+ const result = makeResObj();
511
+ for (const [k, v] of fallback.fields) {
512
+ result.fields.set(k, v);
513
+ }
514
+ for (const [k, v] of fallback.priorValues) {
515
+ result.priorValues.set(k, v);
516
+ }
517
+ for (const [k, rv] of receiver.fields) {
518
+ const existing = result.fields.get(k);
519
+ if (existing !== void 0) {
520
+ const recPrior = receiver.priorValues.get(k);
521
+ if (recPrior !== void 0 && !isResObj(recPrior)) {
522
+ result.fields.set(k, rv);
523
+ continue;
524
+ }
525
+ if (isResObj(rv) && isResObj(existing)) {
526
+ result.fields.set(k, mergeUnresolved(rv, existing));
527
+ continue;
528
+ }
529
+ const foldedPrior = foldOrSkipPrior(existing, k, void 0);
530
+ if (foldedPrior !== void 0) result.priorValues.set(k, foldedPrior);
531
+ }
532
+ result.fields.set(k, rv);
533
+ }
534
+ for (const [k, v] of receiver.priorValues) {
535
+ if (receiver.fields.has(k)) {
536
+ result.priorValues.set(k, v);
537
+ }
538
+ }
539
+ return result;
540
+ }
541
+
535
542
  // src/internal/resolver/utils.ts
536
543
  function segmentsToKey(segments) {
537
544
  return segments.map((s) => {
@@ -585,9 +592,19 @@ function deepMergeResObjInto(dst, src, pathPrefix = []) {
585
592
  deepMergeResObjInto(dstVal, srcVal, childPrefix);
586
593
  } else {
587
594
  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);
595
+ if (src.resetKeys.has(k)) {
596
+ dst.priorValues.delete(k);
597
+ } else {
598
+ const dstFolded = foldOrSkipPrior(dstVal, fullKey, dst.priorValues.get(k));
599
+ if (dstFolded !== void 0) {
600
+ const srcPrior = src.priorValues.get(k);
601
+ if (srcPrior !== void 0) {
602
+ dst.priorValues.set(k, foldKnownAbsentSelfRef(srcPrior, fullKey, dstFolded));
603
+ } else {
604
+ dst.priorValues.set(k, dstFolded);
605
+ }
606
+ }
607
+ }
591
608
  }
592
609
  dst.fields.set(k, srcVal);
593
610
  }
@@ -597,6 +614,7 @@ function deepMergeResObjInto(dst, src, pathPrefix = []) {
597
614
  dst.priorValues.set(k, srcPrior);
598
615
  }
599
616
  }
617
+ for (const k of src.resetKeys) dst.resetKeys.add(k);
600
618
  }
601
619
  function hoconValueToResObj(hv) {
602
620
  const obj = makeResObj();
@@ -1852,31 +1870,15 @@ var StructureBuilder = class _StructureBuilder {
1852
1870
  const childPrefix = [...pathPrefix, head];
1853
1871
  const fullKey = stringSegmentsToKey(childPrefix);
1854
1872
  if (field.append) {
1855
- const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1856
- const oldPrior = obj.priorValues.get(head);
1857
- const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
1858
- if (folded !== void 0) obj.priorValues.set(head, folded);
1859
- obj.fields.set(head, {
1860
- _kind: "append-placeholder",
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,
1874
- elem: this.astToResolverValue(field.value, childPrefix)
1875
- });
1873
+ const synthetic = this.desugarAppend(field, childPrefix);
1874
+ this.applyField(obj, synthetic, pathPrefix);
1876
1875
  return;
1877
1876
  }
1878
1877
  const existing = obj.fields.get(head);
1879
1878
  const newVal = this.astToResolverValue(field.value, childPrefix);
1879
+ if (!containsSelfRef(newVal, fullKey)) {
1880
+ obj.resetKeys.add(head);
1881
+ }
1880
1882
  if (existing !== void 0) {
1881
1883
  const oldPrior = obj.priorValues.get(head);
1882
1884
  const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
@@ -1918,19 +1920,15 @@ var StructureBuilder = class _StructureBuilder {
1918
1920
  const childPrefix = [...pathPrefix, head];
1919
1921
  const fullKey = stringSegmentsToKey(childPrefix);
1920
1922
  if (field.append) {
1921
- const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1922
- const oldPrior = obj.priorValues.get(head);
1923
- const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
1924
- if (folded !== void 0) obj.priorValues.set(head, folded);
1925
- obj.fields.set(head, {
1926
- _kind: "append-placeholder",
1927
- existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
1928
- elem: await this.astToResolverValueAsync(field.value, childPrefix)
1929
- });
1923
+ const synthetic = this.desugarAppend(field, childPrefix);
1924
+ await this.applyFieldAsync(obj, synthetic, pathPrefix);
1930
1925
  return;
1931
1926
  }
1932
1927
  const existing = obj.fields.get(head);
1933
1928
  const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
1929
+ if (!containsSelfRef(newVal, fullKey)) {
1930
+ obj.resetKeys.add(head);
1931
+ }
1934
1932
  if (existing !== void 0) {
1935
1933
  const oldPrior = obj.priorValues.get(head);
1936
1934
  const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
@@ -1943,6 +1941,21 @@ var StructureBuilder = class _StructureBuilder {
1943
1941
  }
1944
1942
  obj.fields.set(head, newVal);
1945
1943
  }
1944
+ /** Builds the `key = ${?fullkey} [value]` field that `key += value` desugars
1945
+ * to (S13b.2, HOCON.md L732). `childPrefix` is the field's fully-qualified
1946
+ * path (pathPrefix + key), so a nested `srv { items += x }` references
1947
+ * `${?srv.items}`; include relativization rewrites it under a mount prefix.
1948
+ * `field.key` is already single-segment here (multi-segment keys are split
1949
+ * into nested objects upstream). Returns a NEW AstField (append=false);
1950
+ * positions inherit from the original field so resolve-time errors (the
1951
+ * S13b.2 non-array check, now via the scalar+array concat) keep a location. */
1952
+ desugarAppend(field, childPrefix) {
1953
+ const segments = childPrefix.map((text) => ({ text, line: field.pos.line, col: field.pos.col }));
1954
+ const subst = { kind: "subst", segments, optional: true, listSuffix: false, pos: field.pos };
1955
+ const elemArray = { kind: "array", items: [field.value], pos: field.pos };
1956
+ const synthetic = { kind: "concat", nodes: [subst, elemArray], pos: field.pos };
1957
+ return { key: field.key, value: synthetic, append: false, pos: field.pos };
1958
+ }
1946
1959
  astToResolverValue(ast, pathPrefix) {
1947
1960
  switch (ast.kind) {
1948
1961
  case "scalar": {
@@ -2007,11 +2020,6 @@ var StructureBuilder = class _StructureBuilder {
2007
2020
  }
2008
2021
  return;
2009
2022
  }
2010
- if (isAppend(val)) {
2011
- this.relativizeSubstPaths(val.existing, prefixSegments);
2012
- this.relativizeSubstPaths(val.elem, prefixSegments);
2013
- return;
2014
- }
2015
2023
  if (isResObj(val)) {
2016
2024
  this.relativizeResObj(val, prefixSegments);
2017
2025
  return;
@@ -2160,7 +2168,6 @@ var SubstitutionResolver = class {
2160
2168
  this.resolvingConcats.delete(v);
2161
2169
  }
2162
2170
  }
2163
- if (isAppend(v)) return this.resolveAppend(v, scope);
2164
2171
  if (isResObj(v)) return this.resolveResObj(v);
2165
2172
  const hv = v;
2166
2173
  if (hv.kind === "array") {
@@ -2445,38 +2452,6 @@ var SubstitutionResolver = class {
2445
2452
  }
2446
2453
  return folded;
2447
2454
  }
2448
- resolveAppend(a, scope) {
2449
- const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
2450
- if (this.opts.allowUnresolved && (isSubst(existing) || isConcat(existing) || isAppend(existing))) {
2451
- return a;
2452
- }
2453
- const elem = this.resolveVal(a.elem, scope);
2454
- let priorItems;
2455
- if (existing.kind === "array") {
2456
- priorItems = existing.items;
2457
- } else if (existing.kind === "object") {
2458
- const converted = numericObjectToArray(existing);
2459
- if (converted === null) {
2460
- throw new ResolveError(
2461
- `'+=' on non-array value: prior value is object with non-numeric keys (spec L732)`,
2462
- "",
2463
- 0,
2464
- 0
2465
- );
2466
- }
2467
- priorItems = converted;
2468
- } else {
2469
- throw new ResolveError(
2470
- `'+=' on non-array value: prior value is ${existing.kind} (spec L732)`,
2471
- "",
2472
- 0,
2473
- 0
2474
- );
2475
- }
2476
- const items = [...priorItems];
2477
- if (elem !== void 0) items.push(elem);
2478
- return { kind: "array", items };
2479
- }
2480
2455
  };
2481
2456
 
2482
2457
  // src/internal/resolver/resolver.ts
@@ -2494,7 +2469,7 @@ function containsPlaceholders(tree) {
2494
2469
  }
2495
2470
  function valContainsPlaceholders(v) {
2496
2471
  const rv = v;
2497
- if (isSubst(rv) || isConcat(rv) || isAppend(rv)) return true;
2472
+ if (isSubst(rv) || isConcat(rv)) return true;
2498
2473
  if (isResObj(rv)) return containsPlaceholders(rv);
2499
2474
  const hv = v;
2500
2475
  if (hv?.kind === "array") {
@@ -2505,7 +2480,7 @@ function valContainsPlaceholders(v) {
2505
2480
  function buildPartialHoconFromResObj(tree) {
2506
2481
  const fields = /* @__PURE__ */ new Map();
2507
2482
  for (const [k, v] of tree.fields) {
2508
- if (!isSubst(v) && !isConcat(v) && !isAppend(v)) {
2483
+ if (!isSubst(v) && !isConcat(v)) {
2509
2484
  if (isResObj(v)) {
2510
2485
  fields.set(k, buildPartialHoconFromResObj(v));
2511
2486
  } else {
@@ -2898,7 +2873,7 @@ function hoconToJs(v) {
2898
2873
  function resObjToKeyShape(tree) {
2899
2874
  const fields = /* @__PURE__ */ new Map();
2900
2875
  for (const [k, v] of tree.fields) {
2901
- if (isSubst(v) || isConcat(v) || isAppend(v)) {
2876
+ if (isSubst(v) || isConcat(v)) {
2902
2877
  fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
2903
2878
  } else if (isResObj(v)) {
2904
2879
  fields.set(k, resObjToKeyShape(v));
@@ -2912,7 +2887,7 @@ function stripPlaceholderFields(v) {
2912
2887
  const fields = /* @__PURE__ */ new Map();
2913
2888
  let hadPlaceholders = false;
2914
2889
  for (const [k, val] of v.fields) {
2915
- if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder" || val._kind === "append-placeholder") {
2890
+ if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder") {
2916
2891
  hadPlaceholders = true;
2917
2892
  } else if (val.kind === "object") {
2918
2893
  const inner = stripPlaceholderFields(val);