kopytko-formatter 1.0.9 → 1.1.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.
Files changed (129) hide show
  1. package/dist/src/config.d.ts +4 -0
  2. package/dist/src/config.d.ts.map +1 -1
  3. package/dist/src/config.js +3 -0
  4. package/dist/src/config.js.map +1 -1
  5. package/dist/src/cst-passes/aaThreshold.d.ts +4 -0
  6. package/dist/src/cst-passes/aaThreshold.d.ts.map +1 -0
  7. package/dist/src/cst-passes/aaThreshold.js +11 -0
  8. package/dist/src/cst-passes/aaThreshold.js.map +1 -0
  9. package/dist/src/cst-passes/alignAssignments.d.ts +4 -0
  10. package/dist/src/cst-passes/alignAssignments.d.ts.map +1 -0
  11. package/dist/src/cst-passes/alignAssignments.js +11 -0
  12. package/dist/src/cst-passes/alignAssignments.js.map +1 -0
  13. package/dist/src/cst-passes/blankLines.d.ts +19 -0
  14. package/dist/src/cst-passes/blankLines.d.ts.map +1 -0
  15. package/dist/src/cst-passes/blankLines.js +40 -0
  16. package/dist/src/cst-passes/blankLines.js.map +1 -0
  17. package/dist/src/cst-passes/casingPass.d.ts +12 -0
  18. package/dist/src/cst-passes/casingPass.d.ts.map +1 -0
  19. package/dist/src/cst-passes/casingPass.js +214 -0
  20. package/dist/src/cst-passes/casingPass.js.map +1 -0
  21. package/dist/src/cst-passes/commentNormalization.d.ts +24 -0
  22. package/dist/src/cst-passes/commentNormalization.d.ts.map +1 -0
  23. package/dist/src/cst-passes/commentNormalization.js +76 -0
  24. package/dist/src/cst-passes/commentNormalization.js.map +1 -0
  25. package/dist/src/cst-passes/commentWidth.d.ts +4 -0
  26. package/dist/src/cst-passes/commentWidth.d.ts.map +1 -0
  27. package/dist/src/cst-passes/commentWidth.js +50 -0
  28. package/dist/src/cst-passes/commentWidth.js.map +1 -0
  29. package/dist/src/cst-passes/elseOnNewLine.d.ts +4 -0
  30. package/dist/src/cst-passes/elseOnNewLine.d.ts.map +1 -0
  31. package/dist/src/cst-passes/elseOnNewLine.js +11 -0
  32. package/dist/src/cst-passes/elseOnNewLine.js.map +1 -0
  33. package/dist/src/cst-passes/emptyLinesBetweenMethods.d.ts +4 -0
  34. package/dist/src/cst-passes/emptyLinesBetweenMethods.d.ts.map +1 -0
  35. package/dist/src/cst-passes/emptyLinesBetweenMethods.js +11 -0
  36. package/dist/src/cst-passes/emptyLinesBetweenMethods.js.map +1 -0
  37. package/dist/src/cst-passes/endKeywordStyle.d.ts +21 -0
  38. package/dist/src/cst-passes/endKeywordStyle.d.ts.map +1 -0
  39. package/dist/src/cst-passes/endKeywordStyle.js +95 -0
  40. package/dist/src/cst-passes/endKeywordStyle.js.map +1 -0
  41. package/dist/src/cst-passes/fieldAccessConsistency.d.ts +4 -0
  42. package/dist/src/cst-passes/fieldAccessConsistency.d.ts.map +1 -0
  43. package/dist/src/cst-passes/fieldAccessConsistency.js +11 -0
  44. package/dist/src/cst-passes/fieldAccessConsistency.js.map +1 -0
  45. package/dist/src/cst-passes/functionVsSub.d.ts +13 -0
  46. package/dist/src/cst-passes/functionVsSub.d.ts.map +1 -0
  47. package/dist/src/cst-passes/functionVsSub.js +112 -0
  48. package/dist/src/cst-passes/functionVsSub.js.map +1 -0
  49. package/dist/src/cst-passes/importSorting.d.ts +25 -0
  50. package/dist/src/cst-passes/importSorting.d.ts.map +1 -0
  51. package/dist/src/cst-passes/importSorting.js +126 -0
  52. package/dist/src/cst-passes/importSorting.js.map +1 -0
  53. package/dist/src/cst-passes/indentation.d.ts +21 -0
  54. package/dist/src/cst-passes/indentation.d.ts.map +1 -0
  55. package/dist/src/cst-passes/indentation.js +115 -0
  56. package/dist/src/cst-passes/indentation.js.map +1 -0
  57. package/dist/src/cst-passes/index.d.ts +45 -0
  58. package/dist/src/cst-passes/index.d.ts.map +1 -0
  59. package/dist/src/cst-passes/index.js +77 -0
  60. package/dist/src/cst-passes/index.js.map +1 -0
  61. package/dist/src/cst-passes/infrastructure.d.ts +39 -0
  62. package/dist/src/cst-passes/infrastructure.d.ts.map +1 -0
  63. package/dist/src/cst-passes/infrastructure.js +63 -0
  64. package/dist/src/cst-passes/infrastructure.js.map +1 -0
  65. package/dist/src/cst-passes/lineCommentPosition.d.ts +4 -0
  66. package/dist/src/cst-passes/lineCommentPosition.d.ts.map +1 -0
  67. package/dist/src/cst-passes/lineCommentPosition.js +42 -0
  68. package/dist/src/cst-passes/lineCommentPosition.js.map +1 -0
  69. package/dist/src/cst-passes/mPrefixStyle.d.ts +4 -0
  70. package/dist/src/cst-passes/mPrefixStyle.d.ts.map +1 -0
  71. package/dist/src/cst-passes/mPrefixStyle.js +11 -0
  72. package/dist/src/cst-passes/mPrefixStyle.js.map +1 -0
  73. package/dist/src/cst-passes/observeFieldStyle.d.ts +6 -0
  74. package/dist/src/cst-passes/observeFieldStyle.d.ts.map +1 -0
  75. package/dist/src/cst-passes/observeFieldStyle.js +21 -0
  76. package/dist/src/cst-passes/observeFieldStyle.js.map +1 -0
  77. package/dist/src/cst-passes/paramAlignment.d.ts +4 -0
  78. package/dist/src/cst-passes/paramAlignment.d.ts.map +1 -0
  79. package/dist/src/cst-passes/paramAlignment.js +11 -0
  80. package/dist/src/cst-passes/paramAlignment.js.map +1 -0
  81. package/dist/src/cst-passes/parenthesisIfCase.d.ts +6 -0
  82. package/dist/src/cst-passes/parenthesisIfCase.d.ts.map +1 -0
  83. package/dist/src/cst-passes/parenthesisIfCase.js +42 -0
  84. package/dist/src/cst-passes/parenthesisIfCase.js.map +1 -0
  85. package/dist/src/cst-passes/printStatementRemoval.d.ts +20 -0
  86. package/dist/src/cst-passes/printStatementRemoval.d.ts.map +1 -0
  87. package/dist/src/cst-passes/printStatementRemoval.js +67 -0
  88. package/dist/src/cst-passes/printStatementRemoval.js.map +1 -0
  89. package/dist/src/cst-passes/spacing.d.ts +19 -0
  90. package/dist/src/cst-passes/spacing.d.ts.map +1 -0
  91. package/dist/src/cst-passes/spacing.js +81 -0
  92. package/dist/src/cst-passes/spacing.js.map +1 -0
  93. package/dist/src/cst-passes/splitArrayOpenBracket.d.ts +4 -0
  94. package/dist/src/cst-passes/splitArrayOpenBracket.d.ts.map +1 -0
  95. package/dist/src/cst-passes/splitArrayOpenBracket.js +11 -0
  96. package/dist/src/cst-passes/splitArrayOpenBracket.js.map +1 -0
  97. package/dist/src/cst-passes/stringConcatStyle.d.ts +4 -0
  98. package/dist/src/cst-passes/stringConcatStyle.d.ts.map +1 -0
  99. package/dist/src/cst-passes/stringConcatStyle.js +11 -0
  100. package/dist/src/cst-passes/stringConcatStyle.js.map +1 -0
  101. package/dist/src/cst-passes/stripCatchParens.d.ts +4 -0
  102. package/dist/src/cst-passes/stripCatchParens.d.ts.map +1 -0
  103. package/dist/src/cst-passes/stripCatchParens.js +29 -0
  104. package/dist/src/cst-passes/stripCatchParens.js.map +1 -0
  105. package/dist/src/cst-passes/thenStyle.d.ts +15 -0
  106. package/dist/src/cst-passes/thenStyle.d.ts.map +1 -0
  107. package/dist/src/cst-passes/thenStyle.js +140 -0
  108. package/dist/src/cst-passes/thenStyle.js.map +1 -0
  109. package/dist/src/cst-passes/trailingCommas.d.ts +13 -0
  110. package/dist/src/cst-passes/trailingCommas.d.ts.map +1 -0
  111. package/dist/src/cst-passes/trailingCommas.js +56 -0
  112. package/dist/src/cst-passes/trailingCommas.js.map +1 -0
  113. package/dist/src/cst-passes/trailingWhitespace.d.ts +13 -0
  114. package/dist/src/cst-passes/trailingWhitespace.d.ts.map +1 -0
  115. package/dist/src/cst-passes/trailingWhitespace.js +26 -0
  116. package/dist/src/cst-passes/trailingWhitespace.js.map +1 -0
  117. package/dist/src/cst-passes/wrapLongStrings.d.ts +4 -0
  118. package/dist/src/cst-passes/wrapLongStrings.d.ts.map +1 -0
  119. package/dist/src/cst-passes/wrapLongStrings.js +11 -0
  120. package/dist/src/cst-passes/wrapLongStrings.js.map +1 -0
  121. package/dist/src/formatter.d.ts +2 -1
  122. package/dist/src/formatter.d.ts.map +1 -1
  123. package/dist/src/formatter.js +74 -333
  124. package/dist/src/formatter.js.map +1 -1
  125. package/dist/src/index.d.ts +3 -2
  126. package/dist/src/index.d.ts.map +1 -1
  127. package/dist/src/index.js +11 -10
  128. package/dist/src/index.js.map +1 -1
  129. package/package.json +5 -2
