@o3co/ts.hocon 1.5.2 → 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.
@@ -24,6 +24,8 @@ type SubstPlaceholder = {
24
24
  _kind: 'subst-placeholder';
25
25
  segments: Segment[];
26
26
  optional: boolean;
27
+ /** Internal sentinel for a folded optional self-ref with no prior value. */
28
+ knownAbsent: boolean;
27
29
  listSuffix: boolean;
28
30
  line: number;
29
31
  col: number;
@@ -24,6 +24,8 @@ type SubstPlaceholder = {
24
24
  _kind: 'subst-placeholder';
25
25
  segments: Segment[];
26
26
  optional: boolean;
27
+ /** Internal sentinel for a folded optional self-ref with no prior value. */
28
+ knownAbsent: boolean;
27
29
  listSuffix: boolean;
28
30
  line: number;
29
31
  col: number;
package/dist/index.cjs CHANGED
@@ -351,7 +351,7 @@ function stringSegmentsToKey(segments) {
351
351
  }).join(".");
352
352
  }
353
353
  function containsSelfRef(v, fullKey) {
354
- if (isSubst(v)) return substFullKey(v) === fullKey;
354
+ if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
355
355
  if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
356
356
  if (isAppend(v)) {
357
357
  return containsSelfRef(v.existing, fullKey) || containsSelfRef(v.elem, fullKey);
@@ -418,15 +418,75 @@ function foldSelfRef(v, fullKey, replacement) {
418
418
  }
419
419
  function foldOrSkipPrior(prior, fullKey, old) {
420
420
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
421
- if (old === void 0) return void 0;
421
+ if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
422
422
  return foldSelfRef(prior, fullKey, old);
423
423
  }
424
+ function foldOptionalSelfRefAbsent(v, fullKey) {
425
+ if (isSubst(v) && substFullKey(v) === fullKey) {
426
+ if (!v.optional) return void 0;
427
+ return {
428
+ _kind: "subst-placeholder",
429
+ segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
430
+ optional: v.optional,
431
+ knownAbsent: true,
432
+ listSuffix: v.listSuffix,
433
+ line: v.line,
434
+ col: v.col,
435
+ prefixLen: v.prefixLen
436
+ };
437
+ }
438
+ if (isConcat(v)) {
439
+ const nodes = [];
440
+ for (const node of v.nodes) {
441
+ const folded = foldOptionalSelfRefAbsent(node, fullKey);
442
+ if (folded === void 0) return void 0;
443
+ nodes.push(folded);
444
+ }
445
+ return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
446
+ }
447
+ if (isAppend(v)) {
448
+ const existing = foldOptionalSelfRefAbsent(v.existing, fullKey);
449
+ const elem = foldOptionalSelfRefAbsent(v.elem, fullKey);
450
+ if (existing === void 0 || elem === void 0) return void 0;
451
+ return { _kind: "append-placeholder", existing, elem };
452
+ }
453
+ if (isResObj(v)) {
454
+ const fields = /* @__PURE__ */ new Map();
455
+ for (const [key, value] of v.fields) {
456
+ const folded = foldOptionalSelfRefAbsent(value, fullKey);
457
+ if (folded === void 0) return void 0;
458
+ fields.set(key, folded);
459
+ }
460
+ return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues) };
461
+ }
462
+ const hv = v;
463
+ if (hv.kind === "array") {
464
+ const items = [];
465
+ for (const item of hv.items) {
466
+ const folded = foldOptionalSelfRefAbsent(item, fullKey);
467
+ if (folded === void 0) return void 0;
468
+ items.push(folded);
469
+ }
470
+ return { kind: "array", items };
471
+ }
472
+ if (hv.kind === "object") {
473
+ const fields = /* @__PURE__ */ new Map();
474
+ for (const [key, value] of hv.fields) {
475
+ const folded = foldOptionalSelfRefAbsent(value, fullKey);
476
+ if (folded === void 0) return void 0;
477
+ fields.set(key, folded);
478
+ }
479
+ return { kind: "object", fields };
480
+ }
481
+ return cloneResolverValue(v);
482
+ }
424
483
  function cloneResolverValue(v) {
425
484
  if (isSubst(v)) {
426
485
  return {
427
486
  _kind: "subst-placeholder",
428
487
  segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
429
488
  optional: v.optional,
489
+ knownAbsent: v.knownAbsent,
430
490
  listSuffix: v.listSuffix,
431
491
  line: v.line,
432
492
  col: v.col,
@@ -491,7 +551,7 @@ function foldNestedSelfRefs(v, pathPrefix) {
491
551
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
492
552
  }
493
553
  function containsSubstByPath(v, target) {
494
- if (isSubst(v)) return segmentsTextEqual(v.segments, target);
554
+ if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
495
555
  if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
496
556
  if (isAppend(v)) {
497
557
  return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
@@ -658,6 +718,11 @@ var Lexer = class {
658
718
  this.line = 1;
659
719
  this.col = 1;
660
720
  this.hadSpace = false;
721
+ // Accumulates literal whitespace chars consumed between tokens. Reset on every
722
+ // token push. Used so the parser can preserve path-expression whitespace
723
+ // (xx.hocon#42, E13) — e.g. tab vs space adjacent to dots in key paths.
724
+ // Comment text is NOT accumulated; only the actual WS chars.
725
+ this.whitespaceBuffer = "";
661
726
  this.tokens = [];
662
727
  if (input.charCodeAt(0) === 65279) this.input = input.slice(1);
663
728
  }
@@ -673,6 +738,7 @@ var Lexer = class {
673
738
  continue;
674
739
  }
675
740
  if (isHoconWhitespace(ch)) {
741
+ this.whitespaceBuffer += ch;
676
742
  this.advance();
677
743
  this.hadSpace = true;
678
744
  continue;
@@ -764,7 +830,7 @@ var Lexer = class {
764
830
  }
765
831
  throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
766
832
  }
767
- this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false });
833
+ this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false, precedingWhitespace: "" });
768
834
  return this.tokens;
769
835
  }
