@logtape/logtape 1.2.0-dev.354 → 1.2.0-dev.359
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/deno.json +1 -1
- package/dist/logger.cjs +224 -4
- package/dist/logger.js +224 -4
- package/dist/logger.js.map +1 -1
- package/package.json +1 -1
- package/src/logger.test.ts +456 -0
- package/src/logger.ts +305 -2
package/deno.json
CHANGED
package/dist/logger.cjs
CHANGED
|
@@ -330,10 +330,227 @@ var LoggerCtx = class LoggerCtx {
|
|
|
330
330
|
*/
|
|
331
331
|
const metaLogger = LoggerImpl.getLogger(["logtape", "meta"]);
|
|
332
332
|
/**
|
|
333
|
+
* Check if a property access key contains nested access patterns.
|
|
334
|
+
* @param key The property key to check.
|
|
335
|
+
* @returns True if the key contains nested access patterns.
|
|
336
|
+
*/
|
|
337
|
+
function isNestedAccess(key) {
|
|
338
|
+
return key.includes(".") || key.includes("[") || key.includes("?.");
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Safely access an own property from an object, blocking prototype pollution.
|
|
342
|
+
*
|
|
343
|
+
* @param obj The object to access the property from.
|
|
344
|
+
* @param key The property key to access.
|
|
345
|
+
* @returns The property value or undefined if not accessible.
|
|
346
|
+
*/
|
|
347
|
+
function getOwnProperty(obj, key) {
|
|
348
|
+
if (key === "__proto__" || key === "prototype" || key === "constructor") return void 0;
|
|
349
|
+
if ((typeof obj === "object" || typeof obj === "function") && obj !== null) return Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : void 0;
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Parse the next segment from a property path string.
|
|
354
|
+
*
|
|
355
|
+
* @param path The full property path string.
|
|
356
|
+
* @param fromIndex The index to start parsing from.
|
|
357
|
+
* @returns The parsed segment and next index, or null if parsing fails.
|
|
358
|
+
*/
|
|
359
|
+
function parseNextSegment(path, fromIndex) {
|
|
360
|
+
const len = path.length;
|
|
361
|
+
let i = fromIndex;
|
|
362
|
+
if (i >= len) return null;
|
|
363
|
+
let segment;
|
|
364
|
+
if (path[i] === "[") {
|
|
365
|
+
i++;
|
|
366
|
+
if (i >= len) return null;
|
|
367
|
+
if (path[i] === "\"" || path[i] === "'") {
|
|
368
|
+
const quote = path[i];
|
|
369
|
+
i++;
|
|
370
|
+
let segmentStr = "";
|
|
371
|
+
while (i < len && path[i] !== quote) if (path[i] === "\\") {
|
|
372
|
+
i++;
|
|
373
|
+
if (i < len) {
|
|
374
|
+
const escapeChar = path[i];
|
|
375
|
+
switch (escapeChar) {
|
|
376
|
+
case "n":
|
|
377
|
+
segmentStr += "\n";
|
|
378
|
+
break;
|
|
379
|
+
case "t":
|
|
380
|
+
segmentStr += " ";
|
|
381
|
+
break;
|
|
382
|
+
case "r":
|
|
383
|
+
segmentStr += "\r";
|
|
384
|
+
break;
|
|
385
|
+
case "b":
|
|
386
|
+
segmentStr += "\b";
|
|
387
|
+
break;
|
|
388
|
+
case "f":
|
|
389
|
+
segmentStr += "\f";
|
|
390
|
+
break;
|
|
391
|
+
case "v":
|
|
392
|
+
segmentStr += "\v";
|
|
393
|
+
break;
|
|
394
|
+
case "0":
|
|
395
|
+
segmentStr += "\0";
|
|
396
|
+
break;
|
|
397
|
+
case "\\":
|
|
398
|
+
segmentStr += "\\";
|
|
399
|
+
break;
|
|
400
|
+
case "\"":
|
|
401
|
+
segmentStr += "\"";
|
|
402
|
+
break;
|
|
403
|
+
case "'":
|
|
404
|
+
segmentStr += "'";
|
|
405
|
+
break;
|
|
406
|
+
case "u":
|
|
407
|
+
if (i + 4 < len) {
|
|
408
|
+
const hex = path.slice(i + 1, i + 5);
|
|
409
|
+
const codePoint = Number.parseInt(hex, 16);
|
|
410
|
+
if (!Number.isNaN(codePoint)) {
|
|
411
|
+
segmentStr += String.fromCharCode(codePoint);
|
|
412
|
+
i += 4;
|
|
413
|
+
} else segmentStr += escapeChar;
|
|
414
|
+
} else segmentStr += escapeChar;
|
|
415
|
+
break;
|
|
416
|
+
default: segmentStr += escapeChar;
|
|
417
|
+
}
|
|
418
|
+
i++;
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
segmentStr += path[i];
|
|
422
|
+
i++;
|
|
423
|
+
}
|
|
424
|
+
if (i >= len) return null;
|
|
425
|
+
segment = segmentStr;
|
|
426
|
+
i++;
|
|
427
|
+
} else {
|
|
428
|
+
const startIndex = i;
|
|
429
|
+
while (i < len && path[i] !== "]" && path[i] !== "'" && path[i] !== "\"") i++;
|
|
430
|
+
if (i >= len) return null;
|
|
431
|
+
const indexStr = path.slice(startIndex, i);
|
|
432
|
+
if (indexStr.length === 0) return null;
|
|
433
|
+
const indexNum = Number(indexStr);
|
|
434
|
+
segment = Number.isNaN(indexNum) ? indexStr : indexNum;
|
|
435
|
+
}
|
|
436
|
+
while (i < len && path[i] !== "]") i++;
|
|
437
|
+
if (i < len) i++;
|
|
438
|
+
} else {
|
|
439
|
+
const startIndex = i;
|
|
440
|
+
while (i < len && path[i] !== "." && path[i] !== "[" && path[i] !== "?" && path[i] !== "]") i++;
|
|
441
|
+
segment = path.slice(startIndex, i);
|
|
442
|
+
if (segment.length === 0) return null;
|
|
443
|
+
}
|
|
444
|
+
if (i < len && path[i] === ".") i++;
|
|
445
|
+
return {
|
|
446
|
+
segment,
|
|
447
|
+
nextIndex: i
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Access a property or index on an object or array.
|
|
452
|
+
*
|
|
453
|
+
* @param obj The object or array to access.
|
|
454
|
+
* @param segment The property key or array index.
|
|
455
|
+
* @returns The accessed value or undefined if not accessible.
|
|
456
|
+
*/
|
|
457
|
+
function accessProperty(obj, segment) {
|
|
458
|
+
if (typeof segment === "string") return getOwnProperty(obj, segment);
|
|
459
|
+
if (Array.isArray(obj) && segment >= 0 && segment < obj.length) return obj[segment];
|
|
460
|
+
return void 0;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Resolve a nested property path from an object.
|
|
464
|
+
*
|
|
465
|
+
* There are two types of property access patterns:
|
|
466
|
+
* 1. Array/index access: [0] or ["prop"]
|
|
467
|
+
* 2. Property access: prop or prop?.next
|
|
468
|
+
*
|
|
469
|
+
* @param obj The object to traverse.
|
|
470
|
+
* @param path The property path (e.g., "user.name", "users[0].email", "user['full-name']").
|
|
471
|
+
* @returns The resolved value or undefined if path doesn't exist.
|
|
472
|
+
*/
|
|
473
|
+
function resolvePropertyPath(obj, path) {
|
|
474
|
+
if (obj == null) return void 0;
|
|
475
|
+
if (path.length === 0 || path.endsWith(".")) return void 0;
|
|
476
|
+
let current = obj;
|
|
477
|
+
let i = 0;
|
|
478
|
+
const len = path.length;
|
|
479
|
+
while (i < len) {
|
|
480
|
+
const isOptional = path.slice(i, i + 2) === "?.";
|
|
481
|
+
if (isOptional) {
|
|
482
|
+
i += 2;
|
|
483
|
+
if (current == null) return void 0;
|
|
484
|
+
} else if (current == null) return void 0;
|
|
485
|
+
const result = parseNextSegment(path, i);
|
|
486
|
+
if (result === null) return void 0;
|
|
487
|
+
const { segment, nextIndex } = result;
|
|
488
|
+
i = nextIndex;
|
|
489
|
+
current = accessProperty(current, segment);
|
|
490
|
+
if (current === void 0) return void 0;
|
|
491
|
+
}
|
|
492
|
+
return current;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
333
495
|
* Parse a message template into a message template array and a values array.
|
|
334
|
-
*
|
|
496
|
+
*
|
|
497
|
+
* Placeholders to be replaced with `values` are indicated by keys in curly braces
|
|
498
|
+
* (e.g., `{value}`). The system supports both simple property access and nested
|
|
499
|
+
* property access patterns:
|
|
500
|
+
*
|
|
501
|
+
* **Simple property access:**
|
|
502
|
+
* ```ts
|
|
503
|
+
* parseMessageTemplate("Hello, {user}!", { user: "foo" })
|
|
504
|
+
* // Returns: ["Hello, ", "foo", "!"]
|
|
505
|
+
* ```
|
|
506
|
+
*
|
|
507
|
+
* **Nested property access (dot notation):**
|
|
508
|
+
* ```ts
|
|
509
|
+
* parseMessageTemplate("Hello, {user.name}!", {
|
|
510
|
+
* user: { name: "foo", email: "foo@example.com" }
|
|
511
|
+
* })
|
|
512
|
+
* // Returns: ["Hello, ", "foo", "!"]
|
|
513
|
+
* ```
|
|
514
|
+
*
|
|
515
|
+
* **Array indexing:**
|
|
516
|
+
* ```ts
|
|
517
|
+
* parseMessageTemplate("First: {users[0]}", {
|
|
518
|
+
* users: ["foo", "bar", "baz"]
|
|
519
|
+
* })
|
|
520
|
+
* // Returns: ["First: ", "foo", ""]
|
|
521
|
+
* ```
|
|
522
|
+
*
|
|
523
|
+
* **Bracket notation for special property names:**
|
|
524
|
+
* ```ts
|
|
525
|
+
* parseMessageTemplate("Name: {user[\"full-name\"]}", {
|
|
526
|
+
* user: { "full-name": "foo bar" }
|
|
527
|
+
* })
|
|
528
|
+
* // Returns: ["Name: ", "foo bar", ""]
|
|
529
|
+
* ```
|
|
530
|
+
*
|
|
531
|
+
* **Optional chaining for safe navigation:**
|
|
532
|
+
* ```ts
|
|
533
|
+
* parseMessageTemplate("Email: {user?.profile?.email}", {
|
|
534
|
+
* user: { name: "foo" }
|
|
535
|
+
* })
|
|
536
|
+
* // Returns: ["Email: ", undefined, ""]
|
|
537
|
+
* ```
|
|
538
|
+
*
|
|
539
|
+
* **Wildcard patterns:**
|
|
540
|
+
* - `{*}` - Replaced with the entire properties object
|
|
541
|
+
* - `{ key-with-whitespace }` - Whitespace is trimmed when looking up keys
|
|
542
|
+
*
|
|
543
|
+
* **Escaping:**
|
|
544
|
+
* - `{{` and `}}` are escaped literal braces
|
|
545
|
+
*
|
|
546
|
+
* **Error handling:**
|
|
547
|
+
* - Non-existent paths return `undefined`
|
|
548
|
+
* - Malformed expressions resolve to `undefined` without throwing errors
|
|
549
|
+
* - Out of bounds array access returns `undefined`
|
|
550
|
+
*
|
|
551
|
+
* @param template The message template string containing placeholders.
|
|
335
552
|
* @param properties The values to replace placeholders with.
|
|
336
|
-
* @returns The message template array
|
|
553
|
+
* @returns The message template array with values interleaved between text segments.
|
|
337
554
|
*/
|
|
338
555
|
function parseMessageTemplate(template, properties) {
|
|
339
556
|
const length = template.length;
|
|
@@ -357,8 +574,11 @@ function parseMessageTemplate(template, properties) {
|
|
|
357
574
|
let prop;
|
|
358
575
|
const trimmedKey = key.trim();
|
|
359
576
|
if (trimmedKey === "*") prop = key in properties ? properties[key] : "*" in properties ? properties["*"] : properties;
|
|
360
|
-
else
|
|
361
|
-
|
|
577
|
+
else {
|
|
578
|
+
if (key !== trimmedKey) prop = key in properties ? properties[key] : properties[trimmedKey];
|
|
579
|
+
else prop = properties[key];
|
|
580
|
+
if (prop === void 0 && isNestedAccess(trimmedKey)) prop = resolvePropertyPath(properties, trimmedKey);
|
|
581
|
+
}
|
|
362
582
|
message.push(prop);
|
|
363
583
|
i = closeIndex;
|
|
364
584
|
startIndex = i + 1;
|
package/dist/logger.js
CHANGED
|
@@ -330,10 +330,227 @@ var LoggerCtx = class LoggerCtx {
|
|
|
330
330
|
*/
|
|
331
331
|
const metaLogger = LoggerImpl.getLogger(["logtape", "meta"]);
|
|
332
332
|
/**
|
|
333
|
+
* Check if a property access key contains nested access patterns.
|
|
334
|
+
* @param key The property key to check.
|
|
335
|
+
* @returns True if the key contains nested access patterns.
|
|
336
|
+
*/
|
|
337
|
+
function isNestedAccess(key) {
|
|
338
|
+
return key.includes(".") || key.includes("[") || key.includes("?.");
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Safely access an own property from an object, blocking prototype pollution.
|
|
342
|
+
*
|
|
343
|
+
* @param obj The object to access the property from.
|
|
344
|
+
* @param key The property key to access.
|
|
345
|
+
* @returns The property value or undefined if not accessible.
|
|
346
|
+
*/
|
|
347
|
+
function getOwnProperty(obj, key) {
|
|
348
|
+
if (key === "__proto__" || key === "prototype" || key === "constructor") return void 0;
|
|
349
|
+
if ((typeof obj === "object" || typeof obj === "function") && obj !== null) return Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : void 0;
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Parse the next segment from a property path string.
|
|
354
|
+
*
|
|
355
|
+
* @param path The full property path string.
|
|
356
|
+
* @param fromIndex The index to start parsing from.
|
|
357
|
+
* @returns The parsed segment and next index, or null if parsing fails.
|
|
358
|
+
*/
|
|
359
|
+
function parseNextSegment(path, fromIndex) {
|
|
360
|
+
const len = path.length;
|
|
361
|
+
let i = fromIndex;
|
|
362
|
+
if (i >= len) return null;
|
|
363
|
+
let segment;
|
|
364
|
+
if (path[i] === "[") {
|
|
365
|
+
i++;
|
|
366
|
+
if (i >= len) return null;
|
|
367
|
+
if (path[i] === "\"" || path[i] === "'") {
|
|
368
|
+
const quote = path[i];
|
|
369
|
+
i++;
|
|
370
|
+
let segmentStr = "";
|
|
371
|
+
while (i < len && path[i] !== quote) if (path[i] === "\\") {
|
|
372
|
+
i++;
|
|
373
|
+
if (i < len) {
|
|
374
|
+
const escapeChar = path[i];
|
|
375
|
+
switch (escapeChar) {
|
|
376
|
+
case "n":
|
|
377
|
+
segmentStr += "\n";
|
|
378
|
+
break;
|
|
379
|
+
case "t":
|
|
380
|
+
segmentStr += " ";
|
|
381
|
+
break;
|
|
382
|
+
case "r":
|
|
383
|
+
segmentStr += "\r";
|
|
384
|
+
break;
|
|
385
|
+
case "b":
|
|
386
|
+
segmentStr += "\b";
|
|
387
|
+
break;
|
|
388
|
+
case "f":
|
|
389
|
+
segmentStr += "\f";
|
|
390
|
+
break;
|
|
391
|
+
case "v":
|
|
392
|
+
segmentStr += "\v";
|
|
393
|
+
break;
|
|
394
|
+
case "0":
|
|
395
|
+
segmentStr += "\0";
|
|
396
|
+
break;
|
|
397
|
+
case "\\":
|
|
398
|
+
segmentStr += "\\";
|
|
399
|
+
break;
|
|
400
|
+
case "\"":
|
|
401
|
+
segmentStr += "\"";
|
|
402
|
+
break;
|
|
403
|
+
case "'":
|
|
404
|
+
segmentStr += "'";
|
|
405
|
+
break;
|
|
406
|
+
case "u":
|
|
407
|
+
if (i + 4 < len) {
|
|
408
|
+
const hex = path.slice(i + 1, i + 5);
|
|
409
|
+
const codePoint = Number.parseInt(hex, 16);
|
|
410
|
+
if (!Number.isNaN(codePoint)) {
|
|
411
|
+
segmentStr += String.fromCharCode(codePoint);
|
|
412
|
+
i += 4;
|
|
413
|
+
} else segmentStr += escapeChar;
|
|
414
|
+
} else segmentStr += escapeChar;
|
|
415
|
+
break;
|
|
416
|
+
default: segmentStr += escapeChar;
|
|
417
|
+
}
|
|
418
|
+
i++;
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
segmentStr += path[i];
|
|
422
|
+
i++;
|
|
423
|
+
}
|
|
424
|
+
if (i >= len) return null;
|
|
425
|
+
segment = segmentStr;
|
|
426
|
+
i++;
|
|
427
|
+
} else {
|
|
428
|
+
const startIndex = i;
|
|
429
|
+
while (i < len && path[i] !== "]" && path[i] !== "'" && path[i] !== "\"") i++;
|
|
430
|
+
if (i >= len) return null;
|
|
431
|
+
const indexStr = path.slice(startIndex, i);
|
|
432
|
+
if (indexStr.length === 0) return null;
|
|
433
|
+
const indexNum = Number(indexStr);
|
|
434
|
+
segment = Number.isNaN(indexNum) ? indexStr : indexNum;
|
|
435
|
+
}
|
|
436
|
+
while (i < len && path[i] !== "]") i++;
|
|
437
|
+
if (i < len) i++;
|
|
438
|
+
} else {
|
|
439
|
+
const startIndex = i;
|
|
440
|
+
while (i < len && path[i] !== "." && path[i] !== "[" && path[i] !== "?" && path[i] !== "]") i++;
|
|
441
|
+
segment = path.slice(startIndex, i);
|
|
442
|
+
if (segment.length === 0) return null;
|
|
443
|
+
}
|
|
444
|
+
if (i < len && path[i] === ".") i++;
|
|
445
|
+
return {
|
|
446
|
+
segment,
|
|
447
|
+
nextIndex: i
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Access a property or index on an object or array.
|
|
452
|
+
*
|
|
453
|
+
* @param obj The object or array to access.
|
|
454
|
+
* @param segment The property key or array index.
|
|
455
|
+
* @returns The accessed value or undefined if not accessible.
|
|
456
|
+
*/
|
|
457
|
+
function accessProperty(obj, segment) {
|
|
458
|
+
if (typeof segment === "string") return getOwnProperty(obj, segment);
|
|
459
|
+
if (Array.isArray(obj) && segment >= 0 && segment < obj.length) return obj[segment];
|
|
460
|
+
return void 0;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Resolve a nested property path from an object.
|
|
464
|
+
*
|
|
465
|
+
* There are two types of property access patterns:
|
|
466
|
+
* 1. Array/index access: [0] or ["prop"]
|
|
467
|
+
* 2. Property access: prop or prop?.next
|
|
468
|
+
*
|
|
469
|
+
* @param obj The object to traverse.
|
|
470
|
+
* @param path The property path (e.g., "user.name", "users[0].email", "user['full-name']").
|
|
471
|
+
* @returns The resolved value or undefined if path doesn't exist.
|
|
472
|
+
*/
|
|
473
|
+
function resolvePropertyPath(obj, path) {
|
|
474
|
+
if (obj == null) return void 0;
|
|
475
|
+
if (path.length === 0 || path.endsWith(".")) return void 0;
|
|
476
|
+
let current = obj;
|
|
477
|
+
let i = 0;
|
|
478
|
+
const len = path.length;
|
|
479
|
+
while (i < len) {
|
|
480
|
+
const isOptional = path.slice(i, i + 2) === "?.";
|
|
481
|
+
if (isOptional) {
|
|
482
|
+
i += 2;
|
|
483
|
+
if (current == null) return void 0;
|
|
484
|
+
} else if (current == null) return void 0;
|
|
485
|
+
const result = parseNextSegment(path, i);
|
|
486
|
+
if (result === null) return void 0;
|
|
487
|
+
const { segment, nextIndex } = result;
|
|
488
|
+
i = nextIndex;
|
|
489
|
+
current = accessProperty(current, segment);
|
|
490
|
+
if (current === void 0) return void 0;
|
|
491
|
+
}
|
|
492
|
+
return current;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
333
495
|
* Parse a message template into a message template array and a values array.
|
|
334
|
-
*
|
|
496
|
+
*
|
|
497
|
+
* Placeholders to be replaced with `values` are indicated by keys in curly braces
|
|
498
|
+
* (e.g., `{value}`). The system supports both simple property access and nested
|
|
499
|
+
* property access patterns:
|
|
500
|
+
*
|
|
501
|
+
* **Simple property access:**
|
|
502
|
+
* ```ts
|
|
503
|
+
* parseMessageTemplate("Hello, {user}!", { user: "foo" })
|
|
504
|
+
* // Returns: ["Hello, ", "foo", "!"]
|
|
505
|
+
* ```
|
|
506
|
+
*
|
|
507
|
+
* **Nested property access (dot notation):**
|
|
508
|
+
* ```ts
|
|
509
|
+
* parseMessageTemplate("Hello, {user.name}!", {
|
|
510
|
+
* user: { name: "foo", email: "foo@example.com" }
|
|
511
|
+
* })
|
|
512
|
+
* // Returns: ["Hello, ", "foo", "!"]
|
|
513
|
+
* ```
|
|
514
|
+
*
|
|
515
|
+
* **Array indexing:**
|
|
516
|
+
* ```ts
|
|
517
|
+
* parseMessageTemplate("First: {users[0]}", {
|
|
518
|
+
* users: ["foo", "bar", "baz"]
|
|
519
|
+
* })
|
|
520
|
+
* // Returns: ["First: ", "foo", ""]
|
|
521
|
+
* ```
|
|
522
|
+
*
|
|
523
|
+
* **Bracket notation for special property names:**
|
|
524
|
+
* ```ts
|
|
525
|
+
* parseMessageTemplate("Name: {user[\"full-name\"]}", {
|
|
526
|
+
* user: { "full-name": "foo bar" }
|
|
527
|
+
* })
|
|
528
|
+
* // Returns: ["Name: ", "foo bar", ""]
|
|
529
|
+
* ```
|
|
530
|
+
*
|
|
531
|
+
* **Optional chaining for safe navigation:**
|
|
532
|
+
* ```ts
|
|
533
|
+
* parseMessageTemplate("Email: {user?.profile?.email}", {
|
|
534
|
+
* user: { name: "foo" }
|
|
535
|
+
* })
|
|
536
|
+
* // Returns: ["Email: ", undefined, ""]
|
|
537
|
+
* ```
|
|
538
|
+
*
|
|
539
|
+
* **Wildcard patterns:**
|
|
540
|
+
* - `{*}` - Replaced with the entire properties object
|
|
541
|
+
* - `{ key-with-whitespace }` - Whitespace is trimmed when looking up keys
|
|
542
|
+
*
|
|
543
|
+
* **Escaping:**
|
|
544
|
+
* - `{{` and `}}` are escaped literal braces
|
|
545
|
+
*
|
|
546
|
+
* **Error handling:**
|
|
547
|
+
* - Non-existent paths return `undefined`
|
|
548
|
+
* - Malformed expressions resolve to `undefined` without throwing errors
|
|
549
|
+
* - Out of bounds array access returns `undefined`
|
|
550
|
+
*
|
|
551
|
+
* @param template The message template string containing placeholders.
|
|
335
552
|
* @param properties The values to replace placeholders with.
|
|
336
|
-
* @returns The message template array
|
|
553
|
+
* @returns The message template array with values interleaved between text segments.
|
|
337
554
|
*/
|
|
338
555
|
function parseMessageTemplate(template, properties) {
|
|
339
556
|
const length = template.length;
|
|
@@ -357,8 +574,11 @@ function parseMessageTemplate(template, properties) {
|
|
|
357
574
|
let prop;
|
|
358
575
|
const trimmedKey = key.trim();
|
|
359
576
|
if (trimmedKey === "*") prop = key in properties ? properties[key] : "*" in properties ? properties["*"] : properties;
|
|
360
|
-
else
|
|
361
|
-
|
|
577
|
+
else {
|
|
578
|
+
if (key !== trimmedKey) prop = key in properties ? properties[key] : properties[trimmedKey];
|
|
579
|
+
else prop = properties[key];
|
|
580
|
+
if (prop === void 0 && isNestedAccess(trimmedKey)) prop = resolvePropertyPath(properties, trimmedKey);
|
|
581
|
+
}
|
|
362
582
|
message.push(prop);
|
|
363
583
|
i = closeIndex;
|
|
364
584
|
startIndex = i + 1;
|