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,1013 +0,0 @@
1
- /**
2
- * dartCompressor — Regex + brace-counting Dart semantic compressor.
3
- *
4
- * Dart has no npm AST parser, so we use a high-fidelity regex engine combined
5
- * with precise brace-counting to extract structural signatures from Dart source.
6
- * This approach handles all real-world Flutter/Dart patterns including generics,
7
- * factory constructors, mixins, extensions, and enum members.
8
- *
9
- * Design principles:
10
- * 1. Brace-counting for block boundaries — never fails on nested blocks
11
- * 2. Regex for signature detection — generics-aware, handles multi-line
12
- * 3. Preserve doc comments (///) for semantic context
13
- * 4. Preserve annotations (@override, @visibleForTesting, etc.)
14
- * 5. Preserve field declarations (they ARE the structural contract)
15
- */
16
-
17
- // ─── Constants ──────────────────────────────────────────────────────
18
-
19
- /**
20
- * Regex patterns for Dart structural elements.
21
- * These MUST handle generics (e.g., `Map<String, List<int>>`) which
22
- * contain `>` characters that could confuse naive parsers.
23
- */
24
-
25
- // Top-level and class declarations
26
- const DECLARATION_REGEX =
27
- /^(\s*)((?:abstract\s+|sealed\s+|base\s+|final\s+|interface\s+)*)(class|mixin|extension(?:\s+type)?|enum)\s+(\w+)/;
28
-
29
- // Function/method declarations — handles generics, async, arrow syntax
30
- // Matches: `Type? name<T>(params) async => ...` or `Type? name<T>(params) { ... }`
31
- const METHOD_REGEX =
32
- /^(\s*)((?:static\s+|abstract\s+|external\s+|@\w+\s+)*)([\w<>,?\s[\]]+?\s+)?(\w+)\s*(<[^>]*>)?\s*\(([^)]*(?:\([^)]*\)[^)]*)*)\)\s*(async\*?|sync\*?)?\s*(=>\s*.+;|\{)/;
33
-
34
- // Constructor regex — `ClassName(`, `ClassName.named(`, `factory ClassName(`
35
- const CONSTRUCTOR_REGEX =
36
- /^(\s*)(const\s+|factory\s+)?((\w+)(\.\w+)?)\s*\(([^)]*(?:\([^)]*\)[^)]*)*)\)\s*(:\s*[^{;]+)?\s*([{;]|=>)/;
37
-
38
- // Field/property declaration
39
- const FIELD_REGEX =
40
- /^(\s*)((?:static\s+|late\s+|final\s+|const\s+|covariant\s+)*)([\w<>,?\s[\].]+?\s+)(\w+)\s*(=\s*[^;]+)?;/;
41
-
42
- // Getter/setter
43
- const GETTER_REGEX =
44
- /^(\s*)((?:static\s+)?)([\w<>,?\s[\].]*?\s+)?get\s+(\w+)\b/;
45
- const SETTER_REGEX =
46
- /^(\s*)((?:static\s+)?)set\s+(\w+)\s*\(([^)]*)\)\s*([{]|=>)/;
47
-
48
- // Import / export / part statements
49
- const IMPORT_REGEX = /^(import|export|part\s+of|part)\s+['"]([^'"]+)['"]/;
50
-
51
- // Typedef
52
- const TYPEDEF_REGEX = /^(\s*)typedef\s+(.+);/;
53
-
54
- // Enum case (inside an enum body)
55
- const ENUM_CASE_REGEX = /^(\s*)(\w+)(\([^)]*\))?\s*[,;]/;
56
-
57
- // ─── Public API ────────────────────────────────────────────────────
58
-
59
- /**
60
- * Compress a Dart file into its semantic skeleton.
61
- * Returns only structural signatures — no function/method bodies.
62
- */
63
- export function compressDart(content: string): string {
64
- const lines = content.split("\n");
65
- const result: string[] = [];
66
- let i = 0;
67
-
68
- // Phase 1: Collect imports
69
- const importBlock: string[] = [];
70
- for (const line of lines) {
71
- const t = line.trim();
72
- if (IMPORT_REGEX.test(t)) {
73
- importBlock.push(line);
74
- }
75
- }
76
- if (importBlock.length > 0) {
77
- result.push("// ── Imports ──");
78
- result.push(...importBlock);
79
- result.push("");
80
- }
81
-
82
- // Phase 2: Walk the file extracting structural signatures
83
- while (i < lines.length) {
84
- const line = lines[i];
85
- const trimmed = line.trim();
86
-
87
- // Skip blank lines
88
- if (trimmed === "") {
89
- i++;
90
- continue;
91
- }
92
-
93
- // Skip already-collected imports
94
- if (IMPORT_REGEX.test(trimmed)) {
95
- i++;
96
- continue;
97
- }
98
-
99
- // Skip single-line comments (not doc comments)
100
- if (trimmed.startsWith("//") && !trimmed.startsWith("///")) {
101
- i++;
102
- continue;
103
- }
104
-
105
- // Preserve doc comments (///) — collect them
106
- if (trimmed.startsWith("///")) {
107
- const docLines = collectDocComments(lines, i);
108
- result.push(...docLines.lines);
109
- i = docLines.nextIndex;
110
- continue;
111
- }
112
-
113
- // Preserve annotations (@override, @visibleForTesting, etc.)
114
- if (trimmed.startsWith("@") && !trimmed.startsWith("@override")) {
115
- result.push(line);
116
- i++;
117
- continue;
118
- }
119
- // @override — attach to next item, don't emit standalone
120
- if (trimmed === "@override") {
121
- result.push(line);
122
- i++;
123
- continue;
124
- }
125
-
126
- // Typedefs — pass through
127
- const typedefMatch = TYPEDEF_REGEX.exec(trimmed);
128
- if (typedefMatch) {
129
- result.push(line);
130
- i++;
131
- continue;
132
- }
133
-
134
- // Top-level constants and variables
135
- if (isTopLevelField(lines, i)) {
136
- const field = processFieldDeclaration(lines, i);
137
- result.push(...field.lines);
138
- i = field.nextIndex;
139
- continue;
140
- }
141
-
142
- // Class / Mixin / Extension / Enum declarations
143
- const declMatch = DECLARATION_REGEX.exec(trimmed);
144
- if (declMatch) {
145
- const classResult = processClassDeclaration(lines, i);
146
- result.push(...classResult.lines);
147
- i = classResult.nextIndex;
148
- continue;
149
- }
150
-
151
- // Top-level functions
152
- if (isTopLevelFunction(trimmed)) {
153
- const funcResult = processTopLevelFunction(lines, i);
154
- result.push(...funcResult.lines);
155
- i = funcResult.nextIndex;
156
- continue;
157
- }
158
-
159
- i++;
160
- }
161
-
162
- return result.join("\n");
163
- }
164
-
165
- /**
166
- * Extract the full source code of a named symbol from a Dart file.
167
- * Supports classes, mixins, extensions, enums, functions, and methods.
168
- * When className is provided, scopes method search to within that class only.
169
- * Returns null if the symbol is not found.
170
- */
171
- export function extractDartSymbol(
172
- content: string,
173
- symbolName: string,
174
- className?: string,
175
- ): string | null {
176
- const lines = content.split("\n");
177
-
178
- // If className is provided, scope the search to within that class
179
- if (className) {
180
- for (let i = 0; i < lines.length; i++) {
181
- const trimmed = lines[i].trim();
182
- const declMatch = DECLARATION_REGEX.exec(trimmed);
183
- if (declMatch && declMatch[4] === className) {
184
- // Found the class — determine body boundaries
185
- let braceLineIdx = i;
186
- while (braceLineIdx < lines.length && !lines[braceLineIdx].includes("{")) {
187
- braceLineIdx++;
188
- }
189
- const bodyEnd = findMatchingBrace(lines, braceLineIdx + 1, 1);
190
-
191
- // Search only within the class body for the method
192
- for (let j = braceLineIdx + 1; j < bodyEnd; j++) {
193
- const bodyTrimmed = lines[j].trim();
194
- const methodMatch = bodyTrimmed.match(
195
- new RegExp(`\\b${escapeRegex(symbolName)}\\s*[<(]`),
196
- );
197
- if (
198
- methodMatch &&
199
- !DECLARATION_REGEX.test(bodyTrimmed) &&
200
- !IMPORT_REGEX.test(bodyTrimmed) &&
201
- (bodyTrimmed.includes("(") || bodyTrimmed.includes("<"))
202
- ) {
203
- if (
204
- isMethodOrConstructor(bodyTrimmed) ||
205
- isTopLevelFunction(bodyTrimmed)
206
- ) {
207
- return extractFullBlock(lines, j);
208
- }
209
- }
210
- }
211
- return null; // Method not found within the specified class
212
- }
213
- }
214
- return null; // Class not found
215
- }
216
-
217
- // Original behavior when no className is provided
218
- for (let i = 0; i < lines.length; i++) {
219
- const trimmed = lines[i].trim();
220
-
221
- // Match class-level declarations (class, mixin, extension, enum)
222
- const declMatch = DECLARATION_REGEX.exec(trimmed);
223
- if (declMatch && declMatch[4] === symbolName) {
224
- return extractFullBlock(lines, i);
225
- }
226
-
227
- // Match top-level functions
228
- if (isTopLevelFunction(trimmed)) {
229
- const funcName = extractFunctionName(trimmed);
230
- if (funcName === symbolName) {
231
- return extractFullBlock(lines, i);
232
- }
233
- }
234
-
235
- // Match methods inside classes (search inside class bodies)
236
- // Look for `methodName(` pattern
237
- const methodMatch = trimmed.match(
238
- new RegExp(`\\b${escapeRegex(symbolName)}\\s*[<(]`),
239
- );
240
- if (
241
- methodMatch &&
242
- !DECLARATION_REGEX.test(trimmed) &&
243
- !IMPORT_REGEX.test(trimmed) &&
244
- (trimmed.includes("(") || trimmed.includes("<"))
245
- ) {
246
- // Verify this is actually a method/function definition
247
- if (
248
- isMethodOrConstructor(trimmed) ||
249
- isTopLevelFunction(trimmed)
250
- ) {
251
- return extractFullBlock(lines, i);
252
- }
253
- }
254
- }
255
-
256
- return null;
257
- }
258
-
259
- // ─── Class / Mixin / Extension / Enum Processing ───────────────────
260
-
261
- interface ProcessResult {
262
- lines: string[];
263
- nextIndex: number;
264
- }
265
-
266
- function processClassDeclaration(
267
- lines: string[],
268
- startIndex: number,
269
- ): ProcessResult {
270
- const result: string[] = [];
271
- let i = startIndex;
272
-
273
- // Collect leading doc comments that precede this declaration
274
- // (they may already have been emitted, so check if we need to go backwards)
275
-
276
- // Collect the full declaration header (may span multiple lines)
277
- let header = "";
278
- while (i < lines.length) {
279
- header += (header ? " " : "") + lines[i].trim();
280
- if (header.includes("{")) {
281
- break;
282
- }
283
- i++;
284
- }
285
-
286
- // Emit the header up to (and including) the opening brace
287
- const bracePos = header.indexOf("{");
288
- if (bracePos >= 0) {
289
- result.push(header.substring(0, bracePos + 1));
290
- } else {
291
- result.push(header);
292
- return { lines: result, nextIndex: i + 1 };
293
- }
294
-
295
- // Determine if this is an enum (special handling for cases)
296
- const isEnum = /\benum\s+/.test(header);
297
-
298
- // Find the matching closing brace
299
- i++;
300
- let braceDepth = 1;
301
- const bodyStart = i;
302
- const bodyEnd = findMatchingBrace(lines, i, braceDepth);
303
-
304
- // Process the body
305
- const bodyLines = lines.slice(bodyStart, bodyEnd);
306
- let j = 0;
307
-
308
- while (j < bodyLines.length) {
309
- const bodyLine = bodyLines[j];
310
- const bodyTrimmed = bodyLine.trim();
311
-
312
- // Skip blank lines
313
- if (bodyTrimmed === "") {
314
- j++;
315
- continue;
316
- }
317
-
318
- // Doc comments — preserve
319
- if (bodyTrimmed.startsWith("///")) {
320
- const docs = collectDocCommentsRelative(bodyLines, j);
321
- result.push(...docs.lines);
322
- j = docs.nextIndex;
323
- continue;
324
- }
325
-
326
- // Annotations — preserve
327
- if (bodyTrimmed.startsWith("@")) {
328
- result.push(bodyLine.replace(/\r$/, ""));
329
- j++;
330
- continue;
331
- }
332
-
333
- // Enum cases (before the semicolon or methods)
334
- if (isEnum && isEnumCase(bodyTrimmed)) {
335
- result.push(bodyLine.replace(/\r$/, ""));
336
- j++;
337
- continue;
338
- }
339
-
340
- // Enum semicolon separator (between cases and methods/fields)
341
- if (isEnum && bodyTrimmed === ";") {
342
- result.push(bodyLine.replace(/\r$/, ""));
343
- j++;
344
- continue;
345
- }
346
-
347
- // Field declarations
348
- if (isFieldDeclaration(bodyLines, j)) {
349
- const field = processFieldDeclaration(bodyLines, j);
350
- result.push(...field.lines);
351
- j = field.nextIndex;
352
- continue;
353
- }
354
-
355
- // Getter
356
- if (GETTER_REGEX.test(bodyTrimmed)) {
357
- const getter = processGetterSetter(bodyLines, j);
358
- result.push(...getter.lines);
359
- j = getter.nextIndex;
360
- continue;
361
- }
362
-
363
- // Setter
364
- if (bodyTrimmed.includes(" set ") && bodyTrimmed.includes("(")) {
365
- const setter = processGetterSetter(bodyLines, j);
366
- result.push(...setter.lines);
367
- j = setter.nextIndex;
368
- continue;
369
- }
370
-
371
- // Constructor or method
372
- if (isMethodOrConstructor(bodyTrimmed)) {
373
- const method = processMethodSignature(bodyLines, j);
374
- result.push(...method.lines);
375
- j = method.nextIndex;
376
- continue;
377
- }
378
-
379
- j++;
380
- }
381
-
382
- result.push("}");
383
- result.push("");
384
- return { lines: result, nextIndex: bodyEnd + 1 };
385
- }
386
-
387
- // ─── Top-level Functions ───────────────────────────────────────────
388
-
389
- function processTopLevelFunction(
390
- lines: string[],
391
- startIndex: number,
392
- ): ProcessResult {
393
- const result: string[] = [];
394
- let i = startIndex;
395
-
396
- // Collect the full signature
397
- let signature = "";
398
- while (i < lines.length) {
399
- signature += (signature ? " " : "") + lines[i].trim();
400
- // Stop when we find `{` or `=> ...;`
401
- if (findBlockBraceIndex(signature) >= 0 || (signature.includes("=>") && signature.endsWith(";"))) {
402
- break;
403
- }
404
- i++;
405
- }
406
-
407
- const indent = getIndent(lines[startIndex]);
408
-
409
- if (signature.includes("=>") && signature.endsWith(";")) {
410
- const arrowPos = signature.indexOf("=>");
411
- result.push(`${indent}${signature.substring(0, arrowPos).trim()} => /* ... */;`);
412
- } else if (findBlockBraceIndex(signature) >= 0) {
413
- const bracePos = findBlockBraceIndex(signature);
414
- result.push(`${indent}${signature.substring(0, bracePos).trim()} { /* ... */ }`);
415
- // Skip the body
416
- i = findMatchingBrace(lines, i + 1, 1);
417
- }
418
-
419
- result.push("");
420
- return { lines: result, nextIndex: i + 1 };
421
- }
422
-
423
- // ─── Method/Constructor Signature Processing ───────────────────────
424
-
425
- function processMethodSignature(
426
- bodyLines: string[],
427
- startIndex: number,
428
- ): ProcessResult {
429
- const result: string[] = [];
430
- let j = startIndex;
431
-
432
- // Collect the full signature (may span multiple lines until { or ; or =>)
433
- let signature = "";
434
- let foundEnd = false;
435
-
436
- while (j < bodyLines.length) {
437
- const lineContent = bodyLines[j].trim();
438
- signature += (signature ? " " : "") + lineContent;
439
-
440
- // Check if we have the complete signature
441
- if (findBlockBraceIndex(signature) >= 0) {
442
- foundEnd = true;
443
- break;
444
- }
445
- if (signature.includes("=>")) {
446
- // Arrow body — find the semicolon
447
- if (signature.endsWith(";")) {
448
- foundEnd = true;
449
- break;
450
- }
451
- // Multi-line arrow — keep collecting until ;
452
- j++;
453
- while (j < bodyLines.length) {
454
- const arrowLine = bodyLines[j].trim();
455
- signature += " " + arrowLine;
456
- if (arrowLine.endsWith(";")) {
457
- break;
458
- }
459
- j++;
460
- }
461
- foundEnd = true;
462
- break;
463
- }
464
- if (lineContent.endsWith(";")) {
465
- // Abstract method or similar
466
- foundEnd = true;
467
- break;
468
- }
469
- j++;
470
- }
471
-
472
- const indent = getIndent(bodyLines[startIndex]);
473
-
474
- if (!foundEnd) {
475
- result.push(bodyLines[startIndex].replace(/\r$/, ""));
476
- return { lines: result, nextIndex: j + 1 };
477
- }
478
-
479
- if (signature.includes("=>")) {
480
- const arrowPos = signature.indexOf("=>");
481
- result.push(`${indent}${signature.substring(0, arrowPos).trim()} => /* ... */;`);
482
- } else if (findBlockBraceIndex(signature) >= 0) {
483
- const bracePos = findBlockBraceIndex(signature);
484
- const sigPart = signature.substring(0, bracePos).trim();
485
-
486
- result.push(`${indent}${sigPart} { /* ... */ }`);
487
- j = findMatchingBraceRelative(bodyLines, j + 1, 1);
488
- } else if (signature.endsWith(";")) {
489
- result.push(`${indent}${signature}`);
490
- }
491
-
492
- return { lines: result, nextIndex: j + 1 };
493
- }
494
-
495
- function processFieldDeclaration(
496
- bodyLines: string[],
497
- startIndex: number,
498
- ): ProcessResult {
499
- const result: string[] = [];
500
- let j = startIndex;
501
- let signature = "";
502
- while (j < bodyLines.length) {
503
- signature += (signature ? " " : "") + bodyLines[j].trim();
504
- if (signature.endsWith(";")) break;
505
- j++;
506
- }
507
- const indent = getIndent(bodyLines[startIndex]);
508
- result.push(`${indent}${signature}`);
509
- return { lines: result, nextIndex: j + 1 };
510
- }
511
-
512
- // ─── Getter/Setter Processing ──────────────────────────────────────
513
-
514
- function processGetterSetter(
515
- bodyLines: string[],
516
- startIndex: number,
517
- ): ProcessResult {
518
- const result: string[] = [];
519
- let j = startIndex;
520
-
521
- let signature = "";
522
- while (j < bodyLines.length) {
523
- signature += (signature ? " " : "") + bodyLines[j].trim();
524
-
525
- if (signature.includes("=>")) {
526
- if (signature.endsWith(";")) break;
527
- j++;
528
- while (j < bodyLines.length) {
529
- signature += " " + bodyLines[j].trim();
530
- if (bodyLines[j].trim().endsWith(";")) break;
531
- j++;
532
- }
533
- break;
534
- }
535
- if (findBlockBraceIndex(signature) >= 0) break;
536
- if (signature.endsWith(";")) break;
537
- j++;
538
- }
539
-
540
- const indent = getIndent(bodyLines[startIndex]);
541
-
542
- if (signature.includes("=>")) {
543
- const arrowPos = signature.indexOf("=>");
544
- result.push(`${indent}${signature.substring(0, arrowPos).trim()} => /* ... */;`);
545
- } else if (findBlockBraceIndex(signature) >= 0) {
546
- const bracePos = findBlockBraceIndex(signature);
547
- result.push(`${indent}${signature.substring(0, bracePos).trim()} { /* ... */ }`);
548
- j = findMatchingBraceRelative(bodyLines, j + 1, 1);
549
- } else {
550
- result.push(`${indent}${signature}`);
551
- }
552
-
553
- return { lines: result, nextIndex: j + 1 };
554
- }
555
-
556
- // ─── Utility Functions ─────────────────────────────────────────────
557
-
558
- function collectDocComments(
559
- lines: string[],
560
- startIndex: number,
561
- ): ProcessResult {
562
- const result: string[] = [];
563
- let i = startIndex;
564
- while (i < lines.length && lines[i].trim().startsWith("///")) {
565
- result.push(lines[i].replace(/\r$/, ""));
566
- i++;
567
- }
568
- return { lines: result, nextIndex: i };
569
- }
570
-
571
- function collectDocCommentsRelative(
572
- lines: string[],
573
- startIndex: number,
574
- ): ProcessResult {
575
- return collectDocComments(lines, startIndex);
576
- }
577
-
578
- function isTopLevelField(lines: string[], startIndex: number): boolean {
579
- const line = lines[startIndex];
580
- const trimmed = line.trim();
581
- // Must be at indentation 0
582
- if (line.length > 0 && (line[0] === " " || line[0] === "\t")) return false;
583
-
584
- let signature = "";
585
- let j = startIndex;
586
- while (j < lines.length) {
587
- const t = lines[j].trim();
588
- if (t.startsWith("//") || t.startsWith("@")) break;
589
- signature += (signature ? " " : "") + t;
590
- if (signature.includes("{") || signature.includes("=>") || signature.includes("(")) return false;
591
- if (signature.endsWith(";")) break;
592
- j++;
593
- }
594
-
595
- if (!signature.endsWith(";")) return false;
596
-
597
- // Must look like a field: starts with const, final, var, type + name
598
- if (
599
- /^(const|final|var)\s+/.test(signature)
600
- ) {
601
- return true;
602
- }
603
- // Typed top-level: `String name = ...;` or `Type name;`
604
- if (
605
- /^[A-Z]\w*[<?\s]/.test(signature)
606
- ) {
607
- return true;
608
- }
609
- return false;
610
- }
611
-
612
- function isTopLevelFunction(trimmed: string): boolean {
613
- // Top-level function: async? returnType? functionName<T>(params) {
614
- // Exclude class/mixin/etc declarations
615
- if (DECLARATION_REGEX.test(trimmed)) return false;
616
- if (IMPORT_REGEX.test(trimmed)) return false;
617
- if (trimmed.startsWith("//")) return false;
618
- if (trimmed.startsWith("@")) return false;
619
-
620
- // Must contain `(` for parameter list
621
- if (!trimmed.includes("(")) return false;
622
-
623
- // Must not start with common non-function keywords
624
- if (/^(return|if|for|while|switch|try|catch|throw|assert|yield)\b/.test(trimmed)) {
625
- return false;
626
- }
627
-
628
- // Check common patterns:
629
- // `void main() {`
630
- // `Future<void> loadSections({bool forceRefresh = false}) async {`
631
- // `static Widget buildSection(Map<String, dynamic> section) {`
632
- if (
633
- /^([\w<>,?\s[\].]+?\s+)?(\w+)\s*(<[^>]*>)?\s*\(/.test(trimmed) &&
634
- (trimmed.includes("{") || trimmed.includes("=>") || trimmed.endsWith(")") || trimmed.includes(") async") || trimmed.includes(") sync"))
635
- ) {
636
- return true;
637
- }
638
-
639
- return false;
640
- }
641
-
642
- function extractFunctionName(trimmed: string): string {
643
- // Extract function name before the `(`
644
- const match = trimmed.match(/(\w+)\s*[<(]/);
645
- if (match) {
646
- // Filter out keywords that aren't actual function names
647
- const name = match[1];
648
- const keywords = new Set([
649
- "if", "for", "while", "switch", "try", "catch", "return",
650
- "class", "mixin", "enum", "extension", "import", "export",
651
- "void", "int", "double", "String", "bool", "List", "Map",
652
- "Set", "Future", "Stream", "const", "final", "var",
653
- "static", "abstract", "factory", "async", "await",
654
- ]);
655
- if (!keywords.has(name)) return name;
656
- }
657
- return "";
658
- }
659
-
660
- function isMethodOrConstructor(trimmed: string): boolean {
661
- if (trimmed.startsWith("//")) return false;
662
- if (trimmed.startsWith("@")) return false;
663
- if (!trimmed.includes("(")) return false;
664
-
665
- // Skip non-method statements
666
- if (/^(return|if|for|while|switch|try|catch|throw|assert|yield|super|this)\b/.test(trimmed)) {
667
- return false;
668
- }
669
- // Skip field declarations
670
- if (
671
- !trimmed.includes("(") ||
672
- (trimmed.endsWith(";") && !trimmed.includes(")") && !trimmed.includes("=>"))
673
- ) {
674
- return false;
675
- }
676
-
677
- // Factory constructors
678
- if (/^(const\s+|factory\s+)/.test(trimmed)) return true;
679
-
680
- // Regular methods/constructors: `Type? name(` or `name(`
681
- if (/^([\w<>,?\s[\].]*?\s*)?(\w+)(\.\w+)?\s*[<(]/.test(trimmed)) {
682
- return true;
683
- }
684
-
685
- return false;
686
- }
687
-
688
- function isFieldDeclaration(lines: string[], startIndex: number): boolean {
689
- const firstTrimmed = lines[startIndex].trim();
690
- if (firstTrimmed.startsWith("//")) return false;
691
- if (firstTrimmed.startsWith("@")) return false;
692
-
693
- let signature = "";
694
- let j = startIndex;
695
- while (j < lines.length) {
696
- const t = lines[j].trim();
697
- if (t.startsWith("//") || t.startsWith("@")) break;
698
- signature += (signature ? " " : "") + t;
699
- // Check for block braces OUTSIDE string literals
700
- // This prevents `'${...}'` Dart interpolation from being misidentified as block openers
701
- if (containsBlockBraceOutsideStrings(signature) || containsArrowOutsideStrings(signature)) {
702
- return false;
703
- }
704
- if (signature.endsWith(";")) break;
705
- j++;
706
- }
707
-
708
- // Field must end with a semicolon
709
- if (!signature.endsWith(";")) {
710
- return false;
711
- }
712
-
713
- // Contains `(` before `=` — probably a method or constructor, not a field
714
- // Exception: default values with constructors like `= SomeClass()`
715
- const parenIdx = signature.indexOf("(");
716
- const equalsIdx = signature.indexOf("=");
717
- if (parenIdx >= 0 && (equalsIdx < 0 || parenIdx < equalsIdx)) {
718
- return false;
719
- }
720
-
721
- // Looks like: modifiers type name [= value];
722
- return /^(static\s+|late\s+|final\s+|const\s+|covariant\s+)*([\w<>,?\s[\].]*?\s+)(\w+)/.test(signature);
723
- }
724
-
725
- function isEnumCase(trimmed: string): boolean {
726
- // Enum case: `caseName,` or `caseName;` or `caseName(args),`
727
- if (trimmed.startsWith("//") || trimmed.startsWith("@")) return false;
728
-
729
- // Must not look like a method or field with a type
730
- if (/^(static|final|const|late|void|int|double|String|bool|Future|Stream|List|Map|Set|var)\b/.test(trimmed)) {
731
- return false;
732
- }
733
-
734
- // Enum case pattern: identifier optionally followed by (args), then , or ;
735
- if (/^\w+(\([^)]*\))?\s*[,;]?\s*$/.test(trimmed)) {
736
- return true;
737
- }
738
-
739
- return false;
740
- }
741
-
742
- function findMatchingBrace(
743
- lines: string[],
744
- startIndex: number,
745
- initialDepth: number,
746
- ): number {
747
- let depth = initialDepth;
748
- let i = startIndex;
749
-
750
- while (i < lines.length && depth > 0) {
751
- const line = lines[i];
752
- for (const ch of line) {
753
- if (ch === "{") depth++;
754
- else if (ch === "}") {
755
- depth--;
756
- if (depth === 0) return i;
757
- }
758
- }
759
- i++;
760
- }
761
-
762
- return i;
763
- }
764
-
765
- function findMatchingBraceRelative(
766
- lines: string[],
767
- startIndex: number,
768
- initialDepth: number,
769
- ): number {
770
- return findMatchingBrace(lines, startIndex, initialDepth);
771
- }
772
-
773
- function extractFullBlock(
774
- lines: string[],
775
- startIndex: number,
776
- ): string {
777
- // Walk backwards to capture doc comments and annotations
778
- let actualStart = startIndex;
779
- for (let k = startIndex - 1; k >= 0; k--) {
780
- const t = lines[k].trim();
781
- if (
782
- t.startsWith("///") ||
783
- t.startsWith("@") ||
784
- t === ""
785
- ) {
786
- if (t !== "") actualStart = k;
787
- } else {
788
- break;
789
- }
790
- }
791
-
792
- // Determine block end
793
- const line = lines[startIndex].trim();
794
-
795
- // Check if this is a single-line declaration (no body)
796
- if (line.endsWith(";") && !line.includes("{")) {
797
- const block = lines.slice(actualStart, startIndex + 1);
798
- while (block.length > 0 && block[block.length - 1].trim() === "") {
799
- block.pop();
800
- }
801
- return block.map((l) => l.replace(/\r$/, "")).join("\n");
802
- }
803
-
804
- // Find the matching closing brace using a combined approach
805
- // We skip all braces until we find the real '{' that opens the block,
806
- // which is the first '{' encountered when parens are balanced.
807
- let parenDepth = 0;
808
- let blockDepth = 0;
809
- let startedCounting = false;
810
- let endIdx = startIndex;
811
- let inString = false;
812
- let stringDelim = "";
813
- let inTripleQuote = false;
814
- let inComment = false;
815
- let inMultiLineComment = false;
816
-
817
- for (let k = startIndex; k < lines.length; k++) {
818
- const lineStr = lines[k];
819
-
820
- if (!startedCounting && lineStr.includes("=>")) {
821
- let arrowEndIdx = k;
822
- while (arrowEndIdx < lines.length && !lines[arrowEndIdx].trim().endsWith(";")) {
823
- arrowEndIdx++;
824
- }
825
- const block = lines.slice(actualStart, arrowEndIdx + 1);
826
- return block.map((l) => l.replace(/\r$/, "")).join("\n");
827
- }
828
-
829
- for (let j = 0; j < lineStr.length; j++) {
830
- const ch = lineStr[j];
831
- const prev = j > 0 ? lineStr[j - 1] : "";
832
- const next = j < lineStr.length - 1 ? lineStr[j + 1] : "";
833
-
834
- if (prev === "\\" && (inString || inTripleQuote)) continue;
835
-
836
- if (!inString && !inTripleQuote) {
837
- if (!inMultiLineComment && ch === "/" && next === "/") {
838
- inComment = true;
839
- break;
840
- }
841
- if (!inMultiLineComment && ch === "/" && next === "*") {
842
- inMultiLineComment = true;
843
- j++;
844
- continue;
845
- }
846
- if (inMultiLineComment && ch === "*" && next === "/") {
847
- inMultiLineComment = false;
848
- j++;
849
- continue;
850
- }
851
- }
852
- if (inComment || inMultiLineComment) continue;
853
-
854
- if (!inString && !inMultiLineComment) {
855
- const isTriple = lineStr.slice(j, j + 3) === "'''" || lineStr.slice(j, j + 3) === '"""';
856
- if (isTriple) {
857
- if (!inTripleQuote) {
858
- inTripleQuote = true;
859
- stringDelim = lineStr.slice(j, j + 3);
860
- j += 2;
861
- continue;
862
- } else if (lineStr.slice(j, j + 3) === stringDelim) {
863
- inTripleQuote = false;
864
- stringDelim = "";
865
- j += 2;
866
- continue;
867
- }
868
- }
869
- }
870
- if (inTripleQuote) continue;
871
-
872
- if (!inMultiLineComment && (ch === "'" || ch === '"')) {
873
- if (!inString) {
874
- inString = true;
875
- stringDelim = ch;
876
- } else if (ch === stringDelim) {
877
- inString = false;
878
- stringDelim = "";
879
- }
880
- continue;
881
- }
882
- if (inString) continue;
883
-
884
- if (!startedCounting) {
885
- if (ch === "(") parenDepth++;
886
- else if (ch === ")") parenDepth--;
887
- else if (ch === "{" && parenDepth === 0) {
888
- startedCounting = true;
889
- blockDepth = 1;
890
- }
891
- } else {
892
- if (ch === "{") {
893
- blockDepth++;
894
- } else if (ch === "}") {
895
- blockDepth--;
896
- if (blockDepth === 0) {
897
- endIdx = k;
898
- break;
899
- }
900
- }
901
- }
902
- }
903
- inComment = false;
904
- if (startedCounting && blockDepth === 0) break;
905
- }
906
-
907
- // If we never started counting, it's probably an abstract method or just a field
908
- if (!startedCounting) {
909
- return lines.slice(actualStart, startIndex + 1).map((l) => l.replace(/\r$/, "")).join("\n");
910
- }
911
-
912
- const block = lines.slice(actualStart, endIdx + 1);
913
- while (block.length > 0 && block[block.length - 1].trim() === "") {
914
- block.pop();
915
- }
916
- return block.map((l) => l.replace(/\r$/, "")).join("\n");
917
- }
918
-
919
- function getIndent(line: string): string {
920
- const match = line.match(/^(\s*)/);
921
- return match ? match[1] : "";
922
- }
923
-
924
- function escapeRegex(str: string): string {
925
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
926
- }
927
-
928
- function findBlockBraceIndex(signature: string): number {
929
- let parenDepth = 0;
930
- for (let i = 0; i < signature.length; i++) {
931
- const ch = signature[i];
932
- if (ch === "(") parenDepth++;
933
- else if (ch === ")") parenDepth--;
934
- else if (ch === "{" && parenDepth === 0) {
935
- return i;
936
- }
937
- }
938
- return -1;
939
- }
940
-
941
- /**
942
- * Check if a signature contains `{` outside of string literals.
943
- * This is critical for Dart where string interpolation `'${expr}'`
944
- * contains braces that are NOT block openers.
945
- */
946
- function containsBlockBraceOutsideStrings(signature: string): boolean {
947
- let inSingleQuote = false;
948
- let inDoubleQuote = false;
949
- let parenDepth = 0;
950
-
951
- for (let i = 0; i < signature.length; i++) {
952
- const ch = signature[i];
953
-
954
- // Handle escape sequences
955
- if ((inSingleQuote || inDoubleQuote) && ch === "\\" && i + 1 < signature.length) {
956
- i++; // skip escaped character
957
- continue;
958
- }
959
-
960
- // Toggle string state
961
- if (ch === "'" && !inDoubleQuote) {
962
- inSingleQuote = !inSingleQuote;
963
- continue;
964
- }
965
- if (ch === '"' && !inSingleQuote) {
966
- inDoubleQuote = !inDoubleQuote;
967
- continue;
968
- }
969
-
970
- // Skip characters inside strings
971
- if (inSingleQuote || inDoubleQuote) continue;
972
-
973
- if (ch === "(") parenDepth++;
974
- else if (ch === ")") parenDepth--;
975
- else if (ch === "{" && parenDepth === 0) {
976
- return true;
977
- }
978
- }
979
- return false;
980
- }
981
-
982
- /**
983
- * Check if a signature contains `=>` outside of string literals.
984
- */
985
- function containsArrowOutsideStrings(signature: string): boolean {
986
- let inSingleQuote = false;
987
- let inDoubleQuote = false;
988
-
989
- for (let i = 0; i < signature.length; i++) {
990
- const ch = signature[i];
991
-
992
- if ((inSingleQuote || inDoubleQuote) && ch === "\\" && i + 1 < signature.length) {
993
- i++;
994
- continue;
995
- }
996
-
997
- if (ch === "'" && !inDoubleQuote) {
998
- inSingleQuote = !inSingleQuote;
999
- continue;
1000
- }
1001
- if (ch === '"' && !inSingleQuote) {
1002
- inDoubleQuote = !inDoubleQuote;
1003
- continue;
1004
- }
1005
-
1006
- if (inSingleQuote || inDoubleQuote) continue;
1007
-
1008
- if (ch === "=" && i + 1 < signature.length && signature[i + 1] === ">") {
1009
- return true;
1010
- }
1011
- }
1012
- return false;
1013
- }