agent-ide 0.5.1 → 0.7.0

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.
Files changed (149) hide show
  1. package/dist/core/change-signature/change-signature-service.d.ts.map +1 -1
  2. package/dist/core/change-signature/change-signature-service.js +21 -14
  3. package/dist/core/change-signature/change-signature-service.js.map +1 -1
  4. package/dist/core/change-signature/signature-parser.d.ts +0 -16
  5. package/dist/core/change-signature/signature-parser.d.ts.map +1 -1
  6. package/dist/core/change-signature/signature-parser.js +28 -111
  7. package/dist/core/change-signature/signature-parser.js.map +1 -1
  8. package/dist/core/change-signature/types.d.ts +2 -0
  9. package/dist/core/change-signature/types.d.ts.map +1 -1
  10. package/dist/core/change-signature/types.js +2 -0
  11. package/dist/core/change-signature/types.js.map +1 -1
  12. package/dist/core/dead-code/dead-code-detector.d.ts +63 -0
  13. package/dist/core/dead-code/dead-code-detector.d.ts.map +1 -0
  14. package/dist/core/dead-code/dead-code-detector.js +256 -0
  15. package/dist/core/dead-code/dead-code-detector.js.map +1 -0
  16. package/dist/core/dead-code/dead-code-remover.d.ts +133 -0
  17. package/dist/core/dead-code/dead-code-remover.d.ts.map +1 -0
  18. package/dist/core/dead-code/dead-code-remover.js +803 -0
  19. package/dist/core/dead-code/dead-code-remover.js.map +1 -0
  20. package/dist/core/dead-code/index.d.ts +9 -0
  21. package/dist/core/dead-code/index.d.ts.map +1 -0
  22. package/dist/core/dead-code/index.js +8 -0
  23. package/dist/core/dead-code/index.js.map +1 -0
  24. package/dist/core/dead-code/types.d.ts +185 -0
  25. package/dist/core/dead-code/types.d.ts.map +1 -0
  26. package/dist/core/dead-code/types.js +53 -0
  27. package/dist/core/dead-code/types.js.map +1 -0
  28. package/dist/core/dependency/dependency-analyzer.d.ts.map +1 -1
  29. package/dist/core/dependency/dependency-analyzer.js +2 -15
  30. package/dist/core/dependency/dependency-analyzer.js.map +1 -1
  31. package/dist/core/indexing/index-engine.d.ts.map +1 -1
  32. package/dist/core/indexing/index-engine.js +0 -12
  33. package/dist/core/indexing/index-engine.js.map +1 -1
  34. package/dist/core/indexing/types.d.ts +1 -1
  35. package/dist/core/indexing/types.js +1 -1
  36. package/dist/core/indexing/types.js.map +1 -1
  37. package/dist/core/move-file/import-resolver.d.ts +1 -1
  38. package/dist/core/move-file/import-resolver.d.ts.map +1 -1
  39. package/dist/core/move-file/import-resolver.js +1 -9
  40. package/dist/core/move-file/import-resolver.js.map +1 -1
  41. package/dist/core/move-file/move-service.d.ts +8 -0
  42. package/dist/core/move-file/move-service.d.ts.map +1 -1
  43. package/dist/core/move-file/move-service.js +102 -21
  44. package/dist/core/move-file/move-service.js.map +1 -1
  45. package/dist/core/move-member/member-extractor.d.ts +0 -8
  46. package/dist/core/move-member/member-extractor.d.ts.map +1 -1
  47. package/dist/core/move-member/member-extractor.js +0 -49
  48. package/dist/core/move-member/member-extractor.js.map +1 -1
  49. package/dist/core/move-member/move-member-service.js +1 -1
  50. package/dist/core/move-member/move-member-service.js.map +1 -1
  51. package/dist/core/shared/symbol-finder.d.ts +4 -0
  52. package/dist/core/shared/symbol-finder.d.ts.map +1 -1
  53. package/dist/core/shared/symbol-finder.js +41 -0
  54. package/dist/core/shared/symbol-finder.js.map +1 -1
  55. package/dist/core/snapshot/index.d.ts +2 -0
  56. package/dist/core/snapshot/index.d.ts.map +1 -1
  57. package/dist/core/snapshot/index.js +1 -0
  58. package/dist/core/snapshot/index.js.map +1 -1
  59. package/dist/core/snapshot/snapshot-cache.d.ts +103 -0
  60. package/dist/core/snapshot/snapshot-cache.d.ts.map +1 -0
  61. package/dist/core/snapshot/snapshot-cache.js +249 -0
  62. package/dist/core/snapshot/snapshot-cache.js.map +1 -0
  63. package/dist/core/snapshot/snapshot-generator.d.ts +12 -0
  64. package/dist/core/snapshot/snapshot-generator.d.ts.map +1 -1
  65. package/dist/core/snapshot/snapshot-generator.js +61 -3
  66. package/dist/core/snapshot/snapshot-generator.js.map +1 -1
  67. package/dist/infrastructure/formatters/index.d.ts +1 -1
  68. package/dist/infrastructure/formatters/index.d.ts.map +1 -1
  69. package/dist/infrastructure/formatters/index.js.map +1 -1
  70. package/dist/infrastructure/formatters/preview-converter.d.ts +62 -0
  71. package/dist/infrastructure/formatters/preview-converter.d.ts.map +1 -1
  72. package/dist/infrastructure/formatters/preview-converter.js +82 -0
  73. package/dist/infrastructure/formatters/preview-converter.js.map +1 -1
  74. package/dist/infrastructure/formatters/query-formatter.d.ts +16 -0
  75. package/dist/infrastructure/formatters/query-formatter.d.ts.map +1 -1
  76. package/dist/infrastructure/formatters/query-formatter.js +155 -3
  77. package/dist/infrastructure/formatters/query-formatter.js.map +1 -1
  78. package/dist/infrastructure/formatters/query-types.d.ts +73 -2
  79. package/dist/infrastructure/formatters/query-types.d.ts.map +1 -1
  80. package/dist/infrastructure/formatters/types.d.ts +2 -1
  81. package/dist/infrastructure/formatters/types.d.ts.map +1 -1
  82. package/dist/infrastructure/formatters/types.js +1 -0
  83. package/dist/infrastructure/formatters/types.js.map +1 -1
  84. package/dist/interfaces/cli/cli.d.ts.map +1 -1
  85. package/dist/interfaces/cli/cli.js +2 -29
  86. package/dist/interfaces/cli/cli.js.map +1 -1
  87. package/dist/interfaces/cli/commands/change-signature.command.js +3 -3
  88. package/dist/interfaces/cli/commands/change-signature.command.js.map +1 -1
  89. package/dist/interfaces/cli/commands/deadcode.command.d.ts +11 -0
  90. package/dist/interfaces/cli/commands/deadcode.command.d.ts.map +1 -0
  91. package/dist/interfaces/cli/commands/deadcode.command.js +164 -0
  92. package/dist/interfaces/cli/commands/deadcode.command.js.map +1 -0
  93. package/dist/interfaces/cli/commands/index.d.ts +1 -0
  94. package/dist/interfaces/cli/commands/index.d.ts.map +1 -1
  95. package/dist/interfaces/cli/commands/index.js +1 -0
  96. package/dist/interfaces/cli/commands/index.js.map +1 -1
  97. package/dist/interfaces/cli/commands/move.command.js +1 -1
  98. package/dist/interfaces/cli/commands/move.command.js.map +1 -1
  99. package/dist/interfaces/cli/commands/rename.command.js +2 -3
  100. package/dist/interfaces/cli/commands/rename.command.js.map +1 -1
  101. package/dist/interfaces/cli/commands/snapshot.command.d.ts.map +1 -1
  102. package/dist/interfaces/cli/commands/snapshot.command.js +20 -0
  103. package/dist/interfaces/cli/commands/snapshot.command.js.map +1 -1
  104. package/package.json +10 -12
  105. package/dist/plugins/python/dependency-analyzer.d.ts +0 -86
  106. package/dist/plugins/python/dependency-analyzer.d.ts.map +0 -1
  107. package/dist/plugins/python/dependency-analyzer.js +0 -252
  108. package/dist/plugins/python/dependency-analyzer.js.map +0 -1
  109. package/dist/plugins/python/index.d.ts +0 -9
  110. package/dist/plugins/python/index.d.ts.map +0 -1
  111. package/dist/plugins/python/index.js +0 -14
  112. package/dist/plugins/python/index.js.map +0 -1
  113. package/dist/plugins/python/parser.d.ts +0 -117
  114. package/dist/plugins/python/parser.d.ts.map +0 -1
  115. package/dist/plugins/python/parser.js +0 -414
  116. package/dist/plugins/python/parser.js.map +0 -1
  117. package/dist/plugins/python/symbol-extractor.d.ts +0 -108
  118. package/dist/plugins/python/symbol-extractor.d.ts.map +0 -1
  119. package/dist/plugins/python/symbol-extractor.js +0 -389
  120. package/dist/plugins/python/symbol-extractor.js.map +0 -1
  121. package/dist/plugins/python/tree-sitter-bridge.d.ts +0 -57
  122. package/dist/plugins/python/tree-sitter-bridge.d.ts.map +0 -1
  123. package/dist/plugins/python/tree-sitter-bridge.js +0 -269
  124. package/dist/plugins/python/tree-sitter-bridge.js.map +0 -1
  125. package/dist/plugins/python/types.d.ts +0 -191
  126. package/dist/plugins/python/types.d.ts.map +0 -1
  127. package/dist/plugins/python/types.js +0 -279
  128. package/dist/plugins/python/types.js.map +0 -1
  129. package/dist/plugins/swift/dependency-analyzer.d.ts +0 -33
  130. package/dist/plugins/swift/dependency-analyzer.d.ts.map +0 -1
  131. package/dist/plugins/swift/dependency-analyzer.js +0 -95
  132. package/dist/plugins/swift/dependency-analyzer.js.map +0 -1
  133. package/dist/plugins/swift/index.d.ts +0 -14
  134. package/dist/plugins/swift/index.d.ts.map +0 -1
  135. package/dist/plugins/swift/index.js +0 -19
  136. package/dist/plugins/swift/index.js.map +0 -1
  137. package/dist/plugins/swift/parser.d.ts +0 -164
  138. package/dist/plugins/swift/parser.d.ts.map +0 -1
  139. package/dist/plugins/swift/parser.js +0 -688
  140. package/dist/plugins/swift/parser.js.map +0 -1
  141. package/dist/plugins/swift/swift-bridge/swift-parser +0 -0
  142. package/dist/plugins/swift/symbol-extractor.d.ts +0 -46
  143. package/dist/plugins/swift/symbol-extractor.d.ts.map +0 -1
  144. package/dist/plugins/swift/symbol-extractor.js +0 -187
  145. package/dist/plugins/swift/symbol-extractor.js.map +0 -1
  146. package/dist/plugins/swift/types.d.ts +0 -149
  147. package/dist/plugins/swift/types.d.ts.map +0 -1
  148. package/dist/plugins/swift/types.js +0 -251
  149. package/dist/plugins/swift/types.js.map +0 -1
