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.
@@ -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
+ ]