mcp-code-context 2.2.0 → 2.4.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 (132) hide show
  1. package/.io/RULES.md +12 -0
  2. package/CHANGELOG.md +114 -1
  3. package/README.md +28 -1
  4. package/dist/src/ast/writers/safeRename.d.ts +14 -0
  5. package/dist/src/ast/writers/safeRename.js +258 -0
  6. package/dist/src/ast/writers/safeRename.js.map +1 -0
  7. package/dist/src/ast/writers/symbolWriter.d.ts +2 -0
  8. package/dist/src/ast/writers/symbolWriter.js +9 -14
  9. package/dist/src/ast/writers/symbolWriter.js.map +1 -1
  10. package/dist/src/cache/astCache.d.ts +33 -0
  11. package/dist/src/cache/astCache.js +113 -0
  12. package/dist/src/cache/astCache.js.map +1 -0
  13. package/dist/src/handlers/readHandlers.d.ts +70 -0
  14. package/dist/src/handlers/readHandlers.js +431 -0
  15. package/dist/src/handlers/readHandlers.js.map +1 -0
  16. package/dist/src/handlers/utilHandlers.d.ts +31 -0
  17. package/dist/src/handlers/utilHandlers.js +69 -0
  18. package/dist/src/handlers/utilHandlers.js.map +1 -0
  19. package/dist/src/handlers/writeHandlers.d.ts +57 -0
  20. package/dist/src/handlers/writeHandlers.js +475 -0
  21. package/dist/src/handlers/writeHandlers.js.map +1 -0
  22. package/dist/src/index.d.ts +6 -143
  23. package/dist/src/index.js +77 -1091
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/tools/searchCodePattern.d.ts +7 -2
  26. package/dist/src/tools/searchCodePattern.js +68 -4
  27. package/dist/src/tools/searchCodePattern.js.map +1 -1
  28. package/dist/src/utils/arrayUtils.d.ts +12 -0
  29. package/dist/src/utils/arrayUtils.js +27 -0
  30. package/dist/src/utils/arrayUtils.js.map +1 -0
  31. package/dist/src/utils/constants.d.ts +46 -0
  32. package/dist/src/utils/constants.js +112 -0
  33. package/dist/src/utils/constants.js.map +1 -0
  34. package/dist/src/utils/ignoreManager.d.ts +8 -0
  35. package/dist/src/utils/ignoreManager.js +49 -0
  36. package/dist/src/utils/ignoreManager.js.map +1 -1
  37. package/dist/src/utils/normalization.d.ts +49 -0
  38. package/dist/src/utils/normalization.js +173 -0
  39. package/dist/src/utils/normalization.js.map +1 -0
  40. package/dist/src/utils/projectRoot.d.ts +12 -5
  41. package/dist/src/utils/projectRoot.js +22 -20
  42. package/dist/src/utils/projectRoot.js.map +1 -1
  43. package/dist/src/utils/secureValidation.d.ts +15 -0
  44. package/dist/src/utils/secureValidation.js +41 -0
  45. package/dist/src/utils/secureValidation.js.map +1 -0
  46. package/dist/src/utils/transactionManager.d.ts +45 -0
  47. package/dist/src/utils/transactionManager.js +102 -0
  48. package/dist/src/utils/transactionManager.js.map +1 -0
  49. package/dist/src/utils/validation.d.ts +87 -0
  50. package/dist/src/utils/validation.js +231 -0
  51. package/dist/src/utils/validation.js.map +1 -0
  52. package/docs/ARCHITECTURE.md +300 -0
  53. package/docs/architecture/ADR-001-modular-handlers.md +119 -0
  54. package/llms.txt +10 -1
  55. package/package.json +5 -5
  56. package/AGENT_INSTRUCTIONS.md +0 -135
  57. package/DEPLOYMENT_INSTRUCTIONS.md +0 -110
  58. package/MEJORAS_PROPUESTAS.md +0 -324
  59. package/RELEASE_NOTES_v2.1.3.md +0 -78
  60. package/RELEASE_NOTES_v2.2.0.md +0 -286
  61. package/dist/src/tests/test-confirmation-flow.d.ts +0 -1
  62. package/dist/src/tests/test-confirmation-flow.js +0 -43
  63. package/dist/src/tests/test-confirmation-flow.js.map +0 -1
  64. package/dist/tests/debug-replace.d.ts +0 -1
  65. package/dist/tests/debug-replace.js +0 -17
  66. package/dist/tests/debug-replace.js.map +0 -1
  67. package/dist/tests/test-backup-system.d.ts +0 -7
  68. package/dist/tests/test-backup-system.js +0 -152
  69. package/dist/tests/test-backup-system.js.map +0 -1
  70. package/dist/tests/test-confirmation-flow.d.ts +0 -1
  71. package/dist/tests/test-confirmation-flow.js +0 -43
  72. package/dist/tests/test-confirmation-flow.js.map +0 -1
  73. package/dist/tests/test-dart.d.ts +0 -1
  74. package/dist/tests/test-dart.js +0 -242
  75. package/dist/tests/test-dart.js.map +0 -1
  76. package/dist/tests/test-new-tools.d.ts +0 -4
  77. package/dist/tests/test-new-tools.js +0 -147
  78. package/dist/tests/test-new-tools.js.map +0 -1
  79. package/dist/tests/test-php-ast.d.ts +0 -1
  80. package/dist/tests/test-php-ast.js +0 -152
  81. package/dist/tests/test-php-ast.js.map +0 -1
  82. package/dist/tests/test-php.d.ts +0 -1
  83. package/dist/tests/test-php.js +0 -234
  84. package/dist/tests/test-php.js.map +0 -1
  85. package/dist/tests/test-smoke.d.ts +0 -5
  86. package/dist/tests/test-smoke.js +0 -83
  87. package/dist/tests/test-smoke.js.map +0 -1
  88. package/dist/tests/writers/test-crlf-handling.d.ts +0 -4
  89. package/dist/tests/writers/test-crlf-handling.js +0 -73
  90. package/dist/tests/writers/test-crlf-handling.js.map +0 -1
  91. package/dist/tests/writers/test-write-smoke.d.ts +0 -9
  92. package/dist/tests/writers/test-write-smoke.js +0 -346
  93. package/dist/tests/writers/test-write-smoke.js.map +0 -1
  94. package/src/ast/dartCompressor.ts +0 -1013
  95. package/src/ast/phpCompressor.ts +0 -814
  96. package/src/ast/semanticCompressor.ts +0 -808
  97. package/src/ast/writers/dartWriter.ts +0 -648
  98. package/src/ast/writers/phpWriter.ts +0 -508
  99. package/src/ast/writers/pyWriter.ts +0 -446
  100. package/src/ast/writers/symbolWriter.ts +0 -277
  101. package/src/ast/writers/tsWriter.ts +0 -418
  102. package/src/index.ts +0 -1784
  103. package/src/tools/readFileLines.ts +0 -104
  104. package/src/tools/searchCodePattern.ts +0 -129
  105. package/src/utils/backupManager.ts +0 -141
  106. package/src/utils/confirmationCache.ts +0 -61
  107. package/src/utils/diffEngine.ts +0 -223
  108. package/src/utils/fuzzyMatch.ts +0 -86
  109. package/src/utils/ignoreManager.ts +0 -144
  110. package/src/utils/projectRoot.ts +0 -44
  111. package/tests/test-backup-system.ts +0 -174
  112. package/tests/test-confirmation-flow.ts +0 -49
  113. package/tests/test-dart.d.ts +0 -1
  114. package/tests/test-dart.js +0 -209
  115. package/tests/test-dart.js.map +0 -1
  116. package/tests/test-dart.ts +0 -265
  117. package/tests/test-new-tools.ts +0 -165
  118. package/tests/test-php-ast.d.ts +0 -1
  119. package/tests/test-php-ast.js +0 -152
  120. package/tests/test-php-ast.js.map +0 -1
  121. package/tests/test-php-ast.ts +0 -155
  122. package/tests/test-php.d.ts +0 -1
  123. package/tests/test-php.js +0 -234
  124. package/tests/test-php.js.map +0 -1
  125. package/tests/test-php.ts +0 -261
  126. package/tests/test-smoke.d.ts +0 -5
  127. package/tests/test-smoke.js +0 -83
  128. package/tests/test-smoke.js.map +0 -1
  129. package/tests/test-smoke.ts +0 -93
  130. package/tests/writers/test-crlf-handling.ts +0 -100
  131. package/tests/writers/test-write-smoke.ts +0 -568
  132. package/tsconfig.json +0 -19