770
836
  peek(offset = 0) {
@@ -781,12 +847,14 @@ var Lexer = class {
781
847
  return ch;
782
848
  }
783
849
  push(kind, value, l, c, isQuoted = false) {
784
- this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
850
+ this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer });
785
851
  this.hadSpace = false;
852
+ this.whitespaceBuffer = "";
786
853
  }
787
854
  pushSubst(payload, value, l, c) {
788
- this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
855
+ this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer, subst: payload });
789
856
  this.hadSpace = false;
857
+ this.whitespaceBuffer = "";
790
858
  }
791
859
  /**
792
860
  * Read body of a quoted string. Opening `"` must already be consumed.
@@ -1032,7 +1100,7 @@ function isUnquotedContinue(ch, nextFn) {
1032
1100
  }
1033
1101
 
1034
1102
  // src/internal/parser/parser.ts
1035
- var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
1103
+ var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, precedingWhitespace: "", subst: void 0 };
1036
1104
  var Parser = class {
1037
1105
  constructor(tokens) {
1038
1106
  this.tokens = tokens;
@@ -1145,13 +1213,17 @@ var Parser = class {
1145
1213
  let trailingDot = false;
1146
1214
  let firstWasQuoted = false;
1147
1215
  let spaceConcat = false;
1216
+ let postDotPrefix = "";
1148
1217
  while (true) {
1149
1218
  const t = this.peek();
1150
1219
  if (t.kind === "string") {
1151
1220
  this.advance();
1152
1221
  if (segments.length === 0) firstWasQuoted = true;
1153
1222
  if (spaceConcat) {
1154
- segments[segments.length - 1] = `${segments[segments.length - 1]} ${t.value}`;
1223
+ segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}${t.value}`;
1224
+ } else if (postDotPrefix !== "") {
1225
+ segments.push(`${postDotPrefix}${t.value}`);
1226
+ postDotPrefix = "";
1155
1227
  } else {
1156
1228
  segments.push(t.value);
1157
1229
  }
@@ -1161,24 +1233,23 @@ var Parser = class {
1161
1233
  const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
1162
1234
  const parts = raw.split(".");
1163
1235
  const filtered = parts.filter((s) => s.length > 0);
1164
- for (const seg of filtered) {
1165
- if (seg[0] === "-" && !(seg.length >= 2 && seg[1] >= "0" && seg[1] <= "9")) {
1166
- const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
1167
- throw new ParseError(
1168
- `unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
1169
- t.line,
1170
- t.col
1171
- );
1172
- }
1173
- }
1174
- if (spaceConcat && filtered.length > 0) {
1236
+ if (spaceConcat) {
1237
+ segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
1175
1238
  if (raw.startsWith(".")) {
1176
1239
  segments.push(...filtered);
1177
- } else {
1240
+ } else if (filtered.length > 0) {
1178
1241
  const [head, ...tail] = filtered;
1179
- segments[segments.length - 1] = `${segments[segments.length - 1]} ${head}`;
1242
+ segments[segments.length - 1] = `${segments[segments.length - 1]}${head}`;
1180
1243
  segments.push(...tail);
1181
1244
  }
1245
+ } else if (postDotPrefix !== "" && raw.startsWith(".")) {
1246
+ segments.push(postDotPrefix);
1247
+ postDotPrefix = "";
1248
+ segments.push(...filtered);
1249
+ } else if (postDotPrefix !== "" && filtered.length > 0) {
1250
+ const [head, ...tail] = filtered;
1251
+ segments.push(`${postDotPrefix}${head}`, ...tail);
1252
+ postDotPrefix = "";
1182
1253
  } else {
1183
1254
  segments.push(...filtered);
1184
1255
  }
@@ -1188,11 +1259,23 @@ var Parser = class {
1188
1259
  break;
1189
1260
  }
1190
1261
  spaceConcat = false;
1191
- if (trailingDot) continue;
1262
+ if (trailingDot) {
1263
+ const next2 = this.peek();
1264
+ if ((next2.kind === "unquoted" || next2.kind === "string") && next2.precedingWhitespace.length > 0) {
1265
+ postDotPrefix = next2.precedingWhitespace;
1266
+ } else {
1267
+ postDotPrefix = "";
1268
+ }
1269
+ continue;
1270
+ }
1192
1271
  const next = this.peek();
1193
1272
  if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
1194
1273
  this.advance();
1195
1274
  trailingDot = true;
1275
+ const afterDot = this.peek();
1276
+ if ((afterDot.kind === "unquoted" || afterDot.kind === "string") && afterDot.precedingWhitespace.length > 0) {
1277
+ postDotPrefix = afterDot.precedingWhitespace;
1278
+ }
1196
1279
  continue;
1197
1280
  }
1198
1281
  if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
@@ -1205,6 +1288,14 @@ var Parser = class {
1205
1288
  }
1206
1289
  break;
1207
1290
  }
1291
+ if (trailingDot) {
1292
+ const here = this.peek();
1293
+ throw new ParseError(
1294
+ `path has a trailing period '.' \u2014 empty key segment not allowed (HOCON.md path rules)`,
1295
+ here.line,
1296
+ here.col
1297
+ );
1298
+ }
1208
1299
  return { segments, firstWasQuoted };
1209
1300
  }
1210
1301
  parseInclude() {
@@ -1377,7 +1468,8 @@ var Parser = class {
1377
1468
  break;
1378
1469
  }
1379
1470
  if (hadSpace) {
1380
- parts.push({ kind: "scalar", raw: " ", valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
1471
+ const sep = t.precedingWhitespace.length > 0 ? t.precedingWhitespace : " ";
1472
+ parts.push({ kind: "scalar", raw: sep, valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
1381
1473
  }
1382
1474
  parts.push(node);
1383
1475
  }
@@ -1839,9 +1931,9 @@ var StructureBuilder = class _StructureBuilder {
1839
1931
  const existing = obj.fields.get(head);
1840
1932
  const newVal = this.astToResolverValue(field.value, childPrefix);
1841
1933
  if (existing !== void 0) {
1842
- const exFolded = foldNestedSelfRefs(existing, childPrefix);
1843
1934
  const oldPrior = obj.priorValues.get(head);
1844
- const prior = foldOrSkipPrior(exFolded, fullKey, oldPrior);
1935
+ const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
1936
+ const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
1845
1937
  if (prior !== void 0) obj.priorValues.set(head, prior);
1846
1938
  }
1847
1939
  if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
@@ -1893,9 +1985,9 @@ var StructureBuilder = class _StructureBuilder {
1893
1985
  const existing = obj.fields.get(head);
1894
1986
  const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
1895
1987
  if (existing !== void 0) {
1896
- const exFolded = foldNestedSelfRefs(existing, childPrefix);
1897
1988
  const oldPrior = obj.priorValues.get(head);
1898
- const prior = foldOrSkipPrior(exFolded, fullKey, oldPrior);
1989
+ const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
1990
+ const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
1899
1991
  if (prior !== void 0) obj.priorValues.set(head, prior);
1900
1992
  }
1901
1993
  if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
@@ -1918,7 +2010,7 @@ var StructureBuilder = class _StructureBuilder {
1918
2010
  return inner;
1919
2011
  }
1920
2012
  case "subst":
1921
- return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
2013
+ 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 };
1922
2014
  case "concat":
1923
2015
  return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
1924
2016
  case "include":
@@ -1942,7 +2034,7 @@ var StructureBuilder = class _StructureBuilder {
1942
2034
  case "object":
1943
2035
  return await this.buildAsync(ast, pathPrefix);
1944
2036
  case "subst":
1945
- return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
2037
+ 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 };
1946
2038
  case "concat": {
1947
2039
  const nodes = [];
1948
2040
  for (const n of ast.nodes) {
@@ -2060,6 +2152,8 @@ var SubstitutionResolver = class {
2060
2152
  const result = /* @__PURE__ */ new Map();
