@o3co/ts.hocon 1.13.0 → 1.14.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/README.ja.md +253 -29
- package/README.md +161 -29
- package/dist/adapters/env.cjs +26 -2018
- package/dist/adapters/env.d.cts +1 -1
- package/dist/adapters/env.d.ts +1 -1
- package/dist/adapters/env.js +11 -1965
- package/dist/adapters/json5.cjs +15 -1969
- package/dist/adapters/json5.d.cts +1 -1
- package/dist/adapters/json5.d.ts +1 -1
- package/dist/adapters/json5.js +12 -1929
- package/dist/adapters/jsonc.cjs +17 -1972
- package/dist/adapters/jsonc.d.cts +1 -1
- package/dist/adapters/jsonc.d.ts +1 -1
- package/dist/adapters/jsonc.js +9 -1926
- package/dist/adapters/properties.cjs +7 -2113
- package/dist/adapters/properties.d.cts +1 -1
- package/dist/adapters/properties.d.ts +1 -1
- package/dist/adapters/properties.js +6 -2075
- package/dist/adapters/toml.cjs +12 -1966
- package/dist/adapters/toml.d.cts +1 -1
- package/dist/adapters/toml.d.ts +1 -1
- package/dist/adapters/toml.js +10 -1929
- package/dist/adapters/yaml.cjs +20 -1975
- package/dist/adapters/yaml.d.cts +1 -1
- package/dist/adapters/yaml.d.ts +1 -1
- package/dist/adapters/yaml.js +10 -1929
- package/dist/chunk-GOXMKYWS.js +3801 -0
- package/dist/chunk-JVABBNF5.js +158 -0
- package/dist/chunk-L3D2RE2U.cjs +3801 -0
- package/dist/chunk-OUPKYWL3.cjs +158 -0
- package/dist/chunk-PCAMDXJ4.cjs +83 -0
- package/dist/chunk-YRNDXYGQ.js +83 -0
- package/dist/{config-6sApTEMM.d.cts → config-OccaKqXu.d.cts} +126 -4
- package/dist/{config-6sApTEMM.d.ts → config-OccaKqXu.d.ts} +126 -4
- package/dist/index.cjs +34 -3793
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +25 -3728
- package/dist/zod.cjs +195 -3636
- package/dist/zod.d.cts +86 -3
- package/dist/zod.d.ts +86 -3
- package/dist/zod.js +182 -3589
- package/package.json +5 -5
|
@@ -0,0 +1,3801 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }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
|
+
|
|
8
|
+
// src/internal/exact-ratio.ts
|
|
9
|
+
var EXACT_LIMIT = 2n ** 53n;
|
|
10
|
+
function bitLength(n) {
|
|
11
|
+
return n.toString(2).length;
|
|
12
|
+
}
|
|
13
|
+
function timesPowerOfTwo(x, e) {
|
|
14
|
+
while (e > 1e3) {
|
|
15
|
+
x *= 2 ** 1e3;
|
|
16
|
+
e -= 1e3;
|
|
17
|
+
}
|
|
18
|
+
while (e < -1e3) {
|
|
19
|
+
x *= 2 ** -1e3;
|
|
20
|
+
e += 1e3;
|
|
21
|
+
}
|
|
22
|
+
return x * 2 ** e;
|
|
23
|
+
}
|
|
24
|
+
function ratioToNumber(n, d) {
|
|
25
|
+
if (n === 0n) return 0;
|
|
26
|
+
if (n <= EXACT_LIMIT && n >= -EXACT_LIMIT && d <= EXACT_LIMIT) return Number(n) / Number(d);
|
|
27
|
+
const negative = n < 0n;
|
|
28
|
+
const a = negative ? -n : n;
|
|
29
|
+
let shift = 65 - (bitLength(a) - bitLength(d));
|
|
30
|
+
for (; ; ) {
|
|
31
|
+
const num = shift >= 0 ? a << BigInt(shift) : a;
|
|
32
|
+
const den = shift >= 0 ? d : d << BigInt(-shift);
|
|
33
|
+
const q = num / den;
|
|
34
|
+
const length = bitLength(q);
|
|
35
|
+
if (length > 65) {
|
|
36
|
+
shift--;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (length < 65) {
|
|
40
|
+
shift++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const value = shift > 1086 ? subnormalRatio(a, d) : timesPowerOfTwo(Number(q | (num % den === 0n ? 0n : 1n)), -shift);
|
|
44
|
+
return negative ? -value : value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function subnormalRatio(a, d) {
|
|
48
|
+
const num = a << 1074n;
|
|
49
|
+
let k = num / d;
|
|
50
|
+
const twice = num % d * 2n;
|
|
51
|
+
if (twice > d || twice === d && (k & 1n) === 1n) k++;
|
|
52
|
+
return Number(k) * 2 ** -1074;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/coerce.ts
|
|
56
|
+
var DECIMAL_NUMBER_RE = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
57
|
+
function isHoconWs(cp) {
|
|
58
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
59
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
60
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
61
|
+
if (cp === 5760) return true;
|
|
62
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
63
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
64
|
+
if (cp === 12288) return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
function trimHoconWs(s) {
|
|
68
|
+
let start = 0;
|
|
69
|
+
let end = s.length;
|
|
70
|
+
while (start < end && isHoconWs(s.charCodeAt(start))) {
|
|
71
|
+
start++;
|
|
72
|
+
}
|
|
73
|
+
while (end > start && isHoconWs(s.charCodeAt(end - 1))) {
|
|
74
|
+
end--;
|
|
75
|
+
}
|
|
76
|
+
return s.slice(start, end);
|
|
77
|
+
}
|
|
78
|
+
var TRUTHY = /* @__PURE__ */ new Set(["true", "yes", "on"]);
|
|
79
|
+
var FALSY = /* @__PURE__ */ new Set(["false", "no", "off"]);
|
|
80
|
+
function coerceBoolean(value) {
|
|
81
|
+
const lower = value.toLowerCase();
|
|
82
|
+
if (TRUTHY.has(lower)) return true;
|
|
83
|
+
if (FALSY.has(lower)) return false;
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
function coerceNumber(value) {
|
|
87
|
+
if (!DECIMAL_NUMBER_RE.test(value)) return void 0;
|
|
88
|
+
return Number(value);
|
|
89
|
+
}
|
|
90
|
+
function isDigitChar(ch) {
|
|
91
|
+
return ch !== void 0 && ch >= "0" && ch <= "9";
|
|
92
|
+
}
|
|
93
|
+
function splitUnitText(value) {
|
|
94
|
+
const t = trimHoconWs(value);
|
|
95
|
+
let i = 0;
|
|
96
|
+
const negative = t[0] === "-";
|
|
97
|
+
if (t[0] === "-" || t[0] === "+") i++;
|
|
98
|
+
const intStart = i;
|
|
99
|
+
while (isDigitChar(t[i])) i++;
|
|
100
|
+
const intDigits = t.slice(intStart, i);
|
|
101
|
+
let fraction = "";
|
|
102
|
+
if (t[i] === ".") {
|
|
103
|
+
const fracStart = ++i;
|
|
104
|
+
while (isDigitChar(t[i])) i++;
|
|
105
|
+
fraction = t.slice(fracStart, i);
|
|
106
|
+
}
|
|
107
|
+
if (intDigits === "" && fraction === "") return void 0;
|
|
108
|
+
let exponent = 0;
|
|
109
|
+
if (t[i] === "e" || t[i] === "E") {
|
|
110
|
+
let j = i + 1;
|
|
111
|
+
if (t[j] === "+" || t[j] === "-") j++;
|
|
112
|
+
if (isDigitChar(t[j])) {
|
|
113
|
+
const expStart = j;
|
|
114
|
+
while (isDigitChar(t[j])) j++;
|
|
115
|
+
const digits = t.slice(expStart, j).replace(/^0+(?=\d)/, "");
|
|
116
|
+
const magnitude2 = digits.length > 12 ? 1e12 : Number(digits);
|
|
117
|
+
exponent = t[i + 1] === "-" ? -magnitude2 : magnitude2;
|
|
118
|
+
i = j;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const magnitude = BigInt(intDigits + fraction || "0");
|
|
122
|
+
return {
|
|
123
|
+
mantissa: negative ? -magnitude : magnitude,
|
|
124
|
+
scale: fraction.length - exponent,
|
|
125
|
+
unit: trimHoconWs(t.slice(i))
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function decimalMagnitude(text) {
|
|
129
|
+
if (text.mantissa === 0n) return void 0;
|
|
130
|
+
const digits = (text.mantissa < 0n ? -text.mantissa : text.mantissa).toString().length;
|
|
131
|
+
return digits - 1 - text.scale;
|
|
132
|
+
}
|
|
133
|
+
function scaledRatio(text, factor) {
|
|
134
|
+
return text.scale >= 0 ? [text.mantissa * factor, 10n ** BigInt(text.scale)] : [text.mantissa * factor * 10n ** BigInt(-text.scale), 1n];
|
|
135
|
+
}
|
|
136
|
+
var DURATION_UNITS = new Map([
|
|
137
|
+
// S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
|
|
138
|
+
// spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
|
|
139
|
+
// (typesafe-config 1.4.6 probe, 2026-08-18).
|
|
140
|
+
...["ns", "nano", "nanos", "nanosecond", "nanoseconds"].map((u) => [u, 1n]),
|
|
141
|
+
...["us", "micro", "micros", "microsecond", "microseconds"].map((u) => [u, 1000n]),
|
|
142
|
+
...["ms", "milli", "millis", "millisecond", "milliseconds"].map((u) => [u, 1000000n]),
|
|
143
|
+
...["s", "second", "seconds"].map((u) => [u, 1000000000n]),
|
|
144
|
+
...["m", "minute", "minutes"].map((u) => [u, 60000000000n]),
|
|
145
|
+
...["h", "hour", "hours"].map((u) => [u, 3600000000000n]),
|
|
146
|
+
...["d", "day", "days"].map((u) => [u, 86400000000000n])
|
|
147
|
+
]);
|
|
148
|
+
var OUTPUT_DURATION_UNITS = /* @__PURE__ */ new Map([
|
|
149
|
+
["ns", 1n],
|
|
150
|
+
["us", 1000n],
|
|
151
|
+
["ms", 1000000n],
|
|
152
|
+
["s", 1000000000n],
|
|
153
|
+
["m", 60000000000n],
|
|
154
|
+
["h", 3600000000000n],
|
|
155
|
+
["d", 86400000000000n]
|
|
156
|
+
]);
|
|
157
|
+
function parseDuration(value, outputUnit = "ms") {
|
|
158
|
+
const text = splitUnitText(value);
|
|
159
|
+
if (text === void 0) return NaN;
|
|
160
|
+
const per = OUTPUT_DURATION_UNITS.get(outputUnit);
|
|
161
|
+
if (per === void 0) return NaN;
|
|
162
|
+
const unit = text.unit === "" ? 1000000n : DURATION_UNITS.get(text.unit);
|
|
163
|
+
if (unit === void 0) return NaN;
|
|
164
|
+
const magnitude = decimalMagnitude(text);
|
|
165
|
+
if (magnitude === void 0) return 0;
|
|
166
|
+
if (magnitude > 330) return text.mantissa < 0n ? -Infinity : Infinity;
|
|
167
|
+
if (magnitude < -345) return text.mantissa < 0n ? -0 : 0;
|
|
168
|
+
const [n, d] = scaledRatio(text, unit);
|
|
169
|
+
return ratioToNumber(n, d * per);
|
|
170
|
+
}
|
|
171
|
+
var BYTE_UNITS = new Map([
|
|
172
|
+
// S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
|
|
173
|
+
// 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
|
|
174
|
+
// form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
|
|
175
|
+
// are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
|
|
176
|
+
// first letter, and long forms are lowercase only (`kilobyte`, never
|
|
177
|
+
// `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
|
|
178
|
+
// -Xmx forms accept both cases.
|
|
179
|
+
...["B", "b", "byte", "bytes"].map((u) => [u, 1n]),
|
|
180
|
+
...[
|
|
181
|
+
["kB", "kilo"],
|
|
182
|
+
["MB", "mega"],
|
|
183
|
+
["GB", "giga"],
|
|
184
|
+
["TB", "tera"],
|
|
185
|
+
["PB", "peta"],
|
|
186
|
+
["EB", "exa"],
|
|
187
|
+
["ZB", "zetta"],
|
|
188
|
+
["YB", "yotta"]
|
|
189
|
+
].flatMap(([short, prefix], i) => {
|
|
190
|
+
const size = 1000n ** BigInt(i + 1);
|
|
191
|
+
return [[short, size], [`${prefix}byte`, size], [`${prefix}bytes`, size]];
|
|
192
|
+
}),
|
|
193
|
+
...[
|
|
194
|
+
["K", "kibi"],
|
|
195
|
+
["M", "mebi"],
|
|
196
|
+
["G", "gibi"],
|
|
197
|
+
["T", "tebi"],
|
|
198
|
+
["P", "pebi"],
|
|
199
|
+
["E", "exbi"],
|
|
200
|
+
["Z", "zebi"],
|
|
201
|
+
["Y", "yobi"]
|
|
202
|
+
].flatMap(([letter, prefix], i) => {
|
|
203
|
+
const size = 1024n ** BigInt(i + 1);
|
|
204
|
+
return [
|
|
205
|
+
[`${letter}i`, size],
|
|
206
|
+
[`${letter}iB`, size],
|
|
207
|
+
[`${prefix}byte`, size],
|
|
208
|
+
[`${prefix}bytes`, size],
|
|
209
|
+
// S21.4 — single letters are powers of two (java -Xmx convention), in
|
|
210
|
+
// both cases, through the full ladder.
|
|
211
|
+
[letter, size],
|
|
212
|
+
[letter.toLowerCase(), size]
|
|
213
|
+
];
|
|
214
|
+
})
|
|
215
|
+
]);
|
|
216
|
+
var OUTPUT_BYTE_UNITS = /* @__PURE__ */ new Map([
|
|
217
|
+
["B", 1],
|
|
218
|
+
["KB", 1e3],
|
|
219
|
+
["KiB", 1024],
|
|
220
|
+
["MB", 1e6],
|
|
221
|
+
["MiB", 1048576],
|
|
222
|
+
["GB", 1e9],
|
|
223
|
+
["GiB", 1073741824],
|
|
224
|
+
["TB", 1e12],
|
|
225
|
+
["TiB", 1099511627776]
|
|
226
|
+
]);
|
|
227
|
+
var BYTE_SIZE_OVERFLOW = "byte size overflows representable range (max 2^53-1 bytes)";
|
|
228
|
+
function byteSizeOverflow() {
|
|
229
|
+
return new RangeError(BYTE_SIZE_OVERFLOW);
|
|
230
|
+
}
|
|
231
|
+
function parseBytes(value, outputUnit = "B") {
|
|
232
|
+
const text = splitUnitText(value);
|
|
233
|
+
if (text === void 0) return NaN;
|
|
234
|
+
const per = OUTPUT_BYTE_UNITS.get(outputUnit);
|
|
235
|
+
if (per === void 0) return NaN;
|
|
236
|
+
const unit = text.unit === "" ? 1n : BYTE_UNITS.get(text.unit);
|
|
237
|
+
if (unit === void 0) return NaN;
|
|
238
|
+
const magnitude = decimalMagnitude(text);
|
|
239
|
+
if (magnitude === void 0) return 0;
|
|
240
|
+
if (magnitude >= 16) throw byteSizeOverflow();
|
|
241
|
+
if (magnitude < -25) return 0;
|
|
242
|
+
const [n, d] = scaledRatio(text, unit);
|
|
243
|
+
const bytes = n / d;
|
|
244
|
+
if (bytes > MAX_SAFE_BYTES || bytes < -MAX_SAFE_BYTES) throw byteSizeOverflow();
|
|
245
|
+
return Number(bytes) / per;
|
|
246
|
+
}
|
|
247
|
+
var MAX_SAFE_BYTES = BigInt(Number.MAX_SAFE_INTEGER);
|
|
248
|
+
function readDuration(text, outputUnit) {
|
|
249
|
+
const value = parseDuration(text, outputUnit);
|
|
250
|
+
if (Number.isNaN(value)) return { ok: false, problem: "invalid" };
|
|
251
|
+
if (!Number.isFinite(value)) return { ok: false, problem: "overflow" };
|
|
252
|
+
return { ok: true, value };
|
|
253
|
+
}
|
|
254
|
+
function readBytes(text, outputUnit) {
|
|
255
|
+
let value;
|
|
256
|
+
try {
|
|
257
|
+
value = parseBytes(text, outputUnit);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
if (err instanceof RangeError && err.message === BYTE_SIZE_OVERFLOW) {
|
|
260
|
+
return { ok: false, problem: "overflow" };
|
|
261
|
+
}
|
|
262
|
+
throw err;
|
|
263
|
+
}
|
|
264
|
+
if (Number.isNaN(value)) return { ok: false, problem: "invalid" };
|
|
265
|
+
if (value < 0) return { ok: false, problem: "negative" };
|
|
266
|
+
return { ok: true, value };
|
|
267
|
+
}
|
|
268
|
+
var PERIOD_UNITS = /* @__PURE__ */ new Map([
|
|
269
|
+
["", { field: "days", factor: 1 }],
|
|
270
|
+
["d", { field: "days", factor: 1 }],
|
|
271
|
+
["day", { field: "days", factor: 1 }],
|
|
272
|
+
["days", { field: "days", factor: 1 }],
|
|
273
|
+
["w", { field: "days", factor: 7 }],
|
|
274
|
+
["week", { field: "days", factor: 7 }],
|
|
275
|
+
["weeks", { field: "days", factor: 7 }],
|
|
276
|
+
["m", { field: "months", factor: 1 }],
|
|
277
|
+
["mo", { field: "months", factor: 1 }],
|
|
278
|
+
["month", { field: "months", factor: 1 }],
|
|
279
|
+
["months", { field: "months", factor: 1 }],
|
|
280
|
+
["y", { field: "years", factor: 1 }],
|
|
281
|
+
["year", { field: "years", factor: 1 }],
|
|
282
|
+
["years", { field: "years", factor: 1 }]
|
|
283
|
+
]);
|
|
284
|
+
var I32_MIN = -(2 ** 31);
|
|
285
|
+
var I32_MAX = 2 ** 31 - 1;
|
|
286
|
+
function readPeriod(text) {
|
|
287
|
+
const trimmed = trimHoconWs(text);
|
|
288
|
+
let i = 0;
|
|
289
|
+
while (i < trimmed.length && "+-0123456789".includes(trimmed[i])) i++;
|
|
290
|
+
const digits = trimmed.slice(0, i);
|
|
291
|
+
const unit = PERIOD_UNITS.get(trimHoconWs(trimmed.slice(i)));
|
|
292
|
+
if (!/^[+-]?\d+$/.test(digits) || unit === void 0) return { ok: false, problem: "invalid" };
|
|
293
|
+
const n = Number(digits) * unit.factor;
|
|
294
|
+
if (n < I32_MIN || n > I32_MAX) return { ok: false, problem: "invalid" };
|
|
295
|
+
const value = { years: 0, months: 0, days: 0, [unit.field]: n === 0 ? 0 : n };
|
|
296
|
+
return { ok: true, value };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/errors.ts
|
|
300
|
+
var ParseError = class extends Error {
|
|
301
|
+
constructor(message, line, col, file) {
|
|
302
|
+
super(message);
|
|
303
|
+
this.line = line;
|
|
304
|
+
this.col = col;
|
|
305
|
+
this.file = file;
|
|
306
|
+
this.name = "ParseError";
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
var ResolveError = class extends Error {
|
|
310
|
+
constructor(message, path, line, col, file) {
|
|
311
|
+
super(message);
|
|
312
|
+
this.path = path;
|
|
313
|
+
this.line = line;
|
|
314
|
+
this.col = col;
|
|
315
|
+
this.file = file;
|
|
316
|
+
this.name = "ResolveError";
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
var PackageLookupError = class extends ResolveError {
|
|
320
|
+
constructor(message, identifier, packageFile, line, col) {
|
|
321
|
+
super(message, `${identifier}/${packageFile}`, line, col);
|
|
322
|
+
this.identifier = identifier;
|
|
323
|
+
this.packageFile = packageFile;
|
|
324
|
+
this.name = "PackageLookupError";
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
var ConfigError = class extends Error {
|
|
328
|
+
constructor(message, path) {
|
|
329
|
+
super(message);
|
|
330
|
+
this.path = path;
|
|
331
|
+
this.name = "ConfigError";
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var NotResolvedError = class extends ConfigError {
|
|
335
|
+
constructor(path) {
|
|
336
|
+
super(`value at path "${path}" is not resolved (call resolve() first)`, path);
|
|
337
|
+
this.name = "NotResolvedError";
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// src/internal/strip-bom.ts
|
|
342
|
+
function stripBom(input) {
|
|
343
|
+
return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/internal/depth.ts
|
|
347
|
+
var MAX_PATH_SEGMENTS = 64;
|
|
348
|
+
function tooDeep(segments) {
|
|
349
|
+
return segments > MAX_PATH_SEGMENTS;
|
|
350
|
+
}
|
|
351
|
+
function guardStackDepth(fn, wrap) {
|
|
352
|
+
try {
|
|
353
|
+
return fn();
|
|
354
|
+
} catch (e) {
|
|
355
|
+
if (!isStackOverflow(e)) throw e;
|
|
356
|
+
throw wrap(STACK_MESSAGE);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
|
|
360
|
+
function isStackOverflow(e) {
|
|
361
|
+
if (!(e instanceof RangeError)) return false;
|
|
362
|
+
const m = e.message.toLowerCase();
|
|
363
|
+
return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
|
|
364
|
+
m.includes("recursion") || // SpiderMonkey: "too much recursion"
|
|
365
|
+
m.includes("stack overflow");
|
|
366
|
+
}
|
|
367
|
+
function depthError(message) {
|
|
368
|
+
return new ConfigError(message, "");
|
|
369
|
+
}
|
|
370
|
+
async function guardStackDepthAsync(fn, wrap) {
|
|
371
|
+
try {
|
|
372
|
+
return await fn();
|
|
373
|
+
} catch (e) {
|
|
374
|
+
if (!isStackOverflow(e)) throw e;
|
|
375
|
+
throw wrap(STACK_MESSAGE);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/internal/properties/properties.ts
|
|
380
|
+
function parseProperties(input) {
|
|
381
|
+
const pairs = [];
|
|
382
|
+
for (const { text, line } of logicalLines(stripBom(input))) {
|
|
383
|
+
const [rawKey, rawValue] = splitKeyValue(text);
|
|
384
|
+
const key = unescapeProps(rawKey, line);
|
|
385
|
+
if (key === "") continue;
|
|
386
|
+
const segments = key.split(".");
|
|
387
|
+
if (tooDeep(segments.length)) {
|
|
388
|
+
throw new ParseError(
|
|
389
|
+
`key maps to a path ${segments.length} segments deep, over the limit of ${MAX_PATH_SEGMENTS}`,
|
|
390
|
+
line,
|
|
391
|
+
1
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
pairs.push([segments, unescapeProps(rawValue, line)]);
|
|
395
|
+
}
|
|
396
|
+
return nestPairs(pairs);
|
|
397
|
+
}
|
|
398
|
+
function nestPairs(pairs) {
|
|
399
|
+
const root = /* @__PURE__ */ Object.create(null);
|
|
400
|
+
const sorted = [...pairs].sort(([a], [b]) => compareSegments(a, b));
|
|
401
|
+
for (const [segments, value] of sorted) {
|
|
402
|
+
setNested(root, segments, value);
|
|
403
|
+
}
|
|
404
|
+
return root;
|
|
405
|
+
}
|
|
406
|
+
function compareSegments(a, b) {
|
|
407
|
+
const n = Math.min(a.length, b.length);
|
|
408
|
+
for (let i = 0; i < n; i++) {
|
|
409
|
+
const x = a[i];
|
|
410
|
+
const y = b[i];
|
|
411
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
412
|
+
}
|
|
413
|
+
return a.length - b.length;
|
|
414
|
+
}
|
|
415
|
+
function pathKey(segments) {
|
|
416
|
+
return JSON.stringify(segments);
|
|
417
|
+
}
|
|
418
|
+
function logicalLines(input) {
|
|
419
|
+
const natural = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
420
|
+
const out = [];
|
|
421
|
+
for (let i = 0; i < natural.length; i++) {
|
|
422
|
+
let text = trimLeadingSpace(_nullishCoalesce(natural[i], () => ( "")));
|
|
423
|
+
if (text === "" || text.startsWith("#") || text.startsWith("!")) continue;
|
|
424
|
+
const start = i + 1;
|
|
425
|
+
while (endsWithContinuation(text)) {
|
|
426
|
+
text = text.slice(0, -1);
|
|
427
|
+
if (i + 1 >= natural.length) break;
|
|
428
|
+
i++;
|
|
429
|
+
text += trimLeadingSpace(_nullishCoalesce(natural[i], () => ( "")));
|
|
430
|
+
}
|
|
431
|
+
out.push({ text, line: start });
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
function trimLeadingSpace(s) {
|
|
436
|
+
return s.replace(/^[ \t\f]+/, "");
|
|
437
|
+
}
|
|
438
|
+
function endsWithContinuation(line) {
|
|
439
|
+
let n = 0;
|
|
440
|
+
for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i--) n++;
|
|
441
|
+
return n % 2 === 1;
|
|
442
|
+
}
|
|
443
|
+
function splitKeyValue(line) {
|
|
444
|
+
let key = "";
|
|
445
|
+
let i = 0;
|
|
446
|
+
for (; i < line.length; i++) {
|
|
447
|
+
const c = line[i];
|
|
448
|
+
if (c === "\\" && i + 1 < line.length) {
|
|
449
|
+
key += c + line[i + 1];
|
|
450
|
+
i++;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (c === "=" || c === ":" || isPropsSpace(c)) break;
|
|
454
|
+
key += c;
|
|
455
|
+
}
|
|
456
|
+
while (i < line.length && isPropsSpace(line[i])) i++;
|
|
457
|
+
if (i < line.length && (line[i] === "=" || line[i] === ":")) {
|
|
458
|
+
i++;
|
|
459
|
+
while (i < line.length && isPropsSpace(line[i])) i++;
|
|
460
|
+
}
|
|
461
|
+
return [key, line.slice(i)];
|
|
462
|
+
}
|
|
463
|
+
function isPropsSpace(c) {
|
|
464
|
+
return c === " " || c === " " || c === "\f";
|
|
465
|
+
}
|
|
466
|
+
function unescapeProps(s, line) {
|
|
467
|
+
let out = "";
|
|
468
|
+
for (let i = 0; i < s.length; i++) {
|
|
469
|
+
if (s[i] !== "\\") {
|
|
470
|
+
out += s[i];
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
i++;
|
|
474
|
+
if (i >= s.length) break;
|
|
475
|
+
const e = s[i];
|
|
476
|
+
switch (e) {
|
|
477
|
+
case "t":
|
|
478
|
+
out += " ";
|
|
479
|
+
break;
|
|
480
|
+
case "n":
|
|
481
|
+
out += "\n";
|
|
482
|
+
break;
|
|
483
|
+
case "r":
|
|
484
|
+
out += "\r";
|
|
485
|
+
break;
|
|
486
|
+
case "f":
|
|
487
|
+
out += "\f";
|
|
488
|
+
break;
|
|
489
|
+
case "u": {
|
|
490
|
+
const hex = s.slice(i + 1, i + 5);
|
|
491
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
492
|
+
throw new ParseError(`malformed \\u escape in .properties: \\u${hex}`, line, i + 1);
|
|
493
|
+
}
|
|
494
|
+
out += String.fromCharCode(parseInt(hex, 16));
|
|
495
|
+
i += 4;
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
default:
|
|
499
|
+
out += e;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
function setNested(obj, segments, value) {
|
|
505
|
+
let current = obj;
|
|
506
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
507
|
+
const seg = segments[i];
|
|
508
|
+
if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
|
|
509
|
+
current[seg] = /* @__PURE__ */ Object.create(null);
|
|
510
|
+
}
|
|
511
|
+
current = current[seg];
|
|
512
|
+
}
|
|
513
|
+
const last = segments[segments.length - 1];
|
|
514
|
+
if (typeof current[last] === "object" && current[last] !== null) return;
|
|
515
|
+
current[last] = value;
|
|
516
|
+
}
|
|
517
|
+
function propertiesToHoconValue(input) {
|
|
518
|
+
const parsed = parseProperties(input);
|
|
519
|
+
return recordToHoconValue(parsed);
|
|
520
|
+
}
|
|
521
|
+
function recordToHoconValue(obj) {
|
|
522
|
+
const fields = /* @__PURE__ */ new Map();
|
|
523
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
524
|
+
if (typeof val === "string") {
|
|
525
|
+
fields.set(key, { kind: "scalar", raw: val, valueType: "string" });
|
|
526
|
+
} else if (val !== null && typeof val === "object") {
|
|
527
|
+
fields.set(key, recordToHoconValue(val));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return { kind: "object", fields };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/internal/resolver/fold-self-ref.ts
|
|
534
|
+
function substFullKey(s) {
|
|
535
|
+
return stringSegmentsToKey(s.segments.map((seg) => seg.text));
|
|
536
|
+
}
|
|
537
|
+
function stringSegmentsToKey(segments) {
|
|
538
|
+
return segments.map((t) => {
|
|
539
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
540
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
541
|
+
}
|
|
542
|
+
return t;
|
|
543
|
+
}).join(".");
|
|
544
|
+
}
|
|
545
|
+
function prefixSelfRefRemainder(s, fullKey) {
|
|
546
|
+
const texts = s.segments.map((seg) => seg.text);
|
|
547
|
+
if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
|
|
548
|
+
for (let n = 1; n < texts.length; n++) {
|
|
549
|
+
if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
|
|
550
|
+
}
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
function navigateResolverValue(v, remainder) {
|
|
554
|
+
let cur = v;
|
|
555
|
+
for (const seg of remainder) {
|
|
556
|
+
if (isSubst(cur) || isConcat(cur)) return null;
|
|
557
|
+
if (isResObj(cur)) {
|
|
558
|
+
const next = cur.fields.get(seg);
|
|
559
|
+
if (next === void 0) return void 0;
|
|
560
|
+
cur = next;
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
const hv = cur;
|
|
564
|
+
if (hv.kind === "object") {
|
|
565
|
+
const next = hv.fields.get(seg);
|
|
566
|
+
if (next === void 0) return void 0;
|
|
567
|
+
cur = next;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
return void 0;
|
|
571
|
+
}
|
|
572
|
+
return cur;
|
|
573
|
+
}
|
|
574
|
+
function foldPrefixSelfRef(s, replacement, remainder) {
|
|
575
|
+
const navigated = navigateResolverValue(replacement, remainder);
|
|
576
|
+
if (navigated === null) return s;
|
|
577
|
+
if (navigated === void 0) {
|
|
578
|
+
return {
|
|
579
|
+
_kind: "subst-placeholder",
|
|
580
|
+
segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
581
|
+
optional: s.optional,
|
|
582
|
+
knownAbsent: true,
|
|
583
|
+
listSuffix: s.listSuffix,
|
|
584
|
+
line: s.line,
|
|
585
|
+
col: s.col,
|
|
586
|
+
prefixLen: s.prefixLen
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
return cloneResolverValue(navigated);
|
|
590
|
+
}
|
|
591
|
+
function mergeResObjLayers(base, top) {
|
|
592
|
+
const fields = new Map(base.fields);
|
|
593
|
+
for (const [k, tv] of top.fields) {
|
|
594
|
+
const bv = fields.get(k);
|
|
595
|
+
if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
|
|
596
|
+
fields.set(k, mergeResObjLayers(bv, tv));
|
|
597
|
+
} else {
|
|
598
|
+
fields.set(k, tv);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const priorValues = new Map(base.priorValues);
|
|
602
|
+
for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
|
|
603
|
+
const resetKeys = new Set(base.resetKeys);
|
|
604
|
+
for (const k of top.resetKeys) resetKeys.add(k);
|
|
605
|
+
return {
|
|
606
|
+
_kind: "res-obj",
|
|
607
|
+
fields,
|
|
608
|
+
priorValues,
|
|
609
|
+
resetKeys
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function containsSelfRef(v, fullKey) {
|
|
613
|
+
return containsSelfRefInner(v, fullKey, true);
|
|
614
|
+
}
|
|
615
|
+
function containsSelfRefInner(v, fullKey, allowPrefix) {
|
|
616
|
+
if (isSubst(v))
|
|
617
|
+
return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
|
|
618
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
|
|
619
|
+
if (isResObj(v)) {
|
|
620
|
+
for (const f of v.fields.values()) {
|
|
621
|
+
if (containsSelfRefInner(f, fullKey, false)) return true;
|
|
622
|
+
}
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
const hv = v;
|
|
626
|
+
if (hv.kind === "array") {
|
|
627
|
+
return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
|
|
628
|
+
}
|
|
629
|
+
if (hv.kind === "object") {
|
|
630
|
+
for (const f of hv.fields.values()) {
|
|
631
|
+
if (containsSelfRefInner(f, fullKey, false)) return true;
|
|
632
|
+
}
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
function foldSelfRef(v, fullKey, replacement) {
|
|
638
|
+
return foldSelfRefInner(v, fullKey, replacement, true);
|
|
639
|
+
}
|
|
640
|
+
function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
|
|
641
|
+
if (isSubst(v)) {
|
|
642
|
+
if (substFullKey(v) === fullKey) return replacement;
|
|
643
|
+
if (allowPrefix) {
|
|
644
|
+
const remainder = prefixSelfRefRemainder(v, fullKey);
|
|
645
|
+
if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
|
|
646
|
+
}
|
|
647
|
+
return v;
|
|
648
|
+
}
|
|
649
|
+
if (isConcat(v)) {
|
|
650
|
+
return {
|
|
651
|
+
_kind: "concat-placeholder",
|
|
652
|
+
nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
|
|
653
|
+
line: v.line,
|
|
654
|
+
col: v.col
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
if (isResObj(v)) {
|
|
658
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
659
|
+
for (const [k, val] of v.fields) {
|
|
660
|
+
newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
|
|
661
|
+
}
|
|
662
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
|
|
663
|
+
}
|
|
664
|
+
const hv = v;
|
|
665
|
+
if (hv.kind === "array") {
|
|
666
|
+
return {
|
|
667
|
+
kind: "array",
|
|
668
|
+
items: hv.items.map(
|
|
669
|
+
(item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
|
|
670
|
+
)
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
if (hv.kind === "object") {
|
|
674
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
675
|
+
for (const [k, val] of hv.fields) {
|
|
676
|
+
newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
|
|
677
|
+
}
|
|
678
|
+
return { kind: "object", fields: newFields };
|
|
679
|
+
}
|
|
680
|
+
return v;
|
|
681
|
+
}
|
|
682
|
+
function foldKnownAbsentSelfRef(v, fullKey, replacement) {
|
|
683
|
+
if (isSubst(v)) {
|
|
684
|
+
return v.knownAbsent && substFullKey(v) === fullKey ? replacement : v;
|
|
685
|
+
}
|
|
686
|
+
if (isConcat(v)) {
|
|
687
|
+
return {
|
|
688
|
+
_kind: "concat-placeholder",
|
|
689
|
+
nodes: v.nodes.map((n) => foldKnownAbsentSelfRef(n, fullKey, replacement)),
|
|
690
|
+
line: v.line,
|
|
691
|
+
col: v.col
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
if (isResObj(v)) {
|
|
695
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
696
|
+
for (const [k, val] of v.fields) {
|
|
697
|
+
newFields.set(k, foldKnownAbsentSelfRef(val, fullKey, replacement));
|
|
698
|
+
}
|
|
699
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
|
|
700
|
+
}
|
|
701
|
+
const hv = v;
|
|
702
|
+
if (hv.kind === "array") {
|
|
703
|
+
return {
|
|
704
|
+
kind: "array",
|
|
705
|
+
items: hv.items.map((item) => foldKnownAbsentSelfRef(item, fullKey, replacement))
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
if (hv.kind === "object") {
|
|
709
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
710
|
+
for (const [k, val] of hv.fields) {
|
|
711
|
+
newFields.set(k, foldKnownAbsentSelfRef(val, fullKey, replacement));
|
|
712
|
+
}
|
|
713
|
+
return { kind: "object", fields: newFields };
|
|
714
|
+
}
|
|
715
|
+
return v;
|
|
716
|
+
}
|
|
717
|
+
function foldOrSkipPrior(prior, fullKey, old) {
|
|
718
|
+
if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
|
|
719
|
+
if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
|
|
720
|
+
if (isSubst(prior)) {
|
|
721
|
+
const remainder = prefixSelfRefRemainder(prior, fullKey);
|
|
722
|
+
if (remainder !== null) {
|
|
723
|
+
const navigated = navigateResolverValue(old, remainder);
|
|
724
|
+
if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
|
|
725
|
+
if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
|
|
726
|
+
return mergeResObjLayers(
|
|
727
|
+
cloneResolverValue(old),
|
|
728
|
+
cloneResolverValue(navigated)
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return foldSelfRef(prior, fullKey, old);
|
|
734
|
+
}
|
|
735
|
+
function foldOptionalSelfRefAbsent(v, fullKey) {
|
|
736
|
+
return foldOptionalSelfRefAbsentInner(v, fullKey, true);
|
|
737
|
+
}
|
|
738
|
+
function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
|
|
739
|
+
if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
|
|
740
|
+
if (!v.optional) return void 0;
|
|
741
|
+
return {
|
|
742
|
+
_kind: "subst-placeholder",
|
|
743
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
744
|
+
optional: v.optional,
|
|
745
|
+
knownAbsent: true,
|
|
746
|
+
listSuffix: v.listSuffix,
|
|
747
|
+
line: v.line,
|
|
748
|
+
col: v.col,
|
|
749
|
+
prefixLen: v.prefixLen
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
if (isConcat(v)) {
|
|
753
|
+
const nodes = [];
|
|
754
|
+
for (const node of v.nodes) {
|
|
755
|
+
const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
|
|
756
|
+
if (folded === void 0) return void 0;
|
|
757
|
+
nodes.push(folded);
|
|
758
|
+
}
|
|
759
|
+
return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
|
|
760
|
+
}
|
|
761
|
+
if (isResObj(v)) {
|
|
762
|
+
const fields = /* @__PURE__ */ new Map();
|
|
763
|
+
for (const [key, value] of v.fields) {
|
|
764
|
+
const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
|
|
765
|
+
if (folded === void 0) return void 0;
|
|
766
|
+
fields.set(key, folded);
|
|
767
|
+
}
|
|
768
|
+
return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
|
|
769
|
+
}
|
|
770
|
+
const hv = v;
|
|
771
|
+
if (hv.kind === "array") {
|
|
772
|
+
const items = [];
|
|
773
|
+
for (const item of hv.items) {
|
|
774
|
+
const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
|
|
775
|
+
if (folded === void 0) return void 0;
|
|
776
|
+
items.push(folded);
|
|
777
|
+
}
|
|
778
|
+
return { kind: "array", items };
|
|
779
|
+
}
|
|
780
|
+
if (hv.kind === "object") {
|
|
781
|
+
const fields = /* @__PURE__ */ new Map();
|
|
782
|
+
for (const [key, value] of hv.fields) {
|
|
783
|
+
const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
|
|
784
|
+
if (folded === void 0) return void 0;
|
|
785
|
+
fields.set(key, folded);
|
|
786
|
+
}
|
|
787
|
+
return { kind: "object", fields };
|
|
788
|
+
}
|
|
789
|
+
return cloneResolverValue(v);
|
|
790
|
+
}
|
|
791
|
+
function cloneResolverValue(v) {
|
|
792
|
+
if (isSubst(v)) {
|
|
793
|
+
return {
|
|
794
|
+
_kind: "subst-placeholder",
|
|
795
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
796
|
+
optional: v.optional,
|
|
797
|
+
knownAbsent: v.knownAbsent,
|
|
798
|
+
listSuffix: v.listSuffix,
|
|
799
|
+
line: v.line,
|
|
800
|
+
col: v.col,
|
|
801
|
+
prefixLen: v.prefixLen
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
if (isConcat(v)) {
|
|
805
|
+
return {
|
|
806
|
+
_kind: "concat-placeholder",
|
|
807
|
+
nodes: v.nodes.map((n) => cloneResolverValue(n)),
|
|
808
|
+
line: v.line,
|
|
809
|
+
col: v.col
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
if (isResObj(v)) {
|
|
813
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
814
|
+
for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
|
|
815
|
+
const newPriors = /* @__PURE__ */ new Map();
|
|
816
|
+
for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
|
|
817
|
+
return { _kind: "res-obj", fields: newFields, priorValues: newPriors, resetKeys: new Set(v.resetKeys) };
|
|
818
|
+
}
|
|
819
|
+
const hv = v;
|
|
820
|
+
if (hv.kind === "array") {
|
|
821
|
+
return {
|
|
822
|
+
kind: "array",
|
|
823
|
+
items: hv.items.map((item) => cloneResolverValue(item))
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
if (hv.kind === "object") {
|
|
827
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
828
|
+
for (const [k, val] of hv.fields) {
|
|
829
|
+
newFields.set(k, cloneResolverValue(val));
|
|
830
|
+
}
|
|
831
|
+
return { kind: "object", fields: newFields };
|
|
832
|
+
}
|
|
833
|
+
return hv;
|
|
834
|
+
}
|
|
835
|
+
function foldNestedSelfRefs(v, pathPrefix) {
|
|
836
|
+
if (!isResObj(v)) return v;
|
|
837
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
838
|
+
for (const [k, fieldVal] of v.fields) {
|
|
839
|
+
const childPath = [...pathPrefix, k];
|
|
840
|
+
const fullKey = stringSegmentsToKey(childPath);
|
|
841
|
+
const folded = foldNestedSelfRefs(fieldVal, childPath);
|
|
842
|
+
let finalVal = folded;
|
|
843
|
+
if (containsSelfRef(folded, fullKey)) {
|
|
844
|
+
const leafPrior = v.priorValues.get(k);
|
|
845
|
+
if (leafPrior !== void 0) {
|
|
846
|
+
const leafPriorFolded = foldNestedSelfRefs(leafPrior, childPath);
|
|
847
|
+
finalVal = foldSelfRef(folded, fullKey, leafPriorFolded);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
newFields.set(k, finalVal);
|
|
851
|
+
}
|
|
852
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
|
|
853
|
+
}
|
|
854
|
+
function containsSubstByPath(v, target) {
|
|
855
|
+
if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
|
|
856
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
|
|
857
|
+
if (isResObj(v)) {
|
|
858
|
+
for (const f of v.fields.values()) {
|
|
859
|
+
if (containsSubstByPath(f, target)) return true;
|
|
860
|
+
}
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
const hv = v;
|
|
864
|
+
if (hv.kind === "array") {
|
|
865
|
+
return hv.items.some((item) => containsSubstByPath(item, target));
|
|
866
|
+
}
|
|
867
|
+
if (hv.kind === "object") {
|
|
868
|
+
for (const f of hv.fields.values()) {
|
|
869
|
+
if (containsSubstByPath(f, target)) return true;
|
|
870
|
+
}
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
875
|
+
function segmentsTextEqual(a, b) {
|
|
876
|
+
if (a.length !== b.length) return false;
|
|
877
|
+
for (let i = 0; i < a.length; i++) {
|
|
878
|
+
const ai = a[i];
|
|
879
|
+
const bi = b[i];
|
|
880
|
+
if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
|
|
881
|
+
}
|
|
882
|
+
return true;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/internal/resolver/types.ts
|
|
886
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
887
|
+
function isSubst(v) {
|
|
888
|
+
return v._kind === "subst-placeholder";
|
|
889
|
+
}
|
|
890
|
+
function isConcat(v) {
|
|
891
|
+
return v._kind === "concat-placeholder";
|
|
892
|
+
}
|
|
893
|
+
function isResObj(v) {
|
|
894
|
+
return v._kind === "res-obj";
|
|
895
|
+
}
|
|
896
|
+
function makeResObj() {
|
|
897
|
+
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map(), resetKeys: /* @__PURE__ */ new Set() };
|
|
898
|
+
}
|
|
899
|
+
function mergeUnresolved(receiver, fallback) {
|
|
900
|
+
const result = makeResObj();
|
|
901
|
+
for (const [k, v] of fallback.fields) {
|
|
902
|
+
result.fields.set(k, v);
|
|
903
|
+
}
|
|
904
|
+
for (const [k, v] of fallback.priorValues) {
|
|
905
|
+
result.priorValues.set(k, v);
|
|
906
|
+
}
|
|
907
|
+
for (const [k, rv] of receiver.fields) {
|
|
908
|
+
const existing = result.fields.get(k);
|
|
909
|
+
if (existing !== void 0) {
|
|
910
|
+
const recPrior = receiver.priorValues.get(k);
|
|
911
|
+
if (recPrior !== void 0 && !isResObj(recPrior)) {
|
|
912
|
+
result.fields.set(k, rv);
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (isResObj(rv) && isResObj(existing)) {
|
|
916
|
+
result.fields.set(k, mergeUnresolved(rv, existing));
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
const foldedPrior = foldOrSkipPrior(existing, k, void 0);
|
|
920
|
+
if (foldedPrior !== void 0) result.priorValues.set(k, foldedPrior);
|
|
921
|
+
}
|
|
922
|
+
result.fields.set(k, rv);
|
|
923
|
+
}
|
|
924
|
+
for (const [k, v] of receiver.priorValues) {
|
|
925
|
+
if (receiver.fields.has(k)) {
|
|
926
|
+
result.priorValues.set(k, v);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
return result;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/internal/resolver/utils.ts
|
|
933
|
+
function segmentsToKey(segments) {
|
|
934
|
+
return segments.map((s) => {
|
|
935
|
+
const t = s.text;
|
|
936
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
937
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
938
|
+
}
|
|
939
|
+
return t;
|
|
940
|
+
}).join(".");
|
|
941
|
+
}
|
|
942
|
+
function lookupPath(root, segments) {
|
|
943
|
+
const [head, ...tail] = segments;
|
|
944
|
+
if (head === void 0) return void 0;
|
|
945
|
+
const key = head.text;
|
|
946
|
+
const val = root.fields.get(key);
|
|
947
|
+
if (val === void 0) return void 0;
|
|
948
|
+
if (tail.length === 0) return val;
|
|
949
|
+
if (isResObj(val)) return lookupPath(val, tail);
|
|
950
|
+
return void 0;
|
|
951
|
+
}
|
|
952
|
+
function lookupResObj(root, segments) {
|
|
953
|
+
let cur = root;
|
|
954
|
+
for (const seg of segments) {
|
|
955
|
+
const val = cur.fields.get(seg.text);
|
|
956
|
+
if (val === void 0 || !isResObj(val)) return void 0;
|
|
957
|
+
cur = val;
|
|
958
|
+
}
|
|
959
|
+
return cur;
|
|
960
|
+
}
|
|
961
|
+
function deepMergeHoconValues(base, overlay) {
|
|
962
|
+
const merged = new Map(base.fields);
|
|
963
|
+
for (const [k, v] of overlay.fields) {
|
|
964
|
+
const existing = merged.get(k);
|
|
965
|
+
if (_optionalChain([existing, 'optionalAccess', _ => _.kind]) === "object" && v.kind === "object") {
|
|
966
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
967
|
+
} else {
|
|
968
|
+
merged.set(k, v);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return { kind: "object", fields: merged };
|
|
972
|
+
}
|
|
973
|
+
function deepMergeResObjInto(dst, src, pathPrefix = []) {
|
|
974
|
+
for (const [k, srcVal] of src.fields) {
|
|
975
|
+
const childPrefix = [...pathPrefix, k];
|
|
976
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
977
|
+
const dstVal = dst.fields.get(k);
|
|
978
|
+
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
979
|
+
const priorExisting = dst.priorValues.get(k);
|
|
980
|
+
const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
|
|
981
|
+
if (prior !== void 0) dst.priorValues.set(k, prior);
|
|
982
|
+
deepMergeResObjInto(dstVal, srcVal, childPrefix);
|
|
983
|
+
} else {
|
|
984
|
+
if (dstVal !== void 0) {
|
|
985
|
+
if (src.resetKeys.has(k)) {
|
|
986
|
+
dst.priorValues.delete(k);
|
|
987
|
+
} else {
|
|
988
|
+
const dstFolded = foldOrSkipPrior(dstVal, fullKey, dst.priorValues.get(k));
|
|
989
|
+
if (dstFolded !== void 0) {
|
|
990
|
+
const srcPrior = src.priorValues.get(k);
|
|
991
|
+
if (srcPrior !== void 0) {
|
|
992
|
+
dst.priorValues.set(k, foldKnownAbsentSelfRef(srcPrior, fullKey, dstFolded));
|
|
993
|
+
} else {
|
|
994
|
+
dst.priorValues.set(k, dstFolded);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
dst.fields.set(k, srcVal);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
for (const [k, srcPrior] of src.priorValues) {
|
|
1003
|
+
if (!dst.priorValues.has(k)) {
|
|
1004
|
+
dst.priorValues.set(k, srcPrior);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
for (const k of src.resetKeys) dst.resetKeys.add(k);
|
|
1008
|
+
}
|
|
1009
|
+
function hoconValueToResObj(hv) {
|
|
1010
|
+
const obj = makeResObj();
|
|
1011
|
+
if (hv.kind !== "object") return obj;
|
|
1012
|
+
for (const [key, val] of hv.fields) {
|
|
1013
|
+
if (val.kind === "object") {
|
|
1014
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
1015
|
+
} else {
|
|
1016
|
+
obj.fields.set(key, val);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return obj;
|
|
1020
|
+
}
|
|
1021
|
+
function isFileNotFoundError(e) {
|
|
1022
|
+
if (!(e instanceof Error)) return false;
|
|
1023
|
+
const code = e.code;
|
|
1024
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
1025
|
+
const msg = e.message.toLowerCase();
|
|
1026
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/internal/resolver/include-loader.ts
|
|
1030
|
+
var _module = require('module'); var nodeModule = _interopRequireWildcard(_module);
|
|
1031
|
+
var _path = require('path'); var nodePath = _interopRequireWildcard(_path);
|
|
1032
|
+
var _url = require('url'); var nodeUrl = _interopRequireWildcard(_url);
|
|
1033
|
+
|
|
1034
|
+
// src/internal/lexer/lexer.ts
|
|
1035
|
+
function isHoconWhitespace(ch) {
|
|
1036
|
+
const cp = _nullishCoalesce(ch.codePointAt(0), () => ( -1));
|
|
1037
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
1038
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
1039
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
1040
|
+
if (cp === 5760) return true;
|
|
1041
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
1042
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
1043
|
+
if (cp === 12288) return true;
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
function isHoconNewline(ch) {
|
|
1047
|
+
return ch === "\n";
|
|
1048
|
+
}
|
|
1049
|
+
var SINGLE_CHAR_TOKENS = {
|
|
1050
|
+
"{": "lbrace",
|
|
1051
|
+
"}": "rbrace",
|
|
1052
|
+
"[": "lbracket",
|
|
1053
|
+
"]": "rbracket",
|
|
1054
|
+
",": "comma",
|
|
1055
|
+
":": "colon"
|
|
1056
|
+
};
|
|
1057
|
+
var Lexer = class {
|
|
1058
|
+
constructor(input) {
|
|
1059
|
+
this.input = input;
|
|
1060
|
+
this.pos = 0;
|
|
1061
|
+
this.line = 1;
|
|
1062
|
+
this.col = 1;
|
|
1063
|
+
this.hadSpace = false;
|
|
1064
|
+
// Accumulates literal whitespace chars consumed between tokens. Reset on every
|
|
1065
|
+
// token push. Used so the parser can preserve path-expression whitespace
|
|
1066
|
+
// (xx.hocon#42, E13) — e.g. tab vs space adjacent to dots in key paths.
|
|
1067
|
+
// Comment text is NOT accumulated; only the actual WS chars.
|
|
1068
|
+
this.whitespaceBuffer = "";
|
|
1069
|
+
this.tokens = [];
|
|
1070
|
+
if (input.charCodeAt(0) === 65279) this.input = input.slice(1);
|
|
1071
|
+
}
|
|
1072
|
+
tokenize() {
|
|
1073
|
+
while (this.pos < this.input.length) {
|
|
1074
|
+
const sl = this.line, sc = this.col;
|
|
1075
|
+
const ch = this.peek();
|
|
1076
|
+
if (isHoconNewline(ch)) {
|
|
1077
|
+
this.advance();
|
|
1078
|
+
if (_optionalChain([this, 'access', _2 => _2.tokens, 'access', _3 => _3[this.tokens.length - 1], 'optionalAccess', _4 => _4.kind]) !== "newline") {
|
|
1079
|
+
this.push("newline", "\n", sl, sc);
|
|
1080
|
+
}
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
if (isHoconWhitespace(ch)) {
|
|
1084
|
+
this.whitespaceBuffer += ch;
|
|
1085
|
+
this.advance();
|
|
1086
|
+
this.hadSpace = true;
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
if (ch === "/" && this.peek(1) === "/") {
|
|
1090
|
+
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
1091
|
+
this.hadSpace = true;
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (ch === "#") {
|
|
1095
|
+
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
1096
|
+
this.hadSpace = true;
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
if (ch in SINGLE_CHAR_TOKENS) {
|
|
1100
|
+
this.advance();
|
|
1101
|
+
this.push(SINGLE_CHAR_TOKENS[ch], ch, sl, sc);
|
|
1102
|
+
continue;
|
|
1103
|
+
}
|
|
1104
|
+
if (ch === "=") {
|
|
1105
|
+
this.advance();
|
|
1106
|
+
this.push("equals", "=", sl, sc);
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
if (ch === "+" && this.peek(1) === "=") {
|
|
1110
|
+
this.advance();
|
|
1111
|
+
this.advance();
|
|
1112
|
+
this.push("plus_equals", "+=", sl, sc);
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
if (ch === "$" && this.peek(1) === "{") {
|
|
1116
|
+
this.advance();
|
|
1117
|
+
this.advance();
|
|
1118
|
+
const payload = this.parseSubstBody(sl, sc);
|
|
1119
|
+
const value = payload.segments.map((s) => {
|
|
1120
|
+
const t = s.text;
|
|
1121
|
+
if (t === "" || t.includes(".") || t.includes(" ") || t.includes(" ") || t.includes('"') || t.includes("\\") || t !== t.trim()) {
|
|
1122
|
+
const escaped = t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1123
|
+
return `"${escaped}"`;
|
|
1124
|
+
}
|
|
1125
|
+
return t;
|
|
1126
|
+
}).join(".");
|
|
1127
|
+
this.pushSubst(payload, value, sl, sc);
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (ch === '"' && this.peek(1) === '"' && this.peek(2) === '"') {
|
|
1131
|
+
this.advance();
|
|
1132
|
+
this.advance();
|
|
1133
|
+
this.advance();
|
|
1134
|
+
let value = "";
|
|
1135
|
+
let closed = false;
|
|
1136
|
+
while (this.pos < this.input.length) {
|
|
1137
|
+
if (this.peek() === '"') {
|
|
1138
|
+
let quoteCount = 0;
|
|
1139
|
+
while (this.pos < this.input.length && this.peek() === '"') {
|
|
1140
|
+
quoteCount++;
|
|
1141
|
+
this.advance();
|
|
1142
|
+
}
|
|
1143
|
+
if (quoteCount >= 3) {
|
|
1144
|
+
for (let i = 0; i < quoteCount - 3; i++) value += '"';
|
|
1145
|
+
closed = true;
|
|
1146
|
+
break;
|
|
1147
|
+
}
|
|
1148
|
+
for (let i = 0; i < quoteCount; i++) value += '"';
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
value += this.advance();
|
|
1152
|
+
}
|
|
1153
|
+
if (!closed) {
|
|
1154
|
+
throw new ParseError("unterminated triple-quoted string", sl, sc);
|
|
1155
|
+
}
|
|
1156
|
+
this.push("triple_string", value, sl, sc, true);
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (ch === '"') {
|
|
1160
|
+
this.advance();
|
|
1161
|
+
const value = this.readQuotedStringBody(sl, sc);
|
|
1162
|
+
this.push("string", value, sl, sc, true);
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
if (isUnquotedStart(ch)) {
|
|
1166
|
+
const plusAllowedUntil = this.numberRunEnd(sl, sc);
|
|
1167
|
+
let value = "";
|
|
1168
|
+
while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
|
|
1169
|
+
if (this.peek() === "+" && this.pos >= plusAllowedUntil) throw reservedPlus(this.line, this.col);
|
|
1170
|
+
value += this.advance();
|
|
1171
|
+
}
|
|
1172
|
+
this.push("unquoted", value.trimEnd(), sl, sc);
|
|
1173
|
+
continue;
|
|
1174
|
+
}
|
|
1175
|
+
throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
|
|
1176
|
+
}
|
|
1177
|
+
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false, precedingWhitespace: "" });
|
|
1178
|
+
return this.tokens;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* S8.1 + E8 step 4: outside quotes, `+` is reserved except as the exponent
|
|
1182
|
+
* sign of a numeric literal (the Lightbend 1.4.6 behaviour). Where a token
|
|
1183
|
+
* starts like a number (a digit or `-`: Lightbend's `firstNumberChars`),
|
|
1184
|
+
* `pullNumber` takes the maximal `[0-9.eE+-]` run; when that run is a number
|
|
1185
|
+
* (`1e+5`, `-.5e+3`), a `+` inside it is the exponent sign. A run that
|
|
1186
|
+
* contains `+` and is not a number is an error right here — this also covers
|
|
1187
|
+
* `1+= 2`, where the run swallows the `+` of `+=` in Lightbend. Returns the
|
|
1188
|
+
* input index up to which a `+` is allowed: the end of a number run, else
|
|
1189
|
+
* the token start, so the caller rejects every other `+` (`abc+1`, `1x+2`).
|
|
1190
|
+
*/
|
|
1191
|
+
numberRunEnd(line, col) {
|
|
1192
|
+
const first = this.peek();
|
|
1193
|
+
if (!isDecimalDigit(first) && first !== "-") return this.pos;
|
|
1194
|
+
let end = this.pos;
|
|
1195
|
+
while (end < this.input.length && NUMBER_RUN_CHARS.includes(this.input[end])) end++;
|
|
1196
|
+
const run = this.input.slice(this.pos, end);
|
|
1197
|
+
if (JAVA_NUMBER_RE.test(run)) return end;
|
|
1198
|
+
if (run.includes("+")) throw reservedPlus(line, col);
|
|
1199
|
+
return this.pos;
|
|
1200
|
+
}
|
|
1201
|
+
peek(offset = 0) {
|
|
1202
|
+
return _nullishCoalesce(this.input[this.pos + offset], () => ( ""));
|
|
1203
|
+
}
|
|
1204
|
+
advance() {
|
|
1205
|
+
const ch = _nullishCoalesce(this.input[this.pos++], () => ( ""));
|
|
1206
|
+
if (ch === "\n") {
|
|
1207
|
+
this.line++;
|
|
1208
|
+
this.col = 1;
|
|
1209
|
+
} else {
|
|
1210
|
+
this.col++;
|
|
1211
|
+
}
|
|
1212
|
+
return ch;
|
|
1213
|
+
}
|
|
1214
|
+
push(kind, value, l, c, isQuoted = false) {
|
|
1215
|
+
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer });
|
|
1216
|
+
this.hadSpace = false;
|
|
1217
|
+
this.whitespaceBuffer = "";
|
|
1218
|
+
}
|
|
1219
|
+
pushSubst(payload, value, l, c) {
|
|
1220
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, precedingWhitespace: this.whitespaceBuffer, subst: payload });
|
|
1221
|
+
this.hadSpace = false;
|
|
1222
|
+
this.whitespaceBuffer = "";
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
1226
|
+
* Returns decoded string. Throws ParseError on unterminated/invalid-escape.
|
|
1227
|
+
* openLine/openCol are the position of the opening `"` for error reporting.
|
|
1228
|
+
*/
|
|
1229
|
+
readQuotedStringBody(openLine, openCol) {
|
|
1230
|
+
let value = "";
|
|
1231
|
+
while (this.pos < this.input.length && this.peek() !== '"') {
|
|
1232
|
+
if (this.peek() === "\n") throw new ParseError("unterminated string", openLine, openCol);
|
|
1233
|
+
if (this.peek() === "\\") {
|
|
1234
|
+
const escCol = this.col;
|
|
1235
|
+
this.advance();
|
|
1236
|
+
if (this.pos >= this.input.length) {
|
|
1237
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
1238
|
+
}
|
|
1239
|
+
const esc = this.advance();
|
|
1240
|
+
switch (esc) {
|
|
1241
|
+
case "n":
|
|
1242
|
+
value += "\n";
|
|
1243
|
+
break;
|
|
1244
|
+
case "t":
|
|
1245
|
+
value += " ";
|
|
1246
|
+
break;
|
|
1247
|
+
case "r":
|
|
1248
|
+
value += "\r";
|
|
1249
|
+
break;
|
|
1250
|
+
case '"':
|
|
1251
|
+
value += '"';
|
|
1252
|
+
break;
|
|
1253
|
+
case "\\":
|
|
1254
|
+
value += "\\";
|
|
1255
|
+
break;
|
|
1256
|
+
case "/":
|
|
1257
|
+
value += "/";
|
|
1258
|
+
break;
|
|
1259
|
+
case "b":
|
|
1260
|
+
value += "\b";
|
|
1261
|
+
break;
|
|
1262
|
+
case "f":
|
|
1263
|
+
value += "\f";
|
|
1264
|
+
break;
|
|
1265
|
+
case "u": {
|
|
1266
|
+
if (this.pos + 4 > this.input.length) {
|
|
1267
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
1268
|
+
}
|
|
1269
|
+
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
1270
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
1271
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
1272
|
+
}
|
|
1273
|
+
const code = parseInt(hex, 16);
|
|
1274
|
+
value += String.fromCharCode(code);
|
|
1275
|
+
for (let i = 0; i < 4; i++) this.advance();
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1278
|
+
default:
|
|
1279
|
+
throw new ParseError(`unknown escape sequence: \\${esc}`, openLine, escCol);
|
|
1280
|
+
}
|
|
1281
|
+
} else {
|
|
1282
|
+
value += this.advance();
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
if (this.pos >= this.input.length || this.peek() !== '"') {
|
|
1286
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
1287
|
+
}
|
|
1288
|
+
this.advance();
|
|
1289
|
+
return value;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Parse the body of a `${...}` substitution (called after `${` has been consumed).
|
|
1293
|
+
* Implements Appendix A state machine from the cross-language spec.
|
|
1294
|
+
*
|
|
1295
|
+
* S13c extension: if the segment-collection loop encounters `[`, it delegates to
|
|
1296
|
+
* `parseListSuffix()` which consumes the literal `[]` and sets `listSuffix=true`.
|
|
1297
|
+
* ASCII space/tab before `[` is buffered in `pendingWs` and discarded (E7).
|
|
1298
|
+
*/
|
|
1299
|
+
parseSubstBody(startLine, startCol) {
|
|
1300
|
+
let optional = false;
|
|
1301
|
+
if (this.peek() === "?") {
|
|
1302
|
+
this.advance();
|
|
1303
|
+
optional = true;
|
|
1304
|
+
}
|
|
1305
|
+
let curText = "";
|
|
1306
|
+
let curStarted = false;
|
|
1307
|
+
let curLine = 0;
|
|
1308
|
+
let curCol = 0;
|
|
1309
|
+
let pendingWs = "";
|
|
1310
|
+
const segments = [];
|
|
1311
|
+
let lastDot = null;
|
|
1312
|
+
let listSuffix = false;
|
|
1313
|
+
while (true) {
|
|
1314
|
+
if (this.pos >= this.input.length) {
|
|
1315
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
1316
|
+
}
|
|
1317
|
+
const ch = this.peek();
|
|
1318
|
+
if (ch === "}") {
|
|
1319
|
+
this.advance();
|
|
1320
|
+
pendingWs = "";
|
|
1321
|
+
break;
|
|
1322
|
+
} else if (ch === "[") {
|
|
1323
|
+
if (!curStarted) {
|
|
1324
|
+
throw new ParseError("empty segment before '[]' suffix in substitution path", startLine, this.col);
|
|
1325
|
+
}
|
|
1326
|
+
for (const w of pendingWs) {
|
|
1327
|
+
if (w !== " " && w !== " ") {
|
|
1328
|
+
throw new ParseError(
|
|
1329
|
+
`only ASCII space or tab allowed between substitution path and '[]' suffix (got ${JSON.stringify(w)}, HOCON extra-spec E7)`,
|
|
1330
|
+
startLine,
|
|
1331
|
+
this.col
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
1336
|
+
pendingWs = "";
|
|
1337
|
+
listSuffix = this.parseListSuffix(startLine);
|
|
1338
|
+
break;
|
|
1339
|
+
} else if (ch === '"') {
|
|
1340
|
+
const qLine = startLine;
|
|
1341
|
+
const qCol = this.col;
|
|
1342
|
+
if (curStarted) {
|
|
1343
|
+
curText += pendingWs;
|
|
1344
|
+
}
|
|
1345
|
+
pendingWs = "";
|
|
1346
|
+
this.advance();
|
|
1347
|
+
const decoded = this.readQuotedStringBody(qLine, qCol);
|
|
1348
|
+
curText += decoded;
|
|
1349
|
+
if (!curStarted) {
|
|
1350
|
+
curLine = qLine;
|
|
1351
|
+
curCol = qCol;
|
|
1352
|
+
curStarted = true;
|
|
1353
|
+
}
|
|
1354
|
+
} else if (isUnquotedSubstChar(ch)) {
|
|
1355
|
+
if (ch === "-" && !curStarted && !isDecimalDigit(this.peek(1))) {
|
|
1356
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
1357
|
+
throw new ParseError(
|
|
1358
|
+
`unquoted path segment cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
1359
|
+
startLine,
|
|
1360
|
+
this.col
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1363
|
+
const uCol = this.col;
|
|
1364
|
+
if (curStarted) {
|
|
1365
|
+
curText += pendingWs;
|
|
1366
|
+
}
|
|
1367
|
+
pendingWs = "";
|
|
1368
|
+
if (!curStarted) {
|
|
1369
|
+
curLine = startLine;
|
|
1370
|
+
curCol = uCol;
|
|
1371
|
+
curStarted = true;
|
|
1372
|
+
}
|
|
1373
|
+
while (this.pos < this.input.length && isUnquotedSubstChar(this.peek())) {
|
|
1374
|
+
curText += this.advance();
|
|
1375
|
+
}
|
|
1376
|
+
} else if (ch === ".") {
|
|
1377
|
+
const dotCol = this.col;
|
|
1378
|
+
pendingWs = "";
|
|
1379
|
+
if (!curStarted) {
|
|
1380
|
+
throw new ParseError("empty segment in path", startLine, dotCol);
|
|
1381
|
+
}
|
|
1382
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
1383
|
+
curText = "";
|
|
1384
|
+
curStarted = false;
|
|
1385
|
+
curLine = 0;
|
|
1386
|
+
curCol = 0;
|
|
1387
|
+
lastDot = [startLine, dotCol];
|
|
1388
|
+
this.advance();
|
|
1389
|
+
} else if (isHoconNewline(ch)) {
|
|
1390
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
1391
|
+
} else if (isHoconWhitespace(ch)) {
|
|
1392
|
+
pendingWs += ch;
|
|
1393
|
+
this.advance();
|
|
1394
|
+
} else {
|
|
1395
|
+
throw new ParseError(`unexpected character in substitution path: ${JSON.stringify(ch)}`, startLine, this.col);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (!listSuffix) {
|
|
1399
|
+
if (curStarted) {
|
|
1400
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
1401
|
+
} else if (segments.length === 0) {
|
|
1402
|
+
throw new ParseError("empty substitution path", startLine, startCol);
|
|
1403
|
+
} else {
|
|
1404
|
+
const [errLine, errCol] = _nullishCoalesce(lastDot, () => ( [startLine, startCol]));
|
|
1405
|
+
throw new ParseError("empty segment in path", errLine, errCol);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return { segments, optional, listSuffix };
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Consume the literal `[]` suffix inside a substitution body.
|
|
1412
|
+
* Called after the `[` has been detected (but not yet consumed).
|
|
1413
|
+
* Requires the next character to be `[`, then exactly `]`, then `}`.
|
|
1414
|
+
* Whitespace inside `[]` is not permitted (per spec Decision §1).
|
|
1415
|
+
* Returns true (always — only called when listSuffix path is taken).
|
|
1416
|
+
*/
|
|
1417
|
+
parseListSuffix(startLine) {
|
|
1418
|
+
this.advance();
|
|
1419
|
+
const afterBracket = this.peek();
|
|
1420
|
+
if (afterBracket !== "]") {
|
|
1421
|
+
const desc = afterBracket === "" ? "EOF" : JSON.stringify(afterBracket);
|
|
1422
|
+
throw new ParseError(
|
|
1423
|
+
`expected ']' after '[' in substitution list suffix (got ${desc})`,
|
|
1424
|
+
startLine,
|
|
1425
|
+
this.col
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
this.advance();
|
|
1429
|
+
if (this.peek() !== "}") {
|
|
1430
|
+
const desc = this.peek() === "" ? "EOF" : JSON.stringify(this.peek());
|
|
1431
|
+
throw new ParseError(
|
|
1432
|
+
`expected '}' after '[]' in substitution list suffix (got ${desc})`,
|
|
1433
|
+
startLine,
|
|
1434
|
+
this.col
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
this.advance();
|
|
1438
|
+
return true;
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
function tokenize(input) {
|
|
1442
|
+
return new Lexer(input).tokenize();
|
|
1443
|
+
}
|
|
1444
|
+
function isUnquotedSubstChar(ch) {
|
|
1445
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
1446
|
+
if ('"\\'.includes(ch)) return false;
|
|
1447
|
+
if ("{}[]".includes(ch)) return false;
|
|
1448
|
+
if (":=,+#`^?!@*&$.".includes(ch)) return false;
|
|
1449
|
+
return true;
|
|
1450
|
+
}
|
|
1451
|
+
function isDecimalDigit(ch) {
|
|
1452
|
+
return ch >= "0" && ch <= "9";
|
|
1453
|
+
}
|
|
1454
|
+
var NUMBER_RUN_CHARS = "0123456789.eE+-";
|
|
1455
|
+
var JAVA_NUMBER_RE = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
1456
|
+
function reservedPlus(line, col) {
|
|
1457
|
+
return new ParseError(`reserved character '+' is not allowed outside quotes (quote the value)`, line, col);
|
|
1458
|
+
}
|
|
1459
|
+
function isUnquotedStart(ch) {
|
|
1460
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
1461
|
+
if ('{}[],:=+#"$?!@*&^`\\'.includes(ch)) return false;
|
|
1462
|
+
return true;
|
|
1463
|
+
}
|
|
1464
|
+
function isUnquotedContinue(ch, nextFn) {
|
|
1465
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
1466
|
+
if ('{}[],:=#"$?!@*&^`\\'.includes(ch)) return false;
|
|
1467
|
+
if (ch === "+" && nextFn() === "=") return false;
|
|
1468
|
+
if (ch === "/" && nextFn() === "/") return false;
|
|
1469
|
+
return true;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// src/internal/parser/parser.ts
|
|
1473
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, precedingWhitespace: "", subst: void 0 };
|
|
1474
|
+
var Parser = class {
|
|
1475
|
+
constructor(tokens) {
|
|
1476
|
+
this.tokens = tokens;
|
|
1477
|
+
this.pos = 0;
|
|
1478
|
+
}
|
|
1479
|
+
parse() {
|
|
1480
|
+
this.skip("newline");
|
|
1481
|
+
const t = this.peek();
|
|
1482
|
+
if (t.kind === "lbrace") {
|
|
1483
|
+
this.advance();
|
|
1484
|
+
const first = this.parseObject(true);
|
|
1485
|
+
const allFields = first.kind === "object" ? [...first.fields] : [];
|
|
1486
|
+
while (true) {
|
|
1487
|
+
this.skip("newline");
|
|
1488
|
+
if (this.peek().kind === "eof") break;
|
|
1489
|
+
if (this.peek().kind === "lbrace") {
|
|
1490
|
+
this.advance();
|
|
1491
|
+
const extra = this.parseObject(true);
|
|
1492
|
+
if (extra.kind === "object") allFields.push(...extra.fields);
|
|
1493
|
+
} else {
|
|
1494
|
+
const rest = this.parseObject(false);
|
|
1495
|
+
if (rest.kind === "object") allFields.push(...rest.fields);
|
|
1496
|
+
break;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
this.skip("newline");
|
|
1500
|
+
const remaining2 = this.peek();
|
|
1501
|
+
if (remaining2.kind !== "eof") {
|
|
1502
|
+
throw new ParseError(`Unexpected token '${remaining2.value}' after closing brace`, remaining2.line, remaining2.col);
|
|
1503
|
+
}
|
|
1504
|
+
return { kind: "object", fields: allFields, pos: first.pos };
|
|
1505
|
+
}
|
|
1506
|
+
if (t.kind === "lbracket") {
|
|
1507
|
+
this.advance();
|
|
1508
|
+
const arr = this.parseArray();
|
|
1509
|
+
this.skip("newline");
|
|
1510
|
+
const remaining2 = this.peek();
|
|
1511
|
+
if (remaining2.kind !== "eof") {
|
|
1512
|
+
throw new ParseError(`Unexpected token '${remaining2.value}' after root array`, remaining2.line, remaining2.col);
|
|
1513
|
+
}
|
|
1514
|
+
return { ...arr, pos: { line: t.line, col: t.col } };
|
|
1515
|
+
}
|
|
1516
|
+
const root = this.parseObject(false);
|
|
1517
|
+
this.skip("newline");
|
|
1518
|
+
const remaining = this.peek();
|
|
1519
|
+
if (remaining.kind !== "eof") {
|
|
1520
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after root content`, remaining.line, remaining.col);
|
|
1521
|
+
}
|
|
1522
|
+
return root;
|
|
1523
|
+
}
|
|
1524
|
+
peek(offset = 0) {
|
|
1525
|
+
return _nullishCoalesce(this.tokens[this.pos + offset], () => ( EOF_TOKEN));
|
|
1526
|
+
}
|
|
1527
|
+
advance() {
|
|
1528
|
+
const t = this.tokens[this.pos];
|
|
1529
|
+
if (this.pos < this.tokens.length) this.pos++;
|
|
1530
|
+
return _nullishCoalesce(t, () => ( EOF_TOKEN));
|
|
1531
|
+
}
|
|
1532
|
+
skip(...kinds) {
|
|
1533
|
+
while (kinds.includes(this.peek().kind)) this.advance();
|
|
1534
|
+
}
|
|
1535
|
+
currentPos() {
|
|
1536
|
+
const t = this.peek();
|
|
1537
|
+
return { line: t.line, col: t.col };
|
|
1538
|
+
}
|
|
1539
|
+
parseObject(expectClosingBrace) {
|
|
1540
|
+
const p = this.currentPos();
|
|
1541
|
+
const fields = [];
|
|
1542
|
+
while (true) {
|
|
1543
|
+
this.skip("newline");
|
|
1544
|
+
const t = this.peek();
|
|
1545
|
+
if (t.kind === "eof" || t.kind === "rbrace") break;
|
|
1546
|
+
if (t.kind === "unquoted" && t.value === "include") {
|
|
1547
|
+
const next = this.peek(1);
|
|
1548
|
+
if (next.kind === "equals" || next.kind === "colon" || next.kind === "plus_equals" || next.kind === "lbrace") {
|
|
1549
|
+
throw new ParseError(
|
|
1550
|
+
`'include' is reserved at the start of a key path expression; use "include" (quoted) to use it as a key`,
|
|
1551
|
+
t.line,
|
|
1552
|
+
t.col
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
this.advance();
|
|
1556
|
+
fields.push(this.parseInclude());
|
|
1557
|
+
this.skip("newline");
|
|
1558
|
+
if (this.peek().kind === "comma") this.advance();
|
|
1559
|
+
this.skip("newline");
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
const keyPos = this.currentPos();
|
|
1563
|
+
const { segments: key, firstWasQuoted } = this.parseKey();
|
|
1564
|
+
if (key[0] === "include" && !firstWasQuoted) {
|
|
1565
|
+
throw new ParseError(
|
|
1566
|
+
`'include' is reserved at the start of a key path expression; use "include".foo (quoted) or rename the key`,
|
|
1567
|
+
keyPos.line,
|
|
1568
|
+
keyPos.col
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
this.skip("newline");
|
|
1572
|
+
let append = false;
|
|
1573
|
+
const sep = this.peek();
|
|
1574
|
+
if (sep.kind === "equals") this.advance();
|
|
1575
|
+
else if (sep.kind === "plus_equals") {
|
|
1576
|
+
this.advance();
|
|
1577
|
+
append = true;
|
|
1578
|
+
} else if (sep.kind === "colon") this.advance();
|
|
1579
|
+
else if (sep.kind === "lbrace") {
|
|
1580
|
+
} else if (sep.kind !== "newline" && sep.kind !== "eof") {
|
|
1581
|
+
throw new ParseError(`unexpected token after key: ${sep.kind}`, sep.line, sep.col);
|
|
1582
|
+
}
|
|
1583
|
+
this.skip("newline");
|
|
1584
|
+
const value = this.parseValue();
|
|
1585
|
+
fields.push({ key, value, append, pos: keyPos });
|
|
1586
|
+
this.skip("newline");
|
|
1587
|
+
if (this.peek().kind === "comma") this.advance();
|
|
1588
|
+
this.skip("newline");
|
|
1589
|
+
}
|
|
1590
|
+
if (expectClosingBrace) {
|
|
1591
|
+
const t = this.peek();
|
|
1592
|
+
if (t.kind !== "rbrace") throw new ParseError("expected }", t.line, t.col);
|
|
1593
|
+
this.advance();
|
|
1594
|
+
}
|
|
1595
|
+
return { kind: "object", fields, pos: p };
|
|
1596
|
+
}
|
|
1597
|
+
parseKey() {
|
|
1598
|
+
const segments = [];
|
|
1599
|
+
let trailingDot = false;
|
|
1600
|
+
let firstWasQuoted = false;
|
|
1601
|
+
let spaceConcat = false;
|
|
1602
|
+
let postDotPrefix = "";
|
|
1603
|
+
while (true) {
|
|
1604
|
+
const t = this.peek();
|
|
1605
|
+
if (t.kind === "string") {
|
|
1606
|
+
this.advance();
|
|
1607
|
+
if (segments.length === 0) firstWasQuoted = true;
|
|
1608
|
+
if (spaceConcat) {
|
|
1609
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}${t.value}`;
|
|
1610
|
+
} else if (postDotPrefix !== "") {
|
|
1611
|
+
segments.push(`${postDotPrefix}${t.value}`);
|
|
1612
|
+
postDotPrefix = "";
|
|
1613
|
+
} else {
|
|
1614
|
+
segments.push(t.value);
|
|
1615
|
+
}
|
|
1616
|
+
trailingDot = false;
|
|
1617
|
+
} else if (t.kind === "unquoted") {
|
|
1618
|
+
this.advance();
|
|
1619
|
+
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
1620
|
+
const parts = raw.split(".");
|
|
1621
|
+
const leadingDotIsSeparator = spaceConcat || postDotPrefix !== "";
|
|
1622
|
+
for (let i = 0; i < parts.length; i++) {
|
|
1623
|
+
if (parts[i] !== "") continue;
|
|
1624
|
+
if (i === parts.length - 1) continue;
|
|
1625
|
+
if (i === 0 && leadingDotIsSeparator) continue;
|
|
1626
|
+
throw new ParseError(
|
|
1627
|
+
`path has ${i === 0 ? "a leading period '.'" : "two adjacent periods '.'"} \u2014 empty key segment not allowed, use quoted "" (HOCON.md path rules)`,
|
|
1628
|
+
t.line,
|
|
1629
|
+
t.col
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
const filtered = parts.filter((s) => s.length > 0);
|
|
1633
|
+
if (spaceConcat) {
|
|
1634
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
|
|
1635
|
+
if (raw.startsWith(".")) {
|
|
1636
|
+
segments.push(...filtered);
|
|
1637
|
+
} else if (filtered.length > 0) {
|
|
1638
|
+
const [head, ...tail] = filtered;
|
|
1639
|
+
segments[segments.length - 1] = `${segments[segments.length - 1]}${head}`;
|
|
1640
|
+
segments.push(...tail);
|
|
1641
|
+
}
|
|
1642
|
+
} else if (postDotPrefix !== "" && raw.startsWith(".")) {
|
|
1643
|
+
segments.push(postDotPrefix);
|
|
1644
|
+
postDotPrefix = "";
|
|
1645
|
+
segments.push(...filtered);
|
|
1646
|
+
} else if (postDotPrefix !== "" && filtered.length > 0) {
|
|
1647
|
+
const [head, ...tail] = filtered;
|
|
1648
|
+
segments.push(`${postDotPrefix}${head}`, ...tail);
|
|
1649
|
+
postDotPrefix = "";
|
|
1650
|
+
} else {
|
|
1651
|
+
segments.push(...filtered);
|
|
1652
|
+
}
|
|
1653
|
+
trailingDot = raw.endsWith(".");
|
|
1654
|
+
} else {
|
|
1655
|
+
if (segments.length === 0) throw new ParseError(`expected key, got ${t.kind}`, t.line, t.col);
|
|
1656
|
+
break;
|
|
1657
|
+
}
|
|
1658
|
+
spaceConcat = false;
|
|
1659
|
+
if (trailingDot) {
|
|
1660
|
+
const next2 = this.peek();
|
|
1661
|
+
if ((next2.kind === "unquoted" || next2.kind === "string") && next2.precedingWhitespace.length > 0) {
|
|
1662
|
+
postDotPrefix = next2.precedingWhitespace;
|
|
1663
|
+
} else {
|
|
1664
|
+
postDotPrefix = "";
|
|
1665
|
+
}
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
const next = this.peek();
|
|
1669
|
+
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
1670
|
+
this.advance();
|
|
1671
|
+
trailingDot = true;
|
|
1672
|
+
const afterDot = this.peek();
|
|
1673
|
+
if ((afterDot.kind === "unquoted" || afterDot.kind === "string") && afterDot.precedingWhitespace.length > 0) {
|
|
1674
|
+
postDotPrefix = afterDot.precedingWhitespace;
|
|
1675
|
+
}
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
1679
|
+
trailingDot = true;
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
if ((next.kind === "unquoted" || next.kind === "string") && next.precedingSpace) {
|
|
1683
|
+
spaceConcat = true;
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
break;
|
|
1687
|
+
}
|
|
1688
|
+
if (trailingDot) {
|
|
1689
|
+
const here = this.peek();
|
|
1690
|
+
throw new ParseError(
|
|
1691
|
+
`path has a trailing period '.' \u2014 empty key segment not allowed (HOCON.md path rules)`,
|
|
1692
|
+
here.line,
|
|
1693
|
+
here.col
|
|
1694
|
+
);
|
|
1695
|
+
}
|
|
1696
|
+
return { segments, firstWasQuoted };
|
|
1697
|
+
}
|
|
1698
|
+
parseInclude() {
|
|
1699
|
+
const p = this.currentPos();
|
|
1700
|
+
this.skip("newline");
|
|
1701
|
+
const t = this.peek();
|
|
1702
|
+
if (t.kind === "unquoted" && (t.value === "required(" || t.value === "required" || t.value.startsWith("required("))) {
|
|
1703
|
+
if (t.value === "required") {
|
|
1704
|
+
const next = this.peek(1);
|
|
1705
|
+
if (next.kind !== "unquoted" || !next.value.startsWith("(")) {
|
|
1706
|
+
throw new ParseError("include required must be followed by (", t.line, t.col);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
this.advance();
|
|
1710
|
+
const innerPrefix = t.value.startsWith("required(") ? t.value.slice("required(".length) : "";
|
|
1711
|
+
if (innerPrefix === "url" || innerPrefix.startsWith("url(") || innerPrefix === "classpath" || innerPrefix.startsWith("classpath(")) {
|
|
1712
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", t.line, t.col);
|
|
1713
|
+
}
|
|
1714
|
+
const nextTok = this.peek();
|
|
1715
|
+
if (innerPrefix === "package" || innerPrefix.startsWith("package(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "package(" || nextTok.value.startsWith("package("))) {
|
|
1716
|
+
const consumePackageToken = innerPrefix === "";
|
|
1717
|
+
const { qualifier, path: path2 } = this.parsePackageArgs(consumePackageToken);
|
|
1718
|
+
return this.makeIncludeField(qualifier, path2, true, p);
|
|
1719
|
+
}
|
|
1720
|
+
if (innerPrefix === "file" || innerPrefix.startsWith("file(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "file(" || nextTok.value === "file")) {
|
|
1721
|
+
if (innerPrefix === "") {
|
|
1722
|
+
this.advance();
|
|
1723
|
+
}
|
|
1724
|
+
const path2 = this.parseQuotedPathSkipWrapper(t);
|
|
1725
|
+
return this.makeIncludeField({ kind: "file" }, path2, true, p);
|
|
1726
|
+
}
|
|
1727
|
+
if (innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "url" || nextTok.value.startsWith("url(") || nextTok.value === "classpath" || nextTok.value.startsWith("classpath("))) {
|
|
1728
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", nextTok.line, nextTok.col);
|
|
1729
|
+
}
|
|
1730
|
+
if (innerPrefix !== "") {
|
|
1731
|
+
if (innerPrefix === ")") {
|
|
1732
|
+
throw new ParseError("include required(...) must contain a path", t.line, t.col);
|
|
1733
|
+
}
|
|
1734
|
+
const name = innerPrefix.replace(/\(.*$/, "").replace(/\).*$/, "");
|
|
1735
|
+
throw new ParseError(`unknown include qualifier inside required(): "${name}"`, t.line, t.col);
|
|
1736
|
+
}
|
|
1737
|
+
const path = this.parseQuotedPathSkipWrapper(t);
|
|
1738
|
+
return this.makeIncludeField({ kind: "bare" }, path, true, p);
|
|
1739
|
+
}
|
|
1740
|
+
if (t.kind === "string") {
|
|
1741
|
+
const path = this.advance().value;
|
|
1742
|
+
return this.makeIncludeField({ kind: "bare" }, path, false, p);
|
|
1743
|
+
}
|
|
1744
|
+
if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
|
|
1745
|
+
this.advance();
|
|
1746
|
+
const path = this.parseQuotedPathSkipWrapper(t);
|
|
1747
|
+
return this.makeIncludeField({ kind: "file" }, path, false, p);
|
|
1748
|
+
}
|
|
1749
|
+
if (t.kind === "unquoted" && (t.value === "package(" || t.value.startsWith("package("))) {
|
|
1750
|
+
const { qualifier, path } = this.parsePackageArgs();
|
|
1751
|
+
return this.makeIncludeField(qualifier, path, false, p);
|
|
1752
|
+
}
|
|
1753
|
+
if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
|
|
1754
|
+
throw new ParseError("include url(...) is not supported", t.line, t.col);
|
|
1755
|
+
}
|
|
1756
|
+
if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
|
|
1757
|
+
throw new ParseError("include classpath(...) is not supported", t.line, t.col);
|
|
1758
|
+
}
|
|
1759
|
+
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
1760
|
+
}
|
|
1761
|
+
makeIncludeField(qualifier, path, required, pos) {
|
|
1762
|
+
return { key: [], value: { kind: "include", qualifier, path, required, pos }, append: false, pos };
|
|
1763
|
+
}
|
|
1764
|
+
/**
|
|
1765
|
+
* Skip past include-syntax wrapper tokens to find the quoted path string.
|
|
1766
|
+
* Strict: only allows `(`, `)`, and bare qualifier keywords (file / file( /
|
|
1767
|
+
* package / package( / url / url( / classpath / classpath() between the
|
|
1768
|
+
* directive and the quoted path. Anything else is a parse error rather than
|
|
1769
|
+
* silently swallowed (see ts#113).
|
|
1770
|
+
*/
|
|
1771
|
+
parseQuotedPathSkipWrapper(errTok) {
|
|
1772
|
+
while (this.isIncludeWrapperToken(this.peek())) this.advance();
|
|
1773
|
+
if (this.peek().kind !== "string") {
|
|
1774
|
+
const tok = this.peek();
|
|
1775
|
+
if (tok.kind === "eof") throw new ParseError("expected include path", errTok.line, errTok.col);
|
|
1776
|
+
const desc = tok.kind === "unquoted" ? `unquoted "${tok.value}"` : tok.kind;
|
|
1777
|
+
throw new ParseError(`expected quoted include path, got ${desc}`, tok.line, tok.col);
|
|
1778
|
+
}
|
|
1779
|
+
const path = this.advance().value;
|
|
1780
|
+
while (this.peek().kind === "unquoted" && (this.peek().value === ")" || this.peek().value === "))")) {
|
|
1781
|
+
this.advance();
|
|
1782
|
+
}
|
|
1783
|
+
const after = this.peek();
|
|
1784
|
+
if (after.kind !== "newline" && after.kind !== "rbrace" && after.kind !== "eof" && after.kind !== "comma") {
|
|
1785
|
+
const desc = after.kind === "unquoted" ? `unquoted "${after.value}"` : after.kind;
|
|
1786
|
+
throw new ParseError(`unexpected token after include path: ${desc}`, after.line, after.col);
|
|
1787
|
+
}
|
|
1788
|
+
return path;
|
|
1789
|
+
}
|
|
1790
|
+
isIncludeWrapperToken(tok) {
|
|
1791
|
+
return tok.kind === "unquoted" && tok.value === "(";
|
|
1792
|
+
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Parse the body of `package("identifier", "file")`.
|
|
1795
|
+
*
|
|
1796
|
+
* @param consumePackageToken - When true (default), the current token is `package(` and
|
|
1797
|
+
* must be consumed first. When false, the lexer already bundled `package(` into the
|
|
1798
|
+
* preceding token (e.g. `required(package("`), so we are already positioned at the
|
|
1799
|
+
* first string argument.
|
|
1800
|
+
*/
|
|
1801
|
+
parsePackageArgs(consumePackageToken = true) {
|
|
1802
|
+
const pkgTok = consumePackageToken ? this.advance() : this.peek();
|
|
1803
|
+
this.skip("newline");
|
|
1804
|
+
const idTok = this.peek();
|
|
1805
|
+
if (idTok.kind !== "string") {
|
|
1806
|
+
throw new ParseError("include package: expected quoted identifier as first argument", pkgTok.line, pkgTok.col);
|
|
1807
|
+
}
|
|
1808
|
+
const identifier = this.advance().value;
|
|
1809
|
+
this.skip("newline");
|
|
1810
|
+
if (this.peek().kind !== "comma") {
|
|
1811
|
+
const after = this.peek();
|
|
1812
|
+
throw new ParseError(
|
|
1813
|
+
after.kind === "string" ? "include package: missing comma between identifier and file arguments" : "include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
|
|
1814
|
+
pkgTok.line,
|
|
1815
|
+
pkgTok.col
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
this.advance();
|
|
1819
|
+
this.skip("newline");
|
|
1820
|
+
const fileTok = this.peek();
|
|
1821
|
+
if (fileTok.kind !== "string") {
|
|
1822
|
+
throw new ParseError(
|
|
1823
|
+
"include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
|
|
1824
|
+
pkgTok.line,
|
|
1825
|
+
pkgTok.col
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
const filePath = this.advance().value;
|
|
1829
|
+
while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") {
|
|
1830
|
+
this.advance();
|
|
1831
|
+
}
|
|
1832
|
+
return { qualifier: { kind: "package", identifier }, path: filePath };
|
|
1833
|
+
}
|
|
1834
|
+
parseValue() {
|
|
1835
|
+
const p = this.currentPos();
|
|
1836
|
+
const parts = [];
|
|
1837
|
+
while (true) {
|
|
1838
|
+
const t = this.peek();
|
|
1839
|
+
if (t.kind === "eof" || t.kind === "newline" || t.kind === "rbrace" || t.kind === "rbracket" || t.kind === "comma") break;
|
|
1840
|
+
const hadSpace = t.precedingSpace && parts.length > 0;
|
|
1841
|
+
let node;
|
|
1842
|
+
if (t.kind === "lbrace") {
|
|
1843
|
+
this.advance();
|
|
1844
|
+
node = this.parseObject(true);
|
|
1845
|
+
} else if (t.kind === "lbracket") {
|
|
1846
|
+
this.advance();
|
|
1847
|
+
node = this.parseArray();
|
|
1848
|
+
} else if (t.kind === "subst") {
|
|
1849
|
+
this.advance();
|
|
1850
|
+
if (!t.subst) {
|
|
1851
|
+
throw new ParseError(`internal: subst token missing payload`, t.line, t.col);
|
|
1852
|
+
}
|
|
1853
|
+
const payload = t.subst;
|
|
1854
|
+
node = { kind: "subst", segments: payload.segments, optional: payload.optional, listSuffix: payload.listSuffix, pos: { line: t.line, col: t.col } };
|
|
1855
|
+
} else if (t.kind === "string" || t.kind === "triple_string") {
|
|
1856
|
+
this.advance();
|
|
1857
|
+
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
1858
|
+
} else if (t.kind === "unquoted") {
|
|
1859
|
+
this.advance();
|
|
1860
|
+
node = { kind: "scalar", raw: t.value, valueType: this.scalarValueType(t.value, t.line, t.col), pos: { line: t.line, col: t.col } };
|
|
1861
|
+
} else if ((t.kind === "colon" || t.kind === "equals") && parts.length > 0) {
|
|
1862
|
+
this.advance();
|
|
1863
|
+
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
1864
|
+
} else {
|
|
1865
|
+
break;
|
|
1866
|
+
}
|
|
1867
|
+
if (hadSpace) {
|
|
1868
|
+
const sep = t.precedingWhitespace.length > 0 ? t.precedingWhitespace : " ";
|
|
1869
|
+
parts.push({ kind: "scalar", raw: sep, valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
|
|
1870
|
+
}
|
|
1871
|
+
parts.push(node);
|
|
1872
|
+
}
|
|
1873
|
+
if (parts.length === 0) throw new ParseError("expected value", this.peek().line, this.peek().col);
|
|
1874
|
+
if (parts.length === 1) {
|
|
1875
|
+
return parts[0];
|
|
1876
|
+
}
|
|
1877
|
+
return { kind: "concat", nodes: parts, pos: p };
|
|
1878
|
+
}
|
|
1879
|
+
parseArray() {
|
|
1880
|
+
const p = this.currentPos();
|
|
1881
|
+
const items = [];
|
|
1882
|
+
while (true) {
|
|
1883
|
+
this.skip("newline");
|
|
1884
|
+
if (this.peek().kind === "rbracket" || this.peek().kind === "eof") break;
|
|
1885
|
+
items.push(this.parseValue());
|
|
1886
|
+
this.skip("newline");
|
|
1887
|
+
if (this.peek().kind === "comma") this.advance();
|
|
1888
|
+
this.skip("newline");
|
|
1889
|
+
}
|
|
1890
|
+
const t = this.peek();
|
|
1891
|
+
if (t.kind !== "rbracket") throw new ParseError("expected ]", t.line, t.col);
|
|
1892
|
+
this.advance();
|
|
1893
|
+
return { kind: "array", items, pos: p };
|
|
1894
|
+
}
|
|
1895
|
+
scalarValueType(raw, line, col) {
|
|
1896
|
+
if (raw === "true" || raw === "false") return "boolean";
|
|
1897
|
+
if (raw === "null") return "null";
|
|
1898
|
+
if (raw.length > 0 && (raw.charCodeAt(0) >= 48 && raw.charCodeAt(0) <= 57 || raw.charCodeAt(0) === 45)) {
|
|
1899
|
+
if (DECIMAL_NUMBER_RE.test(raw)) {
|
|
1900
|
+
if (!Number.isFinite(Number(raw))) {
|
|
1901
|
+
throw new ParseError(`invalid float "${raw}"`, line, col);
|
|
1902
|
+
}
|
|
1903
|
+
return "number";
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
return "string";
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
function parseTokens(tokens) {
|
|
1910
|
+
return new Parser(tokens).parse();
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
// src/internal/resolver/include-loader.ts
|
|
1914
|
+
var _require;
|
|
1915
|
+
function getRequire() {
|
|
1916
|
+
if (_require === void 0) {
|
|
1917
|
+
_require = pickRequire(
|
|
1918
|
+
typeof __require === "function" ? __require : void 0,
|
|
1919
|
+
void 0
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
return _require;
|
|
1923
|
+
}
|
|
1924
|
+
function pickRequire(candidate, metaUrl) {
|
|
1925
|
+
if (typeof _optionalChain([candidate, 'optionalAccess', _5 => _5.resolve]) === "function") {
|
|
1926
|
+
return candidate;
|
|
1927
|
+
}
|
|
1928
|
+
return nodeModule.createRequire(_nullishCoalesce(metaUrl, () => ( nodeUrl.pathToFileURL(nodePath.join(process.cwd(), "noop.js")))));
|
|
1929
|
+
}
|
|
1930
|
+
function isPnpPath(p) {
|
|
1931
|
+
return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
|
|
1932
|
+
}
|
|
1933
|
+
function assertObjectRootedInclude(ast, sourcePath) {
|
|
1934
|
+
if (ast.kind === "array") {
|
|
1935
|
+
throw new ResolveError(
|
|
1936
|
+
`included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
|
|
1937
|
+
sourcePath,
|
|
1938
|
+
ast.pos.line,
|
|
1939
|
+
ast.pos.col
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
function validatePackageFile(file, identifier) {
|
|
1944
|
+
if (file.length === 0) {
|
|
1945
|
+
throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
|
|
1946
|
+
}
|
|
1947
|
+
if (file.includes("\\")) {
|
|
1948
|
+
throw new ParseError(
|
|
1949
|
+
`include package("${identifier}", "${file}"): backslash not allowed in file argument (use forward slash)`,
|
|
1950
|
+
0,
|
|
1951
|
+
0
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
if (file.startsWith("/")) {
|
|
1955
|
+
throw new ParseError(
|
|
1956
|
+
`include package("${identifier}", "${file}"): absolute path not allowed in file argument`,
|
|
1957
|
+
0,
|
|
1958
|
+
0
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
if (/(?:^|\/)\.\.?(?:\/|$)/u.test(file)) {
|
|
1962
|
+
throw new ParseError(
|
|
1963
|
+
`include package("${identifier}", "${file}"): path traversal (. or ..) not allowed in file argument`,
|
|
1964
|
+
0,
|
|
1965
|
+
0
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
if (file.includes("//")) {
|
|
1969
|
+
throw new ParseError(
|
|
1970
|
+
`include package("${identifier}", "${file}"): consecutive slashes not allowed in file argument`,
|
|
1971
|
+
0,
|
|
1972
|
+
0
|
|
1973
|
+
);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
function validatePackageIdentifier(identifier) {
|
|
1977
|
+
if (identifier.length === 0) {
|
|
1978
|
+
throw new ParseError("include package: identifier argument must be non-empty", 0, 0);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
function defaultPackageResolver(identifier, file, includingFile, baseDir, resolveFrom) {
|
|
1982
|
+
const from = resolveFrom ? Array.isArray(resolveFrom) ? resolveFrom : [resolveFrom] : baseDir ? [baseDir] : includingFile ? [nodePath.dirname(includingFile)] : [process.cwd()];
|
|
1983
|
+
let resolved;
|
|
1984
|
+
try {
|
|
1985
|
+
resolved = getRequire().resolve(`${identifier}/${file}`, { paths: from });
|
|
1986
|
+
} catch (e2) {
|
|
1987
|
+
throw new PackageLookupError(
|
|
1988
|
+
`include package("${identifier}", "${file}"): module not found (starting from: ${from.join(", ")})`,
|
|
1989
|
+
identifier,
|
|
1990
|
+
file,
|
|
1991
|
+
0,
|
|
1992
|
+
0
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
if (isPnpPath(resolved)) {
|
|
1996
|
+
throw new ResolveError(
|
|
1997
|
+
`include package("${identifier}", "${file}"): Yarn Berry PnP store paths are not supported. Provide a custom packageResolver option to handle PnP resolution.`,
|
|
1998
|
+
`${identifier}/${file}`,
|
|
1999
|
+
0,
|
|
2000
|
+
0
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
const fileParts = file.split("/");
|
|
2004
|
+
const expectedBasename = _nullishCoalesce(fileParts[fileParts.length - 1], () => ( file));
|
|
2005
|
+
const resolvedBasename = nodePath.basename(resolved);
|
|
2006
|
+
if (resolvedBasename !== expectedBasename) {
|
|
2007
|
+
throw new PackageLookupError(
|
|
2008
|
+
`include package("${identifier}", "${file}"): case mismatch \u2014 resolved "${resolvedBasename}" but expected "${expectedBasename}" (E11 decision 5: case-sensitive)`,
|
|
2009
|
+
identifier,
|
|
2010
|
+
file,
|
|
2011
|
+
0,
|
|
2012
|
+
0
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
2015
|
+
return resolved;
|
|
2016
|
+
}
|
|
2017
|
+
var IncludeLoader = class {
|
|
2018
|
+
constructor(opts) {
|
|
2019
|
+
this.opts = opts;
|
|
2020
|
+
}
|
|
2021
|
+
/**
|
|
2022
|
+
* Resolve an include path to an absolute path.
|
|
2023
|
+
* - file() includes resolve relative to CWD (or as absolute paths),
|
|
2024
|
+
* NOT relative to the including file's directory.
|
|
2025
|
+
* - Bare includes resolve relative to the including file's directory (baseDir).
|
|
2026
|
+
*/
|
|
2027
|
+
resolveIncludePath(includePath, baseDir, isFile) {
|
|
2028
|
+
return isFile ? nodePath.resolve(includePath) : baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
2029
|
+
}
|
|
2030
|
+
load(includePath, required, isFile) {
|
|
2031
|
+
const { baseDir, includeStack = [] } = this.opts;
|
|
2032
|
+
const absPath = this.resolveIncludePath(includePath, baseDir, !!isFile);
|
|
2033
|
+
if (includeStack.includes(absPath)) {
|
|
2034
|
+
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
2035
|
+
}
|
|
2036
|
+
if (includeStack.length >= 50) {
|
|
2037
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
2038
|
+
}
|
|
2039
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
2040
|
+
if (hasExplicitExt) {
|
|
2041
|
+
const result = this.loadSingle(absPath);
|
|
2042
|
+
if (result !== void 0) return result;
|
|
2043
|
+
if (required) {
|
|
2044
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
2045
|
+
}
|
|
2046
|
+
return makeResObj();
|
|
2047
|
+
}
|
|
2048
|
+
const merged = makeResObj();
|
|
2049
|
+
let foundAny = false;
|
|
2050
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
2051
|
+
for (const ext of probeExts) {
|
|
2052
|
+
const obj = this.loadSingle(`${absPath}${ext}`);
|
|
2053
|
+
if (obj !== void 0) {
|
|
2054
|
+
deepMergeResObjInto(merged, obj, []);
|
|
2055
|
+
foundAny = true;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
if (!foundAny && required) {
|
|
2059
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
2060
|
+
}
|
|
2061
|
+
return merged;
|
|
2062
|
+
}
|
|
2063
|
+
async loadAsync(includePath, required, isFile) {
|
|
2064
|
+
const { baseDir, includeStack = [] } = this.opts;
|
|
2065
|
+
const absPath = this.resolveIncludePath(includePath, baseDir, !!isFile);
|
|
2066
|
+
if (includeStack.includes(absPath)) {
|
|
2067
|
+
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
2068
|
+
}
|
|
2069
|
+
if (includeStack.length >= 50) {
|
|
2070
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
2071
|
+
}
|
|
2072
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
2073
|
+
if (hasExplicitExt) {
|
|
2074
|
+
const result = await this.loadSingleAsync(absPath);
|
|
2075
|
+
if (result !== void 0) return result;
|
|
2076
|
+
if (required) {
|
|
2077
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
2078
|
+
}
|
|
2079
|
+
return makeResObj();
|
|
2080
|
+
}
|
|
2081
|
+
const merged = makeResObj();
|
|
2082
|
+
let foundAny = false;
|
|
2083
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
2084
|
+
for (const ext of probeExts) {
|
|
2085
|
+
const obj = await this.loadSingleAsync(`${absPath}${ext}`);
|
|
2086
|
+
if (obj !== void 0) {
|
|
2087
|
+
deepMergeResObjInto(merged, obj, []);
|
|
2088
|
+
foundAny = true;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
if (!foundAny && required) {
|
|
2092
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
2093
|
+
}
|
|
2094
|
+
return merged;
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Resolve and load `include package("identifier", "file")`.
|
|
2098
|
+
* Validates the identifier and file arguments, resolves via the package resolver,
|
|
2099
|
+
* performs cycle detection using a JSON-serialised cycle key, and parses the content.
|
|
2100
|
+
*/
|
|
2101
|
+
// `required` is reserved for future optional-package semantics (E11 decision 7);
|
|
2102
|
+
// currently all package lookup failures throw regardless of this flag.
|
|
2103
|
+
loadPackage(identifier, file, _required) {
|
|
2104
|
+
validatePackageIdentifier(identifier);
|
|
2105
|
+
validatePackageFile(file, identifier);
|
|
2106
|
+
const { includeStack = [], baseDir, resolveFrom, packageResolver, readFileSync } = this.opts;
|
|
2107
|
+
const cycleKey = JSON.stringify(["package", identifier, file]);
|
|
2108
|
+
if (includeStack.includes(cycleKey)) {
|
|
2109
|
+
throw new ResolveError(
|
|
2110
|
+
`circular include: package("${identifier}", "${file}")`,
|
|
2111
|
+
`${identifier}/${file}`,
|
|
2112
|
+
0,
|
|
2113
|
+
0
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
if (includeStack.length >= 50) {
|
|
2117
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
|
|
2118
|
+
}
|
|
2119
|
+
const resolver = _nullishCoalesce(packageResolver, () => ( ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom))));
|
|
2120
|
+
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
2121
|
+
const content = readFileSync(resolvedPath);
|
|
2122
|
+
const tokens = tokenize(content);
|
|
2123
|
+
const ast = parseTokens(tokens);
|
|
2124
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
2125
|
+
return this.onBuildResObj(ast, {
|
|
2126
|
+
...this.opts,
|
|
2127
|
+
baseDir: nodePath.dirname(resolvedPath),
|
|
2128
|
+
includeStack: [...includeStack, cycleKey]
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Async variant of `loadPackage`.
|
|
2133
|
+
*/
|
|
2134
|
+
async loadPackageAsync(identifier, file, _required) {
|
|
2135
|
+
validatePackageIdentifier(identifier);
|
|
2136
|
+
validatePackageFile(file, identifier);
|
|
2137
|
+
const { includeStack = [], baseDir, resolveFrom, packageResolver, readFile, readFileSync } = this.opts;
|
|
2138
|
+
const cycleKey = JSON.stringify(["package", identifier, file]);
|
|
2139
|
+
if (includeStack.includes(cycleKey)) {
|
|
2140
|
+
throw new ResolveError(
|
|
2141
|
+
`circular include: package("${identifier}", "${file}")`,
|
|
2142
|
+
`${identifier}/${file}`,
|
|
2143
|
+
0,
|
|
2144
|
+
0
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
if (includeStack.length >= 50) {
|
|
2148
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
|
|
2149
|
+
}
|
|
2150
|
+
const resolver = _nullishCoalesce(packageResolver, () => ( ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom))));
|
|
2151
|
+
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
2152
|
+
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync(p);
|
|
2153
|
+
const content = await read(resolvedPath);
|
|
2154
|
+
const tokens = tokenize(content);
|
|
2155
|
+
const ast = parseTokens(tokens);
|
|
2156
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
2157
|
+
return this.onBuildResObjAsync(ast, {
|
|
2158
|
+
...this.opts,
|
|
2159
|
+
baseDir: nodePath.dirname(resolvedPath),
|
|
2160
|
+
includeStack: [...includeStack, cycleKey]
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
loadSingle(candidate) {
|
|
2164
|
+
const { readFileSync, includeStack = [] } = this.opts;
|
|
2165
|
+
if (includeStack.includes(candidate)) {
|
|
2166
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
2167
|
+
}
|
|
2168
|
+
let content;
|
|
2169
|
+
try {
|
|
2170
|
+
content = readFileSync(candidate);
|
|
2171
|
+
} catch (e) {
|
|
2172
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
2173
|
+
throw e;
|
|
2174
|
+
}
|
|
2175
|
+
if (candidate.endsWith(".properties")) {
|
|
2176
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
2177
|
+
}
|
|
2178
|
+
const tokens = tokenize(content);
|
|
2179
|
+
const ast = parseTokens(tokens);
|
|
2180
|
+
assertObjectRootedInclude(ast, candidate);
|
|
2181
|
+
return this.onBuildResObj(ast, {
|
|
2182
|
+
...this.opts,
|
|
2183
|
+
baseDir: nodePath.dirname(candidate),
|
|
2184
|
+
includeStack: [...includeStack, candidate]
|
|
2185
|
+
});
|
|
2186
|
+
}
|
|
2187
|
+
async loadSingleAsync(candidate) {
|
|
2188
|
+
const { readFile, readFileSync, includeStack = [] } = this.opts;
|
|
2189
|
+
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync(p);
|
|
2190
|
+
if (includeStack.includes(candidate)) {
|
|
2191
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
2192
|
+
}
|
|
2193
|
+
let content;
|
|
2194
|
+
try {
|
|
2195
|
+
content = await read(candidate);
|
|
2196
|
+
} catch (e) {
|
|
2197
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
2198
|
+
throw e;
|
|
2199
|
+
}
|
|
2200
|
+
if (candidate.endsWith(".properties")) {
|
|
2201
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
2202
|
+
}
|
|
2203
|
+
const tokens = tokenize(content);
|
|
2204
|
+
const ast = parseTokens(tokens);
|
|
2205
|
+
assertObjectRootedInclude(ast, candidate);
|
|
2206
|
+
return this.onBuildResObjAsync(ast, {
|
|
2207
|
+
...this.opts,
|
|
2208
|
+
baseDir: nodePath.dirname(candidate),
|
|
2209
|
+
includeStack: [...includeStack, candidate]
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
|
|
2214
|
+
// src/internal/resolver/structure-builder.ts
|
|
2215
|
+
var StructureBuilder = class _StructureBuilder {
|
|
2216
|
+
constructor(opts) {
|
|
2217
|
+
this.loader = new IncludeLoader(opts);
|
|
2218
|
+
this.loader.onBuildResObj = (a, o) => new _StructureBuilder(o).build(a);
|
|
2219
|
+
this.loader.onBuildResObjAsync = async (a, o) => new _StructureBuilder(o).buildAsync(a);
|
|
2220
|
+
}
|
|
2221
|
+
build(ast, pathPrefix = []) {
|
|
2222
|
+
if (ast.kind !== "object") {
|
|
2223
|
+
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
2224
|
+
}
|
|
2225
|
+
const obj = makeResObj();
|
|
2226
|
+
for (const field of ast.fields) {
|
|
2227
|
+
this.applyField(obj, field, pathPrefix);
|
|
2228
|
+
}
|
|
2229
|
+
return obj;
|
|
2230
|
+
}
|
|
2231
|
+
async buildAsync(ast, pathPrefix = []) {
|
|
2232
|
+
if (ast.kind !== "object") {
|
|
2233
|
+
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
2234
|
+
}
|
|
2235
|
+
const obj = makeResObj();
|
|
2236
|
+
for (const field of ast.fields) {
|
|
2237
|
+
await this.applyFieldAsync(obj, field, pathPrefix);
|
|
2238
|
+
}
|
|
2239
|
+
return obj;
|
|
2240
|
+
}
|
|
2241
|
+
applyField(obj, field, pathPrefix) {
|
|
2242
|
+
if (field.key.length === 0 && field.value.kind === "include") {
|
|
2243
|
+
const { qualifier, path, required } = field.value;
|
|
2244
|
+
let included;
|
|
2245
|
+
if (qualifier.kind === "package") {
|
|
2246
|
+
included = this.loader.loadPackage(qualifier.identifier, path, required);
|
|
2247
|
+
} else {
|
|
2248
|
+
included = this.loader.load(path, required, qualifier.kind === "file");
|
|
2249
|
+
}
|
|
2250
|
+
if (pathPrefix.length > 0) {
|
|
2251
|
+
this.relativizeResObj(included, pathPrefix);
|
|
2252
|
+
}
|
|
2253
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
const [head, ...tail] = field.key;
|
|
2257
|
+
if (head === void 0) return;
|
|
2258
|
+
if (tail.length > 0) {
|
|
2259
|
+
const syntheticAst = {
|
|
2260
|
+
kind: "object",
|
|
2261
|
+
fields: [{ key: tail, value: field.value, append: field.append, pos: field.pos }],
|
|
2262
|
+
pos: field.pos
|
|
2263
|
+
};
|
|
2264
|
+
this.applyField(obj, { key: [head], value: syntheticAst, append: false, pos: field.pos }, pathPrefix);
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
const childPrefix = [...pathPrefix, head];
|
|
2268
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
2269
|
+
if (field.append) {
|
|
2270
|
+
const synthetic = this.desugarAppend(field, childPrefix);
|
|
2271
|
+
this.applyField(obj, synthetic, pathPrefix);
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
const existing = obj.fields.get(head);
|
|
2275
|
+
const newVal = this.astToResolverValue(field.value, childPrefix);
|
|
2276
|
+
if (!containsSelfRef(newVal, fullKey)) {
|
|
2277
|
+
obj.resetKeys.add(head);
|
|
2278
|
+
}
|
|
2279
|
+
if (existing !== void 0) {
|
|
2280
|
+
const oldPrior = obj.priorValues.get(head);
|
|
2281
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
2282
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
2283
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
2284
|
+
}
|
|
2285
|
+
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
2286
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
obj.fields.set(head, newVal);
|
|
2290
|
+
}
|
|
2291
|
+
async applyFieldAsync(obj, field, pathPrefix) {
|
|
2292
|
+
if (field.key.length === 0 && field.value.kind === "include") {
|
|
2293
|
+
const { qualifier, path, required } = field.value;
|
|
2294
|
+
let included;
|
|
2295
|
+
if (qualifier.kind === "package") {
|
|
2296
|
+
included = await this.loader.loadPackageAsync(qualifier.identifier, path, required);
|
|
2297
|
+
} else {
|
|
2298
|
+
included = await this.loader.loadAsync(path, required, qualifier.kind === "file");
|
|
2299
|
+
}
|
|
2300
|
+
if (pathPrefix.length > 0) {
|
|
2301
|
+
this.relativizeResObj(included, pathPrefix);
|
|
2302
|
+
}
|
|
2303
|
+
deepMergeResObjInto(obj, included, pathPrefix);
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
const [head, ...tail] = field.key;
|
|
2307
|
+
if (head === void 0) return;
|
|
2308
|
+
if (tail.length > 0) {
|
|
2309
|
+
const syntheticAst = {
|
|
2310
|
+
kind: "object",
|
|
2311
|
+
fields: [{ key: tail, value: field.value, append: field.append, pos: field.pos }],
|
|
2312
|
+
pos: field.pos
|
|
2313
|
+
};
|
|
2314
|
+
await this.applyFieldAsync(obj, { key: [head], value: syntheticAst, append: false, pos: field.pos }, pathPrefix);
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
const childPrefix = [...pathPrefix, head];
|
|
2318
|
+
const fullKey = stringSegmentsToKey(childPrefix);
|
|
2319
|
+
if (field.append) {
|
|
2320
|
+
const synthetic = this.desugarAppend(field, childPrefix);
|
|
2321
|
+
await this.applyFieldAsync(obj, synthetic, pathPrefix);
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
const existing = obj.fields.get(head);
|
|
2325
|
+
const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
|
|
2326
|
+
if (!containsSelfRef(newVal, fullKey)) {
|
|
2327
|
+
obj.resetKeys.add(head);
|
|
2328
|
+
}
|
|
2329
|
+
if (existing !== void 0) {
|
|
2330
|
+
const oldPrior = obj.priorValues.get(head);
|
|
2331
|
+
const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
|
|
2332
|
+
const prior = foldOrSkipPrior(priorInput, fullKey, oldPrior);
|
|
2333
|
+
if (prior !== void 0) obj.priorValues.set(head, prior);
|
|
2334
|
+
}
|
|
2335
|
+
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
2336
|
+
deepMergeResObjInto(existing, newVal, childPrefix);
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
obj.fields.set(head, newVal);
|
|
2340
|
+
}
|
|
2341
|
+
/** Builds the `key = ${?fullkey} [value]` field that `key += value` desugars
|
|
2342
|
+
* to (S13b.2, HOCON.md L732). `childPrefix` is the field's fully-qualified
|
|
2343
|
+
* path (pathPrefix + key), so a nested `srv { items += x }` references
|
|
2344
|
+
* `${?srv.items}`; include relativization rewrites it under a mount prefix.
|
|
2345
|
+
* `field.key` is already single-segment here (multi-segment keys are split
|
|
2346
|
+
* into nested objects upstream). Returns a NEW AstField (append=false);
|
|
2347
|
+
* positions inherit from the original field so resolve-time errors (the
|
|
2348
|
+
* S13b.2 non-array check, now via the scalar+array concat) keep a location. */
|
|
2349
|
+
desugarAppend(field, childPrefix) {
|
|
2350
|
+
const segments = childPrefix.map((text) => ({ text, line: field.pos.line, col: field.pos.col }));
|
|
2351
|
+
const subst = { kind: "subst", segments, optional: true, listSuffix: false, pos: field.pos };
|
|
2352
|
+
const elemArray = { kind: "array", items: [field.value], pos: field.pos };
|
|
2353
|
+
const synthetic = { kind: "concat", nodes: [subst, elemArray], pos: field.pos };
|
|
2354
|
+
return { key: field.key, value: synthetic, append: false, pos: field.pos };
|
|
2355
|
+
}
|
|
2356
|
+
astToResolverValue(ast, pathPrefix) {
|
|
2357
|
+
switch (ast.kind) {
|
|
2358
|
+
case "scalar": {
|
|
2359
|
+
const sv = { kind: "scalar", raw: ast.raw, valueType: ast.valueType };
|
|
2360
|
+
if (ast._separator) separatorValues.add(sv);
|
|
2361
|
+
return sv;
|
|
2362
|
+
}
|
|
2363
|
+
case "array":
|
|
2364
|
+
return { kind: "array", items: ast.items.map((i) => this.astToResolverValue(i, pathPrefix)) };
|
|
2365
|
+
case "object": {
|
|
2366
|
+
const inner = this.build(ast, pathPrefix);
|
|
2367
|
+
return inner;
|
|
2368
|
+
}
|
|
2369
|
+
case "subst":
|
|
2370
|
+
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 };
|
|
2371
|
+
case "concat":
|
|
2372
|
+
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
|
|
2373
|
+
case "include":
|
|
2374
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
async astToResolverValueAsync(ast, pathPrefix) {
|
|
2378
|
+
switch (ast.kind) {
|
|
2379
|
+
case "scalar": {
|
|
2380
|
+
const sv = { kind: "scalar", raw: ast.raw, valueType: ast.valueType };
|
|
2381
|
+
if (ast._separator) separatorValues.add(sv);
|
|
2382
|
+
return sv;
|
|
2383
|
+
}
|
|
2384
|
+
case "array": {
|
|
2385
|
+
const items = [];
|
|
2386
|
+
for (const i of ast.items) {
|
|
2387
|
+
items.push(await this.astToResolverValueAsync(i, pathPrefix));
|
|
2388
|
+
}
|
|
2389
|
+
return { kind: "array", items };
|
|
2390
|
+
}
|
|
2391
|
+
case "object":
|
|
2392
|
+
return await this.buildAsync(ast, pathPrefix);
|
|
2393
|
+
case "subst":
|
|
2394
|
+
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 };
|
|
2395
|
+
case "concat": {
|
|
2396
|
+
const nodes = [];
|
|
2397
|
+
for (const n of ast.nodes) {
|
|
2398
|
+
nodes.push(await this.astToResolverValueAsync(n, pathPrefix));
|
|
2399
|
+
}
|
|
2400
|
+
return { _kind: "concat-placeholder", nodes, line: ast.pos.line, col: ast.pos.col };
|
|
2401
|
+
}
|
|
2402
|
+
case "include":
|
|
2403
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
// ---- Relativize substitution paths for nested includes ----
|
|
2407
|
+
relativizeSubstPaths(val, prefixSegments) {
|
|
2408
|
+
if (isSubst(val)) {
|
|
2409
|
+
const prefixAsSegments = prefixSegments.map((text) => ({ text, line: 0, col: 0 }));
|
|
2410
|
+
val.segments = [...prefixAsSegments, ...val.segments];
|
|
2411
|
+
val.prefixLen += prefixSegments.length;
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
if (isConcat(val)) {
|
|
2415
|
+
for (const node of val.nodes) {
|
|
2416
|
+
this.relativizeSubstPaths(node, prefixSegments);
|
|
2417
|
+
}
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
if (isResObj(val)) {
|
|
2421
|
+
this.relativizeResObj(val, prefixSegments);
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
const hv = val;
|
|
2425
|
+
if (hv.kind === "array") {
|
|
2426
|
+
for (const item of hv.items) {
|
|
2427
|
+
this.relativizeSubstPaths(item, prefixSegments);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
relativizeResObj(obj, prefixSegments) {
|
|
2432
|
+
for (const val of obj.fields.values()) {
|
|
2433
|
+
this.relativizeSubstPaths(val, prefixSegments);
|
|
2434
|
+
}
|
|
2435
|
+
for (const val of obj.priorValues.values()) {
|
|
2436
|
+
this.relativizeSubstPaths(val, prefixSegments);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
|
|
2441
|
+
// src/value/numeric-array.ts
|
|
2442
|
+
function numericObjectToArray(value) {
|
|
2443
|
+
if (value.kind !== "object") return null;
|
|
2444
|
+
if (value.fields.size === 0) return null;
|
|
2445
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
2446
|
+
const eligible = [];
|
|
2447
|
+
for (const [key, val] of value.fields) {
|
|
2448
|
+
if (!canonicalInt.test(key)) continue;
|
|
2449
|
+
const n = Number(key);
|
|
2450
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
2451
|
+
eligible.push({ n, v: val });
|
|
2452
|
+
}
|
|
2453
|
+
if (eligible.length === 0) return null;
|
|
2454
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
2455
|
+
return eligible.map((e) => e.v);
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
// src/internal/resolver/substitution-resolver.ts
|
|
2459
|
+
var SubstitutionResolver = class {
|
|
2460
|
+
constructor(root, opts) {
|
|
2461
|
+
this.root = root;
|
|
2462
|
+
this.opts = opts;
|
|
2463
|
+
this.resolving = /* @__PURE__ */ new Set();
|
|
2464
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
2465
|
+
// Tracks ConcatPlaceholder nodes currently being iterated by resolveConcat.
|
|
2466
|
+
// Used by isSelfRef detection: fires only when found === a concat currently
|
|
2467
|
+
// mid-iteration (i.e. we are inside found's own resolution), preventing the
|
|
2468
|
+
// "foofoo" double-count without misfiring when an external field looks up the
|
|
2469
|
+
// same concat via a substitution (multi-reviewer convergence fix).
|
|
2470
|
+
//
|
|
2471
|
+
// Spec deviation: the S13a.13 spec ★1 decision #1 specified path-equality
|
|
2472
|
+
// preservation for self-ref detection. Round-2 multi-agent-review surfaced a
|
|
2473
|
+
// false-positive on external lookups (`a = ${?a}foo; b = ${a}`), so the
|
|
2474
|
+
// criterion was tightened to node-membership-in-iterating-set — strictly
|
|
2475
|
+
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
2476
|
+
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
2477
|
+
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
2478
|
+
// Path stack tracking the full dotted path of the field currently being
|
|
2479
|
+
// assigned in `resolveResObj`. Leaf keys are pushed on entry to each
|
|
2480
|
+
// `(key, val)` iteration and popped on exit, so nested objects build up
|
|
2481
|
+
// the full path (e.g. `["o", "history"]` while resolving `o.history`).
|
|
2482
|
+
//
|
|
2483
|
+
// Used to tighten self-ref detection at the L192 found-lookup site:
|
|
2484
|
+
// a substitution `${X}` whose looked-up value contains a Subst pointing
|
|
2485
|
+
// at `X` is only a true self-reference when the field currently being
|
|
2486
|
+
// assigned is `X` (or a descendant of `X`). Without this guard, an
|
|
2487
|
+
// external lookup like `b = ${o}` (where o has a self-referential
|
|
2488
|
+
// field) would mis-fire and short-circuit to o's prior, returning the
|
|
2489
|
+
// wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
|
|
2490
|
+
// narrows the check to the owner case only.
|
|
2491
|
+
//
|
|
2492
|
+
// Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
|
|
2493
|
+
// same prefix-match semantics).
|
|
2494
|
+
this.resolvingFieldPath = [];
|
|
2495
|
+
// S13a.12 recursion guard: field keys whose prior is currently being
|
|
2496
|
+
// resolved through the prefix-self-ref branch. Saved priors are
|
|
2497
|
+
// prefix-self-ref-free by the fold invariant, so re-entry only happens on
|
|
2498
|
+
// a shape the fold could not see through (a live subst mid-navigation) —
|
|
2499
|
+
// report it as a cycle instead of recursing forever.
|
|
2500
|
+
this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
|
|
2501
|
+
}
|
|
2502
|
+
/** Returns a prefix for error messages, including origin info when set. */
|
|
2503
|
+
originPrefix() {
|
|
2504
|
+
return this.opts.originDescription ? `${this.opts.originDescription}: ` : "";
|
|
2505
|
+
}
|
|
2506
|
+
resolve() {
|
|
2507
|
+
return this.resolveResObj(this.root);
|
|
2508
|
+
}
|
|
2509
|
+
resolveResObj(obj) {
|
|
2510
|
+
const result = /* @__PURE__ */ new Map();
|
|
2511
|
+
for (const [key, val] of obj.fields) {
|
|
2512
|
+
this.resolvingFieldPath.push(key);
|
|
2513
|
+
const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
|
|
2514
|
+
this.cache.delete(fullCacheKey);
|
|
2515
|
+
try {
|
|
2516
|
+
const resolved = this.resolveVal(val, obj);
|
|
2517
|
+
if (resolved !== void 0) {
|
|
2518
|
+
if (resolved.kind === "object") {
|
|
2519
|
+
const prior = obj.priorValues.get(key);
|
|
2520
|
+
if (prior !== void 0) {
|
|
2521
|
+
const priorResolved = this.resolveVal(prior, obj);
|
|
2522
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
2523
|
+
const finalValue = deepMergeHoconValues(
|
|
2524
|
+
priorResolved,
|
|
2525
|
+
resolved
|
|
2526
|
+
);
|
|
2527
|
+
result.set(key, finalValue);
|
|
2528
|
+
this.cache.set(fullCacheKey, finalValue);
|
|
2529
|
+
this.cacheDescendants(fullCacheKey, finalValue);
|
|
2530
|
+
continue;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
result.set(key, resolved);
|
|
2535
|
+
this.cache.set(fullCacheKey, resolved);
|
|
2536
|
+
this.cacheDescendants(fullCacheKey, resolved);
|
|
2537
|
+
} else {
|
|
2538
|
+
const prior = obj.priorValues.get(key);
|
|
2539
|
+
if (prior !== void 0) {
|
|
2540
|
+
const priorResolved = this.resolveVal(prior, obj);
|
|
2541
|
+
if (priorResolved !== void 0) {
|
|
2542
|
+
result.set(key, priorResolved);
|
|
2543
|
+
this.cache.set(fullCacheKey, priorResolved);
|
|
2544
|
+
this.cacheDescendants(fullCacheKey, priorResolved);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
} finally {
|
|
2549
|
+
this.resolvingFieldPath.pop();
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
return { kind: "object", fields: result };
|
|
2553
|
+
}
|
|
2554
|
+
/** S13a.12: walk resolved-object fields by segment text. A missing segment
|
|
2555
|
+
* or a walk into a scalar/array is path-absent → undefined. */
|
|
2556
|
+
navigateResolved(v, remainder) {
|
|
2557
|
+
let cur = v;
|
|
2558
|
+
for (const seg of remainder) {
|
|
2559
|
+
if (cur.kind !== "object") return void 0;
|
|
2560
|
+
const next = cur.fields.get(seg);
|
|
2561
|
+
if (next === void 0) return void 0;
|
|
2562
|
+
cur = next;
|
|
2563
|
+
}
|
|
2564
|
+
return cur;
|
|
2565
|
+
}
|
|
2566
|
+
cacheDescendants(prefix, value) {
|
|
2567
|
+
if (value.kind !== "object") return;
|
|
2568
|
+
for (const [key, child] of value.fields) {
|
|
2569
|
+
const childKey = `${prefix}.${stringSegmentsToKey([key])}`;
|
|
2570
|
+
this.cache.set(childKey, child);
|
|
2571
|
+
this.cacheDescendants(childKey, child);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
resolveVal(v, scope) {
|
|
2575
|
+
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
2576
|
+
if (isConcat(v)) {
|
|
2577
|
+
this.resolvingConcats.add(v);
|
|
2578
|
+
try {
|
|
2579
|
+
const concatResult = this.resolveConcat(v.nodes, scope, v.line, v.col);
|
|
2580
|
+
return concatResult;
|
|
2581
|
+
} finally {
|
|
2582
|
+
this.resolvingConcats.delete(v);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
if (isResObj(v)) return this.resolveResObj(v);
|
|
2586
|
+
const hv = v;
|
|
2587
|
+
if (hv.kind === "array") {
|
|
2588
|
+
const items = [];
|
|
2589
|
+
for (const item of hv.items) {
|
|
2590
|
+
const resolved = this.resolveVal(item, scope);
|
|
2591
|
+
if (resolved === void 0) continue;
|
|
2592
|
+
items.push(resolved);
|
|
2593
|
+
}
|
|
2594
|
+
return { kind: "array", items };
|
|
2595
|
+
}
|
|
2596
|
+
return hv;
|
|
2597
|
+
}
|
|
2598
|
+
resolveSubst(s, scope) {
|
|
2599
|
+
if (s.knownAbsent) {
|
|
2600
|
+
if (!s.optional) {
|
|
2601
|
+
if (this.opts.allowUnresolved) return s;
|
|
2602
|
+
const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
2603
|
+
throw new ResolveError(
|
|
2604
|
+
`${this.originPrefix()}could not resolve substitution: \${${k}}`,
|
|
2605
|
+
k,
|
|
2606
|
+
s.line,
|
|
2607
|
+
s.col
|
|
2608
|
+
);
|
|
2609
|
+
}
|
|
2610
|
+
return void 0;
|
|
2611
|
+
}
|
|
2612
|
+
{
|
|
2613
|
+
const rfp = this.resolvingFieldPath;
|
|
2614
|
+
const sTexts = s.segments.map((seg) => seg.text);
|
|
2615
|
+
if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
|
|
2616
|
+
const guardKey = stringSegmentsToKey(rfp);
|
|
2617
|
+
const prior = scope.priorValues.get(rfp[rfp.length - 1]);
|
|
2618
|
+
if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
|
|
2619
|
+
this.resolvingPrefixPriors.add(guardKey);
|
|
2620
|
+
try {
|
|
2621
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
2622
|
+
if (priorResolved !== void 0) {
|
|
2623
|
+
const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
|
|
2624
|
+
if (navigated !== void 0) return navigated;
|
|
2625
|
+
}
|
|
2626
|
+
} finally {
|
|
2627
|
+
this.resolvingPrefixPriors.delete(guardKey);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
if (s.optional) return void 0;
|
|
2631
|
+
if (this.opts.allowUnresolved) return s;
|
|
2632
|
+
const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
2633
|
+
throw new ResolveError(
|
|
2634
|
+
`${this.originPrefix()}could not resolve substitution: \${${k}}`,
|
|
2635
|
+
k,
|
|
2636
|
+
s.line,
|
|
2637
|
+
s.col
|
|
2638
|
+
);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
2642
|
+
if (this.cache.has(key)) return this.cache.get(key);
|
|
2643
|
+
if (this.resolving.has(key)) {
|
|
2644
|
+
let prior;
|
|
2645
|
+
if (s.segments.length > 1) {
|
|
2646
|
+
const leafSeg = _nullishCoalesce(_optionalChain([s, 'access', _6 => _6.segments, 'access', _7 => _7[s.segments.length - 1], 'optionalAccess', _8 => _8.text]), () => ( ""));
|
|
2647
|
+
const parentScope = lookupResObj(
|
|
2648
|
+
this.root,
|
|
2649
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
2650
|
+
);
|
|
2651
|
+
prior = _optionalChain([parentScope, 'optionalAccess', _9 => _9.priorValues, 'access', _10 => _10.get, 'call', _11 => _11(leafSeg)]);
|
|
2652
|
+
} else {
|
|
2653
|
+
const rootSeg = _nullishCoalesce(_optionalChain([s, 'access', _12 => _12.segments, 'access', _13 => _13[0], 'optionalAccess', _14 => _14.text]), () => ( ""));
|
|
2654
|
+
prior = _nullishCoalesce(scope.priorValues.get(rootSeg), () => ( this.root.priorValues.get(rootSeg)));
|
|
2655
|
+
}
|
|
2656
|
+
if (prior !== void 0) {
|
|
2657
|
+
const saved = this.resolving;
|
|
2658
|
+
this.resolving = new Set(saved);
|
|
2659
|
+
try {
|
|
2660
|
+
return this.resolveVal(prior, scope);
|
|
2661
|
+
} finally {
|
|
2662
|
+
this.resolving = saved;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
if (s.optional) return void 0;
|
|
2666
|
+
throw new ResolveError(
|
|
2667
|
+
this.originPrefix() + `circular substitution: ${key}`,
|
|
2668
|
+
key,
|
|
2669
|
+
s.line,
|
|
2670
|
+
s.col
|
|
2671
|
+
);
|
|
2672
|
+
}
|
|
2673
|
+
this.resolving.add(key);
|
|
2674
|
+
try {
|
|
2675
|
+
const found = lookupPath(this.root, s.segments);
|
|
2676
|
+
if (found !== void 0) {
|
|
2677
|
+
const rfp = this.resolvingFieldPath;
|
|
2678
|
+
const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
|
|
2679
|
+
const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
|
|
2680
|
+
if (isSelfRef) {
|
|
2681
|
+
let prior;
|
|
2682
|
+
if (s.segments.length > 1) {
|
|
2683
|
+
const leafSeg = _nullishCoalesce(_optionalChain([s, 'access', _15 => _15.segments, 'access', _16 => _16[s.segments.length - 1], 'optionalAccess', _17 => _17.text]), () => ( ""));
|
|
2684
|
+
const parentScope = lookupResObj(
|
|
2685
|
+
this.root,
|
|
2686
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
2687
|
+
);
|
|
2688
|
+
prior = _optionalChain([parentScope, 'optionalAccess', _18 => _18.priorValues, 'access', _19 => _19.get, 'call', _20 => _20(leafSeg)]);
|
|
2689
|
+
} else {
|
|
2690
|
+
const rootSeg = _nullishCoalesce(_optionalChain([s, 'access', _21 => _21.segments, 'access', _22 => _22[0], 'optionalAccess', _23 => _23.text]), () => ( ""));
|
|
2691
|
+
prior = _nullishCoalesce(scope.priorValues.get(rootSeg), () => ( this.root.priorValues.get(rootSeg)));
|
|
2692
|
+
}
|
|
2693
|
+
if (prior !== void 0) {
|
|
2694
|
+
const result2 = this.resolveVal(prior, scope);
|
|
2695
|
+
if (result2 !== void 0) this.cache.set(key, result2);
|
|
2696
|
+
return result2;
|
|
2697
|
+
}
|
|
2698
|
+
if (s.optional) return void 0;
|
|
2699
|
+
throw new ResolveError(
|
|
2700
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
2701
|
+
key,
|
|
2702
|
+
s.line,
|
|
2703
|
+
s.col
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2706
|
+
let result = this.resolveVal(found, scope);
|
|
2707
|
+
const effectiveLen = s.segments.length - s.prefixLen;
|
|
2708
|
+
if (effectiveLen === 1 && result !== void 0 && result.kind === "object") {
|
|
2709
|
+
const leafSeg = _nullishCoalesce(_optionalChain([s, 'access', _24 => _24.segments, 'access', _25 => _25[s.segments.length - 1], 'optionalAccess', _26 => _26.text]), () => ( ""));
|
|
2710
|
+
let prior;
|
|
2711
|
+
if (s.prefixLen > 0) {
|
|
2712
|
+
const parentScope = lookupResObj(
|
|
2713
|
+
this.root,
|
|
2714
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
2715
|
+
);
|
|
2716
|
+
prior = _optionalChain([parentScope, 'optionalAccess', _27 => _27.priorValues, 'access', _28 => _28.get, 'call', _29 => _29(leafSeg)]);
|
|
2717
|
+
} else {
|
|
2718
|
+
prior = _nullishCoalesce(scope.priorValues.get(leafSeg), () => ( this.root.priorValues.get(leafSeg)));
|
|
2719
|
+
}
|
|
2720
|
+
if (prior !== void 0) {
|
|
2721
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
2722
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
2723
|
+
result = deepMergeHoconValues(
|
|
2724
|
+
priorResolved,
|
|
2725
|
+
result
|
|
2726
|
+
);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
2731
|
+
return result;
|
|
2732
|
+
}
|
|
2733
|
+
if (s.prefixLen > 0 && s.segments.length > s.prefixLen) {
|
|
2734
|
+
const originalSegments = s.segments.slice(s.prefixLen);
|
|
2735
|
+
const fallbackFound = lookupPath(this.root, originalSegments);
|
|
2736
|
+
if (fallbackFound !== void 0) {
|
|
2737
|
+
let result = this.resolveVal(fallbackFound, scope);
|
|
2738
|
+
if (originalSegments.length === 1 && result !== void 0 && result.kind === "object") {
|
|
2739
|
+
const rootSeg = _nullishCoalesce(_optionalChain([originalSegments, 'access', _30 => _30[0], 'optionalAccess', _31 => _31.text]), () => ( ""));
|
|
2740
|
+
const prior = this.root.priorValues.get(rootSeg);
|
|
2741
|
+
if (prior !== void 0) {
|
|
2742
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
2743
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
2744
|
+
result = deepMergeHoconValues(
|
|
2745
|
+
priorResolved,
|
|
2746
|
+
result
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
2752
|
+
return result;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
2756
|
+
const result = this.resolveEnvList(s);
|
|
2757
|
+
if (result !== void 0) {
|
|
2758
|
+
this.cache.set(key, result);
|
|
2759
|
+
return result;
|
|
2760
|
+
}
|
|
2761
|
+
if (s.optional) return void 0;
|
|
2762
|
+
const envBase = s.segments.map((seg) => seg.text).join(".");
|
|
2763
|
+
throw new ResolveError(
|
|
2764
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}} (no environment variables ${envBase}_0, ${envBase}_1, \u2026 set)`,
|
|
2765
|
+
key,
|
|
2766
|
+
s.line,
|
|
2767
|
+
s.col
|
|
2768
|
+
);
|
|
2769
|
+
} else if (s.listSuffix) {
|
|
2770
|
+
if (s.optional) return void 0;
|
|
2771
|
+
if (this.opts.allowUnresolved) return s;
|
|
2772
|
+
throw new ResolveError(
|
|
2773
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
2774
|
+
key,
|
|
2775
|
+
s.line,
|
|
2776
|
+
s.col
|
|
2777
|
+
);
|
|
2778
|
+
}
|
|
2779
|
+
if (this.opts.useSystemEnvironment !== false) {
|
|
2780
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
2781
|
+
const envVal = _nullishCoalesce(this.envVar(envKey), () => ( (s.prefixLen > 0 ? this.envVar(s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")) : void 0)));
|
|
2782
|
+
if (envVal !== void 0) {
|
|
2783
|
+
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
2784
|
+
this.cache.set(key, result);
|
|
2785
|
+
return result;
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
if (s.optional) return void 0;
|
|
2789
|
+
if (this.opts.allowUnresolved) {
|
|
2790
|
+
return s;
|
|
2791
|
+
}
|
|
2792
|
+
throw new ResolveError(
|
|
2793
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
2794
|
+
key,
|
|
2795
|
+
s.line,
|
|
2796
|
+
s.col
|
|
2797
|
+
);
|
|
2798
|
+
} finally {
|
|
2799
|
+
this.resolving.delete(key);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2803
|
+
* S13c: scan environment for NAME_0, NAME_1, … and return an Array<Scalar(String)>.
|
|
2804
|
+
*
|
|
2805
|
+
* Candidate bases follow the same fully-qualified→bare order used by the scalar
|
|
2806
|
+
* env fallback. The first base whose _0 key is present wins entirely — no
|
|
2807
|
+
* cross-base merging (spec §Relativized fallback precedence rules).
|
|
2808
|
+
*
|
|
2809
|
+
* Returns the HoconValue Array if any candidate base has _0, or undefined if
|
|
2810
|
+
* none do (caller applies optional/required handling).
|
|
2811
|
+
*/
|
|
2812
|
+
resolveEnvList(s) {
|
|
2813
|
+
const bases = this.candidateBases(s);
|
|
2814
|
+
for (const base of bases) {
|
|
2815
|
+
const key0 = base + "_0";
|
|
2816
|
+
if (this.envVar(key0) === void 0) continue;
|
|
2817
|
+
const items = [];
|
|
2818
|
+
for (let i = 0; ; i++) {
|
|
2819
|
+
const v = this.envVar(base + "_" + i);
|
|
2820
|
+
if (v === void 0) break;
|
|
2821
|
+
items.push({ kind: "scalar", raw: v, valueType: "string" });
|
|
2822
|
+
}
|
|
2823
|
+
return { kind: "array", items };
|
|
2824
|
+
}
|
|
2825
|
+
return void 0;
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* The environment variable `name`, or undefined when it is not set. Own keys
|
|
2829
|
+
* only: the environment is a plain object (process.env, or the caller's
|
|
2830
|
+
* `env`), so `env[name]` would answer `${?constructor}` or `${?toString}`
|
|
2831
|
+
* from Object.prototype (#228).
|
|
2832
|
+
*/
|
|
2833
|
+
envVar(name) {
|
|
2834
|
+
return Object.prototype.hasOwnProperty.call(this.opts.env, name) ? this.opts.env[name] : void 0;
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Return the candidate base names for env-var-list lookup, in precedence order.
|
|
2838
|
+
* Mirrors the fully-qualified→bare fallback used by the scalar env path.
|
|
2839
|
+
*/
|
|
2840
|
+
candidateBases(s) {
|
|
2841
|
+
const full = s.segments.map((seg) => seg.text).join(".");
|
|
2842
|
+
if (s.prefixLen === 0) return [full];
|
|
2843
|
+
const bare = s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".");
|
|
2844
|
+
return [full, bare];
|
|
2845
|
+
}
|
|
2846
|
+
resolveConcat(nodes, scope, line = 0, col = 0) {
|
|
2847
|
+
const rawResolved = nodes.map((n) => this.resolveVal(n, scope));
|
|
2848
|
+
if (this.opts.allowUnresolved) {
|
|
2849
|
+
for (const v of rawResolved) {
|
|
2850
|
+
if (v !== void 0 && isSubst(v)) {
|
|
2851
|
+
return v;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
const resolved = rawResolved.filter((v) => v !== void 0);
|
|
2856
|
+
if (resolved.length === 0) return void 0;
|
|
2857
|
+
if (resolved.length === 1) return resolved[0];
|
|
2858
|
+
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
2859
|
+
if (nonSep.length === 0) {
|
|
2860
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
2861
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
2862
|
+
}
|
|
2863
|
+
const joinPair = (left, right) => {
|
|
2864
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
2865
|
+
return deepMergeHoconValues(left, right);
|
|
2866
|
+
}
|
|
2867
|
+
if (left.kind === "array" && right.kind === "object") {
|
|
2868
|
+
const converted = numericObjectToArray(right);
|
|
2869
|
+
if (converted !== null) {
|
|
2870
|
+
return { kind: "array", items: [...left.items, ...converted] };
|
|
2871
|
+
}
|
|
2872
|
+
throw new ResolveError(
|
|
2873
|
+
"cannot concatenate array with object: value concatenation requires same-kind operands (S10.4)",
|
|
2874
|
+
"",
|
|
2875
|
+
line,
|
|
2876
|
+
col
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
if (left.kind === "object" && right.kind === "array") {
|
|
2880
|
+
const converted = numericObjectToArray(left);
|
|
2881
|
+
if (converted !== null) {
|
|
2882
|
+
return { kind: "array", items: [...converted, ...right.items] };
|
|
2883
|
+
}
|
|
2884
|
+
throw new ResolveError(
|
|
2885
|
+
"cannot concatenate object with array: value concatenation requires same-kind operands (S10.4)",
|
|
2886
|
+
"",
|
|
2887
|
+
line,
|
|
2888
|
+
col
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
2891
|
+
if (left.kind === "array" && right.kind === "array") {
|
|
2892
|
+
return { kind: "array", items: [...left.items, ...right.items] };
|
|
2893
|
+
}
|
|
2894
|
+
if (left.kind === "array" || right.kind === "array") {
|
|
2895
|
+
throw new ResolveError(
|
|
2896
|
+
`cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
|
|
2897
|
+
"",
|
|
2898
|
+
line,
|
|
2899
|
+
col
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
if (left.kind === "object" || right.kind === "object") {
|
|
2903
|
+
throw new ResolveError(
|
|
2904
|
+
`cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
|
|
2905
|
+
"",
|
|
2906
|
+
line,
|
|
2907
|
+
col
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
return { kind: "scalar", raw: left.raw + right.raw, valueType: "string" };
|
|
2911
|
+
};
|
|
2912
|
+
const [head, ...tail] = nonSep;
|
|
2913
|
+
const folded = tail.reduce(joinPair, head);
|
|
2914
|
+
if (folded.kind === "scalar") {
|
|
2915
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
2916
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
2917
|
+
}
|
|
2918
|
+
return folded;
|
|
2919
|
+
}
|
|
2920
|
+
};
|
|
2921
|
+
|
|
2922
|
+
// src/internal/resolver/resolver.ts
|
|
2923
|
+
function buildTree(ast, opts) {
|
|
2924
|
+
return new StructureBuilder(opts).build(ast);
|
|
2925
|
+
}
|
|
2926
|
+
function buildTreeAsync(ast, opts) {
|
|
2927
|
+
return new StructureBuilder(opts).buildAsync(ast);
|
|
2928
|
+
}
|
|
2929
|
+
function resolveTree(tree, opts) {
|
|
2930
|
+
return new SubstitutionResolver(tree, opts).resolve();
|
|
2931
|
+
}
|
|
2932
|
+
function containsPlaceholders(tree) {
|
|
2933
|
+
for (const val of tree.fields.values()) {
|
|
2934
|
+
if (valContainsPlaceholders(val)) return true;
|
|
2935
|
+
}
|
|
2936
|
+
return false;
|
|
2937
|
+
}
|
|
2938
|
+
function valContainsPlaceholders(v) {
|
|
2939
|
+
const rv = v;
|
|
2940
|
+
if (isSubst(rv) || isConcat(rv)) return true;
|
|
2941
|
+
if (isResObj(rv)) return containsPlaceholders(rv);
|
|
2942
|
+
const hv = v;
|
|
2943
|
+
if (_optionalChain([hv, 'optionalAccess', _32 => _32.kind]) === "array") {
|
|
2944
|
+
return hv.items.some((item) => valContainsPlaceholders(item));
|
|
2945
|
+
}
|
|
2946
|
+
if (_optionalChain([hv, 'optionalAccess', _33 => _33.kind]) === "object") {
|
|
2947
|
+
for (const field of hv.fields.values()) if (valContainsPlaceholders(field)) return true;
|
|
2948
|
+
}
|
|
2949
|
+
return false;
|
|
2950
|
+
}
|
|
2951
|
+
function buildPartialHoconFromResObj(tree) {
|
|
2952
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2953
|
+
for (const [k, v] of tree.fields) {
|
|
2954
|
+
if (!isSubst(v) && !isConcat(v)) {
|
|
2955
|
+
if (isResObj(v)) {
|
|
2956
|
+
fields.set(k, buildPartialHoconFromResObj(v));
|
|
2957
|
+
} else {
|
|
2958
|
+
if (!valContainsPlaceholders(v)) {
|
|
2959
|
+
fields.set(k, partialValue(v));
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
return { kind: "object", fields };
|
|
2965
|
+
}
|
|
2966
|
+
function partialValue(v) {
|
|
2967
|
+
if (isResObj(v)) return buildPartialHoconFromResObj(v);
|
|
2968
|
+
if (v.kind === "array") return { kind: "array", items: v.items.map((item) => partialValue(item)) };
|
|
2969
|
+
return v;
|
|
2970
|
+
}
|
|
2971
|
+
function hoconValueToResObj2(value) {
|
|
2972
|
+
const obj = makeResObj();
|
|
2973
|
+
for (const [k, v] of value.fields) {
|
|
2974
|
+
if (v.kind === "object") {
|
|
2975
|
+
obj.fields.set(k, hoconValueToResObj2(v));
|
|
2976
|
+
} else {
|
|
2977
|
+
obj.fields.set(k, v);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return obj;
|
|
2981
|
+
}
|
|
2982
|
+
function resolve2(ast, opts) {
|
|
2983
|
+
const tree = buildTree(ast, opts);
|
|
2984
|
+
return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
|
|
2985
|
+
}
|
|
2986
|
+
async function resolveAsync(ast, opts) {
|
|
2987
|
+
const root = await buildTreeAsync(ast, opts);
|
|
2988
|
+
return resolveTree(root, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
// src/render-hocon.ts
|
|
2992
|
+
function renderHoconFromRoot(root) {
|
|
2993
|
+
const out = [];
|
|
2994
|
+
renderObjectBody(out, root, 0);
|
|
2995
|
+
return out.join("");
|
|
2996
|
+
}
|
|
2997
|
+
function renderObjectBody(out, o, depth) {
|
|
2998
|
+
const indent = " ".repeat(depth);
|
|
2999
|
+
for (const [k, v] of o.fields) {
|
|
3000
|
+
out.push(indent, renderKey(k));
|
|
3001
|
+
if (v.kind === "object") {
|
|
3002
|
+
out.push(" {");
|
|
3003
|
+
if (v.fields.size === 0) {
|
|
3004
|
+
out.push("}\n");
|
|
3005
|
+
continue;
|
|
3006
|
+
}
|
|
3007
|
+
out.push("\n");
|
|
3008
|
+
renderObjectBody(out, v, depth + 1);
|
|
3009
|
+
out.push(indent, "}\n");
|
|
3010
|
+
} else {
|
|
3011
|
+
out.push(" = ");
|
|
3012
|
+
renderValue(out, v, depth);
|
|
3013
|
+
out.push("\n");
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
function renderValue(out, v, depth) {
|
|
3018
|
+
switch (v.kind) {
|
|
3019
|
+
case "object": {
|
|
3020
|
+
if (v.fields.size === 0) {
|
|
3021
|
+
out.push("{}");
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
out.push("{\n");
|
|
3025
|
+
renderObjectBody(out, v, depth + 1);
|
|
3026
|
+
out.push(" ".repeat(depth), "}");
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
3029
|
+
case "array":
|
|
3030
|
+
renderArray(out, v, depth);
|
|
3031
|
+
return;
|
|
3032
|
+
case "scalar":
|
|
3033
|
+
out.push(renderScalar(v));
|
|
3034
|
+
return;
|
|
3035
|
+
default:
|
|
3036
|
+
throw new ConfigError(
|
|
3037
|
+
"renderHocon: unrenderable value (config must be resolved data)",
|
|
3038
|
+
""
|
|
3039
|
+
);
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
function renderArray(out, a, depth) {
|
|
3043
|
+
if (a.items.length === 0) {
|
|
3044
|
+
out.push("[]");
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
const inner = " ".repeat(depth + 1);
|
|
3048
|
+
out.push("[\n");
|
|
3049
|
+
for (const e of a.items) {
|
|
3050
|
+
out.push(inner);
|
|
3051
|
+
renderValue(out, e, depth + 1);
|
|
3052
|
+
out.push("\n");
|
|
3053
|
+
}
|
|
3054
|
+
out.push(" ".repeat(depth), "]");
|
|
3055
|
+
}
|
|
3056
|
+
function renderScalar(s) {
|
|
3057
|
+
switch (s.valueType) {
|
|
3058
|
+
case "null":
|
|
3059
|
+
return "null";
|
|
3060
|
+
case "boolean":
|
|
3061
|
+
case "number":
|
|
3062
|
+
return s.raw;
|
|
3063
|
+
case "string":
|
|
3064
|
+
return renderString(s.raw);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
|
|
3068
|
+
function renderKey(k) {
|
|
3069
|
+
if (k === "include") return quoteString(k);
|
|
3070
|
+
return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
|
|
3071
|
+
}
|
|
3072
|
+
var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
|
|
3073
|
+
var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
|
|
3074
|
+
function renderString(s) {
|
|
3075
|
+
return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
|
|
3076
|
+
}
|
|
3077
|
+
function quoteString(s) {
|
|
3078
|
+
if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
|
|
3079
|
+
return `"""${s}"""`;
|
|
3080
|
+
}
|
|
3081
|
+
let b = '"';
|
|
3082
|
+
for (const ch of s) {
|
|
3083
|
+
switch (ch) {
|
|
3084
|
+
case '"':
|
|
3085
|
+
b += '\\"';
|
|
3086
|
+
break;
|
|
3087
|
+
case "\\":
|
|
3088
|
+
b += "\\\\";
|
|
3089
|
+
break;
|
|
3090
|
+
case "\n":
|
|
3091
|
+
b += "\\n";
|
|
3092
|
+
break;
|
|
3093
|
+
case "\r":
|
|
3094
|
+
b += "\\r";
|
|
3095
|
+
break;
|
|
3096
|
+
case " ":
|
|
3097
|
+
b += "\\t";
|
|
3098
|
+
break;
|
|
3099
|
+
default:
|
|
3100
|
+
b += ch;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
return b + '"';
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
// src/config.ts
|
|
3107
|
+
var Config = class _Config {
|
|
3108
|
+
constructor(root, opts) {
|
|
3109
|
+
this.root = root;
|
|
3110
|
+
this._resolved = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _34 => _34.resolved]), () => ( true));
|
|
3111
|
+
this._parseBaseDir = _optionalChain([opts, 'optionalAccess', _35 => _35.parseBaseDir]);
|
|
3112
|
+
this._originDescription = _optionalChain([opts, 'optionalAccess', _36 => _36.originDescription]);
|
|
3113
|
+
this._resObjRoot = _optionalChain([opts, 'optionalAccess', _37 => _37.resObjRoot]);
|
|
3114
|
+
this._pendingRoot = _optionalChain([opts, 'optionalAccess', _38 => _38.pendingRoot]);
|
|
3115
|
+
this._resolveOpts = _optionalChain([opts, 'optionalAccess', _39 => _39.resolveOpts]);
|
|
3116
|
+
}
|
|
3117
|
+
/** Returns true when the Config's value tree contains no unresolved
|
|
3118
|
+
* substitution placeholders. Whole-config granularity (E12 decision 11). */
|
|
3119
|
+
isResolved() {
|
|
3120
|
+
return this._resolved;
|
|
3121
|
+
}
|
|
3122
|
+
/** @internal Test-only: construct a resolved Config from a HoconValue. */
|
|
3123
|
+
static _fromResolvedValue(root, opts) {
|
|
3124
|
+
return new _Config(root, { resolved: true, originDescription: _optionalChain([opts, 'optionalAccess', _40 => _40.originDescription]) });
|
|
3125
|
+
}
|
|
3126
|
+
/** @internal Construct an unresolved Config carrying a ResObj tree.
|
|
3127
|
+
* Used by parseStringWithOptions (deferred path) in T6. */
|
|
3128
|
+
static _fromUnresolvedResObj(tree, opts) {
|
|
3129
|
+
const partialRoot = buildPartialHoconFromResObj(tree);
|
|
3130
|
+
return new _Config(partialRoot, {
|
|
3131
|
+
resolved: opts.resolved,
|
|
3132
|
+
parseBaseDir: opts.parseBaseDir,
|
|
3133
|
+
originDescription: opts.originDescription,
|
|
3134
|
+
resObjRoot: tree,
|
|
3135
|
+
resolveOpts: opts.resolveOpts
|
|
3136
|
+
});
|
|
3137
|
+
}
|
|
3138
|
+
get(path) {
|
|
3139
|
+
const v = this.lookupNode(path);
|
|
3140
|
+
if (v === void 0) return void 0;
|
|
3141
|
+
return hoconToJs(v);
|
|
3142
|
+
}
|
|
3143
|
+
/**
|
|
3144
|
+
* Returns the raw {@link HoconValue} node at `path` as a deeply-immutable
|
|
3145
|
+
* {@link ReadonlyHoconValue} view, or `undefined` if the path is absent. Unlike
|
|
3146
|
+
* `get` (which decodes to a plain JS value), this exposes the value tree for
|
|
3147
|
+
* structural introspection via the `value.ts` accessors (`asString`, `asObject`,
|
|
3148
|
+
* `isScalar`, …).
|
|
3149
|
+
*
|
|
3150
|
+
* `getValue('')` returns the root object node.
|
|
3151
|
+
*
|
|
3152
|
+
* Throws {@link NotResolvedError} when the node (or its subtree) is unresolved:
|
|
3153
|
+
* the `HoconValue` union has no placeholder variant, so an unresolved value
|
|
3154
|
+
* cannot be represented. This matches `getConfig` / `getList`.
|
|
3155
|
+
*/
|
|
3156
|
+
getValue(path) {
|
|
3157
|
+
const v = this.lookupNode(path);
|
|
3158
|
+
if (v === void 0) {
|
|
3159
|
+
if (!this._resolved && this._subtreeHasPlaceholders(path)) {
|
|
3160
|
+
throw new NotResolvedError(path);
|
|
3161
|
+
}
|
|
3162
|
+
return void 0;
|
|
3163
|
+
}
|
|
3164
|
+
if (v.kind !== "scalar" && !this._resolved && this._subtreeHasPlaceholders(path)) {
|
|
3165
|
+
throw new NotResolvedError(path);
|
|
3166
|
+
}
|
|
3167
|
+
return v;
|
|
3168
|
+
}
|
|
3169
|
+
/**
|
|
3170
|
+
* The scalar at `path` as its text: a number or boolean comes back as written
|
|
3171
|
+
* (`8080`, `true`).
|
|
3172
|
+
*
|
|
3173
|
+
* @throws {@link NotResolvedError} when the value is an unresolved substitution.
|
|
3174
|
+
* @throws {@link ConfigError} when `path` is missing or is not a scalar, or the value is
|
|
3175
|
+
* `null`.
|
|
3176
|
+
*/
|
|
3177
|
+
getString(path) {
|
|
3178
|
+
const v = this.requireScalar(path);
|
|
3179
|
+
if (v.valueType === "null") {
|
|
3180
|
+
throw new ConfigError(`expected string at ${path}, got ${v.valueType}`, path);
|
|
3181
|
+
}
|
|
3182
|
+
return v.raw;
|
|
3183
|
+
}
|
|
3184
|
+
/**
|
|
3185
|
+
* The scalar at `path` as a number. A string that reads as a decimal number
|
|
3186
|
+
* (`"8080"`, `"1.5e3"`) is accepted.
|
|
3187
|
+
*
|
|
3188
|
+
* @throws {@link NotResolvedError} when the value is an unresolved substitution.
|
|
3189
|
+
* @throws {@link ConfigError} when `path` is missing or is not a scalar, or the value is not a
|
|
3190
|
+
* number.
|
|
3191
|
+
*/
|
|
3192
|
+
getNumber(path) {
|
|
3193
|
+
const v = this.requireScalar(path);
|
|
3194
|
+
const coerced = coerceNumber(v.raw);
|
|
3195
|
+
if (coerced !== void 0) return coerced;
|
|
3196
|
+
throw new ConfigError(`expected number at ${path}, got ${v.valueType}`, path);
|
|
3197
|
+
}
|
|
3198
|
+
/**
|
|
3199
|
+
* The scalar at `path` as a boolean: `true` / `yes` / `on` and `false` /
|
|
3200
|
+
* `no` / `off`, in any case.
|
|
3201
|
+
*
|
|
3202
|
+
* @throws {@link NotResolvedError} when the value is an unresolved substitution.
|
|
3203
|
+
* @throws {@link ConfigError} when `path` is missing or is not a scalar, or the value is not one
|
|
3204
|
+
* of those words.
|
|
3205
|
+
*/
|
|
3206
|
+
getBoolean(path) {
|
|
3207
|
+
const v = this.requireScalar(path);
|
|
3208
|
+
const coerced = coerceBoolean(v.raw);
|
|
3209
|
+
if (coerced !== void 0) return coerced;
|
|
3210
|
+
throw new ConfigError(`expected boolean at ${path}, got ${v.valueType}`, path);
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* The duration at `path` in `unit` (default `ms`): a string with a unit
|
|
3214
|
+
* (`"30s"`, `"1.5h"`), or a number or unit-less string, which is
|
|
3215
|
+
* milliseconds. The number is read exactly and rounded once in `unit` (E20).
|
|
3216
|
+
* Negative durations are allowed.
|
|
3217
|
+
*
|
|
3218
|
+
* @throws {@link NotResolvedError} when the value is an unresolved substitution.
|
|
3219
|
+
* @throws {@link ConfigError} when `path` is missing or is not a string or number, the text is
|
|
3220
|
+
* not a duration, or the result is not a finite number (E21).
|
|
3221
|
+
*/
|
|
3222
|
+
getDuration(path, unit) {
|
|
3223
|
+
const v = this.requireScalar(path);
|
|
3224
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
3225
|
+
throw new ConfigError(`expected duration at ${path}, got ${v.valueType}`, path);
|
|
3226
|
+
}
|
|
3227
|
+
const reading = readDuration(v.raw, unit);
|
|
3228
|
+
if (reading.ok) return reading.value;
|
|
3229
|
+
switch (reading.problem) {
|
|
3230
|
+
case "invalid":
|
|
3231
|
+
throw new ConfigError(`invalid duration at ${path}: ${JSON.stringify(v.raw)}`, path);
|
|
3232
|
+
case "overflow":
|
|
3233
|
+
throw new ConfigError(`duration overflows the representable range at ${path}: ${JSON.stringify(v.raw)}`, path);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
/**
|
|
3237
|
+
* The byte size at `path` in `unit` (default `B`): a string with a unit
|
|
3238
|
+
* (`"64KiB"`, `"1.5MB"`), or a number or unit-less string, which is bytes.
|
|
3239
|
+
* The exact size is truncated toward zero to whole bytes, then converted to
|
|
3240
|
+
* `unit` (E20).
|
|
3241
|
+
*
|
|
3242
|
+
* @throws {@link NotResolvedError} when the value is an unresolved substitution.
|
|
3243
|
+
* @throws {@link ConfigError} when `path` is missing or is not a string or number, the text is
|
|
3244
|
+
* not a byte size, the size is negative, or it exceeds 2^53-1 bytes (the
|
|
3245
|
+
* largest integer a JS number holds exactly).
|
|
3246
|
+
*/
|
|
3247
|
+
getBytes(path, unit) {
|
|
3248
|
+
const v = this.requireScalar(path);
|
|
3249
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
3250
|
+
throw new ConfigError(`expected byte size at ${path}, got ${v.valueType}`, path);
|
|
3251
|
+
}
|
|
3252
|
+
const reading = readBytes(v.raw, unit);
|
|
3253
|
+
if (reading.ok) return reading.value;
|
|
3254
|
+
switch (reading.problem) {
|
|
3255
|
+
case "invalid":
|
|
3256
|
+
throw new ConfigError(`invalid byte size at ${path}: ${JSON.stringify(v.raw)}`, path);
|
|
3257
|
+
case "negative":
|
|
3258
|
+
throw new ConfigError(`byte size must be non-negative at ${path}: ${JSON.stringify(v.raw)}`, path);
|
|
3259
|
+
case "overflow":
|
|
3260
|
+
throw new ConfigError(
|
|
3261
|
+
`byte size overflows the representable range at ${path}: ${JSON.stringify(v.raw)}`,
|
|
3262
|
+
path
|
|
3263
|
+
);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
/**
|
|
3267
|
+
* The calendar period at `path` (`7d`, `2w`, `3m`, `1y`; a bare integer is
|
|
3268
|
+
* days). Integer only, unlike {@link getDuration} / {@link getBytes}.
|
|
3269
|
+
*
|
|
3270
|
+
* @throws {@link NotResolvedError} when the value is an unresolved substitution.
|
|
3271
|
+
* @throws {@link ConfigError} when `path` is missing or is not a string or number, or the text
|
|
3272
|
+
* is not a period.
|
|
3273
|
+
*/
|
|
3274
|
+
getPeriod(path) {
|
|
3275
|
+
const v = this.requireScalar(path);
|
|
3276
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
3277
|
+
throw new ConfigError(`expected period at ${path}, got ${v.valueType}`, path);
|
|
3278
|
+
}
|
|
3279
|
+
const reading = readPeriod(v.raw);
|
|
3280
|
+
if (reading.ok) return reading.value;
|
|
3281
|
+
switch (reading.problem) {
|
|
3282
|
+
case "invalid":
|
|
3283
|
+
throw new ConfigError(`invalid period at ${path}: ${JSON.stringify(v.raw)}`, path);
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
/**
|
|
3287
|
+
* The object at `path` as a Config of its own, with paths relative to it.
|
|
3288
|
+
*
|
|
3289
|
+
* @throws {@link NotResolvedError} when the object holds an unresolved substitution.
|
|
3290
|
+
* @throws {@link ConfigError} when `path` is missing or is not an object.
|
|
3291
|
+
*/
|
|
3292
|
+
getConfig(path) {
|
|
3293
|
+
const v = this.lookupNode(path);
|
|
3294
|
+
if (v === void 0) {
|
|
3295
|
+
if (!this._resolved && this._subtreeHasPlaceholders(path)) {
|
|
3296
|
+
throw new NotResolvedError(path);
|
|
3297
|
+
}
|
|
3298
|
+
throw new ConfigError(`path not found: ${path}`, path);
|
|
3299
|
+
}
|
|
3300
|
+
if (v.kind !== "object") throw new ConfigError(`expected object at ${path}`, path);
|
|
3301
|
+
if (!this._resolved && this._subtreeHasPlaceholders(path)) {
|
|
3302
|
+
throw new NotResolvedError(path);
|
|
3303
|
+
}
|
|
3304
|
+
return new _Config(v, { resolved: true });
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* The list at `path` as plain values (see {@link get}). An object with integer
|
|
3308
|
+
* keys (`"0"`, `"1"`, …) is read as a list too, in key order with gaps closed
|
|
3309
|
+
* up; its other keys are ignored (S15).
|
|
3310
|
+
*
|
|
3311
|
+
* @throws {@link NotResolvedError} when the list holds an unresolved substitution.
|
|
3312
|
+
* @throws {@link ConfigError} when `path` is missing or is not a list.
|
|
3313
|
+
*/
|
|
3314
|
+
getList(path) {
|
|
3315
|
+
const v = this.lookupNode(path);
|
|
3316
|
+
if (v === void 0) {
|
|
3317
|
+
if (!this._resolved && this._subtreeHasPlaceholders(path)) {
|
|
3318
|
+
throw new NotResolvedError(path);
|
|
3319
|
+
}
|
|
3320
|
+
throw new ConfigError(`path not found: ${path}`, path);
|
|
3321
|
+
}
|
|
3322
|
+
if (!this._resolved && this._subtreeHasPlaceholders(path)) {
|
|
3323
|
+
throw new NotResolvedError(path);
|
|
3324
|
+
}
|
|
3325
|
+
if (v.kind === "object") {
|
|
3326
|
+
const converted = numericObjectToArray(v);
|
|
3327
|
+
if (converted !== null) return converted.map((item) => hoconToJs(item));
|
|
3328
|
+
}
|
|
3329
|
+
if (v.kind !== "array") throw new ConfigError(`expected array at ${path}`, path);
|
|
3330
|
+
return v.items.map((item) => hoconToJs(item));
|
|
3331
|
+
}
|
|
3332
|
+
/**
|
|
3333
|
+
* Whether `path` is present. A path whose value is still an unresolved
|
|
3334
|
+
* substitution is present too (`true`, no throw), as in go.hocon; reading it
|
|
3335
|
+
* throws {@link NotResolvedError}. A path *under* such a value is not known
|
|
3336
|
+
* yet and answers `false`.
|
|
3337
|
+
*/
|
|
3338
|
+
has(path) {
|
|
3339
|
+
if (this.lookupNode(path) !== void 0) return true;
|
|
3340
|
+
return !this._resolved && this._unresolvedTreeHas(path);
|
|
3341
|
+
}
|
|
3342
|
+
keys() {
|
|
3343
|
+
return [...this.root.fields.keys()];
|
|
3344
|
+
}
|
|
3345
|
+
withFallback(fallback) {
|
|
3346
|
+
if (fallback == null) return this;
|
|
3347
|
+
const selfTree = _nullishCoalesce(this._resObjRoot, () => ( hoconValueToResObj2(this.root)));
|
|
3348
|
+
const fbTree = _nullishCoalesce(fallback._resObjRoot, () => ( hoconValueToResObj2(fallback.root)));
|
|
3349
|
+
const merged = mergeUnresolved(selfTree, fbTree);
|
|
3350
|
+
const hasPlaceholders = containsPlaceholders(merged);
|
|
3351
|
+
const partialRoot = buildPartialHoconFromResObj(merged);
|
|
3352
|
+
return new _Config(partialRoot, {
|
|
3353
|
+
resolved: !hasPlaceholders,
|
|
3354
|
+
parseBaseDir: this._parseBaseDir,
|
|
3355
|
+
originDescription: this._originDescription,
|
|
3356
|
+
resObjRoot: merged,
|
|
3357
|
+
resolveOpts: _nullishCoalesce(this._resolveOpts, () => ( fallback._resolveOpts))
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
/**
|
|
3361
|
+
* Runs substitution resolution (phase 2) on the stored unresolved tree.
|
|
3362
|
+
* Idempotent: calling resolve() on an already-resolved Config returns an
|
|
3363
|
+
* equivalent resolved Config.
|
|
3364
|
+
*
|
|
3365
|
+
* opts.allowUnresolved (default false) — when true, leaves unresolved
|
|
3366
|
+
* non-optional placeholders in place rather than throwing.
|
|
3367
|
+
* opts.useSystemEnvironment (default true) — when false, env var lookups
|
|
3368
|
+
* are suppressed (hermetic resolution).
|
|
3369
|
+
*
|
|
3370
|
+
* E12 decision 3.
|
|
3371
|
+
*/
|
|
3372
|
+
resolve(opts = {}) {
|
|
3373
|
+
if (this._resolved) {
|
|
3374
|
+
return new _Config(this.root, {
|
|
3375
|
+
resolved: true,
|
|
3376
|
+
parseBaseDir: this._parseBaseDir,
|
|
3377
|
+
originDescription: this._originDescription
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
const tree = this._resObjRoot;
|
|
3381
|
+
if (!tree) {
|
|
3382
|
+
return new _Config(this.root, { resolved: true });
|
|
3383
|
+
}
|
|
3384
|
+
const resolveOpts = {
|
|
3385
|
+
..._nullishCoalesce(this._resolveOpts, () => ( { env: {}, baseDir: void 0, readFileSync: () => {
|
|
3386
|
+
throw new Error("no files");
|
|
3387
|
+
} })),
|
|
3388
|
+
allowUnresolved: _nullishCoalesce(opts.allowUnresolved, () => ( false)),
|
|
3389
|
+
useSystemEnvironment: _nullishCoalesce(opts.useSystemEnvironment, () => ( true)),
|
|
3390
|
+
originDescription: this._originDescription
|
|
3391
|
+
};
|
|
3392
|
+
const resolved = resolveTree(tree, resolveOpts);
|
|
3393
|
+
if (resolved.kind !== "object") throw new Error("resolve: expected object root");
|
|
3394
|
+
if (opts.allowUnresolved) {
|
|
3395
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(resolved);
|
|
3396
|
+
return new _Config(stripped, {
|
|
3397
|
+
resolved: !hadPlaceholders,
|
|
3398
|
+
parseBaseDir: this._parseBaseDir,
|
|
3399
|
+
originDescription: this._originDescription,
|
|
3400
|
+
resObjRoot: hadPlaceholders ? tree : void 0,
|
|
3401
|
+
pendingRoot: hadPlaceholders ? hoconValueToResObj2(resolved) : void 0,
|
|
3402
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
return new _Config(resolved, {
|
|
3406
|
+
resolved: true,
|
|
3407
|
+
parseBaseDir: this._parseBaseDir,
|
|
3408
|
+
originDescription: this._originDescription
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
/**
|
|
3412
|
+
* Resolves receiver substitutions using source as lookup context.
|
|
3413
|
+
* Source's keys are NOT merged into the result — differs from
|
|
3414
|
+
* `this.withFallback(source).resolve()` which includes source keys.
|
|
3415
|
+
*
|
|
3416
|
+
* Precondition (E12 decision 10): source must be resolved. If
|
|
3417
|
+
* source.isResolved() is false, throws NotResolvedError.
|
|
3418
|
+
*
|
|
3419
|
+
* On an already-resolved receiver, resolveWith is a no-op (returns an
|
|
3420
|
+
* equivalent resolved Config, source keys not included).
|
|
3421
|
+
*
|
|
3422
|
+
* E12 decisions 9, 10.
|
|
3423
|
+
*/
|
|
3424
|
+
resolveWith(source, opts = {}) {
|
|
3425
|
+
if (!source.isResolved()) {
|
|
3426
|
+
throw new NotResolvedError("source");
|
|
3427
|
+
}
|
|
3428
|
+
if (this._resolved) {
|
|
3429
|
+
return new _Config(this.root, {
|
|
3430
|
+
resolved: true,
|
|
3431
|
+
parseBaseDir: this._parseBaseDir,
|
|
3432
|
+
originDescription: this._originDescription
|
|
3433
|
+
});
|
|
3434
|
+
}
|
|
3435
|
+
const receiverTree = _nullishCoalesce(this._resObjRoot, () => ( hoconValueToResObj2(this.root)));
|
|
3436
|
+
const srcTree = hoconValueToResObj2(source.root);
|
|
3437
|
+
const merged = mergeUnresolved(receiverTree, srcTree);
|
|
3438
|
+
const resolveOpts = {
|
|
3439
|
+
..._nullishCoalesce(this._resolveOpts, () => ( { env: {}, baseDir: void 0, readFileSync: () => {
|
|
3440
|
+
throw new Error("no files");
|
|
3441
|
+
} })),
|
|
3442
|
+
allowUnresolved: _nullishCoalesce(opts.allowUnresolved, () => ( false)),
|
|
3443
|
+
useSystemEnvironment: _nullishCoalesce(opts.useSystemEnvironment, () => ( true)),
|
|
3444
|
+
originDescription: this._originDescription
|
|
3445
|
+
};
|
|
3446
|
+
const resolved = resolveTree(merged, resolveOpts);
|
|
3447
|
+
if (resolved.kind !== "object") throw new Error("resolveWith: expected object root");
|
|
3448
|
+
const receiverShapeForFilter = resObjToKeyShape(receiverTree);
|
|
3449
|
+
const filtered = filterByReceiverShape(resolved, receiverShapeForFilter);
|
|
3450
|
+
if (opts.allowUnresolved) {
|
|
3451
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(filtered);
|
|
3452
|
+
return new _Config(stripped, {
|
|
3453
|
+
resolved: !hadPlaceholders,
|
|
3454
|
+
parseBaseDir: this._parseBaseDir,
|
|
3455
|
+
originDescription: this._originDescription,
|
|
3456
|
+
resObjRoot: hadPlaceholders ? receiverTree : void 0,
|
|
3457
|
+
pendingRoot: hadPlaceholders ? hoconValueToResObj2(filtered) : void 0,
|
|
3458
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
3459
|
+
});
|
|
3460
|
+
}
|
|
3461
|
+
return new _Config(filtered, {
|
|
3462
|
+
resolved: true,
|
|
3463
|
+
parseBaseDir: this._parseBaseDir,
|
|
3464
|
+
originDescription: this._originDescription
|
|
3465
|
+
});
|
|
3466
|
+
}
|
|
3467
|
+
/**
|
|
3468
|
+
* The whole config as plain JS data: objects, arrays, strings, numbers,
|
|
3469
|
+
* booleans and `null`.
|
|
3470
|
+
*
|
|
3471
|
+
* Keys are reproduced exactly as the config carries them, `__proto__`,
|
|
3472
|
+
* `constructor` and `prototype` included: each is defined as an **own data
|
|
3473
|
+
* property**, so the returned object's prototype is always
|
|
3474
|
+
* `Object.prototype`, and this call writes nothing to the global one.
|
|
3475
|
+
*
|
|
3476
|
+
* ### Passing the result to other code
|
|
3477
|
+
*
|
|
3478
|
+
* A config file — especially a `.properties` file or an env namespace you
|
|
3479
|
+
* merged in — can therefore hand you an own `__proto__` key, and what happens
|
|
3480
|
+
* next depends entirely on how the consumer copies it:
|
|
3481
|
+
*
|
|
3482
|
+
* ```ts
|
|
3483
|
+
* const data = cfg.toObject()
|
|
3484
|
+
* { ...data } // safe — own properties are copied as data
|
|
3485
|
+
* structuredClone(data) // safe
|
|
3486
|
+
* Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
|
|
3487
|
+
* // Object.prototype's __proto__ setter: the key
|
|
3488
|
+
* // vanishes and config data becomes the result's
|
|
3489
|
+
* // prototype
|
|
3490
|
+
* deepMerge({}, data) // NOT safe — a naive recursive merge reads
|
|
3491
|
+
* // target['__proto__'], gets Object.prototype,
|
|
3492
|
+
* // and writes to it: global pollution
|
|
3493
|
+
* for (const k in data) … // prefer Object.entries / Object.keys
|
|
3494
|
+
* ```
|
|
3495
|
+
*
|
|
3496
|
+
* Those two hazards come from the **destination** object's prototype, so no
|
|
3497
|
+
* option here can disarm them — copy with spread or `structuredClone` instead.
|
|
3498
|
+
*
|
|
3499
|
+
* What `{ nullPrototype: true }` does give you is a result that inherits
|
|
3500
|
+
* nothing: every object comes back with a `null` prototype, so reading
|
|
3501
|
+
* `x.__proto__`, `x.constructor` or `x.toString` yields config data or
|
|
3502
|
+
* `undefined` rather than something from `Object.prototype`, and code that
|
|
3503
|
+
* merges *into* this object cannot climb out of it. Keys are all still
|
|
3504
|
+
* present. The trade-off is the usual one for prototype-less objects: not
|
|
3505
|
+
* `instanceof Object`, and no `hasOwnProperty` of their own.
|
|
3506
|
+
*
|
|
3507
|
+
* Numbers follow the JS number model, so an integer wider than 2^53 rounds
|
|
3508
|
+
* here even when the config holds it exactly — read those with
|
|
3509
|
+
* {@link Config.getString} instead.
|
|
3510
|
+
*/
|
|
3511
|
+
toObject(opts) {
|
|
3512
|
+
return hoconToJs(this.root, _optionalChain([opts, 'optionalAccess', _41 => _41.nullPrototype]) === true);
|
|
3513
|
+
}
|
|
3514
|
+
/**
|
|
3515
|
+
* Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
|
|
3516
|
+
* `Config.RenderHOCON`, v1.11.0).
|
|
3517
|
+
*
|
|
3518
|
+
* The output round-trips: parsing it back yields the same value tree. That
|
|
3519
|
+
* is the correctness contract, not byte-for-byte formatting — a scalar is
|
|
3520
|
+
* quoted whenever leaving it bare would re-parse as a different type (a
|
|
3521
|
+
* string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
|
|
3522
|
+
* provably cannot.
|
|
3523
|
+
*
|
|
3524
|
+
* The Config must be resolved and hold only data (objects, arrays, string /
|
|
3525
|
+
* number / boolean / null scalars) — exactly what {@link fromMap} and the
|
|
3526
|
+
* format adapters produce. An unresolved placeholder throws
|
|
3527
|
+
* {@link ConfigError}; substitutions have no textual round trip through a
|
|
3528
|
+
* value tree.
|
|
3529
|
+
*
|
|
3530
|
+
* The root object's fields are emitted without enclosing braces, nested
|
|
3531
|
+
* objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
|
|
3532
|
+
* spaces. Source comments are not represented — a value tree does not carry
|
|
3533
|
+
* them.
|
|
3534
|
+
*/
|
|
3535
|
+
renderHocon() {
|
|
3536
|
+
if (!this._resolved) {
|
|
3537
|
+
throw new ConfigError(
|
|
3538
|
+
"renderHocon: unrenderable unresolved substitution (config must be resolved data)",
|
|
3539
|
+
""
|
|
3540
|
+
);
|
|
3541
|
+
}
|
|
3542
|
+
return renderHoconFromRoot(this.root);
|
|
3543
|
+
}
|
|
3544
|
+
/**
|
|
3545
|
+
* @internal Test-only: renders this resolved Config's value tree as canonical
|
|
3546
|
+
* JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
|
|
3547
|
+
* against Lightbend ground truth. NOT part of the public API.
|
|
3548
|
+
*
|
|
3549
|
+
* Throws if the Config is unresolved (contains placeholders).
|
|
3550
|
+
*/
|
|
3551
|
+
_renderJSONForTest() {
|
|
3552
|
+
return renderHoconAsJSON(this.root);
|
|
3553
|
+
}
|
|
3554
|
+
lookupNode(path) {
|
|
3555
|
+
const segments = splitConfigPath(path);
|
|
3556
|
+
let current = this.root;
|
|
3557
|
+
for (const seg of segments) {
|
|
3558
|
+
if (current.kind !== "object") return void 0;
|
|
3559
|
+
const next = current.fields.get(seg);
|
|
3560
|
+
if (next === void 0) return void 0;
|
|
3561
|
+
current = next;
|
|
3562
|
+
}
|
|
3563
|
+
return current;
|
|
3564
|
+
}
|
|
3565
|
+
/** Whether `path` names a field in the unresolved tree (a placeholder included). */
|
|
3566
|
+
_unresolvedTreeHas(path) {
|
|
3567
|
+
let cur = _nullishCoalesce(this._pendingRoot, () => ( this._resObjRoot));
|
|
3568
|
+
const segments = splitConfigPath(path);
|
|
3569
|
+
for (let i = 0; cur !== void 0 && i < segments.length; i++) {
|
|
3570
|
+
const val = cur.fields.get(segments[i]);
|
|
3571
|
+
if (val === void 0) return false;
|
|
3572
|
+
if (i === segments.length - 1) return true;
|
|
3573
|
+
cur = isResObj(val) ? val : void 0;
|
|
3574
|
+
}
|
|
3575
|
+
return false;
|
|
3576
|
+
}
|
|
3577
|
+
/**
|
|
3578
|
+
* _subtreeHasPlaceholders — walk the unresolved tree (`_pendingRoot` after a
|
|
3579
|
+
* partial resolve, else `_resObjRoot`) to the given path and return true if
|
|
3580
|
+
* the subtree there contains any unresolved placeholder.
|
|
3581
|
+
* Used by getConfig / getList (T2) to detect transitive unresolved placeholders
|
|
3582
|
+
* in object / array subtrees that would otherwise appear to be "resolved" in the
|
|
3583
|
+
* partial HoconValue root (because individual leaf fields are omitted rather than
|
|
3584
|
+
* the whole subtree when their value is a direct placeholder scalar, but object
|
|
3585
|
+
* sub-containers are retained in the partial root with the placeholder-keyed
|
|
3586
|
+
* fields omitted from them — so the object appears "present but empty-ish").
|
|
3587
|
+
*/
|
|
3588
|
+
_subtreeHasPlaceholders(path) {
|
|
3589
|
+
const tree = _nullishCoalesce(this._pendingRoot, () => ( this._resObjRoot));
|
|
3590
|
+
if (!tree) return false;
|
|
3591
|
+
const segments = splitConfigPath(path);
|
|
3592
|
+
let cur = tree;
|
|
3593
|
+
for (const seg of segments) {
|
|
3594
|
+
const val = cur.fields.get(seg);
|
|
3595
|
+
if (val === void 0) return false;
|
|
3596
|
+
if (isResObj(val)) {
|
|
3597
|
+
cur = val;
|
|
3598
|
+
} else {
|
|
3599
|
+
return valContainsPlaceholders(val);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
return containsPlaceholders(cur);
|
|
3603
|
+
}
|
|
3604
|
+
requireScalar(path) {
|
|
3605
|
+
const v = this.lookupNode(path);
|
|
3606
|
+
if (v === void 0) {
|
|
3607
|
+
if (!this._resolved && this._subtreeHasPlaceholders(path)) {
|
|
3608
|
+
throw new NotResolvedError(path);
|
|
3609
|
+
}
|
|
3610
|
+
throw new ConfigError(`path not found: ${path}`, path);
|
|
3611
|
+
}
|
|
3612
|
+
if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path}, got ${v.kind}`, path);
|
|
3613
|
+
return v;
|
|
3614
|
+
}
|
|
3615
|
+
};
|
|
3616
|
+
function splitConfigPath(path) {
|
|
3617
|
+
const segments = [];
|
|
3618
|
+
let i = 0;
|
|
3619
|
+
while (i < path.length) {
|
|
3620
|
+
if (path[i] === '"') {
|
|
3621
|
+
i++;
|
|
3622
|
+
let segment = "";
|
|
3623
|
+
let closed = false;
|
|
3624
|
+
while (i < path.length) {
|
|
3625
|
+
const ch = path[i];
|
|
3626
|
+
if (ch === "\\" && i + 1 < path.length) {
|
|
3627
|
+
const next = path[i + 1];
|
|
3628
|
+
segment += next;
|
|
3629
|
+
i += 2;
|
|
3630
|
+
continue;
|
|
3631
|
+
}
|
|
3632
|
+
if (ch === '"') {
|
|
3633
|
+
closed = true;
|
|
3634
|
+
i++;
|
|
3635
|
+
break;
|
|
3636
|
+
}
|
|
3637
|
+
segment += ch;
|
|
3638
|
+
i++;
|
|
3639
|
+
}
|
|
3640
|
+
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path}`, path);
|
|
3641
|
+
segments.push(segment);
|
|
3642
|
+
if (i < path.length && path[i] === ".") i++;
|
|
3643
|
+
} else {
|
|
3644
|
+
const dot = path.indexOf(".", i);
|
|
3645
|
+
if (dot === -1) {
|
|
3646
|
+
segments.push(path.slice(i));
|
|
3647
|
+
break;
|
|
3648
|
+
}
|
|
3649
|
+
segments.push(path.slice(i, dot));
|
|
3650
|
+
i = dot + 1;
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
return segments;
|
|
3654
|
+
}
|
|
3655
|
+
function scalarToJs(raw, valueType) {
|
|
3656
|
+
switch (valueType) {
|
|
3657
|
+
case "null":
|
|
3658
|
+
return null;
|
|
3659
|
+
case "boolean":
|
|
3660
|
+
return raw === "true";
|
|
3661
|
+
case "number":
|
|
3662
|
+
return Number(raw);
|
|
3663
|
+
case "string":
|
|
3664
|
+
return raw;
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
function hoconToJs(v, nullPrototype = false) {
|
|
3668
|
+
switch (v.kind) {
|
|
3669
|
+
case "scalar":
|
|
3670
|
+
return scalarToJs(v.raw, v.valueType);
|
|
3671
|
+
case "array":
|
|
3672
|
+
return v.items.map((item) => hoconToJs(item, nullPrototype));
|
|
3673
|
+
case "object": {
|
|
3674
|
+
const entries = Array.from(v.fields, ([k, val]) => [k, hoconToJs(val, nullPrototype)]);
|
|
3675
|
+
if (nullPrototype) {
|
|
3676
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
3677
|
+
for (const [k, val] of entries) Object.defineProperty(obj, k, {
|
|
3678
|
+
value: val,
|
|
3679
|
+
writable: true,
|
|
3680
|
+
enumerable: true,
|
|
3681
|
+
configurable: true
|
|
3682
|
+
});
|
|
3683
|
+
return obj;
|
|
3684
|
+
}
|
|
3685
|
+
return Object.fromEntries(entries);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
function resObjToKeyShape(tree) {
|
|
3690
|
+
const fields = /* @__PURE__ */ new Map();
|
|
3691
|
+
for (const [k, v] of tree.fields) {
|
|
3692
|
+
if (isSubst(v) || isConcat(v)) {
|
|
3693
|
+
fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
|
|
3694
|
+
} else if (isResObj(v)) {
|
|
3695
|
+
fields.set(k, resObjToKeyShape(v));
|
|
3696
|
+
} else {
|
|
3697
|
+
fields.set(k, v);
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
return { kind: "object", fields };
|
|
3701
|
+
}
|
|
3702
|
+
function stripPlaceholderFields(v) {
|
|
3703
|
+
const fields = /* @__PURE__ */ new Map();
|
|
3704
|
+
let hadPlaceholders = false;
|
|
3705
|
+
for (const [k, val] of v.fields) {
|
|
3706
|
+
if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder") {
|
|
3707
|
+
hadPlaceholders = true;
|
|
3708
|
+
} else if (val.kind === "object") {
|
|
3709
|
+
const inner = stripPlaceholderFields(val);
|
|
3710
|
+
if (inner.hadPlaceholders) hadPlaceholders = true;
|
|
3711
|
+
fields.set(k, inner.stripped);
|
|
3712
|
+
} else if (val.kind === "array" && valContainsPlaceholders(val)) {
|
|
3713
|
+
hadPlaceholders = true;
|
|
3714
|
+
} else {
|
|
3715
|
+
fields.set(k, val);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
return { stripped: { kind: "object", fields }, hadPlaceholders };
|
|
3719
|
+
}
|
|
3720
|
+
function filterByReceiverShape(resolved, receiverShape) {
|
|
3721
|
+
const fields = /* @__PURE__ */ new Map();
|
|
3722
|
+
for (const [k, rv] of resolved.fields) {
|
|
3723
|
+
if (!receiverShape.fields.has(k)) continue;
|
|
3724
|
+
const receiverVal = receiverShape.fields.get(k);
|
|
3725
|
+
if (rv.kind === "object" && receiverVal.kind === "object") {
|
|
3726
|
+
fields.set(k, filterByReceiverShape(rv, receiverVal));
|
|
3727
|
+
} else {
|
|
3728
|
+
fields.set(k, rv);
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
return { kind: "object", fields };
|
|
3732
|
+
}
|
|
3733
|
+
function renderHoconAsJSON(v) {
|
|
3734
|
+
switch (v.kind) {
|
|
3735
|
+
case "scalar": {
|
|
3736
|
+
switch (v.valueType) {
|
|
3737
|
+
case "null":
|
|
3738
|
+
return "null";
|
|
3739
|
+
case "boolean":
|
|
3740
|
+
return v.raw;
|
|
3741
|
+
case "number": {
|
|
3742
|
+
const norm = completeJsonNumber(normalizeLeadingZeroNumber(v.raw));
|
|
3743
|
+
return JSON_NUMBER_RE.test(norm) ? norm : JSON.stringify(v.raw);
|
|
3744
|
+
}
|
|
3745
|
+
case "string":
|
|
3746
|
+
return JSON.stringify(v.raw);
|
|
3747
|
+
}
|
|
3748
|
+
break;
|
|
3749
|
+
}
|
|
3750
|
+
case "array": {
|
|
3751
|
+
const items = v.items.map(renderHoconAsJSON);
|
|
3752
|
+
return `[${items.join(",")}]`;
|
|
3753
|
+
}
|
|
3754
|
+
case "object": {
|
|
3755
|
+
const keys = [...v.fields.keys()].sort();
|
|
3756
|
+
const pairs = keys.map((k) => {
|
|
3757
|
+
const val = v.fields.get(k);
|
|
3758
|
+
return `${JSON.stringify(k)}:${renderHoconAsJSON(val)}`;
|
|
3759
|
+
});
|
|
3760
|
+
return `{${pairs.join(",")}}`;
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
throw new Error(`renderHoconAsJSON: unsupported value kind (unresolved placeholder?)`);
|
|
3764
|
+
}
|
|
3765
|
+
var JSON_NUMBER_RE = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
3766
|
+
function normalizeLeadingZeroNumber(raw) {
|
|
3767
|
+
return raw.replace(/^([+-]?)0+(\d)/, "$1$2");
|
|
3768
|
+
}
|
|
3769
|
+
function completeJsonNumber(raw) {
|
|
3770
|
+
return raw.replace(/^([+-]?)\./, "$10.").replace(/\.(?=[eE]|$)/, ".0");
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
|
|
3774
|
+
|
|
3775
|
+
|
|
3776
|
+
|
|
3777
|
+
|
|
3778
|
+
|
|
3779
|
+
|
|
3780
|
+
|
|
3781
|
+
|
|
3782
|
+
|
|
3783
|
+
|
|
3784
|
+
|
|
3785
|
+
|
|
3786
|
+
|
|
3787
|
+
|
|
3788
|
+
|
|
3789
|
+
|
|
3790
|
+
|
|
3791
|
+
|
|
3792
|
+
|
|
3793
|
+
|
|
3794
|
+
|
|
3795
|
+
|
|
3796
|
+
|
|
3797
|
+
|
|
3798
|
+
|
|
3799
|
+
|
|
3800
|
+
|
|
3801
|
+
exports.coerceBoolean = coerceBoolean; exports.coerceNumber = coerceNumber; exports.readDuration = readDuration; exports.readBytes = readBytes; exports.readPeriod = readPeriod; exports.ParseError = ParseError; exports.ResolveError = ResolveError; exports.PackageLookupError = PackageLookupError; exports.ConfigError = ConfigError; exports.NotResolvedError = NotResolvedError; exports.tokenize = tokenize; exports.parseTokens = parseTokens; exports.stripBom = stripBom; exports.MAX_PATH_SEGMENTS = MAX_PATH_SEGMENTS; exports.tooDeep = tooDeep; exports.guardStackDepth = guardStackDepth; exports.depthError = depthError; exports.guardStackDepthAsync = guardStackDepthAsync; exports.parseProperties = parseProperties; exports.nestPairs = nestPairs; exports.pathKey = pathKey; exports.buildTree = buildTree; exports.buildTreeAsync = buildTreeAsync; exports.containsPlaceholders = containsPlaceholders; exports.resolve = resolve2; exports.resolveAsync = resolveAsync; exports.Config = Config;
|