@zxsylph/dbml-formatter 1.0.8 → 1.0.10
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/package.json +1 -1
- package/src/cli.ts +2 -2
- package/src/formatter/formatter.ts +300 -125
- package/src/formatter/tokenizer.ts +1 -0
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -55,7 +55,7 @@ if (folderIndex !== -1) {
|
|
|
55
55
|
files.forEach(file => {
|
|
56
56
|
try {
|
|
57
57
|
const content = fs.readFileSync(file, 'utf-8');
|
|
58
|
-
const formatted = format(content, { orderField });
|
|
58
|
+
const formatted = format(content, { orderField, addNote: args.includes('--add-note') });
|
|
59
59
|
|
|
60
60
|
if (isDryRun) {
|
|
61
61
|
console.log(`\n--- Dry Run: ${file} ---`);
|
|
@@ -88,7 +88,7 @@ if (folderIndex !== -1) {
|
|
|
88
88
|
|
|
89
89
|
try {
|
|
90
90
|
const content = fs.readFileSync(absPath, 'utf-8');
|
|
91
|
-
const formatted = format(content, { orderField });
|
|
91
|
+
const formatted = format(content, { orderField, addNote: args.includes('--add-note') });
|
|
92
92
|
console.log(formatted);
|
|
93
93
|
} catch (error) {
|
|
94
94
|
console.error('Error formatting file:', error);
|
|
@@ -4,6 +4,7 @@ export interface FormatterOptions {
|
|
|
4
4
|
indentSize?: number;
|
|
5
5
|
useTabs?: boolean;
|
|
6
6
|
orderField?: boolean;
|
|
7
|
+
addNote?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function format(input: string, options: FormatterOptions = {}): string {
|
|
@@ -144,6 +145,21 @@ export function format(input: string, options: FormatterOptions = {}): string {
|
|
|
144
145
|
otherLinesGroups.push(currentGroup);
|
|
145
146
|
}
|
|
146
147
|
}
|
|
148
|
+
|
|
149
|
+
// NEW: Trim leading and trailing empty lines (whitespace-only groups) from otherLinesGroups
|
|
150
|
+
// Note: we do not touch tableNoteTokens here.
|
|
151
|
+
|
|
152
|
+
const isWhitespaceGroup = (g: Token[]) => g.every(t => t.type === TokenType.Whitespace);
|
|
153
|
+
|
|
154
|
+
// Trim start
|
|
155
|
+
while (otherLinesGroups.length > 0 && isWhitespaceGroup(otherLinesGroups[0])) {
|
|
156
|
+
otherLinesGroups.shift();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Trim end
|
|
160
|
+
while (otherLinesGroups.length > 0 && isWhitespaceGroup(otherLinesGroups[otherLinesGroups.length - 1])) {
|
|
161
|
+
otherLinesGroups.pop();
|
|
162
|
+
}
|
|
147
163
|
|
|
148
164
|
// 4. Print Table Note first (if exists)
|
|
149
165
|
if (tableNoteTokens.length > 0) {
|
|
@@ -164,7 +180,9 @@ export function format(input: string, options: FormatterOptions = {}): string {
|
|
|
164
180
|
}
|
|
165
181
|
} else {
|
|
166
182
|
// NEW: If no table note, add empty Note: ""
|
|
167
|
-
|
|
183
|
+
if (options.addNote) {
|
|
184
|
+
output += getIndent() + 'Note: ""\n\n';
|
|
185
|
+
}
|
|
168
186
|
}
|
|
169
187
|
|
|
170
188
|
// OPTIONAL: Sort Fields within groups
|
|
@@ -249,7 +267,9 @@ export function format(input: string, options: FormatterOptions = {}): string {
|
|
|
249
267
|
}
|
|
250
268
|
}
|
|
251
269
|
|
|
252
|
-
// 5.
|
|
270
|
+
// 5. Process Fields (Transform -> Align -> Print)
|
|
271
|
+
|
|
272
|
+
// 5a. Transformation Pass
|
|
253
273
|
for (let lgIdx = 0; lgIdx < otherLinesGroups.length; lgIdx++) {
|
|
254
274
|
const lineTokens = otherLinesGroups[lgIdx];
|
|
255
275
|
// Check for Field Settings `[...]` reordering
|
|
@@ -307,7 +327,7 @@ export function format(input: string, options: FormatterOptions = {}): string {
|
|
|
307
327
|
}
|
|
308
328
|
}
|
|
309
329
|
|
|
310
|
-
//
|
|
330
|
+
// Apply "Empty Field Note" logic
|
|
311
331
|
const meaningful = lineTokens.filter(t => t.type !== TokenType.Whitespace && t.type !== TokenType.Comment);
|
|
312
332
|
// Heuristic: Is this a field?
|
|
313
333
|
// It should have at least 2 tokens (Name, Type).
|
|
@@ -326,69 +346,66 @@ export function format(input: string, options: FormatterOptions = {}): string {
|
|
|
326
346
|
}
|
|
327
347
|
}
|
|
328
348
|
|
|
329
|
-
if (isField) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// 7. Apply "Quote Data Types" logic
|
|
349
|
+
if (isField && options.addNote) {
|
|
350
|
+
// Find settings block
|
|
351
|
+
let openBracketIdx = -1;
|
|
352
|
+
let closeBracketIdx = -1;
|
|
353
|
+
for(let idx=0; idx<lineTokens.length; idx++) {
|
|
354
|
+
if (lineTokens[idx].type === TokenType.Symbol && lineTokens[idx].value === '[') openBracketIdx = idx;
|
|
355
|
+
if (lineTokens[idx].type === TokenType.Symbol && lineTokens[idx].value === ']') closeBracketIdx = idx;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (openBracketIdx !== -1 && closeBracketIdx !== -1 && closeBracketIdx > openBracketIdx) {
|
|
359
|
+
// Settings exist. Check if 'note' is present.
|
|
360
|
+
const inside = lineTokens.slice(openBracketIdx + 1, closeBracketIdx);
|
|
361
|
+
let hasNote = false;
|
|
362
|
+
|
|
363
|
+
// Simple token scan for 'note' word
|
|
364
|
+
// Ideally we should parse comma groups, but 'note' keyword is reserved in settings.
|
|
365
|
+
for (const t of inside) {
|
|
366
|
+
if (t.type === TokenType.Word && t.value.toLowerCase() === 'note') {
|
|
367
|
+
hasNote = true;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!hasNote) {
|
|
373
|
+
// Insert `note: ""` at the beginning of settings
|
|
374
|
+
// We need to insert: "note", ":", "\"\"", ","
|
|
375
|
+
const newTokens: Token[] = [
|
|
376
|
+
{ type: TokenType.Word, value: 'note', line: 0, column: 0 },
|
|
377
|
+
{ type: TokenType.Symbol, value: ':', line: 0, column: 0 },
|
|
378
|
+
{ type: TokenType.String, value: '""', line: 0, column: 0 },
|
|
379
|
+
{ type: TokenType.Symbol, value: ',', line: 0, column: 0 }
|
|
380
|
+
];
|
|
381
|
+
lineTokens.splice(openBracketIdx + 1, 0, ...newTokens);
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
// No settings exist. Append ` [note: ""]`.
|
|
385
|
+
|
|
386
|
+
// Find last meaningful token index
|
|
387
|
+
let lastMeaningfulIdx = -1;
|
|
388
|
+
for (let idx = lineTokens.length - 1; idx >= 0; idx--) {
|
|
389
|
+
if (lineTokens[idx].type !== TokenType.Whitespace && lineTokens[idx].type !== TokenType.Comment) {
|
|
390
|
+
lastMeaningfulIdx = idx;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (lastMeaningfulIdx !== -1) {
|
|
396
|
+
const appendTokens: Token[] = [
|
|
397
|
+
{ type: TokenType.Symbol, value: '[', line: 0, column: 0 },
|
|
398
|
+
{ type: TokenType.Word, value: 'note', line: 0, column: 0 },
|
|
399
|
+
{ type: TokenType.Symbol, value: ':', line: 0, column: 0 },
|
|
400
|
+
{ type: TokenType.String, value: '""', line: 0, column: 0 },
|
|
401
|
+
{ type: TokenType.Symbol, value: ']', line: 0, column: 0 }
|
|
402
|
+
];
|
|
403
|
+
lineTokens.splice(lastMeaningfulIdx + 1, 0, ...appendTokens);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
391
407
|
|
|
408
|
+
// Apply "Quote Data Types" logic
|
|
392
409
|
let wordCount = 0;
|
|
393
410
|
for (const t of lineTokens) {
|
|
394
411
|
// Only count words before `[`?
|
|
@@ -398,39 +415,148 @@ export function format(input: string, options: FormatterOptions = {}): string {
|
|
|
398
415
|
if (wordCount === 2) {
|
|
399
416
|
// Quote this token!
|
|
400
417
|
t.value = `"${t.value}"`;
|
|
401
|
-
// Note: we are modifying the token object directly in the buffer.
|
|
402
418
|
}
|
|
403
419
|
}
|
|
404
420
|
if (t.type === TokenType.String && wordCount < 2) {
|
|
405
|
-
// Strings count as words/tokens for position?
|
|
406
|
-
// Example `name "varchar"` -> "varchar" IS the string.
|
|
407
421
|
wordCount++;
|
|
408
422
|
}
|
|
409
423
|
}
|
|
424
|
+
}
|
|
410
425
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
426
|
+
// 5b. Alignment Pass
|
|
427
|
+
const isFieldLine = (tokens: Token[]) => {
|
|
428
|
+
const m = tokens.filter(t => t.type !== TokenType.Whitespace && t.type !== TokenType.Comment);
|
|
429
|
+
if (m.length < 2) return false;
|
|
430
|
+
const first = m[0].value.toLowerCase();
|
|
431
|
+
if (first === 'indexes' || first === 'note') return false;
|
|
432
|
+
if (tokens.some(t => t.type === TokenType.Symbol && t.value === '{')) return false;
|
|
433
|
+
return true;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const alignFieldBlock = (blockLines: Token[][]) => {
|
|
437
|
+
// 1. Collect info suitable for alignment
|
|
438
|
+
interface RowInfo {
|
|
439
|
+
lineTokens: Token[];
|
|
440
|
+
nameTokenIdx: number;
|
|
441
|
+
typeStartIdx: number;
|
|
442
|
+
typeEndIdx: number; // exclusive
|
|
443
|
+
settingsStartIdx: number;
|
|
444
|
+
|
|
445
|
+
nameWidth: number;
|
|
446
|
+
typeWidth: number;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const rows: RowInfo[] = [];
|
|
450
|
+
|
|
451
|
+
for (const line of blockLines) {
|
|
452
|
+
// Find Name Token (First meaningful)
|
|
453
|
+
let nameIdx = -1;
|
|
454
|
+
for(let k=0; k<line.length; k++) {
|
|
455
|
+
if (line[k].type !== TokenType.Whitespace && line[k].type !== TokenType.Comment) {
|
|
456
|
+
nameIdx = k;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (nameIdx === -1) continue;
|
|
461
|
+
|
|
462
|
+
// Find Settings Start `[`
|
|
463
|
+
let settingsIdx = -1;
|
|
464
|
+
for(let k=0; k<line.length; k++) {
|
|
465
|
+
if (line[k].type === TokenType.Symbol && line[k].value === '[') {
|
|
466
|
+
settingsIdx = k;
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Determine Type Range
|
|
472
|
+
// Type is between Name and Settings (or End of Line)
|
|
473
|
+
let typeStart = nameIdx + 1;
|
|
474
|
+
while(typeStart < line.length && (line[typeStart].type === TokenType.Whitespace || line[typeStart].type === TokenType.Comment)) {
|
|
475
|
+
typeStart++;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
let typeEnd = settingsIdx;
|
|
479
|
+
if (typeEnd === -1) {
|
|
480
|
+
// No settings, type ends at the last meaningful token of the line
|
|
481
|
+
let lastMeaningful = -1;
|
|
482
|
+
for(let k=line.length-1; k >= 0; k--) {
|
|
483
|
+
if (line[k].type !== TokenType.Whitespace && line[k].type !== TokenType.Comment) {
|
|
484
|
+
lastMeaningful = k;
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (lastMeaningful > nameIdx) {
|
|
490
|
+
typeEnd = lastMeaningful + 1; // exclusive
|
|
491
|
+
} else {
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (typeStart >= typeEnd) continue;
|
|
497
|
+
|
|
498
|
+
// Calculate Widths
|
|
499
|
+
const nameWidth = line[nameIdx].value.length;
|
|
500
|
+
|
|
501
|
+
// Dry run type width
|
|
502
|
+
const typeTokens = line.slice(typeStart, typeEnd);
|
|
503
|
+
const typeStr = processTokens(typeTokens, 0, ' ', 2, false);
|
|
504
|
+
const typeWidth = typeStr.length;
|
|
505
|
+
|
|
506
|
+
rows.push({
|
|
507
|
+
lineTokens: line,
|
|
508
|
+
nameTokenIdx: nameIdx,
|
|
509
|
+
typeStartIdx: typeStart,
|
|
510
|
+
typeEndIdx: typeEnd,
|
|
511
|
+
settingsStartIdx: settingsIdx,
|
|
512
|
+
nameWidth,
|
|
513
|
+
typeWidth
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (rows.length === 0) return;
|
|
518
|
+
|
|
519
|
+
// 2. Calc Max Widths
|
|
520
|
+
const maxNameWidth = Math.max(...rows.map(r => r.nameWidth));
|
|
521
|
+
const maxTypeWidth = Math.max(...rows.map(r => r.typeWidth));
|
|
522
|
+
|
|
523
|
+
// 3. Apply Padding
|
|
524
|
+
for (const row of rows) {
|
|
525
|
+
// Pad Name
|
|
526
|
+
const namePad = (maxNameWidth - row.nameWidth) + 1; // +1 for minimum space
|
|
527
|
+
const nameTok = row.lineTokens[row.nameTokenIdx];
|
|
528
|
+
nameTok.padRight = namePad;
|
|
529
|
+
|
|
530
|
+
// Pad Type (Last token of type sequence)
|
|
531
|
+
// Only needed if there is something after (settings)
|
|
532
|
+
if (row.settingsStartIdx !== -1) {
|
|
533
|
+
const typePad = (maxTypeWidth - row.typeWidth) + 1;
|
|
534
|
+
// Find the last meaningful token of Type sequence
|
|
535
|
+
let lastTypeTokIdx = row.typeEndIdx - 1;
|
|
536
|
+
while(lastTypeTokIdx >= row.typeStartIdx && (row.lineTokens[lastTypeTokIdx].type === TokenType.Whitespace || row.lineTokens[lastTypeTokIdx].type === TokenType.Comment)) {
|
|
537
|
+
lastTypeTokIdx--;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (lastTypeTokIdx >= row.typeStartIdx) {
|
|
541
|
+
row.lineTokens[lastTypeTokIdx].padRight = typePad;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// Align globally across all lines in the table (ignoring inter-field grouping)
|
|
548
|
+
alignFieldBlock(otherLinesGroups);
|
|
549
|
+
|
|
550
|
+
// 5c. Print Pass
|
|
551
|
+
for (let lgIdx = 0; lgIdx < otherLinesGroups.length; lgIdx++) {
|
|
552
|
+
const lineTokens = otherLinesGroups[lgIdx];
|
|
553
|
+
output += processTokens(lineTokens, indentLevel, indentChar, indentSize, true);
|
|
425
554
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
output += '\n';
|
|
432
|
-
}
|
|
433
|
-
}
|
|
555
|
+
if (lgIdx < otherLinesGroups.length - 1) {
|
|
556
|
+
if (!output.endsWith('\n')) {
|
|
557
|
+
output += '\n';
|
|
558
|
+
}
|
|
559
|
+
}
|
|
434
560
|
}
|
|
435
561
|
|
|
436
562
|
// End block
|
|
@@ -596,12 +722,15 @@ function processTokens(
|
|
|
596
722
|
): string {
|
|
597
723
|
|
|
598
724
|
let localOutput = '';
|
|
599
|
-
let currentIndentLevel = baseIndentLevel;
|
|
600
725
|
const oneIndent = indentChar.repeat(indentSize);
|
|
601
|
-
const getLocalIndent = () => oneIndent.repeat(Math.max(0, currentIndentLevel));
|
|
602
726
|
|
|
603
|
-
//
|
|
604
|
-
|
|
727
|
+
// Stack of indentation strings. Start with base indent.
|
|
728
|
+
const indentStack: string[] = [oneIndent.repeat(Math.max(0, baseIndentLevel))];
|
|
729
|
+
|
|
730
|
+
// Helper to get current indentation
|
|
731
|
+
const getCurrentIndent = () => indentStack[indentStack.length - 1];
|
|
732
|
+
|
|
733
|
+
const checkArrayMultiline = (startIdx: number): boolean => {
|
|
605
734
|
let depth = 1;
|
|
606
735
|
let hasComma = false;
|
|
607
736
|
for (let k = startIdx + 1; k < tokens.length; k++) {
|
|
@@ -634,10 +763,10 @@ function processTokens(
|
|
|
634
763
|
continue;
|
|
635
764
|
}
|
|
636
765
|
|
|
637
|
-
//
|
|
766
|
+
// Spacing/Indent Logic
|
|
638
767
|
if (localOutput.length === 0 || localOutput.endsWith('\n')) {
|
|
639
768
|
if (token.value !== '}') {
|
|
640
|
-
localOutput +=
|
|
769
|
+
localOutput += getCurrentIndent();
|
|
641
770
|
}
|
|
642
771
|
} else {
|
|
643
772
|
// Not start of line
|
|
@@ -658,27 +787,69 @@ function processTokens(
|
|
|
658
787
|
case TokenType.Symbol:
|
|
659
788
|
if (token.value === '{') {
|
|
660
789
|
localOutput += '{\n';
|
|
661
|
-
|
|
790
|
+
indentStack.push(getCurrentIndent() + oneIndent);
|
|
662
791
|
} else if (token.value === '}') {
|
|
663
792
|
if (!localOutput.endsWith('\n')) localOutput += '\n';
|
|
664
|
-
|
|
665
|
-
localOutput +=
|
|
793
|
+
if (indentStack.length > 1) indentStack.pop();
|
|
794
|
+
localOutput += getCurrentIndent() + '}';
|
|
666
795
|
} else if (token.value === '[') {
|
|
667
796
|
const isMultiline = checkArrayMultiline(i);
|
|
668
797
|
multilineArrayStack.push(isMultiline);
|
|
798
|
+
|
|
799
|
+
const preBracketLength = localOutput.length;
|
|
800
|
+
const lastNewline = localOutput.lastIndexOf('\n');
|
|
801
|
+
|
|
669
802
|
localOutput += '[';
|
|
803
|
+
|
|
670
804
|
if (isMultiline) {
|
|
671
|
-
|
|
672
|
-
|
|
805
|
+
// Calculate visual column for Anchor
|
|
806
|
+
let visualColOfBracket = 0;
|
|
807
|
+
for(let c=lastNewline + 1; c < preBracketLength; c++) {
|
|
808
|
+
if (localOutput[c] === '\t') visualColOfBracket += (indentSize - (visualColOfBracket % indentSize));
|
|
809
|
+
else visualColOfBracket += 1;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const anchorIndent = ' '.repeat(visualColOfBracket);
|
|
813
|
+
// Push Anchor first (for the closing bracket)
|
|
814
|
+
indentStack.push(anchorIndent);
|
|
815
|
+
// Push Anchor + Indent (for the content)
|
|
816
|
+
indentStack.push(anchorIndent + oneIndent);
|
|
817
|
+
|
|
818
|
+
localOutput += '\n';
|
|
673
819
|
}
|
|
674
820
|
} else if (token.value === ']') {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
821
|
+
const isMultiline = multilineArrayStack.pop();
|
|
822
|
+
if (isMultiline) {
|
|
823
|
+
// We are currently at Content Indent level (top of stack).
|
|
824
|
+
// But for the closing bracket, we want to go back to Anchor Indent level.
|
|
825
|
+
|
|
826
|
+
// First, remove Content Indent from stack
|
|
827
|
+
indentStack.pop();
|
|
828
|
+
|
|
829
|
+
// Now top of stack is Anchor Indent.
|
|
830
|
+
const anchorIndent = getCurrentIndent();
|
|
831
|
+
|
|
832
|
+
// Check what's currently on the last line.
|
|
833
|
+
// The main loop might have already added indentation (Content Indent).
|
|
834
|
+
const lastNewline = localOutput.lastIndexOf('\n');
|
|
835
|
+
const lastLineFormatted = localOutput.substring(lastNewline + 1);
|
|
836
|
+
|
|
837
|
+
if (lastLineFormatted.trim().length === 0) {
|
|
838
|
+
// usage-case: The line is empty or just contains the indentation added by the loop.
|
|
839
|
+
// We should replace that indentation with our Anchor Indent.
|
|
840
|
+
localOutput = localOutput.substring(0, lastNewline + 1) + anchorIndent;
|
|
841
|
+
} else {
|
|
842
|
+
// There is content on this line. We need a new line.
|
|
843
|
+
localOutput += '\n' + anchorIndent;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
localOutput += ']';
|
|
847
|
+
|
|
848
|
+
// Finally, pop the Anchor Indent so we return to previous base indent
|
|
849
|
+
indentStack.pop();
|
|
850
|
+
} else {
|
|
851
|
+
localOutput += ']';
|
|
852
|
+
}
|
|
682
853
|
} else if (token.value === ',') {
|
|
683
854
|
localOutput += ',';
|
|
684
855
|
const currentMultiline = multilineArrayStack.length > 0 && multilineArrayStack[multilineArrayStack.length - 1];
|
|
@@ -689,21 +860,21 @@ function processTokens(
|
|
|
689
860
|
break;
|
|
690
861
|
|
|
691
862
|
case TokenType.Word:
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
863
|
+
// Handle keyword PascalCase
|
|
864
|
+
if (token.value.toLowerCase() === 'table') token.value = 'Table';
|
|
865
|
+
if (token.value.toLowerCase() === 'ref') token.value = 'Ref';
|
|
866
|
+
if (token.value.toLowerCase() === 'note') {
|
|
867
|
+
// Peek locally inside tokens list
|
|
868
|
+
let nextIdx = i + 1;
|
|
869
|
+
while(nextIdx < tokens.length && (tokens[nextIdx].type === TokenType.Whitespace || tokens[nextIdx].type === TokenType.Comment)) {
|
|
870
|
+
nextIdx++;
|
|
871
|
+
}
|
|
872
|
+
if (nextIdx < tokens.length && tokens[nextIdx].type === TokenType.Symbol && tokens[nextIdx].value === ':') {
|
|
873
|
+
token.value = 'Note';
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
localOutput += token.value;
|
|
877
|
+
break;
|
|
707
878
|
|
|
708
879
|
case TokenType.String:
|
|
709
880
|
let val = token.value;
|
|
@@ -716,8 +887,12 @@ function processTokens(
|
|
|
716
887
|
break;
|
|
717
888
|
|
|
718
889
|
default:
|
|
719
|
-
|
|
720
|
-
|
|
890
|
+
localOutput += token.value;
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (token.padRight) {
|
|
895
|
+
localOutput += ' '.repeat(token.padRight);
|
|
721
896
|
}
|
|
722
897
|
}
|
|
723
898
|
|