@compilr-dev/agents-coding-ts 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +66 -0
- package/dist/parser/index.d.ts +7 -0
- package/dist/parser/index.js +6 -0
- package/dist/parser/typescript-parser.d.ts +22 -0
- package/dist/parser/typescript-parser.js +423 -0
- package/dist/skills/code-health.d.ts +9 -0
- package/dist/skills/code-health.js +167 -0
- package/dist/skills/code-structure.d.ts +9 -0
- package/dist/skills/code-structure.js +97 -0
- package/dist/skills/dependency-audit.d.ts +9 -0
- package/dist/skills/dependency-audit.js +110 -0
- package/dist/skills/index.d.ts +16 -0
- package/dist/skills/index.js +27 -0
- package/dist/skills/refactor-impact.d.ts +9 -0
- package/dist/skills/refactor-impact.js +135 -0
- package/dist/skills/type-analysis.d.ts +9 -0
- package/dist/skills/type-analysis.js +150 -0
- package/dist/tools/find-dead-code.d.ts +20 -0
- package/dist/tools/find-dead-code.js +375 -0
- package/dist/tools/find-duplicates.d.ts +21 -0
- package/dist/tools/find-duplicates.js +274 -0
- package/dist/tools/find-implementations.d.ts +21 -0
- package/dist/tools/find-implementations.js +436 -0
- package/dist/tools/find-patterns.d.ts +21 -0
- package/dist/tools/find-patterns.js +457 -0
- package/dist/tools/find-references.d.ts +23 -0
- package/dist/tools/find-references.js +488 -0
- package/dist/tools/find-symbol.d.ts +21 -0
- package/dist/tools/find-symbol.js +458 -0
- package/dist/tools/get-call-graph.d.ts +23 -0
- package/dist/tools/get-call-graph.js +469 -0
- package/dist/tools/get-complexity.d.ts +21 -0
- package/dist/tools/get-complexity.js +394 -0
- package/dist/tools/get-dependency-graph.d.ts +23 -0
- package/dist/tools/get-dependency-graph.js +482 -0
- package/dist/tools/get-documentation.d.ts +21 -0
- package/dist/tools/get-documentation.js +613 -0
- package/dist/tools/get-exports.d.ts +21 -0
- package/dist/tools/get-exports.js +427 -0
- package/dist/tools/get-file-structure.d.ts +27 -0
- package/dist/tools/get-file-structure.js +120 -0
- package/dist/tools/get-imports.d.ts +23 -0
- package/dist/tools/get-imports.js +350 -0
- package/dist/tools/get-signature.d.ts +20 -0
- package/dist/tools/get-signature.js +758 -0
- package/dist/tools/get-type-hierarchy.d.ts +22 -0
- package/dist/tools/get-type-hierarchy.js +485 -0
- package/dist/tools/index.d.ts +23 -0
- package/dist/tools/index.js +25 -0
- package/dist/tools/types.d.ts +1302 -0
- package/dist/tools/types.js +7 -0
- package/package.json +84 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getSignature Tool
|
|
3
|
+
*
|
|
4
|
+
* Get detailed signature information for a function, method, class, or type.
|
|
5
|
+
* Includes parameters, return types, generics, and documentation.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs/promises';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import * as ts from 'typescript';
|
|
10
|
+
import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
|
|
11
|
+
import { detectLanguage, isLanguageSupported } from '../parser/typescript-parser.js';
|
|
12
|
+
// Tool description
|
|
13
|
+
const TOOL_DESCRIPTION = `Get detailed signature information for a function, method, class, interface, or type.
|
|
14
|
+
Returns parameters with types, return type, generics, and extracted documentation (JSDoc/TSDoc).
|
|
15
|
+
Useful for understanding API contracts without reading full source code.`;
|
|
16
|
+
// Tool input schema
|
|
17
|
+
const TOOL_INPUT_SCHEMA = {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
path: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'File containing the symbol',
|
|
23
|
+
},
|
|
24
|
+
name: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Symbol name to get signature for',
|
|
27
|
+
},
|
|
28
|
+
line: {
|
|
29
|
+
type: 'number',
|
|
30
|
+
description: 'Line number for disambiguation (optional)',
|
|
31
|
+
},
|
|
32
|
+
includeDoc: {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
description: 'Include full documentation (default: true)',
|
|
35
|
+
default: true,
|
|
36
|
+
},
|
|
37
|
+
expandTypes: {
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
description: 'Expand type aliases (default: false)',
|
|
40
|
+
default: false,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
required: ['path', 'name'],
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* getSignature tool
|
|
47
|
+
*/
|
|
48
|
+
export const getSignatureTool = defineTool({
|
|
49
|
+
name: 'get_signature',
|
|
50
|
+
description: TOOL_DESCRIPTION,
|
|
51
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
52
|
+
execute: executeGetSignature,
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Execute the getSignature tool
|
|
56
|
+
*/
|
|
57
|
+
async function executeGetSignature(input) {
|
|
58
|
+
const { path: inputPath, name, line, includeDoc = true, expandTypes = false } = input;
|
|
59
|
+
try {
|
|
60
|
+
const resolvedPath = path.resolve(inputPath);
|
|
61
|
+
// Check if file exists
|
|
62
|
+
try {
|
|
63
|
+
await fs.access(resolvedPath);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return createErrorResult(`File not found: ${resolvedPath}`);
|
|
67
|
+
}
|
|
68
|
+
// Check language support
|
|
69
|
+
const detection = detectLanguage(resolvedPath);
|
|
70
|
+
if (!detection.language || !isLanguageSupported(detection.language)) {
|
|
71
|
+
return createErrorResult(`Unsupported language for file: ${resolvedPath}`);
|
|
72
|
+
}
|
|
73
|
+
const content = await fs.readFile(resolvedPath, 'utf-8');
|
|
74
|
+
const sourceFile = ts.createSourceFile(resolvedPath, content, ts.ScriptTarget.Latest, true, resolvedPath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
75
|
+
// Find the symbol
|
|
76
|
+
const result = findSymbolSignature(sourceFile, name, line, includeDoc, expandTypes);
|
|
77
|
+
if (!result) {
|
|
78
|
+
return createErrorResult(`Symbol "${name}" not found in ${resolvedPath}`);
|
|
79
|
+
}
|
|
80
|
+
return createSuccessResult({
|
|
81
|
+
...result,
|
|
82
|
+
path: resolvedPath,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return createErrorResult(`Failed to get signature: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Find a symbol and extract its signature
|
|
91
|
+
*/
|
|
92
|
+
function findSymbolSignature(sourceFile, symbolName, targetLine, includeDoc, _expandTypes) {
|
|
93
|
+
// Use object to track found values (ESLint tracks object mutations better)
|
|
94
|
+
const found = {
|
|
95
|
+
node: null,
|
|
96
|
+
kind: null,
|
|
97
|
+
};
|
|
98
|
+
const visit = (node) => {
|
|
99
|
+
if (found.node && !targetLine)
|
|
100
|
+
return; // Already found, stop if no line disambiguation
|
|
101
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
102
|
+
const nodeLine = line + 1;
|
|
103
|
+
// Skip if we have a target line and this isn't it
|
|
104
|
+
if (targetLine && nodeLine !== targetLine) {
|
|
105
|
+
ts.forEachChild(node, visit);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Function declarations
|
|
109
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === symbolName) {
|
|
110
|
+
found.node = node;
|
|
111
|
+
found.kind = 'function';
|
|
112
|
+
}
|
|
113
|
+
// Variable declarations (arrow functions, const functions)
|
|
114
|
+
else if (ts.isVariableDeclaration(node) &&
|
|
115
|
+
ts.isIdentifier(node.name) &&
|
|
116
|
+
node.name.text === symbolName) {
|
|
117
|
+
if (node.initializer &&
|
|
118
|
+
(ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
|
|
119
|
+
found.node = node;
|
|
120
|
+
found.kind = 'function';
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
found.node = node;
|
|
124
|
+
found.kind = 'variable';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Class declarations
|
|
128
|
+
else if (ts.isClassDeclaration(node) && node.name?.text === symbolName) {
|
|
129
|
+
found.node = node;
|
|
130
|
+
found.kind = 'class';
|
|
131
|
+
}
|
|
132
|
+
// Interface declarations
|
|
133
|
+
else if (ts.isInterfaceDeclaration(node) && node.name.text === symbolName) {
|
|
134
|
+
found.node = node;
|
|
135
|
+
found.kind = 'interface';
|
|
136
|
+
}
|
|
137
|
+
// Type alias declarations
|
|
138
|
+
else if (ts.isTypeAliasDeclaration(node) && node.name.text === symbolName) {
|
|
139
|
+
found.node = node;
|
|
140
|
+
found.kind = 'type';
|
|
141
|
+
}
|
|
142
|
+
// Enum declarations
|
|
143
|
+
else if (ts.isEnumDeclaration(node) && node.name.text === symbolName) {
|
|
144
|
+
found.node = node;
|
|
145
|
+
found.kind = 'enum';
|
|
146
|
+
}
|
|
147
|
+
// Method declarations (inside classes)
|
|
148
|
+
else if (ts.isMethodDeclaration(node) &&
|
|
149
|
+
ts.isIdentifier(node.name) &&
|
|
150
|
+
node.name.text === symbolName) {
|
|
151
|
+
found.node = node;
|
|
152
|
+
found.kind = 'method';
|
|
153
|
+
}
|
|
154
|
+
ts.forEachChild(node, visit);
|
|
155
|
+
};
|
|
156
|
+
visit(sourceFile);
|
|
157
|
+
if (found.node === null || found.kind === null) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
return extractSignature(found.node, found.kind, sourceFile, includeDoc);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Extract signature from a node
|
|
164
|
+
*/
|
|
165
|
+
function extractSignature(node, kind, sourceFile, includeDoc) {
|
|
166
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
167
|
+
const exported = isExported(node);
|
|
168
|
+
const name = getNodeName(node);
|
|
169
|
+
const documentation = includeDoc ? extractDocumentation(node, sourceFile) : undefined;
|
|
170
|
+
switch (kind) {
|
|
171
|
+
case 'function':
|
|
172
|
+
return extractFunctionSignature(node, sourceFile, name, line + 1, exported, documentation);
|
|
173
|
+
case 'method':
|
|
174
|
+
return extractMethodSignature(node, sourceFile, name, line + 1, exported, documentation);
|
|
175
|
+
case 'class':
|
|
176
|
+
return extractClassSignature(node, sourceFile, name, line + 1, exported, documentation);
|
|
177
|
+
case 'interface':
|
|
178
|
+
return extractInterfaceSignature(node, sourceFile, name, line + 1, exported, documentation);
|
|
179
|
+
case 'type':
|
|
180
|
+
return extractTypeSignature(node, sourceFile, name, line + 1, exported, documentation);
|
|
181
|
+
case 'enum':
|
|
182
|
+
return extractEnumSignature(node, sourceFile, name, line + 1, exported, documentation);
|
|
183
|
+
case 'variable':
|
|
184
|
+
return extractVariableSignature(node, sourceFile, name, line + 1, exported, documentation);
|
|
185
|
+
default:
|
|
186
|
+
return {
|
|
187
|
+
name,
|
|
188
|
+
kind,
|
|
189
|
+
line: line + 1,
|
|
190
|
+
signature: node.getText(sourceFile),
|
|
191
|
+
formattedSignature: node.getText(sourceFile),
|
|
192
|
+
exported,
|
|
193
|
+
documentation,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Extract function signature
|
|
199
|
+
*/
|
|
200
|
+
function extractFunctionSignature(node, sourceFile, name, line, exported, documentation) {
|
|
201
|
+
let funcNode;
|
|
202
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
203
|
+
funcNode = node;
|
|
204
|
+
}
|
|
205
|
+
else if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
206
|
+
funcNode = node.initializer;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
return {
|
|
210
|
+
name,
|
|
211
|
+
kind: 'function',
|
|
212
|
+
line,
|
|
213
|
+
signature: node.getText(sourceFile),
|
|
214
|
+
formattedSignature: node.getText(sourceFile),
|
|
215
|
+
exported,
|
|
216
|
+
documentation,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const parameters = extractParameters(funcNode.parameters, sourceFile, documentation);
|
|
220
|
+
const returnType = extractReturnType(funcNode, sourceFile);
|
|
221
|
+
const generics = extractGenerics(funcNode.typeParameters, sourceFile);
|
|
222
|
+
const isAsync = funcNode.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
|
|
223
|
+
// Build signature string
|
|
224
|
+
const asyncPrefix = isAsync ? 'async ' : '';
|
|
225
|
+
const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
|
|
226
|
+
const paramsStr = parameters.map((p) => formatParameter(p)).join(', ');
|
|
227
|
+
const returnStr = returnType ? `: ${returnType.type}` : '';
|
|
228
|
+
const signature = `${asyncPrefix}function ${name}${genericsStr}(${paramsStr})${returnStr}`;
|
|
229
|
+
const formattedSignature = formatMultilineSignature(asyncPrefix, name, genericsStr, parameters, returnType);
|
|
230
|
+
return {
|
|
231
|
+
name,
|
|
232
|
+
kind: 'function',
|
|
233
|
+
line,
|
|
234
|
+
signature,
|
|
235
|
+
formattedSignature,
|
|
236
|
+
exported,
|
|
237
|
+
parameters,
|
|
238
|
+
returnType,
|
|
239
|
+
generics: generics.length > 0 ? generics : undefined,
|
|
240
|
+
documentation,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Extract method signature
|
|
245
|
+
*/
|
|
246
|
+
function extractMethodSignature(node, sourceFile, name, line, exported, documentation) {
|
|
247
|
+
const parameters = extractParameters(node.parameters, sourceFile, documentation);
|
|
248
|
+
const returnType = extractReturnType(node, sourceFile);
|
|
249
|
+
const generics = extractGenerics(node.typeParameters, sourceFile);
|
|
250
|
+
const isAsync = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
|
|
251
|
+
const isStatic = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
|
|
252
|
+
const asyncPrefix = isAsync ? 'async ' : '';
|
|
253
|
+
const staticPrefix = isStatic ? 'static ' : '';
|
|
254
|
+
const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
|
|
255
|
+
const paramsStr = parameters.map((p) => formatParameter(p)).join(', ');
|
|
256
|
+
const returnStr = returnType ? `: ${returnType.type}` : '';
|
|
257
|
+
const signature = `${staticPrefix}${asyncPrefix}${name}${genericsStr}(${paramsStr})${returnStr}`;
|
|
258
|
+
const formattedSignature = signature;
|
|
259
|
+
return {
|
|
260
|
+
name,
|
|
261
|
+
kind: 'method',
|
|
262
|
+
line,
|
|
263
|
+
signature,
|
|
264
|
+
formattedSignature,
|
|
265
|
+
exported,
|
|
266
|
+
parameters,
|
|
267
|
+
returnType,
|
|
268
|
+
generics: generics.length > 0 ? generics : undefined,
|
|
269
|
+
documentation,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Extract class signature
|
|
274
|
+
*/
|
|
275
|
+
function extractClassSignature(node, sourceFile, name, line, exported, documentation) {
|
|
276
|
+
const generics = extractGenerics(node.typeParameters, sourceFile);
|
|
277
|
+
const members = extractClassMembers(node, sourceFile);
|
|
278
|
+
// Find constructor
|
|
279
|
+
const constructor = node.members.find(ts.isConstructorDeclaration);
|
|
280
|
+
let constructorSignature;
|
|
281
|
+
if (constructor) {
|
|
282
|
+
const params = extractParameters(constructor.parameters, sourceFile, undefined);
|
|
283
|
+
constructorSignature = `constructor(${params.map((p) => formatParameter(p)).join(', ')})`;
|
|
284
|
+
}
|
|
285
|
+
// Build extends/implements
|
|
286
|
+
let heritage = '';
|
|
287
|
+
if (node.heritageClauses) {
|
|
288
|
+
for (const clause of node.heritageClauses) {
|
|
289
|
+
const tokenKind = clause.token;
|
|
290
|
+
if (tokenKind === ts.SyntaxKind.ExtendsKeyword) {
|
|
291
|
+
heritage += ` extends ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
|
|
292
|
+
}
|
|
293
|
+
else if (tokenKind === ts.SyntaxKind.ImplementsKeyword) {
|
|
294
|
+
heritage += ` implements ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const isAbstract = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false;
|
|
299
|
+
const abstractPrefix = isAbstract ? 'abstract ' : '';
|
|
300
|
+
const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
|
|
301
|
+
const signature = `${abstractPrefix}class ${name}${genericsStr}${heritage}`;
|
|
302
|
+
const formattedSignature = signature;
|
|
303
|
+
return {
|
|
304
|
+
name,
|
|
305
|
+
kind: 'class',
|
|
306
|
+
line,
|
|
307
|
+
signature,
|
|
308
|
+
formattedSignature,
|
|
309
|
+
exported,
|
|
310
|
+
generics: generics.length > 0 ? generics : undefined,
|
|
311
|
+
documentation,
|
|
312
|
+
constructorSignature,
|
|
313
|
+
members,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Extract interface signature
|
|
318
|
+
*/
|
|
319
|
+
function extractInterfaceSignature(node, sourceFile, name, line, exported, documentation) {
|
|
320
|
+
const generics = extractGenerics(node.typeParameters, sourceFile);
|
|
321
|
+
const members = extractInterfaceMembers(node, sourceFile);
|
|
322
|
+
// Build extends
|
|
323
|
+
let heritage = '';
|
|
324
|
+
if (node.heritageClauses) {
|
|
325
|
+
for (const clause of node.heritageClauses) {
|
|
326
|
+
heritage += ` extends ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
|
|
330
|
+
const signature = `interface ${name}${genericsStr}${heritage}`;
|
|
331
|
+
const formattedSignature = signature;
|
|
332
|
+
return {
|
|
333
|
+
name,
|
|
334
|
+
kind: 'interface',
|
|
335
|
+
line,
|
|
336
|
+
signature,
|
|
337
|
+
formattedSignature,
|
|
338
|
+
exported,
|
|
339
|
+
generics: generics.length > 0 ? generics : undefined,
|
|
340
|
+
documentation,
|
|
341
|
+
members,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Extract type alias signature
|
|
346
|
+
*/
|
|
347
|
+
function extractTypeSignature(node, sourceFile, name, line, exported, documentation) {
|
|
348
|
+
const generics = extractGenerics(node.typeParameters, sourceFile);
|
|
349
|
+
const typeText = node.type.getText(sourceFile);
|
|
350
|
+
const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
|
|
351
|
+
const signature = `type ${name}${genericsStr} = ${typeText}`;
|
|
352
|
+
// Format multi-line for complex types
|
|
353
|
+
const formattedSignature = typeText.length > 60
|
|
354
|
+
? `type ${name}${genericsStr} = \n ${typeText.replace(/\n/g, '\n ')}`
|
|
355
|
+
: signature;
|
|
356
|
+
return {
|
|
357
|
+
name,
|
|
358
|
+
kind: 'type',
|
|
359
|
+
line,
|
|
360
|
+
signature,
|
|
361
|
+
formattedSignature,
|
|
362
|
+
exported,
|
|
363
|
+
generics: generics.length > 0 ? generics : undefined,
|
|
364
|
+
documentation,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Extract enum signature
|
|
369
|
+
*/
|
|
370
|
+
function extractEnumSignature(node, sourceFile, name, line, exported, documentation) {
|
|
371
|
+
const members = node.members.map((member) => ({
|
|
372
|
+
name: member.name.getText(sourceFile),
|
|
373
|
+
kind: 'property',
|
|
374
|
+
signature: member.initializer
|
|
375
|
+
? `${member.name.getText(sourceFile)} = ${member.initializer.getText(sourceFile)}`
|
|
376
|
+
: member.name.getText(sourceFile),
|
|
377
|
+
optional: false,
|
|
378
|
+
readonly: true,
|
|
379
|
+
}));
|
|
380
|
+
const isConst = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword) ?? false;
|
|
381
|
+
const constPrefix = isConst ? 'const ' : '';
|
|
382
|
+
const signature = `${constPrefix}enum ${name}`;
|
|
383
|
+
return {
|
|
384
|
+
name,
|
|
385
|
+
kind: 'enum',
|
|
386
|
+
line,
|
|
387
|
+
signature,
|
|
388
|
+
formattedSignature: signature,
|
|
389
|
+
exported,
|
|
390
|
+
documentation,
|
|
391
|
+
members,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Extract variable signature
|
|
396
|
+
*/
|
|
397
|
+
function extractVariableSignature(node, sourceFile, name, line, exported, documentation) {
|
|
398
|
+
const typeNode = node.type;
|
|
399
|
+
const typeText = typeNode ? typeNode.getText(sourceFile) : undefined;
|
|
400
|
+
// Determine const/let/var
|
|
401
|
+
const parent = node.parent;
|
|
402
|
+
let declKind = 'const';
|
|
403
|
+
if (ts.isVariableDeclarationList(parent)) {
|
|
404
|
+
if (parent.flags & ts.NodeFlags.Let)
|
|
405
|
+
declKind = 'let';
|
|
406
|
+
else if (parent.flags & ts.NodeFlags.Const)
|
|
407
|
+
declKind = 'const';
|
|
408
|
+
else
|
|
409
|
+
declKind = 'var';
|
|
410
|
+
}
|
|
411
|
+
const signature = typeText ? `${declKind} ${name}: ${typeText}` : `${declKind} ${name}`;
|
|
412
|
+
return {
|
|
413
|
+
name,
|
|
414
|
+
kind: 'variable',
|
|
415
|
+
line,
|
|
416
|
+
signature,
|
|
417
|
+
formattedSignature: signature,
|
|
418
|
+
exported,
|
|
419
|
+
documentation,
|
|
420
|
+
returnType: typeText ? { type: typeText, isPromise: false, nullable: false } : undefined,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Extract parameters from parameter list
|
|
425
|
+
*/
|
|
426
|
+
function extractParameters(params, sourceFile, documentation) {
|
|
427
|
+
return params.map((param) => {
|
|
428
|
+
const paramName = param.name.getText(sourceFile);
|
|
429
|
+
const type = param.type ? param.type.getText(sourceFile) : 'any';
|
|
430
|
+
const optional = param.questionToken !== undefined || param.initializer !== undefined;
|
|
431
|
+
const rest = param.dotDotDotToken !== undefined;
|
|
432
|
+
const defaultValue = param.initializer ? param.initializer.getText(sourceFile) : undefined;
|
|
433
|
+
const description = documentation?.params?.[paramName];
|
|
434
|
+
return {
|
|
435
|
+
name: paramName,
|
|
436
|
+
type,
|
|
437
|
+
optional,
|
|
438
|
+
rest,
|
|
439
|
+
defaultValue,
|
|
440
|
+
description,
|
|
441
|
+
};
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Extract return type
|
|
446
|
+
*/
|
|
447
|
+
function extractReturnType(node, sourceFile) {
|
|
448
|
+
if (!node.type)
|
|
449
|
+
return undefined;
|
|
450
|
+
const typeText = node.type.getText(sourceFile);
|
|
451
|
+
const isPromise = typeText.startsWith('Promise<') || typeText.startsWith('Promise ');
|
|
452
|
+
const nullable = typeText.includes('| null') || typeText.includes('| undefined');
|
|
453
|
+
return {
|
|
454
|
+
type: typeText,
|
|
455
|
+
isPromise,
|
|
456
|
+
nullable,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Extract generics
|
|
461
|
+
*/
|
|
462
|
+
function extractGenerics(typeParams, sourceFile) {
|
|
463
|
+
if (!typeParams)
|
|
464
|
+
return [];
|
|
465
|
+
return typeParams.map((param) => ({
|
|
466
|
+
name: param.name.getText(sourceFile),
|
|
467
|
+
constraint: param.constraint ? param.constraint.getText(sourceFile) : undefined,
|
|
468
|
+
default: param.default ? param.default.getText(sourceFile) : undefined,
|
|
469
|
+
}));
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Extract class members
|
|
473
|
+
*/
|
|
474
|
+
function extractClassMembers(node, sourceFile) {
|
|
475
|
+
const members = [];
|
|
476
|
+
for (const member of node.members) {
|
|
477
|
+
if (ts.isConstructorDeclaration(member))
|
|
478
|
+
continue; // Skip constructor
|
|
479
|
+
// Handle property declarations
|
|
480
|
+
if (ts.isPropertyDeclaration(member)) {
|
|
481
|
+
const memberName = member.name.getText(sourceFile);
|
|
482
|
+
const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
|
|
483
|
+
const isStatic = mods?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
|
|
484
|
+
const isReadonly = mods?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword) ?? false;
|
|
485
|
+
const visibility = getVisibility(member);
|
|
486
|
+
const type = member.type ? member.type.getText(sourceFile) : 'any';
|
|
487
|
+
members.push({
|
|
488
|
+
name: memberName,
|
|
489
|
+
kind: 'property',
|
|
490
|
+
signature: `${memberName}: ${type}`,
|
|
491
|
+
optional: member.questionToken !== undefined,
|
|
492
|
+
readonly: isReadonly,
|
|
493
|
+
visibility,
|
|
494
|
+
static: isStatic,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
// Handle method declarations
|
|
498
|
+
else if (ts.isMethodDeclaration(member)) {
|
|
499
|
+
const memberName = member.name.getText(sourceFile);
|
|
500
|
+
const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
|
|
501
|
+
const isStatic = mods?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
|
|
502
|
+
const visibility = getVisibility(member);
|
|
503
|
+
const params = member.parameters.map((p) => p.getText(sourceFile)).join(', ');
|
|
504
|
+
const returnType = member.type ? `: ${member.type.getText(sourceFile)}` : '';
|
|
505
|
+
members.push({
|
|
506
|
+
name: memberName,
|
|
507
|
+
kind: 'method',
|
|
508
|
+
signature: `${memberName}(${params})${returnType}`,
|
|
509
|
+
optional: member.questionToken !== undefined,
|
|
510
|
+
readonly: false,
|
|
511
|
+
visibility,
|
|
512
|
+
static: isStatic,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
// Handle getter accessors
|
|
516
|
+
else if (ts.isGetAccessor(member)) {
|
|
517
|
+
const memberName = member.name.getText(sourceFile);
|
|
518
|
+
const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
|
|
519
|
+
const isStatic = mods?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
|
|
520
|
+
const visibility = getVisibility(member);
|
|
521
|
+
const returnType = member.type ? `: ${member.type.getText(sourceFile)}` : '';
|
|
522
|
+
members.push({
|
|
523
|
+
name: memberName,
|
|
524
|
+
kind: 'getter',
|
|
525
|
+
signature: `get ${memberName}()${returnType}`,
|
|
526
|
+
optional: false,
|
|
527
|
+
readonly: true,
|
|
528
|
+
visibility,
|
|
529
|
+
static: isStatic,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
// Handle setter accessors
|
|
533
|
+
else if (ts.isSetAccessor(member)) {
|
|
534
|
+
const memberName = member.name.getText(sourceFile);
|
|
535
|
+
const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
|
|
536
|
+
const isStatic = mods?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
|
|
537
|
+
const visibility = getVisibility(member);
|
|
538
|
+
const param = member.parameters[0];
|
|
539
|
+
const paramStr = param.getText(sourceFile);
|
|
540
|
+
members.push({
|
|
541
|
+
name: memberName,
|
|
542
|
+
kind: 'setter',
|
|
543
|
+
signature: `set ${memberName}(${paramStr})`,
|
|
544
|
+
optional: false,
|
|
545
|
+
readonly: false,
|
|
546
|
+
visibility,
|
|
547
|
+
static: isStatic,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return members;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Extract interface members
|
|
555
|
+
*/
|
|
556
|
+
function extractInterfaceMembers(node, sourceFile) {
|
|
557
|
+
const members = [];
|
|
558
|
+
for (const member of node.members) {
|
|
559
|
+
if (ts.isPropertySignature(member)) {
|
|
560
|
+
const memberName = member.name.getText(sourceFile);
|
|
561
|
+
const type = member.type ? member.type.getText(sourceFile) : 'any';
|
|
562
|
+
const isReadonly = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword) ?? false;
|
|
563
|
+
members.push({
|
|
564
|
+
name: memberName,
|
|
565
|
+
kind: 'property',
|
|
566
|
+
signature: `${memberName}: ${type}`,
|
|
567
|
+
optional: member.questionToken !== undefined,
|
|
568
|
+
readonly: isReadonly,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
else if (ts.isMethodSignature(member)) {
|
|
572
|
+
const memberName = member.name.getText(sourceFile);
|
|
573
|
+
const params = member.parameters.map((p) => p.getText(sourceFile)).join(', ');
|
|
574
|
+
const returnType = member.type ? `: ${member.type.getText(sourceFile)}` : '';
|
|
575
|
+
members.push({
|
|
576
|
+
name: memberName,
|
|
577
|
+
kind: 'method',
|
|
578
|
+
signature: `${memberName}(${params})${returnType}`,
|
|
579
|
+
optional: member.questionToken !== undefined,
|
|
580
|
+
readonly: false,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return members;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Extract JSDoc documentation
|
|
588
|
+
*/
|
|
589
|
+
function extractDocumentation(node, sourceFile) {
|
|
590
|
+
const jsDocTags = ts.getJSDocTags(node);
|
|
591
|
+
const jsDocComments = node.jsDoc;
|
|
592
|
+
if (!jsDocComments?.length && !jsDocTags.length)
|
|
593
|
+
return undefined;
|
|
594
|
+
const doc = {
|
|
595
|
+
summary: '',
|
|
596
|
+
};
|
|
597
|
+
// Get comment text
|
|
598
|
+
if (jsDocComments?.length) {
|
|
599
|
+
const firstDoc = jsDocComments[0];
|
|
600
|
+
if (typeof firstDoc.comment === 'string') {
|
|
601
|
+
const lines = firstDoc.comment.split('\n');
|
|
602
|
+
doc.summary = lines[0].trim();
|
|
603
|
+
if (lines.length > 1) {
|
|
604
|
+
doc.description = lines.slice(1).join('\n').trim();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
// Process tags
|
|
609
|
+
const params = {};
|
|
610
|
+
const throws = [];
|
|
611
|
+
const examples = [];
|
|
612
|
+
const see = [];
|
|
613
|
+
for (const tag of jsDocTags) {
|
|
614
|
+
const tagName = tag.tagName.text;
|
|
615
|
+
const tagComment = typeof tag.comment === 'string' ? tag.comment : '';
|
|
616
|
+
if (tagName === 'param' && ts.isJSDocParameterTag(tag)) {
|
|
617
|
+
const paramName = tag.name.getText(sourceFile);
|
|
618
|
+
params[paramName] = tagComment;
|
|
619
|
+
}
|
|
620
|
+
else if (tagName === 'returns' || tagName === 'return') {
|
|
621
|
+
doc.returns = tagComment;
|
|
622
|
+
}
|
|
623
|
+
else if (tagName === 'throws' || tagName === 'exception') {
|
|
624
|
+
throws.push(tagComment);
|
|
625
|
+
}
|
|
626
|
+
else if (tagName === 'example') {
|
|
627
|
+
examples.push(tagComment);
|
|
628
|
+
}
|
|
629
|
+
else if (tagName === 'see') {
|
|
630
|
+
see.push(tagComment);
|
|
631
|
+
}
|
|
632
|
+
else if (ts.isJSDocDeprecatedTag(tag)) {
|
|
633
|
+
doc.deprecatedMessage = tagComment || 'This API is deprecated';
|
|
634
|
+
}
|
|
635
|
+
else if (tagName === 'since') {
|
|
636
|
+
doc.since = tagComment;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (Object.keys(params).length > 0)
|
|
640
|
+
doc.params = params;
|
|
641
|
+
if (throws.length > 0)
|
|
642
|
+
doc.throws = throws;
|
|
643
|
+
if (examples.length > 0)
|
|
644
|
+
doc.examples = examples;
|
|
645
|
+
if (see.length > 0)
|
|
646
|
+
doc.see = see;
|
|
647
|
+
// Return undefined if no useful info
|
|
648
|
+
if (!doc.summary && !doc.params && !doc.returns)
|
|
649
|
+
return undefined;
|
|
650
|
+
return doc;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Get node name
|
|
654
|
+
*/
|
|
655
|
+
function getNodeName(node) {
|
|
656
|
+
if (ts.isFunctionDeclaration(node) && node.name)
|
|
657
|
+
return node.name.text;
|
|
658
|
+
if (ts.isClassDeclaration(node) && node.name)
|
|
659
|
+
return node.name.text;
|
|
660
|
+
if (ts.isInterfaceDeclaration(node))
|
|
661
|
+
return node.name.text;
|
|
662
|
+
if (ts.isTypeAliasDeclaration(node))
|
|
663
|
+
return node.name.text;
|
|
664
|
+
if (ts.isEnumDeclaration(node))
|
|
665
|
+
return node.name.text;
|
|
666
|
+
if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name))
|
|
667
|
+
return node.name.text;
|
|
668
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name))
|
|
669
|
+
return node.name.text;
|
|
670
|
+
return '<anonymous>';
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Check if node is exported
|
|
674
|
+
*/
|
|
675
|
+
function isExported(node) {
|
|
676
|
+
// For variable declarations, check the parent VariableStatement
|
|
677
|
+
if (ts.isVariableDeclaration(node)) {
|
|
678
|
+
// VariableDeclaration -> VariableDeclarationList -> VariableStatement
|
|
679
|
+
const varDeclList = node.parent;
|
|
680
|
+
const varStatement = varDeclList.parent;
|
|
681
|
+
if (ts.canHaveModifiers(varStatement)) {
|
|
682
|
+
const modifiers = ts.getModifiers(varStatement);
|
|
683
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
684
|
+
}
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
if (!ts.canHaveModifiers(node))
|
|
688
|
+
return false;
|
|
689
|
+
const modifiers = ts.getModifiers(node);
|
|
690
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Get visibility modifier
|
|
694
|
+
*/
|
|
695
|
+
function getVisibility(node) {
|
|
696
|
+
if (!ts.canHaveModifiers(node))
|
|
697
|
+
return 'public';
|
|
698
|
+
const modifiers = ts.getModifiers(node);
|
|
699
|
+
if (modifiers?.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword))
|
|
700
|
+
return 'private';
|
|
701
|
+
if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ProtectedKeyword))
|
|
702
|
+
return 'protected';
|
|
703
|
+
return 'public';
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Format a generic parameter
|
|
707
|
+
*/
|
|
708
|
+
function formatGeneric(g) {
|
|
709
|
+
let result = g.name;
|
|
710
|
+
if (g.constraint)
|
|
711
|
+
result += ` extends ${g.constraint}`;
|
|
712
|
+
if (g.default)
|
|
713
|
+
result += ` = ${g.default}`;
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Format a parameter
|
|
718
|
+
*/
|
|
719
|
+
function formatParameter(p) {
|
|
720
|
+
let result = p.rest ? `...${p.name}` : p.name;
|
|
721
|
+
if (p.optional && !p.defaultValue)
|
|
722
|
+
result += '?';
|
|
723
|
+
result += `: ${p.type}`;
|
|
724
|
+
if (p.defaultValue)
|
|
725
|
+
result += ` = ${p.defaultValue}`;
|
|
726
|
+
return result;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Format multi-line signature
|
|
730
|
+
*/
|
|
731
|
+
function formatMultilineSignature(asyncPrefix, name, genericsStr, parameters, returnType) {
|
|
732
|
+
if (parameters.length <= 2) {
|
|
733
|
+
const paramsStr = parameters.map((p) => formatParameter(p)).join(', ');
|
|
734
|
+
const returnStr = returnType ? `: ${returnType.type}` : '';
|
|
735
|
+
return `${asyncPrefix}function ${name}${genericsStr}(${paramsStr})${returnStr}`;
|
|
736
|
+
}
|
|
737
|
+
// Multi-line for many parameters
|
|
738
|
+
const paramsLines = parameters.map((p) => ` ${formatParameter(p)}`).join(',\n');
|
|
739
|
+
const returnStr = returnType ? `: ${returnType.type}` : '';
|
|
740
|
+
return `${asyncPrefix}function ${name}${genericsStr}(\n${paramsLines}\n)${returnStr}`;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Create customizable getSignature tool
|
|
744
|
+
*/
|
|
745
|
+
export function createGetSignatureTool(options) {
|
|
746
|
+
return defineTool({
|
|
747
|
+
name: options?.name ?? 'get_signature',
|
|
748
|
+
description: options?.description ?? TOOL_DESCRIPTION,
|
|
749
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
750
|
+
execute: async (input) => {
|
|
751
|
+
const modifiedInput = {
|
|
752
|
+
...input,
|
|
753
|
+
includeDoc: input.includeDoc ?? options?.defaultIncludeDoc,
|
|
754
|
+
};
|
|
755
|
+
return executeGetSignature(modifiedInput);
|
|
756
|
+
},
|
|
757
|
+
});
|
|
758
|
+
}
|