@@ -2,22 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatText = formatText;
4
4
  exports.checkFormatting = checkFormatting;
5
- const builtins_1 = require("./builtins");
6
- const casing_1 = require("./casing");
7
- /** Canonical (catalog-cased) lookup tables, built once at module load. */
8
- const _builtinMap = new Map(builtins_1.BRIGHTSCRIPT_BUILTINS.map((b) => [b.name.toLowerCase(), b.name]));
9
- const _keywordSet = new Set(builtins_1.BRIGHTSCRIPT_KEYWORDS.map((k) => k.toLowerCase()));
10
- /** Mapping from compact end-keyword to spaced form. */
11
- const COMPACT_TO_SPACED = {
12
- 'endif': 'end if',
13
- 'endfunction': 'end function',
14
- 'endsub': 'end sub',
15
- 'endwhile': 'end while',
16
- 'endfor': 'end for',
17
- 'endtry': 'end try',
18
- };
5
+ const kopytko_brightscript_parser_1 = require("kopytko-brightscript-parser");
6
+ const kopytko_brightscript_parser_2 = require("kopytko-brightscript-parser");
7
+ const index_1 = require("./cst-passes/index");
19
8
  /** Reverse mapping: spaced form → compact. */
20
- const SPACED_TO_COMPACT = Object.fromEntries(Object.entries(COMPACT_TO_SPACED).map(([k, v]) => [v, k]));
21
9
  /**
22
10
  * Formats BrightScript source code using an 11-pass engine.
23
11
  *
@@ -30,7 +18,7 @@ const SPACED_TO_COMPACT = Object.fromEntries(Object.entries(COMPACT_TO_SPACED).m
30
18
  * @param userFunctions - Known user-defined functions for casing normalization (optional).
31
19
  * @returns The formatted source text.
32
20
  */
