autosnippet 3.2.7 → 3.2.9
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/bin/cli.js +13 -5
- package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
- package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/lib/cli/AiScanService.js +26 -29
- package/lib/cli/SetupService.js +1 -1
- package/lib/core/AstAnalyzer.js +27 -5
- 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/core/discovery/index.js +2 -2
- package/lib/external/ai/AiProvider.js +66 -172
- package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
- package/lib/external/mcp/McpServer.js +1 -0
- package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
- package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
- package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
- package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
- package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
- package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
- package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
- package/lib/external/mcp/handlers/consolidated.js +9 -0
- package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
- package/lib/external/mcp/handlers/guard.js +3 -3
- package/lib/external/mcp/handlers/structure.js +62 -0
- package/lib/external/mcp/handlers/wiki-external.js +66 -3
- package/lib/external/mcp/tools.js +36 -1
- package/lib/http/HttpServer.js +1 -1
- package/lib/http/middleware/requestLogger.js +1 -0
- package/lib/http/routes/ai.js +240 -35
- package/lib/http/routes/candidates.js +2 -3
- package/lib/http/routes/extract.js +13 -11
- package/lib/http/routes/modules.js +2 -2
- package/lib/http/routes/recipes.js +9 -5
- package/lib/http/routes/remote.js +149 -270
- package/lib/http/routes/violations.js +0 -54
- package/lib/http/utils/sse-sessions.js +1 -1
- package/lib/infrastructure/logging/Logger.js +5 -4
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
- package/lib/injection/ServiceContainer.js +70 -28
- package/lib/platform/ScreenCaptureService.js +177 -0
- package/lib/platform/ios/index.js +2 -2
- package/lib/platform/ios/routes/spm.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/agent/AgentEventBus.js +207 -0
- package/lib/service/agent/AgentFactory.js +490 -0
- package/lib/service/agent/AgentMessage.js +240 -0
- package/lib/service/agent/AgentRouter.js +228 -0
- package/lib/service/agent/AgentRuntime.js +1016 -0
- package/lib/service/agent/AgentState.js +217 -0
- package/lib/service/agent/IntentClassifier.js +331 -0
- package/lib/service/agent/LarkTransport.js +389 -0
- package/lib/service/agent/capabilities.js +408 -0
- package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
- package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
- package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
- package/lib/service/agent/core/LoopContext.js +170 -0
- package/lib/service/agent/core/MessageAdapter.js +223 -0
- package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
- package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
- package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
- package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
- package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
- package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
- package/lib/service/agent/domain/insight-producer.js +267 -0
- package/lib/service/agent/domain/scan-prompts.js +105 -0
- package/lib/service/agent/forced-summary.js +266 -0
- package/lib/service/agent/index.js +91 -0
- package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
- package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
- package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
- package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
- package/lib/service/{chat → agent}/memory/index.js +1 -1
- package/lib/service/agent/policies.js +442 -0
- package/lib/service/agent/presets.js +303 -0
- package/lib/service/agent/strategies.js +717 -0
- package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
- package/lib/service/agent/tools/ai-analysis.js +75 -0
- package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
- package/lib/service/{chat → agent}/tools/composite.js +2 -1
- package/lib/service/{chat → agent}/tools/guard.js +1 -121
- package/lib/service/{chat → agent}/tools/index.js +33 -22
- package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
- package/lib/service/agent/tools/knowledge-graph.js +112 -0
- package/lib/service/agent/tools/scan-recipe.js +189 -0
- package/lib/service/agent/tools/system-interaction.js +476 -0
- package/lib/service/automation/DirectiveDetector.js +0 -1
- package/lib/service/automation/FileWatcher.js +0 -8
- package/lib/service/automation/handlers/CreateHandler.js +7 -3
- package/lib/service/automation/handlers/DraftHandler.js +7 -6
- 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 +49 -73
- package/lib/service/skills/SignalCollector.js +26 -19
- package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
- package/lib/service/wiki/WikiGenerator.js +1 -1
- package/lib/shared/FieldSpec.js +1 -1
- package/lib/shared/PathGuard.js +1 -1
- package/lib/shared/StyleGuide.js +1 -1
- package/package.json +4 -1
- package/resources/native-ui/screenshot.swift +228 -0
- package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
- package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
- package/lib/core/discovery/SpmDiscoverer.js +0 -5
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
- package/lib/http/routes/spm.js +0 -5
- package/lib/infrastructure/external/XcodeAutomation.js +0 -15
- package/lib/service/chat/ChatAgent.js +0 -1602
- package/lib/service/chat/Memory.js +0 -161
- package/lib/service/chat/ProducerAgent.js +0 -431
- package/lib/service/chat/ReasoningTrace.js +0 -523
- package/lib/service/chat/TaskPipeline.js +0 -357
- package/lib/service/chat/WorkingMemory.js +0 -357
- package/lib/service/chat/memory/PersistentMemory.js +0 -450
- package/lib/service/chat/tools/ai-analysis.js +0 -267
- package/lib/service/chat/tools/knowledge-graph.js +0 -234
- package/lib/service/chat/tools.js +0 -18
- package/lib/service/snippet/PlaceholderConverter.js +0 -5
- package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
- /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
- /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
- /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
- /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
- /package/lib/service/{chat → agent}/tools/query.js +0 -0
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
* 提取: struct, enum, trait, impl, function, method, mod, use, const/static
|
|
6
6
|
* 模式: Builder, Newtype, Factory (new/from), Error Handling (Result/Option/?),
|
|
7
7
|
* Async (tokio/async-std), Unsafe block, Derive macro
|
|
8
|
+
*
|
|
9
|
+
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
|
|
8
10
|
*/
|
|
9
11
|
|
|
12
|
+
import { ImportRecord } from '../analysis/ImportRecord.js';
|
|
13
|
+
|
|
10
14
|
function walkRust(root, ctx) {
|
|
11
15
|
for (let i = 0; i < root.namedChildCount; i++) {
|
|
12
16
|
const child = root.namedChild(i);
|
|
@@ -83,7 +87,6 @@ function _walkNode(node, ctx) {
|
|
|
83
87
|
// ── Use Declaration ──────────────────────────────────────────
|
|
84
88
|
|
|
85
89
|
function _parseUseDecl(node, ctx) {
|
|
86
|
-
// 提取 use 路径文本
|
|
87
90
|
const argNode = node.namedChildren.find(
|
|
88
91
|
(c) =>
|
|
89
92
|
c.type === 'use_wildcard' ||
|
|
@@ -93,8 +96,57 @@ function _parseUseDecl(node, ctx) {
|
|
|
93
96
|
c.type === 'identifier' ||
|
|
94
97
|
c.type === 'scoped_use_list'
|
|
95
98
|
);
|
|
96
|
-
if (argNode)
|
|
97
|
-
|
|
99
|
+
if (!argNode) return;
|
|
100
|
+
|
|
101
|
+
const text = argNode.text;
|
|
102
|
+
|
|
103
|
+
if (argNode.type === 'use_as_clause') {
|
|
104
|
+
// use crate::mod::Foo as Bar
|
|
105
|
+
const pathNode = argNode.namedChildren.find(
|
|
106
|
+
(c) => c.type === 'scoped_identifier' || c.type === 'identifier'
|
|
107
|
+
);
|
|
108
|
+
const aliasNode = argNode.namedChildren.find((c) => c.type === 'identifier' && c !== pathNode);
|
|
109
|
+
const fullPath = pathNode?.text || text;
|
|
110
|
+
const segments = fullPath.split('::');
|
|
111
|
+
const lastName = segments[segments.length - 1];
|
|
112
|
+
ctx.imports.push(
|
|
113
|
+
new ImportRecord(fullPath, {
|
|
114
|
+
symbols: [lastName],
|
|
115
|
+
alias: aliasNode?.text || lastName,
|
|
116
|
+
kind: 'named',
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
} else if (argNode.type === 'use_wildcard') {
|
|
120
|
+
// use crate::mod::*
|
|
121
|
+
const pathPart = text.replace(/::\*$/, '');
|
|
122
|
+
ctx.imports.push(
|
|
123
|
+
new ImportRecord(pathPart, { symbols: ['*'], kind: 'namespace' })
|
|
124
|
+
);
|
|
125
|
+
} else if (argNode.type === 'use_list' || argNode.type === 'scoped_use_list') {
|
|
126
|
+
// use crate::mod::{A, B, C} or use {A, B}
|
|
127
|
+
// Extract path prefix and symbol list from text
|
|
128
|
+
const match = text.match(/^(.+)::\{(.+)\}$/s);
|
|
129
|
+
if (match) {
|
|
130
|
+
const prefix = match[1];
|
|
131
|
+
const symbolsStr = match[2];
|
|
132
|
+
const symbols = symbolsStr.split(',').map((s) => s.trim().split('::').pop().split(' as ')[0].trim()).filter(Boolean);
|
|
133
|
+
ctx.imports.push(
|
|
134
|
+
new ImportRecord(prefix, { symbols, kind: 'named' })
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
ctx.imports.push(new ImportRecord(text));
|
|
138
|
+
}
|
|
139
|
+
} else if (argNode.type === 'scoped_identifier') {
|
|
140
|
+
// use crate::mod::Struct
|
|
141
|
+
const segments = text.split('::');
|
|
142
|
+
const lastName = segments[segments.length - 1];
|
|
143
|
+
const prefix = segments.slice(0, -1).join('::');
|
|
144
|
+
ctx.imports.push(
|
|
145
|
+
new ImportRecord(prefix || text, { symbols: [lastName], alias: lastName, kind: 'named' })
|
|
146
|
+
);
|
|
147
|
+
} else {
|
|
148
|
+
// use identifier (rare: e.g. `use std;`)
|
|
149
|
+
ctx.imports.push(new ImportRecord(text, { symbols: ['*'], alias: text, kind: 'namespace' }));
|
|
98
150
|
}
|
|
99
151
|
}
|
|
100
152
|
|
|
@@ -698,6 +750,234 @@ function _maxNesting(node, depth) {
|
|
|
698
750
|
return max;
|
|
699
751
|
}
|
|
700
752
|
|
|
753
|
+
// ── Rust Call Site 提取 (Phase 5) ────────────────────────────
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* 从 Rust AST root 提取所有调用点
|
|
757
|
+
* 遍历 function_item / impl method 中的 block → call_expression / method_call_expression
|
|
758
|
+
*
|
|
759
|
+
* @param {TreeSitterNode} root
|
|
760
|
+
* @param {object} ctx
|
|
761
|
+
* @param {string} _lang
|
|
762
|
+
*/
|
|
763
|
+
function extractCallSitesRust(root, ctx, _lang) {
|
|
764
|
+
const scopes = _collectRustScopes(root);
|
|
765
|
+
for (const scope of scopes) {
|
|
766
|
+
_extractRustCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* 递归收集 Rust 中所有函数/方法体作用域
|
|
772
|
+
*/
|
|
773
|
+
function _collectRustScopes(root) {
|
|
774
|
+
const scopes = [];
|
|
775
|
+
|
|
776
|
+
function visit(node, className) {
|
|
777
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
778
|
+
const child = node.namedChild(i);
|
|
779
|
+
|
|
780
|
+
if (child.type === 'impl_item') {
|
|
781
|
+
// impl Type { ... } or impl Trait for Type { ... }
|
|
782
|
+
const typeIdNodes = child.namedChildren.filter(
|
|
783
|
+
(c) => c.type === 'type_identifier' || c.type === 'scoped_type_identifier' || c.type === 'generic_type'
|
|
784
|
+
);
|
|
785
|
+
const hasFor = child.children?.some((c) => c.type === 'for');
|
|
786
|
+
let selfType = null;
|
|
787
|
+
if (hasFor && typeIdNodes.length >= 2) {
|
|
788
|
+
selfType = typeIdNodes[1]?.text;
|
|
789
|
+
} else if (typeIdNodes.length >= 1) {
|
|
790
|
+
selfType = typeIdNodes[0]?.text;
|
|
791
|
+
}
|
|
792
|
+
const body = child.namedChildren.find((c) => c.type === 'declaration_list');
|
|
793
|
+
if (body) {
|
|
794
|
+
visit(body, selfType || className);
|
|
795
|
+
}
|
|
796
|
+
} else if (child.type === 'function_item') {
|
|
797
|
+
const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
|
|
798
|
+
const body = child.namedChildren.find((c) => c.type === 'block');
|
|
799
|
+
if (name && body) {
|
|
800
|
+
scopes.push({ body, className, methodName: name });
|
|
801
|
+
}
|
|
802
|
+
} else if (child.type === 'mod_item') {
|
|
803
|
+
const body = child.namedChildren.find((c) => c.type === 'declaration_list');
|
|
804
|
+
if (body) {
|
|
805
|
+
visit(body, null);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
visit(root, null);
|
|
812
|
+
return scopes;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* 从 Rust block 中递归提取调用点
|
|
817
|
+
*/
|
|
818
|
+
function _extractRustCallSitesFromBody(bodyNode, className, methodName, ctx) {
|
|
819
|
+
if (!bodyNode) return;
|
|
820
|
+
|
|
821
|
+
const RUST_NOISE = new Set([
|
|
822
|
+
'println', 'eprintln', 'print', 'eprint', 'dbg', 'format',
|
|
823
|
+
'vec', 'panic', 'assert', 'assert_eq', 'assert_ne', 'debug_assert',
|
|
824
|
+
'todo', 'unimplemented', 'unreachable', 'cfg',
|
|
825
|
+
'write', 'writeln', 'log', 'info', 'warn', 'error', 'debug', 'trace',
|
|
826
|
+
]);
|
|
827
|
+
|
|
828
|
+
function walk(node, isAwaited) {
|
|
829
|
+
if (!node || node.type === 'ERROR' || node.isMissing) return;
|
|
830
|
+
|
|
831
|
+
// await expression: expr.await
|
|
832
|
+
if (node.type === 'await_expression') {
|
|
833
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
834
|
+
walk(node.namedChild(i), true);
|
|
835
|
+
}
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// call_expression: func(args) or Struct::method(args)
|
|
840
|
+
if (node.type === 'call_expression') {
|
|
841
|
+
const func = node.namedChildren[0];
|
|
842
|
+
if (!func) { walkChildren(node, false); return; }
|
|
843
|
+
|
|
844
|
+
let callee, receiver = null, receiverType = null, callType;
|
|
845
|
+
|
|
846
|
+
if (func.type === 'scoped_identifier' || func.type === 'scoped_type_identifier') {
|
|
847
|
+
// Struct::method() or crate::mod::func()
|
|
848
|
+
const parts = func.text.split('::');
|
|
849
|
+
if (parts.length >= 2) {
|
|
850
|
+
callee = parts[parts.length - 1];
|
|
851
|
+
receiver = parts.slice(0, -1).join('::');
|
|
852
|
+
// Check if receiver looks like a type (PascalCase)
|
|
853
|
+
const lastReceiver = parts[parts.length - 2];
|
|
854
|
+
if (/^[A-Z]/.test(lastReceiver)) {
|
|
855
|
+
receiverType = lastReceiver;
|
|
856
|
+
callType = callee === 'new' || callee === 'default' ? 'constructor' : 'static';
|
|
857
|
+
} else {
|
|
858
|
+
callType = 'function'; // module-qualified function
|
|
859
|
+
}
|
|
860
|
+
} else {
|
|
861
|
+
callee = func.text;
|
|
862
|
+
callType = 'function';
|
|
863
|
+
}
|
|
864
|
+
} else if (func.type === 'field_expression') {
|
|
865
|
+
// obj.func() — though Rust usually uses method_call_expression for this
|
|
866
|
+
const parts = func.text.split('.');
|
|
867
|
+
if (parts.length >= 2) {
|
|
868
|
+
receiver = parts.slice(0, -1).join('.');
|
|
869
|
+
callee = parts[parts.length - 1];
|
|
870
|
+
callType = 'method';
|
|
871
|
+
if (receiver === 'self' || receiver === '&self' || receiver === '&mut self') {
|
|
872
|
+
receiverType = className;
|
|
873
|
+
}
|
|
874
|
+
} else {
|
|
875
|
+
callee = func.text;
|
|
876
|
+
callType = 'function';
|
|
877
|
+
}
|
|
878
|
+
} else if (func.type === 'identifier') {
|
|
879
|
+
callee = func.text;
|
|
880
|
+
if (RUST_NOISE.has(callee)) { walkChildren(node, false); return; }
|
|
881
|
+
// PascalCase → constructor pattern (rare in Rust — turbofish/struct literal more common)
|
|
882
|
+
callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
|
|
883
|
+
if (callType === 'constructor') receiverType = callee;
|
|
884
|
+
} else {
|
|
885
|
+
callee = func.text?.slice(0, 80) || 'unknown';
|
|
886
|
+
callType = 'function';
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const args = node.namedChildren.find((c) => c.type === 'arguments');
|
|
890
|
+
const argCount = args ? args.namedChildCount : 0;
|
|
891
|
+
|
|
892
|
+
ctx.callSites.push({
|
|
893
|
+
callee,
|
|
894
|
+
callerMethod: methodName,
|
|
895
|
+
callerClass: className,
|
|
896
|
+
callType,
|
|
897
|
+
receiver,
|
|
898
|
+
receiverType,
|
|
899
|
+
argCount,
|
|
900
|
+
line: node.startPosition.row + 1,
|
|
901
|
+
isAwait: isAwaited,
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
if (args) walkChildren(args, false);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// method_call_expression: obj.method(args) — Rust-specific
|
|
909
|
+
if (node.type === 'method_call_expression') {
|
|
910
|
+
const valueNode = node.namedChildren.find(
|
|
911
|
+
(c) => c.type !== 'field_identifier' && c.type !== 'arguments' && c.type !== 'type_arguments'
|
|
912
|
+
);
|
|
913
|
+
const nameNode = node.namedChildren.find((c) => c.type === 'field_identifier');
|
|
914
|
+
const args = node.namedChildren.find((c) => c.type === 'arguments');
|
|
915
|
+
|
|
916
|
+
const callee = nameNode?.text || 'unknown';
|
|
917
|
+
const receiver = valueNode?.text?.slice(0, 80) || null;
|
|
918
|
+
let receiverType = null;
|
|
919
|
+
let callType = 'method';
|
|
920
|
+
|
|
921
|
+
if (receiver === 'self' || receiver === '&self' || receiver === '&mut self') {
|
|
922
|
+
receiverType = className;
|
|
923
|
+
} else if (receiver && /^[A-Z]/.test(receiver)) {
|
|
924
|
+
receiverType = receiver;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Skip noise methods
|
|
928
|
+
if (RUST_NOISE.has(callee)) { walkChildren(node, false); return; }
|
|
929
|
+
|
|
930
|
+
const argCount = args ? args.namedChildCount : 0;
|
|
931
|
+
|
|
932
|
+
ctx.callSites.push({
|
|
933
|
+
callee,
|
|
934
|
+
callerMethod: methodName,
|
|
935
|
+
callerClass: className,
|
|
936
|
+
callType,
|
|
937
|
+
receiver,
|
|
938
|
+
receiverType,
|
|
939
|
+
argCount,
|
|
940
|
+
line: node.startPosition.row + 1,
|
|
941
|
+
isAwait: isAwaited,
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
if (args) walkChildren(args, false);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// macro_invocation: some_macro!(args) — skip noise macros
|
|
949
|
+
if (node.type === 'macro_invocation') {
|
|
950
|
+
const macroName = node.namedChildren.find((c) => c.type === 'identifier')?.text;
|
|
951
|
+
if (macroName && !RUST_NOISE.has(macroName) && !RUST_NOISE.has(macroName.replace(/!$/, ''))) {
|
|
952
|
+
// Only record non-noise macros as function calls
|
|
953
|
+
ctx.callSites.push({
|
|
954
|
+
callee: macroName,
|
|
955
|
+
callerMethod: methodName,
|
|
956
|
+
callerClass: className,
|
|
957
|
+
callType: 'function',
|
|
958
|
+
receiver: null,
|
|
959
|
+
receiverType: null,
|
|
960
|
+
argCount: 0,
|
|
961
|
+
line: node.startPosition.row + 1,
|
|
962
|
+
isAwait: false,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
// Don't recurse into macro bodies
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
walkChildren(node, false);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function walkChildren(node, isAwaited) {
|
|
973
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
974
|
+
walk(node.namedChild(i), isAwaited);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
walk(bodyNode, false);
|
|
979
|
+
}
|
|
980
|
+
|
|
701
981
|
// ── Plugin Export ────────────────────────────────────────────
|
|
702
982
|
|
|
703
983
|
let _grammar = null;
|
|
@@ -712,5 +992,6 @@ export const plugin = {
|
|
|
712
992
|
getGrammar,
|
|
713
993
|
walk: walkRust,
|
|
714
994
|
detectPatterns: detectRustPatterns,
|
|
995
|
+
extractCallSites: extractCallSitesRust,
|
|
715
996
|
extensions: ['.rs'],
|
|
716
997
|
};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module lang-swift
|
|
3
3
|
* @description Swift AST Walker 插件 — 从 AstAnalyzer.js 迁移
|
|
4
|
+
*
|
|
5
|
+
* Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
|
|
4
6
|
*/
|
|
5
7
|
|
|
8
|
+
import { ImportRecord } from '../analysis/ImportRecord.js';
|
|
9
|
+
|
|
6
10
|
// ── Swift AST 遍历 ──
|
|
7
11
|
|
|
8
12
|
function walkSwift(root, ctx) {
|
|
@@ -19,7 +23,9 @@ function _walkSwiftNode(node, ctx, parentClassName) {
|
|
|
19
23
|
(c) => c.type === 'identifier' || c.type === 'simple_identifier'
|
|
20
24
|
);
|
|
21
25
|
if (mod) {
|
|
22
|
-
ctx.imports.push(
|
|
26
|
+
ctx.imports.push(
|
|
27
|
+
new ImportRecord(mod.text, { symbols: ['*'], alias: mod.text, kind: 'namespace' })
|
|
28
|
+
);
|
|
23
29
|
}
|
|
24
30
|
break;
|
|
25
31
|
}
|
|
@@ -315,6 +321,178 @@ function _maxNesting(node, depth) {
|
|
|
315
321
|
return max;
|
|
316
322
|
}
|
|
317
323
|
|
|
324
|
+
// ── Swift Call Site 提取 (Phase 5) ───────────────────────────
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 从 Swift AST root 提取所有调用点
|
|
328
|
+
* 遍历 function_declaration 中的 function_body → call_expression
|
|
329
|
+
*
|
|
330
|
+
* @param {TreeSitterNode} root
|
|
331
|
+
* @param {object} ctx
|
|
332
|
+
* @param {string} _lang
|
|
333
|
+
*/
|
|
334
|
+
function extractCallSitesSwift(root, ctx, _lang) {
|
|
335
|
+
const scopes = _collectSwiftScopes(root);
|
|
336
|
+
for (const scope of scopes) {
|
|
337
|
+
_extractSwiftCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 递归收集 Swift 中所有函数体作用域
|
|
343
|
+
*/
|
|
344
|
+
function _collectSwiftScopes(root) {
|
|
345
|
+
const scopes = [];
|
|
346
|
+
|
|
347
|
+
function visit(node, className) {
|
|
348
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
349
|
+
const child = node.namedChild(i);
|
|
350
|
+
|
|
351
|
+
if (child.type === 'class_declaration' || child.type === 'struct_declaration' || child.type === 'enum_declaration') {
|
|
352
|
+
const name = child.namedChildren.find(
|
|
353
|
+
(c) => c.type === 'type_identifier' || c.type === 'simple_identifier'
|
|
354
|
+
)?.text;
|
|
355
|
+
const body = child.namedChildren.find(
|
|
356
|
+
(c) => c.type === 'class_body' || c.type === 'struct_body' || c.type === 'enum_body'
|
|
357
|
+
);
|
|
358
|
+
if (body) {
|
|
359
|
+
visit(body, name || className);
|
|
360
|
+
}
|
|
361
|
+
} else if (child.type === 'extension_declaration') {
|
|
362
|
+
const extName = child.namedChildren.find(
|
|
363
|
+
(c) => c.type === 'user_type' || c.type === 'type_identifier'
|
|
364
|
+
)?.text;
|
|
365
|
+
const body = child.namedChildren.find((c) => c.type === 'extension_body');
|
|
366
|
+
if (body) {
|
|
367
|
+
visit(body, extName || className);
|
|
368
|
+
}
|
|
369
|
+
} else if (child.type === 'function_declaration') {
|
|
370
|
+
const name = child.namedChildren.find((c) => c.type === 'simple_identifier')?.text || 'unknown';
|
|
371
|
+
const body = child.namedChildren.find((c) => c.type === 'function_body');
|
|
372
|
+
if (body) {
|
|
373
|
+
scopes.push({ body, className, methodName: name });
|
|
374
|
+
}
|
|
375
|
+
} else if (child.type === 'property_declaration') {
|
|
376
|
+
// computed property with getter
|
|
377
|
+
const computed = child.namedChildren.find(
|
|
378
|
+
(c) => c.type === 'computed_property' || c.type === 'willSet_didSet_block'
|
|
379
|
+
);
|
|
380
|
+
if (computed) {
|
|
381
|
+
const propName = child.namedChildren.find(
|
|
382
|
+
(c) => c.type === 'simple_identifier' || c.type === 'pattern'
|
|
383
|
+
)?.text;
|
|
384
|
+
scopes.push({ body: computed, className, methodName: `get_${propName || 'prop'}` });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
visit(root, null);
|
|
391
|
+
return scopes;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 从 Swift function body 中递归提取调用点
|
|
396
|
+
*/
|
|
397
|
+
function _extractSwiftCallSitesFromBody(bodyNode, className, methodName, ctx) {
|
|
398
|
+
if (!bodyNode) return;
|
|
399
|
+
|
|
400
|
+
const SWIFT_NOISE = new Set([
|
|
401
|
+
'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
|
|
402
|
+
'assert', 'assertionFailure', 'NSLog',
|
|
403
|
+
'min', 'max', 'abs', 'stride', 'zip', 'type',
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
function walk(node) {
|
|
407
|
+
if (!node || node.type === 'ERROR' || node.isMissing) return;
|
|
408
|
+
|
|
409
|
+
// call_expression in Swift tree-sitter
|
|
410
|
+
if (node.type === 'call_expression') {
|
|
411
|
+
const func = node.namedChildren[0];
|
|
412
|
+
if (!func) { walkChildren(node); return; }
|
|
413
|
+
|
|
414
|
+
let callee, receiver = null, receiverType = null, callType;
|
|
415
|
+
let isAwait = false;
|
|
416
|
+
|
|
417
|
+
// Check if parent is await
|
|
418
|
+
if (node.parent?.type === 'await_expression') {
|
|
419
|
+
isAwait = true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (func.type === 'navigation_expression' || func.type === 'member_access') {
|
|
423
|
+
// obj.method() or Type.staticMethod()
|
|
424
|
+
const parts = func.text.split('.');
|
|
425
|
+
if (parts.length >= 2) {
|
|
426
|
+
receiver = parts.slice(0, -1).join('.');
|
|
427
|
+
callee = parts[parts.length - 1];
|
|
428
|
+
if (receiver === 'self') {
|
|
429
|
+
receiverType = className;
|
|
430
|
+
callType = 'method';
|
|
431
|
+
} else if (receiver === 'super') {
|
|
432
|
+
receiverType = className;
|
|
433
|
+
callType = 'super';
|
|
434
|
+
} else if (/^[A-Z]/.test(receiver)) {
|
|
435
|
+
receiverType = receiver;
|
|
436
|
+
callType = 'static';
|
|
437
|
+
} else {
|
|
438
|
+
callType = 'method';
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
callee = func.text;
|
|
442
|
+
callType = 'function';
|
|
443
|
+
}
|
|
444
|
+
} else if (func.type === 'simple_identifier' || func.type === 'identifier') {
|
|
445
|
+
callee = func.text;
|
|
446
|
+
if (SWIFT_NOISE.has(callee)) { walkChildren(node); return; }
|
|
447
|
+
// PascalCase → constructor (Swift initializer)
|
|
448
|
+
callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
|
|
449
|
+
if (callType === 'constructor') receiverType = callee;
|
|
450
|
+
} else {
|
|
451
|
+
callee = func.text?.slice(0, 80) || 'unknown';
|
|
452
|
+
callType = 'function';
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Count arguments
|
|
456
|
+
const callSuffix = node.namedChildren.find(
|
|
457
|
+
(c) => c.type === 'call_suffix' || c.type === 'value_arguments'
|
|
458
|
+
);
|
|
459
|
+
const argCount = callSuffix ? callSuffix.namedChildCount : 0;
|
|
460
|
+
|
|
461
|
+
ctx.callSites.push({
|
|
462
|
+
callee,
|
|
463
|
+
callerMethod: methodName,
|
|
464
|
+
callerClass: className,
|
|
465
|
+
callType,
|
|
466
|
+
receiver,
|
|
467
|
+
receiverType,
|
|
468
|
+
argCount,
|
|
469
|
+
line: node.startPosition.row + 1,
|
|
470
|
+
isAwait,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// walk arguments for nested calls
|
|
474
|
+
if (callSuffix) walkChildren(callSuffix);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// try expression → walk into children
|
|
479
|
+
if (node.type === 'try_expression' || node.type === 'await_expression') {
|
|
480
|
+
walkChildren(node);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
walkChildren(node);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function walkChildren(node) {
|
|
488
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
489
|
+
walk(node.namedChild(i));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
walk(bodyNode);
|
|
494
|
+
}
|
|
495
|
+
|
|
318
496
|
// ── 插件导出 ──
|
|
319
497
|
|
|
320
498
|
let _grammar = null;
|
|
@@ -329,5 +507,6 @@ export const plugin = {
|
|
|
329
507
|
getGrammar,
|
|
330
508
|
walk: walkSwift,
|
|
331
509
|
detectPatterns: detectSwiftPatterns,
|
|
510
|
+
extractCallSites: extractCallSitesSwift,
|
|
332
511
|
extensions: ['.swift'],
|
|
333
512
|
};
|