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.
- package/.io/RULES.md +12 -0
- package/CHANGELOG.md +114 -1
- package/README.md +28 -1
- 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/tools/searchCodePattern.d.ts +7 -2
- package/dist/src/tools/searchCodePattern.js +68 -4
- package/dist/src/tools/searchCodePattern.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/llms.txt +10 -1
- package/package.json +5 -5
- package/AGENT_INSTRUCTIONS.md +0 -135
- package/DEPLOYMENT_INSTRUCTIONS.md +0 -110
- package/MEJORAS_PROPUESTAS.md +0 -324
- package/RELEASE_NOTES_v2.1.3.md +0 -78
- package/RELEASE_NOTES_v2.2.0.md +0 -286
- 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,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
|
-
}
|