2061
2153
  for (const [key, val] of obj.fields) {
2062
2154
  this.resolvingFieldPath.push(key);
2155
+ const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
2156
+ this.cache.delete(fullCacheKey);
2063
2157
  let resolved;
2064
2158
  try {
2065
2159
  resolved = this.resolveVal(val, obj);
@@ -2072,28 +2166,42 @@ var SubstitutionResolver = class {
2072
2166
  if (prior !== void 0) {
2073
2167
  const priorResolved = this.resolveVal(prior, obj);
2074
2168
  if (priorResolved !== void 0 && priorResolved.kind === "object") {
2075
- result.set(
2076
- key,
2077
- deepMergeHoconValues(
2078
- priorResolved,
2079
- resolved
2080
- )
2169
+ const finalValue = deepMergeHoconValues(
2170
+ priorResolved,
2171
+ resolved
2081
2172
  );
2173
+ result.set(key, finalValue);
2174
+ this.cache.set(fullCacheKey, finalValue);
2175
+ this.cacheDescendants(fullCacheKey, finalValue);
2082
2176
  continue;
2083
2177
  }
2084
2178
  }
2085
2179
  }
2086
2180
  result.set(key, resolved);
2181
+ this.cache.set(fullCacheKey, resolved);
2182
+ this.cacheDescendants(fullCacheKey, resolved);
2087
2183
  } else {
2088
2184
  const prior = obj.priorValues.get(key);
2089
2185
  if (prior !== void 0) {
2090
2186
  const priorResolved = this.resolveVal(prior, obj);
2091
- if (priorResolved !== void 0) result.set(key, priorResolved);
2187
+ if (priorResolved !== void 0) {
2188
+ result.set(key, priorResolved);
2189
+ this.cache.set(fullCacheKey, priorResolved);
2190
+ this.cacheDescendants(fullCacheKey, priorResolved);
2191
+ }
2092
2192
  }
2093
2193
  }
2094
2194
  }
2095
2195
  return { kind: "object", fields: result };
2096
2196
  }
