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.
- package/dist/core/change-signature/change-signature-service.d.ts.map +1 -1
- package/dist/core/change-signature/change-signature-service.js +21 -14
- package/dist/core/change-signature/change-signature-service.js.map +1 -1
- package/dist/core/change-signature/signature-parser.d.ts +0 -16
- package/dist/core/change-signature/signature-parser.d.ts.map +1 -1
- package/dist/core/change-signature/signature-parser.js +28 -111
- package/dist/core/change-signature/signature-parser.js.map +1 -1
- package/dist/core/change-signature/types.d.ts +2 -0
- package/dist/core/change-signature/types.d.ts.map +1 -1
- package/dist/core/change-signature/types.js +2 -0
- package/dist/core/change-signature/types.js.map +1 -1
- package/dist/core/dead-code/dead-code-detector.d.ts +63 -0
- package/dist/core/dead-code/dead-code-detector.d.ts.map +1 -0
- package/dist/core/dead-code/dead-code-detector.js +256 -0
- package/dist/core/dead-code/dead-code-detector.js.map +1 -0
- package/dist/core/dead-code/dead-code-remover.d.ts +133 -0
- package/dist/core/dead-code/dead-code-remover.d.ts.map +1 -0
- package/dist/core/dead-code/dead-code-remover.js +803 -0
- package/dist/core/dead-code/dead-code-remover.js.map +1 -0
- package/dist/core/dead-code/index.d.ts +9 -0
- package/dist/core/dead-code/index.d.ts.map +1 -0
- package/dist/core/dead-code/index.js +8 -0
- package/dist/core/dead-code/index.js.map +1 -0
- package/dist/core/dead-code/types.d.ts +185 -0
- package/dist/core/dead-code/types.d.ts.map +1 -0
- package/dist/core/dead-code/types.js +53 -0
- package/dist/core/dead-code/types.js.map +1 -0
- package/dist/core/dependency/dependency-analyzer.d.ts.map +1 -1
- package/dist/core/dependency/dependency-analyzer.js +2 -15
- package/dist/core/dependency/dependency-analyzer.js.map +1 -1
- package/dist/core/indexing/index-engine.d.ts.map +1 -1
- package/dist/core/indexing/index-engine.js +0 -12
- package/dist/core/indexing/index-engine.js.map +1 -1
- package/dist/core/indexing/types.d.ts +1 -1
- package/dist/core/indexing/types.js +1 -1
- package/dist/core/indexing/types.js.map +1 -1
- package/dist/core/move-file/import-resolver.d.ts +1 -1
- package/dist/core/move-file/import-resolver.d.ts.map +1 -1
- package/dist/core/move-file/import-resolver.js +1 -9
- package/dist/core/move-file/import-resolver.js.map +1 -1
- package/dist/core/move-file/move-service.d.ts +8 -0
- package/dist/core/move-file/move-service.d.ts.map +1 -1
- package/dist/core/move-file/move-service.js +102 -21
- package/dist/core/move-file/move-service.js.map +1 -1
- package/dist/core/move-member/member-extractor.d.ts +0 -8
- package/dist/core/move-member/member-extractor.d.ts.map +1 -1
- package/dist/core/move-member/member-extractor.js +0 -49
- package/dist/core/move-member/member-extractor.js.map +1 -1
- package/dist/core/move-member/move-member-service.js +1 -1
- package/dist/core/move-member/move-member-service.js.map +1 -1
- package/dist/core/shared/symbol-finder.d.ts +4 -0
- package/dist/core/shared/symbol-finder.d.ts.map +1 -1
- package/dist/core/shared/symbol-finder.js +41 -0
- package/dist/core/shared/symbol-finder.js.map +1 -1
- package/dist/core/snapshot/index.d.ts +2 -0
- package/dist/core/snapshot/index.d.ts.map +1 -1
- package/dist/core/snapshot/index.js +1 -0
- package/dist/core/snapshot/index.js.map +1 -1
- package/dist/core/snapshot/snapshot-cache.d.ts +103 -0
- package/dist/core/snapshot/snapshot-cache.d.ts.map +1 -0
- package/dist/core/snapshot/snapshot-cache.js +249 -0
- package/dist/core/snapshot/snapshot-cache.js.map +1 -0
- package/dist/core/snapshot/snapshot-generator.d.ts +12 -0
- package/dist/core/snapshot/snapshot-generator.d.ts.map +1 -1
- package/dist/core/snapshot/snapshot-generator.js +61 -3
- package/dist/core/snapshot/snapshot-generator.js.map +1 -1
- package/dist/infrastructure/formatters/index.d.ts +1 -1
- package/dist/infrastructure/formatters/index.d.ts.map +1 -1
- package/dist/infrastructure/formatters/index.js.map +1 -1
- package/dist/infrastructure/formatters/preview-converter.d.ts +62 -0
- package/dist/infrastructure/formatters/preview-converter.d.ts.map +1 -1
- package/dist/infrastructure/formatters/preview-converter.js +82 -0
- package/dist/infrastructure/formatters/preview-converter.js.map +1 -1
- package/dist/infrastructure/formatters/query-formatter.d.ts +16 -0
- package/dist/infrastructure/formatters/query-formatter.d.ts.map +1 -1
- package/dist/infrastructure/formatters/query-formatter.js +155 -3
- package/dist/infrastructure/formatters/query-formatter.js.map +1 -1
- package/dist/infrastructure/formatters/query-types.d.ts +73 -2
- package/dist/infrastructure/formatters/query-types.d.ts.map +1 -1
- package/dist/infrastructure/formatters/types.d.ts +2 -1
- package/dist/infrastructure/formatters/types.d.ts.map +1 -1
- package/dist/infrastructure/formatters/types.js +1 -0
- package/dist/infrastructure/formatters/types.js.map +1 -1
- package/dist/interfaces/cli/cli.d.ts.map +1 -1
- package/dist/interfaces/cli/cli.js +2 -29
- package/dist/interfaces/cli/cli.js.map +1 -1
- package/dist/interfaces/cli/commands/change-signature.command.js +3 -3
- package/dist/interfaces/cli/commands/change-signature.command.js.map +1 -1
- package/dist/interfaces/cli/commands/deadcode.command.d.ts +11 -0
- package/dist/interfaces/cli/commands/deadcode.command.d.ts.map +1 -0
- package/dist/interfaces/cli/commands/deadcode.command.js +164 -0
- package/dist/interfaces/cli/commands/deadcode.command.js.map +1 -0
- package/dist/interfaces/cli/commands/index.d.ts +1 -0
- package/dist/interfaces/cli/commands/index.d.ts.map +1 -1
- package/dist/interfaces/cli/commands/index.js +1 -0
- package/dist/interfaces/cli/commands/index.js.map +1 -1
- package/dist/interfaces/cli/commands/move.command.js +1 -1
- package/dist/interfaces/cli/commands/move.command.js.map +1 -1
- package/dist/interfaces/cli/commands/rename.command.js +2 -3
- package/dist/interfaces/cli/commands/rename.command.js.map +1 -1
- package/dist/interfaces/cli/commands/snapshot.command.d.ts.map +1 -1
- package/dist/interfaces/cli/commands/snapshot.command.js +20 -0
- package/dist/interfaces/cli/commands/snapshot.command.js.map +1 -1
- package/package.json +10 -12
- package/dist/plugins/python/dependency-analyzer.d.ts +0 -86
- package/dist/plugins/python/dependency-analyzer.d.ts.map +0 -1
- package/dist/plugins/python/dependency-analyzer.js +0 -252
- package/dist/plugins/python/dependency-analyzer.js.map +0 -1
- package/dist/plugins/python/index.d.ts +0 -9
- package/dist/plugins/python/index.d.ts.map +0 -1
- package/dist/plugins/python/index.js +0 -14
- package/dist/plugins/python/index.js.map +0 -1
- package/dist/plugins/python/parser.d.ts +0 -117
- package/dist/plugins/python/parser.d.ts.map +0 -1
- package/dist/plugins/python/parser.js +0 -414
- package/dist/plugins/python/parser.js.map +0 -1
- package/dist/plugins/python/symbol-extractor.d.ts +0 -108
- package/dist/plugins/python/symbol-extractor.d.ts.map +0 -1
- package/dist/plugins/python/symbol-extractor.js +0 -389
- package/dist/plugins/python/symbol-extractor.js.map +0 -1
- package/dist/plugins/python/tree-sitter-bridge.d.ts +0 -57
- package/dist/plugins/python/tree-sitter-bridge.d.ts.map +0 -1
- package/dist/plugins/python/tree-sitter-bridge.js +0 -269
- package/dist/plugins/python/tree-sitter-bridge.js.map +0 -1
- package/dist/plugins/python/types.d.ts +0 -191
- package/dist/plugins/python/types.d.ts.map +0 -1
- package/dist/plugins/python/types.js +0 -279
- package/dist/plugins/python/types.js.map +0 -1
- package/dist/plugins/swift/dependency-analyzer.d.ts +0 -33
- package/dist/plugins/swift/dependency-analyzer.d.ts.map +0 -1
- package/dist/plugins/swift/dependency-analyzer.js +0 -95
- package/dist/plugins/swift/dependency-analyzer.js.map +0 -1
- package/dist/plugins/swift/index.d.ts +0 -14
- package/dist/plugins/swift/index.d.ts.map +0 -1
- package/dist/plugins/swift/index.js +0 -19
- package/dist/plugins/swift/index.js.map +0 -1
- package/dist/plugins/swift/parser.d.ts +0 -164
- package/dist/plugins/swift/parser.d.ts.map +0 -1
- package/dist/plugins/swift/parser.js +0 -688
- package/dist/plugins/swift/parser.js.map +0 -1
- package/dist/plugins/swift/swift-bridge/swift-parser +0 -0
- package/dist/plugins/swift/symbol-extractor.d.ts +0 -46
- package/dist/plugins/swift/symbol-extractor.d.ts.map +0 -1
- package/dist/plugins/swift/symbol-extractor.js +0 -187
- package/dist/plugins/swift/symbol-extractor.js.map +0 -1
- package/dist/plugins/swift/types.d.ts +0 -149
- package/dist/plugins/swift/types.d.ts.map +0 -1
- package/dist/plugins/swift/types.js +0 -251
- 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
|