autosnippet 3.0.1 → 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 +655 -260
- 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
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* §9 完整插入流程 — cut 触发行 → preflight → headers → offset → paste
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
import { basename, dirname,
|
|
16
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { basename, dirname, resolve as pathResolve, relative, sep } from 'node:path';
|
|
18
18
|
import { saveEventFilter } from './SaveEventFilter.js';
|
|
19
19
|
|
|
20
20
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -22,16 +22,20 @@ import { saveEventFilter } from './SaveEventFilter.js';
|
|
|
22
22
|
// ═══════════════════════════════════════════════════════════════
|
|
23
23
|
|
|
24
24
|
function _sleep(ms) {
|
|
25
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
25
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* 在 import 行末尾附加来源标记注释
|
|
30
30
|
*/
|
|
31
31
|
function _withAutoSnippetNote(importLine) {
|
|
32
|
-
if (!importLine)
|
|
32
|
+
if (!importLine) {
|
|
33
|
+
return importLine;
|
|
34
|
+
}
|
|
33
35
|
const note = '// AutoSnippet: 自动插入';
|
|
34
|
-
if (importLine.includes(note))
|
|
36
|
+
if (importLine.includes(note)) {
|
|
37
|
+
return importLine;
|
|
38
|
+
}
|
|
35
39
|
return `${importLine} ${note}`;
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -49,19 +53,30 @@ function _parseHeaderString(header) {
|
|
|
49
53
|
const t = header.trim();
|
|
50
54
|
// #import <Module/Header.h>
|
|
51
55
|
let m = t.match(/^#(?:import|include)\s+<([^/> ]+)\/([^>]+)>/);
|
|
52
|
-
if (m)
|
|
56
|
+
if (m) {
|
|
57
|
+
return { moduleName: m[1], headerName: m[2], isAngle: true };
|
|
58
|
+
}
|
|
53
59
|
// #import <Module> (framework umbrella)
|
|
54
60
|
m = t.match(/^#(?:import|include)\s+<([^>]+)>/);
|
|
55
|
-
if (m)
|
|
61
|
+
if (m) {
|
|
62
|
+
return { moduleName: m[1], headerName: '', isAngle: true };
|
|
63
|
+
}
|
|
56
64
|
// #import "Header.h" or #import "Dir/Header.h"
|
|
57
65
|
m = t.match(/^#(?:import|include)\s+"([^"]+)"/);
|
|
58
66
|
if (m) {
|
|
59
67
|
const parts = m[1].split('/');
|
|
60
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
moduleName: '',
|
|
70
|
+
headerName: parts[parts.length - 1],
|
|
71
|
+
isAngle: false,
|
|
72
|
+
quotedPath: m[1],
|
|
73
|
+
};
|
|
61
74
|
}
|
|
62
75
|
// @import Module;
|
|
63
76
|
m = t.match(/^@import\s+(\w+)/);
|
|
64
|
-
if (m)
|
|
77
|
+
if (m) {
|
|
78
|
+
return { moduleName: m[1], headerName: '', isAngle: false, isAtImport: true };
|
|
79
|
+
}
|
|
65
80
|
// import Module (Swift)
|
|
66
81
|
m = t.match(/^import\s+(\w+)/);
|
|
67
82
|
if (m && !['class', 'struct', 'enum', 'protocol', 'func', 'var', 'let'].includes(m[1])) {
|
|
@@ -88,19 +103,25 @@ function _parseHeaderString(header) {
|
|
|
88
103
|
* @returns {string|null} 相对路径 (如 "Foo.h" 或 "../SubDir/Foo.h"),null 表示未找到
|
|
89
104
|
*/
|
|
90
105
|
function _findHeaderRelativePath(headerName, currentFilePath, projectRoot) {
|
|
91
|
-
if (!headerName || !currentFilePath)
|
|
106
|
+
if (!headerName || !currentFilePath) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
92
109
|
try {
|
|
93
110
|
const currentDir = dirname(currentFilePath);
|
|
94
111
|
|
|
95
112
|
// 1. 同目录检查
|
|
96
113
|
const sameDir = pathResolve(currentDir, headerName);
|
|
97
|
-
if (existsSync(sameDir))
|
|
114
|
+
if (existsSync(sameDir)) {
|
|
115
|
+
return headerName;
|
|
116
|
+
}
|
|
98
117
|
|
|
99
118
|
// 2. 向上找 Sources/ 或 target 根目录,在其下递归搜索
|
|
100
119
|
const searchRoots = [];
|
|
101
120
|
if (projectRoot) {
|
|
102
121
|
const sourcesDir = pathResolve(projectRoot, 'Sources');
|
|
103
|
-
if (existsSync(sourcesDir))
|
|
122
|
+
if (existsSync(sourcesDir)) {
|
|
123
|
+
searchRoots.push(sourcesDir);
|
|
124
|
+
}
|
|
104
125
|
searchRoots.push(projectRoot);
|
|
105
126
|
}
|
|
106
127
|
// 也从当前文件向上找 Sources 目录
|
|
@@ -112,7 +133,9 @@ function _findHeaderRelativePath(headerName, currentFilePath, projectRoot) {
|
|
|
112
133
|
break;
|
|
113
134
|
}
|
|
114
135
|
const parent = dirname(dir);
|
|
115
|
-
if (parent === dir)
|
|
136
|
+
if (parent === dir) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
116
139
|
dir = parent;
|
|
117
140
|
}
|
|
118
141
|
|
|
@@ -137,25 +160,37 @@ function _findHeaderRelativePath(headerName, currentFilePath, projectRoot) {
|
|
|
137
160
|
* 递归查找文件(限最大深度)
|
|
138
161
|
*/
|
|
139
162
|
function _findFileRecursive(dir, fileName, maxDepth) {
|
|
140
|
-
if (maxDepth <= 0)
|
|
163
|
+
if (maxDepth <= 0) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
141
166
|
try {
|
|
142
167
|
const entries = readdirSync(dir);
|
|
143
168
|
// 先在当前层查找
|
|
144
169
|
for (const e of entries) {
|
|
145
|
-
if (e === fileName)
|
|
170
|
+
if (e === fileName) {
|
|
171
|
+
return pathResolve(dir, e);
|
|
172
|
+
}
|
|
146
173
|
}
|
|
147
174
|
// 再递归子目录(跳过隐藏目录和常见无关目录)
|
|
148
175
|
for (const e of entries) {
|
|
149
|
-
if (e.startsWith('.') || e === 'node_modules' || e === 'build' || e === 'DerivedData')
|
|
176
|
+
if (e.startsWith('.') || e === 'node_modules' || e === 'build' || e === 'DerivedData') {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
150
179
|
const full = pathResolve(dir, e);
|
|
151
180
|
try {
|
|
152
181
|
if (statSync(full).isDirectory()) {
|
|
153
182
|
const found = _findFileRecursive(full, fileName, maxDepth - 1);
|
|
154
|
-
if (found)
|
|
183
|
+
if (found) {
|
|
184
|
+
return found;
|
|
185
|
+
}
|
|
155
186
|
}
|
|
156
|
-
} catch {
|
|
187
|
+
} catch {
|
|
188
|
+
/* 跳过不可访问的目录 */
|
|
189
|
+
}
|
|
157
190
|
}
|
|
158
|
-
} catch {
|
|
191
|
+
} catch {
|
|
192
|
+
/* 跳过不可读目录 */
|
|
193
|
+
}
|
|
159
194
|
return null;
|
|
160
195
|
}
|
|
161
196
|
|
|
@@ -184,24 +219,34 @@ function _resolveHeaderFormat(rawHeader, ctx) {
|
|
|
184
219
|
// Swift: 始终 `import Module`
|
|
185
220
|
if (isSwift || parsed.isSwiftImport) {
|
|
186
221
|
// 已经是完整 swift import 语句
|
|
187
|
-
if (parsed.isSwiftImport)
|
|
222
|
+
if (parsed.isSwiftImport) {
|
|
223
|
+
return rawHeader.trim();
|
|
224
|
+
}
|
|
188
225
|
// 从 ObjC 格式推断 swift import
|
|
189
226
|
const mod = parsed.moduleName || headerModuleName || '';
|
|
190
|
-
if (mod)
|
|
227
|
+
if (mod) {
|
|
228
|
+
return `import ${mod}`;
|
|
229
|
+
}
|
|
191
230
|
return rawHeader.trim(); // 无法推断,原样返回
|
|
192
231
|
}
|
|
193
232
|
|
|
194
233
|
// @import 保持原样(模块级引用不受 target 影响)
|
|
195
|
-
if (parsed.isAtImport)
|
|
234
|
+
if (parsed.isAtImport) {
|
|
235
|
+
return rawHeader.trim();
|
|
236
|
+
}
|
|
196
237
|
|
|
197
238
|
// 已经是尖括号格式 → 保持(明确的跨模块引用)
|
|
198
|
-
if (parsed.isAngle)
|
|
239
|
+
if (parsed.isAngle) {
|
|
240
|
+
return rawHeader.trim();
|
|
241
|
+
}
|
|
199
242
|
|
|
200
243
|
// ── ObjC: 判断同 target vs 跨 target ──
|
|
201
244
|
const effectiveModule = parsed.moduleName || headerModuleName || '';
|
|
202
245
|
|
|
203
246
|
// 如果没有 target 信息,无法判断,保持原样
|
|
204
|
-
if (!currentTarget || !effectiveModule)
|
|
247
|
+
if (!currentTarget || !effectiveModule) {
|
|
248
|
+
return rawHeader.trim();
|
|
249
|
+
}
|
|
205
250
|
|
|
206
251
|
const isSameTarget = currentTarget === effectiveModule;
|
|
207
252
|
|
|
@@ -209,10 +254,16 @@ function _resolveHeaderFormat(rawHeader, ctx) {
|
|
|
209
254
|
// 同 target → 引号格式,计算相对路径
|
|
210
255
|
if (parsed.headerName && fullPath) {
|
|
211
256
|
const relPath = _findHeaderRelativePath(parsed.headerName, fullPath, projectRoot);
|
|
212
|
-
if (relPath)
|
|
257
|
+
if (relPath) {
|
|
258
|
+
return `#import "${relPath}"`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (parsed.quotedPath) {
|
|
262
|
+
return `#import "${parsed.quotedPath}"`;
|
|
263
|
+
}
|
|
264
|
+
if (parsed.headerName) {
|
|
265
|
+
return `#import "${parsed.headerName}"`;
|
|
213
266
|
}
|
|
214
|
-
if (parsed.quotedPath) return `#import "${parsed.quotedPath}"`;
|
|
215
|
-
if (parsed.headerName) return `#import "${parsed.headerName}"`;
|
|
216
267
|
return rawHeader.trim();
|
|
217
268
|
}
|
|
218
269
|
|
|
@@ -226,16 +277,56 @@ function _resolveHeaderFormat(rawHeader, ctx) {
|
|
|
226
277
|
|
|
227
278
|
/** 常见 Apple 系统框架(无需 SPM 依赖检查) */
|
|
228
279
|
const _SYSTEM_FRAMEWORKS = new Set([
|
|
229
|
-
'Foundation',
|
|
230
|
-
'
|
|
231
|
-
'
|
|
232
|
-
'
|
|
233
|
-
'
|
|
234
|
-
'
|
|
235
|
-
'
|
|
236
|
-
'
|
|
237
|
-
'
|
|
238
|
-
'
|
|
280
|
+
'Foundation',
|
|
281
|
+
'UIKit',
|
|
282
|
+
'AppKit',
|
|
283
|
+
'SwiftUI',
|
|
284
|
+
'Combine',
|
|
285
|
+
'CoreFoundation',
|
|
286
|
+
'CoreGraphics',
|
|
287
|
+
'CoreData',
|
|
288
|
+
'CoreAnimation',
|
|
289
|
+
'CoreLocation',
|
|
290
|
+
'CoreMedia',
|
|
291
|
+
'CoreImage',
|
|
292
|
+
'CoreText',
|
|
293
|
+
'CoreVideo',
|
|
294
|
+
'QuartzCore',
|
|
295
|
+
'AVFoundation',
|
|
296
|
+
'AVKit',
|
|
297
|
+
'WebKit',
|
|
298
|
+
'MapKit',
|
|
299
|
+
'Metal',
|
|
300
|
+
'MetalKit',
|
|
301
|
+
'ARKit',
|
|
302
|
+
'SceneKit',
|
|
303
|
+
'SpriteKit',
|
|
304
|
+
'GameKit',
|
|
305
|
+
'GameplayKit',
|
|
306
|
+
'HealthKit',
|
|
307
|
+
'HomeKit',
|
|
308
|
+
'CloudKit',
|
|
309
|
+
'StoreKit',
|
|
310
|
+
'PhotosUI',
|
|
311
|
+
'Photos',
|
|
312
|
+
'Contacts',
|
|
313
|
+
'ContactsUI',
|
|
314
|
+
'EventKit',
|
|
315
|
+
'UserNotifications',
|
|
316
|
+
'MessageUI',
|
|
317
|
+
'MultipeerConnectivity',
|
|
318
|
+
'NetworkExtension',
|
|
319
|
+
'SafariServices',
|
|
320
|
+
'AuthenticationServices',
|
|
321
|
+
'LocalAuthentication',
|
|
322
|
+
'Security',
|
|
323
|
+
'CryptoKit',
|
|
324
|
+
'Accelerate',
|
|
325
|
+
'os',
|
|
326
|
+
'Darwin',
|
|
327
|
+
'ObjectiveC',
|
|
328
|
+
'Dispatch',
|
|
329
|
+
'XCTest',
|
|
239
330
|
]);
|
|
240
331
|
|
|
241
332
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -247,14 +338,18 @@ const _SYSTEM_FRAMEWORKS = new Set([
|
|
|
247
338
|
*/
|
|
248
339
|
function _collectImportsFromFile(filePath, isSwift) {
|
|
249
340
|
try {
|
|
250
|
-
if (!existsSync(filePath))
|
|
341
|
+
if (!existsSync(filePath)) {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
251
344
|
const content = readFileSync(filePath, 'utf8');
|
|
252
345
|
const lines = content.split(/\r?\n/);
|
|
253
346
|
const imports = [];
|
|
254
347
|
for (const line of lines) {
|
|
255
348
|
const t = line.trim();
|
|
256
349
|
if (isSwift) {
|
|
257
|
-
if (t.startsWith('import '))
|
|
350
|
+
if (t.startsWith('import ')) {
|
|
351
|
+
imports.push(t);
|
|
352
|
+
}
|
|
258
353
|
} else {
|
|
259
354
|
if (t.startsWith('#import ') || t.startsWith('@import ') || t.startsWith('#include ')) {
|
|
260
355
|
imports.push(t);
|
|
@@ -272,11 +367,15 @@ function _collectImportsFromFile(filePath, isSwift) {
|
|
|
272
367
|
*/
|
|
273
368
|
function _collectImportsFromHeaderFile(sourcePath, importArray) {
|
|
274
369
|
const dotIndex = sourcePath.lastIndexOf('.');
|
|
275
|
-
if (dotIndex <= 0)
|
|
276
|
-
|
|
370
|
+
if (dotIndex <= 0) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const headerPath = `${sourcePath.substring(0, dotIndex)}.h`;
|
|
277
374
|
const importReg = /^#import\s*<[A-Za-z0-9_]+\/[A-Za-z0-9_+.-]+\.h>$/;
|
|
278
375
|
try {
|
|
279
|
-
if (!existsSync(headerPath))
|
|
376
|
+
if (!existsSync(headerPath)) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
280
379
|
const data = readFileSync(headerPath, 'utf8');
|
|
281
380
|
for (const line of data.split('\n')) {
|
|
282
381
|
const t = line.trim();
|
|
@@ -284,7 +383,9 @@ function _collectImportsFromHeaderFile(sourcePath, importArray) {
|
|
|
284
383
|
importArray.push(t);
|
|
285
384
|
}
|
|
286
385
|
}
|
|
287
|
-
} catch {
|
|
386
|
+
} catch {
|
|
387
|
+
/* ignore */
|
|
388
|
+
}
|
|
288
389
|
}
|
|
289
390
|
|
|
290
391
|
/**
|
|
@@ -307,7 +408,9 @@ function _checkImportStatus(importArray, headerLine, isSwift) {
|
|
|
307
408
|
|
|
308
409
|
if (isSwift) {
|
|
309
410
|
const m = trimmed.match(/^import\s+(\w+)/);
|
|
310
|
-
if (m)
|
|
411
|
+
if (m) {
|
|
412
|
+
moduleName = m[1];
|
|
413
|
+
}
|
|
311
414
|
headerFileName = moduleName;
|
|
312
415
|
} else {
|
|
313
416
|
const angle = trimmed.match(/<([^/]+)\/([^>]+)>/);
|
|
@@ -359,9 +462,13 @@ function _checkImportStatus(importArray, headerLine, isSwift) {
|
|
|
359
462
|
if (headerFileNameLower) {
|
|
360
463
|
let importedFileName = null;
|
|
361
464
|
const a = impT.match(/<[^/]+\/([^>]+)>/);
|
|
362
|
-
if (a)
|
|
465
|
+
if (a) {
|
|
466
|
+
importedFileName = a[1].toLowerCase();
|
|
467
|
+
}
|
|
363
468
|
const q = impT.match(/"([^"]+)"/);
|
|
364
|
-
if (q)
|
|
469
|
+
if (q) {
|
|
470
|
+
importedFileName = basename(q[1]).toLowerCase();
|
|
471
|
+
}
|
|
365
472
|
if (importedFileName && importedFileName === headerFileNameLower) {
|
|
366
473
|
return { hasHeader: false, hasModule: false, hasSimilarHeader: true };
|
|
367
474
|
}
|
|
@@ -390,9 +497,15 @@ function _inferModulesFromHeaders(headers) {
|
|
|
390
497
|
const t = h.trim();
|
|
391
498
|
let m;
|
|
392
499
|
m = t.match(/^#import\s+<([^/> ]+)/);
|
|
393
|
-
if (m) {
|
|
500
|
+
if (m) {
|
|
501
|
+
modules.add(m[1]);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
394
504
|
m = t.match(/^@import\s+(\w+)/);
|
|
395
|
-
if (m) {
|
|
505
|
+
if (m) {
|
|
506
|
+
modules.add(m[1]);
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
396
509
|
m = t.match(/^import\s+(\w+)/);
|
|
397
510
|
if (m && !['class', 'struct', 'enum', 'protocol'].includes(m[1])) {
|
|
398
511
|
modules.add(m[1]);
|
|
@@ -412,7 +525,9 @@ function _inferModulesFromHeaders(headers) {
|
|
|
412
525
|
* review — 依赖缺失但可添加,需用户确认
|
|
413
526
|
*/
|
|
414
527
|
function _evaluateDepResult(ensureResult, from, to) {
|
|
415
|
-
if (ensureResult.exists)
|
|
528
|
+
if (ensureResult.exists) {
|
|
529
|
+
return { action: 'continue' };
|
|
530
|
+
}
|
|
416
531
|
if (!ensureResult.canAdd) {
|
|
417
532
|
return { action: 'block', reason: ensureResult.reason || 'cycleBlocked', from, to };
|
|
418
533
|
}
|
|
@@ -429,33 +544,34 @@ function _handleDepReview(ctx) {
|
|
|
429
544
|
const { spmService, currentTarget, mod, ensureResult, NU, depWarnings, label = '' } = ctx;
|
|
430
545
|
|
|
431
546
|
const fixMode = spmService.getFixMode();
|
|
432
|
-
const buttons =
|
|
433
|
-
|
|
434
|
-
|
|
547
|
+
const buttons =
|
|
548
|
+
fixMode === 'fix'
|
|
549
|
+
? ['直接插入(信任架构)', '提示操作插入', '自动修复依赖', '取消操作']
|
|
550
|
+
: ['直接插入(信任架构)', '提示操作插入', '取消操作'];
|
|
435
551
|
|
|
436
552
|
const crossTag = ensureResult.crossPackage ? ' (跨包)' : '';
|
|
437
553
|
const prefix = label ? `[${label}] ` : '';
|
|
438
|
-
console.log(` ⚠️ ${prefix}依赖缺失: ${currentTarget} -> ${mod}`);
|
|
439
554
|
|
|
440
555
|
const userChoice = NU.promptWithButtons(
|
|
441
556
|
`检测到依赖缺失:${currentTarget} -> ${mod}${crossTag}\n\n请选择处理方式:`,
|
|
442
557
|
buttons,
|
|
443
|
-
'AutoSnippet SPM 依赖决策'
|
|
558
|
+
'AutoSnippet SPM 依赖决策'
|
|
444
559
|
);
|
|
445
560
|
|
|
446
|
-
if (
|
|
561
|
+
if (
|
|
562
|
+
userChoice === '取消操作' ||
|
|
563
|
+
(!userChoice && !['直接插入(信任架构)', '提示操作插入', '自动修复依赖'].includes(userChoice))
|
|
564
|
+
) {
|
|
447
565
|
return { blocked: true };
|
|
448
566
|
}
|
|
449
567
|
|
|
450
568
|
if (userChoice === '提示操作插入') {
|
|
451
|
-
console.log(` 📋 ${prefix}提示操作:依赖缺失 ${currentTarget} -> ${mod}`);
|
|
452
569
|
depWarnings.set(mod, `${currentTarget} -> ${mod}`);
|
|
453
570
|
}
|
|
454
571
|
|
|
455
572
|
if (userChoice === '自动修复依赖') {
|
|
456
573
|
const fixResult = spmService.addDependency(currentTarget, mod);
|
|
457
574
|
if (fixResult.ok) {
|
|
458
|
-
console.log(` ✅ ${prefix}已自动补齐依赖: ${currentTarget} -> ${mod}${fixResult.crossPackage ? ' (跨包)' : ''} (${fixResult.file})`);
|
|
459
575
|
NU.notify(`已补齐依赖:${currentTarget} -> ${mod}`, 'AutoSnippet SPM');
|
|
460
576
|
} else {
|
|
461
577
|
console.warn(` ⚠️ ${prefix}自动修复失败: ${fixResult.error},继续插入`);
|
|
@@ -482,9 +598,11 @@ function _handleDepReview(ctx) {
|
|
|
482
598
|
* @returns {boolean}
|
|
483
599
|
*/
|
|
484
600
|
function _writeImportLineXcode(importLine, insertLine, XA, CM) {
|
|
485
|
-
if (!XA.isXcodeRunning())
|
|
601
|
+
if (!XA.isXcodeRunning()) {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
486
604
|
try {
|
|
487
|
-
const contentToWrite = String(importLine).trim()
|
|
605
|
+
const contentToWrite = `${String(importLine).trim()}\n`;
|
|
488
606
|
const previousClipboard = CM.read();
|
|
489
607
|
|
|
490
608
|
CM.write(contentToWrite);
|
|
@@ -516,7 +634,9 @@ function _writeImportLineFile(filePath, importLine, isSwift) {
|
|
|
516
634
|
for (let i = 0; i < lines.length; i++) {
|
|
517
635
|
const t = lines[i].trim();
|
|
518
636
|
if (isSwift) {
|
|
519
|
-
if (t.startsWith('import ') && !t.startsWith('import ('))
|
|
637
|
+
if (t.startsWith('import ') && !t.startsWith('import (')) {
|
|
638
|
+
lastImportIdx = i;
|
|
639
|
+
}
|
|
520
640
|
} else {
|
|
521
641
|
if (t.startsWith('#import ') || t.startsWith('#include ') || t.startsWith('@import ')) {
|
|
522
642
|
lastImportIdx = i;
|
|
@@ -543,14 +663,20 @@ function _writeImportLineFile(filePath, importLine, isSwift) {
|
|
|
543
663
|
*/
|
|
544
664
|
function _getLastImportLine(filePath) {
|
|
545
665
|
try {
|
|
546
|
-
if (!existsSync(filePath))
|
|
666
|
+
if (!existsSync(filePath)) {
|
|
667
|
+
return 0;
|
|
668
|
+
}
|
|
547
669
|
const content = readFileSync(filePath, 'utf8');
|
|
548
670
|
const lines = content.split(/\r?\n/);
|
|
549
671
|
let lastIdx = -1;
|
|
550
672
|
for (let i = 0; i < lines.length; i++) {
|
|
551
673
|
const t = lines[i].trim();
|
|
552
|
-
if (
|
|
553
|
-
|
|
674
|
+
if (
|
|
675
|
+
t.startsWith('#import ') ||
|
|
676
|
+
t.startsWith('@import ') ||
|
|
677
|
+
t.startsWith('#include ') ||
|
|
678
|
+
t.startsWith('import ')
|
|
679
|
+
) {
|
|
554
680
|
lastIdx = i;
|
|
555
681
|
}
|
|
556
682
|
}
|
|
@@ -601,14 +727,16 @@ function _computePasteLineNumber(triggerLineNumber, headerInsertCount, filePath,
|
|
|
601
727
|
* @returns {Promise<{inserted: string[], skipped: string[], cancelled: boolean}>}
|
|
602
728
|
*/
|
|
603
729
|
export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
604
|
-
const XA = await import('
|
|
605
|
-
const CM = await import('
|
|
606
|
-
const NU = await import('
|
|
730
|
+
const XA = await import('./XcodeAutomation.js');
|
|
731
|
+
const CM = await import('../../../infrastructure/external/ClipboardManager.js');
|
|
732
|
+
const NU = await import('../../../infrastructure/external/NativeUi.js');
|
|
607
733
|
|
|
608
734
|
const result = { inserted: [], skipped: [], cancelled: false };
|
|
609
735
|
/** @type {Map<string, string>} 模块名 → 提示注释('提示操作插入'按钮选择时记录) */
|
|
610
736
|
const depWarnings = opts.depWarnings instanceof Map ? new Map(opts.depWarnings) : new Map();
|
|
611
|
-
if (!headers || headers.length === 0)
|
|
737
|
+
if (!headers || headers.length === 0) {
|
|
738
|
+
return result;
|
|
739
|
+
}
|
|
612
740
|
|
|
613
741
|
const isSwift = opts.isSwift ?? fullPath.endsWith('.swift');
|
|
614
742
|
|
|
@@ -619,7 +747,7 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
619
747
|
_collectImportsFromHeaderFile(fullPath, importArray);
|
|
620
748
|
}
|
|
621
749
|
|
|
622
|
-
// ── Step 2: SPM 服务准备 ──
|
|
750
|
+
// ── Step 2: SPM/模块 服务准备 ──
|
|
623
751
|
// 优先复用 opts 传入的 spmService/currentTarget(避免与 _preflightDeps 重复 load)
|
|
624
752
|
let spmService = opts._spmService || null;
|
|
625
753
|
let currentTarget = opts._currentTarget || null;
|
|
@@ -628,21 +756,27 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
628
756
|
if (opts.moduleName && !inferredModules.includes(opts.moduleName)) {
|
|
629
757
|
inferredModules.push(opts.moduleName);
|
|
630
758
|
}
|
|
631
|
-
const thirdPartyModules = inferredModules.filter(m => !_SYSTEM_FRAMEWORKS.has(m));
|
|
759
|
+
const thirdPartyModules = inferredModules.filter((m) => !_SYSTEM_FRAMEWORKS.has(m));
|
|
632
760
|
if (thirdPartyModules.length > 0) {
|
|
633
761
|
try {
|
|
634
|
-
const { ServiceContainer } = await import('
|
|
762
|
+
const { ServiceContainer } = await import('../../../injection/ServiceContainer.js');
|
|
635
763
|
const container = ServiceContainer.getInstance();
|
|
636
764
|
spmService = container.get('spmService');
|
|
637
765
|
if (spmService) {
|
|
638
766
|
if (spmService.getFixMode() === 'off') {
|
|
639
767
|
spmService = null;
|
|
640
768
|
} else {
|
|
641
|
-
try {
|
|
769
|
+
try {
|
|
770
|
+
await spmService.load();
|
|
771
|
+
} catch {
|
|
772
|
+
/* Package.swift 不存在则跳过 */
|
|
773
|
+
}
|
|
642
774
|
currentTarget = spmService.resolveCurrentTarget(fullPath);
|
|
643
775
|
}
|
|
644
776
|
}
|
|
645
|
-
} catch {
|
|
777
|
+
} catch {
|
|
778
|
+
/* SPM 检查异常不阻断 */
|
|
779
|
+
}
|
|
646
780
|
}
|
|
647
781
|
}
|
|
648
782
|
|
|
@@ -650,15 +784,21 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
650
784
|
const xcodeReady = XA.isXcodeRunning();
|
|
651
785
|
// 从当前文件内容计算 import 插入基准行(1-based)
|
|
652
786
|
let content;
|
|
653
|
-
try {
|
|
787
|
+
try {
|
|
788
|
+
content = readFileSync(fullPath, 'utf8');
|
|
789
|
+
} catch {
|
|
790
|
+
return result;
|
|
791
|
+
}
|
|
654
792
|
const baseInsertLine = findImportInsertLine(content, isSwift) + 1; // 0-based → 1-based
|
|
655
|
-
let xcodeOffset = 0;
|
|
793
|
+
let xcodeOffset = 0; // 每次 Xcode 插入成功后 +1(修正多条 header 行号偏移)
|
|
656
794
|
let fileWriteUsed = false; // 一旦使用文件写入,后续全部走文件写入(避免 Xcode reload 冲突)
|
|
657
795
|
|
|
658
796
|
// ── Step 4: 逐条处理 ──
|
|
659
797
|
for (const header of headers) {
|
|
660
798
|
const headerTrimmed = header.trim();
|
|
661
|
-
if (!headerTrimmed)
|
|
799
|
+
if (!headerTrimmed) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
662
802
|
|
|
663
803
|
// ── 三级去重 ──
|
|
664
804
|
// 先按原始格式检查,再按解析后格式检查(同一 header 可能格式不同)
|
|
@@ -670,21 +810,19 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
670
810
|
projectRoot: watcher?.projectRoot || null,
|
|
671
811
|
});
|
|
672
812
|
const status = _checkImportStatus(importArray, headerTrimmed, isSwift);
|
|
673
|
-
const statusResolved =
|
|
674
|
-
|
|
675
|
-
|
|
813
|
+
const statusResolved =
|
|
814
|
+
preResolvedHeader !== headerTrimmed
|
|
815
|
+
? _checkImportStatus(importArray, preResolvedHeader, isSwift)
|
|
816
|
+
: status;
|
|
676
817
|
if (status.hasHeader || statusResolved.hasHeader) {
|
|
677
|
-
console.log(` ⏭️ 已存在(精确匹配): ${preResolvedHeader}`);
|
|
678
818
|
result.skipped.push(preResolvedHeader);
|
|
679
819
|
continue;
|
|
680
820
|
}
|
|
681
821
|
if (status.hasModule || statusResolved.hasModule) {
|
|
682
|
-
console.log(` ⏭️ 已存在(模块匹配): ${preResolvedHeader}`);
|
|
683
822
|
result.skipped.push(preResolvedHeader);
|
|
684
823
|
continue;
|
|
685
824
|
}
|
|
686
825
|
if (status.hasSimilarHeader || statusResolved.hasSimilarHeader) {
|
|
687
|
-
console.log(` ⏭️ 已存在(相似头文件): ${preResolvedHeader}`);
|
|
688
826
|
result.skipped.push(preResolvedHeader);
|
|
689
827
|
continue;
|
|
690
828
|
}
|
|
@@ -693,7 +831,9 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
693
831
|
const headerModules = _inferModulesFromHeaders([headerTrimmed]);
|
|
694
832
|
if (spmService && currentTarget && !opts.skipDepCheck) {
|
|
695
833
|
for (const mod of headerModules) {
|
|
696
|
-
if (_SYSTEM_FRAMEWORKS.has(mod) || mod === currentTarget)
|
|
834
|
+
if (_SYSTEM_FRAMEWORKS.has(mod) || mod === currentTarget) {
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
697
837
|
|
|
698
838
|
const ensureResult = spmService.ensureDependency(currentTarget, mod);
|
|
699
839
|
const decision = _evaluateDepResult(ensureResult, currentTarget, mod);
|
|
@@ -702,7 +842,7 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
702
842
|
console.warn(` ⛔ 依赖被阻止: ${currentTarget} -> ${mod} (${decision.reason})`);
|
|
703
843
|
NU.notify(
|
|
704
844
|
`已阻止依赖注入\n${currentTarget} -> ${mod}\n${decision.reason}`,
|
|
705
|
-
'AutoSnippet SPM 依赖策略'
|
|
845
|
+
'AutoSnippet SPM 依赖策略'
|
|
706
846
|
);
|
|
707
847
|
result.cancelled = true;
|
|
708
848
|
return result;
|
|
@@ -710,10 +850,14 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
710
850
|
|
|
711
851
|
if (decision.action === 'review') {
|
|
712
852
|
const reviewResult = _handleDepReview({
|
|
713
|
-
spmService,
|
|
853
|
+
spmService,
|
|
854
|
+
currentTarget,
|
|
855
|
+
mod,
|
|
856
|
+
ensureResult,
|
|
857
|
+
NU,
|
|
858
|
+
depWarnings,
|
|
714
859
|
});
|
|
715
860
|
if (reviewResult.blocked) {
|
|
716
|
-
console.log(` ⏹️ 用户取消`);
|
|
717
861
|
result.cancelled = true;
|
|
718
862
|
return result;
|
|
719
863
|
}
|
|
@@ -724,9 +868,9 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
724
868
|
// ── 构建带注释后缀的 import 行 ──
|
|
725
869
|
// 复用 dedup 阶段已计算的 preResolvedHeader
|
|
726
870
|
const resolvedHeader = preResolvedHeader;
|
|
727
|
-
const depHint = headerModules.find(m => depWarnings.has(m));
|
|
871
|
+
const depHint = headerModules.find((m) => depWarnings.has(m));
|
|
728
872
|
const importLine = depHint
|
|
729
|
-
? _withAutoSnippetNote(resolvedHeader)
|
|
873
|
+
? `${_withAutoSnippetNote(resolvedHeader)} // ⚠️ 依赖缺失: ${depWarnings.get(depHint)},需手动补齐 Package.swift`
|
|
730
874
|
: _withAutoSnippetNote(resolvedHeader);
|
|
731
875
|
|
|
732
876
|
// ── 写入:Xcode 自动化优先 → 文件写入回退 ──
|
|
@@ -747,11 +891,9 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
747
891
|
|
|
748
892
|
result.inserted.push(resolvedHeader);
|
|
749
893
|
importArray.push(resolvedHeader); // 添加到去重列表(用解析后格式)
|
|
750
|
-
console.log(` + ${resolvedHeader}`);
|
|
751
894
|
}
|
|
752
895
|
|
|
753
896
|
if (result.inserted.length > 0) {
|
|
754
|
-
console.log(` 📦 已添加 ${result.inserted.length} 个依赖`);
|
|
755
897
|
}
|
|
756
898
|
return result;
|
|
757
899
|
}
|
|
@@ -776,17 +918,16 @@ export async function insertHeaders(watcher, fullPath, headers, opts = {}) {
|
|
|
776
918
|
* @param {import('./FileWatcher.js').FileWatcher} watcher
|
|
777
919
|
*/
|
|
778
920
|
export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine) {
|
|
779
|
-
const XA = await import('
|
|
780
|
-
const CM = await import('
|
|
781
|
-
const NU = await import('
|
|
921
|
+
const XA = await import('./XcodeAutomation.js');
|
|
922
|
+
const CM = await import('../../../infrastructure/external/ClipboardManager.js');
|
|
923
|
+
const NU = await import('../../../infrastructure/external/NativeUi.js');
|
|
782
924
|
|
|
783
925
|
const code = selected.code || '';
|
|
784
926
|
if (!code) {
|
|
785
|
-
console.log(` ℹ️ 选中项无代码内容`);
|
|
786
927
|
return;
|
|
787
928
|
}
|
|
788
929
|
|
|
789
|
-
const headersToInsert = (selected.headers || []).filter(h => h
|
|
930
|
+
const headersToInsert = (selected.headers || []).filter((h) => h?.trim());
|
|
790
931
|
const isSwift = fullPath.endsWith('.swift');
|
|
791
932
|
|
|
792
933
|
// ═══════════════════════════════════════════════════════
|
|
@@ -805,7 +946,9 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
|
|
|
805
946
|
}
|
|
806
947
|
// ── Step 1: 找到触发行号 ──
|
|
807
948
|
let content;
|
|
808
|
-
try {
|
|
949
|
+
try {
|
|
950
|
+
content = readFileSync(fullPath, 'utf8');
|
|
951
|
+
} catch {
|
|
809
952
|
return _fileInsertFallback(fullPath, selected, triggerLine, headersToInsert, watcher);
|
|
810
953
|
}
|
|
811
954
|
const triggerLineNumber = findTriggerLineNumber(content, triggerLine);
|
|
@@ -827,7 +970,6 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
|
|
|
827
970
|
if (headersToInsert.length > 0) {
|
|
828
971
|
const preflight = await _preflightDeps(fullPath, headersToInsert, selected, NU);
|
|
829
972
|
if (preflight.blocked) {
|
|
830
|
-
console.log(` ⏹️ 依赖检查被阻止,跳过代码插入`);
|
|
831
973
|
return;
|
|
832
974
|
}
|
|
833
975
|
if (preflight.depWarnings && preflight.depWarnings.size > 0) {
|
|
@@ -843,7 +985,9 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
|
|
|
843
985
|
if (!cutOk) {
|
|
844
986
|
console.warn(` ⚠️ 自动剪切失败,降级为文件写入`);
|
|
845
987
|
// Preflight 已通过,skipDepCheck 避免重复弹窗
|
|
846
|
-
return _fileInsertFallback(fullPath, selected, triggerLine, headersToInsert, watcher, {
|
|
988
|
+
return _fileInsertFallback(fullPath, selected, triggerLine, headersToInsert, watcher, {
|
|
989
|
+
skipDepCheck: true,
|
|
990
|
+
});
|
|
847
991
|
}
|
|
848
992
|
await _sleep(300);
|
|
849
993
|
|
|
@@ -853,13 +997,8 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
|
|
|
853
997
|
while (codeLines.length > 0 && !codeLines[codeLines.length - 1].trim()) {
|
|
854
998
|
codeLines.pop();
|
|
855
999
|
}
|
|
856
|
-
const indentedLines = codeLines.map(line => line ? indent + line : line);
|
|
857
|
-
|
|
858
|
-
const commentMarker = _generateInsertMarker(fullPath, selected);
|
|
859
|
-
const markedLines = commentMarker
|
|
860
|
-
? [indent + commentMarker, ...indentedLines]
|
|
861
|
-
: indentedLines;
|
|
862
|
-
const indentedCode = markedLines.join('\n');
|
|
1000
|
+
const indentedLines = codeLines.map((line) => (line ? indent + line : line));
|
|
1001
|
+
const indentedCode = indentedLines.join('\n');
|
|
863
1002
|
|
|
864
1003
|
// ── Step 5: 插入 Headers ──
|
|
865
1004
|
let headerInsertCount = 0;
|
|
@@ -873,7 +1012,6 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
|
|
|
873
1012
|
_currentTarget: _currentTargetCached,
|
|
874
1013
|
});
|
|
875
1014
|
if (headerResult.cancelled) {
|
|
876
|
-
console.log(` ⏹️ Headers 插入被取消`);
|
|
877
1015
|
return;
|
|
878
1016
|
}
|
|
879
1017
|
headerInsertCount = headerResult.inserted.length;
|
|
@@ -886,7 +1024,7 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
|
|
|
886
1024
|
triggerLineNumber,
|
|
887
1025
|
headerInsertCount,
|
|
888
1026
|
fullPath,
|
|
889
|
-
{ forceOffset: headerInsertCount > 0, expectedHeaderCount: headerInsertCount }
|
|
1027
|
+
{ forceOffset: headerInsertCount > 0, expectedHeaderCount: headerInsertCount }
|
|
890
1028
|
);
|
|
891
1029
|
|
|
892
1030
|
// 如果 headers 通过文件写入,等待 Xcode reload
|
|
@@ -907,8 +1045,6 @@ export async function insertCodeToXcode(watcher, fullPath, selected, triggerLine
|
|
|
907
1045
|
XA.selectAndPasteInXcode();
|
|
908
1046
|
await _sleep(300);
|
|
909
1047
|
});
|
|
910
|
-
|
|
911
|
-
console.log(` ✅ 代码已粘贴到 Xcode(可 Cmd+Z 撤销)`);
|
|
912
1048
|
NU.notify(`已插入「${selected.title || '代码片段'}」`, 'AutoSnippet');
|
|
913
1049
|
return;
|
|
914
1050
|
}
|
|
@@ -937,44 +1073,67 @@ async function _preflightDeps(fullPath, headers, selected, NU) {
|
|
|
937
1073
|
if (selected.moduleName && !inferredModules.includes(selected.moduleName)) {
|
|
938
1074
|
inferredModules.push(selected.moduleName);
|
|
939
1075
|
}
|
|
940
|
-
const thirdPartyModules = inferredModules.filter(m => !_SYSTEM_FRAMEWORKS.has(m));
|
|
941
|
-
if (thirdPartyModules.length === 0)
|
|
1076
|
+
const thirdPartyModules = inferredModules.filter((m) => !_SYSTEM_FRAMEWORKS.has(m));
|
|
1077
|
+
if (thirdPartyModules.length === 0) {
|
|
1078
|
+
return result;
|
|
1079
|
+
}
|
|
942
1080
|
|
|
943
1081
|
try {
|
|
944
|
-
const { ServiceContainer } = await import('
|
|
1082
|
+
const { ServiceContainer } = await import('../../../injection/ServiceContainer.js');
|
|
945
1083
|
const container = ServiceContainer.getInstance();
|
|
946
1084
|
const spmService = container.get('spmService');
|
|
947
|
-
if (!spmService)
|
|
1085
|
+
if (!spmService) {
|
|
1086
|
+
return result;
|
|
1087
|
+
}
|
|
948
1088
|
|
|
949
1089
|
// Fix Mode 检查:off 模式完全跳过
|
|
950
|
-
if (spmService.getFixMode() === 'off')
|
|
1090
|
+
if (spmService.getFixMode() === 'off') {
|
|
1091
|
+
return result;
|
|
1092
|
+
}
|
|
951
1093
|
|
|
952
|
-
try {
|
|
1094
|
+
try {
|
|
1095
|
+
await spmService.load();
|
|
1096
|
+
} catch {
|
|
1097
|
+
return result;
|
|
1098
|
+
}
|
|
953
1099
|
|
|
954
1100
|
const currentTarget = spmService.resolveCurrentTarget(fullPath);
|
|
955
|
-
if (!currentTarget)
|
|
1101
|
+
if (!currentTarget) {
|
|
1102
|
+
return result;
|
|
1103
|
+
}
|
|
956
1104
|
|
|
957
1105
|
for (const mod of thirdPartyModules) {
|
|
958
|
-
if (mod === currentTarget)
|
|
1106
|
+
if (mod === currentTarget) {
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
959
1109
|
|
|
960
1110
|
const ensureResult = spmService.ensureDependency(currentTarget, mod);
|
|
961
1111
|
const decision = _evaluateDepResult(ensureResult, currentTarget, mod);
|
|
962
1112
|
|
|
963
1113
|
if (decision.action === 'block') {
|
|
964
|
-
console.warn(
|
|
1114
|
+
console.warn(
|
|
1115
|
+
` ⛔ [Preflight] 依赖被阻止: ${currentTarget} -> ${mod} (${decision.reason})`
|
|
1116
|
+
);
|
|
965
1117
|
NU.notify(
|
|
966
1118
|
`已阻止依赖注入\n${currentTarget} -> ${mod}\n${decision.reason}`,
|
|
967
|
-
'AutoSnippet SPM 依赖策略'
|
|
1119
|
+
'AutoSnippet SPM 依赖策略'
|
|
968
1120
|
);
|
|
969
1121
|
result.blocked = true;
|
|
970
1122
|
return result;
|
|
971
1123
|
}
|
|
972
1124
|
|
|
973
1125
|
if (decision.action === 'review') {
|
|
974
|
-
if (!result.depWarnings)
|
|
1126
|
+
if (!result.depWarnings) {
|
|
1127
|
+
result.depWarnings = new Map();
|
|
1128
|
+
}
|
|
975
1129
|
const reviewResult = _handleDepReview({
|
|
976
|
-
spmService,
|
|
977
|
-
|
|
1130
|
+
spmService,
|
|
1131
|
+
currentTarget,
|
|
1132
|
+
mod,
|
|
1133
|
+
ensureResult,
|
|
1134
|
+
NU,
|
|
1135
|
+
depWarnings: result.depWarnings,
|
|
1136
|
+
label: 'Preflight',
|
|
978
1137
|
});
|
|
979
1138
|
if (reviewResult.blocked) {
|
|
980
1139
|
result.blocked = true;
|
|
@@ -997,14 +1156,23 @@ async function _preflightDeps(fullPath, headers, selected, NU) {
|
|
|
997
1156
|
// §13 文件写入降级
|
|
998
1157
|
// ═══════════════════════════════════════════════════════════════
|
|
999
1158
|
|
|
1000
|
-
async function _fileInsertFallback(
|
|
1159
|
+
async function _fileInsertFallback(
|
|
1160
|
+
fullPath,
|
|
1161
|
+
selected,
|
|
1162
|
+
triggerLine,
|
|
1163
|
+
headersToInsert,
|
|
1164
|
+
watcher,
|
|
1165
|
+
opts = {}
|
|
1166
|
+
) {
|
|
1001
1167
|
// 先写 headers
|
|
1002
1168
|
if (headersToInsert.length > 0) {
|
|
1003
1169
|
const headerResult = await insertHeaders(watcher, fullPath, headersToInsert, {
|
|
1004
1170
|
moduleName: selected.moduleName || null,
|
|
1005
1171
|
skipDepCheck: opts.skipDepCheck || false, // Preflight 已通过时跳过重复检查
|
|
1006
1172
|
});
|
|
1007
|
-
if (headerResult.cancelled)
|
|
1173
|
+
if (headerResult.cancelled) {
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1008
1176
|
}
|
|
1009
1177
|
|
|
1010
1178
|
// 再替换触发行为代码
|
|
@@ -1033,27 +1201,20 @@ async function _fileInsertFallback(fullPath, selected, triggerLine, headersToIns
|
|
|
1033
1201
|
while (codeLines.length > 0 && !codeLines[codeLines.length - 1].trim()) {
|
|
1034
1202
|
codeLines.pop();
|
|
1035
1203
|
}
|
|
1036
|
-
const indentedLines = codeLines.map(line => line ? indent + line : line);
|
|
1037
|
-
|
|
1038
|
-
const commentMarker = _generateInsertMarker(fullPath, selected);
|
|
1039
|
-
const markedLines = commentMarker
|
|
1040
|
-
? [indent + commentMarker, ...indentedLines]
|
|
1041
|
-
: indentedLines;
|
|
1204
|
+
const indentedLines = codeLines.map((line) => (line ? indent + line : line));
|
|
1042
1205
|
|
|
1043
|
-
while (
|
|
1044
|
-
|
|
1206
|
+
while (indentedLines.length > 0 && !indentedLines[indentedLines.length - 1].trim()) {
|
|
1207
|
+
indentedLines.pop();
|
|
1045
1208
|
}
|
|
1046
1209
|
|
|
1047
|
-
const newLines = [...lines.slice(0, found), ...
|
|
1210
|
+
const newLines = [...lines.slice(0, found), ...indentedLines, ...lines.slice(found + 1)];
|
|
1048
1211
|
const newContent = newLines.join('\n');
|
|
1049
1212
|
saveEventFilter.markWrite(fullPath, newContent);
|
|
1050
1213
|
writeFileSync(fullPath, newContent, 'utf8');
|
|
1051
|
-
console.log(` ✅ 代码已写入文件(替换触发行)`);
|
|
1052
1214
|
} else {
|
|
1053
|
-
const appendContent = content
|
|
1215
|
+
const appendContent = `${content}\n${code}\n`;
|
|
1054
1216
|
saveEventFilter.markWrite(fullPath, appendContent);
|
|
1055
1217
|
writeFileSync(fullPath, appendContent, 'utf8');
|
|
1056
|
-
console.log(` ✅ 代码已追加到文件末尾`);
|
|
1057
1218
|
}
|
|
1058
1219
|
} catch (err) {
|
|
1059
1220
|
console.warn(` ⚠️ 文件写入失败: ${err.message}`);
|
|
@@ -1070,16 +1231,27 @@ function _generateInsertMarker(filePath, selected) {
|
|
|
1070
1231
|
const trigger = selected.trigger ? `[${selected.trigger}]` : '';
|
|
1071
1232
|
const recipeName = selected.name ? ` from ${selected.name}` : '';
|
|
1072
1233
|
const timestamp = new Date().toLocaleString('zh-CN', {
|
|
1073
|
-
year: 'numeric',
|
|
1074
|
-
|
|
1234
|
+
year: 'numeric',
|
|
1235
|
+
month: '2-digit',
|
|
1236
|
+
day: '2-digit',
|
|
1237
|
+
hour: '2-digit',
|
|
1238
|
+
minute: '2-digit',
|
|
1075
1239
|
});
|
|
1076
1240
|
|
|
1077
1241
|
const marker = `🤖 AutoSnippet${trigger}${recipeName} @ ${timestamp}`;
|
|
1078
1242
|
|
|
1079
|
-
if (['.py', '.rb'].includes(ext))
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
if (['.
|
|
1243
|
+
if (['.py', '.rb'].includes(ext)) {
|
|
1244
|
+
return `# ${marker}`;
|
|
1245
|
+
}
|
|
1246
|
+
if (['.lua', '.sql'].includes(ext)) {
|
|
1247
|
+
return `-- ${marker}`;
|
|
1248
|
+
}
|
|
1249
|
+
if (['.html', '.xml', '.svg'].includes(ext)) {
|
|
1250
|
+
return `<!-- ${marker} -->`;
|
|
1251
|
+
}
|
|
1252
|
+
if (['.css', '.scss', '.less'].includes(ext)) {
|
|
1253
|
+
return `/* ${marker} */`;
|
|
1254
|
+
}
|
|
1083
1255
|
return `// ${marker}`;
|
|
1084
1256
|
} catch {
|
|
1085
1257
|
return null;
|
|
@@ -1094,11 +1266,15 @@ function _generateInsertMarker(filePath, selected) {
|
|
|
1094
1266
|
* 查找触发行的行号(1-based,-1 表示未找到)
|
|
1095
1267
|
*/
|
|
1096
1268
|
export function findTriggerLineNumber(content, triggerLine) {
|
|
1097
|
-
if (!content || !triggerLine)
|
|
1269
|
+
if (!content || !triggerLine) {
|
|
1270
|
+
return -1;
|
|
1271
|
+
}
|
|
1098
1272
|
const needle = triggerLine.trim();
|
|
1099
1273
|
const lines = content.split(/\r?\n/);
|
|
1100
1274
|
for (let i = 0; i < lines.length; i++) {
|
|
1101
|
-
if (lines[i].trim() === needle)
|
|
1275
|
+
if (lines[i].trim() === needle) {
|
|
1276
|
+
return i + 1;
|
|
1277
|
+
}
|
|
1102
1278
|
}
|
|
1103
1279
|
return -1;
|
|
1104
1280
|
}
|
|
@@ -1112,9 +1288,13 @@ export function findImportInsertLine(content, isSwift) {
|
|
|
1112
1288
|
for (let i = 0; i < lines.length; i++) {
|
|
1113
1289
|
const t = lines[i].trim();
|
|
1114
1290
|
if (isSwift) {
|
|
1115
|
-
if (t.startsWith('import ') && !t.startsWith('import ('))
|
|
1291
|
+
if (t.startsWith('import ') && !t.startsWith('import (')) {
|
|
1292
|
+
lastImportLine = i;
|
|
1293
|
+
}
|
|
1116
1294
|
} else {
|
|
1117
|
-
if (t.startsWith('#import') || t.startsWith('@import'))
|
|
1295
|
+
if (t.startsWith('#import') || t.startsWith('@import')) {
|
|
1296
|
+
lastImportLine = i;
|
|
1297
|
+
}
|
|
1118
1298
|
}
|
|
1119
1299
|
}
|
|
1120
1300
|
return lastImportLine >= 0 ? lastImportLine + 1 : 0;
|