@o3co/ts.hocon 1.9.0 → 1.11.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
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/coerce.ts
2
9
  var DECIMAL_NUMBER_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
3
10
  function isHoconWs(cp) {
@@ -639,6 +646,7 @@ function isFileNotFoundError(e) {
639
646
  // src/internal/resolver/include-loader.ts
640
647
  import { createRequire } from "module";
641
648
  import * as nodePath from "path";
649
+ import { pathToFileURL } from "url";
642
650
 
643
651
  // src/internal/lexer/lexer.ts
644
652
  function isHoconWhitespace(ch) {
@@ -1040,12 +1048,12 @@ function isDecimalDigit(ch) {
1040
1048
  }
1041
1049
  function isUnquotedStart(ch) {
1042
1050
  if (ch === "" || isHoconWhitespace(ch)) return false;
1043
- if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
1051
+ if ('{}[],:=+#"$?!@*&^`\\'.includes(ch)) return false;
1044
1052
  return true;
1045
1053
  }
1046
1054
  function isUnquotedContinue(ch, nextFn) {
1047
1055
  if (ch === "" || isHoconWhitespace(ch)) return false;
1048
- if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
1056
+ if ('{}[],:=#"$?!@*&^`\\'.includes(ch)) return false;
1049
1057
  if (ch === "+" && nextFn() === "=") return false;
1050
1058
  if (ch === "/" && nextFn() === "/") return false;
1051
1059
  return true;
@@ -1194,6 +1202,17 @@ var Parser = class {
1194
1202
  this.advance();
1195
1203
  const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
1196
1204
  const parts = raw.split(".");
1205
+ const leadingDotIsSeparator = spaceConcat || postDotPrefix !== "";
1206
+ for (let i = 0; i < parts.length; i++) {
1207
+ if (parts[i] !== "") continue;
1208
+ if (i === parts.length - 1) continue;
1209
+ if (i === 0 && leadingDotIsSeparator) continue;
1210
+ throw new ParseError(
1211
+ `path has ${i === 0 ? "a leading period '.'" : "two adjacent periods '.'"} \u2014 empty key segment not allowed, use quoted "" (HOCON.md path rules)`,
1212
+ t.line,
1213
+ t.col
1214
+ );
1215
+ }
1197
1216
  const filtered = parts.filter((s) => s.length > 0);
1198
1217
  if (spaceConcat) {
1199
1218
  segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
@@ -1470,45 +1489,135 @@ function parseTokens(tokens) {
1470
1489
  return new Parser(tokens).parse();
1471
1490
  }
1472
1491
 
1492
+ // src/internal/strip-bom.ts
1493
+ function stripBom(input) {
1494
+ return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
1495
+ }
1496
+
1473
1497
  // src/internal/properties/properties.ts
1474
1498
  function parseProperties(input) {
1475
- const root = /* @__PURE__ */ Object.create(null);
1476
1499
  const pairs = [];
1477
- for (const line of input.split("\n")) {
1478
- const trimmed = line.trim();
1479
- if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
1480
- const sepIdx = findSeparator(trimmed);
1481
- if (sepIdx === -1) continue;
1482
- const key = trimmed.slice(0, sepIdx).trim();
1483
- const value = trimmed.slice(sepIdx + 1).trim();
1500
+ for (const { text, line } of logicalLines(stripBom(input))) {
1501
+ const [rawKey, rawValue] = splitKeyValue(text);
1502
+ const key = unescapeProps(rawKey, line);
1484
1503
  if (key === "") continue;
1485
- pairs.push([key, value]);
1504
+ pairs.push([key.split("."), unescapeProps(rawValue, line)]);
1486
1505
  }
1487
- pairs.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1488
- for (const [key, value] of pairs) {
1489
- setNested(root, key.split("."), value);
1506
+ return nestPairs(pairs);
1507
+ }
1508
+ function nestPairs(pairs) {
1509
+ const root = /* @__PURE__ */ Object.create(null);
1510
+ const sorted = [...pairs].sort(([a], [b]) => compareSegments(a, b));
1511
+ for (const [segments, value] of sorted) {
1512
+ setNested(root, segments, value);
1490
1513
  }
1491
1514
  return root;
1492
1515
  }
1493
- function findSeparator(line) {
1494
- for (let i = 0; i < line.length; i++) {
1495
- if (line[i] === "=" || line[i] === ":") return i;
1516
+ function compareSegments(a, b) {
1517
+ const n = Math.min(a.length, b.length);
1518
+ for (let i = 0; i < n; i++) {
1519
+ const x = a[i];
1520
+ const y = b[i];
1521
+ if (x !== y) return x < y ? -1 : 1;
1522
+ }
1523
+ return a.length - b.length;
1524
+ }
1525
+ function logicalLines(input) {
1526
+ const natural = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1527
+ const out = [];
1528
+ for (let i = 0; i < natural.length; i++) {
1529
+ let text = trimLeadingSpace(natural[i] ?? "");
1530
+ if (text === "" || text.startsWith("#") || text.startsWith("!")) continue;
1531
+ const start = i + 1;
1532
+ while (endsWithContinuation(text)) {
1533
+ text = text.slice(0, -1);
1534
+ if (i + 1 >= natural.length) break;
1535
+ i++;
1536
+ text += trimLeadingSpace(natural[i] ?? "");
1537
+ }
1538
+ out.push({ text, line: start });
1496
1539
  }
1497
- return -1;
1540
+ return out;
1541
+ }
1542
+ function trimLeadingSpace(s) {
1543
+ return s.replace(/^[ \t\f]+/, "");
1544
+ }
1545
+ function endsWithContinuation(line) {
1546
+ let n = 0;
1547
+ for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i--) n++;
1548
+ return n % 2 === 1;
1549
+ }
1550
+ function splitKeyValue(line) {
1551
+ let key = "";
1552
+ let i = 0;
1553
+ for (; i < line.length; i++) {
1554
+ const c = line[i];
1555
+ if (c === "\\" && i + 1 < line.length) {
1556
+ key += c + line[i + 1];
1557
+ i++;
1558
+ continue;
1559
+ }
1560
+ if (c === "=" || c === ":" || isPropsSpace(c)) break;
1561
+ key += c;
1562
+ }
1563
+ while (i < line.length && isPropsSpace(line[i])) i++;
1564
+ if (i < line.length && (line[i] === "=" || line[i] === ":")) {
1565
+ i++;
1566
+ while (i < line.length && isPropsSpace(line[i])) i++;
1567
+ }
1568
+ return [key, line.slice(i)];
1569
+ }
1570
+ function isPropsSpace(c) {
1571
+ return c === " " || c === " " || c === "\f";
1572
+ }
1573
+ function unescapeProps(s, line) {
1574
+ let out = "";
1575
+ for (let i = 0; i < s.length; i++) {
1576
+ if (s[i] !== "\\") {
1577
+ out += s[i];
1578
+ continue;
1579
+ }
1580
+ i++;
1581
+ if (i >= s.length) break;
1582
+ const e = s[i];
1583
+ switch (e) {
1584
+ case "t":
1585
+ out += " ";
1586
+ break;
1587
+ case "n":
1588
+ out += "\n";
1589
+ break;
1590
+ case "r":
1591
+ out += "\r";
1592
+ break;
1593
+ case "f":
1594
+ out += "\f";
1595
+ break;
1596
+ case "u": {
1597
+ const hex = s.slice(i + 1, i + 5);
1598
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1599
+ throw new ParseError(`malformed \\u escape in .properties: \\u${hex}`, line, i + 1);
1600
+ }
1601
+ out += String.fromCharCode(parseInt(hex, 16));
1602
+ i += 4;
1603
+ break;
1604
+ }
1605
+ default:
1606
+ out += e;
1607
+ }
1608
+ }
1609
+ return out;
1498
1610
  }
1499
- var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1500
1611
  function setNested(obj, segments, value) {
1501
1612
  let current = obj;
1502
1613
  for (let i = 0; i < segments.length - 1; i++) {
1503
1614
  const seg = segments[i];
1504
- if (seg === void 0 || DANGEROUS_KEYS.has(seg)) return;
1505
1615
  if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
1506
1616
  current[seg] = /* @__PURE__ */ Object.create(null);
1507
1617
  }
1508
1618
  current = current[seg];
1509
1619
  }
1510
1620
  const last = segments[segments.length - 1];
1511
- if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
1512
1621
  if (typeof current[last] === "object" && current[last] !== null) return;
1513
1622
  current[last] = value;
1514
1623
  }
@@ -1529,7 +1638,22 @@ function recordToHoconValue(obj) {
1529
1638
  }
1530
1639
 
1531
1640
  // src/internal/resolver/include-loader.ts
1532
- var _require = createRequire(import.meta.url);
1641
+ var _require;
1642
+ function getRequire() {
1643
+ if (_require === void 0) {
1644
+ _require = pickRequire(
1645
+ typeof __require === "function" ? __require : void 0,
1646
+ import.meta.url
1647
+ );
1648
+ }
1649
+ return _require;
1650
+ }
1651
+ function pickRequire(candidate, metaUrl) {
1652
+ if (typeof candidate?.resolve === "function") {
1653
+ return candidate;
1654
+ }
1655
+ return createRequire(metaUrl ?? pathToFileURL(nodePath.join(process.cwd(), "noop.js")));
1656
+ }
1533
1657
  function isPnpPath(p) {
1534
1658
  return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
1535
1659
  }
@@ -1585,7 +1709,7 @@ function defaultPackageResolver(identifier, file, includingFile, baseDir, resolv
1585
1709
  const from = resolveFrom ? Array.isArray(resolveFrom) ? resolveFrom : [resolveFrom] : baseDir ? [baseDir] : includingFile ? [nodePath.dirname(includingFile)] : [process.cwd()];
1586
1710
  let resolved;
1587
1711
  try {
1588
- resolved = _require.resolve(`${identifier}/${file}`, { paths: from });
1712
+ resolved = getRequire().resolve(`${identifier}/${file}`, { paths: from });
1589
1713
  } catch {
1590
1714
  throw new PackageLookupError(
1591
1715
  `include package("${identifier}", "${file}"): module not found (starting from: ${from.join(", ")})`,
@@ -2639,10 +2763,10 @@ var Config = class _Config {
2639
2763
  }
2640
2764
  if (v.kind === "object") {
2641
2765
  const converted = numericObjectToArray(v);
2642
- if (converted !== null) return converted.map(hoconToJs);
2766
+ if (converted !== null) return converted.map((item) => hoconToJs(item));
2643
2767
  }
2644
2768
  if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
2645
- return v.items.map(hoconToJs);
2769
+ return v.items.map((item) => hoconToJs(item));
2646
2770
  }
2647
2771
  has(path2) {
2648
2772
  return this.lookupNode(path2) !== void 0;
@@ -2770,8 +2894,52 @@ var Config = class _Config {
2770
2894
  originDescription: this._originDescription
2771
2895
  });
2772
2896
  }
2773
- toObject() {
2774
- return hoconToJs(this.root);
2897
+ /**
2898
+ * The whole config as plain JS data: objects, arrays, strings, numbers,
2899
+ * booleans and `null`.
2900
+ *
2901
+ * Keys are reproduced exactly as the config carries them, `__proto__`,
2902
+ * `constructor` and `prototype` included: each is defined as an **own data
2903
+ * property**, so the returned object's prototype is always
2904
+ * `Object.prototype`, and this call writes nothing to the global one.
2905
+ *
2906
+ * ### Passing the result to other code
2907
+ *
2908
+ * A config file — especially a `.properties` file or an env namespace you
2909
+ * merged in — can therefore hand you an own `__proto__` key, and what happens
2910
+ * next depends entirely on how the consumer copies it:
2911
+ *
2912
+ * ```ts
2913
+ * const data = cfg.toObject()
2914
+ * { ...data } // safe — own properties are copied as data
2915
+ * structuredClone(data) // safe
2916
+ * Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
2917
+ * // Object.prototype's __proto__ setter: the key
2918
+ * // vanishes and config data becomes the result's
2919
+ * // prototype
2920
+ * deepMerge({}, data) // NOT safe — a naive recursive merge reads
2921
+ * // target['__proto__'], gets Object.prototype,
2922
+ * // and writes to it: global pollution
2923
+ * for (const k in data) … // prefer Object.entries / Object.keys
2924
+ * ```
2925
+ *
2926
+ * Those two hazards come from the **destination** object's prototype, so no
2927
+ * option here can disarm them — copy with spread or `structuredClone` instead.
2928
+ *
2929
+ * What `{ nullPrototype: true }` does give you is a result that inherits
2930
+ * nothing: every object comes back with a `null` prototype, so reading
2931
+ * `x.__proto__`, `x.constructor` or `x.toString` yields config data or
2932
+ * `undefined` rather than something from `Object.prototype`, and code that
2933
+ * merges *into* this object cannot climb out of it. Keys are all still
2934
+ * present. The trade-off is the usual one for prototype-less objects: not
2935
+ * `instanceof Object`, and no `hasOwnProperty` of their own.
2936
+ *
2937
+ * Numbers follow the JS number model, so an integer wider than 2^53 rounds
2938
+ * here even when the config holds it exactly — read those with
2939
+ * {@link Config.getString} instead.
2940
+ */
2941
+ toObject(opts) {
2942
+ return hoconToJs(this.root, opts?.nullPrototype === true);
2775
2943
  }
2776
2944
  /**
2777
2945
  * @internal Test-only: renders this resolved Config's value tree as canonical
@@ -2883,16 +3051,25 @@ function scalarToJs(raw, valueType) {
2883
3051
  return raw;
2884
3052
  }
2885
3053
  }
2886
- function hoconToJs(v) {
3054
+ function hoconToJs(v, nullPrototype = false) {
2887
3055
  switch (v.kind) {
2888
3056
  case "scalar":
2889
3057
  return scalarToJs(v.raw, v.valueType);
2890
3058
  case "array":
2891
- return v.items.map(hoconToJs);
3059
+ return v.items.map((item) => hoconToJs(item, nullPrototype));
2892
3060
  case "object": {
2893
- const obj = /* @__PURE__ */ Object.create(null);
2894
- for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
2895
- return Object.assign({}, obj);
3061
+ const entries = Array.from(v.fields, ([k, val]) => [k, hoconToJs(val, nullPrototype)]);
3062
+ if (nullPrototype) {
3063
+ const obj = /* @__PURE__ */ Object.create(null);
3064
+ for (const [k, val] of entries) Object.defineProperty(obj, k, {
3065
+ value: val,
3066
+ writable: true,
3067
+ enumerable: true,
3068
+ configurable: true
3069
+ });
3070
+ return obj;
3071
+ }
3072
+ return Object.fromEntries(entries);
2896
3073
  }
2897
3074
  }
2898
3075
  }
@@ -3126,6 +3303,8 @@ function empty(originDescription) {
3126
3303
  const root = { kind: "object", fields: /* @__PURE__ */ new Map() };
3127
3304
  return Config._fromResolvedValue(root, { originDescription });
3128
3305
  }
3306
+ var INT64_MAX = 9223372036854775807n;
3307
+ var INT64_MIN = -9223372036854775808n;
3129
3308
  function coerceObject(obj, atPath) {
3130
3309
  const fields = /* @__PURE__ */ new Map();
3131
3310
  for (const [k, v] of Object.entries(obj)) {
@@ -3154,14 +3333,14 @@ function coerceValue(v, atPath) {
3154
3333
  atPath
3155
3334
  );
3156
3335
  }
3157
- return { kind: "scalar", raw: String(v), valueType: "number" };
3336
+ return { kind: "scalar", raw: Object.is(v, -0) ? "-0" : String(v), valueType: "number" };
3158
3337
  }
3159
3338
  case "boolean":
3160
3339
  return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
3161
3340
  case "bigint": {
3162
- if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
3341
+ if (v > INT64_MAX || v < INT64_MIN) {
3163
3342
  throw new ConfigError(
3164
- `fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
3343
+ `fromMap: integer ${v} is out of int64 range (HOCON integers are int64; spec F0.5) at path "${atPath}"`,
3165
3344
  atPath
3166
3345
  );
3167
3346
  }