mcp-code-context 2.1.3
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/AGENT_INSTRUCTIONS.md +135 -0
- package/CHANGELOG.md +84 -0
- package/DEPLOYMENT_INSTRUCTIONS.md +110 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/RELEASE_NOTES_v2.1.3.md +78 -0
- package/TROUBLESHOOTING.md +38 -0
- package/dist/src/ast/dartCompressor.d.ts +27 -0
- package/dist/src/ast/dartCompressor.js +859 -0
- package/dist/src/ast/dartCompressor.js.map +1 -0
- package/dist/src/ast/phpCompressor.d.ts +24 -0
- package/dist/src/ast/phpCompressor.js +634 -0
- package/dist/src/ast/phpCompressor.js.map +1 -0
- package/dist/src/ast/semanticCompressor.d.ts +22 -0
- package/dist/src/ast/semanticCompressor.js +677 -0
- package/dist/src/ast/semanticCompressor.js.map +1 -0
- package/dist/src/ast/writers/dartWriter.d.ts +37 -0
- package/dist/src/ast/writers/dartWriter.js +544 -0
- package/dist/src/ast/writers/dartWriter.js.map +1 -0
- package/dist/src/ast/writers/phpWriter.d.ts +37 -0
- package/dist/src/ast/writers/phpWriter.js +411 -0
- package/dist/src/ast/writers/phpWriter.js.map +1 -0
- package/dist/src/ast/writers/pyWriter.d.ts +37 -0
- package/dist/src/ast/writers/pyWriter.js +355 -0
- package/dist/src/ast/writers/pyWriter.js.map +1 -0
- package/dist/src/ast/writers/symbolWriter.d.ts +62 -0
- package/dist/src/ast/writers/symbolWriter.js +179 -0
- package/dist/src/ast/writers/symbolWriter.js.map +1 -0
- package/dist/src/ast/writers/tsWriter.d.ts +38 -0
- package/dist/src/ast/writers/tsWriter.js +344 -0
- package/dist/src/ast/writers/tsWriter.js.map +1 -0
- package/dist/src/index.d.ts +117 -0
- package/dist/src/index.js +1289 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/tests/test-confirmation-flow.d.ts +1 -0
- package/dist/src/tests/test-confirmation-flow.js +43 -0
- package/dist/src/tests/test-confirmation-flow.js.map +1 -0
- package/dist/src/utils/backupManager.d.ts +24 -0
- package/dist/src/utils/backupManager.js +59 -0
- package/dist/src/utils/backupManager.js.map +1 -0
- package/dist/src/utils/confirmationCache.d.ts +16 -0
- package/dist/src/utils/confirmationCache.js +40 -0
- package/dist/src/utils/confirmationCache.js.map +1 -0
- package/dist/src/utils/diffEngine.d.ts +21 -0
- package/dist/src/utils/diffEngine.js +163 -0
- package/dist/src/utils/diffEngine.js.map +1 -0
- package/dist/src/utils/fuzzyMatch.d.ts +8 -0
- package/dist/src/utils/fuzzyMatch.js +69 -0
- package/dist/src/utils/fuzzyMatch.js.map +1 -0
- package/dist/src/utils/ignoreManager.d.ts +22 -0
- package/dist/src/utils/ignoreManager.js +135 -0
- package/dist/src/utils/ignoreManager.js.map +1 -0
- package/dist/tests/debug-replace.d.ts +1 -0
- package/dist/tests/debug-replace.js +17 -0
- package/dist/tests/debug-replace.js.map +1 -0
- package/dist/tests/test-confirmation-flow.d.ts +1 -0
- package/dist/tests/test-confirmation-flow.js +43 -0
- package/dist/tests/test-confirmation-flow.js.map +1 -0
- package/dist/tests/test-dart.d.ts +1 -0
- package/dist/tests/test-dart.js +242 -0
- package/dist/tests/test-dart.js.map +1 -0
- package/dist/tests/test-php-ast.d.ts +1 -0
- package/dist/tests/test-php-ast.js +152 -0
- package/dist/tests/test-php-ast.js.map +1 -0
- package/dist/tests/test-php.d.ts +1 -0
- package/dist/tests/test-php.js +234 -0
- package/dist/tests/test-php.js.map +1 -0
- package/dist/tests/test-smoke.d.ts +5 -0
- package/dist/tests/test-smoke.js +83 -0
- package/dist/tests/test-smoke.js.map +1 -0
- package/dist/tests/writers/test-crlf-handling.d.ts +4 -0
- package/dist/tests/writers/test-crlf-handling.js +73 -0
- package/dist/tests/writers/test-crlf-handling.js.map +1 -0
- package/dist/tests/writers/test-write-smoke.d.ts +9 -0
- package/dist/tests/writers/test-write-smoke.js +346 -0
- package/dist/tests/writers/test-write-smoke.js.map +1 -0
- package/llms.txt +164 -0
- package/package.json +57 -0
- package/src/ast/dartCompressor.ts +1013 -0
- package/src/ast/phpCompressor.ts +814 -0
- package/src/ast/semanticCompressor.ts +808 -0
- package/src/ast/writers/dartWriter.ts +648 -0
- package/src/ast/writers/phpWriter.ts +508 -0
- package/src/ast/writers/pyWriter.ts +446 -0
- package/src/ast/writers/symbolWriter.ts +277 -0
- package/src/ast/writers/tsWriter.ts +418 -0
- package/src/index.ts +1553 -0
- package/src/utils/backupManager.ts +66 -0
- package/src/utils/confirmationCache.ts +61 -0
- package/src/utils/diffEngine.ts +223 -0
- package/src/utils/fuzzyMatch.ts +86 -0
- package/src/utils/ignoreManager.ts +144 -0
- package/tests/test-confirmation-flow.ts +49 -0
- package/tests/test-dart.d.ts +1 -0
- package/tests/test-dart.js +209 -0
- package/tests/test-dart.js.map +1 -0
- package/tests/test-dart.ts +265 -0
- package/tests/test-php-ast.d.ts +1 -0
- package/tests/test-php-ast.js +152 -0
- package/tests/test-php-ast.js.map +1 -0
- package/tests/test-php-ast.ts +155 -0
- package/tests/test-php.d.ts +1 -0
- package/tests/test-php.js +234 -0
- package/tests/test-php.js.map +1 -0
- package/tests/test-php.ts +261 -0
- package/tests/test-smoke.d.ts +5 -0
- package/tests/test-smoke.js +83 -0
- package/tests/test-smoke.js.map +1 -0
- package/tests/test-smoke.ts +93 -0
- package/tests/writers/test-crlf-handling.ts +100 -0
- package/tests/writers/test-write-smoke.ts +568 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tsWriter.ts — TypeScript / JavaScript Surgical Writer
|
|
3
|
+
*
|
|
4
|
+
* Uses ts-morph to perform AST-aware code modifications on TypeScript
|
|
5
|
+
* and JavaScript files. This is the most robust writer because ts-morph
|
|
6
|
+
* provides a full read-write AST with source manipulation capabilities.
|
|
7
|
+
*
|
|
8
|
+
* Supports: .ts, .tsx, .js, .jsx, .mts, .mjs, .cts, .cjs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
Project,
|
|
13
|
+
type SourceFile,
|
|
14
|
+
type ClassDeclaration,
|
|
15
|
+
SyntaxKind,
|
|
16
|
+
IndentationText,
|
|
17
|
+
} from "ts-morph";
|
|
18
|
+
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
|
|
21
|
+
// ─── Types ──────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export interface WriteResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
newContent: string;
|
|
26
|
+
symbolsAffected: string[];
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ─── Singleton Project ──────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
let _project: Project | null = null;
|
|
33
|
+
|
|
34
|
+
function getProject(): Project {
|
|
35
|
+
if (!_project) {
|
|
36
|
+
_project = new Project({
|
|
37
|
+
compilerOptions: {
|
|
38
|
+
allowJs: true,
|
|
39
|
+
jsx: 2, // React
|
|
40
|
+
},
|
|
41
|
+
useInMemoryFileSystem: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return _project;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createSourceFile(content: string, filePath: string): SourceFile {
|
|
48
|
+
const project = getProject();
|
|
49
|
+
const ext = path.extname(filePath);
|
|
50
|
+
const tempName = `write_${Date.now()}_${Math.random().toString(36).slice(2)}${ext}`;
|
|
51
|
+
return project.createSourceFile(tempName, content, { overwrite: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cleanupSourceFile(sf: SourceFile): void {
|
|
55
|
+
try {
|
|
56
|
+
sf.getProject().removeSourceFile(sf);
|
|
57
|
+
} catch {
|
|
58
|
+
// Ignore cleanup errors
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Replace Symbol ─────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Replace the full text of a named symbol in a TypeScript/JavaScript file.
|
|
66
|
+
* The newContent should be the complete replacement (signature + body).
|
|
67
|
+
* When className is provided, scopes the search to within that class only.
|
|
68
|
+
*/
|
|
69
|
+
export function replaceTSSymbol(
|
|
70
|
+
content: string,
|
|
71
|
+
symbolName: string,
|
|
72
|
+
newContent: string,
|
|
73
|
+
filePath: string,
|
|
74
|
+
className?: string,
|
|
75
|
+
): WriteResult {
|
|
76
|
+
const sf = createSourceFile(content, filePath);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const node = findNode(sf, symbolName, className);
|
|
80
|
+
if (!node) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
newContent: content,
|
|
84
|
+
symbolsAffected: [],
|
|
85
|
+
error: className
|
|
86
|
+
? `Symbol "${symbolName}" not found in class "${className}"`
|
|
87
|
+
: `Symbol "${symbolName}" not found in file`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Replace the node's full text with the new content
|
|
92
|
+
node.replaceWithText(newContent.trim());
|
|
93
|
+
|
|
94
|
+
const result = sf.getFullText();
|
|
95
|
+
|
|
96
|
+
// Validate that the result is parseable
|
|
97
|
+
const validationSf = createSourceFile(result, filePath);
|
|
98
|
+
const diagnostics = validationSf.getPreEmitDiagnostics();
|
|
99
|
+
const hasSyntaxErrors = diagnostics.some(
|
|
100
|
+
(d) => d.getCategory() === 1, // DiagnosticCategory.Error
|
|
101
|
+
);
|
|
102
|
+
cleanupSourceFile(validationSf);
|
|
103
|
+
|
|
104
|
+
if (hasSyntaxErrors) {
|
|
105
|
+
// Still return the result but mark potential syntax issues
|
|
106
|
+
// We don't reject because some "errors" are just type errors, not syntax
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
success: true,
|
|
111
|
+
newContent: result,
|
|
112
|
+
symbolsAffected: [symbolName],
|
|
113
|
+
};
|
|
114
|
+
} catch (error: unknown) {
|
|
115
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
newContent: content,
|
|
119
|
+
symbolsAffected: [],
|
|
120
|
+
error: `Failed to replace symbol "${symbolName}": ${msg}`,
|
|
121
|
+
};
|
|
122
|
+
} finally {
|
|
123
|
+
cleanupSourceFile(sf);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── Insert Code ────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
export type InsertPosition = "before" | "after" | "inside_start" | "inside_end";
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Insert new code at a precise location relative to an anchor symbol.
|
|
133
|
+
* When className is provided, scopes the anchor search to within that class only.
|
|
134
|
+
*/
|
|
135
|
+
export function insertTSCode(
|
|
136
|
+
content: string,
|
|
137
|
+
code: string,
|
|
138
|
+
anchorSymbol: string | null,
|
|
139
|
+
position: InsertPosition,
|
|
140
|
+
filePath: string,
|
|
141
|
+
className?: string,
|
|
142
|
+
): WriteResult {
|
|
143
|
+
const sf = createSourceFile(content, filePath);
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
if (!anchorSymbol) {
|
|
147
|
+
// No anchor — append at end of file
|
|
148
|
+
sf.addStatements(code.trim());
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
newContent: sf.getFullText(),
|
|
152
|
+
symbolsAffected: [],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const node = findNode(sf, anchorSymbol, className);
|
|
157
|
+
if (!node) {
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
newContent: content,
|
|
161
|
+
symbolsAffected: [],
|
|
162
|
+
error: className
|
|
163
|
+
? `Anchor symbol "${anchorSymbol}" not found in class "${className}"`
|
|
164
|
+
: `Anchor symbol "${anchorSymbol}" not found`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const trimmedCode = code.trim();
|
|
169
|
+
|
|
170
|
+
switch (position) {
|
|
171
|
+
case "before": {
|
|
172
|
+
const startPos = node.getStart();
|
|
173
|
+
sf.insertText(startPos, trimmedCode + "\n\n");
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case "after": {
|
|
177
|
+
const endPos = node.getEnd();
|
|
178
|
+
sf.insertText(endPos, "\n\n" + trimmedCode);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case "inside_start": {
|
|
182
|
+
// Only valid for classes/interfaces
|
|
183
|
+
const cls =
|
|
184
|
+
sf.getClass(anchorSymbol) || sf.getInterface(anchorSymbol);
|
|
185
|
+
if (!cls) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
newContent: content,
|
|
189
|
+
symbolsAffected: [],
|
|
190
|
+
error: `"inside_start" requires a class or interface. "${anchorSymbol}" is not one.`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if ("insertMember" in cls) {
|
|
194
|
+
(cls as ClassDeclaration).insertMember(0, trimmedCode);
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "inside_end": {
|
|
199
|
+
const cls2 =
|
|
200
|
+
sf.getClass(anchorSymbol) || sf.getInterface(anchorSymbol);
|
|
201
|
+
if (!cls2) {
|
|
202
|
+
return {
|
|
203
|
+
success: false,
|
|
204
|
+
newContent: content,
|
|
205
|
+
symbolsAffected: [],
|
|
206
|
+
error: `"inside_end" requires a class or interface. "${anchorSymbol}" is not one.`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if ("addMember" in cls2) {
|
|
210
|
+
(cls2 as ClassDeclaration).addMember(trimmedCode);
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
success: true,
|
|
218
|
+
newContent: sf.getFullText(),
|
|
219
|
+
symbolsAffected: [anchorSymbol],
|
|
220
|
+
};
|
|
221
|
+
} catch (error: unknown) {
|
|
222
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
223
|
+
return {
|
|
224
|
+
success: false,
|
|
225
|
+
newContent: content,
|
|
226
|
+
symbolsAffected: [],
|
|
227
|
+
error: `Failed to insert code: ${msg}`,
|
|
228
|
+
};
|
|
229
|
+
} finally {
|
|
230
|
+
cleanupSourceFile(sf);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ─── Rename Symbol ──────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Rename all occurrences of a symbol within a single TypeScript/JavaScript file.
|
|
238
|
+
* This performs a smart rename that understands scoping.
|
|
239
|
+
*/
|
|
240
|
+
export function renameTSSymbol(
|
|
241
|
+
content: string,
|
|
242
|
+
oldName: string,
|
|
243
|
+
newName: string,
|
|
244
|
+
filePath: string,
|
|
245
|
+
): WriteResult {
|
|
246
|
+
const sf = createSourceFile(content, filePath);
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const node = findNode(sf, oldName);
|
|
250
|
+
if (!node) {
|
|
251
|
+
return {
|
|
252
|
+
success: false,
|
|
253
|
+
newContent: content,
|
|
254
|
+
symbolsAffected: [],
|
|
255
|
+
error: `Symbol "${oldName}" not found for renaming`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Use regex-based replacement scoped to identifiers
|
|
260
|
+
// ts-morph rename in single-file mode: find all references within the file
|
|
261
|
+
const fullText = sf.getFullText();
|
|
262
|
+
|
|
263
|
+
// Smart rename: replace whole-word occurrences of oldName with newName
|
|
264
|
+
// This is not a naive replace — it respects word boundaries
|
|
265
|
+
const escaped = oldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
266
|
+
const regex = new RegExp(`\\b${escaped}\\b`, "g");
|
|
267
|
+
const newText = fullText.replace(regex, newName);
|
|
268
|
+
|
|
269
|
+
// Validate result is parseable
|
|
270
|
+
const validationSf = createSourceFile(newText, filePath);
|
|
271
|
+
cleanupSourceFile(validationSf);
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
success: true,
|
|
275
|
+
newContent: newText,
|
|
276
|
+
symbolsAffected: [oldName],
|
|
277
|
+
};
|
|
278
|
+
} catch (error: unknown) {
|
|
279
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
newContent: content,
|
|
283
|
+
symbolsAffected: [],
|
|
284
|
+
error: `Failed to rename "${oldName}" to "${newName}": ${msg}`,
|
|
285
|
+
};
|
|
286
|
+
} finally {
|
|
287
|
+
cleanupSourceFile(sf);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ─── Remove Symbol ──────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Remove a named symbol from a TypeScript/JavaScript file.
|
|
295
|
+
* Returns the file content without the symbol.
|
|
296
|
+
* When className is provided, scopes the search to within that class only.
|
|
297
|
+
*/
|
|
298
|
+
export function removeTSSymbol(
|
|
299
|
+
content: string,
|
|
300
|
+
symbolName: string,
|
|
301
|
+
filePath: string,
|
|
302
|
+
className?: string,
|
|
303
|
+
): WriteResult {
|
|
304
|
+
const sf = createSourceFile(content, filePath);
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const node = findNode(sf, symbolName, className);
|
|
308
|
+
if (!node) {
|
|
309
|
+
return {
|
|
310
|
+
success: false,
|
|
311
|
+
newContent: content,
|
|
312
|
+
symbolsAffected: [],
|
|
313
|
+
error: className
|
|
314
|
+
? `Symbol "${symbolName}" not found in class "${className}" for removal`
|
|
315
|
+
: `Symbol "${symbolName}" not found for removal`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
node.remove();
|
|
320
|
+
|
|
321
|
+
// Clean up double blank lines left behind
|
|
322
|
+
let result = sf.getFullText();
|
|
323
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
success: true,
|
|
327
|
+
newContent: result,
|
|
328
|
+
symbolsAffected: [symbolName],
|
|
329
|
+
};
|
|
330
|
+
} catch (error: unknown) {
|
|
331
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
newContent: content,
|
|
335
|
+
symbolsAffected: [],
|
|
336
|
+
error: `Failed to remove symbol "${symbolName}": ${msg}`,
|
|
337
|
+
};
|
|
338
|
+
} finally {
|
|
339
|
+
cleanupSourceFile(sf);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ─── Node Finder ────────────────────────────────────────────────────
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Find a named declaration node in a source file.
|
|
347
|
+
* Searches: functions, classes, interfaces, type aliases, enums,
|
|
348
|
+
* class methods, getters, setters, and variable declarations.
|
|
349
|
+
* When className is provided, scopes method/property search to within that class only.
|
|
350
|
+
*/
|
|
351
|
+
function findNode(sf: SourceFile, symbolName: string, className?: string) {
|
|
352
|
+
// If className is provided, scope search to within that class
|
|
353
|
+
if (className) {
|
|
354
|
+
const cls = sf.getClass(className);
|
|
355
|
+
if (!cls) return null;
|
|
356
|
+
|
|
357
|
+
const method = cls.getMethod(symbolName);
|
|
358
|
+
if (method) return method;
|
|
359
|
+
|
|
360
|
+
const getter = cls.getGetAccessor(symbolName);
|
|
361
|
+
if (getter) return getter;
|
|
362
|
+
|
|
363
|
+
const setter = cls.getSetAccessor(symbolName);
|
|
364
|
+
if (setter) return setter;
|
|
365
|
+
|
|
366
|
+
const prop = cls.getProperty(symbolName);
|
|
367
|
+
if (prop) return prop;
|
|
368
|
+
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Top-level function
|
|
373
|
+
const func = sf.getFunction(symbolName);
|
|
374
|
+
if (func) return func;
|
|
375
|
+
|
|
376
|
+
// Class
|
|
377
|
+
const cls = sf.getClass(symbolName);
|
|
378
|
+
if (cls) return cls;
|
|
379
|
+
|
|
380
|
+
// Interface
|
|
381
|
+
const iface = sf.getInterface(symbolName);
|
|
382
|
+
if (iface) return iface;
|
|
383
|
+
|
|
384
|
+
// Type alias
|
|
385
|
+
const typeAlias = sf.getTypeAlias(symbolName);
|
|
386
|
+
if (typeAlias) return typeAlias;
|
|
387
|
+
|
|
388
|
+
// Enum
|
|
389
|
+
const enumDecl = sf.getEnum(symbolName);
|
|
390
|
+
if (enumDecl) return enumDecl;
|
|
391
|
+
|
|
392
|
+
// Methods inside classes
|
|
393
|
+
for (const c of sf.getClasses()) {
|
|
394
|
+
const method = c.getMethod(symbolName);
|
|
395
|
+
if (method) return method;
|
|
396
|
+
|
|
397
|
+
const getter = c.getGetAccessor(symbolName);
|
|
398
|
+
if (getter) return getter;
|
|
399
|
+
|
|
400
|
+
const setter = c.getSetAccessor(symbolName);
|
|
401
|
+
if (setter) return setter;
|
|
402
|
+
|
|
403
|
+
// Properties
|
|
404
|
+
const prop = c.getProperty(symbolName);
|
|
405
|
+
if (prop) return prop;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Variable declarations (const, let, var — including arrow functions)
|
|
409
|
+
for (const varStmt of sf.getVariableStatements()) {
|
|
410
|
+
for (const decl of varStmt.getDeclarations()) {
|
|
411
|
+
if (decl.getName() === symbolName) {
|
|
412
|
+
return varStmt;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return null;
|
|
418
|
+
}
|