@@ -1,648 +0,0 @@
1
- /**
2
- * dartWriter.ts — Dart Surgical Writer
3
- *
4
- * Uses the same brace-counting + regex engine as dartCompressor.ts
5
- * to locate symbol boundaries, then performs line-splice operations.
6
- *
7
- * Dart code structure is brace-delimited like most C-style languages,
8
- * so we reuse the proven boundary detection from the read side.
9
- *
10
- * Supports: .dart
11
- */
12
-
13
- // ─── Types ──────────────────────────────────────────────────────────
14
-
15
- export interface WriteResult {
16
- success: boolean;
17
- newContent: string;
18
- symbolsAffected: string[];
19
- error?: string;
20
- }
21
-
22
- export type InsertPosition = "before" | "after" | "inside_start" | "inside_end";
23
-
24
- // ─── Regex Patterns ─────────────────────────────────────────────────
25
-
26
- const DECLARATION_REGEX =
27
- /^(abstract\s+)?(class|mixin|extension|enum)\s+(\w+)/;
28
-
29
- const IMPORT_REGEX = /^\s*import\s+['"]/;
30
-
31
- // ─── Replace Symbol ─────────────────────────────────────────────────
32
-
33
- /**
34
- * Replace the full text of a named symbol in a Dart file.
35
- * When className is provided, scopes the search to within that class only.
36
- */
37
- export function replaceDartSymbol(
38
- content: string,
39
- symbolName: string,
40
- newContent: string,
41
- className?: string,
42
- ): WriteResult {
43
- // Normalize CRLF to LF for consistent processing
44
- content = content.replace(/\r\n/g, "\n");
45
- newContent = newContent.replace(/\r\n/g, "\n");
46
-
47
- const lines = content.split("\n");
48
- const range = findSymbolRange(lines, symbolName, className);
49
-
50
- if (!range) {
51
- return {
52
- success: false,
53
- newContent: content,
54
- symbolsAffected: [],
55
- error: className
56
- ? `Symbol "${symbolName}" not found in class "${className}" in Dart file`
57
- : `Symbol "${symbolName}" not found in Dart file`,
58
- };
59
- }
60
-
61
- const { start, end } = range;
62
-
63
- // Detect indentation of original
64
- const originalIndent = getIndent(lines[start]);
65
- const reindented = reindentCode(newContent.trim(), originalIndent);
66
-
67
- // Splice
68
- const before = lines.slice(0, start);
69
- const after = lines.slice(end + 1);
70
- const result = [...before, reindented, ...after].join("\n");
71
-
72
- // Basic validation: check brace balance
73
- if (!hasBraceBalance(result)) {
74
- return {
75
- success: false,
76
- newContent: content,
77
- symbolsAffected: [],
78
- error: `Replacement produced unbalanced braces. Aborting.`,
79
- };
80
- }
81
-
82
- return {
83
- success: true,
84
- newContent: result,
85
- symbolsAffected: [symbolName],
86
- };
87
- }
88
-
89
- // ─── Insert Code ────────────────────────────────────────────────────
90
-
91
- /**
92
- * Insert new code at a precise location relative to an anchor symbol.
93
- * When className is provided, scopes the anchor search to within that class only.
94
- */
95
- export function insertDartCode(
96
- content: string,
97
- code: string,
98
- anchorSymbol: string | null,
99
- position: InsertPosition,
100
- className?: string,
101
- ): WriteResult {
102
- // Normalize CRLF to LF for consistent processing
103
- content = content.replace(/\r\n/g, "\n");
104
- code = code.replace(/\r\n/g, "\n");
105
-
106
- const lines = content.split("\n");
107
-
108
- if (!anchorSymbol) {
109
- // No anchor — append at end of file
110
- lines.push("", code.trim(), "");
111
- return {
112
- success: true,
113
- newContent: lines.join("\n"),
114
- symbolsAffected: [],
115
- };
116
- }
117
-
118
- const range = findSymbolRange(lines, anchorSymbol, className);
119
- if (!range) {
120
- return {
121
- success: false,
122
- newContent: content,
123
- symbolsAffected: [],
124
- error: className
125
- ? `Anchor symbol "${anchorSymbol}" not found in class "${className}"`
126
- : `Anchor symbol "${anchorSymbol}" not found`,
127
- };
128
- }
129
-
130
- const { start, end } = range;
131
- const indent = getIndent(lines[start]);
132
-
133
- switch (position) {
134
- case "before": {
135
- const reindented = reindentCode(code.trim(), indent);
136
- lines.splice(start, 0, reindented, "");
137
- break;
138
- }
139
- case "after": {
140
- const reindented = reindentCode(code.trim(), indent);
141
- lines.splice(end + 1, 0, "", reindented);
142
- break;
143
- }
144
- case "inside_start": {
145
- // Find opening brace
146
- const braceIdx = findBraceLine(lines, start);
147
- if (braceIdx === -1) {
148
- return {
149
- success: false,
150
- newContent: content,
151
- symbolsAffected: [],
152
- error: `Cannot find opening brace for "${anchorSymbol}"`,
153
- };
154
- }
155
- const innerIndent = indent + " "; // Dart convention: 2-space indent
156
- const innerCode = reindentCode(code.trim(), innerIndent);
157
- lines.splice(braceIdx + 1, 0, innerCode, "");
158
- break;
159
- }
160
- case "inside_end": {
161
- const innerIndent = indent + " ";
162
- const innerCode = reindentCode(code.trim(), innerIndent);
163
- lines.splice(end, 0, "", innerCode);
164
- break;
165
- }
166
- }
167
-
168
- return {
169
- success: true,
170
- newContent: lines.join("\n"),
171
- symbolsAffected: [anchorSymbol],
172
- };
173
- }
174
-
175
- // ─── Rename Symbol ──────────────────────────────────────────────────
176
-
177
- /**
178
- * Rename all occurrences of a symbol within a Dart file.
179
- */
180
- export function renameDartSymbol(
181
- content: string,
182
- oldName: string,
183
- newName: string,
184
- ): WriteResult {
185
- // Normalize CRLF to LF for consistent processing
186
- content = content.replace(/\r\n/g, "\n");
187
-
188
- const lines = content.split("\n");
189
- const range = findSymbolRange(lines, oldName);
190
-
191
- if (!range) {
192
- return {
193
- success: false,
194
- newContent: content,
195
- symbolsAffected: [],
196
- error: `Symbol "${oldName}" not found for renaming`,
197
- };
198
- }
199
-
200
- // Whole-word replacement
201
- const escaped = oldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
202
- const regex = new RegExp(`\\b${escaped}\\b`, "g");
203
- const result = content.replace(regex, newName);
204
-
205
- return {
206
- success: true,
207
- newContent: result,
208
- symbolsAffected: [oldName],
209
- };
210
- }
211
-
212
- // ─── Remove Symbol ──────────────────────────────────────────────────
213
-
214
- /**
215
- * Remove a named symbol from a Dart file.
216
- * When className is provided, scopes the search to within that class only.
217
- */
218
- export function removeDartSymbol(
219
- content: string,
220
- symbolName: string,
221
- className?: string,
222
- ): WriteResult {
223
- // Normalize CRLF to LF for consistent processing
224
- content = content.replace(/\r\n/g, "\n");
225
-
226
- const lines = content.split("\n");
227
- const range = findSymbolRange(lines, symbolName, className);
228
-
229
- if (!range) {
230
- return {
231
- success: false,
232
- newContent: content,
233
- symbolsAffected: [],
234
- error: className
235
- ? `Symbol "${symbolName}" not found in class "${className}" for removal`
236
- : `Symbol "${symbolName}" not found for removal`,
237
- };
238
- }
239
-
240
- lines.splice(range.start, range.end - range.start + 1);
241
-
242
- let result = lines.join("\n");
243
- result = result.replace(/\n{3,}/g, "\n\n");
244
-
245
- return {
246
- success: true,
247
- newContent: result,
248
- symbolsAffected: [symbolName],
249
- };
250
- }
251
-
252
- // ─── Symbol Finder ──────────────────────────────────────────────────
253
-
254
- interface SymbolRange {
255
- start: number; // 0-indexed line
256
- end: number; // 0-indexed line (inclusive)
257
- }
258
-
259
- /**
260
- * Find the start and end line indices of a named symbol in a Dart file.
261
- * Includes leading doc comments and annotations.
262
- * When className is provided, scopes method search to within that class only.
263
- */
264
- function findSymbolRange(
265
- lines: string[],
266
- symbolName: string,
267
- className?: string,
268
- ): SymbolRange | null {
269
- // If className is provided, scope the search to within that class
270
- if (className) {
271
- for (let i = 0; i < lines.length; i++) {
272
- const trimmed = lines[i].trim();
273
- const declMatch = DECLARATION_REGEX.exec(trimmed);
274
- if (declMatch && declMatch[3] === className) {
275
- // Found the class — determine body boundaries
276
- const classEnd = findBlockEnd(lines, i);
277
-
278
- // Search only within the class body for the method
279
- for (let j = i + 1; j < classEnd; j++) {
280
- const bodyTrimmed = lines[j].trim();
281
- const escaped = symbolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
282
- const methodMatch = bodyTrimmed.match(
283
- new RegExp(`\\b${escaped}\\s*[<(]`),
284
- );
285
- if (
286
- methodMatch &&
287
- !DECLARATION_REGEX.test(bodyTrimmed) &&
288
- !IMPORT_REGEX.test(bodyTrimmed) &&
289
- (bodyTrimmed.includes("(") || bodyTrimmed.includes("<"))
290
- ) {
291
- if (isMethodOrConstructor(bodyTrimmed) || isTopLevelFunction(bodyTrimmed)) {
292
- const start = findDocStart(lines, j);
293
- const end = findBlockEnd(lines, j);
294
- return { start, end };
295
- }
296
- }
297
- }
298
- return null; // Method not found within class
299
- }
300
- }
301
- return null; // Class not found
302
- }
303
-
304
- // Original behavior when no className is provided
305
- for (let i = 0; i < lines.length; i++) {
306
- const trimmed = lines[i].trim();
307
-
308
- // Match class-level declarations
309
- const declMatch = DECLARATION_REGEX.exec(trimmed);
310
- if (declMatch && declMatch[3] === symbolName) {
311
- const start = findDocStart(lines, i);
312
- const end = findBlockEnd(lines, i);
313
- return { start, end };
314
- }
315
-
316
- // Match top-level functions
317
- if (isTopLevelFunction(trimmed)) {
318
- const funcName = extractFunctionName(trimmed);
319
- if (funcName === symbolName) {
320
- const start = findDocStart(lines, i);
321
- const end = findBlockEnd(lines, i);
322
- return { start, end };
323
- }
324
- }
325
-
326
- // Match methods inside classes
327
- const escaped = symbolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
328
- const methodMatch = trimmed.match(
329
- new RegExp(`\\b${escaped}\\s*[<(]`),
330
- );
331
- if (
332
- methodMatch &&
333
- !DECLARATION_REGEX.test(trimmed) &&
334
- !IMPORT_REGEX.test(trimmed) &&
335
- (trimmed.includes("(") || trimmed.includes("<"))
336
- ) {
337
- if (isMethodOrConstructor(trimmed) || isTopLevelFunction(trimmed)) {
338
- const start = findDocStart(lines, i);
339
- const end = findBlockEnd(lines, i);
340
- return { start, end };
341
- }
342
- }
343
- }
344
-
345
- return null;
346
- }
347
-
348
- /**
349
- * Walk backward from a declaration to find leading doc comments/annotations.
350
- */
351
- function findDocStart(lines: string[], declLine: number): number {
352
- let start = declLine;
353
- for (let k = declLine - 1; k >= 0; k--) {
354
- const t = lines[k].trim();
355
- if (t.startsWith("///") || t.startsWith("@") || t === "") {
356
- if (t !== "") start = k;
357
- } else {
358
- break;
359
- }
360
- }
361
- return start;
362
- }
363
-
364
- /**
365
- * Find the end of a block (matching closing brace).
366
- */
367
- function findBlockEnd(lines: string[], startIdx: number): number {
368
- const line = lines[startIdx].trim();
369
-
370
- // Single-line declaration (no body)
371
- if (line.endsWith(";") && !line.includes("{")) {
372
- return startIdx;
373
- }
374
-
375
- // Find opening brace
376
- let braceLineIdx = startIdx;
377
- while (
378
- braceLineIdx < lines.length &&
379
- !lines[braceLineIdx].includes("{")
380
- ) {
381
- braceLineIdx++;
382
- }
383
-
384
- if (braceLineIdx >= lines.length) return lines.length - 1;
385
-
386
- const result = findMatchingBraceStateAware(lines, braceLineIdx);
387
- return result !== -1 ? result : lines.length - 1;
388
- }
389
-
390
- /**
391
- * Advanced brace matcher that ignores strings and comments.
392
- * Returns the line index of the matching closing brace.
393
- * Starts counting from the FIRST brace that appears after a closing parenthesis
394
- * to avoid counting braces in method parameters (e.g., {int? cart}).
395
- */
396
- function findMatchingBraceStateAware(lines: string[], startLine: number): number {
397
- let depth = 0;
398
- let inString = false;
399
- let stringDelim = "";
400
- let inTripleQuote = false;
401
- let inComment = false;
402
- let inMultiLineComment = false;
403
- let foundMethodBodyBrace = false;
404
- let lastCloseParen = -1;
405
-
406
- for (let i = startLine; i < lines.length; i++) {
407
- const line = lines[i];
408
- for (let j = 0; j < line.length; j++) {
409
- const char = line[j];
410
- const prev = j > 0 ? line[j - 1] : "";
411
- const next = j < line.length - 1 ? line[j + 1] : "";
412
-
413
- // Handle Escape
414
- if (prev === "\\" && (inString || inTripleQuote)) continue;
415
-
416
- // Handle Comments
417
- if (!inString && !inTripleQuote) {
418
- if (!inMultiLineComment && char === "/" && next === "/") {
419
- inComment = true; // Skip rest of line
420
- break;
421
- }
422
- if (!inMultiLineComment && char === "/" && next === "*") {
423
- inMultiLineComment = true;
424
- j++;
425
- continue;
426
- }
427
- if (inMultiLineComment && char === "*" && next === "/") {
428
- inMultiLineComment = false;
429
- j++;
430
- continue;
431
- }
432
- }
433
-
434
- if (inComment || inMultiLineComment) continue;
435
-
436
- // Handle Multi-line Triple Quotes (''' or """)
437
- if (!inString && !inMultiLineComment) {
438
- const isTriple = line.slice(j, j + 3) === "'''" || line.slice(j, j + 3) === '"""';
439
- if (isTriple) {
440
- if (!inTripleQuote) {
441
- inTripleQuote = true;
442
- stringDelim = line.slice(j, j + 3);
443
- j += 2;
444
- continue;
445
- } else if (line.slice(j, j + 3) === stringDelim) {
446
- inTripleQuote = false;
447
- stringDelim = "";
448
- j += 2;
449
- continue;
450
- }
451
- }
452
- }
453
-
454
- if (inTripleQuote) continue;
455
-
456
- // Handle Basic Strings (' or ")
457
- if (!inMultiLineComment && (char === "'" || char === '"')) {
458
- if (!inString) {
459
- inString = true;
460
- stringDelim = char;
461
- } else if (char === stringDelim) {
462
- inString = false;
463
- stringDelim = "";
464
- }
465
- continue;
466
- }
467
-
468
- if (inString) continue;
469
-
470
- // Track closing parenthesis to find where method signature ends
471
- if (char === ")") {
472
- lastCloseParen = j;
473
- }
474
-
475
- // Real Braces
476
- if (char === "{") {
477
- // Only start counting after we've seen a closing paren (method signature end)
478
- if (!foundMethodBodyBrace && lastCloseParen !== -1) {
479
- foundMethodBodyBrace = true;
480
- depth = 1; // This is the opening brace of the method body
481
- } else if (foundMethodBodyBrace) {
482
- depth++;
483
- }
484
- // Ignore braces before the method body (e.g., in parameters)
485
- }
486
- else if (char === "}") {
487
- if (foundMethodBodyBrace) {
488
- depth--;
489
- if (depth === 0) return i;
490
- }
491
- }
492
- }
493
- inComment = false; // Reset line comment
494
- }
495
-
496
- return -1;
497
- }
498
-
499
- // ─── Utility Functions ──────────────────────────────────────────────
500
-
501
- function isTopLevelFunction(trimmed: string): boolean {
502
- // Matches: void foo(), Future<T> bar(), static Type baz()
503
- // But NOT: class/mixin/extension/enum declarations
504
- if (DECLARATION_REGEX.test(trimmed)) return false;
505
- if (IMPORT_REGEX.test(trimmed)) return false;
506
- if (trimmed.startsWith("//") || trimmed.startsWith("///")) return false;
507
- if (trimmed.startsWith("@")) return false;
508
-
509
- // Look for pattern: [returnType] functionName( or functionName<
510
- return /^(?:(?:static|final|const|async|Future|Stream|void|int|double|String|bool|List|Map|Set|dynamic|var)\s+)*\w+\s*[<(]/.test(
511
- trimmed,
512
- );
513
- }
514
-
515
- function extractFunctionName(trimmed: string): string {
516
- // Remove return type and modifiers, extract the name before ( or <
517
- const match = trimmed.match(/(\w+)\s*[<(]/);
518
- return match ? match[1] : "";
519
- }
520
-
521
- function isMethodOrConstructor(trimmed: string): boolean {
522
- // Similar to top-level function but may be indented
523
- if (DECLARATION_REGEX.test(trimmed)) return false;
524
- if (trimmed.startsWith("//") || trimmed.startsWith("///")) return false;
525
- if (trimmed.startsWith("@")) return false;
526
-
527
- return /^(?:(?:static|final|const|async|override|abstract|Future|Stream|void|int|double|String|bool|List|Map|Set|dynamic|var)\s+)*\w+\s*[<(]/.test(
528
- trimmed,
529
- );
530
- }
531
-
532
- function getIndent(line: string): string {
533
- const match = line.match(/^(\s*)/);
534
- return match ? match[1] : "";
535
- }
536
-
537
- function reindentCode(code: string, targetIndent: string): string {
538
- // Normalize CRLF to LF
539
- code = code.replace(/\r\n/g, "\n");
540
-
541
- const lines = code.split("\n");
542
- if (lines.length === 0) return code;
543
-
544
- let minIndent = Infinity;
545
- for (const line of lines) {
546
- if (line.trim() === "") continue;
547
- const indent = line.match(/^(\s*)/)?.[1]?.length ?? 0;
548
- if (indent < minIndent) minIndent = indent;
549
- }
550
-
551
- if (minIndent === Infinity) minIndent = 0;
552
-
553
- return lines
554
- .map((line) => {
555
- if (line.trim() === "") return "";
556
- return targetIndent + line.slice(minIndent);
557
- })
558
- .join("\n");
559
- }
560
-
561
- function findBraceLine(lines: string[], startIdx: number): number {
562
- for (let i = startIdx; i < lines.length; i++) {
563
- if (lines[i].includes("{")) return i;
564
- }
565
- return -1;
566
- }
567
-
568
- function hasBraceBalance(content: string): boolean {
569
- // Normalize CRLF to LF for consistent processing
570
- content = content.replace(/\r\n/g, "\n");
571
-
572
- const lines = content.split("\n");
573
- let depth = 0;
574
- let inString = false;
575
- let stringDelim = "";
576
- let inTripleQuote = false;
577
- let inComment = false;
578
- let inMultiLineComment = false;
579
-
580
- for (let i = 0; i < lines.length; i++) {
581
- const line = lines[i];
582
- for (let j = 0; j < line.length; j++) {
583
- const char = line[j];
584
- const prev = j > 0 ? line[j - 1] : "";
585
- const next = j < line.length - 1 ? line[j + 1] : "";
586
-
587
- if (prev === "\\" && (inString || inTripleQuote)) continue;
588
-
589
- if (!inString && !inTripleQuote) {
590
- if (!inMultiLineComment && char === "/" && next === "/") {
591
- inComment = true;
592
- break;
593
- }
594
- if (!inMultiLineComment && char === "/" && next === "*") {
595
- inMultiLineComment = true;
596
- j++;
597
- continue;
598
- }
599
- if (inMultiLineComment && char === "*" && next === "/") {
600
- inMultiLineComment = false;
601
- j++;
602
- continue;
603
- }
604
- }
605
-
606
- if (inComment || inMultiLineComment) continue;
607
-
608
- if (!inString && !inMultiLineComment) {
609
- const isTriple = line.slice(j, j + 3) === "'''" || line.slice(j, j + 3) === '"""';
610
- if (isTriple) {
611
- if (!inTripleQuote) {
612
- inTripleQuote = true;
613
- stringDelim = line.slice(j, j + 3);
614
- j += 2;
615
- continue;
616
- } else if (line.slice(j, j + 3) === stringDelim) {
617
- inTripleQuote = false;
618
- stringDelim = "";
619
- j += 2;
620
- continue;
621
- }
622
- }
623
- }
624
- if (inTripleQuote) continue;
625
-
626
- if (!inMultiLineComment && (char === "'" || char === '"')) {
627
- if (!inString) {
628
- inString = true;
629
- stringDelim = char;
630
- } else if (char === stringDelim) {
631
- inString = false;
632
- stringDelim = "";
633
- }
634
- continue;
635
- }
636
- if (inString) continue;
637
-
638
- if (char === "{") depth++;
639
- else if (char === "}") {
640
- depth--;
641
- if (depth < 0) return false;
642
- }
643
- }
644
- inComment = false;
645
- }
646
-
647
- return depth === 0;
648
- }