2197
+ cacheDescendants(prefix, value) {
2198
+ if (value.kind !== "object") return;
2199
+ for (const [key, child] of value.fields) {
2200
+ const childKey = `${prefix}.${stringSegmentsToKey([key])}`;
2201
+ this.cache.set(childKey, child);
2202
+ this.cacheDescendants(childKey, child);
2203
+ }
2204
+ }
2097
2205
  resolveVal(v, scope) {
2098
2206
  if (isSubst(v)) return this.resolveSubst(v, scope);
2099
2207
  if (isConcat(v)) {
@@ -2119,6 +2227,7 @@ var SubstitutionResolver = class {
2119
2227
  return hv;
2120
2228
  }
2121
2229
  resolveSubst(s, scope) {
2230
+ if (s.knownAbsent) return void 0;
2122
2231
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2123
2232
  if (this.cache.has(key)) return this.cache.get(key);
2124
2233
  if (this.resolving.has(key)) {
@@ -2211,6 +2320,28 @@ var SubstitutionResolver = class {
2211
2320
  if (result !== void 0) this.cache.set(key, result);
2212
2321
  return result;
2213
2322
  }
2323
+ if (s.prefixLen > 0 && s.segments.length > s.prefixLen) {
2324
+ const originalSegments = s.segments.slice(s.prefixLen);
2325
+ const fallbackFound = lookupPath(this.root, originalSegments);
2326
+ if (fallbackFound !== void 0) {
2327
+ let result = this.resolveVal(fallbackFound, scope);
2328
+ if (originalSegments.length === 1 && result !== void 0 && result.kind === "object") {
2329
+ const rootSeg = originalSegments[0]?.text ?? "";
2330
+ const prior = this.root.priorValues.get(rootSeg);
2331
+ if (prior !== void 0) {
2332
+ const priorResolved = this.resolveVal(prior, scope);
2333
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
2334
+ result = deepMergeHoconValues(
2335
+ priorResolved,
2336
+ result
2337
+ );
2338
+ }
2339
+ }
2340
+ }
2341
+ if (result !== void 0) this.cache.set(key, result);
2342
+ return result;
2343
+ }
2344
+ }
2214
2345
  if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
2215
2346
  const result = this.resolveEnvList(s);
2216
2347
  if (result !== void 0) {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as Config } from './config-BwjvKeZ5.cjs';
2
- export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ResolveOptions, S as ScalarValueType, d as defaultParseOptions, b as defaultResolveOptions, p as parse, c as parseAsync, e as parseFile, f as parseFileAsync, g as parseFileWithOptions, h as parseString, i as parseStringWithOptions } from './config-BwjvKeZ5.cjs';
1
+ import { C as Config } from './config-CjSkQ4_l.cjs';
2
+ export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ResolveOptions, S as ScalarValueType, d as defaultParseOptions, b as defaultResolveOptions, p as parse, c as parseAsync, e as parseFile, f as parseFileAsync, g as parseFileWithOptions, h as parseString, i as parseStringWithOptions } from './config-CjSkQ4_l.cjs';
3
3
 
4
4
  declare class ParseError extends Error {
5
5
  readonly line: number;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as Config } from './config-BwjvKeZ5.js';
2
- export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ResolveOptions, S as ScalarValueType, d as defaultParseOptions, b as defaultResolveOptions, p as parse, c as parseAsync, e as parseFile, f as parseFileAsync, g as parseFileWithOptions, h as parseString, i as parseStringWithOptions } from './config-BwjvKeZ5.js';
1
+ import { C as Config } from './config-CjSkQ4_l.js';
2
+ export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ResolveOptions, S as ScalarValueType, d as defaultParseOptions, b as defaultResolveOptions, p as parse, c as parseAsync, e as parseFile, f as parseFileAsync, g as parseFileWithOptions, h as parseString, i as parseStringWithOptions } from './config-CjSkQ4_l.js';
3
3
 
4
4
  declare class ParseError extends Error {
5
5
  readonly line: number;