mcp-code-context 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.io/RULES.md +12 -0
- package/CHANGELOG.md +88 -1
- package/README.md +27 -0
- package/RELEASE_NOTES_v2.3.0.md +228 -0
- package/RELEASE_NOTES_v2.3.1.md +110 -0
- package/dist/src/ast/writers/safeRename.d.ts +14 -0
- package/dist/src/ast/writers/safeRename.js +258 -0
- package/dist/src/ast/writers/safeRename.js.map +1 -0
- package/dist/src/ast/writers/symbolWriter.d.ts +2 -0
- package/dist/src/ast/writers/symbolWriter.js +9 -14
- package/dist/src/ast/writers/symbolWriter.js.map +1 -1
- package/dist/src/cache/astCache.d.ts +33 -0
- package/dist/src/cache/astCache.js +113 -0
- package/dist/src/cache/astCache.js.map +1 -0
- package/dist/src/handlers/readHandlers.d.ts +70 -0
- package/dist/src/handlers/readHandlers.js +431 -0
- package/dist/src/handlers/readHandlers.js.map +1 -0
- package/dist/src/handlers/utilHandlers.d.ts +31 -0
- package/dist/src/handlers/utilHandlers.js +69 -0
- package/dist/src/handlers/utilHandlers.js.map +1 -0
- package/dist/src/handlers/writeHandlers.d.ts +57 -0
- package/dist/src/handlers/writeHandlers.js +475 -0
- package/dist/src/handlers/writeHandlers.js.map +1 -0
- package/dist/src/index.d.ts +6 -143
- package/dist/src/index.js +77 -1091
- package/dist/src/index.js.map +1 -1
- package/dist/src/utils/arrayUtils.d.ts +12 -0
- package/dist/src/utils/arrayUtils.js +27 -0
- package/dist/src/utils/arrayUtils.js.map +1 -0
- package/dist/src/utils/constants.d.ts +46 -0
- package/dist/src/utils/constants.js +112 -0
- package/dist/src/utils/constants.js.map +1 -0
- package/dist/src/utils/ignoreManager.d.ts +8 -0
- package/dist/src/utils/ignoreManager.js +49 -0
- package/dist/src/utils/ignoreManager.js.map +1 -1
- package/dist/src/utils/normalization.d.ts +49 -0
- package/dist/src/utils/normalization.js +173 -0
- package/dist/src/utils/normalization.js.map +1 -0
- package/dist/src/utils/projectRoot.d.ts +12 -5
- package/dist/src/utils/projectRoot.js +22 -20
- package/dist/src/utils/projectRoot.js.map +1 -1
- package/dist/src/utils/secureValidation.d.ts +15 -0
- package/dist/src/utils/secureValidation.js +41 -0
- package/dist/src/utils/secureValidation.js.map +1 -0
- package/dist/src/utils/transactionManager.d.ts +45 -0
- package/dist/src/utils/transactionManager.js +102 -0
- package/dist/src/utils/transactionManager.js.map +1 -0
- package/dist/src/utils/validation.d.ts +87 -0
- package/dist/src/utils/validation.js +231 -0
- package/dist/src/utils/validation.js.map +1 -0
- package/docs/ARCHITECTURE.md +300 -0
- package/docs/architecture/ADR-001-modular-handlers.md +119 -0
- package/package.json +5 -5
- package/AGENT_INSTRUCTIONS.md +0 -135
- package/DEPLOYMENT_INSTRUCTIONS.md +0 -110
- package/MEJORAS_PROPUESTAS.md +0 -324
- package/dist/src/tests/test-confirmation-flow.d.ts +0 -1
- package/dist/src/tests/test-confirmation-flow.js +0 -43
- package/dist/src/tests/test-confirmation-flow.js.map +0 -1
- package/dist/tests/debug-replace.d.ts +0 -1
- package/dist/tests/debug-replace.js +0 -17
- package/dist/tests/debug-replace.js.map +0 -1
- package/dist/tests/test-backup-system.d.ts +0 -7
- package/dist/tests/test-backup-system.js +0 -152
- package/dist/tests/test-backup-system.js.map +0 -1
- package/dist/tests/test-confirmation-flow.d.ts +0 -1
- package/dist/tests/test-confirmation-flow.js +0 -43
- package/dist/tests/test-confirmation-flow.js.map +0 -1
- package/dist/tests/test-dart.d.ts +0 -1
- package/dist/tests/test-dart.js +0 -242
- package/dist/tests/test-dart.js.map +0 -1
- package/dist/tests/test-new-tools.d.ts +0 -4
- package/dist/tests/test-new-tools.js +0 -147
- package/dist/tests/test-new-tools.js.map +0 -1
- package/dist/tests/test-php-ast.d.ts +0 -1
- package/dist/tests/test-php-ast.js +0 -152
- package/dist/tests/test-php-ast.js.map +0 -1
- package/dist/tests/test-php.d.ts +0 -1
- package/dist/tests/test-php.js +0 -234
- package/dist/tests/test-php.js.map +0 -1
- package/dist/tests/test-smoke.d.ts +0 -5
- package/dist/tests/test-smoke.js +0 -83
- package/dist/tests/test-smoke.js.map +0 -1
- package/dist/tests/writers/test-crlf-handling.d.ts +0 -4
- package/dist/tests/writers/test-crlf-handling.js +0 -73
- package/dist/tests/writers/test-crlf-handling.js.map +0 -1
- package/dist/tests/writers/test-write-smoke.d.ts +0 -9
- package/dist/tests/writers/test-write-smoke.js +0 -346
- package/dist/tests/writers/test-write-smoke.js.map +0 -1
- package/src/ast/dartCompressor.ts +0 -1013
- package/src/ast/phpCompressor.ts +0 -814
- package/src/ast/semanticCompressor.ts +0 -808
- package/src/ast/writers/dartWriter.ts +0 -648
- package/src/ast/writers/phpWriter.ts +0 -508
- package/src/ast/writers/pyWriter.ts +0 -446
- package/src/ast/writers/symbolWriter.ts +0 -277
- package/src/ast/writers/tsWriter.ts +0 -418
- package/src/index.ts +0 -1784
- package/src/tools/readFileLines.ts +0 -104
- package/src/tools/searchCodePattern.ts +0 -129
- package/src/utils/backupManager.ts +0 -141
- package/src/utils/confirmationCache.ts +0 -61
- package/src/utils/diffEngine.ts +0 -223
- package/src/utils/fuzzyMatch.ts +0 -86
- package/src/utils/ignoreManager.ts +0 -144
- package/src/utils/projectRoot.ts +0 -44
- package/tests/test-backup-system.ts +0 -174
- package/tests/test-confirmation-flow.ts +0 -49
- package/tests/test-dart.d.ts +0 -1
- package/tests/test-dart.js +0 -209
- package/tests/test-dart.js.map +0 -1
- package/tests/test-dart.ts +0 -265
- package/tests/test-new-tools.ts +0 -165
- package/tests/test-php-ast.d.ts +0 -1
- package/tests/test-php-ast.js +0 -152
- package/tests/test-php-ast.js.map +0 -1
- package/tests/test-php-ast.ts +0 -155
- package/tests/test-php.d.ts +0 -1
- package/tests/test-php.js +0 -234
- package/tests/test-php.js.map +0 -1
- package/tests/test-php.ts +0 -261
- package/tests/test-smoke.d.ts +0 -5
- package/tests/test-smoke.js +0 -83
- package/tests/test-smoke.js.map +0 -1
- package/tests/test-smoke.ts +0 -93
- package/tests/writers/test-crlf-handling.ts +0 -100
- package/tests/writers/test-write-smoke.ts +0 -568
- 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
|
-
}
|