@o3co/ts.hocon 1.8.0 → 1.10.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.cjs CHANGED
@@ -141,7 +141,7 @@ function parseDuration(value, outputUnit = "ms") {
141
141
  if (i === 0) return NaN;
142
142
  const num = Number(trimmed.slice(0, i));
143
143
  if (Number.isNaN(num)) return NaN;
144
- const unit = trimHoconWs(trimmed.slice(i)).toLowerCase();
144
+ const unit = trimHoconWs(trimmed.slice(i));
145
145
  const divisor = OUTPUT_DURATION_UNITS[outputUnit];
146
146
  if (divisor === void 0) return NaN;
147
147
  if (unit === "") {
@@ -701,20 +701,6 @@ function isFileNotFoundError(e) {
701
701
  var import_node_module = require("module");
702
702
  var nodePath = __toESM(require("path"), 1);
703
703
 
704
- // src/internal/parser/empty-check.ts
705
- function hasContentTokens(tokens) {
706
- return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
707
- }
708
- function assertNonEmptyDocument(tokens, sourceDescriptor) {
709
- if (!hasContentTokens(tokens)) {
710
- throw new ParseError(
711
- `empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
712
- 1,
713
- 1
714
- );
715
- }
716
- }
717
-
718
704
  // src/internal/lexer/lexer.ts
719
705
  function isHoconWhitespace(ch) {
720
706
  const cp = ch.codePointAt(0) ?? -1;
@@ -1115,12 +1101,12 @@ function isDecimalDigit(ch) {
1115
1101
  }
1116
1102
  function isUnquotedStart(ch) {
1117
1103
  if (ch === "" || isHoconWhitespace(ch)) return false;
1118
- if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
1104
+ if ('{}[],:=+#"$?!@*&^`\\'.includes(ch)) return false;
1119
1105
  return true;
1120
1106
  }
1121
1107
  function isUnquotedContinue(ch, nextFn) {
1122
1108
  if (ch === "" || isHoconWhitespace(ch)) return false;
1123
- if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
1109
+ if ('{}[],:=#"$?!@*&^`\\'.includes(ch)) return false;
1124
1110
  if (ch === "+" && nextFn() === "=") return false;
1125
1111
  if (ch === "/" && nextFn() === "/") return false;
1126
1112
  return true;
@@ -1160,6 +1146,16 @@ var Parser = class {
1160
1146
  }
1161
1147
  return { kind: "object", fields: allFields, pos: first.pos };
1162
1148
  }
1149
+ if (t.kind === "lbracket") {
1150
+ this.advance();
1151
+ const arr = this.parseArray();
1152
+ this.skip("newline");
1153
+ const remaining = this.peek();
1154
+ if (remaining.kind !== "eof") {
1155
+ throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1156
+ }
1157
+ return { ...arr, pos: { line: t.line, col: t.col } };
1158
+ }
1163
1159
  return this.parseObject(false);
1164
1160
  }
1165
1161
  peek(offset = 0) {
@@ -1259,6 +1255,17 @@ var Parser = class {
1259
1255
  this.advance();
1260
1256
  const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
1261
1257
  const parts = raw.split(".");
1258
+ const leadingDotIsSeparator = spaceConcat || postDotPrefix !== "";
1259
+ for (let i = 0; i < parts.length; i++) {
1260
+ if (parts[i] !== "") continue;
1261
+ if (i === parts.length - 1) continue;
1262
+ if (i === 0 && leadingDotIsSeparator) continue;
1263
+ throw new ParseError(
1264
+ `path has ${i === 0 ? "a leading period '.'" : "two adjacent periods '.'"} \u2014 empty key segment not allowed, use quoted "" (HOCON.md path rules)`,
1265
+ t.line,
1266
+ t.col
1267
+ );
1268
+ }
1262
1269
  const filtered = parts.filter((s) => s.length > 0);
1263
1270
  if (spaceConcat) {
1264
1271
  segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
@@ -1537,29 +1544,108 @@ function parseTokens(tokens) {
1537
1544
 
1538
1545
  // src/internal/properties/properties.ts
1539
1546
  function parseProperties(input) {
1540
- const root = /* @__PURE__ */ Object.create(null);
1541
1547
  const pairs = [];
1542
- for (const line of input.split("\n")) {
1543
- const trimmed = line.trim();
1544
- if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
1545
- const sepIdx = findSeparator(trimmed);
1546
- if (sepIdx === -1) continue;
1547
- const key = trimmed.slice(0, sepIdx).trim();
1548
- const value = trimmed.slice(sepIdx + 1).trim();
1548
+ for (const { text, line } of logicalLines(input)) {
1549
+ const [rawKey, rawValue] = splitKeyValue(text);
1550
+ const key = unescapeProps(rawKey, line);
1549
1551
  if (key === "") continue;
1550
- pairs.push([key, value]);
1552
+ pairs.push([key, unescapeProps(rawValue, line)]);
1551
1553
  }
1552
- pairs.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1553
- for (const [key, value] of pairs) {
1554
+ return nestPairs(pairs);
1555
+ }
1556
+ function nestPairs(pairs) {
1557
+ const root = /* @__PURE__ */ Object.create(null);
1558
+ const sorted = [...pairs].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1559
+ for (const [key, value] of sorted) {
1554
1560
  setNested(root, key.split("."), value);
1555
1561
  }
1556
1562
  return root;
1557
1563
  }
1558
- function findSeparator(line) {
1559
- for (let i = 0; i < line.length; i++) {
1560
- if (line[i] === "=" || line[i] === ":") return i;
1564
+ function logicalLines(input) {
1565
+ const natural = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1566
+ const out = [];
1567
+ for (let i = 0; i < natural.length; i++) {
1568
+ let text = trimLeadingSpace(natural[i] ?? "");
1569
+ if (text === "" || text.startsWith("#") || text.startsWith("!")) continue;
1570
+ const start = i + 1;
1571
+ while (endsWithContinuation(text)) {
1572
+ text = text.slice(0, -1);
1573
+ if (i + 1 >= natural.length) break;
1574
+ i++;
1575
+ text += trimLeadingSpace(natural[i] ?? "");
1576
+ }
1577
+ out.push({ text, line: start });
1578
+ }
1579
+ return out;
1580
+ }
1581
+ function trimLeadingSpace(s) {
1582
+ return s.replace(/^[ \t\f]+/, "");
1583
+ }
1584
+ function endsWithContinuation(line) {
1585
+ let n = 0;
1586
+ for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i--) n++;
1587
+ return n % 2 === 1;
1588
+ }
1589
+ function splitKeyValue(line) {
1590
+ let key = "";
1591
+ let i = 0;
1592
+ for (; i < line.length; i++) {
1593
+ const c = line[i];
1594
+ if (c === "\\" && i + 1 < line.length) {
1595
+ key += c + line[i + 1];
1596
+ i++;
1597
+ continue;
1598
+ }
1599
+ if (c === "=" || c === ":" || isPropsSpace(c)) break;
1600
+ key += c;
1601
+ }
1602
+ while (i < line.length && isPropsSpace(line[i])) i++;
1603
+ if (i < line.length && (line[i] === "=" || line[i] === ":")) {
1604
+ i++;
1605
+ while (i < line.length && isPropsSpace(line[i])) i++;
1606
+ }
1607
+ return [key, line.slice(i)];
1608
+ }
1609
+ function isPropsSpace(c) {
1610
+ return c === " " || c === " " || c === "\f";
1611
+ }
1612
+ function unescapeProps(s, line) {
1613
+ let out = "";
1614
+ for (let i = 0; i < s.length; i++) {
1615
+ if (s[i] !== "\\") {
1616
+ out += s[i];
1617
+ continue;
1618
+ }
1619
+ i++;
1620
+ if (i >= s.length) break;
1621
+ const e = s[i];
1622
+ switch (e) {
1623
+ case "t":
1624
+ out += " ";
1625
+ break;
1626
+ case "n":
1627
+ out += "\n";
1628
+ break;
1629
+ case "r":
1630
+ out += "\r";
1631
+ break;
1632
+ case "f":
1633
+ out += "\f";
1634
+ break;
1635
+ case "u": {
1636
+ const hex = s.slice(i + 1, i + 5);
1637
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1638
+ throw new ParseError(`malformed \\u escape in .properties: \\u${hex}`, line, i + 1);
1639
+ }
1640
+ out += String.fromCharCode(parseInt(hex, 16));
1641
+ i += 4;
1642
+ break;
1643
+ }
1644
+ default:
1645
+ out += e;
1646
+ }
1561
1647
  }
1562
- return -1;
1648
+ return out;
1563
1649
  }
1564
1650
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1565
1651
  function setNested(obj, segments, value) {
@@ -1599,6 +1685,16 @@ var _require = (0, import_node_module.createRequire)(import_meta.url);
1599
1685
  function isPnpPath(p) {
1600
1686
  return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
1601
1687
  }
1688
+ function assertObjectRootedInclude(ast, sourcePath) {
1689
+ if (ast.kind === "array") {
1690
+ throw new ResolveError(
1691
+ `included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
1692
+ sourcePath,
1693
+ ast.pos.line,
1694
+ ast.pos.col
1695
+ );
1696
+ }
1697
+ }
1602
1698
  function validatePackageFile(file, identifier) {
1603
1699
  if (file.length === 0) {
1604
1700
  throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
@@ -1778,10 +1874,9 @@ var IncludeLoader = class {
1778
1874
  const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
1779
1875
  const resolvedPath = resolver(identifier, file, void 0, baseDir);
1780
1876
  const content = readFileSync2(resolvedPath);
1781
- if (content.length === 0) return makeResObj();
1782
1877
  const tokens = tokenize(content);
1783
- assertNonEmptyDocument(tokens, resolvedPath);
1784
1878
  const ast = parseTokens(tokens);
1879
+ assertObjectRootedInclude(ast, resolvedPath);
1785
1880
  return this.onBuildResObj(ast, {
1786
1881
  ...this.opts,
1787
1882
  baseDir: nodePath.dirname(resolvedPath),
@@ -1811,10 +1906,9 @@ var IncludeLoader = class {
1811
1906
  const resolvedPath = resolver(identifier, file, void 0, baseDir);
1812
1907
  const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
1813
1908
  const content = await read(resolvedPath);
1814
- if (content.length === 0) return makeResObj();
1815
1909
  const tokens = tokenize(content);
1816
- assertNonEmptyDocument(tokens, resolvedPath);
1817
1910
  const ast = parseTokens(tokens);
1911
+ assertObjectRootedInclude(ast, resolvedPath);
1818
1912
  return this.onBuildResObjAsync(ast, {
1819
1913
  ...this.opts,
1820
1914
  baseDir: nodePath.dirname(resolvedPath),
@@ -1837,10 +1931,8 @@ var IncludeLoader = class {
1837
1931
  return hoconValueToResObj(propertiesToHoconValue(content));
1838
1932
  }
1839
1933
  const tokens = tokenize(content);
1840
- if (!hasContentTokens(tokens)) {
1841
- return makeResObj();
1842
- }
1843
1934
  const ast = parseTokens(tokens);
1935
+ assertObjectRootedInclude(ast, candidate);
1844
1936
  return this.onBuildResObj(ast, {
1845
1937
  ...this.opts,
1846
1938
  baseDir: nodePath.dirname(candidate),
@@ -1864,10 +1956,8 @@ var IncludeLoader = class {
1864
1956
  return hoconValueToResObj(propertiesToHoconValue(content));
1865
1957
  }
1866
1958
  const tokens = tokenize(content);
1867
- if (!hasContentTokens(tokens)) {
1868
- return makeResObj();
1869
- }
1870
1959
  const ast = parseTokens(tokens);
1960
+ assertObjectRootedInclude(ast, candidate);
1871
1961
  return this.onBuildResObjAsync(ast, {
1872
1962
  ...this.opts,
1873
1963
  baseDir: nodePath.dirname(candidate),
@@ -3059,8 +3149,14 @@ async function defaultReadFile(filePath) {
3059
3149
  }
3060
3150
  function buildResolveContext(input, opts) {
3061
3151
  const tokens = tokenize(input);
3062
- assertNonEmptyDocument(tokens, "input");
3063
3152
  const ast = parseTokens(tokens);
3153
+ if (ast.kind === "array") {
3154
+ const origin = opts.originDescription ?? "input";
3155
+ throw new ConfigError(
3156
+ `${origin}: ${ast.pos.line}:${ast.pos.col}: document has type array rather than object at file root (HOCON.md L989-991); the Config API requires an object root`,
3157
+ ""
3158
+ );
3159
+ }
3064
3160
  const resolveOpts = {
3065
3161
  env: getEnv(opts),
3066
3162
  baseDir: opts.baseDir,
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as Config } from './config-D27s3_aG.cjs';
2
- export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ReadonlyHoconValue, b as ResolveOptions, S as ScalarValueType, c as asArray, d as asBoolean, e as asNumber, f as asObject, g as asString, h as defaultParseOptions, i as defaultResolveOptions, j as isArray, k as isNull, l as isObject, m as isScalar, p as parse, n as parseAsync, o as parseFile, q as parseFileAsync, r as parseFileWithOptions, s as parseString, t as parseStringWithOptions } from './config-D27s3_aG.cjs';
1
+ import { C as Config } from './config-PdhVgeLS.cjs';
2
+ export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ReadonlyHoconValue, b as ResolveOptions, S as ScalarValueType, c as asArray, d as asBoolean, e as asNumber, f as asObject, g as asString, h as defaultParseOptions, i as defaultResolveOptions, j as isArray, k as isNull, l as isObject, m as isScalar, p as parse, n as parseAsync, o as parseFile, q as parseFileAsync, r as parseFileWithOptions, s as parseString, t as parseStringWithOptions } from './config-PdhVgeLS.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-D27s3_aG.js';
2
- export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ReadonlyHoconValue, b as ResolveOptions, S as ScalarValueType, c as asArray, d as asBoolean, e as asNumber, f as asObject, g as asString, h as defaultParseOptions, i as defaultResolveOptions, j as isArray, k as isNull, l as isObject, m as isScalar, p as parse, n as parseAsync, o as parseFile, q as parseFileAsync, r as parseFileWithOptions, s as parseString, t as parseStringWithOptions } from './config-D27s3_aG.js';
1
+ import { C as Config } from './config-PdhVgeLS.js';
2
+ export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ReadonlyHoconValue, b as ResolveOptions, S as ScalarValueType, c as asArray, d as asBoolean, e as asNumber, f as asObject, g as asString, h as defaultParseOptions, i as defaultResolveOptions, j as isArray, k as isNull, l as isObject, m as isScalar, p as parse, n as parseAsync, o as parseFile, q as parseFileAsync, r as parseFileWithOptions, s as parseString, t as parseStringWithOptions } from './config-PdhVgeLS.js';
3
3
 
4
4
  declare class ParseError extends Error {
5
5
  readonly line: number;
package/dist/index.js CHANGED
@@ -80,7 +80,7 @@ function parseDuration(value, outputUnit = "ms") {
80
80
  if (i === 0) return NaN;
81
81
  const num = Number(trimmed.slice(0, i));
82
82
  if (Number.isNaN(num)) return NaN;
83
- const unit = trimHoconWs(trimmed.slice(i)).toLowerCase();
83
+ const unit = trimHoconWs(trimmed.slice(i));
84
84
  const divisor = OUTPUT_DURATION_UNITS[outputUnit];
85
85
  if (divisor === void 0) return NaN;
86
86
  if (unit === "") {
@@ -640,20 +640,6 @@ function isFileNotFoundError(e) {
640
640
  import { createRequire } from "module";
641
641
  import * as nodePath from "path";
642
642
 
643
- // src/internal/parser/empty-check.ts
644
- function hasContentTokens(tokens) {
645
- return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
646
- }
647
- function assertNonEmptyDocument(tokens, sourceDescriptor) {
648
- if (!hasContentTokens(tokens)) {
649
- throw new ParseError(
650
- `empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
651
- 1,
652
- 1
653
- );
654
- }
655
- }
656
-
657
643
  // src/internal/lexer/lexer.ts
658
644
  function isHoconWhitespace(ch) {
659
645
  const cp = ch.codePointAt(0) ?? -1;
@@ -1054,12 +1040,12 @@ function isDecimalDigit(ch) {
1054
1040
  }
1055
1041
  function isUnquotedStart(ch) {
1056
1042
  if (ch === "" || isHoconWhitespace(ch)) return false;
1057
- if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
1043
+ if ('{}[],:=+#"$?!@*&^`\\'.includes(ch)) return false;
1058
1044
  return true;
1059
1045
  }
1060
1046
  function isUnquotedContinue(ch, nextFn) {
1061
1047
  if (ch === "" || isHoconWhitespace(ch)) return false;
1062
- if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
1048
+ if ('{}[],:=#"$?!@*&^`\\'.includes(ch)) return false;
1063
1049
  if (ch === "+" && nextFn() === "=") return false;
1064
1050
  if (ch === "/" && nextFn() === "/") return false;
1065
1051
  return true;
@@ -1099,6 +1085,16 @@ var Parser = class {
1099
1085
  }
1100
1086
  return { kind: "object", fields: allFields, pos: first.pos };
1101
1087
  }
1088
+ if (t.kind === "lbracket") {
1089
+ this.advance();
1090
+ const arr = this.parseArray();
1091
+ this.skip("newline");
1092
+ const remaining = this.peek();
1093
+ if (remaining.kind !== "eof") {
1094
+ throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1095
+ }
1096
+ return { ...arr, pos: { line: t.line, col: t.col } };
1097
+ }
1102
1098
  return this.parseObject(false);
1103
1099
  }
1104
1100
  peek(offset = 0) {
@@ -1198,6 +1194,17 @@ var Parser = class {
1198
1194
  this.advance();
1199
1195
  const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
1200
1196
  const parts = raw.split(".");
1197
+ const leadingDotIsSeparator = spaceConcat || postDotPrefix !== "";
1198
+ for (let i = 0; i < parts.length; i++) {
1199
+ if (parts[i] !== "") continue;
1200
+ if (i === parts.length - 1) continue;
1201
+ if (i === 0 && leadingDotIsSeparator) continue;
1202
+ throw new ParseError(
1203
+ `path has ${i === 0 ? "a leading period '.'" : "two adjacent periods '.'"} \u2014 empty key segment not allowed, use quoted "" (HOCON.md path rules)`,
1204
+ t.line,
1205
+ t.col
1206
+ );
1207
+ }
1201
1208
  const filtered = parts.filter((s) => s.length > 0);
1202
1209
  if (spaceConcat) {
1203
1210
  segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
@@ -1476,29 +1483,108 @@ function parseTokens(tokens) {
1476
1483
 
1477
1484
  // src/internal/properties/properties.ts
1478
1485
  function parseProperties(input) {
1479
- const root = /* @__PURE__ */ Object.create(null);
1480
1486
  const pairs = [];
1481
- for (const line of input.split("\n")) {
1482
- const trimmed = line.trim();
1483
- if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
1484
- const sepIdx = findSeparator(trimmed);
1485
- if (sepIdx === -1) continue;
1486
- const key = trimmed.slice(0, sepIdx).trim();
1487
- const value = trimmed.slice(sepIdx + 1).trim();
1487
+ for (const { text, line } of logicalLines(input)) {
1488
+ const [rawKey, rawValue] = splitKeyValue(text);
1489
+ const key = unescapeProps(rawKey, line);
1488
1490
  if (key === "") continue;
1489
- pairs.push([key, value]);
1491
+ pairs.push([key, unescapeProps(rawValue, line)]);
1490
1492
  }
1491
- pairs.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1492
- for (const [key, value] of pairs) {
1493
+ return nestPairs(pairs);
1494
+ }
1495
+ function nestPairs(pairs) {
1496
+ const root = /* @__PURE__ */ Object.create(null);
1497
+ const sorted = [...pairs].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1498
+ for (const [key, value] of sorted) {
1493
1499
  setNested(root, key.split("."), value);
1494
1500
  }
1495
1501
  return root;
1496
1502
  }
1497
- function findSeparator(line) {
1498
- for (let i = 0; i < line.length; i++) {
1499
- if (line[i] === "=" || line[i] === ":") return i;
1503
+ function logicalLines(input) {
1504
+ const natural = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1505
+ const out = [];
1506
+ for (let i = 0; i < natural.length; i++) {
1507
+ let text = trimLeadingSpace(natural[i] ?? "");
1508
+ if (text === "" || text.startsWith("#") || text.startsWith("!")) continue;
1509
+ const start = i + 1;
1510
+ while (endsWithContinuation(text)) {
1511
+ text = text.slice(0, -1);
1512
+ if (i + 1 >= natural.length) break;
1513
+ i++;
1514
+ text += trimLeadingSpace(natural[i] ?? "");
1515
+ }
1516
+ out.push({ text, line: start });
1517
+ }
1518
+ return out;
1519
+ }
1520
+ function trimLeadingSpace(s) {
1521
+ return s.replace(/^[ \t\f]+/, "");
1522
+ }
1523
+ function endsWithContinuation(line) {
1524
+ let n = 0;
1525
+ for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i--) n++;
1526
+ return n % 2 === 1;
1527
+ }
1528
+ function splitKeyValue(line) {
1529
+ let key = "";
1530
+ let i = 0;
1531
+ for (; i < line.length; i++) {
1532
+ const c = line[i];
1533
+ if (c === "\\" && i + 1 < line.length) {
1534
+ key += c + line[i + 1];
1535
+ i++;
1536
+ continue;
1537
+ }
1538
+ if (c === "=" || c === ":" || isPropsSpace(c)) break;
1539
+ key += c;
1540
+ }
1541
+ while (i < line.length && isPropsSpace(line[i])) i++;
1542
+ if (i < line.length && (line[i] === "=" || line[i] === ":")) {
1543
+ i++;
1544
+ while (i < line.length && isPropsSpace(line[i])) i++;
1545
+ }
1546
+ return [key, line.slice(i)];
1547
+ }
1548
+ function isPropsSpace(c) {
1549
+ return c === " " || c === " " || c === "\f";
1550
+ }
1551
+ function unescapeProps(s, line) {
1552
+ let out = "";
1553
+ for (let i = 0; i < s.length; i++) {
1554
+ if (s[i] !== "\\") {
1555
+ out += s[i];
1556
+ continue;
1557
+ }
1558
+ i++;
1559
+ if (i >= s.length) break;
1560
+ const e = s[i];
1561
+ switch (e) {
1562
+ case "t":
1563
+ out += " ";
1564
+ break;
1565
+ case "n":
1566
+ out += "\n";
1567
+ break;
1568
+ case "r":
1569
+ out += "\r";
1570
+ break;
1571
+ case "f":
1572
+ out += "\f";
1573
+ break;
1574
+ case "u": {
1575
+ const hex = s.slice(i + 1, i + 5);
1576
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1577
+ throw new ParseError(`malformed \\u escape in .properties: \\u${hex}`, line, i + 1);
1578
+ }
1579
+ out += String.fromCharCode(parseInt(hex, 16));
1580
+ i += 4;
1581
+ break;
1582
+ }
1583
+ default:
1584
+ out += e;
1585
+ }
1500
1586
  }
1501
- return -1;
1587
+ return out;
1502
1588
  }
1503
1589
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1504
1590
  function setNested(obj, segments, value) {
@@ -1537,6 +1623,16 @@ var _require = createRequire(import.meta.url);
1537
1623
  function isPnpPath(p) {
1538
1624
  return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
1539
1625
  }
1626
+ function assertObjectRootedInclude(ast, sourcePath) {
1627
+ if (ast.kind === "array") {
1628
+ throw new ResolveError(
1629
+ `included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
1630
+ sourcePath,
1631
+ ast.pos.line,
1632
+ ast.pos.col
1633
+ );
1634
+ }
1635
+ }
1540
1636
  function validatePackageFile(file, identifier) {
1541
1637
  if (file.length === 0) {
1542
1638
  throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
@@ -1716,10 +1812,9 @@ var IncludeLoader = class {
1716
1812
  const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
1717
1813
  const resolvedPath = resolver(identifier, file, void 0, baseDir);
1718
1814
  const content = readFileSync2(resolvedPath);
1719
- if (content.length === 0) return makeResObj();
1720
1815
  const tokens = tokenize(content);
1721
- assertNonEmptyDocument(tokens, resolvedPath);
1722
1816
  const ast = parseTokens(tokens);
1817
+ assertObjectRootedInclude(ast, resolvedPath);
1723
1818
  return this.onBuildResObj(ast, {
1724
1819
  ...this.opts,
1725
1820
  baseDir: nodePath.dirname(resolvedPath),
@@ -1749,10 +1844,9 @@ var IncludeLoader = class {
1749
1844
  const resolvedPath = resolver(identifier, file, void 0, baseDir);
1750
1845
  const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
1751
1846
  const content = await read(resolvedPath);
1752
- if (content.length === 0) return makeResObj();
1753
1847
  const tokens = tokenize(content);
1754
- assertNonEmptyDocument(tokens, resolvedPath);
1755
1848
  const ast = parseTokens(tokens);
1849
+ assertObjectRootedInclude(ast, resolvedPath);
1756
1850
  return this.onBuildResObjAsync(ast, {
1757
1851
  ...this.opts,
1758
1852
  baseDir: nodePath.dirname(resolvedPath),
@@ -1775,10 +1869,8 @@ var IncludeLoader = class {
1775
1869
  return hoconValueToResObj(propertiesToHoconValue(content));
1776
1870
  }
1777
1871
  const tokens = tokenize(content);
1778
- if (!hasContentTokens(tokens)) {
1779
- return makeResObj();
1780
- }
1781
1872
  const ast = parseTokens(tokens);
1873
+ assertObjectRootedInclude(ast, candidate);
1782
1874
  return this.onBuildResObj(ast, {
1783
1875
  ...this.opts,
1784
1876
  baseDir: nodePath.dirname(candidate),
@@ -1802,10 +1894,8 @@ var IncludeLoader = class {
1802
1894
  return hoconValueToResObj(propertiesToHoconValue(content));
1803
1895
  }
1804
1896
  const tokens = tokenize(content);
1805
- if (!hasContentTokens(tokens)) {
1806
- return makeResObj();
1807
- }
1808
1897
  const ast = parseTokens(tokens);
1898
+ assertObjectRootedInclude(ast, candidate);
1809
1899
  return this.onBuildResObjAsync(ast, {
1810
1900
  ...this.opts,
1811
1901
  baseDir: nodePath.dirname(candidate),
@@ -2997,8 +3087,14 @@ async function defaultReadFile(filePath) {
2997
3087
  }
2998
3088
  function buildResolveContext(input, opts) {
2999
3089
  const tokens = tokenize(input);
3000
- assertNonEmptyDocument(tokens, "input");
3001
3090
  const ast = parseTokens(tokens);
3091
+ if (ast.kind === "array") {
3092
+ const origin = opts.originDescription ?? "input";
3093
+ throw new ConfigError(
3094
+ `${origin}: ${ast.pos.line}:${ast.pos.col}: document has type array rather than object at file root (HOCON.md L989-991); the Config API requires an object root`,
3095
+ ""
3096
+ );
3097
+ }
3002
3098
  const resolveOpts = {
3003
3099
  env: getEnv(opts),
3004
3100
  baseDir: opts.baseDir,