33
- function formatText(source, config, casing = casing_1.DEFAULT_CASING_CONFIG, userFunctions = []) {
21
+ function formatText(source, config, casing = kopytko_brightscript_parser_1.DEFAULT_CASING_CONFIG, userFunctions = []) {
34
22
  let lines = source.split(/\r?\n/);
35
23
  const detectedEnding = detectLineEnding(source);
36
24
  const lineEndStr = resolveLineEnding(config.lineEnding, detectedEnding);
@@ -39,62 +27,82 @@ function formatText(source, config, casing = casing_1.DEFAULT_CASING_CONFIG, use
39
27
  if (!userFuncMap.has(fn.nameLower))
40
28
  userFuncMap.set(fn.nameLower, fn.name);
41
29
  }
42
- // Pass 1 — Import sorting
30
+ // Pass 1 — Import sorting (regex — complex multi-group sorting with trivia)
43
31
  lines = passImportSorting(lines, config);
44
- // Pass 2 — Comment normalization
45
- lines = passCommentNormalization(lines, config);
46
- // Pass 3 — End keyword style + function vs sub
47
- lines = passEndKeywordStyle(lines, config);
48
- lines = passFunctionVsSub(lines, config);
49
- // Pass 4 — Then style + parenthesis if case + catch paren strip
50
- lines = passThenStyle(lines, config);
32
+ // Pass 2 — Comment normalization (CST)
33
+ lines = runCstOnLines(lines, lineEndStr, (0, index_1.commentNormalizationPass)({ commentStyle: config.commentStyle, spaceAfterCommentMarker: config.spaceAfterCommentMarker }));
34
+ // Pass 3 — End keyword style (CST) + function vs sub (CST)
35
+ if (config.endKeywordStyle !== 'preserve') {
36
+ lines = runCstOnLines(lines, lineEndStr, (0, index_1.endKeywordStylePass)(config.endKeywordStyle));
37
+ }
38
+ if (config.functionVsSubForVoid !== 'preserve' && config.functionVsSubForVoid !== 'allow-void') {
39
+ lines = runCstOnLines(lines, lineEndStr, (0, index_1.functionVsSubPass)(config.functionVsSubForVoid));
40
+ }
41
+ // Pass 4 — Then style (CST) + parenthesis if case (regex) + catch paren strip (regex)
42
+ if (config.thenStyle !== 'preserve') {
43
+ lines = runCstOnLines(lines, lineEndStr, (0, index_1.thenStylePass)(config.thenStyle));
44
+ }
51
45
  lines = passParenthesisIfCase(lines, config);
52
46
  lines = passStripCatchParens(lines);
53
- // Pass 4c — Else on new line
47
+ // Pass 4c — Else on new line (regex)
54
48
  lines = passElseOnNewLine(lines, config);
55
- // Pass 5 — Print statement handling
56
- lines = passPrintStatement(lines, config);
57
- // Pass 5b Line comment position
49
+ // Pass 5 — Print statement handling (CST)
50
+ if (config.printStatement === 'remove') {
51
+ lines = runCstOnLines(lines, lineEndStr, (0, index_1.printStatementRemovalPass)());
52
+ }
53
+ // Pass 5b — Line comment position (regex)
58
54
  lines = passLineCommentPosition(lines, config);
59
- // Pass 6 — Spacing rules
55
+ // Pass 6 — Spacing rules (regex)
60
56
  lines = passSpacing(lines, config);
61
- // Pass 6b — Wrap long strings
57
+ // Pass 6b — Wrap long strings (regex)
62
58
  lines = passWrapLongStrings(lines, config);
63
- // Pass 6c — String concatenation style
59
+ // Pass 6c — String concatenation style (regex)
64
60
  lines = passStringConcatStyle(lines, config);
65
- // Pass 7 — Casing
66
- lines = passCasing(lines, casing, userFuncMap);
67
- // Pass 7b — Split array open bracket
61
+ // Pass 7 — Casing (CST)
62
+ lines = runCstOnLines(lines, lineEndStr, (0, index_1.casingPass)(casing, userFuncMap));
63
+ // Pass 7b — Split array open bracket (regex)
68
64
  lines = passSplitArrayOpenBracket(lines, config);
69
- // Pass 7c — Associative array single-line threshold
65
+ // Pass 7c — Associative array single-line threshold (regex)
70
66
  lines = passAAThreshold(lines, config);
71
- // Pass 8 — Indentation
67
+ // Pass 8 — Indentation (regex)
72
68
  lines = passIndentation(lines, config);
73
- // Pass 8b — Trailing commas
69
+ // Pass 8b — Trailing commas (regex)
74
70
  lines = passTrailingCommas(lines, config);
75
- // Pass 8c — Align assignments
71
+ // Pass 8c — Align assignments (regex)
76
72
  lines = passAlignAssignments(lines, config);
77
- // Pass 8d — Multi-line param alignment
73
+ // Pass 8d — Multi-line param alignment (regex)
78
74
  lines = passParamAlignment(lines, config);
79
- // Pass 9 — Blank line rules
75
+ // Pass 9 — Blank line rules (regex)
80
76
  lines = passBlankLines(lines, config);
81
- // Pass 9b — Empty lines between methods
77
+ // Pass 9b — Empty lines between methods (regex)
82
78
  lines = passEmptyLinesBetweenMethods(lines, config);
83
- // Pass 10 — Trailing whitespace
84
- lines = passTrimTrailing(lines, config);
85
- // Pass 11 Comment width
79
+ // Pass 10 — Trailing whitespace (CST)
80
+ if (config.trimTrailingWhitespace) {
81
+ lines = runCstOnLines(lines, lineEndStr, (0, index_1.trailingWhitespacePass)());
82
+ }
83
+ // Pass 11 — Comment width (regex)
86
84
  lines = passCommentWidth(lines, config);
87
- // Pass 12 — observeField style
85
+ // Pass 12 — observeField style (regex)
88
86
  lines = passObserveFieldStyle(lines, config);
89
- // Pass 13 — m prefix style
87
+ // Pass 13 — m prefix style (regex)
90
88
  lines = passMPrefixStyle(lines, config);
91
- // Pass 14 — Field access consistency
89
+ // Pass 14 — Field access consistency (regex)
92
90
  lines = passFieldAccessConsistency(lines, config);
93
91
  // Assemble result
94
92
  let newText = lines.join(lineEndStr);
95
93
  if (config.insertFinalNewline && newText.length > 0 && !newText.endsWith(lineEndStr)) {
96
94
  newText += lineEndStr;
97
95
  }
96
+ // ── Syntax safety verification ──────────────────────────────────────────
97
+ // Parse the formatted output to detect if formatting broke the syntax.
98
+ // If errors are found, return the original source unchanged.
99
+ if (config.verifySyntax !== false) {
100
+ const result = (0, kopytko_brightscript_parser_2.parse)(newText);
101
+ if (result.diagnostics.length > 0) {
102
+ // Formatting introduced syntax errors — fall back to original source
103
+ return source;
104
+ }
105
+ }
98
106
  return newText;
99
107
  }
100
108
  /**
@@ -106,6 +114,25 @@ function checkFormatting(source, config, casing, userFunctions) {
106
114
  return formatText(source, config, casing, userFunctions) === source;
107
115
  }
108
116
  // ---------------------------------------------------------------------------
117
+ // CST ↔ lines bridge
118
+ // ---------------------------------------------------------------------------
119
+ /**
120
+ * Runs a CST pass on a string[] lines array.
121
+ * Joins lines → runs CST pass → splits back to lines.
122
+ * Falls back to original lines if the source has parse errors.
123
+ */
124
+ function runCstOnLines(lines, lineEnd, pass) {
125
+ const source = lines.join(lineEnd);
126
+ const parseResult = (0, kopytko_brightscript_parser_2.parse)(source);
127
+ if (parseResult.diagnostics.length > 0)
128
+ return lines; // can't CST-transform broken code
129
+ const edits = pass(parseResult.root, source);
130
+ if (edits.length === 0)
131
+ return lines;
132
+ const result = (0, index_1.applyEdits)(source, edits);
133
+ return result.split(/\r?\n/);
134
+ }
135
+ // ---------------------------------------------------------------------------
109
136
  // Line ending helpers
110
137
  // ---------------------------------------------------------------------------
111
138
  function detectLineEnding(text) {
@@ -212,203 +239,9 @@ function passImportSorting(lines, config) {
212
239
  return [...sorted, ...lines.slice(blockEnd)];
213
240
  }
214
241
  // ---------------------------------------------------------------------------
215
- // Pass 2 — Comment normalization
216
242
  // ---------------------------------------------------------------------------
217
- function passCommentNormalization(lines, config) {
218
- if (config.commentStyle === 'preserve' && !config.spaceAfterCommentMarker)
219
- return lines;
220
- return lines.map(line => {
221
- const trimmed = line.trim();
222
- if (/^\s*'\s*@(?:import|mock)\s+/.test(line))
223
- return line;
224
- const isTickComment = trimmed.startsWith("'");
225
- const isRemComment = /^rem\b/i.test(trimmed);
226
- if (!isTickComment && !isRemComment)
227
- return line;
228
- const indent = line.match(/^(\s*)/)?.[1] ?? '';
229
- let content = isTickComment ? trimmed.slice(1) : trimmed.slice(3);
230
- let marker;
231
- if (config.commentStyle === 'preserve') {
232
- marker = isTickComment ? "'" : trimmed.slice(0, 3);
233
- }
234
- else {
235
- marker = config.commentStyle === "'" ? "'" : 'rem';
236
- }
237
- if (config.spaceAfterCommentMarker && content.length > 0 && !content.startsWith(' ')) {
238
- content = ' ' + content;
239
- }
240
- return indent + marker + content;
241
- });
242
- }
243
- // ---------------------------------------------------------------------------
244
- // Pass 3 — End keyword style
245
- // ---------------------------------------------------------------------------
246
- function passEndKeywordStyle(lines, config) {
247
- if (config.endKeywordStyle === 'preserve')
248
- return lines;
249
- return lines.map(line => {
250
- const lower = line.trim().toLowerCase();
251
- const indent = line.match(/^(\s*)/)?.[1] ?? '';
252
- if (config.endKeywordStyle === 'compact') {
253
- const compact = SPACED_TO_COMPACT[lower];
254
- if (compact)
255
- return indent + compact;
256
- }
257
- else {
258
- const spaced = COMPACT_TO_SPACED[lower];
259
- if (spaced)
260
- return indent + spaced;
261
- }
262
- return line;
263
- });
264
- }
265
- // ---------------------------------------------------------------------------
266
- // Pass 3b — function vs sub for void
267
- // ---------------------------------------------------------------------------
268
- function passFunctionVsSub(lines, config) {
269
- if (config.functionVsSubForVoid === 'preserve')
270
- return lines;
271
- const result = [...lines];
272
- const namedDeclRegex = /^(\s*)(function|sub)\s+(\w+)\s*\((.*)\)(?:\s+as\s+(\w+))?\s*$/i;
273
- const anonDeclRegex = /^(.*\b)(function|sub)(\s*\(.*\))(?:\s+as\s+(\w+))?\s*$/i;
274
- for (let i = 0; i < result.length; i++) {
275
- // Named declarations
276
- const nm = namedDeclRegex.exec(result[i]);
277
- if (nm) {
278
- const [, indent, keyword, name, params, returnType] = nm;
279
- const kw = keyword.toLowerCase();
280
- const endIdx = findMatchingEnd(result, i);
281
- if (endIdx < 0)
282
- continue;
283
- const hasExplicitReturnType = returnType && returnType.toLowerCase() !== 'void';
284
- const hasValueReturn = hasReturnWithValue(result, i + 1, endIdx);
285
- const isVoid = !hasExplicitReturnType && !hasValueReturn;
286
- if (config.functionVsSubForVoid === 'sub' && kw === 'function' && isVoid) {
287
- result[i] = `${indent}sub ${name}(${params})`;
288
- const ei = result[endIdx].match(/^(\s*)/)?.[1] ?? '';
289
- const el = result[endIdx].trim().toLowerCase();
290
- if (el === 'end function')
291
- result[endIdx] = ei + 'end sub';
292
- else if (el === 'endfunction')
293
- result[endIdx] = ei + 'endsub';
294
- }
295
- else if (config.functionVsSubForVoid === 'function' && kw === 'sub') {
296
- result[i] = `${indent}function ${name}(${params}) as Void`;
297
- const ei = result[endIdx].match(/^(\s*)/)?.[1] ?? '';
298
- const el = result[endIdx].trim().toLowerCase();
299
- if (el === 'end sub')
300
- result[endIdx] = ei + 'end function';
301
- else if (el === 'endsub')
302
- result[endIdx] = ei + 'endfunction';
303
- }
304
- continue;
305
- }
306
- // Anonymous function declarations
307
- const am = anonDeclRegex.exec(result[i]);
308
- if (!am)
309
- continue;
310
- if (/^\s*(?:function|sub)\b/i.test(result[i]))
311
- continue;
312
- const [, prefix, keyword, paramsWithParens, returnType] = am;
313
- const kw = keyword.toLowerCase();
314
- const endIdx = findMatchingEnd(result, i);
315
- if (endIdx < 0)
316
- continue;
317
- const hasExplicitReturnType = returnType && returnType.toLowerCase() !== 'void';
318
- const hasValueReturn = hasReturnWithValue(result, i + 1, endIdx);
319
- const isVoid = !hasExplicitReturnType && !hasValueReturn;
320
- if (config.functionVsSubForVoid === 'sub' && kw === 'function' && isVoid) {
321
- result[i] = `${prefix}sub${paramsWithParens}`;
322
- replaceEndKeyword(result, endIdx, 'function', 'sub');
323
- }
324
- else if (config.functionVsSubForVoid === 'function' && kw === 'sub') {
325
- result[i] = `${prefix}function${paramsWithParens} as Void`;
326
- replaceEndKeyword(result, endIdx, 'sub', 'function');
327
- }
328
- }
329
- return result;
330
- }
331
- function replaceEndKeyword(lines, idx, from, to) {
332
- const spaced = new RegExp(`\\bend\\s+${from}\\b`, 'i');
333
- const joined = new RegExp(`\\bend${from}\\b`, 'i');
334
- if (spaced.test(lines[idx])) {
335
- lines[idx] = lines[idx].replace(spaced, `end ${to}`);
336
- }
337
- else {
338
- lines[idx] = lines[idx].replace(joined, `end${to}`);
339
- }
340
- }
341
- function hasReturnWithValue(lines, startIdx, endIdx) {
342
- let depth = 0;
343
- for (let i = startIdx; i < endIdx; i++) {
344
- const trimmed = lines[i].trim().toLowerCase();
345
- if (/^(?:function|sub)\b/.test(trimmed) || isAnonFunctionOpener(trimmed))
346
- depth++;
347
- else if (/^(?:end\s*function|end\s*sub|endfunction|endsub)\b/.test(trimmed))
348
- depth--;
349
- if (depth > 0)
350
- continue;
351
- if (/^return(?:\s+\S|\{|\[|\()/i.test(trimmed))
352
- return true;
353
- }
354
- return false;
355
- }
356
- function findMatchingEnd(lines, startIdx) {
357
- let depth = 1;
358
- for (let i = startIdx + 1; i < lines.length; i++) {
359
- const lower = lines[i].trim().toLowerCase();
360
- if (/^(?:function|sub)\b/i.test(lower) || isAnonFunctionOpener(lower))
361
- depth++;
362
- else if (/^(?:end\s*function|end\s*sub|endfunction|endsub)\b/i.test(lower)) {
363
- if (--depth === 0)
364
- return i;
365
- }
366
- }
367
- return -1;
368
- }
369
243
  // ---------------------------------------------------------------------------
370
- // Pass 4 — Then style
371
244
  // ---------------------------------------------------------------------------
372
- function passThenStyle(lines, config) {
373
- if (config.thenStyle === 'preserve')
374
- return lines;
375
- return lines.map(line => {
376
- const trimmed = line.trim();
377
- if (!/^(?:if|else\s*if|elseif)\b/i.test(trimmed))
378
- return line;
379
- const indent = line.match(/^(\s*)/)?.[1] ?? '';
380
- const hasThen = /\bthen\b/i.test(trimmed);
381
- let isSingleLine = false;
382
- if (hasThen) {
383
- const afterThen = trimmed.replace(/^.*?\bthen\b/i, '').trim();
384
- isSingleLine = afterThen !== '' && !afterThen.startsWith("'") && !/^rem\b/i.test(afterThen);
385
- }
386
- const removeThen = () => {
387
- const split = splitTrailingComment(trimmed);
388
- const codeOnly = split.code.replace(/\s*\bthen\b\s*$/i, '').trimEnd();
389
- return indent + codeOnly + (split.comment ? ' ' + split.comment : '');
390
- };
391
- switch (config.thenStyle) {
392
- case 'always':
393
- if (!hasThen)
394
- return indent + trimmed + ' then';
395
- break;
396
- case 'never':
397
- if (hasThen && !isSingleLine)
398
- return removeThen();
399
- break;
400
- case 'multiline-only':
401
- if (!hasThen)
402
- return indent + trimmed + ' then';
403
- break;
404
- case 'singleline-only':
405
- if (hasThen && !isSingleLine)
406
- return removeThen();
407
- break;
408
- }
409
- return line;
410
- });
411
- }
412
245
  // ---------------------------------------------------------------------------
413
246
  // Pass 4b — Strip catch parentheses (always — BrightScript does not allow them)
414
247
  // ---------------------------------------------------------------------------
@@ -546,13 +379,6 @@ function passElseOnNewLine(lines, config) {
546
379
  return result;
547
380
  }
548
381
  // ---------------------------------------------------------------------------
549
- // Pass 5 — Print statement handling
550
- // ---------------------------------------------------------------------------
551
- function passPrintStatement(lines, config) {
552
- if (config.printStatement !== 'remove')
553
- return lines;
554
- return lines.filter(line => !/^\s*(?:print|\?)\b/i.test(line));
555
- }
556
382
  // ---------------------------------------------------------------------------
557
383
  // Pass 5b — Line comment position
558
384
  // ---------------------------------------------------------------------------
@@ -796,17 +622,6 @@ function applyBracketAndCommaSpacing(line, config) {
796
622
  return result;
797
623
  }
798
624
  // ---------------------------------------------------------------------------
799
- // Pass 7 — Casing
800
- // ---------------------------------------------------------------------------
801
- function passCasing(lines, casing, userFuncMap) {
802
- return lines.map(line => {
803
- const trimmed = line.trim();
804
- if (trimmed === '' || trimmed.startsWith("'") || /^rem\b/i.test(trimmed))
805
- return line;
806
- const indent = line.match(/^(\s*)/)?.[1] ?? '';
807
- return indent + applyCasingToLine(trimmed, casing, userFuncMap);
808
- });
809
- }
810
625
  // ---------------------------------------------------------------------------
811
626
  // Pass 7b — Split array open bracket
812
627
  // ---------------------------------------------------------------------------
@@ -1290,14 +1105,6 @@ function passEmptyLinesBetweenMethods(lines, config) {
1290
1105
  return out;
1291
1106
  }
1292
1107
  // ---------------------------------------------------------------------------
1293
- // Pass 10 — Trim trailing whitespace
1294
- // ---------------------------------------------------------------------------
1295
- function passTrimTrailing(lines, config) {
1296
- if (!config.trimTrailingWhitespace)
1297
- return lines;
1298
- return lines.map(line => line.trimEnd());
1299
- }
1300
- // ---------------------------------------------------------------------------
1301
1108
  // Pass 11 — Comment width
1302
1109
  // ---------------------------------------------------------------------------
1303
1110
  function passCommentWidth(lines, config) {
@@ -1998,22 +1805,6 @@ function isReturnAloneInBlock(lines, returnIdx) {
1998
1805
  }
1999
1806
  return true;
2000
1807
  }
2001
- // ---------------------------------------------------------------------------
2002
- // Casing helpers
2003
- // ---------------------------------------------------------------------------
2004
- function applyCasingToLine(line, casing, userFuncMap) {
2005
- const segments = splitCodeSegments(line);
2006
- let result = '';
2007
- for (const seg of segments) {
2008
- if (seg.isCode) {
2009
- result += transformCodeSegment(seg.text, casing, userFuncMap);
2010
- }
2011
- else {
2012
- result += seg.text;
2013
- }
2014
- }
2015
- return result;
2016
- }
2017
1808
  function splitCodeSegments(line) {
2018
1809
  const segments = [];
2019
1810
  let current = '';
@@ -2053,54 +1844,4 @@ function splitCodeSegments(line) {
2053
1844
  }
2054
1845
  return segments;
2055
1846
  }
2056
- function transformCodeSegment(code, casing, userFuncMap) {
2057
- const exactMap = casing.exact ?? {};
2058
- const userFuncCasing = casing.userFunction ?? 'preserve';
2059
- return code.replace(/\b([a-zA-Z_]\w*)\b/g, (match, _group, offset) => {
2060
- const afterIdx = offset + match.length;
2061
- const restAfter = code.slice(afterIdx);
2062
- if (/^\s*:/.test(restAfter))
2063
- return match;
2064
- if (offset > 0 && code[offset - 1] === '.')
2065
- return match;
2066
- const lower = match.toLowerCase();
2067
- const exact = Object.prototype.hasOwnProperty.call(exactMap, lower) ? exactMap[lower] : undefined;
2068
- if (exact !== undefined)
2069
- return exact;
2070
- if (_keywordSet.has(lower)) {
2071
- let category = (0, builtins_1.getKeywordCategory)(lower);
2072
- if (lower === 'function' && offset >= 3) {
2073
- const before = code.slice(Math.max(0, offset - 10), offset);
2074
- if (/\bas\s+$/i.test(before)) {
2075
- category = 'type';
2076
- }
2077
- }
2078
- const effectiveCasing = (0, casing_1.resolveKeywordCasing)(category, casing);
2079
- if (effectiveCasing !== 'preserve') {
2080
- return (0, casing_1.applyCasingWithOverrides)(match, effectiveCasing, exactMap);
2081
- }
2082
- return match;
2083
- }
2084
- if (_builtinMap.has(lower)) {
2085
- // Only treat as a built-in function when it is actually being called.
2086
- // Identifiers that happen to share a name with a built-in (e.g. a
2087
- // parameter named `str`) should not be re-cased.
2088
- if (!/^\s*\(/.test(restAfter))
2089
- return match;
2090
- const canonical = _builtinMap.get(lower);
2091
- if (casing.builtin !== 'preserve') {
2092
- return (0, casing_1.applyCasingWithOverrides)(canonical, casing.builtin, exactMap);
2093
- }
2094
- return canonical;
2095
- }
2096
- if (userFuncMap.has(lower)) {
2097
- const definitionName = userFuncMap.get(lower);
2098
- if (userFuncCasing !== 'preserve') {
2099
- return (0, casing_1.applyCasing)(definitionName, userFuncCasing);
2100
- }
2101
- return definitionName;
2102
- }
2103
- return match;
2104
- });
2105
- }
2106
1847
  //# sourceMappingURL=formatter.js.map