autosnippet 3.0.0 → 3.0.2
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 +230 -324
- package/bin/api-server.js +1 -1
- package/bin/cli.js +204 -244
- package/bin/mcp-server.js +5 -3
- package/config/knowledge-base.config.js +132 -132
- package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
- package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
- package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
- package/dashboard/dist/index.html +3 -3
- package/lib/bootstrap.js +8 -8
- package/lib/cli/AiScanService.js +86 -40
- package/lib/cli/KnowledgeSyncService.js +113 -74
- package/lib/cli/SetupService.js +439 -277
- package/lib/cli/UpgradeService.js +63 -100
- package/lib/core/AstAnalyzer.js +276 -597
- package/lib/core/ast/ProjectGraph.js +101 -40
- package/lib/core/ast/ensure-grammars.js +232 -0
- package/lib/core/ast/index.js +115 -0
- package/lib/core/ast/lang-dart.js +661 -0
- package/lib/core/ast/lang-go.js +530 -0
- package/lib/core/ast/lang-java.js +435 -0
- package/lib/core/ast/lang-javascript.js +272 -0
- package/lib/core/ast/lang-kotlin.js +423 -0
- package/lib/core/ast/lang-objc.js +388 -0
- package/lib/core/ast/lang-python.js +371 -0
- package/lib/core/ast/lang-swift.js +337 -0
- package/lib/core/ast/lang-typescript.js +503 -0
- package/lib/core/capability/CapabilityProbe.js +18 -9
- package/lib/core/constitution/Constitution.js +2 -3
- package/lib/core/constitution/ConstitutionValidator.js +65 -24
- package/lib/core/discovery/DartDiscoverer.js +534 -0
- package/lib/core/discovery/DiscovererRegistry.js +83 -0
- package/lib/core/discovery/GenericDiscoverer.js +225 -0
- package/lib/core/discovery/GoDiscoverer.js +541 -0
- package/lib/core/discovery/JvmDiscoverer.js +506 -0
- package/lib/core/discovery/NodeDiscoverer.js +466 -0
- package/lib/core/discovery/ProjectDiscoverer.js +93 -0
- package/lib/core/discovery/PythonDiscoverer.js +338 -0
- package/lib/core/discovery/SpmDiscoverer.js +5 -0
- package/lib/core/discovery/index.js +53 -0
- package/lib/core/enhancement/EnhancementPack.js +71 -0
- package/lib/core/enhancement/EnhancementRegistry.js +47 -0
- package/lib/core/enhancement/android-enhancement.js +102 -0
- package/lib/core/enhancement/django-enhancement.js +70 -0
- package/lib/core/enhancement/fastapi-enhancement.js +63 -0
- package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
- package/lib/core/enhancement/go-web-enhancement.js +201 -0
- package/lib/core/enhancement/index.js +65 -0
- package/lib/core/enhancement/node-server-enhancement.js +88 -0
- package/lib/core/enhancement/react-enhancement.js +86 -0
- package/lib/core/enhancement/spring-enhancement.js +112 -0
- package/lib/core/enhancement/vue-enhancement.js +96 -0
- package/lib/core/gateway/Gateway.js +8 -9
- package/lib/core/gateway/GatewayActionRegistry.js +1 -1
- package/lib/core/permission/PermissionManager.js +12 -8
- package/lib/domain/index.js +13 -9
- package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
- package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
- package/lib/domain/knowledge/Lifecycle.js +22 -22
- package/lib/domain/knowledge/index.js +9 -12
- package/lib/domain/knowledge/values/Constraints.js +31 -21
- package/lib/domain/knowledge/values/Content.js +21 -13
- package/lib/domain/knowledge/values/Quality.js +31 -18
- package/lib/domain/knowledge/values/Reasoning.js +20 -12
- package/lib/domain/knowledge/values/Relations.js +37 -25
- package/lib/domain/knowledge/values/Stats.js +18 -12
- package/lib/domain/knowledge/values/index.js +4 -3
- package/lib/domain/snippet/Snippet.js +35 -10
- package/lib/external/ai/AiFactory.js +48 -16
- package/lib/external/ai/AiProvider.js +184 -90
- package/lib/external/ai/providers/ClaudeProvider.js +25 -12
- package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
- package/lib/external/ai/providers/MockProvider.js +9 -3
- package/lib/external/ai/providers/OpenAiProvider.js +51 -29
- package/lib/external/mcp/McpServer.js +66 -36
- package/lib/external/mcp/errorHandler.js +23 -11
- package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
- package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
- package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
- package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
- package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
- package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
- package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
- package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
- package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
- package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
- package/lib/external/mcp/handlers/bootstrap.js +691 -168
- package/lib/external/mcp/handlers/browse.js +66 -22
- package/lib/external/mcp/handlers/candidate.js +118 -35
- package/lib/external/mcp/handlers/consolidated.js +49 -17
- package/lib/external/mcp/handlers/guard.js +104 -39
- package/lib/external/mcp/handlers/knowledge.js +60 -36
- package/lib/external/mcp/handlers/search.js +43 -14
- package/lib/external/mcp/handlers/skill.js +120 -45
- package/lib/external/mcp/handlers/structure.js +240 -86
- package/lib/external/mcp/handlers/system.js +42 -12
- package/lib/external/mcp/handlers/wiki.js +58 -33
- package/lib/external/mcp/tools.js +306 -123
- package/lib/http/HttpServer.js +72 -47
- package/lib/http/middleware/RateLimiter.js +5 -3
- package/lib/http/middleware/errorHandler.js +6 -1
- package/lib/http/middleware/requestLogger.js +14 -3
- package/lib/http/middleware/roleResolver.js +30 -23
- package/lib/http/routes/ai.js +387 -265
- package/lib/http/routes/auth.js +81 -61
- package/lib/http/routes/candidates.js +430 -320
- package/lib/http/routes/commands.js +289 -189
- package/lib/http/routes/extract.js +158 -125
- package/lib/http/routes/guardRules.js +309 -217
- package/lib/http/routes/knowledge.js +213 -154
- package/lib/http/routes/modules.js +578 -0
- package/lib/http/routes/monitoring.js +6 -6
- package/lib/http/routes/recipes.js +104 -93
- package/lib/http/routes/search.js +361 -305
- package/lib/http/routes/skills.js +145 -98
- package/lib/http/routes/snippets.js +42 -30
- package/lib/http/routes/spm.js +3 -405
- package/lib/http/routes/violations.js +113 -93
- package/lib/http/routes/wiki.js +211 -170
- package/lib/http/utils/routeHelpers.js +3 -1
- package/lib/http/utils/sse-sessions.js +16 -6
- package/lib/http/utils/sse.js +15 -5
- package/lib/infrastructure/audit/AuditLogger.js +5 -2
- package/lib/infrastructure/audit/AuditStore.js +10 -7
- package/lib/infrastructure/cache/CacheService.js +3 -1
- package/lib/infrastructure/cache/GraphCache.js +8 -4
- package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
- package/lib/infrastructure/config/ConfigLoader.js +9 -5
- package/lib/infrastructure/config/Defaults.js +30 -10
- package/lib/infrastructure/config/Paths.js +28 -8
- package/lib/infrastructure/config/TriggerSymbol.js +22 -10
- package/lib/infrastructure/database/DatabaseConnection.js +15 -10
- package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
- package/lib/infrastructure/external/ClipboardManager.js +6 -2
- package/lib/infrastructure/external/NativeUi.js +50 -43
- package/lib/infrastructure/external/OpenBrowser.js +14 -17
- package/lib/infrastructure/external/XcodeAutomation.js +14 -258
- package/lib/infrastructure/logging/Logger.js +46 -30
- package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
- package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
- package/lib/infrastructure/paths/HeaderResolver.js +25 -9
- package/lib/infrastructure/paths/PathFinder.js +34 -12
- package/lib/infrastructure/plugin/PluginManager.js +26 -8
- package/lib/infrastructure/realtime/RealtimeService.js +2 -2
- package/lib/infrastructure/vector/Chunker.js +22 -7
- package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
- package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
- package/lib/infrastructure/vector/VectorStore.js +28 -10
- package/lib/injection/ServiceContainer.js +247 -93
- package/lib/platform/ios/index.js +63 -0
- package/lib/platform/ios/routes/spm.js +437 -0
- package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
- package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
- package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
- package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
- package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
- package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
- package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
- package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
- package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
- package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
- package/lib/repository/base/BaseRepository.js +7 -9
- package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
- package/lib/repository/token/TokenUsageStore.js +4 -2
- package/lib/service/automation/ActionPipeline.js +1 -1
- package/lib/service/automation/AutomationOrchestrator.js +8 -4
- package/lib/service/automation/ContextCollector.js +7 -5
- package/lib/service/automation/DirectiveDetector.js +23 -16
- package/lib/service/automation/FileWatcher.js +112 -56
- package/lib/service/automation/TriggerResolver.js +6 -4
- package/lib/service/automation/handlers/AlinkHandler.js +24 -12
- package/lib/service/automation/handlers/CreateHandler.js +19 -20
- package/lib/service/automation/handlers/DraftHandler.js +14 -8
- package/lib/service/automation/handlers/GuardHandler.js +93 -63
- package/lib/service/automation/handlers/HeaderHandler.js +1 -6
- package/lib/service/automation/handlers/SearchHandler.js +155 -88
- package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
- package/lib/service/candidate/SimilarityService.js +25 -9
- package/lib/service/chat/AnalystAgent.js +50 -24
- package/lib/service/chat/CandidateGuardrail.js +143 -17
- package/lib/service/chat/ChatAgent.js +759 -243
- package/lib/service/chat/ContextWindow.js +116 -71
- package/lib/service/chat/ConversationStore.js +77 -36
- package/lib/service/chat/EpisodicConsolidator.js +47 -23
- package/lib/service/chat/HandoffProtocol.js +98 -22
- package/lib/service/chat/Memory.js +34 -14
- package/lib/service/chat/ProducerAgent.js +40 -20
- package/lib/service/chat/ProjectSemanticMemory.js +109 -78
- package/lib/service/chat/ReasoningLayer.js +148 -70
- package/lib/service/chat/ReasoningTrace.js +44 -32
- package/lib/service/chat/TaskPipeline.js +39 -19
- package/lib/service/chat/ToolRegistry.js +48 -29
- package/lib/service/chat/WorkingMemory.js +44 -18
- package/lib/service/chat/tools.js +1096 -494
- package/lib/service/context/RecipeExtractor.js +132 -51
- package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
- package/lib/service/cursor/KnowledgeCompressor.js +25 -22
- package/lib/service/cursor/RulesGenerator.js +13 -7
- package/lib/service/cursor/SkillsSyncer.js +77 -27
- package/lib/service/cursor/TokenBudget.js +2 -2
- package/lib/service/cursor/TopicClassifier.js +54 -20
- package/lib/service/guard/ComplianceReporter.js +55 -43
- package/lib/service/guard/ExclusionManager.js +67 -29
- package/lib/service/guard/GuardCheckEngine.js +381 -86
- package/lib/service/guard/GuardFeedbackLoop.js +22 -10
- package/lib/service/guard/GuardService.js +29 -19
- package/lib/service/guard/RuleLearner.js +55 -23
- package/lib/service/guard/SourceFileCollector.js +27 -20
- package/lib/service/guard/ViolationsStore.js +43 -38
- package/lib/service/knowledge/CodeEntityGraph.js +147 -82
- package/lib/service/knowledge/ConfidenceRouter.js +12 -10
- package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
- package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
- package/lib/service/knowledge/KnowledgeService.js +222 -112
- package/lib/service/module/ModuleService.js +969 -0
- package/lib/service/quality/FeedbackCollector.js +27 -15
- package/lib/service/quality/QualityScorer.js +78 -24
- package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
- package/lib/service/recipe/RecipeParser.js +78 -45
- package/lib/service/search/CoarseRanker.js +43 -28
- package/lib/service/search/CrossEncoderReranker.js +32 -21
- package/lib/service/search/InvertedIndex.js +21 -7
- package/lib/service/search/MultiSignalRanker.js +90 -28
- package/lib/service/search/RetrievalFunnel.js +45 -24
- package/lib/service/search/SearchEngine.js +255 -103
- package/lib/service/skills/EventAggregator.js +32 -15
- package/lib/service/skills/SignalCollector.js +140 -64
- package/lib/service/skills/SkillAdvisor.js +79 -42
- package/lib/service/skills/SkillHooks.js +16 -14
- package/lib/service/snippet/PlaceholderConverter.js +5 -0
- package/lib/service/snippet/SnippetFactory.js +116 -99
- package/lib/service/snippet/SnippetInstaller.js +234 -62
- package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
- package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
- package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
- package/lib/service/wiki/WikiGenerator.js +637 -263
- package/lib/shared/DimensionCopyRegistry.js +472 -0
- package/lib/shared/LanguageService.js +399 -0
- package/lib/shared/PathGuard.js +45 -28
- package/lib/shared/RecipeReadinessChecker.js +72 -12
- package/lib/shared/constants.js +41 -41
- package/lib/shared/errors/BaseError.js +2 -2
- package/lib/shared/errors/index.js +4 -4
- package/lib/shared/similarity.js +25 -8
- package/lib/shared/token-utils.js +6 -2
- package/lib/shared/utils/common.js +12 -4
- package/package.json +49 -13
- package/scripts/bench-real-projects.mjs +256 -0
- package/scripts/build-native-ui.js +30 -30
- package/scripts/clear-old-vector-index.js +5 -35
- package/scripts/clear-vector-cache.js +7 -37
- package/scripts/collect-test-project-stats.mjs +160 -0
- package/scripts/diagnose-mcp.js +41 -32
- package/scripts/ensure-parse-package.js +6 -9
- package/scripts/generate-recipe-drafts.js +116 -77
- package/scripts/init-db.js +3 -20
- package/scripts/init-snippets.js +305 -0
- package/scripts/init-vector-db.js +173 -170
- package/scripts/install-cursor-skill.js +148 -104
- package/scripts/install-full.js +8 -21
- package/scripts/install-vscode-copilot.js +146 -145
- package/scripts/migrate-md-to-knowledge.mjs +139 -151
- package/scripts/postinstall-safe.js +5 -17
- package/scripts/recipe-audit.js +106 -82
- package/scripts/release.js +283 -323
- package/scripts/setup-mcp-config.js +60 -52
- package/scripts/verify-context-api.js +20 -20
- package/skills/autosnippet-analysis/SKILL.md +10 -6
- package/skills/autosnippet-candidates/SKILL.md +27 -26
- package/skills/autosnippet-coldstart/SKILL.md +555 -38
- package/skills/autosnippet-concepts/SKILL.md +349 -337
- package/skills/autosnippet-create/SKILL.md +5 -5
- package/skills/autosnippet-reference-dart/SKILL.md +543 -0
- package/skills/autosnippet-reference-go/SKILL.md +539 -0
- package/skills/autosnippet-reference-java/SKILL.md +534 -0
- package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
- package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
- package/skills/autosnippet-reference-objc/SKILL.md +29 -6
- package/skills/autosnippet-reference-python/SKILL.md +800 -0
- package/skills/autosnippet-reference-swift/SKILL.md +70 -14
- package/skills/autosnippet-structure/SKILL.md +4 -4
- package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
- package/templates/recipes-setup/README.md +2 -2
- package/templates/recipes-setup/_template.md +1 -1
- package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
- package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
- package/resources/asd-entry/main.swift +0 -159
- package/scripts/build-asd-entry.js +0 -51
- package/scripts/init-xcode-snippets.js +0 -311
- package/template.json +0 -39
package/lib/core/AstAnalyzer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module AstAnalyzer
|
|
3
|
-
* @description 基于 Tree-sitter 的多语言 AST
|
|
3
|
+
* @description 基于 Tree-sitter 的多语言 AST 分析器(插件注册制)
|
|
4
4
|
*
|
|
5
5
|
* 提供结构化代码分析能力:
|
|
6
6
|
* - 类/协议/扩展 声明与继承关系
|
|
@@ -9,22 +9,48 @@
|
|
|
9
9
|
* - 设计模式检测(Singleton、Delegate、Factory、Observer)
|
|
10
10
|
* - 代码结构指标(圈复杂度、嵌套深度、方法行数)
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* 支持语言:通过插件注册 — ObjC、Swift、TypeScript、JavaScript、Python、Java、Kotlin
|
|
13
|
+
* 插件注册入口: lib/core/ast/index.js
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
|
-
import { createRequire } from 'module';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
|
|
16
18
|
const require = createRequire(import.meta.url);
|
|
17
19
|
|
|
18
|
-
let Parser
|
|
20
|
+
let Parser;
|
|
19
21
|
|
|
20
22
|
try {
|
|
21
23
|
Parser = require('tree-sitter');
|
|
22
|
-
LangObjC = require('tree-sitter-objc');
|
|
23
|
-
LangSwift = require('tree-sitter-swift');
|
|
24
24
|
} catch {
|
|
25
25
|
// 在没有 tree-sitter 的环境中优雅降级
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// ──────────────────────────────────────────────────────────────────
|
|
29
|
+
// 插件注册表
|
|
30
|
+
// ──────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {object} LangPlugin
|
|
34
|
+
* @property {Function} getGrammar — () => tree-sitter Language Module (lazy load)
|
|
35
|
+
* @property {Function} walk — (rootNode, ctx) => void (AST 遍历)
|
|
36
|
+
* @property {Function} [detectPatterns] — (root, lang, methods, properties, classes) => Pattern[]
|
|
37
|
+
* @property {string[]} [extensions] — 关联的文件扩展名
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** @type {Map<string, LangPlugin>} */
|
|
41
|
+
const _langPlugins = new Map();
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 注册语言 AST 插件
|
|
45
|
+
* @param {string} langId — 语言标识 (e.g. 'objectivec', 'swift', 'typescript')
|
|
46
|
+
* @param {LangPlugin} plugin
|
|
47
|
+
*/
|
|
48
|
+
export function registerLanguage(langId, plugin) {
|
|
49
|
+
_langPlugins.set(langId, plugin);
|
|
50
|
+
// 清除 parser cache 以便下次使用新语法
|
|
51
|
+
_parserCache.delete(langId);
|
|
52
|
+
}
|
|
53
|
+
|
|
28
54
|
// ──────────────────────────────────────────────────────────────────
|
|
29
55
|
// 公共 API
|
|
30
56
|
// ──────────────────────────────────────────────────────────────────
|
|
@@ -32,49 +58,58 @@ try {
|
|
|
32
58
|
/**
|
|
33
59
|
* 分析单个源文件,返回结构化 AST 摘要
|
|
34
60
|
* @param {string} source 源代码文本
|
|
35
|
-
* @param {string} lang 语言标识 'objectivec' | 'swift'
|
|
61
|
+
* @param {string} lang 语言标识 'objectivec' | 'swift' | 'typescript' | 'javascript' | 'python' | 'java' | 'kotlin' | 'tsx'
|
|
36
62
|
* @returns {AstSummary | null}
|
|
37
63
|
*/
|
|
38
64
|
function analyzeFile(source, lang) {
|
|
65
|
+
const plugin = _langPlugins.get(lang);
|
|
66
|
+
if (!plugin) {
|
|
67
|
+
return null; // 无插件 → 优雅降级
|
|
68
|
+
}
|
|
69
|
+
|
|
39
70
|
const parser = _getParser(lang);
|
|
40
|
-
if (!parser)
|
|
71
|
+
if (!parser) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
41
74
|
|
|
42
75
|
const tree = parser.parse(source);
|
|
43
76
|
const root = tree.rootNode;
|
|
44
77
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
const ctx = {
|
|
79
|
+
classes: [],
|
|
80
|
+
protocols: [],
|
|
81
|
+
categories: [],
|
|
82
|
+
methods: [],
|
|
83
|
+
properties: [],
|
|
84
|
+
patterns: [],
|
|
85
|
+
imports: [],
|
|
86
|
+
exports: [],
|
|
87
|
+
};
|
|
52
88
|
|
|
53
|
-
|
|
54
|
-
_walkObjC(root, { classes, protocols, categories, methods, properties, patterns, imports });
|
|
55
|
-
} else if (lang === 'swift') {
|
|
56
|
-
_walkSwift(root, { classes, protocols, categories, methods, properties, patterns, imports });
|
|
57
|
-
}
|
|
89
|
+
plugin.walk(root, ctx);
|
|
58
90
|
|
|
59
91
|
// 构建继承图谱
|
|
60
|
-
const inheritanceGraph = _buildInheritanceGraph(classes, protocols, categories);
|
|
92
|
+
const inheritanceGraph = _buildInheritanceGraph(ctx.classes, ctx.protocols, ctx.categories);
|
|
61
93
|
|
|
62
|
-
//
|
|
63
|
-
const detectedPatterns =
|
|
64
|
-
|
|
94
|
+
// 检测设计模式(优先使用插件自带的检测器,否则使用通用检测器)
|
|
95
|
+
const detectedPatterns = plugin.detectPatterns
|
|
96
|
+
? plugin.detectPatterns(root, lang, ctx.methods, ctx.properties, ctx.classes)
|
|
97
|
+
: _detectPatterns(root, lang, ctx.methods, ctx.properties, ctx.classes);
|
|
98
|
+
ctx.patterns.push(...detectedPatterns);
|
|
65
99
|
|
|
66
100
|
// 结构指标
|
|
67
|
-
const metrics = _computeMetrics(root, lang, methods);
|
|
101
|
+
const metrics = _computeMetrics(root, lang, ctx.methods);
|
|
68
102
|
|
|
69
103
|
return {
|
|
70
104
|
lang,
|
|
71
|
-
classes,
|
|
72
|
-
protocols,
|
|
73
|
-
categories,
|
|
74
|
-
methods,
|
|
75
|
-
properties,
|
|
76
|
-
patterns,
|
|
77
|
-
imports,
|
|
105
|
+
classes: ctx.classes,
|
|
106
|
+
protocols: ctx.protocols,
|
|
107
|
+
categories: ctx.categories,
|
|
108
|
+
methods: ctx.methods,
|
|
109
|
+
properties: ctx.properties,
|
|
110
|
+
patterns: ctx.patterns,
|
|
111
|
+
imports: ctx.imports,
|
|
112
|
+
exports: ctx.exports,
|
|
78
113
|
inheritanceGraph,
|
|
79
114
|
metrics,
|
|
80
115
|
};
|
|
@@ -84,9 +119,10 @@ function analyzeFile(source, lang) {
|
|
|
84
119
|
* 批量分析多文件,返回项目级汇总
|
|
85
120
|
* @param {{ name: string, relativePath: string, content: string }[]} files
|
|
86
121
|
* @param {string} lang
|
|
122
|
+
* @param {{ preprocessFile?: (content: string, ext: string) => { content: string, lang: string } | null }} [options]
|
|
87
123
|
* @returns {ProjectAstSummary}
|
|
88
124
|
*/
|
|
89
|
-
function analyzeProject(files, lang) {
|
|
125
|
+
function analyzeProject(files, lang, options) {
|
|
90
126
|
const fileSummaries = [];
|
|
91
127
|
const allClasses = [];
|
|
92
128
|
const allProtocols = [];
|
|
@@ -94,18 +130,34 @@ function analyzeProject(files, lang) {
|
|
|
94
130
|
const allMethods = [];
|
|
95
131
|
const allPatterns = [];
|
|
96
132
|
const allImports = [];
|
|
133
|
+
const preprocessFile = options?.preprocessFile;
|
|
97
134
|
|
|
98
135
|
for (const file of files) {
|
|
99
|
-
|
|
100
|
-
|
|
136
|
+
let { content } = file;
|
|
137
|
+
let fileLang = lang;
|
|
138
|
+
|
|
139
|
+
// SFC 预处理: .vue / .svelte 等文件 → 提取 <script> 块再交给 AST
|
|
140
|
+
if (preprocessFile) {
|
|
141
|
+
const ext = file.name ? ('.' + file.name.split('.').pop()) : '';
|
|
142
|
+
const result = preprocessFile(content, ext);
|
|
143
|
+
if (result) {
|
|
144
|
+
content = result.content;
|
|
145
|
+
fileLang = result.lang || lang;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const summary = analyzeFile(content, fileLang);
|
|
150
|
+
if (!summary) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
101
153
|
|
|
102
154
|
fileSummaries.push({ file: file.relativePath, ...summary });
|
|
103
|
-
allClasses.push(...summary.classes.map(c => ({ ...c, file: file.relativePath })));
|
|
104
|
-
allProtocols.push(...summary.protocols.map(p => ({ ...p, file: file.relativePath })));
|
|
105
|
-
allCategories.push(...summary.categories.map(c => ({ ...c, file: file.relativePath })));
|
|
106
|
-
allMethods.push(...summary.methods.map(m => ({ ...m, file: file.relativePath })));
|
|
107
|
-
allPatterns.push(...summary.patterns.map(p => ({ ...p, file: file.relativePath })));
|
|
108
|
-
allImports.push(...summary.imports.map(i => ({ path: i, file: file.relativePath })));
|
|
155
|
+
allClasses.push(...summary.classes.map((c) => ({ ...c, file: file.relativePath })));
|
|
156
|
+
allProtocols.push(...summary.protocols.map((p) => ({ ...p, file: file.relativePath })));
|
|
157
|
+
allCategories.push(...summary.categories.map((c) => ({ ...c, file: file.relativePath })));
|
|
158
|
+
allMethods.push(...summary.methods.map((m) => ({ ...m, file: file.relativePath })));
|
|
159
|
+
allPatterns.push(...summary.patterns.map((p) => ({ ...p, file: file.relativePath })));
|
|
160
|
+
allImports.push(...summary.imports.map((i) => ({ path: i, file: file.relativePath })));
|
|
109
161
|
}
|
|
110
162
|
|
|
111
163
|
// 项目级继承图(跨文件合并)
|
|
@@ -114,7 +166,9 @@ function analyzeProject(files, lang) {
|
|
|
114
166
|
// 项目级模式统计
|
|
115
167
|
const patternStats = {};
|
|
116
168
|
for (const p of allPatterns) {
|
|
117
|
-
if (!patternStats[p.type])
|
|
169
|
+
if (!patternStats[p.type]) {
|
|
170
|
+
patternStats[p.type] = { count: 0, files: [], instances: [] };
|
|
171
|
+
}
|
|
118
172
|
patternStats[p.type].count++;
|
|
119
173
|
if (!patternStats[p.type].files.includes(p.file)) {
|
|
120
174
|
patternStats[p.type].files.push(p.file);
|
|
@@ -147,7 +201,8 @@ function generateContextForAgent(projectSummary) {
|
|
|
147
201
|
const lines = ['## 项目代码结构分析(AST)', ''];
|
|
148
202
|
|
|
149
203
|
// 类型声明概览
|
|
150
|
-
const { classes, protocols, categories, inheritanceGraph, patternStats, projectMetrics } =
|
|
204
|
+
const { classes, protocols, categories, inheritanceGraph, patternStats, projectMetrics } =
|
|
205
|
+
projectSummary;
|
|
151
206
|
|
|
152
207
|
lines.push(`### 代码规模`);
|
|
153
208
|
lines.push(`- 已分析文件: ${projectSummary.fileCount}`);
|
|
@@ -169,13 +224,15 @@ function generateContextForAgent(projectSummary) {
|
|
|
169
224
|
}
|
|
170
225
|
|
|
171
226
|
// 协议遵循
|
|
172
|
-
const conformances = classes.filter(c => c.protocols && c.protocols.length > 0);
|
|
227
|
+
const conformances = classes.filter((c) => c.protocols && c.protocols.length > 0);
|
|
173
228
|
if (conformances.length > 0) {
|
|
174
229
|
lines.push(`### 协议遵循`);
|
|
175
230
|
for (const c of conformances.slice(0, 20)) {
|
|
176
|
-
lines.push(`- \`${c.name}\` → ${c.protocols.map(p =>
|
|
231
|
+
lines.push(`- \`${c.name}\` → ${c.protocols.map((p) => `\`${p}\``).join(', ')}`);
|
|
232
|
+
}
|
|
233
|
+
if (conformances.length > 20) {
|
|
234
|
+
lines.push(`- ... (共 ${conformances.length} 个)`);
|
|
177
235
|
}
|
|
178
|
-
if (conformances.length > 20) lines.push(`- ... (共 ${conformances.length} 个)`);
|
|
179
236
|
lines.push('');
|
|
180
237
|
}
|
|
181
238
|
|
|
@@ -183,10 +240,15 @@ function generateContextForAgent(projectSummary) {
|
|
|
183
240
|
if (categories.length > 0) {
|
|
184
241
|
lines.push(`### Category / Extension`);
|
|
185
242
|
for (const cat of categories.slice(0, 15)) {
|
|
186
|
-
const methodNames = (cat.methods || [])
|
|
243
|
+
const methodNames = (cat.methods || [])
|
|
244
|
+
.slice(0, 5)
|
|
245
|
+
.map((m) => m.name)
|
|
246
|
+
.join(', ');
|
|
187
247
|
lines.push(`- \`${cat.className}(${cat.categoryName})\` → ${methodNames || '(无方法)'}`);
|
|
188
248
|
}
|
|
189
|
-
if (categories.length > 15)
|
|
249
|
+
if (categories.length > 15) {
|
|
250
|
+
lines.push(`- ... (共 ${categories.length} 个)`);
|
|
251
|
+
}
|
|
190
252
|
lines.push('');
|
|
191
253
|
}
|
|
192
254
|
|
|
@@ -194,7 +256,9 @@ function generateContextForAgent(projectSummary) {
|
|
|
194
256
|
if (Object.keys(patternStats).length > 0) {
|
|
195
257
|
lines.push(`### 检测到的设计模式`);
|
|
196
258
|
for (const [type, stat] of Object.entries(patternStats)) {
|
|
197
|
-
lines.push(
|
|
259
|
+
lines.push(
|
|
260
|
+
`- **${type}**: ${stat.count} 处 (${stat.files.slice(0, 3).join(', ')}${stat.files.length > 3 ? '...' : ''})`
|
|
261
|
+
);
|
|
198
262
|
}
|
|
199
263
|
lines.push('');
|
|
200
264
|
}
|
|
@@ -204,13 +268,17 @@ function generateContextForAgent(projectSummary) {
|
|
|
204
268
|
if (projectMetrics.complexMethods.length > 0) {
|
|
205
269
|
lines.push(`- ⚠️ 高复杂度方法 (cyclomatic > 10):`);
|
|
206
270
|
for (const m of projectMetrics.complexMethods.slice(0, 5)) {
|
|
207
|
-
lines.push(
|
|
271
|
+
lines.push(
|
|
272
|
+
` - \`${m.className || ''}${m.className ? '.' : ''}${m.name}\` (复杂度: ${m.complexity}, ${m.file}:${m.line})`
|
|
273
|
+
);
|
|
208
274
|
}
|
|
209
275
|
}
|
|
210
276
|
if (projectMetrics.longMethods.length > 0) {
|
|
211
277
|
lines.push(`- ⚠️ 过长方法 (> 50 行):`);
|
|
212
278
|
for (const m of projectMetrics.longMethods.slice(0, 5)) {
|
|
213
|
-
lines.push(
|
|
279
|
+
lines.push(
|
|
280
|
+
` - \`${m.className || ''}${m.className ? '.' : ''}${m.name}\` (${m.lines} 行, ${m.file}:${m.line})`
|
|
281
|
+
);
|
|
214
282
|
}
|
|
215
283
|
}
|
|
216
284
|
lines.push('');
|
|
@@ -219,20 +287,17 @@ function generateContextForAgent(projectSummary) {
|
|
|
219
287
|
}
|
|
220
288
|
|
|
221
289
|
/**
|
|
222
|
-
* 检查 Tree-sitter
|
|
290
|
+
* 检查 Tree-sitter 是否可用(至少有一个语言插件注册)
|
|
223
291
|
*/
|
|
224
292
|
function isAvailable() {
|
|
225
|
-
return !!
|
|
293
|
+
return !!Parser && _langPlugins.size > 0;
|
|
226
294
|
}
|
|
227
295
|
|
|
228
296
|
/**
|
|
229
297
|
* 获取支持的语言列表
|
|
230
298
|
*/
|
|
231
299
|
function supportedLanguages() {
|
|
232
|
-
|
|
233
|
-
if (LangObjC) langs.push('objectivec');
|
|
234
|
-
if (LangSwift) langs.push('swift');
|
|
235
|
-
return langs;
|
|
300
|
+
return [..._langPlugins.keys()];
|
|
236
301
|
}
|
|
237
302
|
|
|
238
303
|
// ──────────────────────────────────────────────────────────────────
|
|
@@ -242,509 +307,39 @@ function supportedLanguages() {
|
|
|
242
307
|
const _parserCache = new Map();
|
|
243
308
|
|
|
244
309
|
function _getParser(lang) {
|
|
245
|
-
if (!Parser)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
let langModule;
|
|
249
|
-
if (lang === 'objectivec' && LangObjC) langModule = LangObjC;
|
|
250
|
-
else if (lang === 'swift' && LangSwift) langModule = LangSwift;
|
|
251
|
-
else return null;
|
|
252
|
-
|
|
253
|
-
const parser = new Parser();
|
|
254
|
-
parser.setLanguage(langModule);
|
|
255
|
-
_parserCache.set(lang, parser);
|
|
256
|
-
return parser;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// ──────────────────────────────────────────────────────────────────
|
|
260
|
-
// 内部实现 — ObjC AST 遍历
|
|
261
|
-
// ──────────────────────────────────────────────────────────────────
|
|
262
|
-
|
|
263
|
-
function _walkObjC(root, ctx) {
|
|
264
|
-
for (let i = 0; i < root.namedChildCount; i++) {
|
|
265
|
-
const node = root.namedChild(i);
|
|
266
|
-
|
|
267
|
-
switch (node.type) {
|
|
268
|
-
case 'preproc_include': {
|
|
269
|
-
const pathNode = node.namedChildren.find(c => c.type === 'string_literal' || c.type === 'system_lib_string');
|
|
270
|
-
if (pathNode) ctx.imports.push(pathNode.text.replace(/^["<]|[">]$/g, ''));
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
case 'class_interface': {
|
|
275
|
-
const classInfo = _parseObjCInterface(node);
|
|
276
|
-
if (classInfo.isCategory) {
|
|
277
|
-
ctx.categories.push(classInfo);
|
|
278
|
-
} else {
|
|
279
|
-
ctx.classes.push(classInfo);
|
|
280
|
-
}
|
|
281
|
-
// 提取类中的方法和属性声明
|
|
282
|
-
for (const child of node.namedChildren) {
|
|
283
|
-
if (child.type === 'method_declaration') {
|
|
284
|
-
ctx.methods.push(_parseObjCMethodDecl(child, classInfo.name));
|
|
285
|
-
} else if (child.type === 'property_declaration') {
|
|
286
|
-
ctx.properties.push(_parseObjCProperty(child, classInfo.name));
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
case 'protocol_declaration': {
|
|
293
|
-
ctx.protocols.push(_parseObjCProtocol(node));
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
case 'class_implementation': {
|
|
298
|
-
const implName = _findIdentifier(node);
|
|
299
|
-
// 遍历 implementation_definition 提取方法定义
|
|
300
|
-
for (const child of node.namedChildren) {
|
|
301
|
-
if (child.type === 'implementation_definition') {
|
|
302
|
-
for (const implChild of child.namedChildren) {
|
|
303
|
-
if (implChild.type === 'method_definition') {
|
|
304
|
-
const m = _parseObjCMethodDef(implChild, implName);
|
|
305
|
-
ctx.methods.push(m);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
case 'category_implementation': {
|
|
314
|
-
const catImplName = _findIdentifier(node);
|
|
315
|
-
for (const child of node.namedChildren) {
|
|
316
|
-
if (child.type === 'implementation_definition') {
|
|
317
|
-
for (const implChild of child.namedChildren) {
|
|
318
|
-
if (implChild.type === 'method_definition') {
|
|
319
|
-
ctx.methods.push(_parseObjCMethodDef(implChild, catImplName));
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function _parseObjCInterface(node) {
|
|
331
|
-
const identifiers = node.namedChildren.filter(c => c.type === 'identifier');
|
|
332
|
-
const name = identifiers[0]?.text || 'Unknown';
|
|
333
|
-
|
|
334
|
-
// 判断 Category: @interface ClassName (CategoryName)
|
|
335
|
-
// tree-sitter-objc 中 category 的第二个 identifier 是 categoryName
|
|
336
|
-
const isCategory = node.text.includes('(') && identifiers.length >= 2 &&
|
|
337
|
-
node.text.indexOf('(') < node.text.indexOf(identifiers[1].text);
|
|
338
|
-
|
|
339
|
-
// superclass: 第二个 identifier(非 category 时)
|
|
340
|
-
let superclass = null;
|
|
341
|
-
let categoryName = null;
|
|
342
|
-
if (isCategory) {
|
|
343
|
-
categoryName = identifiers[1]?.text;
|
|
344
|
-
} else if (identifiers.length >= 2) {
|
|
345
|
-
superclass = identifiers[1]?.text;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// protocols: parameterized_arguments 中的 type_identifier
|
|
349
|
-
const protocols = [];
|
|
350
|
-
const protoList = node.namedChildren.find(c => c.type === 'parameterized_arguments');
|
|
351
|
-
if (protoList) {
|
|
352
|
-
for (const child of protoList.namedChildren) {
|
|
353
|
-
if (child.type === 'type_name') {
|
|
354
|
-
const ti = child.namedChildren.find(c => c.type === 'type_identifier');
|
|
355
|
-
if (ti) protocols.push(ti.text);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const methods = [];
|
|
361
|
-
for (const child of node.namedChildren) {
|
|
362
|
-
if (child.type === 'method_declaration') {
|
|
363
|
-
methods.push(_parseObjCMethodDecl(child, name));
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
const result = {
|
|
368
|
-
name,
|
|
369
|
-
superclass,
|
|
370
|
-
protocols,
|
|
371
|
-
isCategory,
|
|
372
|
-
line: node.startPosition.row + 1,
|
|
373
|
-
endLine: node.endPosition.row + 1,
|
|
374
|
-
};
|
|
375
|
-
if (isCategory) {
|
|
376
|
-
result.className = name;
|
|
377
|
-
result.categoryName = categoryName;
|
|
378
|
-
result.methods = methods;
|
|
379
|
-
}
|
|
380
|
-
return result;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function _parseObjCProtocol(node) {
|
|
384
|
-
const name = _findIdentifier(node) || 'Unknown';
|
|
385
|
-
const inherits = [];
|
|
386
|
-
const protoRef = node.namedChildren.find(c => c.type === 'protocol_reference_list');
|
|
387
|
-
if (protoRef) {
|
|
388
|
-
for (const child of protoRef.namedChildren) {
|
|
389
|
-
if (child.type === 'identifier') inherits.push(child.text);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const methods = [];
|
|
394
|
-
let isOptional = false;
|
|
395
|
-
for (const child of node.namedChildren) {
|
|
396
|
-
if (child.type === 'qualified_protocol_interface_declaration') {
|
|
397
|
-
isOptional = true;
|
|
398
|
-
for (const sub of child.namedChildren) {
|
|
399
|
-
if (sub.type === 'method_declaration') {
|
|
400
|
-
const m = _parseObjCMethodDecl(sub, name);
|
|
401
|
-
m.isOptional = true;
|
|
402
|
-
methods.push(m);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
} else if (child.type === 'method_declaration') {
|
|
406
|
-
const m = _parseObjCMethodDecl(child, name);
|
|
407
|
-
m.isOptional = isOptional;
|
|
408
|
-
methods.push(m);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return {
|
|
413
|
-
name,
|
|
414
|
-
inherits,
|
|
415
|
-
methods,
|
|
416
|
-
line: node.startPosition.row + 1,
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function _parseObjCMethodDecl(node, className) {
|
|
421
|
-
const isClassMethod = node.text.trimStart().startsWith('+');
|
|
422
|
-
const name = _findIdentifier(node) || 'unknown';
|
|
423
|
-
|
|
424
|
-
// 收集参数
|
|
425
|
-
const params = [];
|
|
426
|
-
for (const child of node.namedChildren) {
|
|
427
|
-
if (child.type === 'method_parameter') {
|
|
428
|
-
const paramName = _findIdentifier(child);
|
|
429
|
-
params.push(paramName || '?');
|
|
430
|
-
}
|
|
310
|
+
if (!Parser) {
|
|
311
|
+
return null;
|
|
431
312
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
let returnType = 'void';
|
|
435
|
-
const methodType = node.namedChildren.find(c => c.type === 'method_type');
|
|
436
|
-
if (methodType) {
|
|
437
|
-
const tn = methodType.namedChildren.find(c => c.type === 'type_name');
|
|
438
|
-
if (tn) {
|
|
439
|
-
const ti = tn.namedChildren.find(c => c.type === 'type_identifier' || c.type === 'primitive_type');
|
|
440
|
-
if (ti) returnType = ti.text;
|
|
441
|
-
}
|
|
313
|
+
if (_parserCache.has(lang)) {
|
|
314
|
+
return _parserCache.get(lang);
|
|
442
315
|
}
|
|
443
316
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return {
|
|
448
|
-
name,
|
|
449
|
-
selector,
|
|
450
|
-
className,
|
|
451
|
-
isClassMethod,
|
|
452
|
-
returnType,
|
|
453
|
-
paramCount: params.length,
|
|
454
|
-
line: node.startPosition.row + 1,
|
|
455
|
-
kind: 'declaration',
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function _parseObjCMethodDef(node, className) {
|
|
460
|
-
const isClassMethod = node.text.trimStart().startsWith('+');
|
|
461
|
-
const name = _findIdentifier(node) || 'unknown';
|
|
462
|
-
|
|
463
|
-
const params = [];
|
|
464
|
-
for (const child of node.namedChildren) {
|
|
465
|
-
if (child.type === 'method_parameter') {
|
|
466
|
-
const paramName = _findIdentifier(child);
|
|
467
|
-
params.push(paramName || '?');
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// 方法体长度
|
|
472
|
-
const body = node.namedChildren.find(c => c.type === 'compound_statement');
|
|
473
|
-
const bodyLines = body ? (body.endPosition.row - body.startPosition.row + 1) : 0;
|
|
474
|
-
|
|
475
|
-
// 圈复杂度估算
|
|
476
|
-
const complexity = body ? _estimateComplexity(body) : 1;
|
|
477
|
-
|
|
478
|
-
// 嵌套深度
|
|
479
|
-
const nestingDepth = body ? _maxNesting(body, 0) : 0;
|
|
480
|
-
|
|
481
|
-
return {
|
|
482
|
-
name,
|
|
483
|
-
className,
|
|
484
|
-
isClassMethod,
|
|
485
|
-
paramCount: params.length,
|
|
486
|
-
bodyLines,
|
|
487
|
-
complexity,
|
|
488
|
-
nestingDepth,
|
|
489
|
-
line: node.startPosition.row + 1,
|
|
490
|
-
kind: 'definition',
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
function _parseObjCProperty(node, className) {
|
|
495
|
-
// 属性修饰符
|
|
496
|
-
const attrs = [];
|
|
497
|
-
const attrDecl = node.namedChildren.find(c => c.type === 'property_attributes_declaration');
|
|
498
|
-
if (attrDecl) {
|
|
499
|
-
for (const attr of attrDecl.namedChildren) {
|
|
500
|
-
if (attr.type === 'property_attribute') {
|
|
501
|
-
const id = attr.namedChildren.find(c => c.type === 'identifier');
|
|
502
|
-
if (id) attrs.push(id.text);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
317
|
+
const plugin = _langPlugins.get(lang);
|
|
318
|
+
if (!plugin) {
|
|
319
|
+
return null;
|
|
505
320
|
}
|
|
506
321
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (structDecl) {
|
|
512
|
-
// 类型
|
|
513
|
-
const ti = structDecl.namedChildren.find(c => c.type === 'type_identifier');
|
|
514
|
-
if (ti) propType = ti.text;
|
|
515
|
-
|
|
516
|
-
// 名字 — 在 struct_declarator 深处
|
|
517
|
-
const sd = structDecl.namedChildren.find(c => c.type === 'struct_declarator');
|
|
518
|
-
if (sd) {
|
|
519
|
-
const findName = (n) => {
|
|
520
|
-
if (n.type === 'identifier') return n.text;
|
|
521
|
-
for (let j = 0; j < n.namedChildCount; j++) {
|
|
522
|
-
const r = findName(n.namedChild(j));
|
|
523
|
-
if (r) return r;
|
|
524
|
-
}
|
|
525
|
-
return null;
|
|
526
|
-
};
|
|
527
|
-
propName = findName(sd) || propName;
|
|
322
|
+
try {
|
|
323
|
+
const grammar = plugin.getGrammar();
|
|
324
|
+
if (!grammar) {
|
|
325
|
+
return null;
|
|
528
326
|
}
|
|
327
|
+
const parser = new Parser();
|
|
328
|
+
parser.setLanguage(grammar);
|
|
329
|
+
_parserCache.set(lang, parser);
|
|
330
|
+
return parser;
|
|
331
|
+
} catch {
|
|
332
|
+
return null;
|
|
529
333
|
}
|
|
530
|
-
|
|
531
|
-
return {
|
|
532
|
-
name: propName,
|
|
533
|
-
type: propType,
|
|
534
|
-
attributes: attrs,
|
|
535
|
-
className,
|
|
536
|
-
line: node.startPosition.row + 1,
|
|
537
|
-
};
|
|
538
334
|
}
|
|
539
335
|
|
|
540
336
|
// ──────────────────────────────────────────────────────────────────
|
|
541
|
-
// 内部实现 — Swift
|
|
337
|
+
// 内部实现 — ObjC/Swift Walker 已迁移到 ast/lang-objc.js 和 ast/lang-swift.js
|
|
338
|
+
// 通过 ast/index.js 自动注册到 _langPlugins
|
|
542
339
|
// ──────────────────────────────────────────────────────────────────
|
|
543
340
|
|
|
544
|
-
function _walkSwift(root, ctx) {
|
|
545
|
-
_walkSwiftNode(root, ctx, null);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function _walkSwiftNode(node, ctx, parentClassName) {
|
|
549
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
550
|
-
const child = node.namedChild(i);
|
|
551
|
-
|
|
552
|
-
switch (child.type) {
|
|
553
|
-
case 'import_declaration': {
|
|
554
|
-
const mod = child.namedChildren.find(c => c.type === 'identifier' || c.type === 'simple_identifier');
|
|
555
|
-
if (mod) ctx.imports.push(mod.text);
|
|
556
|
-
break;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
case 'class_declaration':
|
|
560
|
-
case 'struct_declaration':
|
|
561
|
-
case 'enum_declaration': {
|
|
562
|
-
const classInfo = _parseSwiftTypeDecl(child);
|
|
563
|
-
ctx.classes.push(classInfo);
|
|
564
|
-
|
|
565
|
-
// 递归遍历类体
|
|
566
|
-
const body = child.namedChildren.find(c => c.type === 'class_body' || c.type === 'struct_body' || c.type === 'enum_body');
|
|
567
|
-
if (body) _walkSwiftNode(body, ctx, classInfo.name);
|
|
568
|
-
break;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
case 'protocol_declaration': {
|
|
572
|
-
const protoInfo = _parseSwiftProtocol(child);
|
|
573
|
-
ctx.protocols.push(protoInfo);
|
|
574
|
-
break;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
case 'extension_declaration': {
|
|
578
|
-
const extInfo = _parseSwiftExtension(child);
|
|
579
|
-
ctx.categories.push(extInfo);
|
|
580
|
-
|
|
581
|
-
const body = child.namedChildren.find(c => c.type === 'extension_body');
|
|
582
|
-
if (body) _walkSwiftNode(body, ctx, extInfo.className);
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
case 'function_declaration': {
|
|
587
|
-
const m = _parseSwiftFunction(child, parentClassName);
|
|
588
|
-
ctx.methods.push(m);
|
|
589
|
-
break;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
case 'property_declaration': {
|
|
593
|
-
const p = _parseSwiftProperty(child, parentClassName);
|
|
594
|
-
if (p) ctx.properties.push(p);
|
|
595
|
-
break;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
default: {
|
|
599
|
-
// 递归进入未识别节点
|
|
600
|
-
if (child.namedChildCount > 0 && !['function_body', 'computed_property', 'willSet_didSet_block'].includes(child.type)) {
|
|
601
|
-
_walkSwiftNode(child, ctx, parentClassName);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function _parseSwiftTypeDecl(node) {
|
|
609
|
-
const name = node.namedChildren.find(c => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text || 'Unknown';
|
|
610
|
-
const kind = node.type.replace('_declaration', ''); // class | struct | enum
|
|
611
|
-
|
|
612
|
-
// 继承/遵循
|
|
613
|
-
const superclass = null;
|
|
614
|
-
const protocols = [];
|
|
615
|
-
for (const child of node.namedChildren) {
|
|
616
|
-
if (child.type === 'inheritance_specifier') {
|
|
617
|
-
const typeNode = child.namedChildren.find(c => c.type === 'user_type');
|
|
618
|
-
if (typeNode) {
|
|
619
|
-
const typeName = typeNode.namedChildren.find(c => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text;
|
|
620
|
-
if (typeName) protocols.push(typeName);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Swift 无法区分 superclass 和 protocol(都是 inheritance_specifier),
|
|
626
|
-
// 约定:第一个继承者如果首字母大写且不含 Protocol/Delegate 后缀可能是 superclass
|
|
627
|
-
let detectedSuper = null;
|
|
628
|
-
if (protocols.length > 0 && kind === 'class') {
|
|
629
|
-
const first = protocols[0];
|
|
630
|
-
if (!first.endsWith('Protocol') && !first.endsWith('Delegate') && !first.endsWith('DataSource')) {
|
|
631
|
-
detectedSuper = first;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return {
|
|
636
|
-
name,
|
|
637
|
-
kind,
|
|
638
|
-
superclass: detectedSuper,
|
|
639
|
-
protocols,
|
|
640
|
-
line: node.startPosition.row + 1,
|
|
641
|
-
endLine: node.endPosition.row + 1,
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
function _parseSwiftProtocol(node) {
|
|
646
|
-
const name = node.namedChildren.find(c => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text || 'Unknown';
|
|
647
|
-
const inherits = [];
|
|
648
|
-
for (const child of node.namedChildren) {
|
|
649
|
-
if (child.type === 'inheritance_specifier') {
|
|
650
|
-
const t = child.namedChildren.find(c => c.type === 'user_type');
|
|
651
|
-
if (t) {
|
|
652
|
-
const n = t.namedChildren.find(c => c.type === 'type_identifier' || c.type === 'simple_identifier');
|
|
653
|
-
if (n) inherits.push(n.text);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
return { name, inherits, line: node.startPosition.row + 1 };
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function _parseSwiftExtension(node) {
|
|
661
|
-
const className = node.namedChildren.find(c => c.type === 'user_type' || c.type === 'type_identifier')?.text || 'Unknown';
|
|
662
|
-
const protocols = [];
|
|
663
|
-
for (const child of node.namedChildren) {
|
|
664
|
-
if (child.type === 'inheritance_specifier') {
|
|
665
|
-
const t = child.namedChildren.find(c => c.type === 'user_type');
|
|
666
|
-
if (t) {
|
|
667
|
-
const n = t.namedChildren.find(c => c.type === 'type_identifier' || c.type === 'simple_identifier');
|
|
668
|
-
if (n) protocols.push(n.text);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const methods = [];
|
|
674
|
-
const body = node.namedChildren.find(c => c.type === 'extension_body');
|
|
675
|
-
if (body) {
|
|
676
|
-
for (const child of body.namedChildren) {
|
|
677
|
-
if (child.type === 'function_declaration') {
|
|
678
|
-
methods.push(_parseSwiftFunction(child, className));
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
return {
|
|
684
|
-
className,
|
|
685
|
-
categoryName: protocols.length > 0 ? protocols.join('+') : 'ext',
|
|
686
|
-
protocols,
|
|
687
|
-
methods,
|
|
688
|
-
line: node.startPosition.row + 1,
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
function _parseSwiftFunction(node, className) {
|
|
693
|
-
const name = node.namedChildren.find(c => c.type === 'simple_identifier')?.text || 'unknown';
|
|
694
|
-
|
|
695
|
-
// static/class
|
|
696
|
-
const modifiers = [];
|
|
697
|
-
for (const child of node.namedChildren) {
|
|
698
|
-
if (child.type === 'modifiers' || child.type === 'modifier') {
|
|
699
|
-
modifiers.push(child.text);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
const isClassMethod = modifiers.some(m => /\b(static|class)\b/.test(m));
|
|
703
|
-
|
|
704
|
-
// 方法体
|
|
705
|
-
const body = node.namedChildren.find(c => c.type === 'function_body');
|
|
706
|
-
const bodyLines = body ? (body.endPosition.row - body.startPosition.row + 1) : 0;
|
|
707
|
-
const complexity = body ? _estimateComplexity(body) : 1;
|
|
708
|
-
const nestingDepth = body ? _maxNesting(body, 0) : 0;
|
|
709
|
-
|
|
710
|
-
return {
|
|
711
|
-
name,
|
|
712
|
-
className,
|
|
713
|
-
isClassMethod,
|
|
714
|
-
bodyLines,
|
|
715
|
-
complexity,
|
|
716
|
-
nestingDepth,
|
|
717
|
-
line: node.startPosition.row + 1,
|
|
718
|
-
kind: 'definition',
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function _parseSwiftProperty(node, className) {
|
|
723
|
-
const name = node.namedChildren.find(c => c.type === 'simple_identifier' || c.type === 'pattern')?.text || null;
|
|
724
|
-
if (!name) return null;
|
|
725
|
-
|
|
726
|
-
const modifiers = [];
|
|
727
|
-
for (const child of node.namedChildren) {
|
|
728
|
-
if (child.type === 'modifiers' || child.type === 'modifier') {
|
|
729
|
-
modifiers.push(child.text);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const isStatic = modifiers.some(m => /\b(static|class)\b/.test(m));
|
|
734
|
-
const isLet = node.text.includes(' let ');
|
|
735
|
-
|
|
736
|
-
return {
|
|
737
|
-
name,
|
|
738
|
-
className,
|
|
739
|
-
isStatic,
|
|
740
|
-
isConstant: isLet,
|
|
741
|
-
attributes: modifiers,
|
|
742
|
-
line: node.startPosition.row + 1,
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
|
|
746
341
|
// ──────────────────────────────────────────────────────────────────
|
|
747
|
-
// 内部实现 —
|
|
342
|
+
// 内部实现 — 设计模式检测(通用回退,插件可提供自己的 detectPatterns)
|
|
748
343
|
// ──────────────────────────────────────────────────────────────────
|
|
749
344
|
|
|
750
345
|
function _detectPatterns(root, lang, methods, properties, classes) {
|
|
@@ -793,8 +388,7 @@ function _detectPatterns(root, lang, methods, properties, classes) {
|
|
|
793
388
|
|
|
794
389
|
// Observer/Notification 检测(通过方法名)
|
|
795
390
|
for (const m of methods) {
|
|
796
|
-
if (/^observe|^addObserver|^subscribe/.test(m.name) ||
|
|
797
|
-
/^didChange|^willChange/.test(m.name)) {
|
|
391
|
+
if (/^observe|^addObserver|^subscribe/.test(m.name) || /^didChange|^willChange/.test(m.name)) {
|
|
798
392
|
patterns.push({
|
|
799
393
|
type: 'observer',
|
|
800
394
|
className: m.className,
|
|
@@ -835,7 +429,11 @@ function _buildInheritanceGraph(classes, protocols, categories) {
|
|
|
835
429
|
}
|
|
836
430
|
|
|
837
431
|
for (const cat of categories) {
|
|
838
|
-
edges.push({
|
|
432
|
+
edges.push({
|
|
433
|
+
from: `${cat.className}(${cat.categoryName})`,
|
|
434
|
+
to: cat.className,
|
|
435
|
+
type: 'extends',
|
|
436
|
+
});
|
|
839
437
|
if (cat.protocols) {
|
|
840
438
|
for (const proto of cat.protocols) {
|
|
841
439
|
edges.push({ from: cat.className, to: proto, type: 'conforms' });
|
|
@@ -848,24 +446,28 @@ function _buildInheritanceGraph(classes, protocols, categories) {
|
|
|
848
446
|
|
|
849
447
|
function _renderInheritanceTree(edges) {
|
|
850
448
|
// 找出根节点(只被继承不继承其他的)
|
|
851
|
-
const allTargets = new Set(edges.map(e => e.to));
|
|
852
|
-
const allSources = new Set(edges.map(e => e.from));
|
|
853
|
-
const roots = [...allTargets].filter(t => !allSources.has(t)).slice(0, 5);
|
|
449
|
+
const allTargets = new Set(edges.map((e) => e.to));
|
|
450
|
+
const allSources = new Set(edges.map((e) => e.from));
|
|
451
|
+
const roots = [...allTargets].filter((t) => !allSources.has(t)).slice(0, 5);
|
|
854
452
|
|
|
855
453
|
const childMap = {};
|
|
856
454
|
for (const e of edges) {
|
|
857
|
-
if (!childMap[e.to])
|
|
455
|
+
if (!childMap[e.to]) {
|
|
456
|
+
childMap[e.to] = [];
|
|
457
|
+
}
|
|
858
458
|
const label = e.type === 'conforms' ? `${e.from} ◇` : e.from;
|
|
859
|
-
if (!childMap[e.to].includes(label))
|
|
459
|
+
if (!childMap[e.to].includes(label)) {
|
|
460
|
+
childMap[e.to].push(label);
|
|
461
|
+
}
|
|
860
462
|
}
|
|
861
463
|
|
|
862
464
|
const lines = [];
|
|
863
465
|
function render(name, prefix, isLast) {
|
|
864
|
-
const connector = prefix.length === 0 ? '' :
|
|
466
|
+
const connector = prefix.length === 0 ? '' : isLast ? '└─ ' : '├─ ';
|
|
865
467
|
lines.push(prefix + connector + name);
|
|
866
468
|
const children = childMap[name] || [];
|
|
867
469
|
for (let i = 0; i < children.length && i < 10; i++) {
|
|
868
|
-
const childPrefix = prefix + (prefix.length === 0 ? '' :
|
|
470
|
+
const childPrefix = prefix + (prefix.length === 0 ? '' : isLast ? ' ' : '│ ');
|
|
869
471
|
render(children[i], childPrefix, i === children.length - 1);
|
|
870
472
|
}
|
|
871
473
|
}
|
|
@@ -884,19 +486,32 @@ function _renderInheritanceTree(edges) {
|
|
|
884
486
|
function _estimateComplexity(node) {
|
|
885
487
|
let complexity = 1;
|
|
886
488
|
const BRANCH_TYPES = new Set([
|
|
887
|
-
'if_statement',
|
|
888
|
-
'
|
|
889
|
-
'
|
|
489
|
+
'if_statement',
|
|
490
|
+
'for_statement',
|
|
491
|
+
'for_in_statement',
|
|
492
|
+
'while_statement',
|
|
493
|
+
'switch_statement',
|
|
494
|
+
'case_statement',
|
|
495
|
+
'catch_clause',
|
|
496
|
+
'conditional_expression',
|
|
497
|
+
'ternary_expression',
|
|
498
|
+
'guard_statement',
|
|
890
499
|
// ObjC specific
|
|
891
500
|
'for_in_expression',
|
|
892
501
|
]);
|
|
893
502
|
|
|
894
503
|
function walk(n) {
|
|
895
|
-
if (BRANCH_TYPES.has(n.type))
|
|
504
|
+
if (BRANCH_TYPES.has(n.type)) {
|
|
505
|
+
complexity++;
|
|
506
|
+
}
|
|
896
507
|
// && / || 也增加复杂度
|
|
897
508
|
if (n.type === 'binary_expression') {
|
|
898
|
-
const op = n.children?.find(
|
|
899
|
-
|
|
509
|
+
const op = n.children?.find(
|
|
510
|
+
(c) => c.type === '&&' || c.type === '||' || c.text === '&&' || c.text === '||'
|
|
511
|
+
);
|
|
512
|
+
if (op) {
|
|
513
|
+
complexity++;
|
|
514
|
+
}
|
|
900
515
|
}
|
|
901
516
|
for (let i = 0; i < n.namedChildCount; i++) {
|
|
902
517
|
walk(n.namedChild(i));
|
|
@@ -909,8 +524,12 @@ function _estimateComplexity(node) {
|
|
|
909
524
|
|
|
910
525
|
function _maxNesting(node, depth) {
|
|
911
526
|
const NESTING_TYPES = new Set([
|
|
912
|
-
'if_statement',
|
|
913
|
-
'
|
|
527
|
+
'if_statement',
|
|
528
|
+
'for_statement',
|
|
529
|
+
'for_in_statement',
|
|
530
|
+
'while_statement',
|
|
531
|
+
'switch_statement',
|
|
532
|
+
'compound_statement',
|
|
914
533
|
]);
|
|
915
534
|
|
|
916
535
|
let max = depth;
|
|
@@ -918,34 +537,38 @@ function _maxNesting(node, depth) {
|
|
|
918
537
|
|
|
919
538
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
920
539
|
const childMax = _maxNesting(node.namedChild(i), nextDepth);
|
|
921
|
-
if (childMax > max)
|
|
540
|
+
if (childMax > max) {
|
|
541
|
+
max = childMax;
|
|
542
|
+
}
|
|
922
543
|
}
|
|
923
544
|
|
|
924
545
|
return max;
|
|
925
546
|
}
|
|
926
547
|
|
|
927
548
|
function _computeMetrics(root, lang, methods) {
|
|
928
|
-
const defs = methods.filter(m => m.kind === 'definition');
|
|
549
|
+
const defs = methods.filter((m) => m.kind === 'definition');
|
|
929
550
|
const totalBodyLines = defs.reduce((sum, m) => sum + (m.bodyLines || 0), 0);
|
|
930
551
|
|
|
931
552
|
return {
|
|
932
553
|
methodCount: defs.length,
|
|
933
554
|
avgBodyLines: defs.length > 0 ? totalBodyLines / defs.length : 0,
|
|
934
|
-
maxComplexity: defs.length > 0 ? Math.max(...defs.map(m => m.complexity || 1)) : 0,
|
|
935
|
-
maxNestingDepth: defs.length > 0 ? Math.max(...defs.map(m => m.nestingDepth || 0)) : 0,
|
|
936
|
-
longMethods: defs.filter(m => (m.bodyLines || 0) > 50),
|
|
937
|
-
complexMethods: defs.filter(m => (m.complexity || 1) > 10),
|
|
555
|
+
maxComplexity: defs.length > 0 ? Math.max(...defs.map((m) => m.complexity || 1)) : 0,
|
|
556
|
+
maxNestingDepth: defs.length > 0 ? Math.max(...defs.map((m) => m.nestingDepth || 0)) : 0,
|
|
557
|
+
longMethods: defs.filter((m) => (m.bodyLines || 0) > 50),
|
|
558
|
+
complexMethods: defs.filter((m) => (m.complexity || 1) > 10),
|
|
938
559
|
};
|
|
939
560
|
}
|
|
940
561
|
|
|
941
562
|
function _aggregateMetrics(fileSummaries) {
|
|
942
|
-
const allMethods = fileSummaries.flatMap(f => f.methods.filter(m => m.kind === 'definition'));
|
|
943
|
-
const allClasses = fileSummaries.flatMap(f => f.classes);
|
|
563
|
+
const allMethods = fileSummaries.flatMap((f) => f.methods.filter((m) => m.kind === 'definition'));
|
|
564
|
+
const allClasses = fileSummaries.flatMap((f) => f.classes);
|
|
944
565
|
|
|
945
566
|
const methodsByClass = {};
|
|
946
567
|
for (const m of allMethods) {
|
|
947
568
|
if (m.className) {
|
|
948
|
-
if (!methodsByClass[m.className])
|
|
569
|
+
if (!methodsByClass[m.className]) {
|
|
570
|
+
methodsByClass[m.className] = 0;
|
|
571
|
+
}
|
|
949
572
|
methodsByClass[m.className]++;
|
|
950
573
|
}
|
|
951
574
|
}
|
|
@@ -954,16 +577,28 @@ function _aggregateMetrics(fileSummaries) {
|
|
|
954
577
|
return {
|
|
955
578
|
totalMethods: allMethods.length,
|
|
956
579
|
totalClasses: allClasses.length,
|
|
957
|
-
avgMethodsPerClass:
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
580
|
+
avgMethodsPerClass:
|
|
581
|
+
classCounts.length > 0 ? classCounts.reduce((a, b) => a + b, 0) / classCounts.length : 0,
|
|
582
|
+
maxNestingDepth:
|
|
583
|
+
allMethods.length > 0 ? Math.max(...allMethods.map((m) => m.nestingDepth || 0)) : 0,
|
|
584
|
+
longMethods: allMethods
|
|
585
|
+
.filter((m) => (m.bodyLines || 0) > 50)
|
|
586
|
+
.map((m) => ({
|
|
587
|
+
name: m.name,
|
|
588
|
+
className: m.className,
|
|
589
|
+
lines: m.bodyLines,
|
|
590
|
+
file: m.file,
|
|
591
|
+
line: m.line,
|
|
592
|
+
})),
|
|
593
|
+
complexMethods: allMethods
|
|
594
|
+
.filter((m) => (m.complexity || 1) > 10)
|
|
595
|
+
.map((m) => ({
|
|
596
|
+
name: m.name,
|
|
597
|
+
className: m.className,
|
|
598
|
+
complexity: m.complexity,
|
|
599
|
+
file: m.file,
|
|
600
|
+
line: m.line,
|
|
601
|
+
})),
|
|
967
602
|
};
|
|
968
603
|
}
|
|
969
604
|
|
|
@@ -974,7 +609,11 @@ function _aggregateMetrics(fileSummaries) {
|
|
|
974
609
|
function _findIdentifier(node) {
|
|
975
610
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
976
611
|
const child = node.namedChild(i);
|
|
977
|
-
if (
|
|
612
|
+
if (
|
|
613
|
+
child.type === 'identifier' ||
|
|
614
|
+
child.type === 'simple_identifier' ||
|
|
615
|
+
child.type === 'type_identifier'
|
|
616
|
+
) {
|
|
978
617
|
return child.text;
|
|
979
618
|
}
|
|
980
619
|
}
|
|
@@ -994,7 +633,9 @@ function _findIdentifier(node) {
|
|
|
994
633
|
*/
|
|
995
634
|
function findCallExpressions(source, lang, targetCallee) {
|
|
996
635
|
const parser = _getParser(lang);
|
|
997
|
-
if (!parser)
|
|
636
|
+
if (!parser) {
|
|
637
|
+
return [];
|
|
638
|
+
}
|
|
998
639
|
|
|
999
640
|
const tree = parser.parse(source);
|
|
1000
641
|
const results = [];
|
|
@@ -1003,12 +644,23 @@ function findCallExpressions(source, lang, targetCallee) {
|
|
|
1003
644
|
function walk(node, enclosingClass) {
|
|
1004
645
|
// 更新当前所处的类
|
|
1005
646
|
let currentClass = enclosingClass;
|
|
1006
|
-
if (
|
|
647
|
+
if (
|
|
648
|
+
[
|
|
649
|
+
'class_declaration',
|
|
650
|
+
'struct_declaration',
|
|
651
|
+
'class_interface',
|
|
652
|
+
'class_implementation',
|
|
653
|
+
].includes(node.type)
|
|
654
|
+
) {
|
|
1007
655
|
currentClass = _findIdentifier(node) || enclosingClass;
|
|
1008
656
|
}
|
|
1009
657
|
|
|
1010
658
|
// 检查调用表达式
|
|
1011
|
-
const isCallLike = [
|
|
659
|
+
const isCallLike = [
|
|
660
|
+
'call_expression',
|
|
661
|
+
'message_expression',
|
|
662
|
+
'function_call_expression',
|
|
663
|
+
].includes(node.type);
|
|
1012
664
|
if (isCallLike) {
|
|
1013
665
|
const nodeText = node.text || '';
|
|
1014
666
|
if (nodeText.includes(targetCallee)) {
|
|
@@ -1059,7 +711,9 @@ function findCallExpressions(source, lang, targetCallee) {
|
|
|
1059
711
|
*/
|
|
1060
712
|
function findPatternInContext(source, lang, pattern, contextFilter = {}) {
|
|
1061
713
|
const parser = _getParser(lang);
|
|
1062
|
-
if (!parser)
|
|
714
|
+
if (!parser) {
|
|
715
|
+
return [];
|
|
716
|
+
}
|
|
1063
717
|
|
|
1064
718
|
const tree = parser.parse(source);
|
|
1065
719
|
const results = [];
|
|
@@ -1068,7 +722,14 @@ function findPatternInContext(source, lang, pattern, contextFilter = {}) {
|
|
|
1068
722
|
function getEnclosingMethodName(node) {
|
|
1069
723
|
let current = node.parent;
|
|
1070
724
|
while (current) {
|
|
1071
|
-
if (
|
|
725
|
+
if (
|
|
726
|
+
[
|
|
727
|
+
'method_definition',
|
|
728
|
+
'method_declaration',
|
|
729
|
+
'function_declaration',
|
|
730
|
+
'function_definition',
|
|
731
|
+
].includes(current.type)
|
|
732
|
+
) {
|
|
1072
733
|
return _findIdentifier(current) || null;
|
|
1073
734
|
}
|
|
1074
735
|
current = current.parent;
|
|
@@ -1079,7 +740,14 @@ function findPatternInContext(source, lang, pattern, contextFilter = {}) {
|
|
|
1079
740
|
function getEnclosingClassName(node) {
|
|
1080
741
|
let current = node.parent;
|
|
1081
742
|
while (current) {
|
|
1082
|
-
if (
|
|
743
|
+
if (
|
|
744
|
+
[
|
|
745
|
+
'class_declaration',
|
|
746
|
+
'struct_declaration',
|
|
747
|
+
'class_interface',
|
|
748
|
+
'class_implementation',
|
|
749
|
+
].includes(current.type)
|
|
750
|
+
) {
|
|
1083
751
|
return _findIdentifier(current) || null;
|
|
1084
752
|
}
|
|
1085
753
|
current = current.parent;
|
|
@@ -1096,7 +764,10 @@ function findPatternInContext(source, lang, pattern, contextFilter = {}) {
|
|
|
1096
764
|
|
|
1097
765
|
if (contextFilter.forbiddenContext) {
|
|
1098
766
|
// 在禁止上下文中出现 → 报告
|
|
1099
|
-
if (
|
|
767
|
+
if (
|
|
768
|
+
methodName === contextFilter.forbiddenContext ||
|
|
769
|
+
className === contextFilter.forbiddenContext
|
|
770
|
+
) {
|
|
1100
771
|
results.push({
|
|
1101
772
|
line: node.startPosition.row + 1,
|
|
1102
773
|
snippet: lines[node.startPosition.row]?.trim().slice(0, 120) || '',
|
|
@@ -1105,7 +776,10 @@ function findPatternInContext(source, lang, pattern, contextFilter = {}) {
|
|
|
1105
776
|
}
|
|
1106
777
|
} else if (contextFilter.requiredContext) {
|
|
1107
778
|
// 不在要求的上下文中 → 报告
|
|
1108
|
-
if (
|
|
779
|
+
if (
|
|
780
|
+
className !== contextFilter.requiredContext &&
|
|
781
|
+
methodName !== contextFilter.requiredContext
|
|
782
|
+
) {
|
|
1109
783
|
results.push({
|
|
1110
784
|
line: node.startPosition.row + 1,
|
|
1111
785
|
snippet: lines[node.startPosition.row]?.trim().slice(0, 120) || '',
|
|
@@ -1134,11 +808,15 @@ function findPatternInContext(source, lang, pattern, contextFilter = {}) {
|
|
|
1134
808
|
*/
|
|
1135
809
|
function checkProtocolConformance(source, lang, className, protocolName) {
|
|
1136
810
|
const summary = analyzeFile(source, lang);
|
|
1137
|
-
if (!summary)
|
|
811
|
+
if (!summary) {
|
|
812
|
+
return { conforms: false, classFound: false, classDeclLine: null };
|
|
813
|
+
}
|
|
1138
814
|
|
|
1139
815
|
// 在 classes 中查找
|
|
1140
|
-
const cls = summary.classes.find(c => c.name === className);
|
|
1141
|
-
if (!cls)
|
|
816
|
+
const cls = summary.classes.find((c) => c.name === className);
|
|
817
|
+
if (!cls) {
|
|
818
|
+
return { conforms: false, classFound: false, classDeclLine: null };
|
|
819
|
+
}
|
|
1142
820
|
|
|
1143
821
|
// 直接遵循
|
|
1144
822
|
if (cls.protocols?.includes(protocolName)) {
|
|
@@ -1147,7 +825,7 @@ function checkProtocolConformance(source, lang, className, protocolName) {
|
|
|
1147
825
|
|
|
1148
826
|
// 通过 extension/category 遵循
|
|
1149
827
|
const catConforms = summary.categories.some(
|
|
1150
|
-
cat => cat.className === className && cat.protocols?.includes(protocolName)
|
|
828
|
+
(cat) => cat.className === className && cat.protocols?.includes(protocolName)
|
|
1151
829
|
);
|
|
1152
830
|
if (catConforms) {
|
|
1153
831
|
return { conforms: true, classFound: true, classDeclLine: cls.line };
|
|
@@ -1166,6 +844,7 @@ export {
|
|
|
1166
844
|
generateContextForAgent,
|
|
1167
845
|
isAvailable,
|
|
1168
846
|
supportedLanguages,
|
|
847
|
+
// registerLanguage 已在定义处 inline export,此处不再重复
|
|
1169
848
|
// Guard AST 查询 API
|
|
1170
849
|
findCallExpressions,
|
|
1171
850
|
findPatternInContext,
|