jsc-typescript-ast-mcp 1.1.6 → 1.1.8
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/README.md +1 -1
- package/dist/main.js +9 -0
- package/dist/ts-morph/componentTree.js +1 -1
- package/dist/ts-morph/utils/extractJsx.js +38 -14
- package/dist/ts-morph/utils/props.js +44 -34
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/main.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { getComponentTree } from './ts-morph/componentTree.js';
|
|
3
|
+
const rootDir = __dirname === 'dist'
|
|
4
|
+
? path.resolve(__dirname, '../../')
|
|
5
|
+
: __dirname === 'code'
|
|
6
|
+
? path.resolve(__dirname, '../')
|
|
7
|
+
: __dirname;
|
|
8
|
+
const testApp = getComponentTree(path.resolve(rootDir, 'sample-app/src/App.tsx'), 5, 'my-data-id');
|
|
9
|
+
console.log(JSON.stringify(testApp, null, 2));
|
|
@@ -28,7 +28,7 @@ export const registerComponentTreeTool = (server) => {
|
|
|
28
28
|
};
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
|
-
const getComponentTree = (entryFilePath, maxDepth, dataIdAttribute) => {
|
|
31
|
+
export const getComponentTree = (entryFilePath, maxDepth, dataIdAttribute) => {
|
|
32
32
|
const project = createProject();
|
|
33
33
|
const sourceFile = project.getSourceFile(entryFilePath);
|
|
34
34
|
if (!sourceFile) {
|
|
@@ -2,7 +2,7 @@ import { Node, SyntaxKind, } from 'ts-morph';
|
|
|
2
2
|
import { analyzeComponent } from './analyzeComponent.js';
|
|
3
3
|
import { getComponent } from './component.js';
|
|
4
4
|
import { getTagName } from './nameHelper.js';
|
|
5
|
-
import { extractAttributeValueFromNode,
|
|
5
|
+
import { extractAttributeValueFromNode, extractCtaInfoFromNode, extractPropsFromNode, } from './props.js';
|
|
6
6
|
import { handleTernary } from './ternary.js';
|
|
7
7
|
export const extractFromExpression = (expression, project, options, depth) => {
|
|
8
8
|
if (Node.isConditionalExpression(expression)) {
|
|
@@ -42,7 +42,9 @@ const extractFromIdentifier = (expression, project, options, depth) => {
|
|
|
42
42
|
const buildNodeFromJsxFragment = (node, project, options, depth) => ({
|
|
43
43
|
name: 'Fragment',
|
|
44
44
|
type: 'fragment',
|
|
45
|
-
children:
|
|
45
|
+
children: depth < options.maxDepth
|
|
46
|
+
? extractJSX(node, project, options, depth + 1)
|
|
47
|
+
: [],
|
|
46
48
|
});
|
|
47
49
|
export const extractJSX = (node, project, options, depth) => {
|
|
48
50
|
const results = [];
|
|
@@ -173,33 +175,33 @@ const buildNodeFromJSX = (node, project, options, depth) => {
|
|
|
173
175
|
treeNode.dataId = String(dataIdValue);
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
treeNode.
|
|
178
|
+
const cta = extractCtaInfoFromNode(node);
|
|
179
|
+
if (cta) {
|
|
180
|
+
treeNode.cta = cta;
|
|
179
181
|
}
|
|
180
|
-
// Recurse into children
|
|
181
|
-
if (Node.isJsxElement(node)) {
|
|
182
|
+
// Recurse into children while under maxDepth.
|
|
183
|
+
if (Node.isJsxElement(node) && depth < options.maxDepth) {
|
|
182
184
|
const children = node.getJsxChildren();
|
|
183
185
|
children.forEach((child) => {
|
|
184
186
|
if (Node.isJsxElement(child) || Node.isJsxSelfClosingElement(child)) {
|
|
185
|
-
const childNode = buildNodeFromJSX(child, project, options, depth);
|
|
187
|
+
const childNode = buildNodeFromJSX(child, project, options, depth + 1);
|
|
186
188
|
if (childNode) {
|
|
187
189
|
treeNode.children.push(childNode);
|
|
188
190
|
}
|
|
189
191
|
return;
|
|
190
192
|
}
|
|
191
193
|
if (Node.isJsxFragment(child)) {
|
|
192
|
-
treeNode.children.push(buildNodeFromJsxFragment(child, project, options, depth));
|
|
194
|
+
treeNode.children.push(buildNodeFromJsxFragment(child, project, options, depth + 1));
|
|
193
195
|
return;
|
|
194
196
|
}
|
|
195
197
|
if (Node.isJsxExpression(child)) {
|
|
196
198
|
const expression = child.getExpression();
|
|
197
199
|
if (expression) {
|
|
198
|
-
treeNode.children.push(...extractFromExpression(expression, project, options, depth));
|
|
200
|
+
treeNode.children.push(...extractFromExpression(expression, project, options, depth + 1));
|
|
199
201
|
}
|
|
200
202
|
return;
|
|
201
203
|
}
|
|
202
|
-
treeNode.children.push(...extractJSX(child, project, options, depth));
|
|
204
|
+
treeNode.children.push(...extractJSX(child, project, options, depth + 1));
|
|
203
205
|
});
|
|
204
206
|
}
|
|
205
207
|
// Resolve component if custom
|
|
@@ -214,9 +216,31 @@ const buildNodeFromJSX = (node, project, options, depth) => {
|
|
|
214
216
|
return treeNode;
|
|
215
217
|
};
|
|
216
218
|
export const findReturnedJSX = (node) => {
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
219
|
+
const getReturnedJsxExpression = (expression) => {
|
|
220
|
+
if (Node.isParenthesizedExpression(expression)) {
|
|
221
|
+
const innerExpression = expression.getExpression();
|
|
222
|
+
if (!innerExpression) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return getReturnedJsxExpression(innerExpression);
|
|
226
|
+
}
|
|
227
|
+
if (Node.isJsxElement(expression) ||
|
|
228
|
+
Node.isJsxSelfClosingElement(expression) ||
|
|
229
|
+
Node.isJsxFragment(expression)) {
|
|
230
|
+
return expression;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
};
|
|
234
|
+
const returnStatements = node.getDescendantsOfKind(SyntaxKind.ReturnStatement);
|
|
235
|
+
for (const returnStmt of returnStatements) {
|
|
236
|
+
const expression = returnStmt.getExpression();
|
|
237
|
+
if (!expression) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const returnedJsxExpression = getReturnedJsxExpression(expression);
|
|
241
|
+
if (returnedJsxExpression) {
|
|
242
|
+
return returnedJsxExpression;
|
|
243
|
+
}
|
|
220
244
|
}
|
|
221
245
|
// Arrow function implicit return
|
|
222
246
|
if (Node.isArrowFunction(node)) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Node, } from 'ts-morph';
|
|
2
|
-
const
|
|
2
|
+
const EXCLUDE_HTML_ATTRIBUTES = new Set([
|
|
3
3
|
'accept',
|
|
4
4
|
'acceptcharset',
|
|
5
|
-
'action',
|
|
5
|
+
// 'action',
|
|
6
6
|
'allow',
|
|
7
7
|
'allowfullscreen',
|
|
8
|
-
'alt',
|
|
8
|
+
// 'alt',
|
|
9
9
|
'as',
|
|
10
10
|
'async',
|
|
11
11
|
'autocapitalize',
|
|
@@ -18,30 +18,30 @@ const STANDARD_HTML_ATTRIBUTES = new Set([
|
|
|
18
18
|
'cellspacing',
|
|
19
19
|
'charset',
|
|
20
20
|
'challenge',
|
|
21
|
-
'checked',
|
|
21
|
+
// 'checked',
|
|
22
22
|
'cite',
|
|
23
23
|
'class',
|
|
24
24
|
'classname',
|
|
25
25
|
'cols',
|
|
26
26
|
'colspan',
|
|
27
|
-
'content',
|
|
27
|
+
// 'content',
|
|
28
28
|
'contenteditable',
|
|
29
29
|
'controls',
|
|
30
30
|
'coords',
|
|
31
31
|
'crossorigin',
|
|
32
|
-
'data',
|
|
33
|
-
'datetime',
|
|
32
|
+
// 'data',
|
|
33
|
+
// 'datetime',
|
|
34
34
|
'decoding',
|
|
35
35
|
'default',
|
|
36
36
|
'defer',
|
|
37
37
|
'dir',
|
|
38
38
|
'dirname',
|
|
39
|
-
'disabled',
|
|
39
|
+
// 'disabled',
|
|
40
40
|
'download',
|
|
41
41
|
'draggable',
|
|
42
42
|
'enctype',
|
|
43
43
|
'fetchpriority',
|
|
44
|
-
'for',
|
|
44
|
+
// 'for',
|
|
45
45
|
'form',
|
|
46
46
|
'formaction',
|
|
47
47
|
'formenctype',
|
|
@@ -50,20 +50,20 @@ const STANDARD_HTML_ATTRIBUTES = new Set([
|
|
|
50
50
|
'formtarget',
|
|
51
51
|
'frameborder',
|
|
52
52
|
'headers',
|
|
53
|
-
'height',
|
|
54
|
-
'hidden',
|
|
53
|
+
// 'height',
|
|
54
|
+
// 'hidden',
|
|
55
55
|
'high',
|
|
56
56
|
'href',
|
|
57
57
|
'hreflang',
|
|
58
58
|
'htmlfor',
|
|
59
59
|
'httpequiv',
|
|
60
|
-
'id',
|
|
60
|
+
// 'id',
|
|
61
61
|
'inputmode',
|
|
62
62
|
'integrity',
|
|
63
63
|
'is',
|
|
64
|
-
'key',
|
|
64
|
+
// 'key',
|
|
65
65
|
'kind',
|
|
66
|
-
'label',
|
|
66
|
+
// 'label',
|
|
67
67
|
'lang',
|
|
68
68
|
'list',
|
|
69
69
|
'loading',
|
|
@@ -77,23 +77,23 @@ const STANDARD_HTML_ATTRIBUTES = new Set([
|
|
|
77
77
|
'minlength',
|
|
78
78
|
'multiple',
|
|
79
79
|
'muted',
|
|
80
|
-
'name',
|
|
80
|
+
// 'name',
|
|
81
81
|
'nomodule',
|
|
82
82
|
'nonce',
|
|
83
83
|
'novalidate',
|
|
84
84
|
'open',
|
|
85
85
|
'optimum',
|
|
86
86
|
'pattern',
|
|
87
|
-
'placeholder',
|
|
87
|
+
// 'placeholder',
|
|
88
88
|
'playsinline',
|
|
89
89
|
'poster',
|
|
90
90
|
'preload',
|
|
91
|
-
'readonly',
|
|
91
|
+
// 'readonly',
|
|
92
92
|
'referrerpolicy',
|
|
93
|
-
'rel',
|
|
93
|
+
// 'rel',
|
|
94
94
|
'required',
|
|
95
95
|
'reversed',
|
|
96
|
-
'role',
|
|
96
|
+
// 'role',
|
|
97
97
|
'rows',
|
|
98
98
|
'rowspan',
|
|
99
99
|
'sandbox',
|
|
@@ -114,12 +114,12 @@ const STANDARD_HTML_ATTRIBUTES = new Set([
|
|
|
114
114
|
'style',
|
|
115
115
|
'tabindex',
|
|
116
116
|
'target',
|
|
117
|
-
'title',
|
|
117
|
+
// 'title',
|
|
118
118
|
'translate',
|
|
119
119
|
'type',
|
|
120
120
|
'usemap',
|
|
121
|
-
'value',
|
|
122
|
-
'width',
|
|
121
|
+
// 'value',
|
|
122
|
+
// 'width',
|
|
123
123
|
'wrap',
|
|
124
124
|
// Common DOM event handler props
|
|
125
125
|
'onabort',
|
|
@@ -204,13 +204,22 @@ const STANDARD_HTML_ATTRIBUTES = new Set([
|
|
|
204
204
|
'onwaiting',
|
|
205
205
|
'onwheel',
|
|
206
206
|
]);
|
|
207
|
+
const CTA_EVENTS = new Set([
|
|
208
|
+
'onClick',
|
|
209
|
+
'onChange',
|
|
210
|
+
'onInput',
|
|
211
|
+
'onSubmit',
|
|
212
|
+
'onSelect',
|
|
213
|
+
'onKeyDown',
|
|
214
|
+
'onKeyUp',
|
|
215
|
+
'onBlur',
|
|
216
|
+
]);
|
|
207
217
|
const isStandardHtmlAttribute = (name) => {
|
|
208
218
|
const normalizedName = name.toLowerCase();
|
|
209
|
-
if (normalizedName.startsWith('aria-')
|
|
210
|
-
normalizedName.startsWith('data-')) {
|
|
219
|
+
if (normalizedName.startsWith('aria-')) {
|
|
211
220
|
return true;
|
|
212
221
|
}
|
|
213
|
-
return
|
|
222
|
+
return EXCLUDE_HTML_ATTRIBUTES.has(normalizedName);
|
|
214
223
|
};
|
|
215
224
|
const normalizeAttributeName = (name) => name.replace(/[-_]/g, '').toLowerCase();
|
|
216
225
|
const matchesAttributeName = (name, target) => normalizeAttributeName(name) === normalizeAttributeName(target);
|
|
@@ -294,26 +303,27 @@ const getExpressionKind = (expression) => {
|
|
|
294
303
|
}
|
|
295
304
|
return 'expression';
|
|
296
305
|
};
|
|
297
|
-
export const
|
|
306
|
+
export const extractCtaInfoFromNode = (node) => {
|
|
298
307
|
const attributes = Node.isJsxElement(node)
|
|
299
308
|
? node.getOpeningElement().getAttributes()
|
|
300
309
|
: node.getAttributes();
|
|
301
|
-
const
|
|
302
|
-
attribute.getNameNode().getText()
|
|
303
|
-
if (!
|
|
310
|
+
const ctaAttribute = attributes.find((attribute) => Node.isJsxAttribute(attribute) &&
|
|
311
|
+
CTA_EVENTS.has(attribute.getNameNode().getText()));
|
|
312
|
+
if (!ctaAttribute || !Node.isJsxAttribute(ctaAttribute)) {
|
|
304
313
|
return undefined;
|
|
305
314
|
}
|
|
306
|
-
const
|
|
315
|
+
const eventAttributeName = ctaAttribute.getNameNode().getText();
|
|
316
|
+
const initializer = ctaAttribute.getInitializer();
|
|
307
317
|
if (!initializer) {
|
|
308
318
|
return {
|
|
309
|
-
attribute:
|
|
319
|
+
attribute: eventAttributeName,
|
|
310
320
|
expression: 'true',
|
|
311
321
|
kind: 'boolean-shorthand',
|
|
312
322
|
};
|
|
313
323
|
}
|
|
314
324
|
if (Node.isStringLiteral(initializer)) {
|
|
315
325
|
return {
|
|
316
|
-
attribute:
|
|
326
|
+
attribute: eventAttributeName,
|
|
317
327
|
expression: initializer.getLiteralText(),
|
|
318
328
|
kind: 'string-literal',
|
|
319
329
|
};
|
|
@@ -324,13 +334,13 @@ export const extractOnClickInfoFromNode = (node) => {
|
|
|
324
334
|
return undefined;
|
|
325
335
|
}
|
|
326
336
|
return {
|
|
327
|
-
attribute:
|
|
337
|
+
attribute: eventAttributeName,
|
|
328
338
|
expression: expression.getText(),
|
|
329
339
|
kind: getExpressionKind(expression),
|
|
330
340
|
};
|
|
331
341
|
}
|
|
332
342
|
return {
|
|
333
|
-
attribute:
|
|
343
|
+
attribute: eventAttributeName,
|
|
334
344
|
expression: initializer.getText(),
|
|
335
345
|
kind: 'expression',
|
|
336
346
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsc-typescript-ast-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"mcpName": "io.github.jscoobyced/jsc-typescript-ast-mcp",
|
|
5
5
|
"description": "A Model Context Protocol (MCP) server that provides an abstract syntax tree (AST) representation of TypeScript code using the ts-morph library. It allows clients to analyze and manipulate TypeScript code structures, making it easier for AI models to understand and work with TypeScript projects. You can create a JSON representation of your React components, and use it for various purposes such as documentation, code analysis, or even generating new code based on existing components.",
|
|
6
6
|
"main": "dist/index.js",
|