autosnippet 3.2.6 → 3.2.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 +16 -1
- package/bin/cli.js +7 -0
- package/dashboard/dist/assets/index-D5jiDBQG.css +1 -0
- package/dashboard/dist/assets/{index-DfHY_3ln.js → index-e5OKj-Ni.js} +38 -38
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +3 -3
- package/lib/core/AstAnalyzer.js +26 -4
- package/lib/core/analysis/CallEdgeResolver.js +402 -0
- package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
- package/lib/core/analysis/CallSiteExtractor.js +629 -0
- package/lib/core/analysis/DataFlowInferrer.js +57 -0
- package/lib/core/analysis/ImportPathResolver.js +189 -0
- package/lib/core/analysis/ImportRecord.js +105 -0
- package/lib/core/analysis/SymbolTableBuilder.js +211 -0
- package/lib/core/ast/ProjectGraph.js +8 -0
- package/lib/core/ast/lang-dart.js +352 -5
- package/lib/core/ast/lang-go.js +212 -10
- package/lib/core/ast/lang-java.js +205 -1
- package/lib/core/ast/lang-kotlin.js +330 -1
- package/lib/core/ast/lang-python.js +31 -2
- package/lib/core/ast/lang-rust.js +284 -3
- package/lib/core/ast/lang-swift.js +180 -1
- package/lib/core/ast/lang-typescript.js +290 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +21 -0
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +5 -4
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +70 -4
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +95 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +17 -6
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/task.js +182 -10
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/HttpServer.js +4 -0
- package/lib/http/routes/remote.js +1138 -0
- package/lib/http/routes/task.js +1 -0
- package/lib/infrastructure/database/migrations/003_add_remote_commands.js +27 -0
- package/lib/injection/ServiceContainer.js +6 -11
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
- package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
- package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
- package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
- package/lib/service/chat/ChatAgent.js +1 -1
- package/lib/service/chat/ChatAgentPrompts.js +13 -1
- package/lib/service/chat/ExplorationTracker.js +52 -8
- package/lib/service/chat/HandoffProtocol.js +19 -1
- package/lib/service/chat/WorkingMemory.js +3 -1
- package/lib/service/chat/memory/ActiveContext.js +3 -1
- package/lib/service/chat/memory/SessionStore.js +4 -3
- package/lib/service/chat/tools/ast-graph.js +229 -32
- package/lib/service/chat/tools/index.js +6 -1
- package/lib/service/chat/tools/infrastructure.js +5 -0
- package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
- package/lib/service/knowledge/CodeEntityGraph.js +327 -2
- package/lib/service/knowledge/KnowledgeService.js +5 -1
- package/lib/service/module/ModuleService.js +9 -0
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/package.json +12 -1
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module CallSiteExtractor
|
|
3
|
+
* @description Phase 5: 从 AST 中提取调用点 (Call Sites)
|
|
4
|
+
*
|
|
5
|
+
* 采用 Post-walk extraction(方案 B):在 walker 的 walk() 完成后,
|
|
6
|
+
* 通过二次遍历提取调用点,零修改现有 walker 逻辑。
|
|
7
|
+
*
|
|
8
|
+
* 职责:
|
|
9
|
+
* - 从 statement_block/block 中提取 call_expression / new_expression
|
|
10
|
+
* - 解析 callee、receiver、callType、argCount 等
|
|
11
|
+
* - 关联到所在的 className + methodName (上下文推断)
|
|
12
|
+
*
|
|
13
|
+
* 支持语言:
|
|
14
|
+
* - TypeScript / JavaScript / TSX (P0)
|
|
15
|
+
* - Python (P0)
|
|
16
|
+
* - Go / Java / Kotlin (P1 — via lang plugin extractCallSites)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} CallSite
|
|
21
|
+
* @property {string} callee — 被调用者名称 (尽可能 qualified)
|
|
22
|
+
* @property {string} callerMethod — 调用者所在函数
|
|
23
|
+
* @property {string|null} callerClass — 调用者所在类
|
|
24
|
+
* @property {'function'|'method'|'constructor'|'super'|'static'} callType
|
|
25
|
+
* @property {string|null} receiver — 接收者对象
|
|
26
|
+
* @property {string|null} receiverType — 接收者推断类型
|
|
27
|
+
* @property {number} argCount — 参数数量
|
|
28
|
+
* @property {number} line — 行号
|
|
29
|
+
* @property {boolean} isAwait — 是否 await
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
// ── TypeScript / JavaScript / TSX ──────────────────────────
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 从 TS/JS AST root 中提取所有调用点
|
|
36
|
+
* 使用 post-walk 策略,遍历已由 walker 收集的 methods/classes 来定位方法体,
|
|
37
|
+
* 然后从方法体中递归提取 call_expression / new_expression。
|
|
38
|
+
*
|
|
39
|
+
* @param {TreeSitterNode} root — AST root 节点
|
|
40
|
+
* @param {object} ctx — walker context (含 classes, methods, callSites, references 等)
|
|
41
|
+
* @param {string} lang — 语言标识
|
|
42
|
+
*/
|
|
43
|
+
export function extractCallSitesTS(root, ctx, lang) {
|
|
44
|
+
// 收集所有 function/method body 节点与其上下文
|
|
45
|
+
const scopes = _collectTSScopes(root);
|
|
46
|
+
|
|
47
|
+
for (const scope of scopes) {
|
|
48
|
+
_extractCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 收集 TS/JS 中所有函数/方法作用域
|
|
54
|
+
* 遍历 AST 找到 function_declaration / method_definition / arrow_function 等,
|
|
55
|
+
* 以及它们对应的 statement_block 和上下文信息。
|
|
56
|
+
*
|
|
57
|
+
* @param {TreeSitterNode} root
|
|
58
|
+
* @returns {Array<{ body: TreeSitterNode, className: string|null, methodName: string }>}
|
|
59
|
+
*/
|
|
60
|
+
function _collectTSScopes(root) {
|
|
61
|
+
const scopes = [];
|
|
62
|
+
|
|
63
|
+
function walk(node, className) {
|
|
64
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
65
|
+
const child = node.namedChild(i);
|
|
66
|
+
|
|
67
|
+
switch (child.type) {
|
|
68
|
+
case 'class_declaration':
|
|
69
|
+
case 'abstract_class_declaration': {
|
|
70
|
+
const name =
|
|
71
|
+
child.namedChildren.find(
|
|
72
|
+
(c) => c.type === 'type_identifier' || c.type === 'identifier'
|
|
73
|
+
)?.text || null;
|
|
74
|
+
const body = child.namedChildren.find((c) => c.type === 'class_body');
|
|
75
|
+
if (body && name) {
|
|
76
|
+
walk(body, name);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case 'method_definition': {
|
|
82
|
+
const name =
|
|
83
|
+
child.namedChildren.find(
|
|
84
|
+
(c) =>
|
|
85
|
+
c.type === 'property_identifier' ||
|
|
86
|
+
c.type === 'identifier' ||
|
|
87
|
+
c.type === 'computed_property_name'
|
|
88
|
+
)?.text || 'unknown';
|
|
89
|
+
const body = child.namedChildren.find((c) => c.type === 'statement_block');
|
|
90
|
+
if (body) {
|
|
91
|
+
scopes.push({ body, className, methodName: name });
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
case 'function_declaration': {
|
|
97
|
+
const name =
|
|
98
|
+
child.namedChildren.find((c) => c.type === 'identifier')?.text || 'unknown';
|
|
99
|
+
const body = child.namedChildren.find((c) => c.type === 'statement_block');
|
|
100
|
+
if (body) {
|
|
101
|
+
scopes.push({ body, className, methodName: name });
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
case 'lexical_declaration':
|
|
107
|
+
case 'variable_declaration': {
|
|
108
|
+
// const foo = () => { ... }
|
|
109
|
+
for (const decl of child.namedChildren) {
|
|
110
|
+
if (decl.type === 'variable_declarator') {
|
|
111
|
+
const nameNode = decl.namedChildren.find((c) => c.type === 'identifier');
|
|
112
|
+
const valueNode = decl.namedChildren.find(
|
|
113
|
+
(c) => c.type === 'arrow_function' || c.type === 'function'
|
|
114
|
+
);
|
|
115
|
+
if (nameNode && valueNode) {
|
|
116
|
+
const body = valueNode.namedChildren.find((c) => c.type === 'statement_block');
|
|
117
|
+
if (body) {
|
|
118
|
+
scopes.push({ body, className, methodName: nameNode.text });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case 'export_statement': {
|
|
127
|
+
// export 下面可能有 function_declaration / class_declaration
|
|
128
|
+
walk(child, className);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
default: {
|
|
133
|
+
// 递归进入其他有子节点的容器(但避免进入函数体)
|
|
134
|
+
if (
|
|
135
|
+
child.namedChildCount > 0 &&
|
|
136
|
+
!['statement_block', 'function_body', 'template_string'].includes(child.type)
|
|
137
|
+
) {
|
|
138
|
+
walk(child, className);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
walk(root, null);
|
|
146
|
+
return scopes;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 从方法体中递归提取调用点
|
|
151
|
+
*
|
|
152
|
+
* @param {TreeSitterNode} bodyNode — statement_block 节点
|
|
153
|
+
* @param {string|null} className — 所在类名
|
|
154
|
+
* @param {string} methodName — 所在方法名
|
|
155
|
+
* @param {object} ctx — walker context
|
|
156
|
+
*/
|
|
157
|
+
function _extractCallSitesFromBody(bodyNode, className, methodName, ctx) {
|
|
158
|
+
if (!bodyNode) return;
|
|
159
|
+
|
|
160
|
+
function walk(node, isAwaited) {
|
|
161
|
+
// 跳过语法错误节点 (Issue #17: 防御性处理)
|
|
162
|
+
if (!node || node.type === 'ERROR' || node.isMissing) return;
|
|
163
|
+
|
|
164
|
+
// await expression → 标记下一层的调用为 awaited
|
|
165
|
+
if (node.type === 'await_expression') {
|
|
166
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
167
|
+
walk(node.namedChild(i), true);
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (node.type === 'call_expression') {
|
|
173
|
+
const callSite = _parseTSCallExpression(node, className, methodName, isAwaited);
|
|
174
|
+
if (callSite) {
|
|
175
|
+
ctx.callSites.push(callSite);
|
|
176
|
+
}
|
|
177
|
+
// 继续遍历参数中的嵌套调用
|
|
178
|
+
const args = node.namedChildren.find((c) => c.type === 'arguments');
|
|
179
|
+
if (args) {
|
|
180
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
181
|
+
walk(args.namedChild(i), false);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (node.type === 'new_expression') {
|
|
188
|
+
const ctor = node.namedChildren.find(
|
|
189
|
+
(c) => c.type === 'identifier' || c.type === 'member_expression'
|
|
190
|
+
);
|
|
191
|
+
if (ctor) {
|
|
192
|
+
ctx.callSites.push({
|
|
193
|
+
callee: ctor.text,
|
|
194
|
+
callerMethod: methodName,
|
|
195
|
+
callerClass: className,
|
|
196
|
+
callType: 'constructor',
|
|
197
|
+
receiver: null,
|
|
198
|
+
receiverType: ctor.text,
|
|
199
|
+
argCount: _countArgs(node),
|
|
200
|
+
line: node.startPosition.row + 1,
|
|
201
|
+
isAwait: isAwaited,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// 继续遍历参数中的嵌套调用
|
|
205
|
+
const args = node.namedChildren.find((c) => c.type === 'arguments');
|
|
206
|
+
if (args) {
|
|
207
|
+
for (let i = 0; i < args.namedChildCount; i++) {
|
|
208
|
+
walk(args.namedChild(i), false);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// JSX/TSX: 组件渲染视为调用点 (Issue #13)
|
|
215
|
+
// <MyComponent /> 或 <MyComponent>...</MyComponent> → 视为 constructor 调用
|
|
216
|
+
if (node.type === 'jsx_self_closing_element' || node.type === 'jsx_opening_element') {
|
|
217
|
+
const tagNode =
|
|
218
|
+
node.namedChildren.find((c) => c.type === 'identifier' || c.type === 'jsx_identifier') ||
|
|
219
|
+
node.namedChildren.find((c) => c.type === 'member_expression' || c.type === 'jsx_member_expression');
|
|
220
|
+
if (tagNode) {
|
|
221
|
+
const tagName = tagNode.text;
|
|
222
|
+
// 仅大写开头为组件 (小写为 HTML 原生标签如 div, span)
|
|
223
|
+
if (tagName && /^[A-Z]/.test(tagName)) {
|
|
224
|
+
// 计算 JSX 属性数量作为 argCount
|
|
225
|
+
const attrNodes = node.namedChildren.filter(
|
|
226
|
+
(c) => c.type === 'jsx_attribute'
|
|
227
|
+
);
|
|
228
|
+
ctx.callSites.push({
|
|
229
|
+
callee: tagName,
|
|
230
|
+
callerMethod: methodName,
|
|
231
|
+
callerClass: className,
|
|
232
|
+
callType: 'constructor',
|
|
233
|
+
receiver: null,
|
|
234
|
+
receiverType: tagName,
|
|
235
|
+
argCount: attrNodes.length,
|
|
236
|
+
line: node.startPosition.row + 1,
|
|
237
|
+
isAwait: false,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// 继续遍历 JSX 表达式中的嵌套调用 (如 onClick={handleClick()})
|
|
242
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
243
|
+
const child = node.namedChild(i);
|
|
244
|
+
if (child.type === 'jsx_attribute') {
|
|
245
|
+
// 属性值中可能有嵌套调用: onClick={doSomething()}
|
|
246
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
247
|
+
walk(child.namedChild(j), false);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 递归子节点
|
|
255
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
256
|
+
walk(node.namedChild(i), false);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
walk(bodyNode, false);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 解析 TS/JS 的 call_expression 节点
|
|
265
|
+
*
|
|
266
|
+
* @param {TreeSitterNode} node — call_expression 节点
|
|
267
|
+
* @param {string|null} className — 所在类名
|
|
268
|
+
* @param {string} methodName — 所在方法名
|
|
269
|
+
* @param {boolean} isAwaited — 是否被 await
|
|
270
|
+
* @returns {CallSite|null}
|
|
271
|
+
*/
|
|
272
|
+
function _parseTSCallExpression(node, className, methodName, isAwaited) {
|
|
273
|
+
const func = node.namedChildren[0]; // call_expression 的第一个子节点是被调用者
|
|
274
|
+
if (!func) return null;
|
|
275
|
+
|
|
276
|
+
let callee;
|
|
277
|
+
let receiver = null;
|
|
278
|
+
let receiverType = null;
|
|
279
|
+
let callType;
|
|
280
|
+
|
|
281
|
+
if (func.type === 'member_expression') {
|
|
282
|
+
// obj.method() — method call
|
|
283
|
+
const object = func.namedChildren.find((c) => c.type !== 'property_identifier');
|
|
284
|
+
const prop = func.namedChildren.find((c) => c.type === 'property_identifier');
|
|
285
|
+
receiver = object?.text || null;
|
|
286
|
+
callee = prop?.text || func.text;
|
|
287
|
+
callType = 'method';
|
|
288
|
+
|
|
289
|
+
// 推断 receiverType
|
|
290
|
+
if (receiver === 'this' || receiver === 'self') {
|
|
291
|
+
receiverType = className;
|
|
292
|
+
} else if (receiver === 'super') {
|
|
293
|
+
callType = 'super';
|
|
294
|
+
receiverType = className; // 需要 CHA 进一步解析到父类
|
|
295
|
+
} else if (receiver && /^[A-Z]/.test(receiver)) {
|
|
296
|
+
// 静态调用推断 e.g. UserService.create()
|
|
297
|
+
receiverType = receiver;
|
|
298
|
+
callType = 'static';
|
|
299
|
+
} else if (receiver?.startsWith('this.')) {
|
|
300
|
+
// this.xxx.method() → xxx 可能是注入的 field
|
|
301
|
+
receiverType = null; // 后续由 CallEdgeResolver 从 properties 解析
|
|
302
|
+
}
|
|
303
|
+
} else if (func.type === 'identifier') {
|
|
304
|
+
// foo() — function call
|
|
305
|
+
callee = func.text;
|
|
306
|
+
callType = 'function';
|
|
307
|
+
} else if (func.type === 'super') {
|
|
308
|
+
// super() — constructor call
|
|
309
|
+
callee = 'super';
|
|
310
|
+
callType = 'super';
|
|
311
|
+
receiverType = className;
|
|
312
|
+
} else {
|
|
313
|
+
// 复杂表达式调用 (e.g. getFactory()(), callback())
|
|
314
|
+
callee = func.text?.slice(0, 80) || 'unknown';
|
|
315
|
+
callType = 'function';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 过滤噪声:跳过常见的内置/工具调用
|
|
319
|
+
if (_isNoiseCall(callee, receiver)) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
callee,
|
|
325
|
+
callerMethod: methodName,
|
|
326
|
+
callerClass: className,
|
|
327
|
+
callType,
|
|
328
|
+
receiver,
|
|
329
|
+
receiverType,
|
|
330
|
+
argCount: _countArgs(node),
|
|
331
|
+
line: node.startPosition.row + 1,
|
|
332
|
+
isAwait: isAwaited,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── Python ─────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* 从 Python AST root 中提取所有调用点
|
|
340
|
+
*
|
|
341
|
+
* @param {TreeSitterNode} root — AST root 节点
|
|
342
|
+
* @param {object} ctx — walker context
|
|
343
|
+
* @param {string} lang — 语言标识
|
|
344
|
+
*/
|
|
345
|
+
export function extractCallSitesPython(root, ctx, lang) {
|
|
346
|
+
const scopes = _collectPyScopes(root);
|
|
347
|
+
|
|
348
|
+
for (const scope of scopes) {
|
|
349
|
+
_extractPyCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 收集 Python 中所有函数/方法作用域
|
|
355
|
+
*
|
|
356
|
+
* @param {TreeSitterNode} root
|
|
357
|
+
* @returns {Array<{ body: TreeSitterNode, className: string|null, methodName: string }>}
|
|
358
|
+
*/
|
|
359
|
+
function _collectPyScopes(root) {
|
|
360
|
+
const scopes = [];
|
|
361
|
+
|
|
362
|
+
function walk(node, className) {
|
|
363
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
364
|
+
const child = node.namedChild(i);
|
|
365
|
+
|
|
366
|
+
switch (child.type) {
|
|
367
|
+
case 'class_definition': {
|
|
368
|
+
const name =
|
|
369
|
+
child.namedChildren.find((c) => c.type === 'identifier')?.text || null;
|
|
370
|
+
const body = child.namedChildren.find((c) => c.type === 'block');
|
|
371
|
+
if (body && name) {
|
|
372
|
+
walk(body, name);
|
|
373
|
+
}
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
case 'function_definition': {
|
|
378
|
+
const name =
|
|
379
|
+
child.namedChildren.find((c) => c.type === 'identifier')?.text || 'unknown';
|
|
380
|
+
const body = child.namedChildren.find((c) => c.type === 'block');
|
|
381
|
+
if (body) {
|
|
382
|
+
scopes.push({ body, className, methodName: name });
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
case 'decorated_definition': {
|
|
388
|
+
// decorator 后面跟着 function_definition 或 class_definition
|
|
389
|
+
const actualDef = child.namedChildren.find(
|
|
390
|
+
(c) => c.type === 'class_definition' || c.type === 'function_definition'
|
|
391
|
+
);
|
|
392
|
+
if (actualDef?.type === 'class_definition') {
|
|
393
|
+
const name =
|
|
394
|
+
actualDef.namedChildren.find((c) => c.type === 'identifier')?.text || null;
|
|
395
|
+
const body = actualDef.namedChildren.find((c) => c.type === 'block');
|
|
396
|
+
if (body && name) {
|
|
397
|
+
walk(body, name);
|
|
398
|
+
}
|
|
399
|
+
} else if (actualDef?.type === 'function_definition') {
|
|
400
|
+
const name =
|
|
401
|
+
actualDef.namedChildren.find((c) => c.type === 'identifier')?.text || 'unknown';
|
|
402
|
+
const body = actualDef.namedChildren.find((c) => c.type === 'block');
|
|
403
|
+
if (body) {
|
|
404
|
+
scopes.push({ body, className, methodName: name });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
default: {
|
|
411
|
+
if (child.namedChildCount > 0 && child.type !== 'block') {
|
|
412
|
+
walk(child, className);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
walk(root, null);
|
|
420
|
+
return scopes;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* 从 Python 方法体中递归提取调用点
|
|
425
|
+
*
|
|
426
|
+
* @param {TreeSitterNode} bodyNode — block 节点
|
|
427
|
+
* @param {string|null} className
|
|
428
|
+
* @param {string} methodName
|
|
429
|
+
* @param {object} ctx
|
|
430
|
+
*/
|
|
431
|
+
function _extractPyCallSitesFromBody(bodyNode, className, methodName, ctx) {
|
|
432
|
+
if (!bodyNode) return;
|
|
433
|
+
|
|
434
|
+
function walk(node, isAwaited) {
|
|
435
|
+
// 跳过语法错误节点 (Issue #17: 防御性处理)
|
|
436
|
+
if (!node || node.type === 'ERROR' || node.isMissing) return;
|
|
437
|
+
|
|
438
|
+
if (node.type === 'await') {
|
|
439
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
440
|
+
walk(node.namedChild(i), true);
|
|
441
|
+
}
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (node.type === 'call') {
|
|
446
|
+
const callSite = _parsePyCallExpression(node, className, methodName, isAwaited);
|
|
447
|
+
if (callSite) {
|
|
448
|
+
ctx.callSites.push(callSite);
|
|
449
|
+
}
|
|
450
|
+
// 继续遍历参数中的嵌套调用
|
|
451
|
+
const argList = node.namedChildren.find((c) => c.type === 'argument_list');
|
|
452
|
+
if (argList) {
|
|
453
|
+
for (let i = 0; i < argList.namedChildCount; i++) {
|
|
454
|
+
walk(argList.namedChild(i), false);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 递归子节点
|
|
461
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
462
|
+
walk(node.namedChild(i), false);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
walk(bodyNode, false);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* 解析 Python 的 call 节点
|
|
471
|
+
*
|
|
472
|
+
* @param {TreeSitterNode} node — call 节点
|
|
473
|
+
* @param {string|null} className
|
|
474
|
+
* @param {string} methodName
|
|
475
|
+
* @param {boolean} isAwaited
|
|
476
|
+
* @returns {CallSite|null}
|
|
477
|
+
*/
|
|
478
|
+
function _parsePyCallExpression(node, className, methodName, isAwaited) {
|
|
479
|
+
// Python call 节点: function 是第一个 named child
|
|
480
|
+
const func = node.namedChildren[0];
|
|
481
|
+
if (!func) return null;
|
|
482
|
+
|
|
483
|
+
let callee;
|
|
484
|
+
let receiver = null;
|
|
485
|
+
let receiverType = null;
|
|
486
|
+
let callType;
|
|
487
|
+
|
|
488
|
+
if (func.type === 'attribute') {
|
|
489
|
+
// obj.method() — method call
|
|
490
|
+
const object = func.namedChildren.find((c) => c.type !== 'identifier' || c === func.namedChildren[0]);
|
|
491
|
+
const prop = func.namedChildren.find((c) => c.type === 'identifier' && c !== func.namedChildren[0]);
|
|
492
|
+
|
|
493
|
+
// attribute 节点结构: object.attribute — 第一个子节点是 object, 第二个是 attribute name
|
|
494
|
+
const parts = func.text.split('.');
|
|
495
|
+
if (parts.length >= 2) {
|
|
496
|
+
receiver = parts.slice(0, -1).join('.');
|
|
497
|
+
callee = parts[parts.length - 1];
|
|
498
|
+
} else {
|
|
499
|
+
receiver = object?.text || null;
|
|
500
|
+
callee = prop?.text || func.text;
|
|
501
|
+
}
|
|
502
|
+
callType = 'method';
|
|
503
|
+
|
|
504
|
+
// 推断 receiverType
|
|
505
|
+
if (receiver === 'self') {
|
|
506
|
+
receiverType = className;
|
|
507
|
+
} else if (receiver === 'super()') {
|
|
508
|
+
callType = 'super';
|
|
509
|
+
receiverType = className;
|
|
510
|
+
} else if (receiver && /^[A-Z]/.test(receiver)) {
|
|
511
|
+
receiverType = receiver;
|
|
512
|
+
callType = 'static';
|
|
513
|
+
}
|
|
514
|
+
} else if (func.type === 'identifier') {
|
|
515
|
+
callee = func.text;
|
|
516
|
+
// Python: 大写开头通常是类/构造函数
|
|
517
|
+
if (/^[A-Z]/.test(callee)) {
|
|
518
|
+
callType = 'constructor';
|
|
519
|
+
receiverType = callee;
|
|
520
|
+
} else {
|
|
521
|
+
callType = 'function';
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
callee = func.text?.slice(0, 80) || 'unknown';
|
|
525
|
+
callType = 'function';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// 过滤噪声
|
|
529
|
+
if (_isNoiseCall(callee, receiver)) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
callee,
|
|
535
|
+
callerMethod: methodName,
|
|
536
|
+
callerClass: className,
|
|
537
|
+
callType,
|
|
538
|
+
receiver,
|
|
539
|
+
receiverType,
|
|
540
|
+
argCount: _countPyArgs(node),
|
|
541
|
+
line: node.startPosition.row + 1,
|
|
542
|
+
isAwait: isAwaited,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ── 通用提取器注册 ─────────────────────────────────────────
|
|
547
|
+
|
|
548
|
+
/** @type {Map<string, (root, ctx, lang) => void>} */
|
|
549
|
+
const _extractors = new Map([
|
|
550
|
+
['typescript', extractCallSitesTS],
|
|
551
|
+
['tsx', extractCallSitesTS],
|
|
552
|
+
['javascript', extractCallSitesTS],
|
|
553
|
+
['python', extractCallSitesPython],
|
|
554
|
+
]);
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* 获取特定语言的 CallSite 提取器
|
|
558
|
+
* @param {string} lang
|
|
559
|
+
* @returns {((root: TreeSitterNode, ctx: object, lang: string) => void)|null}
|
|
560
|
+
*/
|
|
561
|
+
export function getCallSiteExtractor(lang) {
|
|
562
|
+
return _extractors.get(lang) || null;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* 默认的 CallSite 提取器 — 用于无专门提取器的语言
|
|
567
|
+
* 使用通用的 call_expression 匹配策略
|
|
568
|
+
*
|
|
569
|
+
* @param {TreeSitterNode} root
|
|
570
|
+
* @param {object} ctx
|
|
571
|
+
* @param {string} lang
|
|
572
|
+
*/
|
|
573
|
+
export function defaultExtractCallSites(root, ctx, lang) {
|
|
574
|
+
// 对于未适配的语言,暂不提取(降级为空)
|
|
575
|
+
// Phase 5.1 将逐步增加 Go / Rust / Java / Kotlin 等
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ── 工具函数 ───────────────────────────────────────────────
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* 计算参数数量 (TS/JS)
|
|
582
|
+
*/
|
|
583
|
+
function _countArgs(node) {
|
|
584
|
+
const args = node.namedChildren.find((c) => c.type === 'arguments');
|
|
585
|
+
if (!args) return 0;
|
|
586
|
+
return args.namedChildCount;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* 计算参数数量 (Python)
|
|
591
|
+
*/
|
|
592
|
+
function _countPyArgs(node) {
|
|
593
|
+
const args = node.namedChildren.find((c) => c.type === 'argument_list');
|
|
594
|
+
if (!args) return 0;
|
|
595
|
+
return args.namedChildCount;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* 判断是否为噪声调用(内置/console/日志等,不产生有意义的调用边)
|
|
600
|
+
*
|
|
601
|
+
* @param {string} callee
|
|
602
|
+
* @param {string|null} receiver
|
|
603
|
+
* @returns {boolean}
|
|
604
|
+
*/
|
|
605
|
+
function _isNoiseCall(callee, receiver) {
|
|
606
|
+
// 常见内置调用噪声
|
|
607
|
+
const NOISE_RECEIVERS = new Set([
|
|
608
|
+
'console', 'Math', 'JSON', 'Object', 'Array', 'String',
|
|
609
|
+
'Number', 'Boolean', 'Date', 'RegExp', 'Promise', 'Set',
|
|
610
|
+
'Map', 'WeakMap', 'WeakSet', 'Symbol', 'Reflect', 'Proxy',
|
|
611
|
+
'parseInt', 'parseFloat',
|
|
612
|
+
]);
|
|
613
|
+
|
|
614
|
+
const NOISE_CALLEES = new Set([
|
|
615
|
+
'require', 'import', 'console', 'log', 'warn', 'error', 'info', 'debug',
|
|
616
|
+
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
|
617
|
+
'requestAnimationFrame', 'cancelAnimationFrame',
|
|
618
|
+
'alert', 'confirm', 'prompt',
|
|
619
|
+
'print', 'len', 'range', 'enumerate', 'zip', 'map', 'filter',
|
|
620
|
+
'isinstance', 'issubclass', 'hasattr', 'getattr', 'setattr',
|
|
621
|
+
'str', 'int', 'float', 'bool', 'list', 'dict', 'tuple', 'set',
|
|
622
|
+
'type', 'super', 'property', 'staticmethod', 'classmethod',
|
|
623
|
+
]);
|
|
624
|
+
|
|
625
|
+
if (receiver && NOISE_RECEIVERS.has(receiver)) return true;
|
|
626
|
+
if (callee && NOISE_CALLEES.has(callee)) return true;
|
|
627
|
+
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module DataFlowInferrer
|
|
3
|
+
* @description Phase 5: 从调用边推断数据流边 (L0 + L1 粒度)
|
|
4
|
+
*
|
|
5
|
+
* 数据流粒度:
|
|
6
|
+
* - L0: 模块级 — A 文件 import B 文件 → 数据可能从 B 流向 A
|
|
7
|
+
* - L1: 函数级 — A.foo() 调用 B.bar(x) → x 从 A 流向 B
|
|
8
|
+
*
|
|
9
|
+
* Phase 5.0 只实现 L0 + L1,L2/L3 (参数级/语句级) 留待 Phase 5.2。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} DataFlowEdge
|
|
14
|
+
* @property {string} from — 源 FQN
|
|
15
|
+
* @property {string} to — 目标 FQN
|
|
16
|
+
* @property {'argument'|'return-value'} flowType
|
|
17
|
+
* @property {'forward'|'backward'} direction
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export class DataFlowInferrer {
|
|
21
|
+
/**
|
|
22
|
+
* 从已解析的调用边推断数据流边
|
|
23
|
+
*
|
|
24
|
+
* @param {import('./CallEdgeResolver.js').ResolvedEdge[]} resolvedEdges
|
|
25
|
+
* @returns {DataFlowEdge[]}
|
|
26
|
+
*/
|
|
27
|
+
static infer(resolvedEdges) {
|
|
28
|
+
/** @type {DataFlowEdge[]} */
|
|
29
|
+
const dataFlowEdges = [];
|
|
30
|
+
|
|
31
|
+
for (const edge of resolvedEdges) {
|
|
32
|
+
// 仅当调用有参数时生成正向数据流 (参数从 caller 流向 callee)
|
|
33
|
+
if ((edge.argCount || 0) > 0) {
|
|
34
|
+
dataFlowEdges.push({
|
|
35
|
+
from: edge.caller,
|
|
36
|
+
to: edge.callee,
|
|
37
|
+
flowType: 'argument',
|
|
38
|
+
direction: 'forward',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 每个调用边还隐含一条反向数据流 (返回值从 callee 流向 caller)
|
|
43
|
+
// Phase 5.0 无法判断是否 void,保守生成但标记低置信度
|
|
44
|
+
dataFlowEdges.push({
|
|
45
|
+
from: edge.callee,
|
|
46
|
+
to: edge.caller,
|
|
47
|
+
flowType: 'return-value',
|
|
48
|
+
direction: 'backward',
|
|
49
|
+
confidence: 0.3,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return dataFlowEdges;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default DataFlowInferrer;
|