vcode-analysis 0.1.0__py3-none-any.whl
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.
- analyzers/__init__.py +24 -0
- analyzers/architecture.py +510 -0
- analyzers/code_review.py +150 -0
- analyzers/directory.py +867 -0
- analyzers/documentation.py +209 -0
- analyzers/security.py +671 -0
- core/__init__.py +17 -0
- core/analyzer.py +207 -0
- core/config.py +166 -0
- core/git_handler.py +718 -0
- core/llm_client.py +186 -0
- parsers/__init__.py +209 -0
- parsers/c/__init__.py +57 -0
- parsers/c/ast_parser.py +424 -0
- parsers/c/models.py +211 -0
- parsers/c/patterns.py +143 -0
- parsers/c/regex_parser.py +594 -0
- parsers/c_parser.py +275 -0
- parsers/java_parser.py +430 -0
- parsers/javascript_parser.py +587 -0
- parsers/kotlin/__init__.py +61 -0
- parsers/kotlin/ast_parser.py +591 -0
- parsers/kotlin/models.py +274 -0
- parsers/kotlin/patterns.py +146 -0
- parsers/kotlin/regex_parser.py +906 -0
- parsers/kotlin_parser.py +279 -0
- parsers/python_parser.py +429 -0
- parsers/typescript_parser.py +381 -0
- vcode_analysis-0.1.0.dist-info/METADATA +246 -0
- vcode_analysis-0.1.0.dist-info/RECORD +34 -0
- vcode_analysis-0.1.0.dist-info/WHEEL +5 -0
- vcode_analysis-0.1.0.dist-info/entry_points.txt +2 -0
- vcode_analysis-0.1.0.dist-info/licenses/LICENSE +21 -0
- vcode_analysis-0.1.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
"""JavaScript AST 解析器
|
|
2
|
+
|
|
3
|
+
使用 pyjsparser (esprima 的 Python 移植) 解析 JavaScript 代码结构。
|
|
4
|
+
支持 ES5 语法,ES6+ 语法使用正则表达式回退解析。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from pyjsparser import PyJsParser
|
|
14
|
+
JS_PARSER_AVAILABLE = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
JS_PARSER_AVAILABLE = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ES6+ 语法的正则表达式模式(用于回退解析)
|
|
20
|
+
ES6_PATTERNS = {
|
|
21
|
+
"import": r'import\s+(?:\{[^}]*\}|\w+)\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
22
|
+
"import_default": r'import\s+(\w+)\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
23
|
+
"import_all": r'import\s+\*\s+as\s+(\w+)\s+from\s+[\'"]([^\'"]+)[\'"]',
|
|
24
|
+
"require": r'(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)',
|
|
25
|
+
"export_default": r'export\s+default\s+(\w+)',
|
|
26
|
+
"export_named": r'export\s+\{([^}]+)\}',
|
|
27
|
+
"class": r'class\s+(\w+)(?:\s+extends\s+(\w+))?\s*\{',
|
|
28
|
+
"function": r'(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)',
|
|
29
|
+
"arrow_function": r'(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>',
|
|
30
|
+
"variable": r'(?:const|let|var)\s+(\w+)\s*=',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class JSFunctionInfo:
|
|
36
|
+
"""JavaScript 函数信息"""
|
|
37
|
+
name: str
|
|
38
|
+
line_start: int
|
|
39
|
+
line_end: int
|
|
40
|
+
params: list[str]
|
|
41
|
+
is_async: bool
|
|
42
|
+
is_generator: bool
|
|
43
|
+
is_arrow: bool # 箭头函数
|
|
44
|
+
is_method: bool = False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class JSClassInfo:
|
|
49
|
+
"""JavaScript 类信息"""
|
|
50
|
+
name: str
|
|
51
|
+
line_start: int
|
|
52
|
+
line_end: int
|
|
53
|
+
methods: list[JSFunctionInfo]
|
|
54
|
+
properties: list[str]
|
|
55
|
+
extends: Optional[str] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class JSImportInfo:
|
|
60
|
+
"""JavaScript 导入信息"""
|
|
61
|
+
source: str # 模块路径
|
|
62
|
+
specifiers: list[str] # 导入的标识符
|
|
63
|
+
line: int
|
|
64
|
+
import_type: str # import, require, dynamic
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class JSExportInfo:
|
|
69
|
+
"""JavaScript 导出信息"""
|
|
70
|
+
name: str
|
|
71
|
+
line: int
|
|
72
|
+
export_type: str # default, named
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class JSVariableInfo:
|
|
77
|
+
"""JavaScript 变量信息"""
|
|
78
|
+
name: str
|
|
79
|
+
line: int
|
|
80
|
+
kind: str # var, let, const
|
|
81
|
+
is_destructured: bool = False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class JavaScriptASTResult:
|
|
86
|
+
"""JavaScript AST 解析结果"""
|
|
87
|
+
file_path: str
|
|
88
|
+
success: bool
|
|
89
|
+
error: Optional[str] = None
|
|
90
|
+
|
|
91
|
+
# 结构信息
|
|
92
|
+
imports: list[JSImportInfo] = field(default_factory=list)
|
|
93
|
+
exports: list[JSExportInfo] = field(default_factory=list)
|
|
94
|
+
classes: list[JSClassInfo] = field(default_factory=list)
|
|
95
|
+
functions: list[JSFunctionInfo] = field(default_factory=list)
|
|
96
|
+
variables: list[JSVariableInfo] = field(default_factory=list)
|
|
97
|
+
|
|
98
|
+
# 统计信息
|
|
99
|
+
total_lines: int = 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class JavaScriptASTParser:
|
|
103
|
+
"""JavaScript AST 解析器"""
|
|
104
|
+
|
|
105
|
+
def __init__(self):
|
|
106
|
+
if not JS_PARSER_AVAILABLE:
|
|
107
|
+
raise ImportError("请安装 pyjsparser: pip install pyjsparser")
|
|
108
|
+
|
|
109
|
+
def parse_file(self, file_path: str) -> JavaScriptASTResult:
|
|
110
|
+
"""解析 JavaScript 文件"""
|
|
111
|
+
path = Path(file_path)
|
|
112
|
+
|
|
113
|
+
if not path.exists():
|
|
114
|
+
return JavaScriptASTResult(
|
|
115
|
+
file_path=file_path,
|
|
116
|
+
success=False,
|
|
117
|
+
error=f"文件不存在: {file_path}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if path.suffix not in (".js", ".jsx", ".mjs", ".cjs"):
|
|
121
|
+
return JavaScriptASTResult(
|
|
122
|
+
file_path=file_path,
|
|
123
|
+
success=False,
|
|
124
|
+
error=f"不是 JavaScript 文件: {file_path}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
content = path.read_text(encoding="utf-8")
|
|
129
|
+
return self.parse_code(content, file_path)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
return JavaScriptASTResult(
|
|
132
|
+
file_path=file_path,
|
|
133
|
+
success=False,
|
|
134
|
+
error=str(e)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def parse_code(self, code: str, file_path: str = "<string>") -> JavaScriptASTResult:
|
|
138
|
+
"""解析 JavaScript 代码字符串"""
|
|
139
|
+
result = JavaScriptASTResult(
|
|
140
|
+
file_path=file_path,
|
|
141
|
+
success=True,
|
|
142
|
+
total_lines=len(code.splitlines())
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# 检测是否包含 ES6+ 语法
|
|
146
|
+
has_es6 = self._detect_es6_syntax(code)
|
|
147
|
+
|
|
148
|
+
if has_es6:
|
|
149
|
+
# ES6+ 语法,使用正则表达式回退解析
|
|
150
|
+
self._parse_with_regex(code, result)
|
|
151
|
+
else:
|
|
152
|
+
# ES5 语法,使用 AST 解析器
|
|
153
|
+
try:
|
|
154
|
+
parser = PyJsParser()
|
|
155
|
+
tree = parser.parse(code)
|
|
156
|
+
self._traverse(tree, result)
|
|
157
|
+
except Exception:
|
|
158
|
+
# AST 解析失败,回退到正则表达式
|
|
159
|
+
self._parse_with_regex(code, result)
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
def _detect_es6_syntax(self, code: str) -> bool:
|
|
164
|
+
"""检测是否包含 ES6+ 语法"""
|
|
165
|
+
es6_indicators = [
|
|
166
|
+
r'\bimport\s+', # import 语句
|
|
167
|
+
r'\bexport\s+', # export 语句
|
|
168
|
+
r'\bclass\s+\w+', # class 声明
|
|
169
|
+
r'=>', # 箭头函数
|
|
170
|
+
r'`[^`]*\$\{', # 模板字符串
|
|
171
|
+
r'\blet\s+', # let 声明
|
|
172
|
+
r'\bconst\s+', # const 声明
|
|
173
|
+
r'\basync\s+', # async 关键字
|
|
174
|
+
r'\bawait\s+', # await 关键字
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
for pattern in es6_indicators:
|
|
178
|
+
if re.search(pattern, code):
|
|
179
|
+
return True
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
def _parse_with_regex(self, code: str, result: JavaScriptASTResult):
|
|
183
|
+
"""使用正则表达式解析(ES6+ 回退方案)"""
|
|
184
|
+
lines = code.split('\n')
|
|
185
|
+
|
|
186
|
+
# 解析 import 语句
|
|
187
|
+
for match in re.finditer(ES6_PATTERNS["import_all"], code):
|
|
188
|
+
result.imports.append(JSImportInfo(
|
|
189
|
+
source=match.group(2),
|
|
190
|
+
specifiers=[f"* as {match.group(1)}"],
|
|
191
|
+
line=code[:match.start()].count('\n') + 1,
|
|
192
|
+
import_type="import"
|
|
193
|
+
))
|
|
194
|
+
|
|
195
|
+
for match in re.finditer(ES6_PATTERNS["import_default"], code):
|
|
196
|
+
if '* as' not in match.group(0):
|
|
197
|
+
result.imports.append(JSImportInfo(
|
|
198
|
+
source=match.group(2),
|
|
199
|
+
specifiers=[match.group(1)],
|
|
200
|
+
line=code[:match.start()].count('\n') + 1,
|
|
201
|
+
import_type="import"
|
|
202
|
+
))
|
|
203
|
+
|
|
204
|
+
for match in re.finditer(ES6_PATTERNS["import"], code):
|
|
205
|
+
if 'import *' not in match.group(0):
|
|
206
|
+
result.imports.append(JSImportInfo(
|
|
207
|
+
source=match.group(1),
|
|
208
|
+
specifiers=[],
|
|
209
|
+
line=code[:match.start()].count('\n') + 1,
|
|
210
|
+
import_type="import"
|
|
211
|
+
))
|
|
212
|
+
|
|
213
|
+
# 解析 require 语句
|
|
214
|
+
for match in re.finditer(ES6_PATTERNS["require"], code):
|
|
215
|
+
result.imports.append(JSImportInfo(
|
|
216
|
+
source=match.group(2),
|
|
217
|
+
specifiers=[match.group(1)],
|
|
218
|
+
line=code[:match.start()].count('\n') + 1,
|
|
219
|
+
import_type="require"
|
|
220
|
+
))
|
|
221
|
+
|
|
222
|
+
# 解析 class 声明
|
|
223
|
+
for match in re.finditer(ES6_PATTERNS["class"], code):
|
|
224
|
+
class_name = match.group(1)
|
|
225
|
+
extends = match.group(2) if match.lastindex >= 2 else None
|
|
226
|
+
line = code[:match.start()].count('\n') + 1
|
|
227
|
+
|
|
228
|
+
result.classes.append(JSClassInfo(
|
|
229
|
+
name=class_name,
|
|
230
|
+
line_start=line,
|
|
231
|
+
line_end=line, # 简化处理
|
|
232
|
+
methods=[],
|
|
233
|
+
properties=[],
|
|
234
|
+
extends=extends
|
|
235
|
+
))
|
|
236
|
+
|
|
237
|
+
# 解析 function 声明
|
|
238
|
+
for match in re.finditer(ES6_PATTERNS["function"], code):
|
|
239
|
+
full_match = match.group(0)
|
|
240
|
+
is_async = 'async' in full_match
|
|
241
|
+
# 只取函数名和参数
|
|
242
|
+
groups = match.groups()
|
|
243
|
+
if groups:
|
|
244
|
+
func_name = groups[0] if groups[0] else "anonymous"
|
|
245
|
+
params = groups[1] if len(groups) > 1 and groups[1] else ""
|
|
246
|
+
else:
|
|
247
|
+
continue
|
|
248
|
+
|
|
249
|
+
line = code[:match.start()].count('\n') + 1
|
|
250
|
+
|
|
251
|
+
result.functions.append(JSFunctionInfo(
|
|
252
|
+
name=func_name,
|
|
253
|
+
line_start=line,
|
|
254
|
+
line_end=line,
|
|
255
|
+
params=[p.strip() for p in params.split(',') if p.strip()],
|
|
256
|
+
is_async=is_async,
|
|
257
|
+
is_generator=False,
|
|
258
|
+
is_arrow=False,
|
|
259
|
+
is_method=False
|
|
260
|
+
))
|
|
261
|
+
|
|
262
|
+
# 解析箭头函数
|
|
263
|
+
for match in re.finditer(ES6_PATTERNS["arrow_function"], code):
|
|
264
|
+
line = code[:match.start()].count('\n') + 1
|
|
265
|
+
result.functions.append(JSFunctionInfo(
|
|
266
|
+
name=match.group(1),
|
|
267
|
+
line_start=line,
|
|
268
|
+
line_end=line,
|
|
269
|
+
params=[],
|
|
270
|
+
is_async='async' in match.group(0),
|
|
271
|
+
is_generator=False,
|
|
272
|
+
is_arrow=True,
|
|
273
|
+
is_method=False
|
|
274
|
+
))
|
|
275
|
+
|
|
276
|
+
# 解析 export 语句
|
|
277
|
+
for match in re.finditer(ES6_PATTERNS["export_default"], code):
|
|
278
|
+
line = code[:match.start()].count('\n') + 1
|
|
279
|
+
result.exports.append(JSExportInfo(
|
|
280
|
+
name=match.group(1),
|
|
281
|
+
line=line,
|
|
282
|
+
export_type="default"
|
|
283
|
+
))
|
|
284
|
+
|
|
285
|
+
for match in re.finditer(ES6_PATTERNS["export_named"], code):
|
|
286
|
+
line = code[:match.start()].count('\n') + 1
|
|
287
|
+
names = [n.strip() for n in match.group(1).split(',')]
|
|
288
|
+
for name in names:
|
|
289
|
+
result.exports.append(JSExportInfo(
|
|
290
|
+
name=name,
|
|
291
|
+
line=line,
|
|
292
|
+
export_type="named"
|
|
293
|
+
))
|
|
294
|
+
|
|
295
|
+
# 解析变量声明
|
|
296
|
+
for match in re.finditer(ES6_PATTERNS["variable"], code):
|
|
297
|
+
line = code[:match.start()].count('\n') + 1
|
|
298
|
+
kind = 'const' if 'const' in match.group(0) else ('let' if 'let' in match.group(0) else 'var')
|
|
299
|
+
result.variables.append(JSVariableInfo(
|
|
300
|
+
name=match.group(1),
|
|
301
|
+
line=line,
|
|
302
|
+
kind=kind,
|
|
303
|
+
is_destructured=False
|
|
304
|
+
))
|
|
305
|
+
|
|
306
|
+
def _traverse(self, node: dict, result: JavaScriptASTResult, in_class: bool = False):
|
|
307
|
+
"""遍历 AST 节点"""
|
|
308
|
+
if not isinstance(node, dict):
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
node_type = node.get("type")
|
|
312
|
+
|
|
313
|
+
# 处理不同类型的节点
|
|
314
|
+
if node_type == "FunctionDeclaration":
|
|
315
|
+
func = self._parse_function(node, is_method=False)
|
|
316
|
+
if func:
|
|
317
|
+
result.functions.append(func)
|
|
318
|
+
|
|
319
|
+
elif node_type == "ClassDeclaration":
|
|
320
|
+
cls = self._parse_class(node)
|
|
321
|
+
if cls:
|
|
322
|
+
result.classes.append(cls)
|
|
323
|
+
|
|
324
|
+
elif node_type == "VariableDeclaration":
|
|
325
|
+
for decl in node.get("declarations", []):
|
|
326
|
+
var = self._parse_variable(decl, node.get("kind", "var"))
|
|
327
|
+
if var:
|
|
328
|
+
result.variables.append(var)
|
|
329
|
+
|
|
330
|
+
elif node_type == "ImportDeclaration":
|
|
331
|
+
imp = self._parse_import(node)
|
|
332
|
+
if imp:
|
|
333
|
+
result.imports.append(imp)
|
|
334
|
+
|
|
335
|
+
elif node_type == "ExportDefaultDeclaration":
|
|
336
|
+
exp = self._parse_export(node, "default")
|
|
337
|
+
if exp:
|
|
338
|
+
result.exports.append(exp)
|
|
339
|
+
|
|
340
|
+
elif node_type == "ExportNamedDeclaration":
|
|
341
|
+
for spec in node.get("specifiers", []):
|
|
342
|
+
exp = self._parse_export(spec, "named")
|
|
343
|
+
if exp:
|
|
344
|
+
result.exports.append(exp)
|
|
345
|
+
|
|
346
|
+
elif node_type == "CallExpression":
|
|
347
|
+
# 检测 require() 调用
|
|
348
|
+
callee = node.get("callee", {})
|
|
349
|
+
if callee.get("name") == "require":
|
|
350
|
+
args = node.get("arguments", [])
|
|
351
|
+
if args and args[0].get("type") == "Literal":
|
|
352
|
+
imp = JSImportInfo(
|
|
353
|
+
source=args[0].get("value", ""),
|
|
354
|
+
specifiers=[],
|
|
355
|
+
line=node.get("loc", {}).get("start", {}).get("line", 0),
|
|
356
|
+
import_type="require"
|
|
357
|
+
)
|
|
358
|
+
result.imports.append(imp)
|
|
359
|
+
|
|
360
|
+
# 递归遍历子节点
|
|
361
|
+
for key, value in node.items():
|
|
362
|
+
if key in ("loc", "range", "start", "end"):
|
|
363
|
+
continue
|
|
364
|
+
if isinstance(value, dict):
|
|
365
|
+
self._traverse(value, result, in_class)
|
|
366
|
+
elif isinstance(value, list):
|
|
367
|
+
for item in value:
|
|
368
|
+
if isinstance(item, dict):
|
|
369
|
+
self._traverse(item, result, in_class)
|
|
370
|
+
|
|
371
|
+
def _parse_function(self, node: dict, is_method: bool = False) -> Optional[JSFunctionInfo]:
|
|
372
|
+
"""解析函数声明"""
|
|
373
|
+
name = None
|
|
374
|
+
if node.get("id"):
|
|
375
|
+
name = node["id"].get("name")
|
|
376
|
+
elif is_method:
|
|
377
|
+
# 方法的名称在 key 中
|
|
378
|
+
key = node.get("key", {})
|
|
379
|
+
name = key.get("name") if isinstance(key, dict) else None
|
|
380
|
+
|
|
381
|
+
if not name:
|
|
382
|
+
return None
|
|
383
|
+
|
|
384
|
+
params = []
|
|
385
|
+
for param in node.get("params", []):
|
|
386
|
+
if param.get("type") == "Identifier":
|
|
387
|
+
params.append(param.get("name"))
|
|
388
|
+
elif param.get("type") == "AssignmentPattern":
|
|
389
|
+
# 默认参数
|
|
390
|
+
left = param.get("left", {})
|
|
391
|
+
if left.get("type") == "Identifier":
|
|
392
|
+
params.append(left.get("name"))
|
|
393
|
+
elif param.get("type") == "RestElement":
|
|
394
|
+
# 剩余参数
|
|
395
|
+
arg = param.get("argument", {})
|
|
396
|
+
if arg.get("type") == "Identifier":
|
|
397
|
+
params.append(f"...{arg.get('name')}")
|
|
398
|
+
|
|
399
|
+
loc = node.get("loc", {})
|
|
400
|
+
start_line = loc.get("start", {}).get("line", 0)
|
|
401
|
+
end_line = loc.get("end", {}).get("line", start_line)
|
|
402
|
+
|
|
403
|
+
return JSFunctionInfo(
|
|
404
|
+
name=name,
|
|
405
|
+
line_start=start_line,
|
|
406
|
+
line_end=end_line,
|
|
407
|
+
params=params,
|
|
408
|
+
is_async=node.get("async", False),
|
|
409
|
+
is_generator=node.get("generator", False),
|
|
410
|
+
is_arrow=node.get("type") == "ArrowFunctionExpression",
|
|
411
|
+
is_method=is_method
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def _parse_class(self, node: dict) -> Optional[JSClassInfo]:
|
|
415
|
+
"""解析类声明"""
|
|
416
|
+
if not node.get("id"):
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
name = node["id"].get("name")
|
|
420
|
+
loc = node.get("loc", {})
|
|
421
|
+
start_line = loc.get("start", {}).get("line", 0)
|
|
422
|
+
end_line = loc.get("end", {}).get("line", start_line)
|
|
423
|
+
|
|
424
|
+
# 获取继承的类
|
|
425
|
+
extends = None
|
|
426
|
+
if node.get("superClass"):
|
|
427
|
+
super_class = node["superClass"]
|
|
428
|
+
if super_class.get("type") == "Identifier":
|
|
429
|
+
extends = super_class.get("name")
|
|
430
|
+
|
|
431
|
+
# 解析方法
|
|
432
|
+
methods = []
|
|
433
|
+
properties = []
|
|
434
|
+
|
|
435
|
+
body = node.get("body", {})
|
|
436
|
+
for item in body.get("body", []):
|
|
437
|
+
if item.get("type") == "MethodDefinition":
|
|
438
|
+
func = self._parse_method(item)
|
|
439
|
+
if func:
|
|
440
|
+
methods.append(func)
|
|
441
|
+
elif item.get("type") == "ClassProperty":
|
|
442
|
+
key = item.get("key", {})
|
|
443
|
+
if key.get("type") == "Identifier":
|
|
444
|
+
properties.append(key.get("name"))
|
|
445
|
+
|
|
446
|
+
return JSClassInfo(
|
|
447
|
+
name=name,
|
|
448
|
+
line_start=start_line,
|
|
449
|
+
line_end=end_line,
|
|
450
|
+
methods=methods,
|
|
451
|
+
properties=properties,
|
|
452
|
+
extends=extends
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
def _parse_method(self, node: dict) -> Optional[JSFunctionInfo]:
|
|
456
|
+
"""解析类方法"""
|
|
457
|
+
key = node.get("key", {})
|
|
458
|
+
name = key.get("name") if isinstance(key, dict) else None
|
|
459
|
+
|
|
460
|
+
if not name:
|
|
461
|
+
return None
|
|
462
|
+
|
|
463
|
+
value = node.get("value", {})
|
|
464
|
+
params = []
|
|
465
|
+
for param in value.get("params", []):
|
|
466
|
+
if param.get("type") == "Identifier":
|
|
467
|
+
params.append(param.get("name"))
|
|
468
|
+
|
|
469
|
+
loc = node.get("loc", {})
|
|
470
|
+
start_line = loc.get("start", {}).get("line", 0)
|
|
471
|
+
end_line = loc.get("end", {}).get("line", start_line)
|
|
472
|
+
|
|
473
|
+
return JSFunctionInfo(
|
|
474
|
+
name=name,
|
|
475
|
+
line_start=start_line,
|
|
476
|
+
line_end=end_line,
|
|
477
|
+
params=params,
|
|
478
|
+
is_async=value.get("async", False),
|
|
479
|
+
is_generator=value.get("generator", False),
|
|
480
|
+
is_arrow=False,
|
|
481
|
+
is_method=True
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
def _parse_variable(self, node: dict, kind: str) -> Optional[JSVariableInfo]:
|
|
485
|
+
"""解析变量声明"""
|
|
486
|
+
if node.get("id", {}).get("type") == "Identifier":
|
|
487
|
+
name = node["id"].get("name")
|
|
488
|
+
loc = node.get("loc", {})
|
|
489
|
+
line = loc.get("start", {}).get("line", 0)
|
|
490
|
+
|
|
491
|
+
return JSVariableInfo(
|
|
492
|
+
name=name,
|
|
493
|
+
line=line,
|
|
494
|
+
kind=kind,
|
|
495
|
+
is_destructured=False
|
|
496
|
+
)
|
|
497
|
+
elif node.get("id", {}).get("type") in ("ObjectPattern", "ArrayPattern"):
|
|
498
|
+
# 解构赋值
|
|
499
|
+
loc = node.get("loc", {})
|
|
500
|
+
line = loc.get("start", {}).get("line", 0)
|
|
501
|
+
return JSVariableInfo(
|
|
502
|
+
name="<destructured>",
|
|
503
|
+
line=line,
|
|
504
|
+
kind=kind,
|
|
505
|
+
is_destructured=True
|
|
506
|
+
)
|
|
507
|
+
return None
|
|
508
|
+
|
|
509
|
+
def _parse_import(self, node: dict) -> Optional[JSImportInfo]:
|
|
510
|
+
"""解析 import 声明"""
|
|
511
|
+
source = node.get("source", {})
|
|
512
|
+
source_path = source.get("value", "") if source.get("type") == "Literal" else ""
|
|
513
|
+
|
|
514
|
+
specifiers = []
|
|
515
|
+
for spec in node.get("specifiers", []):
|
|
516
|
+
if spec.get("type") == "ImportDefaultSpecifier":
|
|
517
|
+
local = spec.get("local", {})
|
|
518
|
+
if local.get("type") == "Identifier":
|
|
519
|
+
specifiers.append(local.get("name"))
|
|
520
|
+
elif spec.get("type") == "ImportSpecifier":
|
|
521
|
+
local = spec.get("local", {})
|
|
522
|
+
if local.get("type") == "Identifier":
|
|
523
|
+
specifiers.append(local.get("name"))
|
|
524
|
+
elif spec.get("type") == "ImportNamespaceSpecifier":
|
|
525
|
+
local = spec.get("local", {})
|
|
526
|
+
if local.get("type") == "Identifier":
|
|
527
|
+
specifiers.append(f"* as {local.get('name')}")
|
|
528
|
+
|
|
529
|
+
loc = node.get("loc", {})
|
|
530
|
+
line = loc.get("start", {}).get("line", 0)
|
|
531
|
+
|
|
532
|
+
return JSImportInfo(
|
|
533
|
+
source=source_path,
|
|
534
|
+
specifiers=specifiers,
|
|
535
|
+
line=line,
|
|
536
|
+
import_type="import"
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
def _parse_export(self, node: dict, export_type: str) -> Optional[JSExportInfo]:
|
|
540
|
+
"""解析 export 声明"""
|
|
541
|
+
name = None
|
|
542
|
+
|
|
543
|
+
if export_type == "default":
|
|
544
|
+
decl = node.get("declaration", {})
|
|
545
|
+
if decl.get("type") == "Identifier":
|
|
546
|
+
name = decl.get("name")
|
|
547
|
+
elif decl.get("type") == "FunctionDeclaration":
|
|
548
|
+
if decl.get("id"):
|
|
549
|
+
name = decl["id"].get("name")
|
|
550
|
+
else:
|
|
551
|
+
name = "<anonymous>"
|
|
552
|
+
elif decl.get("type") == "ClassDeclaration":
|
|
553
|
+
if decl.get("id"):
|
|
554
|
+
name = decl["id"].get("name")
|
|
555
|
+
else:
|
|
556
|
+
name = "<anonymous>"
|
|
557
|
+
else:
|
|
558
|
+
name = "<default>"
|
|
559
|
+
else:
|
|
560
|
+
# named export
|
|
561
|
+
local = node.get("local", {})
|
|
562
|
+
if local.get("type") == "Identifier":
|
|
563
|
+
name = local.get("name")
|
|
564
|
+
|
|
565
|
+
if not name:
|
|
566
|
+
return None
|
|
567
|
+
|
|
568
|
+
loc = node.get("loc", {})
|
|
569
|
+
line = loc.get("start", {}).get("line", 0)
|
|
570
|
+
|
|
571
|
+
return JSExportInfo(
|
|
572
|
+
name=name,
|
|
573
|
+
line=line,
|
|
574
|
+
export_type=export_type
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def analyze_javascript_file(file_path: str) -> JavaScriptASTResult:
|
|
579
|
+
"""分析 JavaScript 文件的便捷函数"""
|
|
580
|
+
parser = JavaScriptASTParser()
|
|
581
|
+
return parser.parse_file(file_path)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def analyze_javascript_code(code: str) -> JavaScriptASTResult:
|
|
585
|
+
"""分析 JavaScript 代码字符串的便捷函数"""
|
|
586
|
+
parser = JavaScriptASTParser()
|
|
587
|
+
return parser.parse_code(code)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Kotlin 解析器子模块
|
|
2
|
+
|
|
3
|
+
提供双模式解析能力:
|
|
4
|
+
- 快速模式:正则表达式解析,无外部依赖
|
|
5
|
+
- 精确模式:tree-sitter AST 解析,完整语法信息
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .models import (
|
|
9
|
+
# 基础类型
|
|
10
|
+
KotlinFunctionInfo,
|
|
11
|
+
KotlinPropertyInfo,
|
|
12
|
+
KotlinClassInfo,
|
|
13
|
+
KotlinImportInfo,
|
|
14
|
+
KotlinASTResult,
|
|
15
|
+
# 新增类型
|
|
16
|
+
KotlinSealedClassInfo,
|
|
17
|
+
KotlinWhenExpression,
|
|
18
|
+
KotlinCoroutineInfo,
|
|
19
|
+
KotlinFlowOperator,
|
|
20
|
+
KotlinTypeAlias,
|
|
21
|
+
KotlinValueClass,
|
|
22
|
+
KotlinGenericType,
|
|
23
|
+
KotlinDelegatedProperty,
|
|
24
|
+
KotlinPropertyDelegate,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from .regex_parser import KotlinRegexParser
|
|
28
|
+
from .patterns import KOTLIN_PATTERNS
|
|
29
|
+
|
|
30
|
+
# 尝试导入 AST 解析器
|
|
31
|
+
try:
|
|
32
|
+
from .ast_parser import KotlinTreeSitterParser
|
|
33
|
+
AST_PARSER_AVAILABLE = True
|
|
34
|
+
except ImportError:
|
|
35
|
+
KotlinTreeSitterParser = None
|
|
36
|
+
AST_PARSER_AVAILABLE = False
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
# 基础类型
|
|
40
|
+
"KotlinFunctionInfo",
|
|
41
|
+
"KotlinPropertyInfo",
|
|
42
|
+
"KotlinClassInfo",
|
|
43
|
+
"KotlinImportInfo",
|
|
44
|
+
"KotlinASTResult",
|
|
45
|
+
# 新增类型
|
|
46
|
+
"KotlinSealedClassInfo",
|
|
47
|
+
"KotlinWhenExpression",
|
|
48
|
+
"KotlinCoroutineInfo",
|
|
49
|
+
"KotlinFlowOperator",
|
|
50
|
+
"KotlinTypeAlias",
|
|
51
|
+
"KotlinValueClass",
|
|
52
|
+
"KotlinGenericType",
|
|
53
|
+
"KotlinDelegatedProperty",
|
|
54
|
+
"KotlinPropertyDelegate",
|
|
55
|
+
# 解析器
|
|
56
|
+
"KotlinRegexParser",
|
|
57
|
+
"KotlinTreeSitterParser",
|
|
58
|
+
"AST_PARSER_AVAILABLE",
|
|
59
|
+
# 其他
|
|
60
|
+
"KOTLIN_PATTERNS",
|
|
61
|
+
]
|