@@ -1,688 +0,0 @@
1
- /**
2
- * Swift Parser 主要實作
3
- * 實作 ParserPlugin 介面
4
- */
5
- import { exec } from 'child_process';
6
- import { promisify } from 'util';
7
- import * as path from 'path';
8
- import * as fs from 'fs';
9
- import { createValidationSuccess, createValidationFailure, createCodeEdit, createDefinition, createUsage } from '../../infrastructure/parser/index.js';
10
- import { createAST, createASTMetadata, ReferenceType, SymbolType } from '../../shared/types/index.js';
11
- import { SwiftNodeKind, createSwiftASTNode, createParseError, getNodeName, isValidIdentifier } from './types.js';
12
- import { createSymbolExtractor } from '../swift/symbol-extractor.js';
13
- import { createDependencyAnalyzer } from '../swift/dependency-analyzer.js';
14
- const execAsync = promisify(exec);
15
- /**
16
- * Swift Parser 實作
17
- */
18
- export class SwiftParser {
19
- name = 'swift';
20
- version = '1.0.0';
21
- supportedExtensions = ['.swift'];
22
- supportedLanguages = ['swift'];
23
- symbolExtractor;
24
- dependencyAnalyzer;
25
- cliPath;
26
- constructor(cliPath = 'swift-parser') {
27
- this.symbolExtractor = createSymbolExtractor();
28
- this.dependencyAnalyzer = createDependencyAnalyzer();
29
- this.cliPath = cliPath;
30
- }
31
- // ===== 10 個基本方法 =====
32
- /**
33
- * 解析 Swift 程式碼
34
- */
35
- async parse(code, filePath) {
36
- this.validateInput(code, filePath);
37
- try {
38
- // 呼叫 Swift CLI Bridge
39
- const cliPath = this.resolveCliPath();
40
- const escapedCode = code.replace(/'/g, '\'\\\'\''); // 逸出單引號
41
- const { stdout } = await execAsync(`echo '${escapedCode}' | ${cliPath}`, {
42
- maxBuffer: 10 * 1024 * 1024 // 10MB buffer
43
- });
44
- // 解析 JSON 輸出
45
- const cliOutput = JSON.parse(stdout);
46
- // 檢查解析錯誤
47
- if (cliOutput.parseErrors) {
48
- throw createParseError(`Swift 語法錯誤: ${cliOutput.parseErrors.map((e) => e.message).join(', ')}`);
49
- }
50
- // 轉換為標準 AST
51
- const rootNode = this.convertCLINodeToASTNode(cliOutput.root);
52
- const metadata = createASTMetadata('swift', cliOutput.metadata?.parserVersion || this.version, {}, Date.now(), 0);
53
- const baseAST = createAST(filePath, rootNode, metadata);
54
- const ast = {
55
- ...baseAST,
56
- root: rootNode,
57
- swiftVersion: cliOutput.metadata?.parserVersion,
58
- diagnostics: []
59
- };
60
- return ast;
61
- }
62
- catch (error) {
63
- throw createParseError(`解析失敗: ${error instanceof Error ? error.message : String(error)}`);
64
- }
65
- }
66
- /**
67
- * 解析 Swift CLI Bridge 路徑
68
- */
69
- resolveCliPath() {
70
- // 優先使用建構參數指定的路徑
71
- if (this.cliPath && this.cliPath !== 'swift-parser') {
72
- if (fs.existsSync(this.cliPath)) {
73
- return this.cliPath;
74
- }
75
- }
76
- // 從 dist/ 目錄解析(build 時已複製)
77
- const distPath = path.join(__dirname, 'swift-bridge', 'swift-parser');
78
- if (fs.existsSync(distPath)) {
79
- return distPath;
80
- }
81
- // 開發環境:從 src/ 目錄解析
82
- const projectRoot = process.cwd();
83
- const srcPath = path.join(projectRoot, 'src/plugins/swift/swift-bridge/swift-parser');
84
- if (fs.existsSync(srcPath)) {
85
- return srcPath;
86
- }
87
- // 錯誤:CLI Bridge 未找到
88
- throw new Error('Swift CLI Bridge not found. Please run: cd src/plugins/swift/swift-bridge && bash build.sh');
89
- }
90
- /**
91
- * 轉換 CLI Bridge 節點為 AST 節點
92
- */
93
- convertCLINodeToASTNode(cliNode) {
94
- const swiftKind = this.nodeTypeToSwiftKind(cliNode.type);
95
- const node = {
96
- type: cliNode.type,
97
- range: cliNode.range,
98
- swiftKind,
99
- properties: cliNode.properties || {},
100
- children: (cliNode.children || []).map((child) => this.convertCLINodeToASTNode(child)),
101
- attributes: this.extractAttributes(cliNode),
102
- modifiers: this.extractModifiers(cliNode),
103
- source: cliNode.source || undefined
104
- };
105
- return node;
106
- }
107
- /**
108
- * 節點類型轉換為 SwiftNodeKind
109
- */
110
- nodeTypeToSwiftKind(nodeType) {
111
- // 對應 Swift CLI Bridge 的節點類型
112
- const mapping = {
113
- 'SourceFile': SwiftNodeKind.SourceFile,
114
- 'ClassDecl': SwiftNodeKind.Class,
115
- 'StructDecl': SwiftNodeKind.Struct,
116
- 'ProtocolDecl': SwiftNodeKind.Protocol,
117
- 'EnumDecl': SwiftNodeKind.Enum,
118
- 'FunctionDecl': SwiftNodeKind.Function,
119
- 'FuncDecl': SwiftNodeKind.Function,
120
- 'VariableDecl': SwiftNodeKind.Variable,
121
- 'ImportDecl': SwiftNodeKind.Import
122
- };
123
- return mapping[nodeType] || SwiftNodeKind.Unknown;
124
- }
125
- /**
126
- * 從節點提取屬性(@Published, @State 等)
127
- */
128
- extractAttributes(node) {
129
- // 從 properties 或子節點中提取屬性
130
- if (node.properties?.attributes) {
131
- return node.properties.attributes.split(',').map((a) => a.trim());
132
- }
133
- return [];
134
- }
135
- /**
136
- * 從節點提取修飾符(public, private 等)
137
- */
138
- extractModifiers(node) {
139
- // 從 properties 或子節點中提取修飾符
140
- if (node.properties?.modifiers) {
141
- return node.properties.modifiers.split(' ').filter((m) => m.length > 0);
142
- }
143
- return [];
144
- }
145
- /**
146
- * 提取符號
147
- */
148
- async extractSymbols(ast) {
149
- const typedAst = ast;
150
- return await this.symbolExtractor.extractSymbols(typedAst);
151
- }
152
- /**
153
- * 查找符號引用
154
- * 在當前 AST 中查找所有對指定符號的引用
155
- */
156
- async findReferences(ast, symbol) {
157
- const typedAst = ast;
158
- const references = [];
159
- // 遍歷 AST 尋找匹配的識別符
160
- this.findReferencesInNode(typedAst.root, symbol.name, typedAst.sourceFile, references, symbol);
161
- return references;
162
- }
163
- /**
164
- * 提取依賴關係
165
- */
166
- async extractDependencies(ast) {
167
- const typedAst = ast;
168
- return await this.dependencyAnalyzer.extractDependencies(typedAst);
169
- }
170
- /**
171
- * 重新命名符號
172
- */
173
- async rename(ast, position, newName) {
174
- this.validateRenameInput(newName);
175
- // 查找位置上的符號
176
- const symbol = await this.findSymbolAtPosition(ast, position);
177
- if (!symbol) {
178
- throw new Error('在指定位置找不到符號');
179
- }
180
- // 查找所有引用
181
- const references = await this.findReferences(ast, symbol);
182
- // 建立編輯操作
183
- const edits = [];
184
- for (const reference of references) {
185
- const edit = createCodeEdit(reference.location.filePath, reference.location.range, newName, 'rename');
186
- edits.push(edit);
187
- }
188
- return edits;
189
- }
190
- /**
191
- * 提取函式重構
192
- */
193
- async extractFunction(_ast, _selection) {
194
- throw new Error('提取函式重構尚未實作');
195
- }
196
- /**
197
- * 查找定義
198
- */
199
- async findDefinition(ast, position) {
200
- const symbol = await this.findSymbolAtPosition(ast, position);
201
- if (!symbol) {
202
- return null;
203
- }
204
- return createDefinition(symbol.location, this.symbolTypeToDefinitionKind(symbol.type));
205
- }
206
- /**
207
- * 查找使用位置
208
- */
209
- async findUsages(ast, symbol) {
210
- const references = await this.findReferences(ast, symbol);
211
- return references
212
- .filter(ref => ref.type === ReferenceType.Usage)
213
- .map(ref => createUsage(ref.location, 'reference'));
214
- }
215
- /**
216
- * 驗證插件狀態
217
- */
218
- async validate() {
219
- try {
220
- // 檢查 Swift CLI Bridge 是否可用
221
- const { stdout } = await execAsync(`command -v ${this.cliPath}`);
222
- if (!stdout) {
223
- return createValidationFailure([{
224
- code: 'SWIFT_CLI_UNAVAILABLE',
225
- message: 'Swift CLI Bridge 不可用',
226
- location: {
227
- filePath: '',
228
- range: {
229
- start: { line: 0, column: 0, offset: 0 },
230
- end: { line: 0, column: 0, offset: 0 }
231
- }
232
- }
233
- }]);
234
- }
235
- return createValidationSuccess();
236
- }
237
- catch (error) {
238
- // CLI Bridge 尚未實作,暫時返回成功
239
- return createValidationSuccess();
240
- }
241
- }
242
- /**
243
- * 清理資源
244
- */
245
- async dispose() {
246
- // 清理資源
247
- this.symbolExtractor = null;
248
- this.dependencyAnalyzer = null;
249
- }
250
- // ===== 9 個分析方法 =====
251
- /**
252
- * 檢測未使用的符號
253
- */
254
- async detectUnusedSymbols(ast, allSymbols) {
255
- const unusedCodes = [];
256
- for (const symbol of allSymbols) {
257
- const references = await this.findReferences(ast, symbol);
258
- // 只有定義但沒有使用的符號
259
- const usages = references.filter(ref => ref.type === ReferenceType.Usage);
260
- if (usages.length === 0) {
261
- unusedCodes.push({
262
- type: symbol.type === SymbolType.Function ? 'function' :
263
- symbol.type === SymbolType.Class ? 'class' : 'variable',
264
- name: symbol.name,
265
- location: {
266
- filePath: symbol.location.filePath,
267
- line: symbol.location.range.start.line,
268
- column: symbol.location.range.start.column
269
- },
270
- confidence: 0.9,
271
- reason: `符號 "${symbol.name}" 已定義但從未使用`
272
- });
273
- }
274
- }
275
- return unusedCodes;
276
- }
277
- /**
278
- * 分析程式碼複雜度
279
- */
280
- async analyzeComplexity(code, _ast) {
281
- const lines = code.split('\n');
282
- const complexity = this.calculateCyclomaticComplexity(code);
283
- return {
284
- cyclomaticComplexity: complexity,
285
- cognitiveComplexity: Math.floor(complexity * 1.2),
286
- evaluation: complexity > 20 ? 'very-complex' :
287
- complexity > 10 ? 'complex' :
288
- complexity > 5 ? 'moderate' : 'simple',
289
- functionCount: (code.match(/\bfunc\b/g) || []).length,
290
- averageComplexity: complexity / Math.max(1, (code.match(/\bfunc\b/g) || []).length),
291
- maxComplexity: complexity,
292
- maxComplexityFunction: undefined
293
- };
294
- }
295
- /**
296
- * 提取程式碼片段(用於重複代碼檢測)
297
- */
298
- async extractCodeFragments(code, filePath) {
299
- const fragments = [];
300
- const lines = code.split('\n');
301
- // 提取方法片段
302
- for (let i = 0; i < lines.length; i++) {
303
- const line = lines[i];
304
- if (/func\s+\w+/.test(line)) {
305
- let braceCount = (line.match(/{/g) || []).length;
306
- let endLine = i;
307
- for (let j = i + 1; j < lines.length && braceCount > 0; j++) {
308
- braceCount += (lines[j].match(/{/g) || []).length;
309
- braceCount -= (lines[j].match(/}/g) || []).length;
310
- endLine = j;
311
- }
312
- if (endLine > i && endLine - i + 1 >= 3) {
313
- const methodCode = lines.slice(i, endLine + 1).join('\n');
314
- const { createHash } = await import('crypto');
315
- fragments.push({
316
- type: 'method',
317
- code: methodCode,
318
- tokens: methodCode.split(/\s+/).filter(t => t.length > 0),
319
- location: { filePath, startLine: i + 1, endLine: endLine + 1 },
320
- hash: createHash('md5').update(methodCode).digest('hex')
321
- });
322
- }
323
- }
324
- }
325
- return fragments;
326
- }
327
- /**
328
- * 檢測樣板模式
329
- */
330
- async detectPatterns(_code, _ast) {
331
- // Swift 特定模式檢測(如 guard let、if let 等)
332
- return [];
333
- }
334
- /**
335
- * 檢查型別安全問題
336
- */
337
- async checkTypeSafety(code, ast) {
338
- const issues = [];
339
- const lines = code.split('\n');
340
- for (let i = 0; i < lines.length; i++) {
341
- const line = lines[i];
342
- // 檢測 as! 強制轉型
343
- if (/as!\s+/.test(line)) {
344
- issues.push({
345
- type: 'unsafe-cast',
346
- location: { filePath: ast.sourceFile, line: i + 1, column: 0 },
347
- message: '使用了 as! 強制轉型,可能導致運行時崩潰',
348
- severity: 'error'
349
- });
350
- }
351
- // 檢測強制解包 !
352
- if (/\w+!/.test(line) && !/if\s+let|guard\s+let/.test(line)) {
353
- issues.push({
354
- type: 'unsafe-cast',
355
- location: { filePath: ast.sourceFile, line: i + 1, column: 0 },
356
- message: '使用了強制解包 !,建議使用 if let 或 guard let',
357
- severity: 'warning'
358
- });
359
- }
360
- }
361
- return issues;
362
- }
363
- /**
364
- * 檢查錯誤處理問題
365
- */
366
- async checkErrorHandling(code, ast) {
367
- const issues = [];
368
- const lines = code.split('\n');
369
- for (let i = 0; i < lines.length; i++) {
370
- const line = lines[i];
371
- // 檢測空 catch 區塊
372
- if (/catch\s*\{?\s*\}/.test(line)) {
373
- issues.push({
374
- type: 'empty-catch',
375
- location: { filePath: ast.sourceFile, line: i + 1, column: 0 },
376
- message: '空的 catch 區塊,應該處理錯誤',
377
- severity: 'warning'
378
- });
379
- }
380
- // 檢測 try? 靜默錯誤
381
- if (/try\?/.test(line)) {
382
- issues.push({
383
- type: 'silent-error',
384
- location: { filePath: ast.sourceFile, line: i + 1, column: 0 },
385
- message: '使用 try? 靜默錯誤,建議使用 do-catch 處理',
386
- severity: 'warning'
387
- });
388
- }
389
- }
390
- return issues;
391
- }
392
- /**
393
- * 檢查安全性問題
394
- */
395
- async checkSecurity(code, ast) {
396
- const issues = [];
397
- const lines = code.split('\n');
398
- for (let i = 0; i < lines.length; i++) {
399
- const line = lines[i];
400
- // 檢測硬編碼密碼
401
- if (/(password|apiKey|token|secret)\s*=\s*"[^"]{3,}"/.test(line) &&
402
- !/ProcessInfo/.test(line)) {
403
- issues.push({
404
- type: 'hardcoded-secret',
405
- location: { filePath: ast.sourceFile, line: i + 1, column: 0 },
406
- message: '硬編碼的密碼或密鑰,應使用 Keychain 或環境變數',
407
- severity: 'critical'
408
- });
409
- }
410
- }
411
- return issues;
412
- }
413
- /**
414
- * 檢查命名規範問題
415
- */
416
- async checkNamingConventions(symbols, filePath) {
417
- const issues = [];
418
- for (const symbol of symbols) {
419
- // Swift 規範:變數和函式用 camelCase
420
- if (symbol.type === SymbolType.Variable ||
421
- symbol.type === SymbolType.Function) {
422
- if (/^[A-Z]/.test(symbol.name)) {
423
- issues.push({
424
- type: 'invalid-naming',
425
- symbolName: symbol.name,
426
- symbolType: symbol.type,
427
- location: {
428
- filePath,
429
- line: symbol.location.range.start.line,
430
- column: symbol.location.range.start.column
431
- },
432
- message: `變數/函式 "${symbol.name}" 應使用 camelCase 命名`
433
- });
434
- }
435
- }
436
- // Swift 規範:類別和協定用 PascalCase
437
- if (symbol.type === SymbolType.Class ||
438
- symbol.type === SymbolType.Interface) {
439
- if (!/^[A-Z]/.test(symbol.name)) {
440
- issues.push({
441
- type: 'invalid-naming',
442
- symbolName: symbol.name,
443
- symbolType: symbol.type,
444
- location: {
445
- filePath,
446
- line: symbol.location.range.start.line,
447
- column: symbol.location.range.start.column
448
- },
449
- message: `類別/協定 "${symbol.name}" 應使用 PascalCase 命名`
450
- });
451
- }
452
- }
453
- // 檢測底線開頭變數(Swift 不建議)
454
- if (symbol.name.startsWith('_')) {
455
- issues.push({
456
- type: 'invalid-naming',
457
- symbolName: symbol.name,
458
- symbolType: symbol.type,
459
- location: {
460
- filePath,
461
- line: symbol.location.range.start.line,
462
- column: symbol.location.range.start.column
463
- },
464
- message: `符號 "${symbol.name}" 以底線開頭,違反 Swift 命名規範`
465
- });
466
- }
467
- }
468
- return issues;
469
- }
470
- /**
471
- * 判斷檔案是否為測試檔案
472
- */
473
- isTestFile(filePath) {
474
- return /Tests\.swift$/.test(filePath) || filePath.includes('/Tests/');
475
- }
476
- // ===== 可選方法 =====
477
- /**
478
- * 獲取 Swift 特定的排除模式
479
- */
480
- getDefaultExcludePatterns() {
481
- return [
482
- '.build/**',
483
- 'DerivedData/**',
484
- '**/*Tests.swift',
485
- '**/Tests/**',
486
- '*.xcodeproj/**',
487
- '*.xcworkspace/**',
488
- 'Pods/**',
489
- 'Carthage/**'
490
- ];
491
- }
492
- /**
493
- * 判斷是否應該忽略特定檔案
494
- */
495
- shouldIgnoreFile(filePath) {
496
- const patterns = this.getDefaultExcludePatterns();
497
- return patterns.some(pattern => {
498
- const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'));
499
- return regex.test(filePath);
500
- });
501
- }
502
- /**
503
- * 判斷符號是否為抽象宣告
504
- */
505
- isAbstractDeclaration(symbol) {
506
- const abstractTypes = [
507
- SymbolType.Class,
508
- SymbolType.Struct,
509
- SymbolType.Protocol,
510
- SymbolType.Interface, // 保留以兼容舊程式碼
511
- SymbolType.Type, // typealias
512
- SymbolType.Enum
513
- ];
514
- return abstractTypes.includes(symbol.type);
515
- }
516
- // ===== 私有輔助方法 =====
517
- validateInput(code, filePath) {
518
- if (!code.trim()) {
519
- throw new Error('程式碼內容不能為空');
520
- }
521
- if (!filePath.trim()) {
522
- throw new Error('檔案路徑不能為空');
523
- }
524
- }
525
- validateRenameInput(newName) {
526
- if (!newName.trim()) {
527
- throw new Error('新名稱不能為空');
528
- }
529
- if (!isValidIdentifier(newName)) {
530
- throw new Error('新名稱必須是有效的 Swift 識別符');
531
- }
532
- }
533
- /**
534
- * 模擬解析(實際需要呼叫 CLI Bridge)
535
- */
536
- mockParse(code) {
537
- return createSwiftASTNode(SwiftNodeKind.SourceFile, code);
538
- }
539
- /**
540
- * 在節點中遞歸查找引用
541
- */
542
- findReferencesInNode(node, symbolName, filePath, references, symbol, isInImport = false) {
543
- // 檢查是否進入 import 節點
544
- const currentIsInImport = isInImport || node.swiftKind === SwiftNodeKind.Import;
545
- // 檢查當前節點名稱
546
- const nodeName = getNodeName(node);
547
- if (nodeName === symbolName) {
548
- // 使用節點的 range 資訊(如果有)
549
- const range = node.range || {
550
- start: { line: 1, column: 1, offset: 0 },
551
- end: { line: 1, column: 1, offset: 0 }
552
- };
553
- references.push({
554
- symbol,
555
- location: {
556
- filePath,
557
- range
558
- },
559
- type: this.getReferenceType(node, symbol, currentIsInImport)
560
- });
561
- }
562
- // 檢查節點的 source code 是否包含符號(用於捕捉類型引用等)
563
- // 例如:`let user: User` 中的 User 可能不是獨立節點
564
- if (node.source) {
565
- const regex = new RegExp(`\\b${this.escapeRegex(symbolName)}\\b`, 'g');
566
- let match;
567
- while ((match = regex.exec(node.source)) !== null) {
568
- // 確保不重複添加(如果已經透過 nodeName 匹配到)
569
- if (nodeName !== symbolName) {
570
- const range = node.range || {
571
- start: { line: 1, column: match.index + 1, offset: 0 },
572
- end: { line: 1, column: match.index + symbolName.length + 1, offset: 0 }
573
- };
574
- references.push({
575
- symbol,
576
- location: {
577
- filePath,
578
- range
579
- },
580
- type: this.getReferenceType(node, symbol, currentIsInImport)
581
- });
582
- }
583
- }
584
- }
585
- // 遞歸處理子節點
586
- if (node.children) {
587
- for (const child of node.children) {
588
- this.findReferencesInNode(child, symbolName, filePath, references, symbol, currentIsInImport);
589
- }
590
- }
591
- }
592
- /**
593
- * 判斷引用類型
594
- */
595
- getReferenceType(node, symbol, isInImport) {
596
- // 檢查是否為符號定義位置
597
- if (node.range
598
- && node.range.start.line === symbol.location.range.start.line
599
- && node.range.start.column === symbol.location.range.start.column) {
600
- return ReferenceType.Definition;
601
- }
602
- // 檢查是否在 import 語句內
603
- if (isInImport) {
604
- return ReferenceType.Import;
605
- }
606
- return ReferenceType.Usage;
607
- }
608
- /**
609
- * 逸出正則表達式特殊字符
610
- */
611
- escapeRegex(text) {
612
- return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
613
- }
614
- /**
615
- * 查找指定位置的符號
616
- */
617
- async findSymbolAtPosition(ast, position) {
618
- const symbols = await this.extractSymbols(ast);
619
- // 簡化實作:返回第一個符號
620
- return symbols[0] || null;
621
- }
622
- /**
623
- * 計算圈複雜度
624
- */
625
- calculateCyclomaticComplexity(code) {
626
- let complexity = 1;
627
- // 計算控制流關鍵字(單詞類)
628
- const wordKeywords = ['if', 'else', 'for', 'while', 'guard', 'switch', 'case'];
629
- for (const keyword of wordKeywords) {
630
- const regex = new RegExp(`\\b${keyword}\\b`, 'g');
631
- const matches = code.match(regex);
632
- if (matches) {
633
- complexity += matches.length;
634
- }
635
- }
636
- // 計算邏輯運算子(需要轉義特殊字元)
637
- const operators = [
638
- { pattern: '&&', name: 'logical-and' },
639
- { pattern: '||', name: 'logical-or' },
640
- { pattern: '?', name: 'ternary' }
641
- ];
642
- for (const op of operators) {
643
- const escapedPattern = op.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
644
- const regex = new RegExp(escapedPattern, 'g');
645
- const matches = code.match(regex);
646
- if (matches) {
647
- complexity += matches.length;
648
- }
649
- }
650
- return complexity;
651
- }
652
- /**
653
- * SymbolType 轉換為 DefinitionKind
654
- */
655
- symbolTypeToDefinitionKind(symbolType) {
656
- switch (symbolType) {
657
- case SymbolType.Class:
658
- return 'class';
659
- case SymbolType.Struct:
660
- return 'struct';
661
- case SymbolType.Protocol:
662
- return 'protocol';
663
- case SymbolType.Interface:
664
- return 'interface';
665
- case SymbolType.Function:
666
- return 'function';
667
- case SymbolType.Variable:
668
- return 'variable';
669
- case SymbolType.Property:
670
- return 'property';
671
- case SymbolType.Constant:
672
- return 'constant';
673
- case SymbolType.Type:
674
- return 'type';
675
- case SymbolType.Enum:
676
- return 'enum';
677
- default:
678
- return 'variable';
679
- }
680
- }
681
- }
682
- /**
683
- * 建立 Swift Parser 實例
684
- */
685
- export function createSwiftParser(cliPath) {
686
- return new SwiftParser(cliPath);
687
- }
688
- //# sourceMappingURL=parser.js.map