kopytko-formatter 0.1.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -42
- package/dist/bin/kopytko-format.js +42 -34
- package/dist/bin/kopytko-format.js.map +1 -1
- package/dist/src/casing.d.ts +16 -16
- package/dist/src/casing.d.ts.map +1 -1
- package/dist/src/casing.js +18 -18
- package/dist/src/casing.js.map +1 -1
- package/dist/src/config.d.ts +15 -33
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +21 -39
- package/dist/src/config.js.map +1 -1
- package/dist/src/formatter.d.ts.map +1 -1
- package/dist/src/formatter.js +748 -19
- package/dist/src/formatter.js.map +1 -1
- package/package.json +1 -1
package/dist/src/formatter.js
CHANGED
|
@@ -46,28 +46,54 @@ function formatText(source, config, casing = casing_1.DEFAULT_CASING_CONFIG, use
|
|
|
46
46
|
// Pass 3 — End keyword style + function vs sub
|
|
47
47
|
lines = passEndKeywordStyle(lines, config);
|
|
48
48
|
lines = passFunctionVsSub(lines, config);
|
|
49
|
+
// Pass 3b — Return type annotations
|
|
50
|
+
lines = passReturnTypeAnnotations(lines, config);
|
|
51
|
+
// Pass 3c — Param type annotations
|
|
52
|
+
lines = passParamTypeAnnotations(lines, config);
|
|
49
53
|
// Pass 4 — Then style + parenthesis if case + catch paren style
|
|
50
54
|
lines = passThenStyle(lines, config);
|
|
51
55
|
lines = passParenthesisIfCase(lines, config);
|
|
52
56
|
lines = passCatchParenStyle(lines, config);
|
|
57
|
+
// Pass 4c — Else on new line
|
|
58
|
+
lines = passElseOnNewLine(lines, config);
|
|
53
59
|
// Pass 5 — Print statement handling
|
|
54
60
|
lines = passPrintStatement(lines, config);
|
|
61
|
+
// Pass 5b — Line comment position
|
|
62
|
+
lines = passLineCommentPosition(lines, config);
|
|
55
63
|
// Pass 6 — Spacing rules
|
|
56
64
|
lines = passSpacing(lines, config);
|
|
65
|
+
// Pass 6b — Wrap long strings
|
|
66
|
+
lines = passWrapLongStrings(lines, config);
|
|
67
|
+
// Pass 6c — String concatenation style
|
|
68
|
+
lines = passStringConcatStyle(lines, config);
|
|
57
69
|
// Pass 7 — Casing
|
|
58
70
|
lines = passCasing(lines, casing, userFuncMap);
|
|
59
71
|
// Pass 7b — Split array open bracket
|
|
60
72
|
lines = passSplitArrayOpenBracket(lines, config);
|
|
73
|
+
// Pass 7c — Associative array single-line threshold
|
|
74
|
+
lines = passAAThreshold(lines, config);
|
|
61
75
|
// Pass 8 — Indentation
|
|
62
76
|
lines = passIndentation(lines, config);
|
|
63
77
|
// Pass 8b — Trailing commas
|
|
64
78
|
lines = passTrailingCommas(lines, config);
|
|
79
|
+
// Pass 8c — Align assignments
|
|
80
|
+
lines = passAlignAssignments(lines, config);
|
|
81
|
+
// Pass 8d — Multi-line param alignment
|
|
82
|
+
lines = passParamAlignment(lines, config);
|
|
65
83
|
// Pass 9 — Blank line rules
|
|
66
84
|
lines = passBlankLines(lines, config);
|
|
85
|
+
// Pass 9b — Empty lines between methods
|
|
86
|
+
lines = passEmptyLinesBetweenMethods(lines, config);
|
|
67
87
|
// Pass 10 — Trailing whitespace
|
|
68
88
|
lines = passTrimTrailing(lines, config);
|
|
69
89
|
// Pass 11 — Comment width
|
|
70
90
|
lines = passCommentWidth(lines, config);
|
|
91
|
+
// Pass 12 — observeField style
|
|
92
|
+
lines = passObserveFieldStyle(lines, config);
|
|
93
|
+
// Pass 13 — m prefix style
|
|
94
|
+
lines = passMPrefixStyle(lines, config);
|
|
95
|
+
// Pass 14 — Field access consistency
|
|
96
|
+
lines = passFieldAccessConsistency(lines, config);
|
|
71
97
|
// Assemble result
|
|
72
98
|
let newText = lines.join(lineEndStr);
|
|
73
99
|
if (config.insertFinalNewline && newText.length > 0 && !newText.endsWith(lineEndStr)) {
|
|
@@ -345,6 +371,118 @@ function findMatchingEnd(lines, startIdx) {
|
|
|
345
371
|
return -1;
|
|
346
372
|
}
|
|
347
373
|
// ---------------------------------------------------------------------------
|
|
374
|
+
// Pass 3b — Return type annotations
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
function passReturnTypeAnnotations(lines, config) {
|
|
377
|
+
if (config.returnTypeAnnotations === 'preserve')
|
|
378
|
+
return lines;
|
|
379
|
+
const result = [...lines];
|
|
380
|
+
// Matches: optional whitespace, `function`, name, `(params)`, optional `as Type`, optional comment
|
|
381
|
+
const declRegex = /^(\s*function\s+\w+\s*\([^)]*\))(\s+as\s+\w+)?(\s*(?:'.*)?)?$/i;
|
|
382
|
+
for (let i = 0; i < result.length; i++) {
|
|
383
|
+
const m = declRegex.exec(result[i]);
|
|
384
|
+
if (!m)
|
|
385
|
+
continue;
|
|
386
|
+
const [, before, asClause, trailing] = m;
|
|
387
|
+
const comment = trailing ?? '';
|
|
388
|
+
if (config.returnTypeAnnotations === 'always') {
|
|
389
|
+
if (!asClause) {
|
|
390
|
+
result[i] = before + ' as Dynamic' + comment;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// 'never' — remove the as Type clause
|
|
395
|
+
if (asClause) {
|
|
396
|
+
result[i] = before + comment;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
// Pass 3c — Param type annotations
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
function passParamTypeAnnotations(lines, config) {
|
|
406
|
+
if (config.paramTypeAnnotations === 'preserve')
|
|
407
|
+
return lines;
|
|
408
|
+
const result = [...lines];
|
|
409
|
+
// Match function/sub declaration lines (single-line params)
|
|
410
|
+
const declRegex = /^(\s*(?:function|sub)\s+\w*\s*)\(([^)]*)\)(.*)$/i;
|
|
411
|
+
for (let i = 0; i < result.length; i++) {
|
|
412
|
+
const m = declRegex.exec(result[i]);
|
|
413
|
+
if (!m)
|
|
414
|
+
continue;
|
|
415
|
+
const [, prefix, paramStr, suffix] = m;
|
|
416
|
+
if (paramStr.trim() === '')
|
|
417
|
+
continue;
|
|
418
|
+
const newParams = transformParams(paramStr, config.paramTypeAnnotations);
|
|
419
|
+
result[i] = prefix + '(' + newParams + ')' + suffix;
|
|
420
|
+
}
|
|
421
|
+
// Handle multi-line params: continuation lines between `(` and `)`
|
|
422
|
+
for (let i = 0; i < result.length; i++) {
|
|
423
|
+
const openMatch = /^(\s*(?:function|sub)\s+\w*\s*)\([^)]*$/i.exec(result[i]);
|
|
424
|
+
if (!openMatch)
|
|
425
|
+
continue;
|
|
426
|
+
// Transform params in the opening line after `(`
|
|
427
|
+
const parenIdx = result[i].indexOf('(');
|
|
428
|
+
const afterParen = result[i].substring(parenIdx + 1);
|
|
429
|
+
if (afterParen.trim() !== '') {
|
|
430
|
+
result[i] = result[i].substring(0, parenIdx + 1) + transformParams(afterParen, config.paramTypeAnnotations);
|
|
431
|
+
}
|
|
432
|
+
// Transform continuation lines
|
|
433
|
+
for (let j = i + 1; j < result.length; j++) {
|
|
434
|
+
const line = result[j];
|
|
435
|
+
const closeIdx = line.indexOf(')');
|
|
436
|
+
if (closeIdx >= 0) {
|
|
437
|
+
// Line with closing paren — transform params before `)`
|
|
438
|
+
const beforeClose = line.substring(0, closeIdx);
|
|
439
|
+
if (beforeClose.trim() !== '') {
|
|
440
|
+
result[j] = transformParams(beforeClose, config.paramTypeAnnotations) + line.substring(closeIdx);
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
// Pure continuation line — transform entire line preserving indent
|
|
445
|
+
const indentMatch = line.match(/^(\s*)/);
|
|
446
|
+
const indent = indentMatch ? indentMatch[1] : '';
|
|
447
|
+
const content = line.trim();
|
|
448
|
+
if (content !== '') {
|
|
449
|
+
result[j] = indent + transformParams(content, config.paramTypeAnnotations);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
function transformParams(paramStr, mode) {
|
|
456
|
+
// Split on commas (respecting that param names/types don't contain commas)
|
|
457
|
+
const parts = paramStr.split(',');
|
|
458
|
+
const transformed = parts.map(part => {
|
|
459
|
+
const trimmed = part.trim();
|
|
460
|
+
if (trimmed === '')
|
|
461
|
+
return part;
|
|
462
|
+
// Pattern: name [as Type] [= default]
|
|
463
|
+
const paramRegex = /^(\w+)(\s+as\s+\w+)?(\s*=\s*.*)?$/i;
|
|
464
|
+
const m = paramRegex.exec(trimmed);
|
|
465
|
+
if (!m)
|
|
466
|
+
return part;
|
|
467
|
+
const [, name, asClause, defaultVal] = m;
|
|
468
|
+
const preservedLeading = part.match(/^(\s*)/)?.[1] ?? '';
|
|
469
|
+
const trailingCommaSpace = part.match(/(\s*)$/)?.[1] ?? '';
|
|
470
|
+
if (mode === 'always') {
|
|
471
|
+
if (!asClause) {
|
|
472
|
+
return preservedLeading + name + ' as Dynamic' + (defaultVal ?? '') + trailingCommaSpace;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
// 'never' — remove as Type
|
|
477
|
+
if (asClause) {
|
|
478
|
+
return preservedLeading + name + (defaultVal ?? '') + trailingCommaSpace;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return part;
|
|
482
|
+
});
|
|
483
|
+
return transformed.join(',');
|
|
484
|
+
}
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
348
486
|
// Pass 4 — Then style
|
|
349
487
|
// ---------------------------------------------------------------------------
|
|
350
488
|
function passThenStyle(lines, config) {
|
|
@@ -487,6 +625,53 @@ function splitTrailingComment(s) {
|
|
|
487
625
|
return { code: s, comment: '' };
|
|
488
626
|
}
|
|
489
627
|
// ---------------------------------------------------------------------------
|
|
628
|
+
// Pass 4c — Else on new line
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
function passElseOnNewLine(lines, config) {
|
|
631
|
+
// true = keep else on its own line (default, no-op)
|
|
632
|
+
if (config.elseOnNewLine)
|
|
633
|
+
return lines;
|
|
634
|
+
const result = [];
|
|
635
|
+
let i = 0;
|
|
636
|
+
while (i < lines.length) {
|
|
637
|
+
const trimmed = lines[i].trim();
|
|
638
|
+
// Only match `if ...` (not `else if`)
|
|
639
|
+
if (/^if\b/i.test(trimmed) && i + 4 < lines.length) {
|
|
640
|
+
const { code, comment: ifComment } = splitTrailingComment(trimmed);
|
|
641
|
+
const codeClean = code.trimEnd();
|
|
642
|
+
const endsWithThen = /\bthen\s*$/i.test(codeClean);
|
|
643
|
+
const hasInlineThen = /\bthen\b/i.test(codeClean) && !endsWithThen;
|
|
644
|
+
// Multi-line if: ends with `then` or has no `then` at all (no code after then)
|
|
645
|
+
if (!hasInlineThen && !ifComment) {
|
|
646
|
+
const thenStmt = lines[i + 1].trim();
|
|
647
|
+
const elseLine = lines[i + 2].trim();
|
|
648
|
+
const elseStmt = lines[i + 3].trim();
|
|
649
|
+
const endIfLine = lines[i + 4].trim();
|
|
650
|
+
const isSimpleStmt = (s) => {
|
|
651
|
+
if (s === '' || s.startsWith("'") || /^rem\b/i.test(s))
|
|
652
|
+
return false;
|
|
653
|
+
return !splitTrailingComment(s).comment;
|
|
654
|
+
};
|
|
655
|
+
if (isSimpleStmt(thenStmt) &&
|
|
656
|
+
/^else\s*$/i.test(elseLine) &&
|
|
657
|
+
isSimpleStmt(elseStmt) &&
|
|
658
|
+
/^(?:end\s*if|endif)\s*$/i.test(endIfLine)) {
|
|
659
|
+
const indent = lines[i].match(/^(\s*)/)?.[1] ?? '';
|
|
660
|
+
let condPart = codeClean;
|
|
661
|
+
if (!endsWithThen)
|
|
662
|
+
condPart += ' then';
|
|
663
|
+
result.push(indent + condPart + ' ' + thenStmt + ' else ' + elseStmt);
|
|
664
|
+
i += 5;
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
result.push(lines[i]);
|
|
670
|
+
i++;
|
|
671
|
+
}
|
|
672
|
+
return result;
|
|
673
|
+
}
|
|
674
|
+
// ---------------------------------------------------------------------------
|
|
490
675
|
// Pass 5 — Print statement handling
|
|
491
676
|
// ---------------------------------------------------------------------------
|
|
492
677
|
function passPrintStatement(lines, config) {
|
|
@@ -495,6 +680,32 @@ function passPrintStatement(lines, config) {
|
|
|
495
680
|
return lines.filter(line => !/^\s*(?:print|\?)\b/i.test(line));
|
|
496
681
|
}
|
|
497
682
|
// ---------------------------------------------------------------------------
|
|
683
|
+
// Pass 5b — Line comment position
|
|
684
|
+
// ---------------------------------------------------------------------------
|
|
685
|
+
function passLineCommentPosition(lines, config) {
|
|
686
|
+
if (config.lineCommentPosition !== 'above')
|
|
687
|
+
return lines;
|
|
688
|
+
const result = [];
|
|
689
|
+
for (const line of lines) {
|
|
690
|
+
const trimmed = line.trim();
|
|
691
|
+
// Skip pure comment lines and blank lines
|
|
692
|
+
if (trimmed === '' || trimmed.startsWith("'") || /^rem\b/i.test(trimmed)) {
|
|
693
|
+
result.push(line);
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
const { code, comment } = splitTrailingComment(trimmed);
|
|
697
|
+
if (!comment) {
|
|
698
|
+
result.push(line);
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
// Move trailing comment to the line above, preserving indentation
|
|
702
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? '';
|
|
703
|
+
result.push(indent + comment);
|
|
704
|
+
result.push(indent + code);
|
|
705
|
+
}
|
|
706
|
+
return result;
|
|
707
|
+
}
|
|
708
|
+
// ---------------------------------------------------------------------------
|
|
498
709
|
// Pass 6 — Spacing rules
|
|
499
710
|
// ---------------------------------------------------------------------------
|
|
500
711
|
function passSpacing(lines, config) {
|
|
@@ -586,7 +797,7 @@ function applySpacingToCode(code, config, fullLine) {
|
|
|
586
797
|
return r;
|
|
587
798
|
}
|
|
588
799
|
/**
|
|
589
|
-
* Applies `
|
|
800
|
+
* Applies `associativeArrayBracketSpacing` and `associativeArrayCommaSpacing` to a fully-assembled line
|
|
590
801
|
* (code + string-literal segments joined together).
|
|
591
802
|
*
|
|
592
803
|
* This runs after all code segments are assembled so that the rules work
|
|
@@ -596,8 +807,8 @@ function applySpacingToCode(code, config, fullLine) {
|
|
|
596
807
|
* String literal contents are never modified.
|
|
597
808
|
*/
|
|
598
809
|
function applyBracketAndCommaSpacing(line, config) {
|
|
599
|
-
const addBracket = config.
|
|
600
|
-
const commaMode = config.
|
|
810
|
+
const addBracket = config.associativeArrayBracketSpacing;
|
|
811
|
+
const commaMode = config.associativeArrayCommaSpacing ?? 'preserve';
|
|
601
812
|
// Fast path: nothing to process if the line has no braces at all
|
|
602
813
|
if (!line.includes('{') && !line.includes('}'))
|
|
603
814
|
return line;
|
|
@@ -726,7 +937,7 @@ function passCasing(lines, casing, userFuncMap) {
|
|
|
726
937
|
// Pass 7b — Split array open bracket
|
|
727
938
|
// ---------------------------------------------------------------------------
|
|
728
939
|
function passSplitArrayOpenBracket(lines, config) {
|
|
729
|
-
if (!config.
|
|
940
|
+
if (!config.arraySplitOpenBracket)
|
|
730
941
|
return lines;
|
|
731
942
|
const result = [];
|
|
732
943
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -774,7 +985,7 @@ function passTrailingCommas(lines, config) {
|
|
|
774
985
|
const closerChar = trimmed[0];
|
|
775
986
|
const isArray = closerChar === ']';
|
|
776
987
|
const isAA = closerChar === '}';
|
|
777
|
-
const itemStyle = isArray ? config.arrayCommaStyle : isAA ? config.
|
|
988
|
+
const itemStyle = isArray ? config.arrayCommaStyle : isAA ? config.associativeArrayCommaStyle : 'preserve';
|
|
778
989
|
const openerChar = isArray ? '[' : '{';
|
|
779
990
|
let depth = 0;
|
|
780
991
|
for (let j = i; j >= 0; j--) {
|
|
@@ -935,6 +1146,108 @@ function passIndentation(lines, config) {
|
|
|
935
1146
|
});
|
|
936
1147
|
}
|
|
937
1148
|
// ---------------------------------------------------------------------------
|
|
1149
|
+
// Pass 8c — Align assignments
|
|
1150
|
+
// ---------------------------------------------------------------------------
|
|
1151
|
+
function passAlignAssignments(lines, config) {
|
|
1152
|
+
if (!config.alignAssignments)
|
|
1153
|
+
return lines;
|
|
1154
|
+
const result = [];
|
|
1155
|
+
let group = [];
|
|
1156
|
+
let containerDepth = 0;
|
|
1157
|
+
const flushGroup = () => {
|
|
1158
|
+
if (group.length <= 1) {
|
|
1159
|
+
for (const g of group)
|
|
1160
|
+
result.push(lines[g.idx]);
|
|
1161
|
+
}
|
|
1162
|
+
else {
|
|
1163
|
+
const maxLen = Math.max(...group.map(g => g.before.length));
|
|
1164
|
+
for (const g of group) {
|
|
1165
|
+
const padding = ' '.repeat(maxLen - g.before.length);
|
|
1166
|
+
result.push(g.indent + g.before + padding + ' = ' + g.after);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
group = [];
|
|
1170
|
+
};
|
|
1171
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1172
|
+
const trimmed = lines[i].trim();
|
|
1173
|
+
if (trimmed === '') {
|
|
1174
|
+
flushGroup();
|
|
1175
|
+
result.push(lines[i]);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
if (trimmed.startsWith("'") || /^rem\b/i.test(trimmed)) {
|
|
1179
|
+
flushGroup();
|
|
1180
|
+
result.push(lines[i]);
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
const prevDepth = containerDepth;
|
|
1184
|
+
containerDepth += netContainerDepth(trimmed);
|
|
1185
|
+
// Inside a multi-line container — not an independent assignment
|
|
1186
|
+
if (prevDepth > 0) {
|
|
1187
|
+
flushGroup();
|
|
1188
|
+
result.push(lines[i]);
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
const assignInfo = findSimpleAssignment(trimmed);
|
|
1192
|
+
if (!assignInfo) {
|
|
1193
|
+
flushGroup();
|
|
1194
|
+
result.push(lines[i]);
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
const indent = lines[i].match(/^(\s*)/)?.[1] ?? '';
|
|
1198
|
+
group.push({ idx: i, before: assignInfo.before, after: assignInfo.after, indent });
|
|
1199
|
+
}
|
|
1200
|
+
flushGroup();
|
|
1201
|
+
return result;
|
|
1202
|
+
}
|
|
1203
|
+
/** Find a simple `=` assignment in a trimmed line, returning the parts before and after `=`. */
|
|
1204
|
+
function findSimpleAssignment(trimmed) {
|
|
1205
|
+
if (!/^[a-zA-Z_]/.test(trimmed))
|
|
1206
|
+
return null;
|
|
1207
|
+
if (/^(?:if|else|elseif|for|while|end|return|function|sub|print|next|try|catch|throw|exit|stop|dim|goto)\b/i.test(trimmed))
|
|
1208
|
+
return null;
|
|
1209
|
+
let inStr = false;
|
|
1210
|
+
let bracketDepth = 0;
|
|
1211
|
+
let eqPos = -1;
|
|
1212
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
1213
|
+
const ch = trimmed[i];
|
|
1214
|
+
if (ch === '"') {
|
|
1215
|
+
if (inStr && trimmed[i + 1] === '"') {
|
|
1216
|
+
i++;
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
inStr = !inStr;
|
|
1220
|
+
}
|
|
1221
|
+
else if (!inStr) {
|
|
1222
|
+
if (ch === "'")
|
|
1223
|
+
break;
|
|
1224
|
+
if (ch === '(' || ch === '[' || ch === '{')
|
|
1225
|
+
bracketDepth++;
|
|
1226
|
+
else if (ch === ')' || ch === ']' || ch === '}')
|
|
1227
|
+
bracketDepth--;
|
|
1228
|
+
else if (ch === '=' && bracketDepth === 0 && i > 0) {
|
|
1229
|
+
const prev = trimmed[i - 1];
|
|
1230
|
+
const next = i + 1 < trimmed.length ? trimmed[i + 1] : '';
|
|
1231
|
+
if (next === '=') {
|
|
1232
|
+
i++;
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (prev === '<' || prev === '>' || prev === '!' || prev === '+' || prev === '-')
|
|
1236
|
+
continue;
|
|
1237
|
+
if (eqPos !== -1)
|
|
1238
|
+
return null;
|
|
1239
|
+
eqPos = i;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (eqPos < 0)
|
|
1244
|
+
return null;
|
|
1245
|
+
return {
|
|
1246
|
+
before: trimmed.substring(0, eqPos).trimEnd(),
|
|
1247
|
+
after: trimmed.substring(eqPos + 1).trimStart(),
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
// ---------------------------------------------------------------------------
|
|
938
1251
|
// Pass 9 — Blank line rules
|
|
939
1252
|
// ---------------------------------------------------------------------------
|
|
940
1253
|
function passBlankLines(lines, config) {
|
|
@@ -972,7 +1285,7 @@ function passBlankLines(lines, config) {
|
|
|
972
1285
|
}
|
|
973
1286
|
result = out;
|
|
974
1287
|
}
|
|
975
|
-
if (config.
|
|
1288
|
+
if (config.emptyLineAfterFunctionOpen) {
|
|
976
1289
|
const out = [];
|
|
977
1290
|
for (let i = 0; i < result.length; i++) {
|
|
978
1291
|
out.push(result[i]);
|
|
@@ -983,7 +1296,7 @@ function passBlankLines(lines, config) {
|
|
|
983
1296
|
}
|
|
984
1297
|
result = out;
|
|
985
1298
|
}
|
|
986
|
-
if (config.
|
|
1299
|
+
if (config.emptyLineBeforeFunctionClose) {
|
|
987
1300
|
const out = [];
|
|
988
1301
|
for (let i = 0; i < result.length; i++) {
|
|
989
1302
|
if (/^\s*(?:end\s*function|end\s*sub|endfunction|endsub)\b/i.test(result[i])) {
|
|
@@ -994,13 +1307,13 @@ function passBlankLines(lines, config) {
|
|
|
994
1307
|
}
|
|
995
1308
|
result = out;
|
|
996
1309
|
}
|
|
997
|
-
if (config.
|
|
1310
|
+
if (config.emptyLineBeforeReturn) {
|
|
998
1311
|
const out = [];
|
|
999
1312
|
for (let i = 0; i < result.length; i++) {
|
|
1000
1313
|
if (/^\s*return\b/i.test(result[i])) {
|
|
1001
1314
|
const isAlone = isReturnAloneInBlock(result, i);
|
|
1002
|
-
const shouldAdd = config.
|
|
1003
|
-
(config.
|
|
1315
|
+
const shouldAdd = config.emptyLineBeforeReturn === 'always' ||
|
|
1316
|
+
(config.emptyLineBeforeReturn === 'not-alone' && !isAlone);
|
|
1004
1317
|
if (shouldAdd && out.length > 0) {
|
|
1005
1318
|
const prevTrimmed = out[out.length - 1].trim();
|
|
1006
1319
|
// Skip when the preceding line is a comment — the blank line belongs before the comment.
|
|
@@ -1009,7 +1322,7 @@ function passBlankLines(lines, config) {
|
|
|
1009
1322
|
}
|
|
1010
1323
|
}
|
|
1011
1324
|
// With 'not-alone', actively remove blank lines before return when it IS alone in its block.
|
|
1012
|
-
if (config.
|
|
1325
|
+
if (config.emptyLineBeforeReturn === 'not-alone' && isAlone) {
|
|
1013
1326
|
while (out.length > 0 && out[out.length - 1].trim() === '')
|
|
1014
1327
|
out.pop();
|
|
1015
1328
|
}
|
|
@@ -1018,7 +1331,7 @@ function passBlankLines(lines, config) {
|
|
|
1018
1331
|
}
|
|
1019
1332
|
result = out;
|
|
1020
1333
|
}
|
|
1021
|
-
if (config.
|
|
1334
|
+
if (config.emptyLineBeforeComment) {
|
|
1022
1335
|
const out = [];
|
|
1023
1336
|
for (let i = 0; i < result.length; i++) {
|
|
1024
1337
|
const trimmed = result[i].trim();
|
|
@@ -1049,6 +1362,33 @@ function passBlankLines(lines, config) {
|
|
|
1049
1362
|
return result;
|
|
1050
1363
|
}
|
|
1051
1364
|
// ---------------------------------------------------------------------------
|
|
1365
|
+
// Pass 9b — Empty lines between methods
|
|
1366
|
+
// ---------------------------------------------------------------------------
|
|
1367
|
+
function passEmptyLinesBetweenMethods(lines, config) {
|
|
1368
|
+
if (config.emptyLinesBetweenMethods <= 0)
|
|
1369
|
+
return lines;
|
|
1370
|
+
const methodDefPattern = /^\w+\.\w+\s*=\s*(?:function|sub)\s*\(/i;
|
|
1371
|
+
const endPattern = /^(?:end\s*function|end\s*sub|endfunction|endsub)\b/i;
|
|
1372
|
+
const out = [];
|
|
1373
|
+
let prevWasEndMethod = false;
|
|
1374
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1375
|
+
const trimmed = lines[i].trim();
|
|
1376
|
+
const isMethodDef = methodDefPattern.test(trimmed);
|
|
1377
|
+
if (isMethodDef && prevWasEndMethod) {
|
|
1378
|
+
while (out.length > 0 && out[out.length - 1].trim() === '')
|
|
1379
|
+
out.pop();
|
|
1380
|
+
for (let n = 0; n < config.emptyLinesBetweenMethods; n++)
|
|
1381
|
+
out.push('');
|
|
1382
|
+
}
|
|
1383
|
+
out.push(lines[i]);
|
|
1384
|
+
// Blank lines don't reset the flag
|
|
1385
|
+
if (trimmed !== '') {
|
|
1386
|
+
prevWasEndMethod = endPattern.test(trimmed);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return out;
|
|
1390
|
+
}
|
|
1391
|
+
// ---------------------------------------------------------------------------
|
|
1052
1392
|
// Pass 10 — Trim trailing whitespace
|
|
1053
1393
|
// ---------------------------------------------------------------------------
|
|
1054
1394
|
function passTrimTrailing(lines, config) {
|
|
@@ -1100,8 +1440,397 @@ function passCommentWidth(lines, config) {
|
|
|
1100
1440
|
return result;
|
|
1101
1441
|
}
|
|
1102
1442
|
// ---------------------------------------------------------------------------
|
|
1103
|
-
//
|
|
1443
|
+
// Pass 12 — observeField style
|
|
1444
|
+
// ---------------------------------------------------------------------------
|
|
1445
|
+
function passObserveFieldStyle(lines, config) {
|
|
1446
|
+
if (config.observeFieldStyle === 'preserve')
|
|
1447
|
+
return lines;
|
|
1448
|
+
return lines.map(line => {
|
|
1449
|
+
const { code, comment } = splitTrailingComment(line);
|
|
1450
|
+
if (comment && /\.observeField\s*\(/i.test(comment))
|
|
1451
|
+
return line;
|
|
1452
|
+
if (!/\.observeField\s*\(/i.test(code))
|
|
1453
|
+
return line;
|
|
1454
|
+
if (/\.observeFieldScoped\s*\(/i.test(code))
|
|
1455
|
+
return line;
|
|
1456
|
+
if (config.observeFieldStyle === 'always-scoped') {
|
|
1457
|
+
const newCode = code.replace(/\.observeField\s*\(/gi, '.observeFieldScoped(');
|
|
1458
|
+
return comment ? newCode + ' ' + comment : newCode;
|
|
1459
|
+
}
|
|
1460
|
+
// 'warn'
|
|
1461
|
+
if (comment && /TODO:.*observeFieldScoped/i.test(comment))
|
|
1462
|
+
return line;
|
|
1463
|
+
return line + " ' TODO: consider using observeFieldScoped";
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
// ---------------------------------------------------------------------------
|
|
1467
|
+
// Pass 13 — m prefix style
|
|
1104
1468
|
// ---------------------------------------------------------------------------
|
|
1469
|
+
const M_KNOWN_PROPS = new Set(['top', 'global']);
|
|
1470
|
+
function passMPrefixStyle(lines, config) {
|
|
1471
|
+
if (config.mPrefixStyle === 'preserve')
|
|
1472
|
+
return lines;
|
|
1473
|
+
return lines.map(line => {
|
|
1474
|
+
const { code, comment } = splitTrailingComment(line);
|
|
1475
|
+
let newCode = code;
|
|
1476
|
+
if (config.mPrefixStyle === 'dot') {
|
|
1477
|
+
// m["field"] → m.field
|
|
1478
|
+
newCode = newCode.replace(/\bm\["([a-zA-Z_]\w*)"\]/g, (_match, field) => {
|
|
1479
|
+
return `m.${field}`;
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
// m.field → m["field"], but not m.top, m.global, or method calls m.func()
|
|
1484
|
+
newCode = newCode.replace(/\bm\.([a-zA-Z_]\w*)/g, (match, field, offset) => {
|
|
1485
|
+
if (M_KNOWN_PROPS.has(field.toLowerCase()))
|
|
1486
|
+
return match;
|
|
1487
|
+
const afterField = newCode.slice(offset + match.length);
|
|
1488
|
+
if (/^\s*\(/.test(afterField))
|
|
1489
|
+
return match;
|
|
1490
|
+
return `m["${field}"]`;
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
return comment ? newCode + ' ' + comment : newCode;
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
// ---------------------------------------------------------------------------
|
|
1497
|
+
// Pass 14 — Field access consistency
|
|
1498
|
+
// ---------------------------------------------------------------------------
|
|
1499
|
+
const FIELD_ACCESS_SKIP_METHODS = new Set([
|
|
1500
|
+
'observefield', 'observefieldscoped', 'unobservefield', 'unobservefieldscoped',
|
|
1501
|
+
'update', 'getchild', 'getchildren', 'getparent', 'findnode',
|
|
1502
|
+
'createchild', 'removechild', 'appendchild', 'getfield', 'setfield',
|
|
1503
|
+
'hasfield', 'addfield', 'addfields', 'removechildindex', 'removechildren',
|
|
1504
|
+
'getchildcount', 'replacechild', 'insertchild', 'createobject',
|
|
1505
|
+
]);
|
|
1506
|
+
function passFieldAccessConsistency(lines, config) {
|
|
1507
|
+
if (config.fieldAccessConsistency === 'preserve')
|
|
1508
|
+
return lines;
|
|
1509
|
+
if (config.fieldAccessConsistency === 'dot') {
|
|
1510
|
+
return lines.map(line => {
|
|
1511
|
+
const { code, comment } = splitTrailingComment(line);
|
|
1512
|
+
let newCode = code;
|
|
1513
|
+
// m.top.getField("x") → m.top.x
|
|
1514
|
+
newCode = newCode.replace(/\bm\.top\.getField\s*\(\s*"([a-zA-Z_]\w*)"\s*\)/gi, (_m, field) => `m.top.${field}`);
|
|
1515
|
+
// m.top.setField("x", val) → m.top.x = val
|
|
1516
|
+
newCode = newCode.replace(/\bm\.top\.setField\s*\(\s*"([a-zA-Z_]\w*)"\s*,\s*/gi, (_m, field) => `m.top.${field} = `);
|
|
1517
|
+
// Remove trailing ) from setField conversion
|
|
1518
|
+
if (newCode !== code && /\bm\.top\.\w+\s*=\s*.+\)/.test(newCode)) {
|
|
1519
|
+
const lastParen = newCode.lastIndexOf(')');
|
|
1520
|
+
if (lastParen > -1)
|
|
1521
|
+
newCode = newCode.slice(0, lastParen) + newCode.slice(lastParen + 1);
|
|
1522
|
+
}
|
|
1523
|
+
return comment ? newCode + ' ' + comment : newCode;
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
// 'method' — convert dot access to method calls
|
|
1527
|
+
return lines.map(line => {
|
|
1528
|
+
const { code, comment } = splitTrailingComment(line);
|
|
1529
|
+
const trimmed = code.trim();
|
|
1530
|
+
let newCode = code;
|
|
1531
|
+
// Assignment: m.top.field = value → m.top.setField("field", value)
|
|
1532
|
+
const assignMatch = trimmed.match(/^(\s*)m\.top\.([a-zA-Z_]\w*)\s*=\s*(.+)$/i);
|
|
1533
|
+
if (assignMatch) {
|
|
1534
|
+
const [, indent, field, value] = assignMatch;
|
|
1535
|
+
if (!FIELD_ACCESS_SKIP_METHODS.has(field.toLowerCase())) {
|
|
1536
|
+
newCode = `${indent}m.top.setField("${field}", ${value})`;
|
|
1537
|
+
return comment ? newCode + ' ' + comment : newCode;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
// Read: m.top.field (not a method call, not assignment target)
|
|
1541
|
+
newCode = newCode.replace(/\bm\.top\.([a-zA-Z_]\w*)/gi, (match, field, offset) => {
|
|
1542
|
+
if (FIELD_ACCESS_SKIP_METHODS.has(field.toLowerCase()))
|
|
1543
|
+
return match;
|
|
1544
|
+
const afterField = newCode.slice(offset + match.length);
|
|
1545
|
+
if (/^\s*\(/.test(afterField))
|
|
1546
|
+
return match;
|
|
1547
|
+
if (/^\s*=/.test(afterField))
|
|
1548
|
+
return match;
|
|
1549
|
+
return `m.top.getField("${field}")`;
|
|
1550
|
+
});
|
|
1551
|
+
return comment ? newCode + ' ' + comment : newCode;
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
// ---------------------------------------------------------------------------
|
|
1555
|
+
// Pass 6b — Wrap long strings
|
|
1556
|
+
// ---------------------------------------------------------------------------
|
|
1557
|
+
const WRAP_LONG_STRINGS_WIDTH = 120;
|
|
1558
|
+
function passWrapLongStrings(lines, config) {
|
|
1559
|
+
if (config.wrapLongStrings === 'preserve')
|
|
1560
|
+
return lines;
|
|
1561
|
+
const result = [];
|
|
1562
|
+
for (const line of lines) {
|
|
1563
|
+
if (line.length <= WRAP_LONG_STRINGS_WIDTH) {
|
|
1564
|
+
result.push(line);
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? '';
|
|
1568
|
+
const { code, comment } = splitTrailingComment(line);
|
|
1569
|
+
// Find a long string literal in the line
|
|
1570
|
+
const stringMatch = code.match(/"([^"]{40,})"/);
|
|
1571
|
+
if (!stringMatch) {
|
|
1572
|
+
result.push(line);
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
const fullStr = stringMatch[0];
|
|
1576
|
+
const strContent = stringMatch[1];
|
|
1577
|
+
const strStart = code.indexOf(fullStr);
|
|
1578
|
+
const before = code.slice(0, strStart);
|
|
1579
|
+
const after = code.slice(strStart + fullStr.length);
|
|
1580
|
+
const childIndent = indent + ' '.repeat(4);
|
|
1581
|
+
const maxChunk = WRAP_LONG_STRINGS_WIDTH - childIndent.length - 6;
|
|
1582
|
+
if (maxChunk <= 10) {
|
|
1583
|
+
result.push(line);
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
const chunks = [];
|
|
1587
|
+
let remaining = strContent;
|
|
1588
|
+
while (remaining.length > 0) {
|
|
1589
|
+
if (remaining.length <= maxChunk) {
|
|
1590
|
+
chunks.push(remaining);
|
|
1591
|
+
break;
|
|
1592
|
+
}
|
|
1593
|
+
let breakAt = remaining.lastIndexOf(' ', maxChunk);
|
|
1594
|
+
if (breakAt <= 0)
|
|
1595
|
+
breakAt = maxChunk;
|
|
1596
|
+
chunks.push(remaining.slice(0, breakAt + 1));
|
|
1597
|
+
remaining = remaining.slice(breakAt + 1);
|
|
1598
|
+
}
|
|
1599
|
+
if (chunks.length <= 1) {
|
|
1600
|
+
result.push(line);
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
if (config.wrapLongStrings === 'plus') {
|
|
1604
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1605
|
+
const piece = `"${chunks[i]}"`;
|
|
1606
|
+
if (i === 0) {
|
|
1607
|
+
const suffix = i < chunks.length - 1 ? ' + _' : '';
|
|
1608
|
+
result.push(before + piece + suffix);
|
|
1609
|
+
}
|
|
1610
|
+
else if (i < chunks.length - 1) {
|
|
1611
|
+
result.push(childIndent + piece + ' + _');
|
|
1612
|
+
}
|
|
1613
|
+
else {
|
|
1614
|
+
result.push(childIndent + piece + after + (comment ? ' ' + comment : ''));
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
else {
|
|
1619
|
+
// array-join
|
|
1620
|
+
result.push(before + '[');
|
|
1621
|
+
for (const chunk of chunks) {
|
|
1622
|
+
result.push(childIndent + `"${chunk}"`);
|
|
1623
|
+
}
|
|
1624
|
+
result.push(indent + '].join("")' + after + (comment ? ' ' + comment : ''));
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return result;
|
|
1628
|
+
}
|
|
1629
|
+
// ---------------------------------------------------------------------------
|
|
1630
|
+
// Pass 6c — String concatenation style
|
|
1631
|
+
// ---------------------------------------------------------------------------
|
|
1632
|
+
function passStringConcatStyle(lines, config) {
|
|
1633
|
+
if (config.stringConcatStyle === 'preserve')
|
|
1634
|
+
return lines;
|
|
1635
|
+
if (config.stringConcatStyle === 'plus') {
|
|
1636
|
+
// Convert [a, b, c].join("") → a + b + c
|
|
1637
|
+
const result = [];
|
|
1638
|
+
let i = 0;
|
|
1639
|
+
while (i < lines.length) {
|
|
1640
|
+
const trimmed = lines[i].trim();
|
|
1641
|
+
// Single-line: [a, b, c].join("")
|
|
1642
|
+
const singleMatch = trimmed.match(/^(.*)(\[.+\])\.join\s*\(\s*""\s*\)(.*)$/);
|
|
1643
|
+
if (singleMatch) {
|
|
1644
|
+
const indent = lines[i].match(/^(\s*)/)?.[1] ?? '';
|
|
1645
|
+
const before = singleMatch[1];
|
|
1646
|
+
const arrContent = singleMatch[2];
|
|
1647
|
+
const after = singleMatch[3];
|
|
1648
|
+
// Extract items from [...]
|
|
1649
|
+
const inner = arrContent.slice(1, -1);
|
|
1650
|
+
const items = splitArrayItems(inner);
|
|
1651
|
+
if (items.length > 0) {
|
|
1652
|
+
result.push(indent + before + items.join(' + ') + after);
|
|
1653
|
+
i++;
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
result.push(lines[i]);
|
|
1658
|
+
i++;
|
|
1659
|
+
}
|
|
1660
|
+
return result;
|
|
1661
|
+
}
|
|
1662
|
+
// 'array-join': Convert a + b + c → [a, b, c].join("") when at least one is a string
|
|
1663
|
+
return lines.map(line => {
|
|
1664
|
+
const { code, comment } = splitTrailingComment(line);
|
|
1665
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? '';
|
|
1666
|
+
// Match: expr + expr + expr (with at least one string literal)
|
|
1667
|
+
const plusParts = splitPlusParts(code.trim());
|
|
1668
|
+
if (plusParts.length < 2)
|
|
1669
|
+
return line;
|
|
1670
|
+
const hasString = plusParts.some(p => /^".*"$/.test(p.trim()));
|
|
1671
|
+
if (!hasString)
|
|
1672
|
+
return line;
|
|
1673
|
+
// Find the assignment prefix
|
|
1674
|
+
const assignMatch = code.match(/^(\s*\S+\s*=\s*)/);
|
|
1675
|
+
const prefix = assignMatch ? assignMatch[1] : indent;
|
|
1676
|
+
const items = plusParts.map(p => p.trim()).join(', ');
|
|
1677
|
+
const newCode = prefix + `[${items}].join("")`;
|
|
1678
|
+
return comment ? newCode + ' ' + comment : newCode;
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
function splitArrayItems(inner) {
|
|
1682
|
+
const items = [];
|
|
1683
|
+
let depth = 0;
|
|
1684
|
+
let current = '';
|
|
1685
|
+
let inStr = false;
|
|
1686
|
+
for (let i = 0; i < inner.length; i++) {
|
|
1687
|
+
const ch = inner[i];
|
|
1688
|
+
if (ch === '"' && (i === 0 || inner[i - 1] !== '\\'))
|
|
1689
|
+
inStr = !inStr;
|
|
1690
|
+
if (!inStr) {
|
|
1691
|
+
if (ch === '[' || ch === '{' || ch === '(')
|
|
1692
|
+
depth++;
|
|
1693
|
+
if (ch === ']' || ch === '}' || ch === ')')
|
|
1694
|
+
depth--;
|
|
1695
|
+
if (ch === ',' && depth === 0) {
|
|
1696
|
+
items.push(current.trim());
|
|
1697
|
+
current = '';
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
current += ch;
|
|
1702
|
+
}
|
|
1703
|
+
if (current.trim())
|
|
1704
|
+
items.push(current.trim());
|
|
1705
|
+
return items;
|
|
1706
|
+
}
|
|
1707
|
+
function splitPlusParts(code) {
|
|
1708
|
+
const parts = [];
|
|
1709
|
+
let depth = 0;
|
|
1710
|
+
let current = '';
|
|
1711
|
+
let inStr = false;
|
|
1712
|
+
for (let i = 0; i < code.length; i++) {
|
|
1713
|
+
const ch = code[i];
|
|
1714
|
+
if (ch === '"' && (i === 0 || code[i - 1] !== '\\'))
|
|
1715
|
+
inStr = !inStr;
|
|
1716
|
+
if (!inStr) {
|
|
1717
|
+
if (ch === '[' || ch === '{' || ch === '(')
|
|
1718
|
+
depth++;
|
|
1719
|
+
if (ch === ']' || ch === '}' || ch === ')')
|
|
1720
|
+
depth--;
|
|
1721
|
+
if (ch === '+' && depth === 0 && !inStr) {
|
|
1722
|
+
// Check it's not +=
|
|
1723
|
+
if (i > 0 && code[i - 1] === ' ' || i + 1 < code.length) {
|
|
1724
|
+
const before = code.slice(0, i).trim();
|
|
1725
|
+
if (!before.endsWith('=')) {
|
|
1726
|
+
parts.push(current.trim());
|
|
1727
|
+
current = '';
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
current += ch;
|
|
1734
|
+
}
|
|
1735
|
+
if (current.trim())
|
|
1736
|
+
parts.push(current.trim());
|
|
1737
|
+
return parts.length > 1 ? parts : [];
|
|
1738
|
+
}
|
|
1739
|
+
// ---------------------------------------------------------------------------
|
|
1740
|
+
// Pass 7c — Associative array single-line threshold
|
|
1741
|
+
// ---------------------------------------------------------------------------
|
|
1742
|
+
function passAAThreshold(lines, config) {
|
|
1743
|
+
if (config.associativeArraySingleLineThreshold <= 0)
|
|
1744
|
+
return lines;
|
|
1745
|
+
const threshold = config.associativeArraySingleLineThreshold;
|
|
1746
|
+
const indentStr = config.useTabs ? '\t' : ' '.repeat(config.indentSize);
|
|
1747
|
+
const result = [];
|
|
1748
|
+
for (const line of lines) {
|
|
1749
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? '';
|
|
1750
|
+
const trimmed = line.trim();
|
|
1751
|
+
const { code, comment } = splitTrailingComment(line);
|
|
1752
|
+
const codeT = code.trim();
|
|
1753
|
+
// Find single-line AA: { key: val, key: val }
|
|
1754
|
+
const aaMatch = codeT.match(/^(.*?)(\{[^{}]+\})(.*)$/);
|
|
1755
|
+
if (!aaMatch) {
|
|
1756
|
+
result.push(line);
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
1759
|
+
const [, before, aaBlock, after] = aaMatch;
|
|
1760
|
+
if (aaBlock.length <= threshold) {
|
|
1761
|
+
result.push(line);
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
// Extract key-value pairs
|
|
1765
|
+
const inner = aaBlock.slice(1, -1).trim();
|
|
1766
|
+
const pairs = splitArrayItems(inner);
|
|
1767
|
+
if (pairs.length === 0) {
|
|
1768
|
+
result.push(line);
|
|
1769
|
+
continue;
|
|
1770
|
+
}
|
|
1771
|
+
const childIndent = indent + indentStr;
|
|
1772
|
+
result.push(indent + before + '{');
|
|
1773
|
+
for (const pair of pairs) {
|
|
1774
|
+
result.push(childIndent + pair.trim());
|
|
1775
|
+
}
|
|
1776
|
+
result.push(indent + '}' + after + (comment ? ' ' + comment : ''));
|
|
1777
|
+
}
|
|
1778
|
+
return result;
|
|
1779
|
+
}
|
|
1780
|
+
// ---------------------------------------------------------------------------
|
|
1781
|
+
// Pass 8d — Multi-line param alignment
|
|
1782
|
+
// ---------------------------------------------------------------------------
|
|
1783
|
+
function passParamAlignment(lines, config) {
|
|
1784
|
+
if (config.paramAlignmentStyle === 'preserve')
|
|
1785
|
+
return lines;
|
|
1786
|
+
const indentStr = config.useTabs ? '\t' : ' '.repeat(config.indentSize);
|
|
1787
|
+
const result = [];
|
|
1788
|
+
let i = 0;
|
|
1789
|
+
while (i < lines.length) {
|
|
1790
|
+
const trimmed = lines[i].trim().toLowerCase();
|
|
1791
|
+
// Detect function/sub declaration with ( but no closing )
|
|
1792
|
+
const isFuncLine = /^(?:function|sub)\s+/i.test(trimmed) || /^\s*(?:function|sub)\s+/i.test(lines[i]);
|
|
1793
|
+
if (!isFuncLine || !lines[i].includes('(') || lines[i].includes(')')) {
|
|
1794
|
+
result.push(lines[i]);
|
|
1795
|
+
i++;
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
// Multi-line params: collect lines until we find )
|
|
1799
|
+
const funcLine = lines[i];
|
|
1800
|
+
const funcIndent = funcLine.match(/^(\s*)/)?.[1] ?? '';
|
|
1801
|
+
const parenCol = funcLine.indexOf('(');
|
|
1802
|
+
const paramLines = [funcLine];
|
|
1803
|
+
let j = i + 1;
|
|
1804
|
+
while (j < lines.length) {
|
|
1805
|
+
paramLines.push(lines[j]);
|
|
1806
|
+
if (lines[j].includes(')'))
|
|
1807
|
+
break;
|
|
1808
|
+
j++;
|
|
1809
|
+
}
|
|
1810
|
+
if (paramLines.length <= 1) {
|
|
1811
|
+
result.push(lines[i]);
|
|
1812
|
+
i++;
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
if (config.paramAlignmentStyle === 'indent') {
|
|
1816
|
+
result.push(paramLines[0]);
|
|
1817
|
+
const paramIndent = funcIndent + indentStr;
|
|
1818
|
+
for (let k = 1; k < paramLines.length; k++) {
|
|
1819
|
+
result.push(paramIndent + paramLines[k].trim());
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
else {
|
|
1823
|
+
// align-to-paren
|
|
1824
|
+
result.push(paramLines[0]);
|
|
1825
|
+
const alignStr = ' '.repeat(parenCol + 1);
|
|
1826
|
+
for (let k = 1; k < paramLines.length; k++) {
|
|
1827
|
+
result.push(alignStr + paramLines[k].trim());
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
i = j + 1;
|
|
1831
|
+
}
|
|
1832
|
+
return result;
|
|
1833
|
+
}
|
|
1105
1834
|
function isIndentLine(trimmed) {
|
|
1106
1835
|
const lower = trimmed.toLowerCase();
|
|
1107
1836
|
if (/^(?:if|else\s*if|elseif)\b/i.test(lower)) {
|
|
@@ -1321,8 +2050,8 @@ function splitCodeSegments(line) {
|
|
|
1321
2050
|
return segments;
|
|
1322
2051
|
}
|
|
1323
2052
|
function transformCodeSegment(code, casing, userFuncMap) {
|
|
1324
|
-
const exactMap = casing.
|
|
1325
|
-
const userFuncCasing = casing.
|
|
2053
|
+
const exactMap = casing.exact ?? {};
|
|
2054
|
+
const userFuncCasing = casing.userFunction ?? 'preserve';
|
|
1326
2055
|
return code.replace(/\b([a-zA-Z_]\w*)\b/g, (match, _group, offset) => {
|
|
1327
2056
|
const afterIdx = offset + match.length;
|
|
1328
2057
|
const restAfter = code.slice(afterIdx);
|
|
@@ -1343,7 +2072,7 @@ function transformCodeSegment(code, casing, userFuncMap) {
|
|
|
1343
2072
|
}
|
|
1344
2073
|
}
|
|
1345
2074
|
const effectiveCasing = (0, casing_1.resolveKeywordCasing)(category, casing);
|
|
1346
|
-
if (effectiveCasing !== '
|
|
2075
|
+
if (effectiveCasing !== 'preserve') {
|
|
1347
2076
|
return (0, casing_1.applyCasingWithOverrides)(match, effectiveCasing, exactMap);
|
|
1348
2077
|
}
|
|
1349
2078
|
return match;
|
|
@@ -1355,14 +2084,14 @@ function transformCodeSegment(code, casing, userFuncMap) {
|
|
|
1355
2084
|
if (!/^\s*\(/.test(restAfter))
|
|
1356
2085
|
return match;
|
|
1357
2086
|
const canonical = _builtinMap.get(lower);
|
|
1358
|
-
if (casing.
|
|
1359
|
-
return (0, casing_1.applyCasingWithOverrides)(canonical, casing.
|
|
2087
|
+
if (casing.builtin !== 'preserve') {
|
|
2088
|
+
return (0, casing_1.applyCasingWithOverrides)(canonical, casing.builtin, exactMap);
|
|
1360
2089
|
}
|
|
1361
2090
|
return canonical;
|
|
1362
2091
|
}
|
|
1363
2092
|
if (userFuncMap.has(lower)) {
|
|
1364
2093
|
const definitionName = userFuncMap.get(lower);
|
|
1365
|
-
if (userFuncCasing !== '
|
|
2094
|
+
if (userFuncCasing !== 'preserve') {
|
|
1366
2095
|
return (0, casing_1.applyCasing)(definitionName, userFuncCasing);
|
|
1367
2096
|
}
|
|
1368
2097
|
return definitionName;
|