@decimalturn/toml-patch 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/toml-patch.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//! @decimalturn/toml-patch v0.
|
|
1
|
+
//! @decimalturn/toml-patch v0.6.0 - https://github.com/DecimalTurn/toml-patch - @license: MIT
|
|
2
2
|
var NodeType;
|
|
3
3
|
(function (NodeType) {
|
|
4
4
|
NodeType["Document"] = "Document";
|
|
@@ -176,7 +176,7 @@ function getLine$1(input, position) {
|
|
|
176
176
|
const lines = findLines(input);
|
|
177
177
|
const start = lines[position.line - 2] || 0;
|
|
178
178
|
const end = lines[position.line - 1] || input.length;
|
|
179
|
-
return input.
|
|
179
|
+
return input.substring(start, end);
|
|
180
180
|
}
|
|
181
181
|
function findLines(input) {
|
|
182
182
|
// exec is stateful, so create new regexp each time
|
|
@@ -416,6 +416,11 @@ function checkThree(input, current, check) {
|
|
|
416
416
|
if (!has3) {
|
|
417
417
|
return false;
|
|
418
418
|
}
|
|
419
|
+
// Only check for escaping in basic strings (double quotes)
|
|
420
|
+
// Literal strings (single quotes) don't support escape sequences
|
|
421
|
+
if (check === SINGLE_QUOTE) {
|
|
422
|
+
return check; // No escaping in literal strings
|
|
423
|
+
}
|
|
419
424
|
// Check if the sequence is escaped
|
|
420
425
|
const precedingText = input.slice(0, current); // Get the text before the current position
|
|
421
426
|
const backslashes = precedingText.match(/\\+$/); // Match trailing backslashes
|
|
@@ -564,11 +569,41 @@ function escapeDoubleQuotes(value) {
|
|
|
564
569
|
}
|
|
565
570
|
return result;
|
|
566
571
|
}
|
|
572
|
+
function isBackslashEscaped(source, backslashOffset) {
|
|
573
|
+
let precedingBackslashes = 0;
|
|
574
|
+
for (let i = backslashOffset - 1; i >= 0 && source[i] === '\\'; i--) {
|
|
575
|
+
precedingBackslashes++;
|
|
576
|
+
}
|
|
577
|
+
return precedingBackslashes % 2 !== 0;
|
|
578
|
+
}
|
|
567
579
|
function unescapeLargeUnicode(escaped) {
|
|
580
|
+
// TOML 1.1.0: Handle \xHH hex escapes (for codepoints < 255)
|
|
581
|
+
const HEX_ESCAPE = /\\x([a-fA-F0-9]{2})/g;
|
|
582
|
+
const hexEscapeSource = escaped;
|
|
583
|
+
let withHexEscapes = hexEscapeSource.replace(HEX_ESCAPE, (match, hex, offset) => {
|
|
584
|
+
if (isBackslashEscaped(hexEscapeSource, offset)) {
|
|
585
|
+
return match;
|
|
586
|
+
}
|
|
587
|
+
const codePoint = parseInt(hex, 16);
|
|
588
|
+
const asString = String.fromCharCode(codePoint);
|
|
589
|
+
// Escape for JSON if needed
|
|
590
|
+
if (codePoint < 0x20 || codePoint === 0x22 || codePoint === 0x5C) {
|
|
591
|
+
return trim(JSON.stringify(asString), 1);
|
|
592
|
+
}
|
|
593
|
+
return asString;
|
|
594
|
+
});
|
|
595
|
+
// TOML 1.1.0: Handle \e escape character (ESC = 0x1B)
|
|
596
|
+
const eEscapeSource = withHexEscapes;
|
|
597
|
+
withHexEscapes = eEscapeSource.replace(/\\e/g, (match, offset) => {
|
|
598
|
+
if (isBackslashEscaped(eEscapeSource, offset)) {
|
|
599
|
+
return match;
|
|
600
|
+
}
|
|
601
|
+
return '\\u001b';
|
|
602
|
+
});
|
|
568
603
|
// JSON.parse handles everything except \UXXXXXXXX
|
|
569
604
|
// replace those instances with code point, escape that, and then parse
|
|
570
605
|
const LARGE_UNICODE = /\\U[a-fA-F0-9]{8}/g;
|
|
571
|
-
const json_escaped =
|
|
606
|
+
const json_escaped = withHexEscapes.replace(LARGE_UNICODE, value => {
|
|
572
607
|
const code_point = parseInt(value.replace('\\U', ''), 16);
|
|
573
608
|
const as_string = String.fromCodePoint(code_point);
|
|
574
609
|
return trim(JSON.stringify(as_string), 1);
|
|
@@ -789,14 +824,14 @@ class DateFormatHelper {
|
|
|
789
824
|
}
|
|
790
825
|
// Patterns for different date/time formats
|
|
791
826
|
DateFormatHelper.IS_DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
792
|
-
DateFormatHelper.IS_TIME_ONLY = /^\d{2}:\d{2}
|
|
793
|
-
DateFormatHelper.IS_LOCAL_DATETIME_T = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}
|
|
794
|
-
DateFormatHelper.IS_LOCAL_DATETIME_SPACE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}
|
|
795
|
-
DateFormatHelper.IS_OFFSET_DATETIME_T = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}
|
|
796
|
-
DateFormatHelper.IS_OFFSET_DATETIME_SPACE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}
|
|
827
|
+
DateFormatHelper.IS_TIME_ONLY = /^\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?$/;
|
|
828
|
+
DateFormatHelper.IS_LOCAL_DATETIME_T = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?$/;
|
|
829
|
+
DateFormatHelper.IS_LOCAL_DATETIME_SPACE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(?::\d{2})?(?:\.\d+)?$/;
|
|
830
|
+
DateFormatHelper.IS_OFFSET_DATETIME_T = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/;
|
|
831
|
+
DateFormatHelper.IS_OFFSET_DATETIME_SPACE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/;
|
|
797
832
|
// Legacy patterns from parse-toml.ts (for compatibility)
|
|
798
833
|
DateFormatHelper.IS_FULL_DATE = /(\d{4})-(\d{2})-(\d{2})/;
|
|
799
|
-
DateFormatHelper.IS_FULL_TIME = /(\d{2}):(\d{2})
|
|
834
|
+
DateFormatHelper.IS_FULL_TIME = /(\d{2}):(\d{2})(?::(\d{2}))?/;
|
|
800
835
|
/**
|
|
801
836
|
* Custom Date class for local dates (date-only).
|
|
802
837
|
* Format: 1979-05-27
|
|
@@ -818,9 +853,15 @@ class LocalDate extends Date {
|
|
|
818
853
|
*/
|
|
819
854
|
class LocalTime extends Date {
|
|
820
855
|
constructor(value, originalFormat) {
|
|
821
|
-
//
|
|
856
|
+
// Normalize time to include seconds if missing (TOML 1.1.0 allows optional seconds)
|
|
857
|
+
let normalizedValue = value;
|
|
858
|
+
if (!/:\d{2}:\d{2}/.test(value)) {
|
|
859
|
+
// No seconds present, add :00
|
|
860
|
+
normalizedValue = value + ':00';
|
|
861
|
+
}
|
|
862
|
+
// For local time, use year 0000 as the base (TOML spec compliance)
|
|
822
863
|
// Add 'Z' to ensure it's parsed as UTC regardless of system timezone
|
|
823
|
-
super(`
|
|
864
|
+
super(`0000-01-01T${normalizedValue}Z`);
|
|
824
865
|
this.originalFormat = originalFormat;
|
|
825
866
|
}
|
|
826
867
|
toISOString() {
|
|
@@ -852,8 +893,13 @@ class LocalTime extends Date {
|
|
|
852
893
|
*/
|
|
853
894
|
class LocalDateTime extends Date {
|
|
854
895
|
constructor(value, useSpaceSeparator = false, originalFormat) {
|
|
896
|
+
// Normalize time part to include seconds if missing (TOML 1.1.0 allows optional seconds)
|
|
897
|
+
let normalizedValue = value;
|
|
898
|
+
if (!/\d{2}:\d{2}:\d{2}/.test(value)) {
|
|
899
|
+
normalizedValue = value.replace(/(\d{2}:\d{2})([\s\-+TZ]|$)/, '$1:00$2');
|
|
900
|
+
}
|
|
855
901
|
// Convert space to T for Date parsing, but remember the original format
|
|
856
|
-
super(
|
|
902
|
+
super(normalizedValue.replace(' ', 'T') + 'Z');
|
|
857
903
|
this.useSpaceSeparator = false;
|
|
858
904
|
this.useSpaceSeparator = useSpaceSeparator;
|
|
859
905
|
this.originalFormat = originalFormat || value;
|
|
@@ -892,7 +938,12 @@ class LocalDateTime extends Date {
|
|
|
892
938
|
*/
|
|
893
939
|
class OffsetDateTime extends Date {
|
|
894
940
|
constructor(value, useSpaceSeparator = false) {
|
|
895
|
-
|
|
941
|
+
// Normalize time part to include seconds if missing (TOML 1.1.0 allows optional seconds)
|
|
942
|
+
let normalizedValue = value;
|
|
943
|
+
if (!/\d{2}:\d{2}:\d{2}/.test(value)) {
|
|
944
|
+
normalizedValue = value.replace(/(\d{2}:\d{2})([\s\-+TZ]|$)/, '$1:00$2');
|
|
945
|
+
}
|
|
946
|
+
super(normalizedValue.replace(' ', 'T'));
|
|
896
947
|
this.useSpaceSeparator = false;
|
|
897
948
|
this.useSpaceSeparator = useSpaceSeparator;
|
|
898
949
|
this.originalFormat = value;
|
|
@@ -948,7 +999,7 @@ class OffsetDateTime extends Date {
|
|
|
948
999
|
}
|
|
949
1000
|
|
|
950
1001
|
// Create a shorter alias for convenience
|
|
951
|
-
const dateFormatHelper
|
|
1002
|
+
const dateFormatHelper = DateFormatHelper;
|
|
952
1003
|
const TRUE = 'true';
|
|
953
1004
|
const FALSE = 'false';
|
|
954
1005
|
const HAS_E = /e/i;
|
|
@@ -1004,7 +1055,7 @@ function* walkValue$1(cursor, input) {
|
|
|
1004
1055
|
else if (cursor.value.raw === TRUE || cursor.value.raw === FALSE) {
|
|
1005
1056
|
yield boolean(cursor);
|
|
1006
1057
|
}
|
|
1007
|
-
else if (dateFormatHelper
|
|
1058
|
+
else if (dateFormatHelper.IS_FULL_DATE.test(cursor.value.raw) || dateFormatHelper.IS_FULL_TIME.test(cursor.value.raw)) {
|
|
1008
1059
|
yield datetime(cursor, input);
|
|
1009
1060
|
}
|
|
1010
1061
|
else if ((!cursor.peek().done && cursor.peek().value.type === TokenType.Dot) ||
|
|
@@ -1018,7 +1069,9 @@ function* walkValue$1(cursor, input) {
|
|
|
1018
1069
|
}
|
|
1019
1070
|
}
|
|
1020
1071
|
else if (cursor.value.type === TokenType.Curly) {
|
|
1021
|
-
|
|
1072
|
+
const [inline_table, comments] = inlineTable(cursor, input);
|
|
1073
|
+
yield inline_table;
|
|
1074
|
+
yield* comments;
|
|
1022
1075
|
}
|
|
1023
1076
|
else if (cursor.value.type === TokenType.Bracket) {
|
|
1024
1077
|
const [inline_array, comments] = inlineArray(cursor, input);
|
|
@@ -1217,8 +1270,8 @@ function datetime(cursor, input) {
|
|
|
1217
1270
|
// check if raw is full date and following is full time
|
|
1218
1271
|
if (!cursor.peek().done &&
|
|
1219
1272
|
cursor.peek().value.type === TokenType.Literal &&
|
|
1220
|
-
dateFormatHelper
|
|
1221
|
-
dateFormatHelper
|
|
1273
|
+
dateFormatHelper.IS_FULL_DATE.test(raw) &&
|
|
1274
|
+
dateFormatHelper.IS_FULL_TIME.test(cursor.peek().value.raw)) {
|
|
1222
1275
|
const start = loc.start;
|
|
1223
1276
|
cursor.next();
|
|
1224
1277
|
loc = { start, end: cursor.value.loc.end };
|
|
@@ -1234,9 +1287,9 @@ function datetime(cursor, input) {
|
|
|
1234
1287
|
loc = { start, end: cursor.value.loc.end };
|
|
1235
1288
|
raw += `.${cursor.value.raw}`;
|
|
1236
1289
|
}
|
|
1237
|
-
if (!dateFormatHelper
|
|
1290
|
+
if (!dateFormatHelper.IS_FULL_DATE.test(raw)) {
|
|
1238
1291
|
// Local time only (e.g., "07:32:00" or "07:32:00.999")
|
|
1239
|
-
if (dateFormatHelper
|
|
1292
|
+
if (dateFormatHelper.IS_TIME_ONLY.test(raw)) {
|
|
1240
1293
|
value = new LocalTime(raw, raw);
|
|
1241
1294
|
}
|
|
1242
1295
|
else {
|
|
@@ -1245,23 +1298,23 @@ function datetime(cursor, input) {
|
|
|
1245
1298
|
value = new Date(`${local_date}T${raw}`);
|
|
1246
1299
|
}
|
|
1247
1300
|
}
|
|
1248
|
-
else if (dateFormatHelper
|
|
1301
|
+
else if (dateFormatHelper.IS_DATE_ONLY.test(raw)) {
|
|
1249
1302
|
// Local date only (e.g., "1979-05-27")
|
|
1250
1303
|
value = new LocalDate(raw);
|
|
1251
1304
|
}
|
|
1252
|
-
else if (dateFormatHelper
|
|
1305
|
+
else if (dateFormatHelper.IS_LOCAL_DATETIME_T.test(raw)) {
|
|
1253
1306
|
// Local datetime with T separator (e.g., "1979-05-27T07:32:00")
|
|
1254
1307
|
value = new LocalDateTime(raw, false);
|
|
1255
1308
|
}
|
|
1256
|
-
else if (dateFormatHelper
|
|
1309
|
+
else if (dateFormatHelper.IS_LOCAL_DATETIME_SPACE.test(raw)) {
|
|
1257
1310
|
// Local datetime with space separator (e.g., "1979-05-27 07:32:00")
|
|
1258
1311
|
value = new LocalDateTime(raw, true);
|
|
1259
1312
|
}
|
|
1260
|
-
else if (dateFormatHelper
|
|
1313
|
+
else if (dateFormatHelper.IS_OFFSET_DATETIME_T.test(raw)) {
|
|
1261
1314
|
// Offset datetime with T separator (e.g., "1979-05-27T07:32:00Z" or "1979-05-27T07:32:00-07:00")
|
|
1262
1315
|
value = new OffsetDateTime(raw, false);
|
|
1263
1316
|
}
|
|
1264
|
-
else if (dateFormatHelper
|
|
1317
|
+
else if (dateFormatHelper.IS_OFFSET_DATETIME_SPACE.test(raw)) {
|
|
1265
1318
|
// Offset datetime with space separator (e.g., "1979-05-27 07:32:00Z")
|
|
1266
1319
|
value = new OffsetDateTime(raw, true);
|
|
1267
1320
|
}
|
|
@@ -1347,9 +1400,16 @@ function inlineTable(cursor, input) {
|
|
|
1347
1400
|
loc: cloneLocation(cursor.value.loc),
|
|
1348
1401
|
items: []
|
|
1349
1402
|
};
|
|
1403
|
+
const comments = [];
|
|
1350
1404
|
cursor.next();
|
|
1351
1405
|
while (!cursor.done &&
|
|
1352
1406
|
!(cursor.value.type === TokenType.Curly && cursor.value.raw === '}')) {
|
|
1407
|
+
// TOML 1.1.0: Handle comments in inline tables
|
|
1408
|
+
if (cursor.value.type === TokenType.Comment) {
|
|
1409
|
+
comments.push(comment(cursor));
|
|
1410
|
+
cursor.next();
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1353
1413
|
if (cursor.value.type === TokenType.Comma) {
|
|
1354
1414
|
const previous = value.items[value.items.length - 1];
|
|
1355
1415
|
if (!previous) {
|
|
@@ -1360,7 +1420,7 @@ function inlineTable(cursor, input) {
|
|
|
1360
1420
|
cursor.next();
|
|
1361
1421
|
continue;
|
|
1362
1422
|
}
|
|
1363
|
-
const [item] = walkBlock(cursor, input);
|
|
1423
|
+
const [item, ...additional_comments] = walkBlock(cursor, input);
|
|
1364
1424
|
if (item.type !== NodeType.KeyValue) {
|
|
1365
1425
|
throw new ParseError(input, cursor.value.loc.start, `Only key-values are supported in inline tables, found ${item.type}`);
|
|
1366
1426
|
}
|
|
@@ -1371,6 +1431,7 @@ function inlineTable(cursor, input) {
|
|
|
1371
1431
|
comma: false
|
|
1372
1432
|
};
|
|
1373
1433
|
value.items.push(inline_item);
|
|
1434
|
+
merge(comments, additional_comments);
|
|
1374
1435
|
cursor.next();
|
|
1375
1436
|
}
|
|
1376
1437
|
if (cursor.done ||
|
|
@@ -1379,7 +1440,7 @@ function inlineTable(cursor, input) {
|
|
|
1379
1440
|
throw new ParseError(input, cursor.done ? value.loc.start : cursor.value.loc.start, `Expected "}", found ${cursor.done ? 'end of file' : cursor.value.raw}`);
|
|
1380
1441
|
}
|
|
1381
1442
|
value.loc.end = cursor.value.loc.end;
|
|
1382
|
-
return value;
|
|
1443
|
+
return [value, comments];
|
|
1383
1444
|
}
|
|
1384
1445
|
function inlineArray(cursor, input) {
|
|
1385
1446
|
// 7. InlineArray
|
|
@@ -1499,8 +1560,6 @@ function traverse(ast, visitor) {
|
|
|
1499
1560
|
}
|
|
1500
1561
|
}
|
|
1501
1562
|
|
|
1502
|
-
// Create a shorter alias for convenience
|
|
1503
|
-
const dateFormatHelper = DateFormatHelper;
|
|
1504
1563
|
const enter_offsets = new WeakMap();
|
|
1505
1564
|
const getEnterOffsets = (root) => {
|
|
1506
1565
|
if (!enter_offsets.has(root)) {
|
|
@@ -1515,65 +1574,8 @@ const getExitOffsets = (root) => {
|
|
|
1515
1574
|
}
|
|
1516
1575
|
return exit_offsets.get(root);
|
|
1517
1576
|
};
|
|
1518
|
-
/**
|
|
1519
|
-
* Updates location information for a replacement string to match the existing string's position.
|
|
1520
|
-
* The actual formatting and escaping is done by generateString().
|
|
1521
|
-
*
|
|
1522
|
-
* @param existing - The existing string node with the original format and location
|
|
1523
|
-
* @param replacement - The replacement string node (already fully formatted by generateString)
|
|
1524
|
-
* @returns The replacement string node with updated location information
|
|
1525
|
-
*/
|
|
1526
|
-
function fixStringLocation(existing, replacement) {
|
|
1527
|
-
if (!isMultilineString(replacement.raw)) {
|
|
1528
|
-
return replacement;
|
|
1529
|
-
}
|
|
1530
|
-
// Detect newline character to count lines correctly
|
|
1531
|
-
const newlineChar = replacement.raw.includes('\r\n') ? '\r\n' : '\n';
|
|
1532
|
-
const lineCount = (replacement.raw.match(new RegExp(newlineChar === '\r\n' ? '\\r\\n' : '\\n', 'g')) || []).length;
|
|
1533
|
-
// Update location to match existing position and account for multiple lines
|
|
1534
|
-
if (lineCount > 0) {
|
|
1535
|
-
return Object.assign(Object.assign({}, replacement), { loc: {
|
|
1536
|
-
start: existing.loc.start,
|
|
1537
|
-
end: {
|
|
1538
|
-
line: existing.loc.start.line + lineCount,
|
|
1539
|
-
column: 3 // length of delimiter
|
|
1540
|
-
}
|
|
1541
|
-
} });
|
|
1542
|
-
}
|
|
1543
|
-
else {
|
|
1544
|
-
return Object.assign(Object.assign({}, replacement), { loc: {
|
|
1545
|
-
start: existing.loc.start,
|
|
1546
|
-
end: {
|
|
1547
|
-
line: existing.loc.start.line,
|
|
1548
|
-
column: existing.loc.start.column + replacement.raw.length
|
|
1549
|
-
}
|
|
1550
|
-
} });
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
1577
|
//TODO: Add getOffsets function to get all offsets contained in the tree
|
|
1554
1578
|
function replace(root, parent, existing, replacement) {
|
|
1555
|
-
// Special handling for String nodes to preserve multiline format by editing replacement values
|
|
1556
|
-
if (isString$1(existing) && isString$1(replacement)) {
|
|
1557
|
-
// Regenerate the replacement with proper escaping based on existing format
|
|
1558
|
-
const escapedReplacement = generateString(replacement.value, existing.raw);
|
|
1559
|
-
replacement = fixStringLocation(existing, escapedReplacement);
|
|
1560
|
-
}
|
|
1561
|
-
// Special handling for DateTime nodes to preserve original format by editing replacement values
|
|
1562
|
-
if (isDateTime(existing) && isDateTime(replacement)) {
|
|
1563
|
-
// Analyze the original raw format and create a properly formatted replacement
|
|
1564
|
-
const originalRaw = existing.raw;
|
|
1565
|
-
const newValue = replacement.value;
|
|
1566
|
-
// Create a new date with the original format preserved
|
|
1567
|
-
const formattedDate = dateFormatHelper.createDateWithOriginalFormat(newValue, originalRaw);
|
|
1568
|
-
// Update the replacement with the properly formatted date
|
|
1569
|
-
replacement.value = formattedDate;
|
|
1570
|
-
replacement.raw = formattedDate.toISOString();
|
|
1571
|
-
// Adjust the location information to match the new raw length
|
|
1572
|
-
const lengthDiff = replacement.raw.length - originalRaw.length;
|
|
1573
|
-
if (lengthDiff !== 0) {
|
|
1574
|
-
replacement.loc.end.column = replacement.loc.start.column + replacement.raw.length;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
1579
|
// First, replace existing node
|
|
1578
1580
|
// (by index for items, item, or key/value)
|
|
1579
1581
|
if (hasItems(parent)) {
|
|
@@ -2210,13 +2212,37 @@ function generateString(value, existingRaw) {
|
|
|
2210
2212
|
else {
|
|
2211
2213
|
raw = JSON.stringify(value);
|
|
2212
2214
|
}
|
|
2215
|
+
// Calculate proper end location for multiline strings
|
|
2216
|
+
let endLocation;
|
|
2217
|
+
if (raw.includes('\r\n') || (raw.includes('\n') && !raw.includes('\r\n'))) {
|
|
2218
|
+
const newlineChar = raw.includes('\r\n') ? '\r\n' : '\n';
|
|
2219
|
+
const lineCount = (raw.match(new RegExp(newlineChar === '\r\n' ? '\\r\\n' : '\\n', 'g')) || []).length;
|
|
2220
|
+
if (lineCount > 0) {
|
|
2221
|
+
endLocation = {
|
|
2222
|
+
line: 1 + lineCount,
|
|
2223
|
+
column: 3 // length of delimiter (""" or ''')
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
else {
|
|
2227
|
+
endLocation = { line: 1, column: raw.length };
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
else {
|
|
2231
|
+
endLocation = { line: 1, column: raw.length };
|
|
2232
|
+
}
|
|
2213
2233
|
return {
|
|
2214
2234
|
type: NodeType.String,
|
|
2215
|
-
loc: { start: zero(), end:
|
|
2235
|
+
loc: { start: zero(), end: endLocation },
|
|
2216
2236
|
raw,
|
|
2217
2237
|
value
|
|
2218
2238
|
};
|
|
2219
2239
|
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Generates a new Integer node.
|
|
2242
|
+
*
|
|
2243
|
+
* @param value - The integer value.
|
|
2244
|
+
* @returns A new Integer node.
|
|
2245
|
+
*/
|
|
2220
2246
|
function generateInteger(value) {
|
|
2221
2247
|
const raw = value.toString();
|
|
2222
2248
|
return {
|
|
@@ -2307,6 +2333,7 @@ const DEFAULT_TRAILING_COMMA = false;
|
|
|
2307
2333
|
const DEFAULT_BRACKET_SPACING = true;
|
|
2308
2334
|
const DEFAULT_INLINE_TABLE_START = 1;
|
|
2309
2335
|
const DEFAULT_TRUNCATE_ZERO_TIME_IN_DATES = false;
|
|
2336
|
+
const DEFAULT_USE_TABS_FOR_INDENTATION = false;
|
|
2310
2337
|
// Detects if trailing commas are used in the existing TOML by examining the AST
|
|
2311
2338
|
// Returns true if trailing commas are used, false if not or comma-separated structures found (ie. default to false)
|
|
2312
2339
|
function detectTrailingComma(ast) {
|
|
@@ -2500,6 +2527,30 @@ function countTrailingNewlines(str, newlineChar) {
|
|
|
2500
2527
|
}
|
|
2501
2528
|
return count;
|
|
2502
2529
|
}
|
|
2530
|
+
// Detects if tabs are used for indentation by checking the first few indented lines
|
|
2531
|
+
function detectTabsForIndentation(str) {
|
|
2532
|
+
const lines = str.split(/\r?\n/);
|
|
2533
|
+
let tabCount = 0;
|
|
2534
|
+
let spaceCount = 0;
|
|
2535
|
+
for (const line of lines) {
|
|
2536
|
+
// Skip empty lines
|
|
2537
|
+
if (line.length === 0)
|
|
2538
|
+
continue;
|
|
2539
|
+
// Check the first character of non-empty lines
|
|
2540
|
+
if (line[0] === '\t') {
|
|
2541
|
+
tabCount++;
|
|
2542
|
+
}
|
|
2543
|
+
else if (line[0] === ' ') {
|
|
2544
|
+
spaceCount++;
|
|
2545
|
+
}
|
|
2546
|
+
// If we've seen enough evidence, make a decision
|
|
2547
|
+
if (tabCount + spaceCount >= 5) {
|
|
2548
|
+
break;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
// Prefer tabs if we see more tabs than spaces
|
|
2552
|
+
return tabCount > spaceCount;
|
|
2553
|
+
}
|
|
2503
2554
|
/**
|
|
2504
2555
|
* Validates a format object and warns about unsupported properties.
|
|
2505
2556
|
* Throws errors for supported properties with invalid types.
|
|
@@ -2510,61 +2561,62 @@ function validateFormatObject(format) {
|
|
|
2510
2561
|
if (!format || typeof format !== 'object') {
|
|
2511
2562
|
return {};
|
|
2512
2563
|
}
|
|
2513
|
-
const supportedProperties = new Set(['newLine', 'trailingNewline', 'trailingComma', 'bracketSpacing', 'inlineTableStart', 'truncateZeroTimeInDates']);
|
|
2564
|
+
const supportedProperties = new Set(['newLine', 'trailingNewline', 'trailingComma', 'bracketSpacing', 'inlineTableStart', 'truncateZeroTimeInDates', 'useTabsForIndentation']);
|
|
2514
2565
|
const validatedFormat = {};
|
|
2515
2566
|
const unsupportedProperties = [];
|
|
2516
2567
|
const invalidTypeProperties = [];
|
|
2517
|
-
// Check all enumerable properties of the format object
|
|
2568
|
+
// Check all enumerable properties of the format object, including properties
|
|
2569
|
+
// provided via the prototype chain (common in JS Object.create(...) patterns).
|
|
2518
2570
|
for (const key in format) {
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
}
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
}
|
|
2564
|
-
else {
|
|
2565
|
-
unsupportedProperties.push(key);
|
|
2571
|
+
const isOwnEnumerable = Object.prototype.hasOwnProperty.call(format, key);
|
|
2572
|
+
if (supportedProperties.has(key)) {
|
|
2573
|
+
const value = format[key];
|
|
2574
|
+
// Type validation for each property
|
|
2575
|
+
switch (key) {
|
|
2576
|
+
case 'newLine':
|
|
2577
|
+
if (typeof value === 'string') {
|
|
2578
|
+
validatedFormat.newLine = value;
|
|
2579
|
+
}
|
|
2580
|
+
else {
|
|
2581
|
+
invalidTypeProperties.push(`${key} (expected string, got ${typeof value})`);
|
|
2582
|
+
}
|
|
2583
|
+
break;
|
|
2584
|
+
case 'trailingNewline':
|
|
2585
|
+
if (typeof value === 'boolean' || typeof value === 'number') {
|
|
2586
|
+
validatedFormat.trailingNewline = value;
|
|
2587
|
+
}
|
|
2588
|
+
else {
|
|
2589
|
+
invalidTypeProperties.push(`${key} (expected boolean or number, got ${typeof value})`);
|
|
2590
|
+
}
|
|
2591
|
+
break;
|
|
2592
|
+
case 'trailingComma':
|
|
2593
|
+
case 'bracketSpacing':
|
|
2594
|
+
case 'truncateZeroTimeInDates':
|
|
2595
|
+
case 'useTabsForIndentation':
|
|
2596
|
+
if (typeof value === 'boolean') {
|
|
2597
|
+
validatedFormat[key] = value;
|
|
2598
|
+
}
|
|
2599
|
+
else {
|
|
2600
|
+
invalidTypeProperties.push(`${key} (expected boolean, got ${typeof value})`);
|
|
2601
|
+
}
|
|
2602
|
+
break;
|
|
2603
|
+
case 'inlineTableStart':
|
|
2604
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
|
|
2605
|
+
validatedFormat.inlineTableStart = value;
|
|
2606
|
+
}
|
|
2607
|
+
else if (value === undefined || value === null) {
|
|
2608
|
+
// Allow undefined/null to use default
|
|
2609
|
+
validatedFormat.inlineTableStart = value;
|
|
2610
|
+
}
|
|
2611
|
+
else {
|
|
2612
|
+
invalidTypeProperties.push(`${key} (expected non-negative integer or undefined, got ${typeof value})`);
|
|
2613
|
+
}
|
|
2614
|
+
break;
|
|
2566
2615
|
}
|
|
2567
2616
|
}
|
|
2617
|
+
else if (isOwnEnumerable) {
|
|
2618
|
+
unsupportedProperties.push(key);
|
|
2619
|
+
}
|
|
2568
2620
|
}
|
|
2569
2621
|
// Warn about unsupported properties
|
|
2570
2622
|
if (unsupportedProperties.length > 0) {
|
|
@@ -2585,7 +2637,7 @@ function validateFormatObject(format) {
|
|
|
2585
2637
|
* @returns A resolved TomlFormat instance
|
|
2586
2638
|
*/
|
|
2587
2639
|
function resolveTomlFormat(format, fallbackFormat) {
|
|
2588
|
-
var _a, _b, _c, _d, _e;
|
|
2640
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2589
2641
|
if (format) {
|
|
2590
2642
|
// If format is provided, validate and merge it with fallback
|
|
2591
2643
|
if (format instanceof TomlFormat) {
|
|
@@ -2595,7 +2647,7 @@ function resolveTomlFormat(format, fallbackFormat) {
|
|
|
2595
2647
|
// Validate the format object and warn about unsupported properties
|
|
2596
2648
|
const validatedFormat = validateFormatObject(format);
|
|
2597
2649
|
// Create a new TomlFormat instance with validated properties
|
|
2598
|
-
return new TomlFormat((_a = validatedFormat.newLine) !== null && _a !== void 0 ? _a : fallbackFormat.newLine, (_b = validatedFormat.trailingNewline) !== null && _b !== void 0 ? _b : fallbackFormat.trailingNewline, (_c = validatedFormat.trailingComma) !== null && _c !== void 0 ? _c : fallbackFormat.trailingComma, (_d = validatedFormat.bracketSpacing) !== null && _d !== void 0 ? _d : fallbackFormat.bracketSpacing, validatedFormat.inlineTableStart !== undefined ? validatedFormat.inlineTableStart : fallbackFormat.inlineTableStart, (_e = validatedFormat.truncateZeroTimeInDates) !== null && _e !== void 0 ? _e : fallbackFormat.truncateZeroTimeInDates);
|
|
2650
|
+
return new TomlFormat((_a = validatedFormat.newLine) !== null && _a !== void 0 ? _a : fallbackFormat.newLine, (_b = validatedFormat.trailingNewline) !== null && _b !== void 0 ? _b : fallbackFormat.trailingNewline, (_c = validatedFormat.trailingComma) !== null && _c !== void 0 ? _c : fallbackFormat.trailingComma, (_d = validatedFormat.bracketSpacing) !== null && _d !== void 0 ? _d : fallbackFormat.bracketSpacing, validatedFormat.inlineTableStart !== undefined ? validatedFormat.inlineTableStart : fallbackFormat.inlineTableStart, (_e = validatedFormat.truncateZeroTimeInDates) !== null && _e !== void 0 ? _e : fallbackFormat.truncateZeroTimeInDates, (_f = validatedFormat.useTabsForIndentation) !== null && _f !== void 0 ? _f : fallbackFormat.useTabsForIndentation);
|
|
2599
2651
|
}
|
|
2600
2652
|
}
|
|
2601
2653
|
else {
|
|
@@ -2607,8 +2659,7 @@ class TomlFormat {
|
|
|
2607
2659
|
// These options were part of the original TimHall's version and are not yet implemented
|
|
2608
2660
|
//printWidth?: number;
|
|
2609
2661
|
//tabWidth?: number;
|
|
2610
|
-
|
|
2611
|
-
constructor(newLine, trailingNewline, trailingComma, bracketSpacing, inlineTableStart, truncateZeroTimeInDates) {
|
|
2662
|
+
constructor(newLine, trailingNewline, trailingComma, bracketSpacing, inlineTableStart, truncateZeroTimeInDates, useTabsForIndentation) {
|
|
2612
2663
|
// Use provided values or fall back to defaults
|
|
2613
2664
|
this.newLine = newLine !== null && newLine !== void 0 ? newLine : DEFAULT_NEWLINE;
|
|
2614
2665
|
this.trailingNewline = trailingNewline !== null && trailingNewline !== void 0 ? trailingNewline : DEFAULT_TRAILING_NEWLINE;
|
|
@@ -2616,6 +2667,7 @@ class TomlFormat {
|
|
|
2616
2667
|
this.bracketSpacing = bracketSpacing !== null && bracketSpacing !== void 0 ? bracketSpacing : DEFAULT_BRACKET_SPACING;
|
|
2617
2668
|
this.inlineTableStart = inlineTableStart !== null && inlineTableStart !== void 0 ? inlineTableStart : DEFAULT_INLINE_TABLE_START;
|
|
2618
2669
|
this.truncateZeroTimeInDates = truncateZeroTimeInDates !== null && truncateZeroTimeInDates !== void 0 ? truncateZeroTimeInDates : DEFAULT_TRUNCATE_ZERO_TIME_IN_DATES;
|
|
2670
|
+
this.useTabsForIndentation = useTabsForIndentation !== null && useTabsForIndentation !== void 0 ? useTabsForIndentation : DEFAULT_USE_TABS_FOR_INDENTATION;
|
|
2619
2671
|
}
|
|
2620
2672
|
/**
|
|
2621
2673
|
* Creates a new TomlFormat instance with default formatting preferences.
|
|
@@ -2629,7 +2681,7 @@ class TomlFormat {
|
|
|
2629
2681
|
* - truncateZeroTimeInDates: false
|
|
2630
2682
|
*/
|
|
2631
2683
|
static default() {
|
|
2632
|
-
return new TomlFormat(DEFAULT_NEWLINE, DEFAULT_TRAILING_NEWLINE, DEFAULT_TRAILING_COMMA, DEFAULT_BRACKET_SPACING, DEFAULT_INLINE_TABLE_START, DEFAULT_TRUNCATE_ZERO_TIME_IN_DATES);
|
|
2684
|
+
return new TomlFormat(DEFAULT_NEWLINE, DEFAULT_TRAILING_NEWLINE, DEFAULT_TRAILING_COMMA, DEFAULT_BRACKET_SPACING, DEFAULT_INLINE_TABLE_START, DEFAULT_TRUNCATE_ZERO_TIME_IN_DATES, DEFAULT_USE_TABS_FOR_INDENTATION);
|
|
2633
2685
|
}
|
|
2634
2686
|
/**
|
|
2635
2687
|
* Auto-detects formatting preferences from an existing TOML string.
|
|
@@ -2669,6 +2721,8 @@ class TomlFormat {
|
|
|
2669
2721
|
format.trailingComma = DEFAULT_TRAILING_COMMA;
|
|
2670
2722
|
format.bracketSpacing = DEFAULT_BRACKET_SPACING;
|
|
2671
2723
|
}
|
|
2724
|
+
// Detect if tabs are used for indentation
|
|
2725
|
+
format.useTabsForIndentation = detectTabsForIndentation(tomlString);
|
|
2672
2726
|
// inlineTableStart uses default value since auto-detection would require
|
|
2673
2727
|
// complex analysis of nested table formatting preferences
|
|
2674
2728
|
format.inlineTableStart = DEFAULT_INLINE_TABLE_START;
|
|
@@ -2998,71 +3052,85 @@ const BY_NEW_LINE = /(\r\n|\n)/g;
|
|
|
2998
3052
|
* It preserves the original formatting, spacing, and structure of the TOML file.
|
|
2999
3053
|
*
|
|
3000
3054
|
* @param ast - The Abstract Syntax Tree representing the parsed TOML document
|
|
3001
|
-
* @param
|
|
3002
|
-
* @param options - Optional configuration object
|
|
3003
|
-
* @param options.trailingNewline - Number of trailing newlines to add (1 by default)
|
|
3055
|
+
* @param format - The formatting options to use for the output
|
|
3004
3056
|
* @returns The reconstructed TOML document as a string
|
|
3005
3057
|
*
|
|
3006
3058
|
* @example
|
|
3007
3059
|
* ```typescript
|
|
3008
|
-
* const tomlString = toTOML(ast,
|
|
3060
|
+
* const tomlString = toTOML(ast, TomlFormat.default());
|
|
3009
3061
|
* ```
|
|
3010
3062
|
*/
|
|
3011
3063
|
function toTOML(ast, format) {
|
|
3012
3064
|
const lines = [];
|
|
3065
|
+
const paddingChar = format.useTabsForIndentation ? '\t' : SPACE;
|
|
3013
3066
|
traverse(ast, {
|
|
3014
3067
|
[NodeType.TableKey](node) {
|
|
3015
3068
|
const { start, end } = node.loc;
|
|
3016
|
-
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '[');
|
|
3017
|
-
write(lines, { start: { line: end.line, column: end.column - 1 }, end }, ']');
|
|
3069
|
+
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '[', paddingChar);
|
|
3070
|
+
write(lines, { start: { line: end.line, column: end.column - 1 }, end }, ']', paddingChar);
|
|
3018
3071
|
},
|
|
3019
3072
|
[NodeType.TableArrayKey](node) {
|
|
3020
3073
|
const { start, end } = node.loc;
|
|
3021
|
-
write(lines, { start, end: { line: start.line, column: start.column + 2 } }, '[[');
|
|
3022
|
-
write(lines, { start: { line: end.line, column: end.column - 2 }, end }, ']]');
|
|
3074
|
+
write(lines, { start, end: { line: start.line, column: start.column + 2 } }, '[[', paddingChar);
|
|
3075
|
+
write(lines, { start: { line: end.line, column: end.column - 2 }, end }, ']]', paddingChar);
|
|
3023
3076
|
},
|
|
3024
3077
|
[NodeType.KeyValue](node) {
|
|
3025
3078
|
const { start: { line } } = node.loc;
|
|
3026
|
-
write(lines, { start: { line, column: node.equals }, end: { line, column: node.equals + 1 } }, '=');
|
|
3079
|
+
write(lines, { start: { line, column: node.equals }, end: { line, column: node.equals + 1 } }, '=', paddingChar);
|
|
3027
3080
|
},
|
|
3028
3081
|
[NodeType.Key](node) {
|
|
3029
|
-
write(lines, node.loc, node.raw);
|
|
3082
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3030
3083
|
},
|
|
3031
3084
|
[NodeType.String](node) {
|
|
3032
|
-
write(lines, node.loc, node.raw);
|
|
3085
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3033
3086
|
},
|
|
3034
3087
|
[NodeType.Integer](node) {
|
|
3035
|
-
write(lines, node.loc, node.raw);
|
|
3088
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3036
3089
|
},
|
|
3037
3090
|
[NodeType.Float](node) {
|
|
3038
|
-
write(lines, node.loc, node.raw);
|
|
3091
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3039
3092
|
},
|
|
3040
3093
|
[NodeType.Boolean](node) {
|
|
3041
|
-
write(lines, node.loc, node.value.toString());
|
|
3094
|
+
write(lines, node.loc, node.value.toString(), paddingChar);
|
|
3042
3095
|
},
|
|
3043
3096
|
[NodeType.DateTime](node) {
|
|
3044
|
-
write(lines, node.loc, node.raw);
|
|
3097
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3045
3098
|
},
|
|
3046
3099
|
[NodeType.InlineArray](node) {
|
|
3047
3100
|
const { start, end } = node.loc;
|
|
3048
|
-
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '[');
|
|
3049
|
-
write(lines, { start: { line: end.line, column: end.column - 1 }, end }, ']');
|
|
3101
|
+
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '[', paddingChar);
|
|
3102
|
+
write(lines, { start: { line: end.line, column: end.column - 1 }, end }, ']', paddingChar);
|
|
3050
3103
|
},
|
|
3051
3104
|
[NodeType.InlineTable](node) {
|
|
3052
3105
|
const { start, end } = node.loc;
|
|
3053
|
-
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '{');
|
|
3054
|
-
write(lines, { start: { line: end.line, column: end.column - 1 }, end }, '}');
|
|
3106
|
+
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '{', paddingChar);
|
|
3107
|
+
write(lines, { start: { line: end.line, column: end.column - 1 }, end }, '}', paddingChar);
|
|
3055
3108
|
},
|
|
3056
3109
|
[NodeType.InlineItem](node) {
|
|
3057
3110
|
if (!node.comma)
|
|
3058
3111
|
return;
|
|
3059
3112
|
const start = node.loc.end;
|
|
3060
|
-
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, ',');
|
|
3113
|
+
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, ',', paddingChar);
|
|
3061
3114
|
},
|
|
3062
3115
|
[NodeType.Comment](node) {
|
|
3063
|
-
write(lines, node.loc, node.raw);
|
|
3116
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3064
3117
|
}
|
|
3065
3118
|
});
|
|
3119
|
+
// Post-process: convert leading spaces to tabs if useTabsForIndentation is enabled
|
|
3120
|
+
if (format.useTabsForIndentation) {
|
|
3121
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3122
|
+
const line = lines[i];
|
|
3123
|
+
// Find the leading whitespace
|
|
3124
|
+
const match = line.match(/^( +)/);
|
|
3125
|
+
if (match) {
|
|
3126
|
+
const leadingSpaces = match[1];
|
|
3127
|
+
// Replace entire leading space sequence with equivalent tabs
|
|
3128
|
+
// Each space becomes a tab (preserving the visual width)
|
|
3129
|
+
const leadingTabs = '\t'.repeat(leadingSpaces.length);
|
|
3130
|
+
lines[i] = leadingTabs + line.substring(leadingSpaces.length);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3066
3134
|
return lines.join(format.newLine) + format.newLine.repeat(format.trailingNewline);
|
|
3067
3135
|
}
|
|
3068
3136
|
/**
|
|
@@ -3079,6 +3147,7 @@ function toTOML(ast, format) {
|
|
|
3079
3147
|
* - end: { line: number, column: number } - Ending position (1-indexed line, 0-indexed column)
|
|
3080
3148
|
* @param raw - The raw string content to write at the specified location.
|
|
3081
3149
|
* Can contain multiple lines separated by \n or \r\n.
|
|
3150
|
+
* @param paddingChar - The character to use for padding (space or tab)
|
|
3082
3151
|
*
|
|
3083
3152
|
* @throws {Error} When there's a mismatch between location span and raw string line count
|
|
3084
3153
|
* @throws {Error} When attempting to write to an uninitialized line
|
|
@@ -3087,11 +3156,11 @@ function toTOML(ast, format) {
|
|
|
3087
3156
|
* ```typescript
|
|
3088
3157
|
* const lines = ['', ''];
|
|
3089
3158
|
* const location = { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } };
|
|
3090
|
-
* write(lines, location, 'key');
|
|
3159
|
+
* write(lines, location, 'key', ' ');
|
|
3091
3160
|
* // Result: lines[0] becomes 'key'
|
|
3092
3161
|
* ```
|
|
3093
3162
|
*/
|
|
3094
|
-
function write(lines, loc, raw) {
|
|
3163
|
+
function write(lines, loc, raw, paddingChar = SPACE) {
|
|
3095
3164
|
const raw_lines = raw.split(BY_NEW_LINE).filter(line => line !== '\n' && line !== '\r\n');
|
|
3096
3165
|
const expected_lines = loc.end.line - loc.start.line + 1;
|
|
3097
3166
|
if (raw_lines.length !== expected_lines) {
|
|
@@ -3105,10 +3174,19 @@ function write(lines, loc, raw) {
|
|
|
3105
3174
|
}
|
|
3106
3175
|
const is_start_line = i === loc.start.line;
|
|
3107
3176
|
const is_end_line = i === loc.end.line;
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3177
|
+
let before = '';
|
|
3178
|
+
if (is_start_line) {
|
|
3179
|
+
const existingBefore = line.substring(0, loc.start.column);
|
|
3180
|
+
if (existingBefore.length < loc.start.column) {
|
|
3181
|
+
// Need to pad - always use spaces during write phase
|
|
3182
|
+
// Tab conversion happens in post-processing for leading indentation only
|
|
3183
|
+
before = existingBefore.padEnd(loc.start.column, SPACE);
|
|
3184
|
+
}
|
|
3185
|
+
else {
|
|
3186
|
+
before = existingBefore;
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
const after = is_end_line ? line.substring(loc.end.column) : '';
|
|
3112
3190
|
lines[i - 1] = before + raw_lines[i - loc.start.line] + after;
|
|
3113
3191
|
}
|
|
3114
3192
|
}
|
|
@@ -3226,11 +3304,15 @@ function toValue(node) {
|
|
|
3226
3304
|
return result;
|
|
3227
3305
|
case NodeType.InlineArray:
|
|
3228
3306
|
return node.items.map(item => toValue(item.item));
|
|
3307
|
+
case NodeType.DateTime:
|
|
3308
|
+
// Preserve TOML date/time custom classes so format is retained when
|
|
3309
|
+
// round-tripping through stringify() (e.g. date-only, time-only, local vs offset).
|
|
3310
|
+
// These classes extend Date, so JS users can still treat them as Dates.
|
|
3311
|
+
return node.value;
|
|
3229
3312
|
case NodeType.String:
|
|
3230
3313
|
case NodeType.Integer:
|
|
3231
3314
|
case NodeType.Float:
|
|
3232
3315
|
case NodeType.Boolean:
|
|
3233
|
-
case NodeType.DateTime:
|
|
3234
3316
|
return node.value;
|
|
3235
3317
|
default:
|
|
3236
3318
|
throw new Error(`Unrecognized value type "${node.type}"`);
|
|
@@ -3470,13 +3552,29 @@ function findByPath(node, path) {
|
|
|
3470
3552
|
key = key.concat(array_index);
|
|
3471
3553
|
}
|
|
3472
3554
|
else if (isInlineItem(item) && isKeyValue(item.item)) {
|
|
3555
|
+
// For InlineItems wrapping KeyValues, extract the key
|
|
3473
3556
|
key = item.item.key.value;
|
|
3474
3557
|
}
|
|
3475
3558
|
else if (isInlineItem(item)) {
|
|
3476
3559
|
key = [index];
|
|
3477
3560
|
}
|
|
3478
3561
|
if (key.length && arraysEqual(key, path.slice(0, key.length))) {
|
|
3479
|
-
|
|
3562
|
+
// For InlineItems containing KeyValues, we need to search within the value
|
|
3563
|
+
// but still return the InlineItem or its contents appropriately
|
|
3564
|
+
if (isInlineItem(item) && isKeyValue(item.item)) {
|
|
3565
|
+
if (path.length === key.length) {
|
|
3566
|
+
// If we've matched the full path, return the InlineItem itself
|
|
3567
|
+
// so it can be found and replaced in the parent's items array
|
|
3568
|
+
found = item;
|
|
3569
|
+
}
|
|
3570
|
+
else {
|
|
3571
|
+
// Continue searching within the KeyValue's value
|
|
3572
|
+
found = findByPath(item.item.value, path.slice(key.length));
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
else {
|
|
3576
|
+
found = findByPath(item, path.slice(key.length));
|
|
3577
|
+
}
|
|
3480
3578
|
return true;
|
|
3481
3579
|
}
|
|
3482
3580
|
else {
|
|
@@ -3583,6 +3681,54 @@ function reorder(changes) {
|
|
|
3583
3681
|
}
|
|
3584
3682
|
return changes;
|
|
3585
3683
|
}
|
|
3684
|
+
/**
|
|
3685
|
+
* Preserves formatting from the existing node when applying it to the replacement node.
|
|
3686
|
+
* This includes multiline string formats, trailing commas, DateTime formats, etc.
|
|
3687
|
+
*
|
|
3688
|
+
* @param existing - The existing node with formatting to preserve
|
|
3689
|
+
* @param replacement - The replacement node to apply formatting to
|
|
3690
|
+
*/
|
|
3691
|
+
function preserveFormatting(existing, replacement) {
|
|
3692
|
+
// Preserve multiline string format
|
|
3693
|
+
if (isString$1(existing) && isString$1(replacement) && isMultilineString(existing.raw)) {
|
|
3694
|
+
// Generate new string node with preserved multiline format
|
|
3695
|
+
const newString = generateString(replacement.value, existing.raw);
|
|
3696
|
+
replacement.raw = newString.raw;
|
|
3697
|
+
replacement.loc = newString.loc;
|
|
3698
|
+
}
|
|
3699
|
+
// Preserve DateTime format
|
|
3700
|
+
if (isDateTime(existing) && isDateTime(replacement)) {
|
|
3701
|
+
// Analyze the original raw format and create a properly formatted replacement
|
|
3702
|
+
const originalRaw = existing.raw;
|
|
3703
|
+
const newValue = replacement.value;
|
|
3704
|
+
// Create a new date with the original format preserved
|
|
3705
|
+
const formattedDate = DateFormatHelper.createDateWithOriginalFormat(newValue, originalRaw);
|
|
3706
|
+
// Update the replacement with the properly formatted date
|
|
3707
|
+
replacement.value = formattedDate;
|
|
3708
|
+
replacement.raw = formattedDate.toISOString();
|
|
3709
|
+
// Adjust the location information to match the new raw length
|
|
3710
|
+
const lengthDiff = replacement.raw.length - originalRaw.length;
|
|
3711
|
+
if (lengthDiff !== 0) {
|
|
3712
|
+
replacement.loc.end.column = replacement.loc.start.column + replacement.raw.length;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
// Preserve array trailing comma format
|
|
3716
|
+
if (isInlineArray(existing) && isInlineArray(replacement)) {
|
|
3717
|
+
const originalHadTrailingCommas = arrayHadTrailingCommas(existing);
|
|
3718
|
+
if (replacement.items.length > 0) {
|
|
3719
|
+
const lastItem = replacement.items[replacement.items.length - 1];
|
|
3720
|
+
lastItem.comma = originalHadTrailingCommas;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
// Preserve inline table trailing comma format
|
|
3724
|
+
if (isInlineTable(existing) && isInlineTable(replacement)) {
|
|
3725
|
+
const originalHadTrailingCommas = tableHadTrailingCommas(existing);
|
|
3726
|
+
if (replacement.items.length > 0) {
|
|
3727
|
+
const lastItem = replacement.items[replacement.items.length - 1];
|
|
3728
|
+
lastItem.comma = originalHadTrailingCommas;
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3586
3732
|
/**
|
|
3587
3733
|
* Applies a list of changes to the original TOML document AST while preserving formatting and structure.
|
|
3588
3734
|
*
|
|
@@ -3715,7 +3861,13 @@ function applyChanges(original, updated, changes, format) {
|
|
|
3715
3861
|
insert(original, parent, child, undefined, true);
|
|
3716
3862
|
}
|
|
3717
3863
|
else {
|
|
3718
|
-
|
|
3864
|
+
// Unwrap InlineItem if we're adding to a Table (not InlineTable)
|
|
3865
|
+
// InlineItems should only exist within InlineTables or InlineArrays
|
|
3866
|
+
let childToInsert = child;
|
|
3867
|
+
if (isInlineItem(child) && (isTable(parent) || isDocument(parent))) {
|
|
3868
|
+
childToInsert = child.item;
|
|
3869
|
+
}
|
|
3870
|
+
insert(original, parent, childToInsert);
|
|
3719
3871
|
}
|
|
3720
3872
|
}
|
|
3721
3873
|
}
|
|
@@ -3725,26 +3877,8 @@ function applyChanges(original, updated, changes, format) {
|
|
|
3725
3877
|
let parent;
|
|
3726
3878
|
if (isKeyValue(existing) && isKeyValue(replacement)) {
|
|
3727
3879
|
// Edit for key-value means value changes
|
|
3728
|
-
//
|
|
3729
|
-
|
|
3730
|
-
const originalHadTrailingCommas = arrayHadTrailingCommas(existing.value);
|
|
3731
|
-
const newArray = replacement.value;
|
|
3732
|
-
// Apply or remove trailing comma based on original format
|
|
3733
|
-
if (newArray.items.length > 0) {
|
|
3734
|
-
const lastItem = newArray.items[newArray.items.length - 1];
|
|
3735
|
-
lastItem.comma = originalHadTrailingCommas;
|
|
3736
|
-
}
|
|
3737
|
-
}
|
|
3738
|
-
// Special handling for inline tables: preserve original trailing comma format
|
|
3739
|
-
if (isInlineTable(existing.value) && isInlineTable(replacement.value)) {
|
|
3740
|
-
const originalHadTrailingCommas = tableHadTrailingCommas(existing.value);
|
|
3741
|
-
const newTable = replacement.value;
|
|
3742
|
-
// Apply or remove trailing comma based on original format
|
|
3743
|
-
if (newTable.items.length > 0) {
|
|
3744
|
-
const lastItem = newTable.items[newTable.items.length - 1];
|
|
3745
|
-
lastItem.comma = originalHadTrailingCommas;
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3880
|
+
// Preserve formatting from existing value in replacement value
|
|
3881
|
+
preserveFormatting(existing.value, replacement.value);
|
|
3748
3882
|
parent = existing;
|
|
3749
3883
|
existing = existing.value;
|
|
3750
3884
|
replacement = replacement.value;
|
|
@@ -3762,6 +3896,14 @@ function applyChanges(original, updated, changes, format) {
|
|
|
3762
3896
|
parent = existing;
|
|
3763
3897
|
existing = existing.item;
|
|
3764
3898
|
}
|
|
3899
|
+
else if (isInlineItem(existing) && isInlineItem(replacement) && isKeyValue(existing.item) && isKeyValue(replacement.item)) {
|
|
3900
|
+
// Both are InlineItems wrapping KeyValues (nested inline table edits)
|
|
3901
|
+
// Preserve formatting and edit the value within
|
|
3902
|
+
preserveFormatting(existing.item.value, replacement.item.value);
|
|
3903
|
+
parent = existing.item;
|
|
3904
|
+
existing = existing.item.value;
|
|
3905
|
+
replacement = replacement.item.value;
|
|
3906
|
+
}
|
|
3765
3907
|
else {
|
|
3766
3908
|
parent = findParent(original, change.path);
|
|
3767
3909
|
// Special handling for array element edits
|