@o3co/ts.hocon 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +4 -2
- package/README.md +59 -8
- package/dist/config-BwjvKeZ5.d.cts +271 -0
- package/dist/config-BwjvKeZ5.d.ts +271 -0
- package/dist/index.cjs +1379 -400
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +1368 -399
- package/dist/zod.cjs +1112 -253
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +1111 -253
- package/package.json +3 -1
- package/dist/parse-nNbyqnOh.d.cts +0 -53
- package/dist/parse-nNbyqnOh.d.ts +0 -53
package/dist/index.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
// src/coerce.ts
|
|
2
2
|
var DECIMAL_NUMBER_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
3
|
+
function isHoconWs(cp) {
|
|
4
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
5
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
6
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
7
|
+
if (cp === 5760) return true;
|
|
8
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
9
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
10
|
+
if (cp === 12288) return true;
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
function trimHoconWs(s) {
|
|
14
|
+
let start = 0;
|
|
15
|
+
let end = s.length;
|
|
16
|
+
while (start < end && isHoconWs(s.charCodeAt(start))) {
|
|
17
|
+
start++;
|
|
18
|
+
}
|
|
19
|
+
while (end > start && isHoconWs(s.charCodeAt(end - 1))) {
|
|
20
|
+
end--;
|
|
21
|
+
}
|
|
22
|
+
return s.slice(start, end);
|
|
23
|
+
}
|
|
3
24
|
var TRUTHY = /* @__PURE__ */ new Set(["true", "yes", "on"]);
|
|
4
25
|
var FALSY = /* @__PURE__ */ new Set(["false", "no", "off"]);
|
|
5
26
|
function coerceBoolean(value) {
|
|
@@ -45,22 +66,29 @@ var OUTPUT_DURATION_UNITS = {
|
|
|
45
66
|
d: 864e5
|
|
46
67
|
};
|
|
47
68
|
function parseDuration(value, outputUnit = "ms") {
|
|
48
|
-
const trimmed = value
|
|
69
|
+
const trimmed = trimHoconWs(value);
|
|
49
70
|
let i = 0;
|
|
50
71
|
while (i < trimmed.length) {
|
|
51
72
|
const ch = trimmed[i];
|
|
52
|
-
if (
|
|
73
|
+
if (i === 0 && (ch === "-" || ch === "+")) {
|
|
74
|
+
i++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (ch !== "." && (ch < "0" || ch > "9")) break;
|
|
53
78
|
i++;
|
|
54
79
|
}
|
|
55
80
|
if (i === 0) return NaN;
|
|
56
81
|
const num = Number(trimmed.slice(0, i));
|
|
57
82
|
if (Number.isNaN(num)) return NaN;
|
|
58
|
-
const unit = trimmed.slice(i)
|
|
83
|
+
const unit = trimHoconWs(trimmed.slice(i)).toLowerCase();
|
|
84
|
+
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
85
|
+
if (divisor === void 0) return NaN;
|
|
86
|
+
if (unit === "") {
|
|
87
|
+
return num / divisor;
|
|
88
|
+
}
|
|
59
89
|
const mult = DURATION_UNITS[unit];
|
|
60
90
|
if (mult === void 0) return NaN;
|
|
61
91
|
const ms = num * mult;
|
|
62
|
-
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
63
|
-
if (divisor === void 0) return NaN;
|
|
64
92
|
return ms / divisor;
|
|
65
93
|
}
|
|
66
94
|
var BYTE_UNITS = {
|
|
@@ -91,6 +119,22 @@ var BYTE_UNITS = {
|
|
|
91
119
|
TiB: 1099511627776,
|
|
92
120
|
tebibyte: 1099511627776,
|
|
93
121
|
tebibytes: 1099511627776,
|
|
122
|
+
// S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
|
|
123
|
+
// Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
|
|
124
|
+
// Both upper- and lowercase accepted per Lightbend behaviour.
|
|
125
|
+
// Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
|
|
126
|
+
K: 1024,
|
|
127
|
+
k: 1024,
|
|
128
|
+
M: 1024 ** 2,
|
|
129
|
+
m: 1024 ** 2,
|
|
130
|
+
G: 1024 ** 3,
|
|
131
|
+
g: 1024 ** 3,
|
|
132
|
+
T: 1024 ** 4,
|
|
133
|
+
t: 1024 ** 4,
|
|
134
|
+
P: 1024 ** 5,
|
|
135
|
+
p: 1024 ** 5,
|
|
136
|
+
E: 1024 ** 6,
|
|
137
|
+
e: 1024 ** 6,
|
|
94
138
|
// lowercase short-form aliases
|
|
95
139
|
b: 1,
|
|
96
140
|
kb: 1e3,
|
|
@@ -114,25 +158,39 @@ var OUTPUT_BYTE_UNITS = {
|
|
|
114
158
|
TiB: 1099511627776
|
|
115
159
|
};
|
|
116
160
|
function parseBytes(value, outputUnit = "B") {
|
|
117
|
-
const trimmed = value
|
|
161
|
+
const trimmed = trimHoconWs(value);
|
|
118
162
|
let i = 0;
|
|
119
163
|
while (i < trimmed.length) {
|
|
120
164
|
const ch = trimmed[i];
|
|
121
|
-
if (
|
|
165
|
+
if (i === 0 && (ch === "-" || ch === "+")) {
|
|
166
|
+
i++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (ch !== "." && (ch < "0" || ch > "9")) break;
|
|
122
170
|
i++;
|
|
123
171
|
}
|
|
124
172
|
if (i === 0) return NaN;
|
|
125
173
|
const num = Number(trimmed.slice(0, i));
|
|
126
174
|
if (Number.isNaN(num)) return NaN;
|
|
127
|
-
const unit = trimmed.slice(i)
|
|
175
|
+
const unit = trimHoconWs(trimmed.slice(i));
|
|
176
|
+
const divisor = OUTPUT_BYTE_UNITS[outputUnit];
|
|
177
|
+
if (divisor === void 0) return NaN;
|
|
178
|
+
if (unit === "") {
|
|
179
|
+
const bytes2 = Math.trunc(num);
|
|
180
|
+
if (Math.abs(bytes2) > Number.MAX_SAFE_INTEGER) {
|
|
181
|
+
throw new RangeError("byte size overflows representable range (max 2^53-1 bytes)");
|
|
182
|
+
}
|
|
183
|
+
return bytes2 / divisor;
|
|
184
|
+
}
|
|
128
185
|
let mult = BYTE_UNITS[unit];
|
|
129
186
|
if (mult === void 0) {
|
|
130
187
|
mult = BYTE_UNITS[unit.toLowerCase()];
|
|
131
188
|
}
|
|
132
189
|
if (mult === void 0) return NaN;
|
|
133
190
|
const bytes = num * mult;
|
|
134
|
-
|
|
135
|
-
|
|
191
|
+
if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
|
|
192
|
+
throw new RangeError("byte size overflows representable range (max 2^53-1 bytes)");
|
|
193
|
+
}
|
|
136
194
|
const result = bytes / divisor;
|
|
137
195
|
return outputUnit === "B" ? Math.round(result) : result;
|
|
138
196
|
}
|
|
@@ -157,6 +215,14 @@ var ResolveError = class extends Error {
|
|
|
157
215
|
this.name = "ResolveError";
|
|
158
216
|
}
|
|
159
217
|
};
|
|
218
|
+
var PackageLookupError = class extends ResolveError {
|
|
219
|
+
constructor(message, identifier, packageFile, line, col) {
|
|
220
|
+
super(message, `${identifier}/${packageFile}`, line, col);
|
|
221
|
+
this.identifier = identifier;
|
|
222
|
+
this.packageFile = packageFile;
|
|
223
|
+
this.name = "PackageLookupError";
|
|
224
|
+
}
|
|
225
|
+
};
|
|
160
226
|
var ConfigError = class extends Error {
|
|
161
227
|
constructor(message, path2) {
|
|
162
228
|
super(message);
|
|
@@ -164,190 +230,155 @@ var ConfigError = class extends Error {
|
|
|
164
230
|
this.name = "ConfigError";
|
|
165
231
|
}
|
|
166
232
|
};
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (value.fields.size === 0) return null;
|
|
172
|
-
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
173
|
-
const eligible = [];
|
|
174
|
-
for (const [key, val] of value.fields) {
|
|
175
|
-
if (!canonicalInt.test(key)) continue;
|
|
176
|
-
const n = Number(key);
|
|
177
|
-
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
178
|
-
eligible.push({ n, v: val });
|
|
233
|
+
var NotResolvedError = class extends ConfigError {
|
|
234
|
+
constructor(path2) {
|
|
235
|
+
super(`value at path "${path2}" is not resolved (call resolve() first)`, path2);
|
|
236
|
+
this.name = "NotResolvedError";
|
|
179
237
|
}
|
|
180
|
-
|
|
181
|
-
eligible.sort((a, b) => a.n - b.n);
|
|
182
|
-
return eligible.map((e) => e.v);
|
|
183
|
-
}
|
|
238
|
+
};
|
|
184
239
|
|
|
185
|
-
// src/
|
|
186
|
-
var
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const v = this.requireScalar(path2);
|
|
207
|
-
const coerced = coerceBoolean(v.raw);
|
|
208
|
-
if (coerced !== void 0) return coerced;
|
|
209
|
-
throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
|
|
240
|
+
// src/internal/resolver/types.ts
|
|
241
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
242
|
+
function isSubst(v) {
|
|
243
|
+
return v._kind === "subst-placeholder";
|
|
244
|
+
}
|
|
245
|
+
function isConcat(v) {
|
|
246
|
+
return v._kind === "concat-placeholder";
|
|
247
|
+
}
|
|
248
|
+
function isAppend(v) {
|
|
249
|
+
return v._kind === "append-placeholder";
|
|
250
|
+
}
|
|
251
|
+
function isResObj(v) {
|
|
252
|
+
return v._kind === "res-obj";
|
|
253
|
+
}
|
|
254
|
+
function makeResObj() {
|
|
255
|
+
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
256
|
+
}
|
|
257
|
+
function mergeUnresolved(receiver, fallback) {
|
|
258
|
+
const result = makeResObj();
|
|
259
|
+
for (const [k, v] of fallback.fields) {
|
|
260
|
+
result.fields.set(k, v);
|
|
210
261
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
214
|
-
throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
|
|
215
|
-
}
|
|
216
|
-
const result = parseDuration(v.raw, unit);
|
|
217
|
-
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
218
|
-
return result;
|
|
262
|
+
for (const [k, v] of fallback.priorValues) {
|
|
263
|
+
result.priorValues.set(k, v);
|
|
219
264
|
}
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
|
|
265
|
+
for (const [k, rv] of receiver.fields) {
|
|
266
|
+
const existing = result.fields.get(k);
|
|
267
|
+
if (existing !== void 0) {
|
|
268
|
+
const recPrior = receiver.priorValues.get(k);
|
|
269
|
+
if (recPrior !== void 0 && !isResObj(recPrior)) {
|
|
270
|
+
result.fields.set(k, rv);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (isResObj(rv) && isResObj(existing)) {
|
|
274
|
+
result.fields.set(k, mergeUnresolved(rv, existing));
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
result.priorValues.set(k, existing);
|
|
224
278
|
}
|
|
225
|
-
|
|
226
|
-
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
getConfig(path2) {
|
|
230
|
-
const v = this.lookupNode(path2);
|
|
231
|
-
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
232
|
-
if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
|
|
233
|
-
return new _Config(v);
|
|
279
|
+
result.fields.set(k, rv);
|
|
234
280
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (v.kind === "object") {
|
|
239
|
-
const converted = numericObjectToArray(v);
|
|
240
|
-
if (converted !== null) return converted.map(hoconToJs);
|
|
281
|
+
for (const [k, v] of receiver.priorValues) {
|
|
282
|
+
if (receiver.fields.has(k)) {
|
|
283
|
+
result.priorValues.set(k, v);
|
|
241
284
|
}
|
|
242
|
-
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
243
|
-
return v.items.map(hoconToJs);
|
|
244
|
-
}
|
|
245
|
-
has(path2) {
|
|
246
|
-
return this.lookupNode(path2) !== void 0;
|
|
247
|
-
}
|
|
248
|
-
keys() {
|
|
249
|
-
return [...this.root.fields.keys()];
|
|
250
|
-
}
|
|
251
|
-
withFallback(fallback) {
|
|
252
|
-
const merged = deepMergeHocon(this.root, fallback.root);
|
|
253
|
-
return new _Config(merged);
|
|
254
|
-
}
|
|
255
|
-
toObject() {
|
|
256
|
-
return hoconToJs(this.root);
|
|
257
285
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/internal/resolver/utils.ts
|
|
290
|
+
function segmentsToKey(segments) {
|
|
291
|
+
return segments.map((s) => {
|
|
292
|
+
const t = s.text;
|
|
293
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
294
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
266
295
|
}
|
|
267
|
-
return
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
296
|
+
return t;
|
|
297
|
+
}).join(".");
|
|
298
|
+
}
|
|
299
|
+
function lookupPath(root, segments) {
|
|
300
|
+
const [head, ...tail] = segments;
|
|
301
|
+
if (head === void 0) return void 0;
|
|
302
|
+
const key = head.text;
|
|
303
|
+
const val = root.fields.get(key);
|
|
304
|
+
if (val === void 0) return void 0;
|
|
305
|
+
if (tail.length === 0) return val;
|
|
306
|
+
if (isResObj(val)) return lookupPath(val, tail);
|
|
307
|
+
return void 0;
|
|
308
|
+
}
|
|
309
|
+
function lookupResObj(root, segments) {
|
|
310
|
+
let cur = root;
|
|
311
|
+
for (const seg of segments) {
|
|
312
|
+
const val = cur.fields.get(seg.text);
|
|
313
|
+
if (val === void 0 || !isResObj(val)) return void 0;
|
|
314
|
+
cur = val;
|
|
274
315
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
let closed = false;
|
|
284
|
-
while (i < path2.length) {
|
|
285
|
-
const ch = path2[i];
|
|
286
|
-
if (ch === "\\" && i + 1 < path2.length) {
|
|
287
|
-
const next = path2[i + 1];
|
|
288
|
-
segment += next;
|
|
289
|
-
i += 2;
|
|
290
|
-
continue;
|
|
291
|
-
}
|
|
292
|
-
if (ch === '"') {
|
|
293
|
-
closed = true;
|
|
294
|
-
i++;
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
segment += ch;
|
|
298
|
-
i++;
|
|
299
|
-
}
|
|
300
|
-
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
301
|
-
segments.push(segment);
|
|
302
|
-
if (i < path2.length && path2[i] === ".") i++;
|
|
316
|
+
return cur;
|
|
317
|
+
}
|
|
318
|
+
function deepMergeHoconValues(base, overlay) {
|
|
319
|
+
const merged = new Map(base.fields);
|
|
320
|
+
for (const [k, v] of overlay.fields) {
|
|
321
|
+
const existing = merged.get(k);
|
|
322
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
323
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
303
324
|
} else {
|
|
304
|
-
|
|
305
|
-
if (dot === -1) {
|
|
306
|
-
segments.push(path2.slice(i));
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
segments.push(path2.slice(i, dot));
|
|
310
|
-
i = dot + 1;
|
|
325
|
+
merged.set(k, v);
|
|
311
326
|
}
|
|
312
327
|
}
|
|
313
|
-
return
|
|
328
|
+
return { kind: "object", fields: merged };
|
|
314
329
|
}
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return raw;
|
|
330
|
+
function deepMergeResObjInto(dst, src) {
|
|
331
|
+
for (const [k, srcVal] of src.fields) {
|
|
332
|
+
const dstVal = dst.fields.get(k);
|
|
333
|
+
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
334
|
+
deepMergeResObjInto(dstVal, srcVal);
|
|
335
|
+
} else {
|
|
336
|
+
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
337
|
+
dst.fields.set(k, srcVal);
|
|
338
|
+
}
|
|
325
339
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
case "scalar":
|
|
330
|
-
return scalarToJs(v.raw, v.valueType);
|
|
331
|
-
case "array":
|
|
332
|
-
return v.items.map(hoconToJs);
|
|
333
|
-
case "object": {
|
|
334
|
-
const obj = {};
|
|
335
|
-
for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
|
|
336
|
-
return obj;
|
|
340
|
+
for (const [k, srcPrior] of src.priorValues) {
|
|
341
|
+
if (!dst.priorValues.has(k)) {
|
|
342
|
+
dst.priorValues.set(k, srcPrior);
|
|
337
343
|
}
|
|
338
344
|
}
|
|
339
345
|
}
|
|
340
|
-
function
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (
|
|
345
|
-
|
|
346
|
+
function hoconValueToResObj(hv) {
|
|
347
|
+
const obj = makeResObj();
|
|
348
|
+
if (hv.kind !== "object") return obj;
|
|
349
|
+
for (const [key, val] of hv.fields) {
|
|
350
|
+
if (val.kind === "object") {
|
|
351
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
346
352
|
} else {
|
|
347
|
-
|
|
353
|
+
obj.fields.set(key, val);
|
|
348
354
|
}
|
|
349
355
|
}
|
|
350
|
-
return
|
|
356
|
+
return obj;
|
|
357
|
+
}
|
|
358
|
+
function isFileNotFoundError(e) {
|
|
359
|
+
if (!(e instanceof Error)) return false;
|
|
360
|
+
const code = e.code;
|
|
361
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
362
|
+
const msg = e.message.toLowerCase();
|
|
363
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/internal/resolver/include-loader.ts
|
|
367
|
+
import { createRequire } from "module";
|
|
368
|
+
import * as nodePath from "path";
|
|
369
|
+
|
|
370
|
+
// src/internal/parser/empty-check.ts
|
|
371
|
+
function hasContentTokens(tokens) {
|
|
372
|
+
return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
|
|
373
|
+
}
|
|
374
|
+
function assertNonEmptyDocument(tokens, sourceDescriptor) {
|
|
375
|
+
if (!hasContentTokens(tokens)) {
|
|
376
|
+
throw new ParseError(
|
|
377
|
+
`empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
|
|
378
|
+
1,
|
|
379
|
+
1
|
|
380
|
+
);
|
|
381
|
+
}
|
|
351
382
|
}
|
|
352
383
|
|
|
353
384
|
// src/internal/lexer/lexer.ts
|
|
@@ -477,14 +508,6 @@ var Lexer = class {
|
|
|
477
508
|
continue;
|
|
478
509
|
}
|
|
479
510
|
if (isUnquotedStart(ch)) {
|
|
480
|
-
if (ch === "-" && !isDecimalDigit(this.peek(1))) {
|
|
481
|
-
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
482
|
-
throw new ParseError(
|
|
483
|
-
`unquoted string cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
484
|
-
sl,
|
|
485
|
-
sc
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
511
|
let value = "";
|
|
489
512
|
while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
|
|
490
513
|
value += this.advance();
|
|
@@ -588,6 +611,10 @@ var Lexer = class {
|
|
|
588
611
|
/**
|
|
589
612
|
* Parse the body of a `${...}` substitution (called after `${` has been consumed).
|
|
590
613
|
* Implements Appendix A state machine from the cross-language spec.
|
|
614
|
+
*
|
|
615
|
+
* S13c extension: if the segment-collection loop encounters `[`, it delegates to
|
|
616
|
+
* `parseListSuffix()` which consumes the literal `[]` and sets `listSuffix=true`.
|
|
617
|
+
* ASCII space/tab before `[` is buffered in `pendingWs` and discarded (E7).
|
|
591
618
|
*/
|
|
592
619
|
parseSubstBody(startLine, startCol) {
|
|
593
620
|
let optional = false;
|
|
@@ -602,6 +629,7 @@ var Lexer = class {
|
|
|
602
629
|
let pendingWs = "";
|
|
603
630
|
const segments = [];
|
|
604
631
|
let lastDot = null;
|
|
632
|
+
let listSuffix = false;
|
|
605
633
|
while (true) {
|
|
606
634
|
if (this.pos >= this.input.length) {
|
|
607
635
|
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
@@ -611,6 +639,23 @@ var Lexer = class {
|
|
|
611
639
|
this.advance();
|
|
612
640
|
pendingWs = "";
|
|
613
641
|
break;
|
|
642
|
+
} else if (ch === "[") {
|
|
643
|
+
if (!curStarted) {
|
|
644
|
+
throw new ParseError("empty segment before '[]' suffix in substitution path", startLine, this.col);
|
|
645
|
+
}
|
|
646
|
+
for (const w of pendingWs) {
|
|
647
|
+
if (w !== " " && w !== " ") {
|
|
648
|
+
throw new ParseError(
|
|
649
|
+
`only ASCII space or tab allowed between substitution path and '[]' suffix (got ${JSON.stringify(w)}, HOCON extra-spec E7)`,
|
|
650
|
+
startLine,
|
|
651
|
+
this.col
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
656
|
+
pendingWs = "";
|
|
657
|
+
listSuffix = this.parseListSuffix(startLine);
|
|
658
|
+
break;
|
|
614
659
|
} else if (ch === '"') {
|
|
615
660
|
const qLine = startLine;
|
|
616
661
|
const qCol = this.col;
|
|
@@ -670,15 +715,47 @@ var Lexer = class {
|
|
|
670
715
|
throw new ParseError(`unexpected character in substitution path: ${JSON.stringify(ch)}`, startLine, this.col);
|
|
671
716
|
}
|
|
672
717
|
}
|
|
673
|
-
if (
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
718
|
+
if (!listSuffix) {
|
|
719
|
+
if (curStarted) {
|
|
720
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
721
|
+
} else if (segments.length === 0) {
|
|
722
|
+
throw new ParseError("empty substitution path", startLine, startCol);
|
|
723
|
+
} else {
|
|
724
|
+
const [errLine, errCol] = lastDot ?? [startLine, startCol];
|
|
725
|
+
throw new ParseError("empty segment in path", errLine, errCol);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return { segments, optional, listSuffix };
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Consume the literal `[]` suffix inside a substitution body.
|
|
732
|
+
* Called after the `[` has been detected (but not yet consumed).
|
|
733
|
+
* Requires the next character to be `[`, then exactly `]`, then `}`.
|
|
734
|
+
* Whitespace inside `[]` is not permitted (per spec Decision §1).
|
|
735
|
+
* Returns true (always — only called when listSuffix path is taken).
|
|
736
|
+
*/
|
|
737
|
+
parseListSuffix(startLine) {
|
|
738
|
+
this.advance();
|
|
739
|
+
const afterBracket = this.peek();
|
|
740
|
+
if (afterBracket !== "]") {
|
|
741
|
+
const desc = afterBracket === "" ? "EOF" : JSON.stringify(afterBracket);
|
|
742
|
+
throw new ParseError(
|
|
743
|
+
`expected ']' after '[' in substitution list suffix (got ${desc})`,
|
|
744
|
+
startLine,
|
|
745
|
+
this.col
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
this.advance();
|
|
749
|
+
if (this.peek() !== "}") {
|
|
750
|
+
const desc = this.peek() === "" ? "EOF" : JSON.stringify(this.peek());
|
|
751
|
+
throw new ParseError(
|
|
752
|
+
`expected '}' after '[]' in substitution list suffix (got ${desc})`,
|
|
753
|
+
startLine,
|
|
754
|
+
this.col
|
|
755
|
+
);
|
|
680
756
|
}
|
|
681
|
-
|
|
757
|
+
this.advance();
|
|
758
|
+
return true;
|
|
682
759
|
}
|
|
683
760
|
};
|
|
684
761
|
function tokenize(input) {
|
|
@@ -766,6 +843,14 @@ var Parser = class {
|
|
|
766
843
|
const t = this.peek();
|
|
767
844
|
if (t.kind === "eof" || t.kind === "rbrace") break;
|
|
768
845
|
if (t.kind === "unquoted" && t.value === "include") {
|
|
846
|
+
const next = this.peek(1);
|
|
847
|
+
if (next.kind === "equals" || next.kind === "colon" || next.kind === "plus_equals" || next.kind === "lbrace") {
|
|
848
|
+
throw new ParseError(
|
|
849
|
+
`'include' is reserved at the start of a key path expression; use "include" (quoted) to use it as a key`,
|
|
850
|
+
t.line,
|
|
851
|
+
t.col
|
|
852
|
+
);
|
|
853
|
+
}
|
|
769
854
|
this.advance();
|
|
770
855
|
fields.push(this.parseInclude());
|
|
771
856
|
this.skip("newline");
|
|
@@ -774,7 +859,14 @@ var Parser = class {
|
|
|
774
859
|
continue;
|
|
775
860
|
}
|
|
776
861
|
const keyPos = this.currentPos();
|
|
777
|
-
const key = this.parseKey();
|
|
862
|
+
const { segments: key, firstWasQuoted } = this.parseKey();
|
|
863
|
+
if (key[0] === "include" && !firstWasQuoted) {
|
|
864
|
+
throw new ParseError(
|
|
865
|
+
`'include' is reserved at the start of a key path expression; use "include".foo (quoted) or rename the key`,
|
|
866
|
+
keyPos.line,
|
|
867
|
+
keyPos.col
|
|
868
|
+
);
|
|
869
|
+
}
|
|
778
870
|
this.skip("newline");
|
|
779
871
|
let append = false;
|
|
780
872
|
const sep = this.peek();
|
|
@@ -804,10 +896,12 @@ var Parser = class {
|
|
|
804
896
|
parseKey() {
|
|
805
897
|
const segments = [];
|
|
806
898
|
let trailingDot = false;
|
|
899
|
+
let firstWasQuoted = false;
|
|
807
900
|
while (true) {
|
|
808
901
|
const t = this.peek();
|
|
809
902
|
if (t.kind === "string") {
|
|
810
903
|
this.advance();
|
|
904
|
+
if (segments.length === 0) firstWasQuoted = true;
|
|
811
905
|
segments.push(t.value);
|
|
812
906
|
trailingDot = false;
|
|
813
907
|
} else if (t.kind === "unquoted") {
|
|
@@ -844,64 +938,143 @@ var Parser = class {
|
|
|
844
938
|
}
|
|
845
939
|
break;
|
|
846
940
|
}
|
|
847
|
-
return segments;
|
|
941
|
+
return { segments, firstWasQuoted };
|
|
848
942
|
}
|
|
849
943
|
parseInclude() {
|
|
850
944
|
const p = this.currentPos();
|
|
851
945
|
this.skip("newline");
|
|
852
946
|
const t = this.peek();
|
|
853
|
-
let path2;
|
|
854
|
-
let required = false;
|
|
855
|
-
let isFile = false;
|
|
856
947
|
if (t.kind === "unquoted" && (t.value === "required(" || t.value === "required" || t.value.startsWith("required("))) {
|
|
857
948
|
if (t.value === "required") {
|
|
858
|
-
const
|
|
859
|
-
if (
|
|
949
|
+
const next = this.peek(1);
|
|
950
|
+
if (next.kind !== "unquoted" || !next.value.startsWith("(")) {
|
|
860
951
|
throw new ParseError("include required must be followed by (", t.line, t.col);
|
|
861
952
|
}
|
|
862
953
|
}
|
|
863
|
-
required = true;
|
|
864
954
|
this.advance();
|
|
865
955
|
const innerPrefix = t.value.startsWith("required(") ? t.value.slice("required(".length) : "";
|
|
866
|
-
if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
|
|
956
|
+
if (innerPrefix === "url" || innerPrefix.startsWith("url(") || innerPrefix === "classpath" || innerPrefix.startsWith("classpath(")) {
|
|
867
957
|
throw new ParseError("include url(...) and classpath(...) are not supported", t.line, t.col);
|
|
868
958
|
}
|
|
869
|
-
|
|
870
|
-
|
|
959
|
+
const nextTok = this.peek();
|
|
960
|
+
if (innerPrefix === "package" || innerPrefix.startsWith("package(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "package(" || nextTok.value.startsWith("package("))) {
|
|
961
|
+
const consumePackageToken = innerPrefix === "";
|
|
962
|
+
const { qualifier, path: path3 } = this.parsePackageArgs(consumePackageToken);
|
|
963
|
+
return this.makeIncludeField(qualifier, path3, true, p);
|
|
871
964
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
965
|
+
if (innerPrefix === "file" || innerPrefix.startsWith("file(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "file(" || nextTok.value === "file")) {
|
|
966
|
+
if (innerPrefix === "") {
|
|
967
|
+
this.advance();
|
|
968
|
+
}
|
|
969
|
+
const path3 = this.parseQuotedPathSkipWrapper(t);
|
|
970
|
+
return this.makeIncludeField({ kind: "file" }, path3, true, p);
|
|
971
|
+
}
|
|
972
|
+
if (innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "url" || nextTok.value.startsWith("url(") || nextTok.value === "classpath" || nextTok.value.startsWith("classpath("))) {
|
|
973
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", nextTok.line, nextTok.col);
|
|
974
|
+
}
|
|
975
|
+
if (innerPrefix !== "") {
|
|
976
|
+
if (innerPrefix === ")") {
|
|
977
|
+
throw new ParseError("include required(...) must contain a path", t.line, t.col);
|
|
978
|
+
}
|
|
979
|
+
const name = innerPrefix.replace(/\(.*$/, "").replace(/\).*$/, "");
|
|
980
|
+
throw new ParseError(`unknown include qualifier inside required(): "${name}"`, t.line, t.col);
|
|
981
|
+
}
|
|
982
|
+
const path2 = this.parseQuotedPathSkipWrapper(t);
|
|
983
|
+
return this.makeIncludeField({ kind: "bare" }, path2, true, p);
|
|
984
|
+
}
|
|
985
|
+
if (t.kind === "string") {
|
|
986
|
+
const path2 = this.advance().value;
|
|
987
|
+
return this.makeIncludeField({ kind: "bare" }, path2, false, p);
|
|
988
|
+
}
|
|
989
|
+
if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
|
|
887
990
|
this.advance();
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
991
|
+
const path2 = this.parseQuotedPathSkipWrapper(t);
|
|
992
|
+
return this.makeIncludeField({ kind: "file" }, path2, false, p);
|
|
993
|
+
}
|
|
994
|
+
if (t.kind === "unquoted" && (t.value === "package(" || t.value.startsWith("package("))) {
|
|
995
|
+
const { qualifier, path: path2 } = this.parsePackageArgs();
|
|
996
|
+
return this.makeIncludeField(qualifier, path2, false, p);
|
|
997
|
+
}
|
|
998
|
+
if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
|
|
893
999
|
throw new ParseError("include url(...) is not supported", t.line, t.col);
|
|
894
|
-
}
|
|
1000
|
+
}
|
|
1001
|
+
if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
|
|
895
1002
|
throw new ParseError("include classpath(...) is not supported", t.line, t.col);
|
|
896
|
-
} else {
|
|
897
|
-
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
898
1003
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1004
|
+
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
1005
|
+
}
|
|
1006
|
+
makeIncludeField(qualifier, path2, required, pos) {
|
|
1007
|
+
return { key: [], value: { kind: "include", qualifier, path: path2, required, pos }, append: false, pos };
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Skip past include-syntax wrapper tokens to find the quoted path string.
|
|
1011
|
+
* Strict: only allows `(`, `)`, and bare qualifier keywords (file / file( /
|
|
1012
|
+
* package / package( / url / url( / classpath / classpath() between the
|
|
1013
|
+
* directive and the quoted path. Anything else is a parse error rather than
|
|
1014
|
+
* silently swallowed (see ts#113).
|
|
1015
|
+
*/
|
|
1016
|
+
parseQuotedPathSkipWrapper(errTok) {
|
|
1017
|
+
while (this.isIncludeWrapperToken(this.peek())) this.advance();
|
|
1018
|
+
if (this.peek().kind !== "string") {
|
|
1019
|
+
const tok = this.peek();
|
|
1020
|
+
if (tok.kind === "eof") throw new ParseError("expected include path", errTok.line, errTok.col);
|
|
1021
|
+
const desc = tok.kind === "unquoted" ? `unquoted "${tok.value}"` : tok.kind;
|
|
1022
|
+
throw new ParseError(`expected quoted include path, got ${desc}`, tok.line, tok.col);
|
|
1023
|
+
}
|
|
1024
|
+
const path2 = this.advance().value;
|
|
1025
|
+
while (this.peek().kind === "unquoted" && (this.peek().value === ")" || this.peek().value === "))")) {
|
|
1026
|
+
this.advance();
|
|
1027
|
+
}
|
|
1028
|
+
const after = this.peek();
|
|
1029
|
+
if (after.kind !== "newline" && after.kind !== "rbrace" && after.kind !== "eof" && after.kind !== "comma") {
|
|
1030
|
+
const desc = after.kind === "unquoted" ? `unquoted "${after.value}"` : after.kind;
|
|
1031
|
+
throw new ParseError(`unexpected token after include path: ${desc}`, after.line, after.col);
|
|
1032
|
+
}
|
|
1033
|
+
return path2;
|
|
1034
|
+
}
|
|
1035
|
+
isIncludeWrapperToken(tok) {
|
|
1036
|
+
return tok.kind === "unquoted" && tok.value === "(";
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Parse the body of `package("identifier", "file")`.
|
|
1040
|
+
*
|
|
1041
|
+
* @param consumePackageToken - When true (default), the current token is `package(` and
|
|
1042
|
+
* must be consumed first. When false, the lexer already bundled `package(` into the
|
|
1043
|
+
* preceding token (e.g. `required(package("`), so we are already positioned at the
|
|
1044
|
+
* first string argument.
|
|
1045
|
+
*/
|
|
1046
|
+
parsePackageArgs(consumePackageToken = true) {
|
|
1047
|
+
const pkgTok = consumePackageToken ? this.advance() : this.peek();
|
|
1048
|
+
this.skip("newline");
|
|
1049
|
+
const idTok = this.peek();
|
|
1050
|
+
if (idTok.kind !== "string") {
|
|
1051
|
+
throw new ParseError("include package: expected quoted identifier as first argument", pkgTok.line, pkgTok.col);
|
|
1052
|
+
}
|
|
1053
|
+
const identifier = this.advance().value;
|
|
1054
|
+
this.skip("newline");
|
|
1055
|
+
if (this.peek().kind !== "comma") {
|
|
1056
|
+
const after = this.peek();
|
|
1057
|
+
throw new ParseError(
|
|
1058
|
+
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",
|
|
1059
|
+
pkgTok.line,
|
|
1060
|
+
pkgTok.col
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
this.advance();
|
|
1064
|
+
this.skip("newline");
|
|
1065
|
+
const fileTok = this.peek();
|
|
1066
|
+
if (fileTok.kind !== "string") {
|
|
1067
|
+
throw new ParseError(
|
|
1068
|
+
"include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
|
|
1069
|
+
pkgTok.line,
|
|
1070
|
+
pkgTok.col
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const filePath = this.advance().value;
|
|
1074
|
+
while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") {
|
|
1075
|
+
this.advance();
|
|
1076
|
+
}
|
|
1077
|
+
return { qualifier: { kind: "package", identifier }, path: filePath };
|
|
905
1078
|
}
|
|
906
1079
|
parseValue() {
|
|
907
1080
|
const p = this.currentPos();
|
|
@@ -923,7 +1096,7 @@ var Parser = class {
|
|
|
923
1096
|
throw new ParseError(`internal: subst token missing payload`, t.line, t.col);
|
|
924
1097
|
}
|
|
925
1098
|
const payload = t.subst;
|
|
926
|
-
node = { kind: "subst", segments: payload.segments, optional: payload.optional, pos: { line: t.line, col: t.col } };
|
|
1099
|
+
node = { kind: "subst", segments: payload.segments, optional: payload.optional, listSuffix: payload.listSuffix, pos: { line: t.line, col: t.col } };
|
|
927
1100
|
} else if (t.kind === "string" || t.kind === "triple_string") {
|
|
928
1101
|
this.advance();
|
|
929
1102
|
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
@@ -942,7 +1115,9 @@ var Parser = class {
|
|
|
942
1115
|
parts.push(node);
|
|
943
1116
|
}
|
|
944
1117
|
if (parts.length === 0) throw new ParseError("expected value", this.peek().line, this.peek().col);
|
|
945
|
-
if (parts.length === 1)
|
|
1118
|
+
if (parts.length === 1) {
|
|
1119
|
+
return parts[0];
|
|
1120
|
+
}
|
|
946
1121
|
return { kind: "concat", nodes: parts, pos: p };
|
|
947
1122
|
}
|
|
948
1123
|
parseArray() {
|
|
@@ -974,107 +1149,10 @@ function parseTokens(tokens) {
|
|
|
974
1149
|
return new Parser(tokens).parse();
|
|
975
1150
|
}
|
|
976
1151
|
|
|
977
|
-
// src/internal/resolver/types.ts
|
|
978
|
-
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
979
|
-
function isSubst(v) {
|
|
980
|
-
return v._kind === "subst-placeholder";
|
|
981
|
-
}
|
|
982
|
-
function isConcat(v) {
|
|
983
|
-
return v._kind === "concat-placeholder";
|
|
984
|
-
}
|
|
985
|
-
function isAppend(v) {
|
|
986
|
-
return v._kind === "append-placeholder";
|
|
987
|
-
}
|
|
988
|
-
function isResObj(v) {
|
|
989
|
-
return v._kind === "res-obj";
|
|
990
|
-
}
|
|
991
|
-
function makeResObj() {
|
|
992
|
-
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// src/internal/resolver/utils.ts
|
|
996
|
-
function segmentsToKey(segments) {
|
|
997
|
-
return segments.map((s) => {
|
|
998
|
-
const t = s.text;
|
|
999
|
-
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
1000
|
-
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1001
|
-
}
|
|
1002
|
-
return t;
|
|
1003
|
-
}).join(".");
|
|
1004
|
-
}
|
|
1005
|
-
function lookupPath(root, segments) {
|
|
1006
|
-
const [head, ...tail] = segments;
|
|
1007
|
-
if (head === void 0) return void 0;
|
|
1008
|
-
const key = head.text;
|
|
1009
|
-
const val = root.fields.get(key);
|
|
1010
|
-
if (val === void 0) return void 0;
|
|
1011
|
-
if (tail.length === 0) return val;
|
|
1012
|
-
if (isResObj(val)) return lookupPath(val, tail);
|
|
1013
|
-
return void 0;
|
|
1014
|
-
}
|
|
1015
|
-
function lookupResObj(root, segments) {
|
|
1016
|
-
let cur = root;
|
|
1017
|
-
for (const seg of segments) {
|
|
1018
|
-
const val = cur.fields.get(seg.text);
|
|
1019
|
-
if (val === void 0 || !isResObj(val)) return void 0;
|
|
1020
|
-
cur = val;
|
|
1021
|
-
}
|
|
1022
|
-
return cur;
|
|
1023
|
-
}
|
|
1024
|
-
function deepMergeHoconValues(base, overlay) {
|
|
1025
|
-
const merged = new Map(base.fields);
|
|
1026
|
-
for (const [k, v] of overlay.fields) {
|
|
1027
|
-
const existing = merged.get(k);
|
|
1028
|
-
if (existing?.kind === "object" && v.kind === "object") {
|
|
1029
|
-
merged.set(k, deepMergeHoconValues(existing, v));
|
|
1030
|
-
} else {
|
|
1031
|
-
merged.set(k, v);
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
return { kind: "object", fields: merged };
|
|
1035
|
-
}
|
|
1036
|
-
function deepMergeResObjInto(dst, src) {
|
|
1037
|
-
for (const [k, srcVal] of src.fields) {
|
|
1038
|
-
const dstVal = dst.fields.get(k);
|
|
1039
|
-
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
1040
|
-
deepMergeResObjInto(dstVal, srcVal);
|
|
1041
|
-
} else {
|
|
1042
|
-
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
1043
|
-
dst.fields.set(k, srcVal);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
for (const [k, srcPrior] of src.priorValues) {
|
|
1047
|
-
if (!dst.priorValues.has(k)) {
|
|
1048
|
-
dst.priorValues.set(k, srcPrior);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
function hoconValueToResObj(hv) {
|
|
1053
|
-
const obj = makeResObj();
|
|
1054
|
-
if (hv.kind !== "object") return obj;
|
|
1055
|
-
for (const [key, val] of hv.fields) {
|
|
1056
|
-
if (val.kind === "object") {
|
|
1057
|
-
obj.fields.set(key, hoconValueToResObj(val));
|
|
1058
|
-
} else {
|
|
1059
|
-
obj.fields.set(key, val);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
return obj;
|
|
1063
|
-
}
|
|
1064
|
-
function isFileNotFoundError(e) {
|
|
1065
|
-
if (!(e instanceof Error)) return false;
|
|
1066
|
-
const code = e.code;
|
|
1067
|
-
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
1068
|
-
const msg = e.message.toLowerCase();
|
|
1069
|
-
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
// src/internal/resolver/include-loader.ts
|
|
1073
|
-
import * as nodePath from "path";
|
|
1074
|
-
|
|
1075
1152
|
// src/internal/properties/properties.ts
|
|
1076
1153
|
function parseProperties(input) {
|
|
1077
1154
|
const root = /* @__PURE__ */ Object.create(null);
|
|
1155
|
+
const pairs = [];
|
|
1078
1156
|
for (const line of input.split("\n")) {
|
|
1079
1157
|
const trimmed = line.trim();
|
|
1080
1158
|
if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
|
|
@@ -1083,6 +1161,10 @@ function parseProperties(input) {
|
|
|
1083
1161
|
const key = trimmed.slice(0, sepIdx).trim();
|
|
1084
1162
|
const value = trimmed.slice(sepIdx + 1).trim();
|
|
1085
1163
|
if (key === "") continue;
|
|
1164
|
+
pairs.push([key, value]);
|
|
1165
|
+
}
|
|
1166
|
+
pairs.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1167
|
+
for (const [key, value] of pairs) {
|
|
1086
1168
|
setNested(root, key.split("."), value);
|
|
1087
1169
|
}
|
|
1088
1170
|
return root;
|
|
@@ -1106,6 +1188,7 @@ function setNested(obj, segments, value) {
|
|
|
1106
1188
|
}
|
|
1107
1189
|
const last = segments[segments.length - 1];
|
|
1108
1190
|
if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
|
|
1191
|
+
if (typeof current[last] === "object" && current[last] !== null) return;
|
|
1109
1192
|
current[last] = value;
|
|
1110
1193
|
}
|
|
1111
1194
|
function propertiesToHoconValue(input) {
|
|
@@ -1125,6 +1208,84 @@ function recordToHoconValue(obj) {
|
|
|
1125
1208
|
}
|
|
1126
1209
|
|
|
1127
1210
|
// src/internal/resolver/include-loader.ts
|
|
1211
|
+
var _require = createRequire(import.meta.url);
|
|
1212
|
+
function isPnpPath(p) {
|
|
1213
|
+
return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
|
|
1214
|
+
}
|
|
1215
|
+
function validatePackageFile(file, identifier) {
|
|
1216
|
+
if (file.length === 0) {
|
|
1217
|
+
throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
|
|
1218
|
+
}
|
|
1219
|
+
if (file.includes("\\")) {
|
|
1220
|
+
throw new ParseError(
|
|
1221
|
+
`include package("${identifier}", "${file}"): backslash not allowed in file argument (use forward slash)`,
|
|
1222
|
+
0,
|
|
1223
|
+
0
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
if (file.startsWith("/")) {
|
|
1227
|
+
throw new ParseError(
|
|
1228
|
+
`include package("${identifier}", "${file}"): absolute path not allowed in file argument`,
|
|
1229
|
+
0,
|
|
1230
|
+
0
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
if (/(?:^|\/)\.\.?(?:\/|$)/u.test(file)) {
|
|
1234
|
+
throw new ParseError(
|
|
1235
|
+
`include package("${identifier}", "${file}"): path traversal (. or ..) not allowed in file argument`,
|
|
1236
|
+
0,
|
|
1237
|
+
0
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
if (file.includes("//")) {
|
|
1241
|
+
throw new ParseError(
|
|
1242
|
+
`include package("${identifier}", "${file}"): consecutive slashes not allowed in file argument`,
|
|
1243
|
+
0,
|
|
1244
|
+
0
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function validatePackageIdentifier(identifier) {
|
|
1249
|
+
if (identifier.length === 0) {
|
|
1250
|
+
throw new ParseError("include package: identifier argument must be non-empty", 0, 0);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function defaultPackageResolver(identifier, file, includingFile, baseDir, resolveFrom) {
|
|
1254
|
+
const from = resolveFrom ? Array.isArray(resolveFrom) ? resolveFrom : [resolveFrom] : baseDir ? [baseDir] : includingFile ? [nodePath.dirname(includingFile)] : [process.cwd()];
|
|
1255
|
+
let resolved;
|
|
1256
|
+
try {
|
|
1257
|
+
resolved = _require.resolve(`${identifier}/${file}`, { paths: from });
|
|
1258
|
+
} catch {
|
|
1259
|
+
throw new PackageLookupError(
|
|
1260
|
+
`include package("${identifier}", "${file}"): module not found (starting from: ${from.join(", ")})`,
|
|
1261
|
+
identifier,
|
|
1262
|
+
file,
|
|
1263
|
+
0,
|
|
1264
|
+
0
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
if (isPnpPath(resolved)) {
|
|
1268
|
+
throw new ResolveError(
|
|
1269
|
+
`include package("${identifier}", "${file}"): Yarn Berry PnP store paths are not supported. Provide a custom packageResolver option to handle PnP resolution.`,
|
|
1270
|
+
`${identifier}/${file}`,
|
|
1271
|
+
0,
|
|
1272
|
+
0
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
const fileParts = file.split("/");
|
|
1276
|
+
const expectedBasename = fileParts[fileParts.length - 1] ?? file;
|
|
1277
|
+
const resolvedBasename = nodePath.basename(resolved);
|
|
1278
|
+
if (resolvedBasename !== expectedBasename) {
|
|
1279
|
+
throw new PackageLookupError(
|
|
1280
|
+
`include package("${identifier}", "${file}"): case mismatch \u2014 resolved "${resolvedBasename}" but expected "${expectedBasename}" (E11 decision 5: case-sensitive)`,
|
|
1281
|
+
identifier,
|
|
1282
|
+
file,
|
|
1283
|
+
0,
|
|
1284
|
+
0
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
return resolved;
|
|
1288
|
+
}
|
|
1128
1289
|
var IncludeLoader = class {
|
|
1129
1290
|
constructor(opts) {
|
|
1130
1291
|
this.opts = opts;
|
|
@@ -1204,8 +1365,77 @@ var IncludeLoader = class {
|
|
|
1204
1365
|
}
|
|
1205
1366
|
return merged;
|
|
1206
1367
|
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Resolve and load `include package("identifier", "file")`.
|
|
1370
|
+
* Validates the identifier and file arguments, resolves via the package resolver,
|
|
1371
|
+
* performs cycle detection using a JSON-serialised cycle key, and parses the content.
|
|
1372
|
+
*/
|
|
1373
|
+
// `required` is reserved for future optional-package semantics (E11 decision 7);
|
|
1374
|
+
// currently all package lookup failures throw regardless of this flag.
|
|
1375
|
+
loadPackage(identifier, file, _required) {
|
|
1376
|
+
validatePackageIdentifier(identifier);
|
|
1377
|
+
validatePackageFile(file, identifier);
|
|
1378
|
+
const { includeStack = [], baseDir, resolveFrom, packageResolver, readFileSync: readFileSync2 } = this.opts;
|
|
1379
|
+
const cycleKey = JSON.stringify(["package", identifier, file]);
|
|
1380
|
+
if (includeStack.includes(cycleKey)) {
|
|
1381
|
+
throw new ResolveError(
|
|
1382
|
+
`circular include: package("${identifier}", "${file}")`,
|
|
1383
|
+
`${identifier}/${file}`,
|
|
1384
|
+
0,
|
|
1385
|
+
0
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
if (includeStack.length >= 50) {
|
|
1389
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
|
|
1390
|
+
}
|
|
1391
|
+
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1392
|
+
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1393
|
+
const content = readFileSync2(resolvedPath);
|
|
1394
|
+
if (content.length === 0) return makeResObj();
|
|
1395
|
+
const tokens = tokenize(content);
|
|
1396
|
+
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1397
|
+
const ast = parseTokens(tokens);
|
|
1398
|
+
return this.onBuildResObj(ast, {
|
|
1399
|
+
...this.opts,
|
|
1400
|
+
baseDir: nodePath.dirname(resolvedPath),
|
|
1401
|
+
includeStack: [...includeStack, cycleKey]
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Async variant of `loadPackage`.
|
|
1406
|
+
*/
|
|
1407
|
+
async loadPackageAsync(identifier, file, _required) {
|
|
1408
|
+
validatePackageIdentifier(identifier);
|
|
1409
|
+
validatePackageFile(file, identifier);
|
|
1410
|
+
const { includeStack = [], baseDir, resolveFrom, packageResolver, readFile, readFileSync: readFileSync2 } = this.opts;
|
|
1411
|
+
const cycleKey = JSON.stringify(["package", identifier, file]);
|
|
1412
|
+
if (includeStack.includes(cycleKey)) {
|
|
1413
|
+
throw new ResolveError(
|
|
1414
|
+
`circular include: package("${identifier}", "${file}")`,
|
|
1415
|
+
`${identifier}/${file}`,
|
|
1416
|
+
0,
|
|
1417
|
+
0
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
if (includeStack.length >= 50) {
|
|
1421
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
|
|
1422
|
+
}
|
|
1423
|
+
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1424
|
+
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1425
|
+
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1426
|
+
const content = await read(resolvedPath);
|
|
1427
|
+
if (content.length === 0) return makeResObj();
|
|
1428
|
+
const tokens = tokenize(content);
|
|
1429
|
+
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1430
|
+
const ast = parseTokens(tokens);
|
|
1431
|
+
return this.onBuildResObjAsync(ast, {
|
|
1432
|
+
...this.opts,
|
|
1433
|
+
baseDir: nodePath.dirname(resolvedPath),
|
|
1434
|
+
includeStack: [...includeStack, cycleKey]
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1207
1437
|
loadSingle(candidate) {
|
|
1208
|
-
const { readFileSync: readFileSync2, includeStack = []
|
|
1438
|
+
const { readFileSync: readFileSync2, includeStack = [] } = this.opts;
|
|
1209
1439
|
if (includeStack.includes(candidate)) {
|
|
1210
1440
|
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
1211
1441
|
}
|
|
@@ -1219,16 +1449,17 @@ var IncludeLoader = class {
|
|
|
1219
1449
|
if (candidate.endsWith(".properties")) {
|
|
1220
1450
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1221
1451
|
}
|
|
1222
|
-
const
|
|
1452
|
+
const tokens = tokenize(content);
|
|
1453
|
+
assertNonEmptyDocument(tokens, candidate);
|
|
1454
|
+
const ast = parseTokens(tokens);
|
|
1223
1455
|
return this.onBuildResObj(ast, {
|
|
1224
|
-
|
|
1456
|
+
...this.opts,
|
|
1225
1457
|
baseDir: nodePath.dirname(candidate),
|
|
1226
|
-
readFileSync: readFileSync2,
|
|
1227
1458
|
includeStack: [...includeStack, candidate]
|
|
1228
1459
|
});
|
|
1229
1460
|
}
|
|
1230
1461
|
async loadSingleAsync(candidate) {
|
|
1231
|
-
const { readFile, readFileSync: readFileSync2, includeStack = []
|
|
1462
|
+
const { readFile, readFileSync: readFileSync2, includeStack = [] } = this.opts;
|
|
1232
1463
|
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1233
1464
|
if (includeStack.includes(candidate)) {
|
|
1234
1465
|
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
@@ -1243,12 +1474,12 @@ var IncludeLoader = class {
|
|
|
1243
1474
|
if (candidate.endsWith(".properties")) {
|
|
1244
1475
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1245
1476
|
}
|
|
1246
|
-
const
|
|
1477
|
+
const tokens = tokenize(content);
|
|
1478
|
+
assertNonEmptyDocument(tokens, candidate);
|
|
1479
|
+
const ast = parseTokens(tokens);
|
|
1247
1480
|
return this.onBuildResObjAsync(ast, {
|
|
1248
|
-
|
|
1481
|
+
...this.opts,
|
|
1249
1482
|
baseDir: nodePath.dirname(candidate),
|
|
1250
|
-
readFileSync: readFileSync2,
|
|
1251
|
-
readFile,
|
|
1252
1483
|
includeStack: [...includeStack, candidate]
|
|
1253
1484
|
});
|
|
1254
1485
|
}
|
|
@@ -1257,7 +1488,6 @@ var IncludeLoader = class {
|
|
|
1257
1488
|
// src/internal/resolver/structure-builder.ts
|
|
1258
1489
|
var StructureBuilder = class _StructureBuilder {
|
|
1259
1490
|
constructor(opts) {
|
|
1260
|
-
this.opts = opts;
|
|
1261
1491
|
this.loader = new IncludeLoader(opts);
|
|
1262
1492
|
this.loader.onBuildResObj = (a, o) => new _StructureBuilder(o).build(a);
|
|
1263
1493
|
this.loader.onBuildResObjAsync = async (a, o) => new _StructureBuilder(o).buildAsync(a);
|
|
@@ -1284,7 +1514,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1284
1514
|
}
|
|
1285
1515
|
applyField(obj, field, pathPrefix) {
|
|
1286
1516
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1287
|
-
const
|
|
1517
|
+
const { qualifier, path: path2, required } = field.value;
|
|
1518
|
+
let included;
|
|
1519
|
+
if (qualifier.kind === "package") {
|
|
1520
|
+
included = this.loader.loadPackage(qualifier.identifier, path2, required);
|
|
1521
|
+
} else {
|
|
1522
|
+
included = this.loader.load(path2, required, qualifier.kind === "file");
|
|
1523
|
+
}
|
|
1288
1524
|
if (pathPrefix.length > 0) {
|
|
1289
1525
|
this.relativizeResObj(included, pathPrefix);
|
|
1290
1526
|
}
|
|
@@ -1326,7 +1562,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1326
1562
|
}
|
|
1327
1563
|
async applyFieldAsync(obj, field, pathPrefix) {
|
|
1328
1564
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1329
|
-
const
|
|
1565
|
+
const { qualifier, path: path2, required } = field.value;
|
|
1566
|
+
let included;
|
|
1567
|
+
if (qualifier.kind === "package") {
|
|
1568
|
+
included = await this.loader.loadPackageAsync(qualifier.identifier, path2, required);
|
|
1569
|
+
} else {
|
|
1570
|
+
included = await this.loader.loadAsync(path2, required, qualifier.kind === "file");
|
|
1571
|
+
}
|
|
1330
1572
|
if (pathPrefix.length > 0) {
|
|
1331
1573
|
this.relativizeResObj(included, pathPrefix);
|
|
1332
1574
|
}
|
|
@@ -1380,9 +1622,9 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1380
1622
|
return inner;
|
|
1381
1623
|
}
|
|
1382
1624
|
case "subst":
|
|
1383
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1625
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1384
1626
|
case "concat":
|
|
1385
|
-
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)) };
|
|
1627
|
+
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
|
|
1386
1628
|
case "include":
|
|
1387
1629
|
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1388
1630
|
}
|
|
@@ -1404,13 +1646,13 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1404
1646
|
case "object":
|
|
1405
1647
|
return await this.buildAsync(ast, pathPrefix);
|
|
1406
1648
|
case "subst":
|
|
1407
|
-
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1649
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1408
1650
|
case "concat": {
|
|
1409
1651
|
const nodes = [];
|
|
1410
1652
|
for (const n of ast.nodes) {
|
|
1411
1653
|
nodes.push(await this.astToResolverValueAsync(n, pathPrefix));
|
|
1412
1654
|
}
|
|
1413
|
-
return { _kind: "concat-placeholder", nodes };
|
|
1655
|
+
return { _kind: "concat-placeholder", nodes, line: ast.pos.line, col: ast.pos.col };
|
|
1414
1656
|
}
|
|
1415
1657
|
case "include":
|
|
1416
1658
|
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
@@ -1456,6 +1698,23 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1456
1698
|
}
|
|
1457
1699
|
};
|
|
1458
1700
|
|
|
1701
|
+
// src/value/numeric-array.ts
|
|
1702
|
+
function numericObjectToArray(value) {
|
|
1703
|
+
if (value.kind !== "object") return null;
|
|
1704
|
+
if (value.fields.size === 0) return null;
|
|
1705
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
1706
|
+
const eligible = [];
|
|
1707
|
+
for (const [key, val] of value.fields) {
|
|
1708
|
+
if (!canonicalInt.test(key)) continue;
|
|
1709
|
+
const n = Number(key);
|
|
1710
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
1711
|
+
eligible.push({ n, v: val });
|
|
1712
|
+
}
|
|
1713
|
+
if (eligible.length === 0) return null;
|
|
1714
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
1715
|
+
return eligible.map((e) => e.v);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1459
1718
|
// src/internal/resolver/substitution-resolver.ts
|
|
1460
1719
|
var SubstitutionResolver = class {
|
|
1461
1720
|
constructor(root, opts) {
|
|
@@ -1463,6 +1722,23 @@ var SubstitutionResolver = class {
|
|
|
1463
1722
|
this.opts = opts;
|
|
1464
1723
|
this.resolving = /* @__PURE__ */ new Set();
|
|
1465
1724
|
this.cache = /* @__PURE__ */ new Map();
|
|
1725
|
+
// Tracks ConcatPlaceholder nodes currently being iterated by resolveConcat.
|
|
1726
|
+
// Used by isSelfRef detection: fires only when found === a concat currently
|
|
1727
|
+
// mid-iteration (i.e. we are inside found's own resolution), preventing the
|
|
1728
|
+
// "foofoo" double-count without misfiring when an external field looks up the
|
|
1729
|
+
// same concat via a substitution (multi-reviewer convergence fix).
|
|
1730
|
+
//
|
|
1731
|
+
// Spec deviation: the S13a.13 spec ★1 decision #1 specified path-equality
|
|
1732
|
+
// preservation for self-ref detection. Round-2 multi-agent-review surfaced a
|
|
1733
|
+
// false-positive on external lookups (`a = ${?a}foo; b = ${a}`), so the
|
|
1734
|
+
// criterion was tightened to node-membership-in-iterating-set — strictly
|
|
1735
|
+
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
1736
|
+
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
1737
|
+
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
1738
|
+
}
|
|
1739
|
+
/** Returns a prefix for error messages, including origin info when set. */
|
|
1740
|
+
originPrefix() {
|
|
1741
|
+
return this.opts.originDescription ? `${this.opts.originDescription}: ` : "";
|
|
1466
1742
|
}
|
|
1467
1743
|
resolve() {
|
|
1468
1744
|
return this.resolveResObj(this.root);
|
|
@@ -1501,7 +1777,15 @@ var SubstitutionResolver = class {
|
|
|
1501
1777
|
}
|
|
1502
1778
|
resolveVal(v, scope) {
|
|
1503
1779
|
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
1504
|
-
if (isConcat(v))
|
|
1780
|
+
if (isConcat(v)) {
|
|
1781
|
+
this.resolvingConcats.add(v);
|
|
1782
|
+
try {
|
|
1783
|
+
const concatResult = this.resolveConcat(v.nodes, scope, v.line, v.col);
|
|
1784
|
+
return concatResult;
|
|
1785
|
+
} finally {
|
|
1786
|
+
this.resolvingConcats.delete(v);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1505
1789
|
if (isAppend(v)) return this.resolveAppend(v, scope);
|
|
1506
1790
|
if (isResObj(v)) return this.resolveResObj(v);
|
|
1507
1791
|
const hv = v;
|
|
@@ -1515,15 +1799,12 @@ var SubstitutionResolver = class {
|
|
|
1515
1799
|
}
|
|
1516
1800
|
return hv;
|
|
1517
1801
|
}
|
|
1518
|
-
segmentsEqual(a, b) {
|
|
1519
|
-
return a.length === b.length && a.every((seg, i) => seg.text === b[i]?.text);
|
|
1520
|
-
}
|
|
1521
1802
|
resolveSubst(s, scope) {
|
|
1522
|
-
const key = segmentsToKey(s.segments);
|
|
1803
|
+
const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
1523
1804
|
if (this.cache.has(key)) return this.cache.get(key);
|
|
1524
1805
|
if (this.resolving.has(key)) {
|
|
1525
1806
|
let prior;
|
|
1526
|
-
if (s.
|
|
1807
|
+
if (s.segments.length > 1) {
|
|
1527
1808
|
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1528
1809
|
const parentScope = lookupResObj(
|
|
1529
1810
|
this.root,
|
|
@@ -1545,7 +1826,7 @@ var SubstitutionResolver = class {
|
|
|
1545
1826
|
}
|
|
1546
1827
|
if (s.optional) return void 0;
|
|
1547
1828
|
throw new ResolveError(
|
|
1548
|
-
`circular substitution: ${key}`,
|
|
1829
|
+
this.originPrefix() + `circular substitution: ${key}`,
|
|
1549
1830
|
key,
|
|
1550
1831
|
s.line,
|
|
1551
1832
|
s.col
|
|
@@ -1555,29 +1836,31 @@ var SubstitutionResolver = class {
|
|
|
1555
1836
|
try {
|
|
1556
1837
|
const found = lookupPath(this.root, s.segments);
|
|
1557
1838
|
if (found !== void 0) {
|
|
1558
|
-
if (
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
if (prior !== void 0) {
|
|
1576
|
-
const result2 = this.resolveVal(prior, scope);
|
|
1577
|
-
if (result2 !== void 0) this.cache.set(key, result2);
|
|
1578
|
-
return result2;
|
|
1579
|
-
}
|
|
1839
|
+
if (isConcat(found) && this.resolvingConcats.has(found)) {
|
|
1840
|
+
let prior;
|
|
1841
|
+
if (s.segments.length > 1) {
|
|
1842
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1843
|
+
const parentScope = lookupResObj(
|
|
1844
|
+
this.root,
|
|
1845
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
1846
|
+
);
|
|
1847
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
1848
|
+
} else {
|
|
1849
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1850
|
+
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1851
|
+
}
|
|
1852
|
+
if (prior !== void 0) {
|
|
1853
|
+
const result2 = this.resolveVal(prior, scope);
|
|
1854
|
+
if (result2 !== void 0) this.cache.set(key, result2);
|
|
1855
|
+
return result2;
|
|
1580
1856
|
}
|
|
1857
|
+
if (s.optional) return void 0;
|
|
1858
|
+
throw new ResolveError(
|
|
1859
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1860
|
+
key,
|
|
1861
|
+
s.line,
|
|
1862
|
+
s.col
|
|
1863
|
+
);
|
|
1581
1864
|
}
|
|
1582
1865
|
let result = this.resolveVal(found, scope);
|
|
1583
1866
|
const effectiveLen = s.segments.length - s.prefixLen;
|
|
@@ -1606,16 +1889,45 @@ var SubstitutionResolver = class {
|
|
|
1606
1889
|
if (result !== void 0) this.cache.set(key, result);
|
|
1607
1890
|
return result;
|
|
1608
1891
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1892
|
+
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
1893
|
+
const result = this.resolveEnvList(s);
|
|
1894
|
+
if (result !== void 0) {
|
|
1895
|
+
this.cache.set(key, result);
|
|
1896
|
+
return result;
|
|
1897
|
+
}
|
|
1898
|
+
if (s.optional) return void 0;
|
|
1899
|
+
const envBase = s.segments.map((seg) => seg.text).join(".");
|
|
1900
|
+
throw new ResolveError(
|
|
1901
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}} (no environment variables ${envBase}_0, ${envBase}_1, \u2026 set)`,
|
|
1902
|
+
key,
|
|
1903
|
+
s.line,
|
|
1904
|
+
s.col
|
|
1905
|
+
);
|
|
1906
|
+
} else if (s.listSuffix) {
|
|
1907
|
+
if (s.optional) return void 0;
|
|
1908
|
+
if (this.opts.allowUnresolved) return s;
|
|
1909
|
+
throw new ResolveError(
|
|
1910
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1911
|
+
key,
|
|
1912
|
+
s.line,
|
|
1913
|
+
s.col
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
if (this.opts.useSystemEnvironment !== false) {
|
|
1917
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
1918
|
+
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
|
|
1919
|
+
if (envVal !== void 0) {
|
|
1920
|
+
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
1921
|
+
this.cache.set(key, result);
|
|
1922
|
+
return result;
|
|
1923
|
+
}
|
|
1615
1924
|
}
|
|
1616
1925
|
if (s.optional) return void 0;
|
|
1926
|
+
if (this.opts.allowUnresolved) {
|
|
1927
|
+
return s;
|
|
1928
|
+
}
|
|
1617
1929
|
throw new ResolveError(
|
|
1618
|
-
|
|
1930
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
1619
1931
|
key,
|
|
1620
1932
|
s.line,
|
|
1621
1933
|
s.col
|
|
@@ -1624,9 +1936,52 @@ var SubstitutionResolver = class {
|
|
|
1624
1936
|
this.resolving.delete(key);
|
|
1625
1937
|
}
|
|
1626
1938
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1939
|
+
/**
|
|
1940
|
+
* S13c: scan environment for NAME_0, NAME_1, … and return an Array<Scalar(String)>.
|
|
1941
|
+
*
|
|
1942
|
+
* Candidate bases follow the same fully-qualified→bare order used by the scalar
|
|
1943
|
+
* env fallback. The first base whose _0 key is present wins entirely — no
|
|
1944
|
+
* cross-base merging (spec §Relativized fallback precedence rules).
|
|
1945
|
+
*
|
|
1946
|
+
* Returns the HoconValue Array if any candidate base has _0, or undefined if
|
|
1947
|
+
* none do (caller applies optional/required handling).
|
|
1948
|
+
*/
|
|
1949
|
+
resolveEnvList(s) {
|
|
1950
|
+
const bases = this.candidateBases(s);
|
|
1951
|
+
for (const base of bases) {
|
|
1952
|
+
const key0 = base + "_0";
|
|
1953
|
+
if (!(key0 in this.opts.env)) continue;
|
|
1954
|
+
const items = [];
|
|
1955
|
+
for (let i = 0; ; i++) {
|
|
1956
|
+
const k = base + "_" + i;
|
|
1957
|
+
if (!(k in this.opts.env)) break;
|
|
1958
|
+
items.push({ kind: "scalar", raw: this.opts.env[k], valueType: "string" });
|
|
1959
|
+
}
|
|
1960
|
+
return { kind: "array", items };
|
|
1961
|
+
}
|
|
1962
|
+
return void 0;
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Return the candidate base names for env-var-list lookup, in precedence order.
|
|
1966
|
+
* Mirrors the fully-qualified→bare fallback used by the scalar env path.
|
|
1967
|
+
*/
|
|
1968
|
+
candidateBases(s) {
|
|
1969
|
+
const full = s.segments.map((seg) => seg.text).join(".");
|
|
1970
|
+
if (s.prefixLen === 0) return [full];
|
|
1971
|
+
const bare = s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".");
|
|
1972
|
+
return [full, bare];
|
|
1973
|
+
}
|
|
1974
|
+
resolveConcat(nodes, scope, line = 0, col = 0) {
|
|
1975
|
+
const rawResolved = nodes.map((n) => this.resolveVal(n, scope));
|
|
1976
|
+
if (this.opts.allowUnresolved) {
|
|
1977
|
+
for (const v of rawResolved) {
|
|
1978
|
+
if (v !== void 0 && isSubst(v)) {
|
|
1979
|
+
return v;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
const resolved = rawResolved.filter((v) => v !== void 0);
|
|
1984
|
+
if (resolved.length === 0) return void 0;
|
|
1630
1985
|
if (resolved.length === 1) return resolved[0];
|
|
1631
1986
|
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1632
1987
|
if (nonSep.length === 0) {
|
|
@@ -1642,27 +1997,45 @@ var SubstitutionResolver = class {
|
|
|
1642
1997
|
if (converted !== null) {
|
|
1643
1998
|
return { kind: "array", items: [...left.items, ...converted] };
|
|
1644
1999
|
}
|
|
1645
|
-
|
|
2000
|
+
throw new ResolveError(
|
|
2001
|
+
"cannot concatenate array with object: value concatenation requires same-kind operands (S10.4)",
|
|
2002
|
+
"",
|
|
2003
|
+
line,
|
|
2004
|
+
col
|
|
2005
|
+
);
|
|
1646
2006
|
}
|
|
1647
2007
|
if (left.kind === "object" && right.kind === "array") {
|
|
1648
2008
|
const converted = numericObjectToArray(left);
|
|
1649
2009
|
if (converted !== null) {
|
|
1650
2010
|
return { kind: "array", items: [...converted, ...right.items] };
|
|
1651
2011
|
}
|
|
1652
|
-
|
|
2012
|
+
throw new ResolveError(
|
|
2013
|
+
"cannot concatenate object with array: value concatenation requires same-kind operands (S10.4)",
|
|
2014
|
+
"",
|
|
2015
|
+
line,
|
|
2016
|
+
col
|
|
2017
|
+
);
|
|
1653
2018
|
}
|
|
1654
2019
|
if (left.kind === "array" && right.kind === "array") {
|
|
1655
2020
|
return { kind: "array", items: [...left.items, ...right.items] };
|
|
1656
2021
|
}
|
|
1657
|
-
if (left.kind === "array") {
|
|
1658
|
-
|
|
2022
|
+
if (left.kind === "array" || right.kind === "array") {
|
|
2023
|
+
throw new ResolveError(
|
|
2024
|
+
`cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
|
|
2025
|
+
"",
|
|
2026
|
+
line,
|
|
2027
|
+
col
|
|
2028
|
+
);
|
|
1659
2029
|
}
|
|
1660
|
-
if (right.kind === "
|
|
1661
|
-
|
|
2030
|
+
if (left.kind === "object" || right.kind === "object") {
|
|
2031
|
+
throw new ResolveError(
|
|
2032
|
+
`cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
|
|
2033
|
+
"",
|
|
2034
|
+
line,
|
|
2035
|
+
col
|
|
2036
|
+
);
|
|
1662
2037
|
}
|
|
1663
|
-
|
|
1664
|
-
const rightStr = right.kind === "scalar" ? right.raw : JSON.stringify(right);
|
|
1665
|
-
return { kind: "scalar", raw: leftStr + rightStr, valueType: "string" };
|
|
2038
|
+
return { kind: "scalar", raw: left.raw + right.raw, valueType: "string" };
|
|
1666
2039
|
};
|
|
1667
2040
|
const [head, ...tail] = nonSep;
|
|
1668
2041
|
const folded = tail.reduce(joinPair, head);
|
|
@@ -1682,18 +2055,503 @@ var SubstitutionResolver = class {
|
|
|
1682
2055
|
};
|
|
1683
2056
|
|
|
1684
2057
|
// src/internal/resolver/resolver.ts
|
|
2058
|
+
function buildTree(ast, opts) {
|
|
2059
|
+
return new StructureBuilder(opts).build(ast);
|
|
2060
|
+
}
|
|
2061
|
+
function resolveTree(tree, opts) {
|
|
2062
|
+
return new SubstitutionResolver(tree, opts).resolve();
|
|
2063
|
+
}
|
|
2064
|
+
function containsPlaceholders(tree) {
|
|
2065
|
+
for (const val of tree.fields.values()) {
|
|
2066
|
+
if (valContainsPlaceholders(val)) return true;
|
|
2067
|
+
}
|
|
2068
|
+
return false;
|
|
2069
|
+
}
|
|
2070
|
+
function valContainsPlaceholders(v) {
|
|
2071
|
+
const rv = v;
|
|
2072
|
+
if (isSubst(rv) || isConcat(rv) || isAppend(rv)) return true;
|
|
2073
|
+
if (isResObj(rv)) return containsPlaceholders(rv);
|
|
2074
|
+
const hv = v;
|
|
2075
|
+
if (hv?.kind === "array") {
|
|
2076
|
+
return hv.items.some((item) => valContainsPlaceholders(item));
|
|
2077
|
+
}
|
|
2078
|
+
return false;
|
|
2079
|
+
}
|
|
2080
|
+
function buildPartialHoconFromResObj(tree) {
|
|
2081
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2082
|
+
for (const [k, v] of tree.fields) {
|
|
2083
|
+
if (!isSubst(v) && !isConcat(v) && !isAppend(v)) {
|
|
2084
|
+
if (isResObj(v)) {
|
|
2085
|
+
fields.set(k, buildPartialHoconFromResObj(v));
|
|
2086
|
+
} else {
|
|
2087
|
+
if (!valContainsPlaceholders(v)) {
|
|
2088
|
+
fields.set(k, v);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return { kind: "object", fields };
|
|
2094
|
+
}
|
|
2095
|
+
function hoconValueToResObj2(value) {
|
|
2096
|
+
const obj = makeResObj();
|
|
2097
|
+
for (const [k, v] of value.fields) {
|
|
2098
|
+
if (v.kind === "object") {
|
|
2099
|
+
obj.fields.set(k, hoconValueToResObj2(v));
|
|
2100
|
+
} else {
|
|
2101
|
+
obj.fields.set(k, v);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return obj;
|
|
2105
|
+
}
|
|
1685
2106
|
function resolve2(ast, opts) {
|
|
1686
|
-
const
|
|
1687
|
-
return
|
|
2107
|
+
const tree = buildTree(ast, opts);
|
|
2108
|
+
return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
|
|
1688
2109
|
}
|
|
1689
2110
|
async function resolveAsync(ast, opts) {
|
|
1690
2111
|
const root = await new StructureBuilder(opts).buildAsync(ast);
|
|
1691
|
-
return
|
|
2112
|
+
return resolveTree(root, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// src/config.ts
|
|
2116
|
+
var Config = class _Config {
|
|
2117
|
+
constructor(root, opts) {
|
|
2118
|
+
this.root = root;
|
|
2119
|
+
this._resolved = opts?.resolved ?? true;
|
|
2120
|
+
this._parseBaseDir = opts?.parseBaseDir;
|
|
2121
|
+
this._originDescription = opts?.originDescription;
|
|
2122
|
+
this._resObjRoot = opts?.resObjRoot;
|
|
2123
|
+
this._resolveOpts = opts?.resolveOpts;
|
|
2124
|
+
}
|
|
2125
|
+
/** Returns true when the Config's value tree contains no unresolved
|
|
2126
|
+
* substitution placeholders. Whole-config granularity (E12 decision 11). */
|
|
2127
|
+
isResolved() {
|
|
2128
|
+
return this._resolved;
|
|
2129
|
+
}
|
|
2130
|
+
/** @internal Test-only: construct a resolved Config from a HoconValue. */
|
|
2131
|
+
static _fromResolvedValue(root, opts) {
|
|
2132
|
+
return new _Config(root, { resolved: true, originDescription: opts?.originDescription });
|
|
2133
|
+
}
|
|
2134
|
+
/** @internal Construct an unresolved Config carrying a ResObj tree.
|
|
2135
|
+
* Used by parseStringWithOptions (deferred path) in T6. */
|
|
2136
|
+
static _fromUnresolvedResObj(tree, opts) {
|
|
2137
|
+
const partialRoot = buildPartialHoconFromResObj(tree);
|
|
2138
|
+
return new _Config(partialRoot, {
|
|
2139
|
+
resolved: opts.resolved,
|
|
2140
|
+
parseBaseDir: opts.parseBaseDir,
|
|
2141
|
+
originDescription: opts.originDescription,
|
|
2142
|
+
resObjRoot: tree,
|
|
2143
|
+
resolveOpts: opts.resolveOpts
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
get(path2) {
|
|
2147
|
+
const v = this.lookupNode(path2);
|
|
2148
|
+
if (v === void 0) return void 0;
|
|
2149
|
+
return hoconToJs(v);
|
|
2150
|
+
}
|
|
2151
|
+
getString(path2) {
|
|
2152
|
+
const v = this.requireScalar(path2);
|
|
2153
|
+
return v.raw;
|
|
2154
|
+
}
|
|
2155
|
+
getNumber(path2) {
|
|
2156
|
+
const v = this.requireScalar(path2);
|
|
2157
|
+
const coerced = coerceNumber(v.raw);
|
|
2158
|
+
if (coerced !== void 0) return coerced;
|
|
2159
|
+
throw new ConfigError(`expected number at ${path2}, got ${v.valueType}`, path2);
|
|
2160
|
+
}
|
|
2161
|
+
getBoolean(path2) {
|
|
2162
|
+
const v = this.requireScalar(path2);
|
|
2163
|
+
const coerced = coerceBoolean(v.raw);
|
|
2164
|
+
if (coerced !== void 0) return coerced;
|
|
2165
|
+
throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
|
|
2166
|
+
}
|
|
2167
|
+
getDuration(path2, unit) {
|
|
2168
|
+
const v = this.requireScalar(path2);
|
|
2169
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
2170
|
+
throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
|
|
2171
|
+
}
|
|
2172
|
+
const result = parseDuration(v.raw, unit);
|
|
2173
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2174
|
+
return result;
|
|
2175
|
+
}
|
|
2176
|
+
getBytes(path2, unit) {
|
|
2177
|
+
const v = this.requireScalar(path2);
|
|
2178
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
2179
|
+
throw new ConfigError(`expected byte size at ${path2}, got ${v.valueType}`, path2);
|
|
2180
|
+
}
|
|
2181
|
+
const result = parseBytes(v.raw, unit);
|
|
2182
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2183
|
+
if (result < 0) throw new ConfigError(`byte size must be non-negative at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
2184
|
+
return result;
|
|
2185
|
+
}
|
|
2186
|
+
getConfig(path2) {
|
|
2187
|
+
const v = this.lookupNode(path2);
|
|
2188
|
+
if (v === void 0) {
|
|
2189
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2190
|
+
throw new NotResolvedError(path2);
|
|
2191
|
+
}
|
|
2192
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2193
|
+
}
|
|
2194
|
+
if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
|
|
2195
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2196
|
+
throw new NotResolvedError(path2);
|
|
2197
|
+
}
|
|
2198
|
+
return new _Config(v, { resolved: this._resolved });
|
|
2199
|
+
}
|
|
2200
|
+
getList(path2) {
|
|
2201
|
+
const v = this.lookupNode(path2);
|
|
2202
|
+
if (v === void 0) {
|
|
2203
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2204
|
+
throw new NotResolvedError(path2);
|
|
2205
|
+
}
|
|
2206
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2207
|
+
}
|
|
2208
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2209
|
+
throw new NotResolvedError(path2);
|
|
2210
|
+
}
|
|
2211
|
+
if (v.kind === "object") {
|
|
2212
|
+
const converted = numericObjectToArray(v);
|
|
2213
|
+
if (converted !== null) return converted.map(hoconToJs);
|
|
2214
|
+
}
|
|
2215
|
+
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
2216
|
+
return v.items.map(hoconToJs);
|
|
2217
|
+
}
|
|
2218
|
+
has(path2) {
|
|
2219
|
+
return this.lookupNode(path2) !== void 0;
|
|
2220
|
+
}
|
|
2221
|
+
keys() {
|
|
2222
|
+
return [...this.root.fields.keys()];
|
|
2223
|
+
}
|
|
2224
|
+
withFallback(fallback) {
|
|
2225
|
+
if (fallback == null) return this;
|
|
2226
|
+
const selfTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
|
|
2227
|
+
const fbTree = fallback._resObjRoot ?? hoconValueToResObj2(fallback.root);
|
|
2228
|
+
const merged = mergeUnresolved(selfTree, fbTree);
|
|
2229
|
+
const hasPlaceholders = containsPlaceholders(merged);
|
|
2230
|
+
const partialRoot = buildPartialHoconFromResObj(merged);
|
|
2231
|
+
return new _Config(partialRoot, {
|
|
2232
|
+
resolved: !hasPlaceholders,
|
|
2233
|
+
parseBaseDir: this._parseBaseDir,
|
|
2234
|
+
originDescription: this._originDescription,
|
|
2235
|
+
resObjRoot: merged,
|
|
2236
|
+
resolveOpts: this._resolveOpts ?? fallback._resolveOpts
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Runs substitution resolution (phase 2) on the stored unresolved tree.
|
|
2241
|
+
* Idempotent: calling resolve() on an already-resolved Config returns an
|
|
2242
|
+
* equivalent resolved Config.
|
|
2243
|
+
*
|
|
2244
|
+
* opts.allowUnresolved (default false) — when true, leaves unresolved
|
|
2245
|
+
* non-optional placeholders in place rather than throwing.
|
|
2246
|
+
* opts.useSystemEnvironment (default true) — when false, env var lookups
|
|
2247
|
+
* are suppressed (hermetic resolution).
|
|
2248
|
+
*
|
|
2249
|
+
* E12 decision 3.
|
|
2250
|
+
*/
|
|
2251
|
+
resolve(opts = {}) {
|
|
2252
|
+
if (this._resolved) {
|
|
2253
|
+
return new _Config(this.root, {
|
|
2254
|
+
resolved: true,
|
|
2255
|
+
parseBaseDir: this._parseBaseDir,
|
|
2256
|
+
originDescription: this._originDescription
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
const tree = this._resObjRoot;
|
|
2260
|
+
if (!tree) {
|
|
2261
|
+
return new _Config(this.root, { resolved: true });
|
|
2262
|
+
}
|
|
2263
|
+
const resolveOpts = {
|
|
2264
|
+
...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
|
|
2265
|
+
throw new Error("no files");
|
|
2266
|
+
} },
|
|
2267
|
+
allowUnresolved: opts.allowUnresolved ?? false,
|
|
2268
|
+
useSystemEnvironment: opts.useSystemEnvironment ?? true,
|
|
2269
|
+
originDescription: this._originDescription
|
|
2270
|
+
};
|
|
2271
|
+
const resolved = resolveTree(tree, resolveOpts);
|
|
2272
|
+
if (resolved.kind !== "object") throw new Error("resolve: expected object root");
|
|
2273
|
+
if (opts.allowUnresolved) {
|
|
2274
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(resolved);
|
|
2275
|
+
return new _Config(stripped, {
|
|
2276
|
+
resolved: !hadPlaceholders,
|
|
2277
|
+
parseBaseDir: this._parseBaseDir,
|
|
2278
|
+
originDescription: this._originDescription,
|
|
2279
|
+
resObjRoot: hadPlaceholders ? tree : void 0,
|
|
2280
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
return new _Config(resolved, {
|
|
2284
|
+
resolved: true,
|
|
2285
|
+
parseBaseDir: this._parseBaseDir,
|
|
2286
|
+
originDescription: this._originDescription
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* Resolves receiver substitutions using source as lookup context.
|
|
2291
|
+
* Source's keys are NOT merged into the result — differs from
|
|
2292
|
+
* `this.withFallback(source).resolve()` which includes source keys.
|
|
2293
|
+
*
|
|
2294
|
+
* Precondition (E12 decision 10): source must be resolved. If
|
|
2295
|
+
* source.isResolved() is false, throws NotResolvedError.
|
|
2296
|
+
*
|
|
2297
|
+
* On an already-resolved receiver, resolveWith is a no-op (returns an
|
|
2298
|
+
* equivalent resolved Config, source keys not included).
|
|
2299
|
+
*
|
|
2300
|
+
* E12 decisions 9, 10.
|
|
2301
|
+
*/
|
|
2302
|
+
resolveWith(source, opts = {}) {
|
|
2303
|
+
if (!source.isResolved()) {
|
|
2304
|
+
throw new NotResolvedError("source");
|
|
2305
|
+
}
|
|
2306
|
+
if (this._resolved) {
|
|
2307
|
+
return new _Config(this.root, {
|
|
2308
|
+
resolved: true,
|
|
2309
|
+
parseBaseDir: this._parseBaseDir,
|
|
2310
|
+
originDescription: this._originDescription
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
const receiverTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
|
|
2314
|
+
const srcTree = hoconValueToResObj2(source.root);
|
|
2315
|
+
const merged = mergeUnresolved(receiverTree, srcTree);
|
|
2316
|
+
const resolveOpts = {
|
|
2317
|
+
...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
|
|
2318
|
+
throw new Error("no files");
|
|
2319
|
+
} },
|
|
2320
|
+
allowUnresolved: opts.allowUnresolved ?? false,
|
|
2321
|
+
useSystemEnvironment: opts.useSystemEnvironment ?? true,
|
|
2322
|
+
originDescription: this._originDescription
|
|
2323
|
+
};
|
|
2324
|
+
const resolved = resolveTree(merged, resolveOpts);
|
|
2325
|
+
if (resolved.kind !== "object") throw new Error("resolveWith: expected object root");
|
|
2326
|
+
const receiverShapeForFilter = resObjToKeyShape(receiverTree);
|
|
2327
|
+
const filtered = filterByReceiverShape(resolved, receiverShapeForFilter);
|
|
2328
|
+
if (opts.allowUnresolved) {
|
|
2329
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(filtered);
|
|
2330
|
+
return new _Config(stripped, {
|
|
2331
|
+
resolved: !hadPlaceholders,
|
|
2332
|
+
parseBaseDir: this._parseBaseDir,
|
|
2333
|
+
originDescription: this._originDescription,
|
|
2334
|
+
resObjRoot: hadPlaceholders ? receiverTree : void 0,
|
|
2335
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
return new _Config(filtered, {
|
|
2339
|
+
resolved: true,
|
|
2340
|
+
parseBaseDir: this._parseBaseDir,
|
|
2341
|
+
originDescription: this._originDescription
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
toObject() {
|
|
2345
|
+
return hoconToJs(this.root);
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* @internal Test-only: renders this resolved Config's value tree as canonical
|
|
2349
|
+
* JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
|
|
2350
|
+
* against Lightbend ground truth. NOT part of the public API.
|
|
2351
|
+
*
|
|
2352
|
+
* Throws if the Config is unresolved (contains placeholders).
|
|
2353
|
+
*/
|
|
2354
|
+
_renderJSONForTest() {
|
|
2355
|
+
return renderHoconAsJSON(this.root);
|
|
2356
|
+
}
|
|
2357
|
+
lookupNode(path2) {
|
|
2358
|
+
const segments = splitConfigPath(path2);
|
|
2359
|
+
let current = this.root;
|
|
2360
|
+
for (const seg of segments) {
|
|
2361
|
+
if (current.kind !== "object") return void 0;
|
|
2362
|
+
const next = current.fields.get(seg);
|
|
2363
|
+
if (next === void 0) return void 0;
|
|
2364
|
+
current = next;
|
|
2365
|
+
}
|
|
2366
|
+
return current;
|
|
2367
|
+
}
|
|
2368
|
+
/**
|
|
2369
|
+
* _resObjRootSubtreeHasPlaceholders — walk _resObjRoot to the given path and
|
|
2370
|
+
* return true if the subtree there contains any unresolved placeholder.
|
|
2371
|
+
* Used by getConfig / getList (T2) to detect transitive unresolved placeholders
|
|
2372
|
+
* in object / array subtrees that would otherwise appear to be "resolved" in the
|
|
2373
|
+
* partial HoconValue root (because individual leaf fields are omitted rather than
|
|
2374
|
+
* the whole subtree when their value is a direct placeholder scalar, but object
|
|
2375
|
+
* sub-containers are retained in the partial root with the placeholder-keyed
|
|
2376
|
+
* fields omitted from them — so the object appears "present but empty-ish").
|
|
2377
|
+
*/
|
|
2378
|
+
_resObjRootSubtreeHasPlaceholders(path2) {
|
|
2379
|
+
const tree = this._resObjRoot;
|
|
2380
|
+
if (!tree) return false;
|
|
2381
|
+
const segments = splitConfigPath(path2);
|
|
2382
|
+
let cur = tree;
|
|
2383
|
+
for (const seg of segments) {
|
|
2384
|
+
const val = cur.fields.get(seg);
|
|
2385
|
+
if (val === void 0) return false;
|
|
2386
|
+
if (isResObj(val)) {
|
|
2387
|
+
cur = val;
|
|
2388
|
+
} else {
|
|
2389
|
+
return valContainsPlaceholders(val);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
return containsPlaceholders(cur);
|
|
2393
|
+
}
|
|
2394
|
+
requireScalar(path2) {
|
|
2395
|
+
const v = this.lookupNode(path2);
|
|
2396
|
+
if (v === void 0) {
|
|
2397
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2398
|
+
throw new NotResolvedError(path2);
|
|
2399
|
+
}
|
|
2400
|
+
throw new ConfigError(`path not found: ${path2}`, path2);
|
|
2401
|
+
}
|
|
2402
|
+
if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
|
|
2403
|
+
return v;
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2406
|
+
function splitConfigPath(path2) {
|
|
2407
|
+
const segments = [];
|
|
2408
|
+
let i = 0;
|
|
2409
|
+
while (i < path2.length) {
|
|
2410
|
+
if (path2[i] === '"') {
|
|
2411
|
+
i++;
|
|
2412
|
+
let segment = "";
|
|
2413
|
+
let closed = false;
|
|
2414
|
+
while (i < path2.length) {
|
|
2415
|
+
const ch = path2[i];
|
|
2416
|
+
if (ch === "\\" && i + 1 < path2.length) {
|
|
2417
|
+
const next = path2[i + 1];
|
|
2418
|
+
segment += next;
|
|
2419
|
+
i += 2;
|
|
2420
|
+
continue;
|
|
2421
|
+
}
|
|
2422
|
+
if (ch === '"') {
|
|
2423
|
+
closed = true;
|
|
2424
|
+
i++;
|
|
2425
|
+
break;
|
|
2426
|
+
}
|
|
2427
|
+
segment += ch;
|
|
2428
|
+
i++;
|
|
2429
|
+
}
|
|
2430
|
+
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
2431
|
+
segments.push(segment);
|
|
2432
|
+
if (i < path2.length && path2[i] === ".") i++;
|
|
2433
|
+
} else {
|
|
2434
|
+
const dot = path2.indexOf(".", i);
|
|
2435
|
+
if (dot === -1) {
|
|
2436
|
+
segments.push(path2.slice(i));
|
|
2437
|
+
break;
|
|
2438
|
+
}
|
|
2439
|
+
segments.push(path2.slice(i, dot));
|
|
2440
|
+
i = dot + 1;
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
return segments;
|
|
2444
|
+
}
|
|
2445
|
+
function scalarToJs(raw, valueType) {
|
|
2446
|
+
switch (valueType) {
|
|
2447
|
+
case "null":
|
|
2448
|
+
return null;
|
|
2449
|
+
case "boolean":
|
|
2450
|
+
return raw === "true";
|
|
2451
|
+
case "number":
|
|
2452
|
+
return Number(raw);
|
|
2453
|
+
case "string":
|
|
2454
|
+
return raw;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
function hoconToJs(v) {
|
|
2458
|
+
switch (v.kind) {
|
|
2459
|
+
case "scalar":
|
|
2460
|
+
return scalarToJs(v.raw, v.valueType);
|
|
2461
|
+
case "array":
|
|
2462
|
+
return v.items.map(hoconToJs);
|
|
2463
|
+
case "object": {
|
|
2464
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
2465
|
+
for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
|
|
2466
|
+
return Object.assign({}, obj);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
function resObjToKeyShape(tree) {
|
|
2471
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2472
|
+
for (const [k, v] of tree.fields) {
|
|
2473
|
+
if (isSubst(v) || isConcat(v) || isAppend(v)) {
|
|
2474
|
+
fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
|
|
2475
|
+
} else if (isResObj(v)) {
|
|
2476
|
+
fields.set(k, resObjToKeyShape(v));
|
|
2477
|
+
} else {
|
|
2478
|
+
fields.set(k, v);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
return { kind: "object", fields };
|
|
2482
|
+
}
|
|
2483
|
+
function stripPlaceholderFields(v) {
|
|
2484
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2485
|
+
let hadPlaceholders = false;
|
|
2486
|
+
for (const [k, val] of v.fields) {
|
|
2487
|
+
if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder" || val._kind === "append-placeholder") {
|
|
2488
|
+
hadPlaceholders = true;
|
|
2489
|
+
} else if (val.kind === "object") {
|
|
2490
|
+
const inner = stripPlaceholderFields(val);
|
|
2491
|
+
if (inner.hadPlaceholders) hadPlaceholders = true;
|
|
2492
|
+
fields.set(k, inner.stripped);
|
|
2493
|
+
} else {
|
|
2494
|
+
fields.set(k, val);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
return { stripped: { kind: "object", fields }, hadPlaceholders };
|
|
2498
|
+
}
|
|
2499
|
+
function filterByReceiverShape(resolved, receiverShape) {
|
|
2500
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2501
|
+
for (const [k, rv] of resolved.fields) {
|
|
2502
|
+
if (!receiverShape.fields.has(k)) continue;
|
|
2503
|
+
const receiverVal = receiverShape.fields.get(k);
|
|
2504
|
+
if (rv.kind === "object" && receiverVal.kind === "object") {
|
|
2505
|
+
fields.set(k, filterByReceiverShape(rv, receiverVal));
|
|
2506
|
+
} else {
|
|
2507
|
+
fields.set(k, rv);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
return { kind: "object", fields };
|
|
2511
|
+
}
|
|
2512
|
+
function renderHoconAsJSON(v) {
|
|
2513
|
+
switch (v.kind) {
|
|
2514
|
+
case "scalar": {
|
|
2515
|
+
switch (v.valueType) {
|
|
2516
|
+
case "null":
|
|
2517
|
+
return "null";
|
|
2518
|
+
case "boolean":
|
|
2519
|
+
return v.raw;
|
|
2520
|
+
case "number": {
|
|
2521
|
+
const n = Number(v.raw);
|
|
2522
|
+
if (Number.isFinite(n)) return v.raw;
|
|
2523
|
+
return JSON.stringify(v.raw);
|
|
2524
|
+
}
|
|
2525
|
+
case "string":
|
|
2526
|
+
return JSON.stringify(v.raw);
|
|
2527
|
+
}
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2530
|
+
case "array": {
|
|
2531
|
+
const items = v.items.map(renderHoconAsJSON);
|
|
2532
|
+
return `[${items.join(",")}]`;
|
|
2533
|
+
}
|
|
2534
|
+
case "object": {
|
|
2535
|
+
const keys = [...v.fields.keys()].sort();
|
|
2536
|
+
const pairs = keys.map((k) => {
|
|
2537
|
+
const val = v.fields.get(k);
|
|
2538
|
+
return `${JSON.stringify(k)}:${renderHoconAsJSON(val)}`;
|
|
2539
|
+
});
|
|
2540
|
+
return `{${pairs.join(",")}}`;
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
throw new Error(`renderHoconAsJSON: unsupported value kind (unresolved placeholder?)`);
|
|
1692
2544
|
}
|
|
1693
2545
|
|
|
1694
2546
|
// src/parse.ts
|
|
1695
2547
|
import * as fs from "fs";
|
|
1696
2548
|
import * as path from "path";
|
|
2549
|
+
function defaultParseOptions() {
|
|
2550
|
+
return { resolveSubstitutions: true };
|
|
2551
|
+
}
|
|
2552
|
+
function defaultResolveOptions() {
|
|
2553
|
+
return { useSystemEnvironment: true, allowUnresolved: false };
|
|
2554
|
+
}
|
|
1697
2555
|
function getEnv(opts) {
|
|
1698
2556
|
if (opts.env !== void 0) return opts.env;
|
|
1699
2557
|
if (typeof process !== "undefined" && process.env) return process.env;
|
|
@@ -1707,12 +2565,16 @@ async function defaultReadFile(filePath) {
|
|
|
1707
2565
|
}
|
|
1708
2566
|
function buildResolveContext(input, opts) {
|
|
1709
2567
|
const tokens = tokenize(input);
|
|
2568
|
+
assertNonEmptyDocument(tokens, "input");
|
|
1710
2569
|
const ast = parseTokens(tokens);
|
|
1711
2570
|
const resolveOpts = {
|
|
1712
2571
|
env: getEnv(opts),
|
|
1713
2572
|
baseDir: opts.baseDir,
|
|
1714
2573
|
readFileSync: opts.readFileSync ?? defaultReadFileSync,
|
|
1715
|
-
readFile: opts.readFile
|
|
2574
|
+
readFile: opts.readFile,
|
|
2575
|
+
originDescription: opts.originDescription,
|
|
2576
|
+
resolveFrom: opts.resolveFrom,
|
|
2577
|
+
packageResolver: opts.packageResolver
|
|
1716
2578
|
};
|
|
1717
2579
|
return { ast, resolveOpts };
|
|
1718
2580
|
}
|
|
@@ -1720,7 +2582,38 @@ function parse(input, opts = {}) {
|
|
|
1720
2582
|
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
1721
2583
|
const value = resolve2(ast, resolveOpts);
|
|
1722
2584
|
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
1723
|
-
return new Config(value);
|
|
2585
|
+
return new Config(value, { resolved: true, originDescription: opts.originDescription });
|
|
2586
|
+
}
|
|
2587
|
+
var parseString = parse;
|
|
2588
|
+
function parseStringWithOptions(input, opts = {}) {
|
|
2589
|
+
const resolveSubstitutions = opts.resolveSubstitutions ?? true;
|
|
2590
|
+
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
2591
|
+
if (resolveSubstitutions) {
|
|
2592
|
+
const value = resolve2(ast, resolveOpts);
|
|
2593
|
+
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
2594
|
+
return new Config(value, { resolved: true, originDescription: opts.originDescription });
|
|
2595
|
+
}
|
|
2596
|
+
const tree = buildTree(ast, resolveOpts);
|
|
2597
|
+
const hasPlaceholders = containsPlaceholders(tree);
|
|
2598
|
+
return Config._fromUnresolvedResObj(tree, {
|
|
2599
|
+
parseBaseDir: opts.baseDir,
|
|
2600
|
+
originDescription: opts.originDescription,
|
|
2601
|
+
resolved: !hasPlaceholders,
|
|
2602
|
+
resolveOpts
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
function parseFileWithOptions(filePath, opts = {}) {
|
|
2606
|
+
if (typeof process === "undefined" && !opts.readFileSync) {
|
|
2607
|
+
throw new Error("parseFileWithOptions is not supported in browser environments without opts.readFileSync.");
|
|
2608
|
+
}
|
|
2609
|
+
const resolvedPath = path.resolve(filePath);
|
|
2610
|
+
const readFileSync2 = opts.readFileSync ?? defaultReadFileSync;
|
|
2611
|
+
const input = readFileSync2(resolvedPath);
|
|
2612
|
+
return parseStringWithOptions(input, {
|
|
2613
|
+
...opts,
|
|
2614
|
+
baseDir: opts.baseDir ?? path.dirname(resolvedPath),
|
|
2615
|
+
readFileSync: readFileSync2
|
|
2616
|
+
});
|
|
1724
2617
|
}
|
|
1725
2618
|
async function parseAsync(input, opts = {}) {
|
|
1726
2619
|
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
@@ -1755,13 +2648,89 @@ async function parseFileAsync(filePath, opts = {}) {
|
|
|
1755
2648
|
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
1756
2649
|
});
|
|
1757
2650
|
}
|
|
2651
|
+
|
|
2652
|
+
// src/value-factory.ts
|
|
2653
|
+
function fromMap(values, originDescription) {
|
|
2654
|
+
const fields = coerceObject(values, "");
|
|
2655
|
+
const root = { kind: "object", fields };
|
|
2656
|
+
return Config._fromResolvedValue(root, { originDescription });
|
|
2657
|
+
}
|
|
2658
|
+
function empty(originDescription) {
|
|
2659
|
+
const root = { kind: "object", fields: /* @__PURE__ */ new Map() };
|
|
2660
|
+
return Config._fromResolvedValue(root, { originDescription });
|
|
2661
|
+
}
|
|
2662
|
+
function coerceObject(obj, atPath) {
|
|
2663
|
+
const fields = /* @__PURE__ */ new Map();
|
|
2664
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
2665
|
+
const childPath = atPath ? `${atPath}.${k}` : k;
|
|
2666
|
+
fields.set(k, coerceValue(v, childPath));
|
|
2667
|
+
}
|
|
2668
|
+
return fields;
|
|
2669
|
+
}
|
|
2670
|
+
function coerceValue(v, atPath) {
|
|
2671
|
+
if (v === null) {
|
|
2672
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
2673
|
+
}
|
|
2674
|
+
if (v === void 0) {
|
|
2675
|
+
throw new ConfigError(
|
|
2676
|
+
`fromMap: undefined is not a valid HOCON value at path "${atPath}"`,
|
|
2677
|
+
atPath
|
|
2678
|
+
);
|
|
2679
|
+
}
|
|
2680
|
+
switch (typeof v) {
|
|
2681
|
+
case "string":
|
|
2682
|
+
return { kind: "scalar", raw: v, valueType: "string" };
|
|
2683
|
+
case "number": {
|
|
2684
|
+
if (Number.isNaN(v) || !Number.isFinite(v)) {
|
|
2685
|
+
throw new ConfigError(
|
|
2686
|
+
`fromMap: ${v} is not representable in HOCON (NaN/Infinity not allowed) at path "${atPath}"`,
|
|
2687
|
+
atPath
|
|
2688
|
+
);
|
|
2689
|
+
}
|
|
2690
|
+
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
2691
|
+
}
|
|
2692
|
+
case "boolean":
|
|
2693
|
+
return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
|
|
2694
|
+
case "bigint": {
|
|
2695
|
+
if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
2696
|
+
throw new ConfigError(
|
|
2697
|
+
`fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
|
|
2698
|
+
atPath
|
|
2699
|
+
);
|
|
2700
|
+
}
|
|
2701
|
+
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
2702
|
+
}
|
|
2703
|
+
case "object": {
|
|
2704
|
+
if (Array.isArray(v)) {
|
|
2705
|
+
const items = v.map((item, i) => coerceValue(item, `${atPath}[${i}]`));
|
|
2706
|
+
return { kind: "array", items };
|
|
2707
|
+
}
|
|
2708
|
+
const fields = coerceObject(v, atPath);
|
|
2709
|
+
return { kind: "object", fields };
|
|
2710
|
+
}
|
|
2711
|
+
default:
|
|
2712
|
+
throw new ConfigError(
|
|
2713
|
+
`fromMap: unsupported value type "${typeof v}" at path "${atPath}"`,
|
|
2714
|
+
atPath
|
|
2715
|
+
);
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
1758
2718
|
export {
|
|
1759
2719
|
Config,
|
|
1760
2720
|
ConfigError,
|
|
2721
|
+
NotResolvedError,
|
|
2722
|
+
PackageLookupError,
|
|
1761
2723
|
ParseError,
|
|
1762
2724
|
ResolveError,
|
|
2725
|
+
defaultParseOptions,
|
|
2726
|
+
defaultResolveOptions,
|
|
2727
|
+
empty,
|
|
2728
|
+
fromMap,
|
|
1763
2729
|
parse,
|
|
1764
2730
|
parseAsync,
|
|
1765
2731
|
parseFile,
|
|
1766
|
-
parseFileAsync
|
|
2732
|
+
parseFileAsync,
|
|
2733
|
+
parseFileWithOptions,
|
|
2734
|
+
parseString,
|
|
2735
|
+
parseStringWithOptions
|
|
1767
2736
|
};
|