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
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
* 模式: Flutter Widget (Stateless/Stateful/Consumer), Factory, Singleton,
|
|
7
7
|
* Builder, BLoC/Cubit, Provider/Riverpod, Freezed
|
|
8
8
|
*
|
|
9
|
+
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
|
|
10
|
+
*
|
|
9
11
|
* 注意: tree-sitter-dart 目前尚无兼容 tree-sitter ≥0.25 的稳定版。
|
|
10
12
|
* 已迁移至 web-tree-sitter (WASM),无原生编译依赖。
|
|
11
13
|
*/
|
|
12
14
|
|
|
15
|
+
import { ImportRecord } from '../analysis/ImportRecord.js';
|
|
16
|
+
|
|
13
17
|
function walkDart(root, ctx) {
|
|
14
18
|
_walkNode(root, ctx, null);
|
|
15
19
|
}
|
|
@@ -22,11 +26,30 @@ function _walkNode(node, ctx, parentClassName) {
|
|
|
22
26
|
case 'import_or_export': // tree-sitter-dart import 节点
|
|
23
27
|
case 'import_specification':
|
|
24
28
|
case 'library_import': {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
// tree-sitter-dart AST 嵌套: import_or_export > library_import > import_specification
|
|
30
|
+
// URI 节点埋在深层,直接从文本中用正则提取更可靠
|
|
31
|
+
const text = child.text;
|
|
32
|
+
const pathMatch = text.match(/import\s+(['"])(.+?)\1/);
|
|
33
|
+
if (pathMatch) {
|
|
34
|
+
const importPath = pathMatch[2];
|
|
35
|
+
// Dart: import 'pkg' as alias
|
|
36
|
+
const asMatch = text.match(/\bas\s+(\w+)/);
|
|
37
|
+
const alias = asMatch ? asMatch[1] : null;
|
|
38
|
+
// Dart: import 'pkg' show A, B
|
|
39
|
+
const showClause = text.match(/\bshow\s+([\w\s,]+)/);
|
|
40
|
+
// Dart: import 'pkg' hide A, B (暂不使用,记录备查)
|
|
41
|
+
// const hideClause = text.match(/\bhide\s+([\w\s,]+)/);
|
|
42
|
+
|
|
43
|
+
let symbols = ['*'];
|
|
44
|
+
let kind = 'namespace';
|
|
45
|
+
if (showClause) {
|
|
46
|
+
symbols = showClause[1].split(',').map((s) => s.trim()).filter(Boolean);
|
|
47
|
+
kind = 'named';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ctx.imports.push(
|
|
51
|
+
new ImportRecord(importPath, { symbols, alias, kind })
|
|
52
|
+
);
|
|
30
53
|
}
|
|
31
54
|
break;
|
|
32
55
|
}
|
|
@@ -645,6 +668,329 @@ function _maxNesting(node, depth) {
|
|
|
645
668
|
return max;
|
|
646
669
|
}
|
|
647
670
|
|
|
671
|
+
// ── Dart Call Site 提取 (Phase 5) ────────────────────────────
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* 从 Dart AST root 提取所有调用点
|
|
675
|
+
* 遍历 function_definition / method 中的 body → 各种 invocation 节点
|
|
676
|
+
*
|
|
677
|
+
* @param {TreeSitterNode} root
|
|
678
|
+
* @param {object} ctx
|
|
679
|
+
* @param {string} _lang
|
|
680
|
+
*/
|
|
681
|
+
function extractCallSitesDart(root, ctx, _lang) {
|
|
682
|
+
const scopes = _collectDartScopes(root);
|
|
683
|
+
for (const scope of scopes) {
|
|
684
|
+
_extractDartCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* 递归收集 Dart 中所有函数/方法体作用域
|
|
690
|
+
*/
|
|
691
|
+
function _collectDartScopes(root) {
|
|
692
|
+
const scopes = [];
|
|
693
|
+
|
|
694
|
+
function visit(node, className) {
|
|
695
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
696
|
+
const child = node.namedChild(i);
|
|
697
|
+
|
|
698
|
+
if (child.type === 'class_definition') {
|
|
699
|
+
const name = child.namedChildren.find(
|
|
700
|
+
(c) => c.type === 'identifier' || c.type === 'type_identifier'
|
|
701
|
+
)?.text;
|
|
702
|
+
const body = child.namedChildren.find((c) => c.type === 'class_body');
|
|
703
|
+
if (body) {
|
|
704
|
+
visit(body, name || className);
|
|
705
|
+
}
|
|
706
|
+
} else if (child.type === 'mixin_declaration') {
|
|
707
|
+
const name = child.namedChildren.find(
|
|
708
|
+
(c) => c.type === 'identifier' || c.type === 'type_identifier'
|
|
709
|
+
)?.text;
|
|
710
|
+
const body = child.namedChildren.find((c) => c.type === 'class_body');
|
|
711
|
+
if (body) {
|
|
712
|
+
visit(body, name || className);
|
|
713
|
+
}
|
|
714
|
+
} else if (child.type === 'extension_declaration') {
|
|
715
|
+
const name = child.namedChildren.find(
|
|
716
|
+
(c) => c.type === 'identifier' || c.type === 'type_identifier'
|
|
717
|
+
)?.text;
|
|
718
|
+
const body = child.namedChildren.find(
|
|
719
|
+
(c) => c.type === 'class_body' || c.type === 'extension_body'
|
|
720
|
+
);
|
|
721
|
+
if (body) {
|
|
722
|
+
visit(body, name || className);
|
|
723
|
+
}
|
|
724
|
+
} else if (
|
|
725
|
+
child.type === 'function_definition' ||
|
|
726
|
+
child.type === 'method_signature' ||
|
|
727
|
+
child.type === 'function_signature'
|
|
728
|
+
) {
|
|
729
|
+
// 提取方法/函数名
|
|
730
|
+
let name;
|
|
731
|
+
if (child.type === 'method_signature' || child.type === 'function_signature') {
|
|
732
|
+
// method_signature 可能包含嵌套 function_signature
|
|
733
|
+
const funcSig = child.namedChildren.find((c) => c.type === 'function_signature') || child;
|
|
734
|
+
name = funcSig.namedChildren.find(
|
|
735
|
+
(c) => c.type === 'identifier' || c.type === 'function_name'
|
|
736
|
+
)?.text;
|
|
737
|
+
} else {
|
|
738
|
+
name = child.namedChildren.find(
|
|
739
|
+
(c) => c.type === 'identifier' || c.type === 'function_name'
|
|
740
|
+
)?.text;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// tree-sitter-dart: function_body 可能是子节点或下一个兄弟节点
|
|
744
|
+
let body = child.namedChildren.find(
|
|
745
|
+
(c) => c.type === 'function_body' || c.type === 'block'
|
|
746
|
+
);
|
|
747
|
+
// 如果 body 不在子节点中,检查下一个兄弟节点 (tree-sitter-dart 的 sibling 结构)
|
|
748
|
+
if (!body && i + 1 < node.namedChildCount) {
|
|
749
|
+
const nextSibling = node.namedChild(i + 1);
|
|
750
|
+
if (nextSibling?.type === 'function_body' || nextSibling?.type === 'block') {
|
|
751
|
+
body = nextSibling;
|
|
752
|
+
i++; // 跳过已消费的 body 节点
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (name && body) {
|
|
756
|
+
scopes.push({ body, className, methodName: name });
|
|
757
|
+
}
|
|
758
|
+
} else if (child.type === 'getter_signature' || child.type === 'setter_signature') {
|
|
759
|
+
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
|
|
760
|
+
let body = child.namedChildren.find(
|
|
761
|
+
(c) => c.type === 'function_body' || c.type === 'block'
|
|
762
|
+
);
|
|
763
|
+
if (!body && i + 1 < node.namedChildCount) {
|
|
764
|
+
const nextSibling = node.namedChild(i + 1);
|
|
765
|
+
if (nextSibling?.type === 'function_body' || nextSibling?.type === 'block') {
|
|
766
|
+
body = nextSibling;
|
|
767
|
+
i++;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (name && body) {
|
|
771
|
+
scopes.push({ body, className, methodName: `get_${name}` });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
visit(root, null);
|
|
778
|
+
return scopes;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* 从 Dart function body 中递归提取调用点
|
|
783
|
+
*
|
|
784
|
+
* tree-sitter-dart 的调用表达式由 **兄弟节点序列** 构成(而非单个 call_expression 节点):
|
|
785
|
+
* Pattern A: identifier + selector("(args)") → 直接调用: func(args)
|
|
786
|
+
* Pattern B: (identifier|this|super) + selector(".method") → 方法调用: obj.method(args)
|
|
787
|
+
* + selector("(args)")
|
|
788
|
+
* 因此需要 sibling-aware scanning,避免逐个 walk 子节点时丢失上下文。
|
|
789
|
+
*/
|
|
790
|
+
function _extractDartCallSitesFromBody(bodyNode, className, methodName, ctx) {
|
|
791
|
+
if (!bodyNode) return;
|
|
792
|
+
|
|
793
|
+
const DART_NOISE = new Set([
|
|
794
|
+
'print', 'debugPrint', 'log',
|
|
795
|
+
'setState', 'notifyListeners',
|
|
796
|
+
'List', 'Map', 'Set', 'Future', 'Stream',
|
|
797
|
+
]);
|
|
798
|
+
|
|
799
|
+
/** 在 selectorNode 的子树中查找 arguments / argument_part */
|
|
800
|
+
function findArgs(selectorNode) {
|
|
801
|
+
return selectorNode.namedChildren.find(
|
|
802
|
+
(c) => c.type === 'arguments' || c.type === 'argument_part',
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* 尝试从 parent.namedChild(idx) 开始消费一条调用链。
|
|
808
|
+
* 成功 → 返回消费到的最后一个子节点索引;失败 → 返回 null。
|
|
809
|
+
*/
|
|
810
|
+
function tryConsumeCall(parent, idx, startNode, isAwaited) {
|
|
811
|
+
const sib1 = parent.namedChild(idx + 1);
|
|
812
|
+
if (!sib1) return null;
|
|
813
|
+
|
|
814
|
+
// ── Pattern A: identifier + selector("(args)") → 直接调用 ────────
|
|
815
|
+
if (
|
|
816
|
+
(startNode.type === 'identifier' || startNode.type === 'type_identifier') &&
|
|
817
|
+
sib1.type === 'selector' &&
|
|
818
|
+
/^\s*\(/.test(sib1.text)
|
|
819
|
+
) {
|
|
820
|
+
const callee = startNode.text;
|
|
821
|
+
if (!DART_NOISE.has(callee)) {
|
|
822
|
+
const callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
|
|
823
|
+
const args = findArgs(sib1);
|
|
824
|
+
ctx.callSites.push({
|
|
825
|
+
callee,
|
|
826
|
+
callerMethod: methodName,
|
|
827
|
+
callerClass: className,
|
|
828
|
+
callType,
|
|
829
|
+
receiver: null,
|
|
830
|
+
receiverType: callType === 'constructor' ? callee : null,
|
|
831
|
+
argCount: args ? args.namedChildCount : 0,
|
|
832
|
+
line: startNode.startPosition.row + 1,
|
|
833
|
+
isAwait: isAwaited,
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
// 递归扫描 selector 内部(处理嵌套调用,如 MyApp() 内的参数调用)
|
|
837
|
+
scanChildren(sib1, false);
|
|
838
|
+
return idx + 1;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// ── Pattern B: receiver + methodSelector + argsSelector → 方法调用 ──
|
|
842
|
+
const sib2 = parent.namedChild(idx + 2);
|
|
843
|
+
const isMethodSel =
|
|
844
|
+
sib1.type === 'selector' || sib1.type === 'unconditional_assignable_selector';
|
|
845
|
+
const isArgsSel = sib2?.type === 'selector' && sib2.text.includes('(');
|
|
846
|
+
|
|
847
|
+
if (isMethodSel && isArgsSel) {
|
|
848
|
+
const methodMatch = sib1.text.match(/\.(\w+)/);
|
|
849
|
+
if (methodMatch) {
|
|
850
|
+
const callee = methodMatch[1];
|
|
851
|
+
const receiverText = startNode.text;
|
|
852
|
+
let receiver = receiverText;
|
|
853
|
+
let receiverType = null;
|
|
854
|
+
let callType;
|
|
855
|
+
|
|
856
|
+
if (startNode.type === 'this' || receiver === 'this' || receiver === 'self') {
|
|
857
|
+
receiverType = className;
|
|
858
|
+
callType = 'method';
|
|
859
|
+
} else if (startNode.type === 'super' || receiver === 'super') {
|
|
860
|
+
receiverType = className;
|
|
861
|
+
callType = 'super';
|
|
862
|
+
} else if (/^[A-Z]/.test(receiver)) {
|
|
863
|
+
receiverType = receiver;
|
|
864
|
+
callType = 'static';
|
|
865
|
+
} else {
|
|
866
|
+
callType = 'method';
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (!DART_NOISE.has(callee)) {
|
|
870
|
+
const args = findArgs(sib2);
|
|
871
|
+
ctx.callSites.push({
|
|
872
|
+
callee,
|
|
873
|
+
callerMethod: methodName,
|
|
874
|
+
callerClass: className,
|
|
875
|
+
callType,
|
|
876
|
+
receiver,
|
|
877
|
+
receiverType,
|
|
878
|
+
argCount: args ? args.namedChildCount : 0,
|
|
879
|
+
line: startNode.startPosition.row + 1,
|
|
880
|
+
isAwait: isAwaited,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
// 递归扫描 argsSelector 内部
|
|
884
|
+
scanChildren(sib2, false);
|
|
885
|
+
return idx + 2;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
return null; // 未匹配任何模式
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* 以 sibling-aware 方式扫描 node 的 namedChildren。
|
|
894
|
+
* 当发现调用起始节点(identifier / this / super)时,尝试消费完整调用链并跳过已消费兄弟。
|
|
895
|
+
*/
|
|
896
|
+
function scanChildren(node, isAwaited) {
|
|
897
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
898
|
+
const child = node.namedChild(i);
|
|
899
|
+
|
|
900
|
+
// await → 标记子树为 awaited
|
|
901
|
+
if (child.type === 'await_expression') {
|
|
902
|
+
scanChildren(child, true);
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// function_expression_invocation / method_invocation (部分 grammar 变体)
|
|
907
|
+
if (child.type === 'function_expression_invocation' || child.type === 'method_invocation') {
|
|
908
|
+
_processDartCall(child, className, methodName, ctx, isAwaited, DART_NOISE);
|
|
909
|
+
const args = child.namedChildren.find(
|
|
910
|
+
(c) => c.type === 'arguments' || c.type === 'argument_part',
|
|
911
|
+
);
|
|
912
|
+
if (args) scanChildren(args, false);
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// 尝试从当前位置消费调用模式
|
|
917
|
+
const isCallStarter =
|
|
918
|
+
child.type === 'identifier' ||
|
|
919
|
+
child.type === 'type_identifier' ||
|
|
920
|
+
child.type === 'this' ||
|
|
921
|
+
child.type === 'super';
|
|
922
|
+
|
|
923
|
+
if (isCallStarter) {
|
|
924
|
+
const consumed = tryConsumeCall(node, i, child, isAwaited);
|
|
925
|
+
if (consumed !== null) {
|
|
926
|
+
i = consumed; // 跳过已消费的兄弟
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Dart cascade: obj..method1()..method2()
|
|
932
|
+
if (child.type === 'cascade_section') {
|
|
933
|
+
_processDartCall(child, className, methodName, ctx, isAwaited, DART_NOISE);
|
|
934
|
+
scanChildren(child, false);
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// 未匹配 → 递归进入子节点
|
|
939
|
+
scanChildren(child, isAwaited);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
scanChildren(bodyNode, false);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* 处理 Dart 函数/方法调用节点
|
|
948
|
+
*/
|
|
949
|
+
function _processDartCall(node, className, methodName, ctx, isAwaited, DART_NOISE) {
|
|
950
|
+
const text = node.text || '';
|
|
951
|
+
const callMatch = text.match(/^(?:(\w[\w.]*?)\.)?(\w+)\s*\(/);
|
|
952
|
+
if (!callMatch) return;
|
|
953
|
+
|
|
954
|
+
const receiverText = callMatch[1] || null;
|
|
955
|
+
const callee = callMatch[2];
|
|
956
|
+
|
|
957
|
+
if (DART_NOISE.has(callee)) return;
|
|
958
|
+
|
|
959
|
+
let receiver = receiverText;
|
|
960
|
+
let receiverType = null;
|
|
961
|
+
let callType;
|
|
962
|
+
|
|
963
|
+
if (receiver === 'this' || receiver === 'super') {
|
|
964
|
+
receiverType = className;
|
|
965
|
+
callType = receiver === 'super' ? 'super' : 'method';
|
|
966
|
+
} else if (receiver && /^[A-Z]/.test(receiver)) {
|
|
967
|
+
receiverType = receiver;
|
|
968
|
+
callType = 'static';
|
|
969
|
+
} else if (receiver) {
|
|
970
|
+
callType = 'method';
|
|
971
|
+
} else {
|
|
972
|
+
callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
|
|
973
|
+
if (callType === 'constructor') receiverType = callee;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const args = node.namedChildren.find(
|
|
977
|
+
(c) => c.type === 'arguments' || c.type === 'argument_part'
|
|
978
|
+
);
|
|
979
|
+
const argCount = args ? args.namedChildCount : 0;
|
|
980
|
+
|
|
981
|
+
ctx.callSites.push({
|
|
982
|
+
callee,
|
|
983
|
+
callerMethod: methodName,
|
|
984
|
+
callerClass: className,
|
|
985
|
+
callType,
|
|
986
|
+
receiver,
|
|
987
|
+
receiverType,
|
|
988
|
+
argCount,
|
|
989
|
+
line: node.startPosition.row + 1,
|
|
990
|
+
isAwait: isAwaited,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
|
|
648
994
|
// ── Plugin Export ────────────────────────────────────────────
|
|
649
995
|
|
|
650
996
|
let _grammar = null;
|
|
@@ -659,5 +1005,6 @@ export const plugin = {
|
|
|
659
1005
|
getGrammar,
|
|
660
1006
|
walk: walkDart,
|
|
661
1007
|
detectPatterns: detectDartPatterns,
|
|
1008
|
+
extractCallSites: extractCallSitesDart,
|
|
662
1009
|
extensions: ['.dart'],
|
|
663
1010
|
};
|
package/lib/core/ast/lang-go.js
CHANGED
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
* 提取: struct, interface, method (with receiver), function, field, import
|
|
6
6
|
* 模式: Singleton (sync.Once), Factory (New*), Constructor (New*),
|
|
7
7
|
* Goroutine, Channel, Middleware (http.Handler chain)
|
|
8
|
+
*
|
|
9
|
+
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
|
|
8
10
|
*/
|
|
9
11
|
|
|
12
|
+
import { ImportRecord } from '../analysis/ImportRecord.js';
|
|
13
|
+
|
|
10
14
|
function walkGo(root, ctx) {
|
|
11
15
|
for (let i = 0; i < root.namedChildCount; i++) {
|
|
12
16
|
const child = root.namedChild(i);
|
|
@@ -27,22 +31,16 @@ function walkGo(root, ctx) {
|
|
|
27
31
|
for (let j = 0; j < specList.namedChildCount; j++) {
|
|
28
32
|
const spec = specList.namedChild(j);
|
|
29
33
|
if (spec.type === 'import_spec') {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
);
|
|
33
|
-
if (strLit) {
|
|
34
|
-
ctx.imports.push(strLit.text.replace(/"/g, ''));
|
|
35
|
-
}
|
|
34
|
+
const rec = _parseGoImportSpec(spec);
|
|
35
|
+
if (rec) ctx.imports.push(rec);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
} else {
|
|
39
39
|
// 单行 import
|
|
40
40
|
const spec = child.namedChildren.find((c) => c.type === 'import_spec');
|
|
41
41
|
if (spec) {
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
ctx.imports.push(strLit.text.replace(/"/g, ''));
|
|
45
|
-
}
|
|
42
|
+
const rec = _parseGoImportSpec(spec);
|
|
43
|
+
if (rec) ctx.imports.push(rec);
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
46
|
break;
|
|
@@ -494,6 +492,209 @@ function _maxNesting(node, depth) {
|
|
|
494
492
|
return max;
|
|
495
493
|
}
|
|
496
494
|
|
|
495
|
+
// ── Go Import 解析 ──────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* 解析 Go import_spec 节点为 ImportRecord
|
|
499
|
+
*
|
|
500
|
+
* Go import 语法:
|
|
501
|
+
* import "fmt" → namespace, alias='fmt'
|
|
502
|
+
* import alias "pkg/path" → namespace, alias=alias
|
|
503
|
+
* import . "pkg/path" → named (dot import, 类似全部导入)
|
|
504
|
+
* import _ "pkg/path" → side-effect
|
|
505
|
+
*
|
|
506
|
+
* @param {TreeSitterNode} spec — import_spec 节点
|
|
507
|
+
* @returns {ImportRecord|null}
|
|
508
|
+
*/
|
|
509
|
+
function _parseGoImportSpec(spec) {
|
|
510
|
+
const strLit = spec.namedChildren.find(
|
|
511
|
+
(c) => c.type === 'interpreted_string_literal'
|
|
512
|
+
);
|
|
513
|
+
if (!strLit) return null;
|
|
514
|
+
|
|
515
|
+
const importPath = strLit.text.replace(/"/g, '');
|
|
516
|
+
const aliasNode = spec.namedChildren.find(
|
|
517
|
+
(c) => c.type === 'package_identifier' || c.type === 'dot' || c.type === 'blank_identifier'
|
|
518
|
+
|| (c.type === 'identifier' && (c.text === '.' || c.text === '_'))
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
if (aliasNode) {
|
|
522
|
+
if (aliasNode.text === '.' || aliasNode.type === 'dot') {
|
|
523
|
+
// dot import: import . "pkg" → all exports available
|
|
524
|
+
return new ImportRecord(importPath, { symbols: ['*'], kind: 'named' });
|
|
525
|
+
}
|
|
526
|
+
if (aliasNode.text === '_' || aliasNode.type === 'blank_identifier') {
|
|
527
|
+
// blank import: side-effect only
|
|
528
|
+
return new ImportRecord(importPath, { symbols: [], kind: 'side-effect' });
|
|
529
|
+
}
|
|
530
|
+
// explicit alias: import alias "pkg/path"
|
|
531
|
+
return new ImportRecord(importPath, {
|
|
532
|
+
symbols: ['*'],
|
|
533
|
+
alias: aliasNode.text,
|
|
534
|
+
kind: 'namespace',
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// default: import "pkg/path" → alias is last segment of path
|
|
539
|
+
const parts = importPath.split('/');
|
|
540
|
+
const defaultAlias = parts[parts.length - 1];
|
|
541
|
+
return new ImportRecord(importPath, {
|
|
542
|
+
symbols: ['*'],
|
|
543
|
+
alias: defaultAlias,
|
|
544
|
+
kind: 'namespace',
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// ── Go Call Site 提取 (Phase 5) ─────────────────────────────
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* 从 Go AST root 提取所有调用点
|
|
552
|
+
* 遍历 function_declaration / method_declaration 中的 block → call_expression
|
|
553
|
+
*
|
|
554
|
+
* @param {TreeSitterNode} root
|
|
555
|
+
* @param {object} ctx
|
|
556
|
+
* @param {string} _lang
|
|
557
|
+
*/
|
|
558
|
+
function extractCallSitesGo(root, ctx, _lang) {
|
|
559
|
+
const scopes = _collectGoScopes(root);
|
|
560
|
+
for (const scope of scopes) {
|
|
561
|
+
_extractGoCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* 收集 Go 中所有函数/方法作用域
|
|
567
|
+
*/
|
|
568
|
+
function _collectGoScopes(root) {
|
|
569
|
+
const scopes = [];
|
|
570
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
571
|
+
const child = root.namedChild(i);
|
|
572
|
+
|
|
573
|
+
if (child.type === 'function_declaration') {
|
|
574
|
+
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
|
|
575
|
+
const body = child.namedChildren.find((c) => c.type === 'block');
|
|
576
|
+
if (name && body) {
|
|
577
|
+
scopes.push({ body, className: null, methodName: name });
|
|
578
|
+
}
|
|
579
|
+
} else if (child.type === 'method_declaration') {
|
|
580
|
+
const name = child.namedChildren.find((c) => c.type === 'field_identifier')?.text;
|
|
581
|
+
const body = child.namedChildren.find((c) => c.type === 'block');
|
|
582
|
+
// 提取 receiver type
|
|
583
|
+
const paramLists = child.namedChildren.filter((c) => c.type === 'parameter_list');
|
|
584
|
+
let receiverType = null;
|
|
585
|
+
if (paramLists[0]) {
|
|
586
|
+
const paramDecl = paramLists[0].namedChildren.find((c) => c.type === 'parameter_declaration');
|
|
587
|
+
if (paramDecl) {
|
|
588
|
+
const pointer = paramDecl.namedChildren.find((c) => c.type === 'pointer_type');
|
|
589
|
+
if (pointer) {
|
|
590
|
+
receiverType = pointer.namedChildren.find((c) => c.type === 'type_identifier')?.text;
|
|
591
|
+
} else {
|
|
592
|
+
receiverType = paramDecl.namedChildren.find((c) => c.type === 'type_identifier')?.text;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (name && body) {
|
|
597
|
+
scopes.push({ body, className: receiverType, methodName: name });
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return scopes;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* 从 Go block 中递归提取调用点
|
|
606
|
+
*/
|
|
607
|
+
function _extractGoCallSitesFromBody(bodyNode, className, methodName, ctx) {
|
|
608
|
+
if (!bodyNode) return;
|
|
609
|
+
|
|
610
|
+
const GO_NOISE = new Set([
|
|
611
|
+
'fmt', 'log', 'errors', 'strings', 'strconv', 'math', 'sort',
|
|
612
|
+
'time', 'sync', 'context', 'reflect', 'unsafe', 'os', 'io',
|
|
613
|
+
'bytes', 'bufio', 'regexp', 'path', 'filepath', 'encoding',
|
|
614
|
+
]);
|
|
615
|
+
|
|
616
|
+
function walk(node) {
|
|
617
|
+
if (!node || node.type === 'ERROR' || node.isMissing) return;
|
|
618
|
+
|
|
619
|
+
if (node.type === 'call_expression') {
|
|
620
|
+
const func = node.namedChildren[0];
|
|
621
|
+
if (!func) { walkChildren(node); return; }
|
|
622
|
+
|
|
623
|
+
let callee, receiver = null, receiverType = null, callType;
|
|
624
|
+
|
|
625
|
+
if (func.type === 'selector_expression') {
|
|
626
|
+
// pkg.Func() or obj.Method()
|
|
627
|
+
const parts = func.text.split('.');
|
|
628
|
+
if (parts.length >= 2) {
|
|
629
|
+
receiver = parts.slice(0, -1).join('.');
|
|
630
|
+
callee = parts[parts.length - 1];
|
|
631
|
+
callType = 'method';
|
|
632
|
+
// Go: uppercase receiver might be package name → static
|
|
633
|
+
if (receiver && /^[a-z]/.test(receiver) && !GO_NOISE.has(receiver)) {
|
|
634
|
+
receiverType = null; // instance method
|
|
635
|
+
} else if (GO_NOISE.has(receiver)) {
|
|
636
|
+
walkChildren(node); return; // skip noise
|
|
637
|
+
} else {
|
|
638
|
+
receiverType = receiver;
|
|
639
|
+
callType = 'static';
|
|
640
|
+
}
|
|
641
|
+
} else {
|
|
642
|
+
callee = func.text;
|
|
643
|
+
callType = 'function';
|
|
644
|
+
}
|
|
645
|
+
} else if (func.type === 'identifier') {
|
|
646
|
+
callee = func.text;
|
|
647
|
+
// Go: uppercase = exported, New* = constructor pattern
|
|
648
|
+
if (/^New[A-Z]/.test(callee)) {
|
|
649
|
+
callType = 'constructor';
|
|
650
|
+
receiverType = callee.slice(3); // NewUserService → UserService
|
|
651
|
+
} else {
|
|
652
|
+
callType = 'function';
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
callee = func.text?.slice(0, 80) || 'unknown';
|
|
656
|
+
callType = 'function';
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// 计算参数数量
|
|
660
|
+
const args = node.namedChildren.find((c) => c.type === 'argument_list');
|
|
661
|
+
const argCount = args ? args.namedChildCount : 0;
|
|
662
|
+
|
|
663
|
+
ctx.callSites.push({
|
|
664
|
+
callee,
|
|
665
|
+
callerMethod: methodName,
|
|
666
|
+
callerClass: className,
|
|
667
|
+
callType,
|
|
668
|
+
receiver,
|
|
669
|
+
receiverType,
|
|
670
|
+
argCount,
|
|
671
|
+
line: node.startPosition.row + 1,
|
|
672
|
+
isAwait: false, // Go 不使用 await
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
// 遍历参数中的嵌套调用
|
|
676
|
+
if (args) { walkChildren(args); }
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// go goroutine: go func() — 异步调用
|
|
681
|
+
if (node.type === 'go_statement') {
|
|
682
|
+
walkChildren(node);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
walkChildren(node);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function walkChildren(node) {
|
|
690
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
691
|
+
walk(node.namedChild(i));
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
walk(bodyNode);
|
|
696
|
+
}
|
|
697
|
+
|
|
497
698
|
// ── Plugin Export ────────────────────────────────────────────
|
|
498
699
|
|
|
499
700
|
let _grammar = null;
|
|
@@ -508,5 +709,6 @@ export const plugin = {
|
|
|
508
709
|
getGrammar,
|
|
509
710
|
walk: walkGo,
|
|
510
711
|
detectPatterns: detectGoPatterns,
|
|
712
|
+
extractCallSites: extractCallSitesGo,
|
|
511
713
|
extensions: ['.go'],
|
|
512
714
|
};
|