@reactgraph/cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +319 -0
- package/bun.lock +527 -0
- package/dist/cli/components/IndexProgress.d.ts +18 -0
- package/dist/cli/components/IndexProgress.d.ts.map +1 -0
- package/dist/cli/components/IndexProgress.js +26 -0
- package/dist/cli/components/IndexProgress.js.map +1 -0
- package/dist/cli/components/InitResult.d.ts +7 -0
- package/dist/cli/components/InitResult.d.ts.map +1 -0
- package/dist/cli/components/InitResult.js +6 -0
- package/dist/cli/components/InitResult.js.map +1 -0
- package/dist/cli/index-cmd.d.ts +7 -0
- package/dist/cli/index-cmd.d.ts.map +1 -0
- package/dist/cli/index-cmd.js +28 -0
- package/dist/cli/index-cmd.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +81 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +8 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +77 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/serve.d.ts +2 -0
- package/dist/cli/serve.d.ts.map +1 -0
- package/dist/cli/serve.js +28 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/unused.d.ts +2 -0
- package/dist/cli/unused.d.ts.map +1 -0
- package/dist/cli/unused.js +56 -0
- package/dist/cli/unused.js.map +1 -0
- package/dist/graph/graph.d.ts +30 -0
- package/dist/graph/graph.d.ts.map +1 -0
- package/dist/graph/graph.js +166 -0
- package/dist/graph/graph.js.map +1 -0
- package/dist/graph/index.d.ts +5 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +5 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/schema.d.ts +33 -0
- package/dist/graph/schema.d.ts.map +1 -0
- package/dist/graph/schema.js +3 -0
- package/dist/graph/schema.js.map +1 -0
- package/dist/graph/serialize.d.ts +7 -0
- package/dist/graph/serialize.d.ts.map +1 -0
- package/dist/graph/serialize.js +39 -0
- package/dist/graph/serialize.js.map +1 -0
- package/dist/graph/traverse.d.ts +14 -0
- package/dist/graph/traverse.d.ts.map +1 -0
- package/dist/graph/traverse.js +50 -0
- package/dist/graph/traverse.js.map +1 -0
- package/dist/mcp/formatter.d.ts +26 -0
- package/dist/mcp/formatter.d.ts.map +1 -0
- package/dist/mcp/formatter.js +691 -0
- package/dist/mcp/formatter.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +45 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +136 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/output/ai-context.d.ts +7 -0
- package/dist/output/ai-context.d.ts.map +1 -0
- package/dist/output/ai-context.js +26 -0
- package/dist/output/ai-context.js.map +1 -0
- package/dist/parser/extractors/api-calls.d.ts +15 -0
- package/dist/parser/extractors/api-calls.d.ts.map +1 -0
- package/dist/parser/extractors/api-calls.js +168 -0
- package/dist/parser/extractors/api-calls.js.map +1 -0
- package/dist/parser/extractors/components.d.ts +5 -0
- package/dist/parser/extractors/components.d.ts.map +1 -0
- package/dist/parser/extractors/components.js +236 -0
- package/dist/parser/extractors/components.js.map +1 -0
- package/dist/parser/extractors/context.d.ts +14 -0
- package/dist/parser/extractors/context.d.ts.map +1 -0
- package/dist/parser/extractors/context.js +196 -0
- package/dist/parser/extractors/context.js.map +1 -0
- package/dist/parser/extractors/effects.d.ts +14 -0
- package/dist/parser/extractors/effects.d.ts.map +1 -0
- package/dist/parser/extractors/effects.js +175 -0
- package/dist/parser/extractors/effects.js.map +1 -0
- package/dist/parser/extractors/hooks.d.ts +5 -0
- package/dist/parser/extractors/hooks.d.ts.map +1 -0
- package/dist/parser/extractors/hooks.js +242 -0
- package/dist/parser/extractors/hooks.js.map +1 -0
- package/dist/parser/extractors/imports.d.ts +6 -0
- package/dist/parser/extractors/imports.d.ts.map +1 -0
- package/dist/parser/extractors/imports.js +148 -0
- package/dist/parser/extractors/imports.js.map +1 -0
- package/dist/parser/extractors/index.d.ts +12 -0
- package/dist/parser/extractors/index.d.ts.map +1 -0
- package/dist/parser/extractors/index.js +11 -0
- package/dist/parser/extractors/index.js.map +1 -0
- package/dist/parser/extractors/jsx-tree.d.ts +5 -0
- package/dist/parser/extractors/jsx-tree.d.ts.map +1 -0
- package/dist/parser/extractors/jsx-tree.js +226 -0
- package/dist/parser/extractors/jsx-tree.js.map +1 -0
- package/dist/parser/extractors/routes.d.ts +13 -0
- package/dist/parser/extractors/routes.d.ts.map +1 -0
- package/dist/parser/extractors/routes.js +275 -0
- package/dist/parser/extractors/routes.js.map +1 -0
- package/dist/parser/extractors/state.d.ts +14 -0
- package/dist/parser/extractors/state.d.ts.map +1 -0
- package/dist/parser/extractors/state.js +368 -0
- package/dist/parser/extractors/state.js.map +1 -0
- package/dist/parser/extractors/types.d.ts +22 -0
- package/dist/parser/extractors/types.d.ts.map +1 -0
- package/dist/parser/extractors/types.js +51 -0
- package/dist/parser/extractors/types.js.map +1 -0
- package/dist/parser/indexer.d.ts +14 -0
- package/dist/parser/indexer.d.ts.map +1 -0
- package/dist/parser/indexer.js +167 -0
- package/dist/parser/indexer.js.map +1 -0
- package/dist/parser/pipeline.d.ts +16 -0
- package/dist/parser/pipeline.d.ts.map +1 -0
- package/dist/parser/pipeline.js +63 -0
- package/dist/parser/pipeline.js.map +1 -0
- package/dist/parser/setup.d.ts +4 -0
- package/dist/parser/setup.d.ts.map +1 -0
- package/dist/parser/setup.js +29 -0
- package/dist/parser/setup.js.map +1 -0
- package/dist/parser/walker.d.ts +6 -0
- package/dist/parser/walker.d.ts.map +1 -0
- package/dist/parser/walker.js +45 -0
- package/dist/parser/walker.js.map +1 -0
- package/dist/watcher.d.ts +12 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +72 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +51 -0
- package/src/cli/components/IndexProgress.tsx +79 -0
- package/src/cli/components/InitResult.tsx +28 -0
- package/src/cli/index-cmd.ts +41 -0
- package/src/cli/index.ts +92 -0
- package/src/cli/init.ts +97 -0
- package/src/cli/serve.ts +29 -0
- package/src/cli/unused.ts +88 -0
- package/src/graph/graph.ts +179 -0
- package/src/graph/index.ts +4 -0
- package/src/graph/schema.ts +68 -0
- package/src/graph/serialize.ts +40 -0
- package/src/graph/traverse.ts +66 -0
- package/src/mcp/formatter.ts +757 -0
- package/src/mcp/server.ts +59 -0
- package/src/mcp/tools.ts +154 -0
- package/src/output/ai-context.ts +29 -0
- package/src/parser/extractors/api-calls.ts +192 -0
- package/src/parser/extractors/components.ts +273 -0
- package/src/parser/extractors/context.ts +216 -0
- package/src/parser/extractors/effects.ts +205 -0
- package/src/parser/extractors/hooks.ts +268 -0
- package/src/parser/extractors/imports.ts +192 -0
- package/src/parser/extractors/index.ts +11 -0
- package/src/parser/extractors/jsx-tree.ts +271 -0
- package/src/parser/extractors/routes.ts +331 -0
- package/src/parser/extractors/state.ts +392 -0
- package/src/parser/extractors/types.ts +71 -0
- package/src/parser/indexer.ts +197 -0
- package/src/parser/pipeline.ts +89 -0
- package/src/parser/setup.ts +33 -0
- package/src/parser/walker.ts +61 -0
- package/src/watcher.ts +91 -0
- package/templates/CLAUDE.md +7 -0
- package/tests/extractors.test.ts +164 -0
- package/tests/fixtures/basic/src/App.tsx +12 -0
- package/tests/fixtures/basic/src/components/Dashboard.tsx +24 -0
- package/tests/fixtures/basic/src/components/MetricsCard.tsx +15 -0
- package/tests/fixtures/basic/src/components/Sidebar.tsx +20 -0
- package/tests/fixtures/basic/src/contexts/ThemeContext.tsx +16 -0
- package/tests/fixtures/basic/src/hooks/useAuth.ts +25 -0
- package/tests/fixtures/basic/src/stores/authStore.ts +15 -0
- package/tests/fixtures/basic/src/utils.ts +7 -0
- package/tests/graph.test.ts +91 -0
- package/tests/phase2.test.ts +309 -0
- package/tests/smoke.test.ts +77 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { nodeId, isPascalCase, findAll } from './types.js';
|
|
2
|
+
export function extractComponents(tree, filePath, sourceCode, _existingNodes) {
|
|
3
|
+
const nodes = [];
|
|
4
|
+
const edges = [];
|
|
5
|
+
const root = tree.rootNode;
|
|
6
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
7
|
+
const child = root.child(i);
|
|
8
|
+
const component = detectComponent(child, filePath);
|
|
9
|
+
if (component)
|
|
10
|
+
nodes.push(component);
|
|
11
|
+
}
|
|
12
|
+
return { nodes, edges };
|
|
13
|
+
}
|
|
14
|
+
function detectComponent(node, filePath) {
|
|
15
|
+
// Pattern A: function_declaration
|
|
16
|
+
if (node.type === 'function_declaration') {
|
|
17
|
+
return tryFunctionComponent(node, filePath, 'none');
|
|
18
|
+
}
|
|
19
|
+
// Pattern: export_statement wrapping a declaration
|
|
20
|
+
if (node.type === 'export_statement') {
|
|
21
|
+
const isDefault = node.text.startsWith('export default');
|
|
22
|
+
const exportType = isDefault ? 'default' : 'named';
|
|
23
|
+
// export function Foo() { ... }
|
|
24
|
+
const funcDecl = findDirectChild(node, 'function_declaration');
|
|
25
|
+
if (funcDecl)
|
|
26
|
+
return tryFunctionComponent(funcDecl, filePath, exportType);
|
|
27
|
+
// export const Foo = ...
|
|
28
|
+
const lexDecl = findDirectChild(node, 'lexical_declaration');
|
|
29
|
+
if (lexDecl)
|
|
30
|
+
return tryLexicalComponent(lexDecl, filePath, exportType);
|
|
31
|
+
// export default function() { ... } (anonymous)
|
|
32
|
+
const funcExpr = findDirectChild(node, 'function');
|
|
33
|
+
if (funcExpr)
|
|
34
|
+
return tryFunctionComponent(funcExpr, filePath, exportType);
|
|
35
|
+
// export class Foo extends Component { ... }
|
|
36
|
+
const classDecl = findDirectChild(node, 'class_declaration');
|
|
37
|
+
if (classDecl)
|
|
38
|
+
return tryClassComponent(classDecl, filePath, exportType);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
// Pattern B: const Foo = () => ...
|
|
42
|
+
if (node.type === 'lexical_declaration') {
|
|
43
|
+
return tryLexicalComponent(node, filePath, 'none');
|
|
44
|
+
}
|
|
45
|
+
// Pattern D: class Foo extends Component
|
|
46
|
+
if (node.type === 'class_declaration') {
|
|
47
|
+
return tryClassComponent(node, filePath, 'none');
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function tryFunctionComponent(node, filePath, exportType) {
|
|
52
|
+
const nameNode = node.childForFieldName('name');
|
|
53
|
+
const name = nameNode?.text;
|
|
54
|
+
if (!name || !isPascalCase(name))
|
|
55
|
+
return null;
|
|
56
|
+
const body = node.childForFieldName('body');
|
|
57
|
+
if (!body || !containsJSX(body))
|
|
58
|
+
return null;
|
|
59
|
+
return {
|
|
60
|
+
id: nodeId(filePath, name),
|
|
61
|
+
kind: 'Component',
|
|
62
|
+
name,
|
|
63
|
+
file: filePath,
|
|
64
|
+
line: node.startPosition.row + 1,
|
|
65
|
+
exportType,
|
|
66
|
+
props: extractProps(node),
|
|
67
|
+
meta: {},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function tryLexicalComponent(node, filePath, exportType) {
|
|
71
|
+
const declarator = findDirectChild(node, 'variable_declarator');
|
|
72
|
+
if (!declarator)
|
|
73
|
+
return null;
|
|
74
|
+
const nameNode = declarator.childForFieldName('name');
|
|
75
|
+
const name = nameNode?.text;
|
|
76
|
+
if (!name || !isPascalCase(name))
|
|
77
|
+
return null;
|
|
78
|
+
const value = declarator.childForFieldName('value');
|
|
79
|
+
if (!value)
|
|
80
|
+
return null;
|
|
81
|
+
// Direct arrow function: const Foo = () => <div/>
|
|
82
|
+
if (value.type === 'arrow_function' || value.type === 'function_expression') {
|
|
83
|
+
const body = value.childForFieldName('body');
|
|
84
|
+
if (!body)
|
|
85
|
+
return null;
|
|
86
|
+
if (!containsJSX(body) && !isJSX(body))
|
|
87
|
+
return null;
|
|
88
|
+
return {
|
|
89
|
+
id: nodeId(filePath, name),
|
|
90
|
+
kind: 'Component',
|
|
91
|
+
name,
|
|
92
|
+
file: filePath,
|
|
93
|
+
line: node.startPosition.row + 1,
|
|
94
|
+
exportType,
|
|
95
|
+
props: extractPropsFromArrow(value),
|
|
96
|
+
meta: {},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Wrapped: const Foo = React.memo(() => ...) or memo(() => ...)
|
|
100
|
+
if (value.type === 'call_expression') {
|
|
101
|
+
const wrapper = getWrapperName(value);
|
|
102
|
+
if (wrapper) {
|
|
103
|
+
const innerFn = findInnerFunction(value);
|
|
104
|
+
if (innerFn) {
|
|
105
|
+
const body = innerFn.childForFieldName('body');
|
|
106
|
+
if (body && (containsJSX(body) || isJSX(body))) {
|
|
107
|
+
return {
|
|
108
|
+
id: nodeId(filePath, name),
|
|
109
|
+
kind: 'Component',
|
|
110
|
+
name,
|
|
111
|
+
file: filePath,
|
|
112
|
+
line: node.startPosition.row + 1,
|
|
113
|
+
exportType,
|
|
114
|
+
props: extractPropsFromArrow(innerFn),
|
|
115
|
+
meta: { wrapped: wrapper },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
function tryClassComponent(node, filePath, exportType) {
|
|
124
|
+
const nameNode = node.childForFieldName('name');
|
|
125
|
+
const name = nameNode?.text;
|
|
126
|
+
if (!name || !isPascalCase(name))
|
|
127
|
+
return null;
|
|
128
|
+
// Check extends React.Component or Component
|
|
129
|
+
const heritage = findDirectChild(node, 'class_heritage');
|
|
130
|
+
if (!heritage)
|
|
131
|
+
return null;
|
|
132
|
+
const extendsText = heritage.text;
|
|
133
|
+
if (!extendsText.includes('Component') && !extendsText.includes('PureComponent'))
|
|
134
|
+
return null;
|
|
135
|
+
// Check render() method has JSX
|
|
136
|
+
const body = node.childForFieldName('body');
|
|
137
|
+
if (!body)
|
|
138
|
+
return null;
|
|
139
|
+
const renderMethod = findAll(body, 'method_definition').find(m => {
|
|
140
|
+
const n = m.childForFieldName('name');
|
|
141
|
+
return n?.text === 'render';
|
|
142
|
+
});
|
|
143
|
+
if (!renderMethod || !containsJSX(renderMethod))
|
|
144
|
+
return null;
|
|
145
|
+
return {
|
|
146
|
+
id: nodeId(filePath, name),
|
|
147
|
+
kind: 'Component',
|
|
148
|
+
name,
|
|
149
|
+
file: filePath,
|
|
150
|
+
line: node.startPosition.row + 1,
|
|
151
|
+
exportType,
|
|
152
|
+
props: [],
|
|
153
|
+
meta: { isClass: true },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function containsJSX(node) {
|
|
157
|
+
if (isJSX(node))
|
|
158
|
+
return true;
|
|
159
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
160
|
+
if (containsJSX(node.child(i)))
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
function isJSX(node) {
|
|
166
|
+
return (node.type === 'jsx_element' ||
|
|
167
|
+
node.type === 'jsx_self_closing_element' ||
|
|
168
|
+
node.type === 'jsx_fragment');
|
|
169
|
+
}
|
|
170
|
+
function extractProps(funcNode) {
|
|
171
|
+
const params = funcNode.childForFieldName('parameters');
|
|
172
|
+
if (!params || params.childCount === 0)
|
|
173
|
+
return [];
|
|
174
|
+
return extractParamProps(params);
|
|
175
|
+
}
|
|
176
|
+
function extractPropsFromArrow(arrowNode) {
|
|
177
|
+
const params = arrowNode.childForFieldName('parameters');
|
|
178
|
+
if (!params)
|
|
179
|
+
return [];
|
|
180
|
+
return extractParamProps(params);
|
|
181
|
+
}
|
|
182
|
+
function extractParamProps(params) {
|
|
183
|
+
// Look for destructured first param: ({ a, b, c })
|
|
184
|
+
for (let i = 0; i < params.childCount; i++) {
|
|
185
|
+
const param = params.child(i);
|
|
186
|
+
// required_parameter or just object_pattern
|
|
187
|
+
const objPattern = param.type === 'object_pattern'
|
|
188
|
+
? param
|
|
189
|
+
: findDirectChild(param, 'object_pattern');
|
|
190
|
+
if (objPattern) {
|
|
191
|
+
const props = [];
|
|
192
|
+
for (let j = 0; j < objPattern.childCount; j++) {
|
|
193
|
+
const child = objPattern.child(j);
|
|
194
|
+
if (child.type === 'shorthand_property_identifier_pattern' || child.type === 'shorthand_property_identifier') {
|
|
195
|
+
props.push(child.text);
|
|
196
|
+
}
|
|
197
|
+
else if (child.type === 'pair_pattern') {
|
|
198
|
+
const key = child.childForFieldName('key');
|
|
199
|
+
if (key)
|
|
200
|
+
props.push(key.text);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return props;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
function getWrapperName(callNode) {
|
|
209
|
+
const fn = callNode.childForFieldName('function');
|
|
210
|
+
if (!fn)
|
|
211
|
+
return null;
|
|
212
|
+
const WRAPPERS = ['memo', 'React.memo', 'forwardRef', 'React.forwardRef'];
|
|
213
|
+
if (WRAPPERS.includes(fn.text))
|
|
214
|
+
return fn.text;
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
function findInnerFunction(callNode) {
|
|
218
|
+
const args = callNode.childForFieldName('arguments');
|
|
219
|
+
if (!args)
|
|
220
|
+
return null;
|
|
221
|
+
for (let i = 0; i < args.childCount; i++) {
|
|
222
|
+
const child = args.child(i);
|
|
223
|
+
if (child.type === 'arrow_function' || child.type === 'function_expression') {
|
|
224
|
+
return child;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
function findDirectChild(node, type) {
|
|
230
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
231
|
+
if (node.child(i).type === type)
|
|
232
|
+
return node.child(i);
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../../src/parser/extractors/components.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
import type { GraphNode } from '../../graph/schema.js';
|
|
3
|
+
import type { ExtractionResult } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Context extractor — detects React Context creation, Provider usage, and consumers.
|
|
6
|
+
*
|
|
7
|
+
* Patterns:
|
|
8
|
+
* const ThemeCtx = createContext(default) → Context node
|
|
9
|
+
* <ThemeCtx.Provider value={...}> → provides edge
|
|
10
|
+
* useContext(ThemeCtx) → consumes edge
|
|
11
|
+
* <ThemeCtx.Consumer>{fn}</ThemeCtx.Consumer> → consumes edge
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractContext(tree: Parser.Tree, filePath: string, sourceCode: string, existingNodes: GraphNode[]): ExtractionResult;
|
|
14
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/parser/extractors/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,uBAAuB,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,IAAI,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,SAAS,EAAE,GACzB,gBAAgB,CAsIlB"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { nodeId, findAll, findEnclosingFunction } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Context extractor — detects React Context creation, Provider usage, and consumers.
|
|
4
|
+
*
|
|
5
|
+
* Patterns:
|
|
6
|
+
* const ThemeCtx = createContext(default) → Context node
|
|
7
|
+
* <ThemeCtx.Provider value={...}> → provides edge
|
|
8
|
+
* useContext(ThemeCtx) → consumes edge
|
|
9
|
+
* <ThemeCtx.Consumer>{fn}</ThemeCtx.Consumer> → consumes edge
|
|
10
|
+
*/
|
|
11
|
+
export function extractContext(tree, filePath, sourceCode, existingNodes) {
|
|
12
|
+
const nodes = [];
|
|
13
|
+
const edges = [];
|
|
14
|
+
const root = tree.rootNode;
|
|
15
|
+
// Build map of component/hook names in this file for edge sources
|
|
16
|
+
const functionNodes = new Map();
|
|
17
|
+
for (const n of existingNodes) {
|
|
18
|
+
if (n.file === filePath && (n.kind === 'Component' || n.kind === 'Hook')) {
|
|
19
|
+
functionNodes.set(n.name, n);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Pass 1: Find context creation — const Ctx = createContext(...)
|
|
23
|
+
const contextNames = new Map(); // variable name → node id
|
|
24
|
+
for (let i = 0; i < root.childCount; i++) {
|
|
25
|
+
const child = root.child(i);
|
|
26
|
+
const ctx = detectContextCreation(child, filePath);
|
|
27
|
+
if (ctx) {
|
|
28
|
+
nodes.push(ctx);
|
|
29
|
+
contextNames.set(ctx.name, ctx.id);
|
|
30
|
+
}
|
|
31
|
+
// Also check inside export statements
|
|
32
|
+
if (child.type === 'export_statement') {
|
|
33
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
34
|
+
const inner = child.child(j);
|
|
35
|
+
const ctx2 = detectContextCreation(inner, filePath);
|
|
36
|
+
if (ctx2) {
|
|
37
|
+
ctx2.exportType = child.text.startsWith('export default') ? 'default' : 'named';
|
|
38
|
+
nodes.push(ctx2);
|
|
39
|
+
contextNames.set(ctx2.name, ctx2.id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Also track context names imported from other files
|
|
45
|
+
// We look for any identifier used in useContext() or .Provider that we can resolve
|
|
46
|
+
const importedContexts = buildImportedContextMap(root, existingNodes);
|
|
47
|
+
// Pass 2: Find Provider usage in JSX — <Ctx.Provider value={...}>
|
|
48
|
+
const jsxOpenings = findAll(root, 'jsx_opening_element');
|
|
49
|
+
const jsxSelfClosing = findAll(root, 'jsx_self_closing_element');
|
|
50
|
+
for (const jsx of [...jsxOpenings, ...jsxSelfClosing]) {
|
|
51
|
+
const nameNode = jsx.childForFieldName('name') ?? jsx.child(1);
|
|
52
|
+
if (!nameNode || nameNode.type !== 'member_expression')
|
|
53
|
+
continue;
|
|
54
|
+
const prop = nameNode.childForFieldName('property');
|
|
55
|
+
if (prop?.text !== 'Provider')
|
|
56
|
+
continue;
|
|
57
|
+
const obj = nameNode.childForFieldName('object');
|
|
58
|
+
if (!obj)
|
|
59
|
+
continue;
|
|
60
|
+
const ctxName = obj.text;
|
|
61
|
+
const ctxId = contextNames.get(ctxName) ?? importedContexts.get(ctxName) ?? `unresolved:${ctxName}`;
|
|
62
|
+
// Find enclosing component
|
|
63
|
+
const enclosingFn = findEnclosingFunction(jsx);
|
|
64
|
+
if (!enclosingFn)
|
|
65
|
+
continue;
|
|
66
|
+
const enclosingName = getFunctionName(enclosingFn);
|
|
67
|
+
if (!enclosingName)
|
|
68
|
+
continue;
|
|
69
|
+
const enclosingNode = functionNodes.get(enclosingName);
|
|
70
|
+
if (!enclosingNode)
|
|
71
|
+
continue;
|
|
72
|
+
edges.push({
|
|
73
|
+
source: enclosingNode.id,
|
|
74
|
+
target: ctxId,
|
|
75
|
+
kind: 'provides',
|
|
76
|
+
meta: {},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Pass 3: Find useContext() consumers
|
|
80
|
+
const callExprs = findAll(root, 'call_expression');
|
|
81
|
+
for (const call of callExprs) {
|
|
82
|
+
const callee = call.childForFieldName('function');
|
|
83
|
+
if (!callee || callee.text !== 'useContext')
|
|
84
|
+
continue;
|
|
85
|
+
const args = call.childForFieldName('arguments');
|
|
86
|
+
if (!args || args.childCount < 2)
|
|
87
|
+
continue;
|
|
88
|
+
const ctxArg = args.child(1); // skip "("
|
|
89
|
+
if (!ctxArg)
|
|
90
|
+
continue;
|
|
91
|
+
const ctxName = ctxArg.text;
|
|
92
|
+
const ctxId = contextNames.get(ctxName) ?? importedContexts.get(ctxName) ?? `unresolved:${ctxName}`;
|
|
93
|
+
const enclosingFn = findEnclosingFunction(call);
|
|
94
|
+
if (!enclosingFn)
|
|
95
|
+
continue;
|
|
96
|
+
const enclosingName = getFunctionName(enclosingFn);
|
|
97
|
+
if (!enclosingName)
|
|
98
|
+
continue;
|
|
99
|
+
const enclosingNode = functionNodes.get(enclosingName);
|
|
100
|
+
if (!enclosingNode)
|
|
101
|
+
continue;
|
|
102
|
+
edges.push({
|
|
103
|
+
source: enclosingNode.id,
|
|
104
|
+
target: ctxId,
|
|
105
|
+
kind: 'consumes',
|
|
106
|
+
meta: {},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Pass 4: Find <Ctx.Consumer> pattern (legacy)
|
|
110
|
+
for (const jsx of [...jsxOpenings, ...jsxSelfClosing]) {
|
|
111
|
+
const nameNode = jsx.childForFieldName('name') ?? jsx.child(1);
|
|
112
|
+
if (!nameNode || nameNode.type !== 'member_expression')
|
|
113
|
+
continue;
|
|
114
|
+
const prop = nameNode.childForFieldName('property');
|
|
115
|
+
if (prop?.text !== 'Consumer')
|
|
116
|
+
continue;
|
|
117
|
+
const obj = nameNode.childForFieldName('object');
|
|
118
|
+
if (!obj)
|
|
119
|
+
continue;
|
|
120
|
+
const ctxName = obj.text;
|
|
121
|
+
const ctxId = contextNames.get(ctxName) ?? importedContexts.get(ctxName) ?? `unresolved:${ctxName}`;
|
|
122
|
+
const enclosingFn = findEnclosingFunction(jsx);
|
|
123
|
+
if (!enclosingFn)
|
|
124
|
+
continue;
|
|
125
|
+
const enclosingName = getFunctionName(enclosingFn);
|
|
126
|
+
if (!enclosingName)
|
|
127
|
+
continue;
|
|
128
|
+
const enclosingNode = functionNodes.get(enclosingName);
|
|
129
|
+
if (!enclosingNode)
|
|
130
|
+
continue;
|
|
131
|
+
edges.push({
|
|
132
|
+
source: enclosingNode.id,
|
|
133
|
+
target: ctxId,
|
|
134
|
+
kind: 'consumes',
|
|
135
|
+
meta: { legacy: true },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return { nodes, edges };
|
|
139
|
+
}
|
|
140
|
+
function detectContextCreation(node, filePath) {
|
|
141
|
+
if (node.type !== 'lexical_declaration')
|
|
142
|
+
return null;
|
|
143
|
+
const declarator = findDirectChild(node, 'variable_declarator');
|
|
144
|
+
if (!declarator)
|
|
145
|
+
return null;
|
|
146
|
+
const nameNode = declarator.childForFieldName('name');
|
|
147
|
+
const value = declarator.childForFieldName('value');
|
|
148
|
+
if (!nameNode || !value)
|
|
149
|
+
return null;
|
|
150
|
+
if (value.type !== 'call_expression')
|
|
151
|
+
return null;
|
|
152
|
+
const callee = value.childForFieldName('function');
|
|
153
|
+
if (!callee)
|
|
154
|
+
return null;
|
|
155
|
+
// Match createContext or React.createContext
|
|
156
|
+
if (callee.text !== 'createContext' && callee.text !== 'React.createContext')
|
|
157
|
+
return null;
|
|
158
|
+
return {
|
|
159
|
+
id: nodeId(filePath, nameNode.text),
|
|
160
|
+
kind: 'Context',
|
|
161
|
+
name: nameNode.text,
|
|
162
|
+
file: filePath,
|
|
163
|
+
line: node.startPosition.row + 1,
|
|
164
|
+
exportType: 'none',
|
|
165
|
+
meta: {},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function buildImportedContextMap(root, existingNodes) {
|
|
169
|
+
const map = new Map();
|
|
170
|
+
for (const n of existingNodes) {
|
|
171
|
+
if (n.kind === 'Context') {
|
|
172
|
+
map.set(n.name, n.id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return map;
|
|
176
|
+
}
|
|
177
|
+
function getFunctionName(node) {
|
|
178
|
+
const nameField = node.childForFieldName('name');
|
|
179
|
+
if (nameField)
|
|
180
|
+
return nameField.text;
|
|
181
|
+
if (node.type === 'arrow_function' || node.type === 'function_expression') {
|
|
182
|
+
const parent = node.parent;
|
|
183
|
+
if (parent?.type === 'variable_declarator') {
|
|
184
|
+
return parent.childForFieldName('name')?.text ?? null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
function findDirectChild(node, type) {
|
|
190
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
191
|
+
if (node.child(i).type === type)
|
|
192
|
+
return node.child(i);
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/parser/extractors/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
import type { GraphNode } from '../../graph/schema.js';
|
|
3
|
+
import type { ExtractionResult } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Effects extractor — annotates existing component/hook nodes with useEffect metadata.
|
|
6
|
+
* Does not create new nodes — enriches existing ones.
|
|
7
|
+
*
|
|
8
|
+
* Captures:
|
|
9
|
+
* - dependency array contents (or 'mount-only' / 'every-render')
|
|
10
|
+
* - whether cleanup function exists
|
|
11
|
+
* - function calls inside the effect body
|
|
12
|
+
*/
|
|
13
|
+
export declare function extractEffects(tree: Parser.Tree, filePath: string, sourceCode: string, existingNodes: GraphNode[]): ExtractionResult;
|
|
14
|
+
//# sourceMappingURL=effects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.d.ts","sourceRoot":"","sources":["../../../src/parser/extractors/effects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,uBAAuB,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,IAAI,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,SAAS,EAAE,GACzB,gBAAgB,CA+DlB"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { findAll, findEnclosingFunction } from './types.js';
|
|
2
|
+
const EFFECT_HOOKS = new Set(['useEffect', 'useLayoutEffect', 'useInsertionEffect']);
|
|
3
|
+
/**
|
|
4
|
+
* Effects extractor — annotates existing component/hook nodes with useEffect metadata.
|
|
5
|
+
* Does not create new nodes — enriches existing ones.
|
|
6
|
+
*
|
|
7
|
+
* Captures:
|
|
8
|
+
* - dependency array contents (or 'mount-only' / 'every-render')
|
|
9
|
+
* - whether cleanup function exists
|
|
10
|
+
* - function calls inside the effect body
|
|
11
|
+
*/
|
|
12
|
+
export function extractEffects(tree, filePath, sourceCode, existingNodes) {
|
|
13
|
+
const edges = [];
|
|
14
|
+
const root = tree.rootNode;
|
|
15
|
+
// Build function map
|
|
16
|
+
const functionNodes = new Map();
|
|
17
|
+
for (const n of existingNodes) {
|
|
18
|
+
if (n.file === filePath && (n.kind === 'Component' || n.kind === 'Hook')) {
|
|
19
|
+
functionNodes.set(n.name, n);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const callExprs = findAll(root, 'call_expression');
|
|
23
|
+
for (const call of callExprs) {
|
|
24
|
+
const callee = call.childForFieldName('function');
|
|
25
|
+
if (!callee || !EFFECT_HOOKS.has(callee.text))
|
|
26
|
+
continue;
|
|
27
|
+
const enclosingFn = findEnclosingFunction(call);
|
|
28
|
+
if (!enclosingFn)
|
|
29
|
+
continue;
|
|
30
|
+
const enclosingName = getFunctionName(enclosingFn);
|
|
31
|
+
if (!enclosingName)
|
|
32
|
+
continue;
|
|
33
|
+
const enclosingNode = functionNodes.get(enclosingName);
|
|
34
|
+
if (!enclosingNode)
|
|
35
|
+
continue;
|
|
36
|
+
const args = call.childForFieldName('arguments');
|
|
37
|
+
if (!args)
|
|
38
|
+
continue;
|
|
39
|
+
// Extract callback (first arg) and deps array (second arg)
|
|
40
|
+
const { callback, depsNode } = extractEffectArgs(args);
|
|
41
|
+
// Parse dependency array
|
|
42
|
+
let deps;
|
|
43
|
+
if (!depsNode) {
|
|
44
|
+
deps = 'every-render';
|
|
45
|
+
}
|
|
46
|
+
else if (depsNode.type === 'array' && depsNode.childCount <= 2) {
|
|
47
|
+
// [ ] — just brackets, empty array
|
|
48
|
+
deps = 'mount-only';
|
|
49
|
+
}
|
|
50
|
+
else if (depsNode.type === 'array') {
|
|
51
|
+
deps = extractArrayIdentifiers(depsNode);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
deps = 'unknown';
|
|
55
|
+
}
|
|
56
|
+
// Check for cleanup (return statement in callback returning a function)
|
|
57
|
+
const hasCleanup = callback ? detectCleanup(callback) : false;
|
|
58
|
+
// Extract function calls inside the effect body
|
|
59
|
+
const triggers = callback ? extractTriggers(callback) : [];
|
|
60
|
+
// Store as metadata on the enclosing node
|
|
61
|
+
if (!enclosingNode.meta.effects) {
|
|
62
|
+
enclosingNode.meta.effects = [];
|
|
63
|
+
}
|
|
64
|
+
enclosingNode.meta.effects.push({
|
|
65
|
+
type: callee.text,
|
|
66
|
+
deps,
|
|
67
|
+
hasCleanup,
|
|
68
|
+
triggers,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return { nodes: [], edges };
|
|
72
|
+
}
|
|
73
|
+
function extractEffectArgs(args) {
|
|
74
|
+
let callback = null;
|
|
75
|
+
let depsNode = null;
|
|
76
|
+
let argIndex = 0;
|
|
77
|
+
for (let i = 0; i < args.childCount; i++) {
|
|
78
|
+
const child = args.child(i);
|
|
79
|
+
if (child.type === '(' || child.type === ')' || child.type === ',')
|
|
80
|
+
continue;
|
|
81
|
+
if (argIndex === 0) {
|
|
82
|
+
callback = child;
|
|
83
|
+
}
|
|
84
|
+
else if (argIndex === 1) {
|
|
85
|
+
depsNode = child;
|
|
86
|
+
}
|
|
87
|
+
argIndex++;
|
|
88
|
+
}
|
|
89
|
+
return { callback, depsNode };
|
|
90
|
+
}
|
|
91
|
+
function extractArrayIdentifiers(arrayNode) {
|
|
92
|
+
const ids = [];
|
|
93
|
+
for (let i = 0; i < arrayNode.childCount; i++) {
|
|
94
|
+
const child = arrayNode.child(i);
|
|
95
|
+
if (child.type === 'identifier') {
|
|
96
|
+
ids.push(child.text);
|
|
97
|
+
}
|
|
98
|
+
else if (child.type === 'member_expression') {
|
|
99
|
+
ids.push(child.text);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return ids;
|
|
103
|
+
}
|
|
104
|
+
function detectCleanup(callback) {
|
|
105
|
+
// Look for a return statement that returns a function
|
|
106
|
+
const returns = findAll(callback, 'return_statement');
|
|
107
|
+
for (const ret of returns) {
|
|
108
|
+
// Make sure this return is directly in the callback, not in a nested function
|
|
109
|
+
const enclosing = findEnclosingFunction(ret);
|
|
110
|
+
if (enclosing && enclosing !== callback) {
|
|
111
|
+
// Check if enclosing is the callback itself
|
|
112
|
+
// For arrow functions, the callback IS the arrow_function
|
|
113
|
+
if (enclosing.parent !== callback && enclosing !== callback)
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const val = ret.child(1);
|
|
117
|
+
if (!val)
|
|
118
|
+
continue;
|
|
119
|
+
if (val.type === 'arrow_function' ||
|
|
120
|
+
val.type === 'function_expression' ||
|
|
121
|
+
val.type === 'identifier' // return cleanup;
|
|
122
|
+
) {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
function extractTriggers(callback) {
|
|
129
|
+
const triggers = [];
|
|
130
|
+
const calls = findAll(callback, 'call_expression');
|
|
131
|
+
for (const call of calls) {
|
|
132
|
+
const callee = call.childForFieldName('function');
|
|
133
|
+
if (!callee)
|
|
134
|
+
continue;
|
|
135
|
+
// Skip nested function definitions
|
|
136
|
+
const enclosing = findEnclosingFunction(call);
|
|
137
|
+
if (enclosing && enclosing !== callback && !isDirectChild(enclosing, callback))
|
|
138
|
+
continue;
|
|
139
|
+
if (callee.type === 'identifier') {
|
|
140
|
+
triggers.push(callee.text);
|
|
141
|
+
}
|
|
142
|
+
else if (callee.type === 'member_expression') {
|
|
143
|
+
triggers.push(callee.text);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return triggers;
|
|
147
|
+
}
|
|
148
|
+
function isDirectChild(inner, outer) {
|
|
149
|
+
let current = inner.parent;
|
|
150
|
+
while (current) {
|
|
151
|
+
if (current === outer)
|
|
152
|
+
return true;
|
|
153
|
+
if (current.type === 'arrow_function' ||
|
|
154
|
+
current.type === 'function_expression' ||
|
|
155
|
+
current.type === 'function_declaration') {
|
|
156
|
+
// Hit another function boundary — not direct
|
|
157
|
+
return current === outer;
|
|
158
|
+
}
|
|
159
|
+
current = current.parent;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
function getFunctionName(node) {
|
|
164
|
+
const nameField = node.childForFieldName('name');
|
|
165
|
+
if (nameField)
|
|
166
|
+
return nameField.text;
|
|
167
|
+
if (node.type === 'arrow_function' || node.type === 'function_expression') {
|
|
168
|
+
const parent = node.parent;
|
|
169
|
+
if (parent?.type === 'variable_declarator') {
|
|
170
|
+
return parent.childForFieldName('name')?.text ?? null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=effects.js.map
|