@decimalturn/toml-patch 0.5.2 → 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.d.ts
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
|
interface Location {
|
|
3
3
|
start: Position;
|
|
4
4
|
end: Position;
|
|
@@ -222,7 +222,17 @@ declare class TomlFormat {
|
|
|
222
222
|
*
|
|
223
223
|
*/
|
|
224
224
|
truncateZeroTimeInDates?: boolean;
|
|
225
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Whether to use tabs instead of spaces for indentation/padding.
|
|
227
|
+
* When enabled, lines that need to be indented will use tabs.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* - true: Uses tabs for indentation
|
|
231
|
+
* - false: Uses spaces for indentation (default)
|
|
232
|
+
*
|
|
233
|
+
*/
|
|
234
|
+
useTabsForIndentation?: boolean;
|
|
235
|
+
constructor(newLine?: string, trailingNewline?: number, trailingComma?: boolean, bracketSpacing?: boolean, inlineTableStart?: number, truncateZeroTimeInDates?: boolean, useTabsForIndentation?: boolean);
|
|
226
236
|
/**
|
|
227
237
|
* Creates a new TomlFormat instance with default formatting preferences.
|
|
228
238
|
*
|
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;
|
|
@@ -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);
|
|
@@ -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
|
|
@@ -2272,6 +2333,7 @@ const DEFAULT_TRAILING_COMMA = false;
|
|
|
2272
2333
|
const DEFAULT_BRACKET_SPACING = true;
|
|
2273
2334
|
const DEFAULT_INLINE_TABLE_START = 1;
|
|
2274
2335
|
const DEFAULT_TRUNCATE_ZERO_TIME_IN_DATES = false;
|
|
2336
|
+
const DEFAULT_USE_TABS_FOR_INDENTATION = false;
|
|
2275
2337
|
// Detects if trailing commas are used in the existing TOML by examining the AST
|
|
2276
2338
|
// Returns true if trailing commas are used, false if not or comma-separated structures found (ie. default to false)
|
|
2277
2339
|
function detectTrailingComma(ast) {
|
|
@@ -2465,6 +2527,30 @@ function countTrailingNewlines(str, newlineChar) {
|
|
|
2465
2527
|
}
|
|
2466
2528
|
return count;
|
|
2467
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
|
+
}
|
|
2468
2554
|
/**
|
|
2469
2555
|
* Validates a format object and warns about unsupported properties.
|
|
2470
2556
|
* Throws errors for supported properties with invalid types.
|
|
@@ -2475,61 +2561,62 @@ function validateFormatObject(format) {
|
|
|
2475
2561
|
if (!format || typeof format !== 'object') {
|
|
2476
2562
|
return {};
|
|
2477
2563
|
}
|
|
2478
|
-
const supportedProperties = new Set(['newLine', 'trailingNewline', 'trailingComma', 'bracketSpacing', 'inlineTableStart', 'truncateZeroTimeInDates']);
|
|
2564
|
+
const supportedProperties = new Set(['newLine', 'trailingNewline', 'trailingComma', 'bracketSpacing', 'inlineTableStart', 'truncateZeroTimeInDates', 'useTabsForIndentation']);
|
|
2479
2565
|
const validatedFormat = {};
|
|
2480
2566
|
const unsupportedProperties = [];
|
|
2481
2567
|
const invalidTypeProperties = [];
|
|
2482
|
-
// 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).
|
|
2483
2570
|
for (const key in format) {
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
}
|
|
2529
|
-
else {
|
|
2530
|
-
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;
|
|
2531
2615
|
}
|
|
2532
2616
|
}
|
|
2617
|
+
else if (isOwnEnumerable) {
|
|
2618
|
+
unsupportedProperties.push(key);
|
|
2619
|
+
}
|
|
2533
2620
|
}
|
|
2534
2621
|
// Warn about unsupported properties
|
|
2535
2622
|
if (unsupportedProperties.length > 0) {
|
|
@@ -2550,7 +2637,7 @@ function validateFormatObject(format) {
|
|
|
2550
2637
|
* @returns A resolved TomlFormat instance
|
|
2551
2638
|
*/
|
|
2552
2639
|
function resolveTomlFormat(format, fallbackFormat) {
|
|
2553
|
-
var _a, _b, _c, _d, _e;
|
|
2640
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2554
2641
|
if (format) {
|
|
2555
2642
|
// If format is provided, validate and merge it with fallback
|
|
2556
2643
|
if (format instanceof TomlFormat) {
|
|
@@ -2560,7 +2647,7 @@ function resolveTomlFormat(format, fallbackFormat) {
|
|
|
2560
2647
|
// Validate the format object and warn about unsupported properties
|
|
2561
2648
|
const validatedFormat = validateFormatObject(format);
|
|
2562
2649
|
// Create a new TomlFormat instance with validated properties
|
|
2563
|
-
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);
|
|
2564
2651
|
}
|
|
2565
2652
|
}
|
|
2566
2653
|
else {
|
|
@@ -2572,8 +2659,7 @@ class TomlFormat {
|
|
|
2572
2659
|
// These options were part of the original TimHall's version and are not yet implemented
|
|
2573
2660
|
//printWidth?: number;
|
|
2574
2661
|
//tabWidth?: number;
|
|
2575
|
-
|
|
2576
|
-
constructor(newLine, trailingNewline, trailingComma, bracketSpacing, inlineTableStart, truncateZeroTimeInDates) {
|
|
2662
|
+
constructor(newLine, trailingNewline, trailingComma, bracketSpacing, inlineTableStart, truncateZeroTimeInDates, useTabsForIndentation) {
|
|
2577
2663
|
// Use provided values or fall back to defaults
|
|
2578
2664
|
this.newLine = newLine !== null && newLine !== void 0 ? newLine : DEFAULT_NEWLINE;
|
|
2579
2665
|
this.trailingNewline = trailingNewline !== null && trailingNewline !== void 0 ? trailingNewline : DEFAULT_TRAILING_NEWLINE;
|
|
@@ -2581,6 +2667,7 @@ class TomlFormat {
|
|
|
2581
2667
|
this.bracketSpacing = bracketSpacing !== null && bracketSpacing !== void 0 ? bracketSpacing : DEFAULT_BRACKET_SPACING;
|
|
2582
2668
|
this.inlineTableStart = inlineTableStart !== null && inlineTableStart !== void 0 ? inlineTableStart : DEFAULT_INLINE_TABLE_START;
|
|
2583
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;
|
|
2584
2671
|
}
|
|
2585
2672
|
/**
|
|
2586
2673
|
* Creates a new TomlFormat instance with default formatting preferences.
|
|
@@ -2594,7 +2681,7 @@ class TomlFormat {
|
|
|
2594
2681
|
* - truncateZeroTimeInDates: false
|
|
2595
2682
|
*/
|
|
2596
2683
|
static default() {
|
|
2597
|
-
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);
|
|
2598
2685
|
}
|
|
2599
2686
|
/**
|
|
2600
2687
|
* Auto-detects formatting preferences from an existing TOML string.
|
|
@@ -2634,6 +2721,8 @@ class TomlFormat {
|
|
|
2634
2721
|
format.trailingComma = DEFAULT_TRAILING_COMMA;
|
|
2635
2722
|
format.bracketSpacing = DEFAULT_BRACKET_SPACING;
|
|
2636
2723
|
}
|
|
2724
|
+
// Detect if tabs are used for indentation
|
|
2725
|
+
format.useTabsForIndentation = detectTabsForIndentation(tomlString);
|
|
2637
2726
|
// inlineTableStart uses default value since auto-detection would require
|
|
2638
2727
|
// complex analysis of nested table formatting preferences
|
|
2639
2728
|
format.inlineTableStart = DEFAULT_INLINE_TABLE_START;
|
|
@@ -2963,71 +3052,85 @@ const BY_NEW_LINE = /(\r\n|\n)/g;
|
|
|
2963
3052
|
* It preserves the original formatting, spacing, and structure of the TOML file.
|
|
2964
3053
|
*
|
|
2965
3054
|
* @param ast - The Abstract Syntax Tree representing the parsed TOML document
|
|
2966
|
-
* @param
|
|
2967
|
-
* @param options - Optional configuration object
|
|
2968
|
-
* @param options.trailingNewline - Number of trailing newlines to add (1 by default)
|
|
3055
|
+
* @param format - The formatting options to use for the output
|
|
2969
3056
|
* @returns The reconstructed TOML document as a string
|
|
2970
3057
|
*
|
|
2971
3058
|
* @example
|
|
2972
3059
|
* ```typescript
|
|
2973
|
-
* const tomlString = toTOML(ast,
|
|
3060
|
+
* const tomlString = toTOML(ast, TomlFormat.default());
|
|
2974
3061
|
* ```
|
|
2975
3062
|
*/
|
|
2976
3063
|
function toTOML(ast, format) {
|
|
2977
3064
|
const lines = [];
|
|
3065
|
+
const paddingChar = format.useTabsForIndentation ? '\t' : SPACE;
|
|
2978
3066
|
traverse(ast, {
|
|
2979
3067
|
[NodeType.TableKey](node) {
|
|
2980
3068
|
const { start, end } = node.loc;
|
|
2981
|
-
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '[');
|
|
2982
|
-
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);
|
|
2983
3071
|
},
|
|
2984
3072
|
[NodeType.TableArrayKey](node) {
|
|
2985
3073
|
const { start, end } = node.loc;
|
|
2986
|
-
write(lines, { start, end: { line: start.line, column: start.column + 2 } }, '[[');
|
|
2987
|
-
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);
|
|
2988
3076
|
},
|
|
2989
3077
|
[NodeType.KeyValue](node) {
|
|
2990
3078
|
const { start: { line } } = node.loc;
|
|
2991
|
-
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);
|
|
2992
3080
|
},
|
|
2993
3081
|
[NodeType.Key](node) {
|
|
2994
|
-
write(lines, node.loc, node.raw);
|
|
3082
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
2995
3083
|
},
|
|
2996
3084
|
[NodeType.String](node) {
|
|
2997
|
-
write(lines, node.loc, node.raw);
|
|
3085
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
2998
3086
|
},
|
|
2999
3087
|
[NodeType.Integer](node) {
|
|
3000
|
-
write(lines, node.loc, node.raw);
|
|
3088
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3001
3089
|
},
|
|
3002
3090
|
[NodeType.Float](node) {
|
|
3003
|
-
write(lines, node.loc, node.raw);
|
|
3091
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3004
3092
|
},
|
|
3005
3093
|
[NodeType.Boolean](node) {
|
|
3006
|
-
write(lines, node.loc, node.value.toString());
|
|
3094
|
+
write(lines, node.loc, node.value.toString(), paddingChar);
|
|
3007
3095
|
},
|
|
3008
3096
|
[NodeType.DateTime](node) {
|
|
3009
|
-
write(lines, node.loc, node.raw);
|
|
3097
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3010
3098
|
},
|
|
3011
3099
|
[NodeType.InlineArray](node) {
|
|
3012
3100
|
const { start, end } = node.loc;
|
|
3013
|
-
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '[');
|
|
3014
|
-
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);
|
|
3015
3103
|
},
|
|
3016
3104
|
[NodeType.InlineTable](node) {
|
|
3017
3105
|
const { start, end } = node.loc;
|
|
3018
|
-
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, '{');
|
|
3019
|
-
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);
|
|
3020
3108
|
},
|
|
3021
3109
|
[NodeType.InlineItem](node) {
|
|
3022
3110
|
if (!node.comma)
|
|
3023
3111
|
return;
|
|
3024
3112
|
const start = node.loc.end;
|
|
3025
|
-
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, ',');
|
|
3113
|
+
write(lines, { start, end: { line: start.line, column: start.column + 1 } }, ',', paddingChar);
|
|
3026
3114
|
},
|
|
3027
3115
|
[NodeType.Comment](node) {
|
|
3028
|
-
write(lines, node.loc, node.raw);
|
|
3116
|
+
write(lines, node.loc, node.raw, paddingChar);
|
|
3029
3117
|
}
|
|
3030
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
|
+
}
|
|
3031
3134
|
return lines.join(format.newLine) + format.newLine.repeat(format.trailingNewline);
|
|
3032
3135
|
}
|
|
3033
3136
|
/**
|
|
@@ -3044,6 +3147,7 @@ function toTOML(ast, format) {
|
|
|
3044
3147
|
* - end: { line: number, column: number } - Ending position (1-indexed line, 0-indexed column)
|
|
3045
3148
|
* @param raw - The raw string content to write at the specified location.
|
|
3046
3149
|
* Can contain multiple lines separated by \n or \r\n.
|
|
3150
|
+
* @param paddingChar - The character to use for padding (space or tab)
|
|
3047
3151
|
*
|
|
3048
3152
|
* @throws {Error} When there's a mismatch between location span and raw string line count
|
|
3049
3153
|
* @throws {Error} When attempting to write to an uninitialized line
|
|
@@ -3052,11 +3156,11 @@ function toTOML(ast, format) {
|
|
|
3052
3156
|
* ```typescript
|
|
3053
3157
|
* const lines = ['', ''];
|
|
3054
3158
|
* const location = { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } };
|
|
3055
|
-
* write(lines, location, 'key');
|
|
3159
|
+
* write(lines, location, 'key', ' ');
|
|
3056
3160
|
* // Result: lines[0] becomes 'key'
|
|
3057
3161
|
* ```
|
|
3058
3162
|
*/
|
|
3059
|
-
function write(lines, loc, raw) {
|
|
3163
|
+
function write(lines, loc, raw, paddingChar = SPACE) {
|
|
3060
3164
|
const raw_lines = raw.split(BY_NEW_LINE).filter(line => line !== '\n' && line !== '\r\n');
|
|
3061
3165
|
const expected_lines = loc.end.line - loc.start.line + 1;
|
|
3062
3166
|
if (raw_lines.length !== expected_lines) {
|
|
@@ -3070,10 +3174,19 @@ function write(lines, loc, raw) {
|
|
|
3070
3174
|
}
|
|
3071
3175
|
const is_start_line = i === loc.start.line;
|
|
3072
3176
|
const is_end_line = i === loc.end.line;
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
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) : '';
|
|
3077
3190
|
lines[i - 1] = before + raw_lines[i - loc.start.line] + after;
|
|
3078
3191
|
}
|
|
3079
3192
|
}
|
|
@@ -3191,11 +3304,15 @@ function toValue(node) {
|
|
|
3191
3304
|
return result;
|
|
3192
3305
|
case NodeType.InlineArray:
|
|
3193
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;
|
|
3194
3312
|
case NodeType.String:
|
|
3195
3313
|
case NodeType.Integer:
|
|
3196
3314
|
case NodeType.Float:
|
|
3197
3315
|
case NodeType.Boolean:
|
|
3198
|
-
case NodeType.DateTime:
|
|
3199
3316
|
return node.value;
|
|
3200
3317
|
default:
|
|
3201
3318
|
throw new Error(`Unrecognized value type "${node.type}"`);
|
|
@@ -3435,13 +3552,29 @@ function findByPath(node, path) {
|
|
|
3435
3552
|
key = key.concat(array_index);
|
|
3436
3553
|
}
|
|
3437
3554
|
else if (isInlineItem(item) && isKeyValue(item.item)) {
|
|
3555
|
+
// For InlineItems wrapping KeyValues, extract the key
|
|
3438
3556
|
key = item.item.key.value;
|
|
3439
3557
|
}
|
|
3440
3558
|
else if (isInlineItem(item)) {
|
|
3441
3559
|
key = [index];
|
|
3442
3560
|
}
|
|
3443
3561
|
if (key.length && arraysEqual(key, path.slice(0, key.length))) {
|
|
3444
|
-
|
|
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
|
+
}
|
|
3445
3578
|
return true;
|
|
3446
3579
|
}
|
|
3447
3580
|
else {
|
|
@@ -3728,7 +3861,13 @@ function applyChanges(original, updated, changes, format) {
|
|
|
3728
3861
|
insert(original, parent, child, undefined, true);
|
|
3729
3862
|
}
|
|
3730
3863
|
else {
|
|
3731
|
-
|
|
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);
|
|
3732
3871
|
}
|
|
3733
3872
|
}
|
|
3734
3873
|
}
|
|
@@ -3757,6 +3896,14 @@ function applyChanges(original, updated, changes, format) {
|
|
|
3757
3896
|
parent = existing;
|
|
3758
3897
|
existing = existing.item;
|
|
3759
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
|
+
}
|
|
3760
3907
|
else {
|
|
3761
3908
|
parent = findParent(original, change.path);
|
|
3762
3909
|
// Special handling for array element edits
|