mofox-plugin-dev-toolkit 0.2.1__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.
- mofox_plugin_dev_toolkit-0.2.1.dist-info/METADATA +409 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/RECORD +43 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/WHEEL +5 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/entry_points.txt +2 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/licenses/LICENSE +674 -0
- mofox_plugin_dev_toolkit-0.2.1.dist-info/top_level.txt +1 -0
- mpdt/__init__.py +15 -0
- mpdt/__main__.py +8 -0
- mpdt/cli.py +314 -0
- mpdt/commands/__init__.py +9 -0
- mpdt/commands/check.py +316 -0
- mpdt/commands/dev.py +550 -0
- mpdt/commands/generate.py +366 -0
- mpdt/commands/init.py +487 -0
- mpdt/dev/bridge_plugin/__init__.py +17 -0
- mpdt/dev/bridge_plugin/discovery_server.py +126 -0
- mpdt/dev/bridge_plugin/plugin.py +258 -0
- mpdt/templates/__init__.py +165 -0
- mpdt/templates/action_template.py +102 -0
- mpdt/templates/adapter_template.py +129 -0
- mpdt/templates/chatter_template.py +103 -0
- mpdt/templates/event_template.py +116 -0
- mpdt/templates/plus_command_template.py +150 -0
- mpdt/templates/prompt_template.py +92 -0
- mpdt/templates/router_template.py +175 -0
- mpdt/templates/tool_template.py +98 -0
- mpdt/utils/__init__.py +10 -0
- mpdt/utils/color_printer.py +99 -0
- mpdt/utils/config_loader.py +171 -0
- mpdt/utils/config_manager.py +297 -0
- mpdt/utils/file_ops.py +203 -0
- mpdt/utils/license_generator.py +980 -0
- mpdt/utils/plugin_parser.py +196 -0
- mpdt/utils/template_engine.py +112 -0
- mpdt/validators/__init__.py +26 -0
- mpdt/validators/auto_fix_validator.py +182 -0
- mpdt/validators/base.py +121 -0
- mpdt/validators/component_validator.py +415 -0
- mpdt/validators/config_validator.py +173 -0
- mpdt/validators/metadata_validator.py +125 -0
- mpdt/validators/structure_validator.py +70 -0
- mpdt/validators/style_validator.py +125 -0
- mpdt/validators/type_validator.py +223 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
插件元数据验证器
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
|
|
7
|
+
from .base import BaseValidator, ValidationResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MetadataValidator(BaseValidator):
|
|
11
|
+
"""插件元数据验证器
|
|
12
|
+
|
|
13
|
+
检查 plugin.py 中的 PluginMetadata 是否完整
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# 必需的元数据字段
|
|
17
|
+
REQUIRED_FIELDS = ["name", "description", "usage"]
|
|
18
|
+
|
|
19
|
+
# 推荐的元数据字段
|
|
20
|
+
RECOMMENDED_FIELDS = ["version", "author", "license"]
|
|
21
|
+
|
|
22
|
+
def validate(self) -> ValidationResult:
|
|
23
|
+
"""执行元数据验证
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
ValidationResult: 验证结果
|
|
27
|
+
"""
|
|
28
|
+
# 获取插件名称
|
|
29
|
+
plugin_name = self._get_plugin_name()
|
|
30
|
+
if not plugin_name:
|
|
31
|
+
self.result.add_error("无法确定插件名称")
|
|
32
|
+
return self.result
|
|
33
|
+
|
|
34
|
+
# 元数据在 __init__.py 中
|
|
35
|
+
init_file = self.plugin_path / "__init__.py"
|
|
36
|
+
if not init_file.exists():
|
|
37
|
+
self.result.add_error(
|
|
38
|
+
"__init__.py 文件不存在",
|
|
39
|
+
suggestion="请创建 __init__.py 文件并定义 __plugin_meta__",
|
|
40
|
+
)
|
|
41
|
+
return self.result
|
|
42
|
+
|
|
43
|
+
# 读取并解析 __init__.py
|
|
44
|
+
try:
|
|
45
|
+
with open(init_file, encoding="utf-8") as f:
|
|
46
|
+
tree = ast.parse(f.read(), filename=str(init_file))
|
|
47
|
+
except SyntaxError as e:
|
|
48
|
+
self.result.add_error(
|
|
49
|
+
f"__init__.py 存在语法错误: {e.msg}",
|
|
50
|
+
file_path="__init__.py",
|
|
51
|
+
line_number=e.lineno,
|
|
52
|
+
)
|
|
53
|
+
return self.result
|
|
54
|
+
except Exception as e:
|
|
55
|
+
self.result.add_error(f"读取 __init__.py 失败: {e}")
|
|
56
|
+
return self.result
|
|
57
|
+
|
|
58
|
+
# 查找 __plugin_meta__ 变量赋值
|
|
59
|
+
metadata_found = False
|
|
60
|
+
metadata_node = None
|
|
61
|
+
|
|
62
|
+
for node in ast.walk(tree):
|
|
63
|
+
# 查找 __plugin_meta__ = PluginMetadata(...) 赋值
|
|
64
|
+
if isinstance(node, ast.Assign):
|
|
65
|
+
for target in node.targets:
|
|
66
|
+
if isinstance(target, ast.Name) and target.id == "__plugin_meta__":
|
|
67
|
+
if isinstance(node.value, ast.Call):
|
|
68
|
+
if isinstance(node.value.func, ast.Name) and node.value.func.id == "PluginMetadata":
|
|
69
|
+
metadata_found = True
|
|
70
|
+
metadata_node = node.value
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
if not metadata_found:
|
|
74
|
+
self.result.add_error(
|
|
75
|
+
"未找到 __plugin_meta__ 变量或 PluginMetadata 实例",
|
|
76
|
+
file_path="__init__.py",
|
|
77
|
+
suggestion="请在 __init__.py 中定义: __plugin_meta__ = PluginMetadata(...)",
|
|
78
|
+
)
|
|
79
|
+
return self.result
|
|
80
|
+
|
|
81
|
+
# 提取元数据字段
|
|
82
|
+
metadata_fields = {}
|
|
83
|
+
if metadata_node:
|
|
84
|
+
# 处理关键字参数
|
|
85
|
+
for keyword in metadata_node.keywords:
|
|
86
|
+
if keyword.arg:
|
|
87
|
+
# 尝试获取值
|
|
88
|
+
value = self._extract_value(keyword.value)
|
|
89
|
+
metadata_fields[keyword.arg] = value
|
|
90
|
+
|
|
91
|
+
# 检查必需字段
|
|
92
|
+
for field in self.REQUIRED_FIELDS:
|
|
93
|
+
if field not in metadata_fields:
|
|
94
|
+
self.result.add_error(
|
|
95
|
+
f"PluginMetadata 缺少必需字段: {field}",
|
|
96
|
+
file_path="__init__.py",
|
|
97
|
+
)
|
|
98
|
+
elif not metadata_fields[field]:
|
|
99
|
+
self.result.add_warning(
|
|
100
|
+
f"PluginMetadata 字段 {field} 为空",
|
|
101
|
+
file_path="__init__.py",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# 检查推荐字段
|
|
105
|
+
for field in self.RECOMMENDED_FIELDS:
|
|
106
|
+
if field not in metadata_fields:
|
|
107
|
+
self.result.add_warning(
|
|
108
|
+
f"PluginMetadata 缺少推荐字段: {field}",
|
|
109
|
+
file_path="__init__.py",
|
|
110
|
+
suggestion=f"建议添加 {field} 字段",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return self.result
|
|
114
|
+
|
|
115
|
+
def _extract_value(self, node: ast.AST) -> str | None:
|
|
116
|
+
"""提取 AST 节点的值"""
|
|
117
|
+
if isinstance(node, ast.Constant):
|
|
118
|
+
return str(node.value) if node.value else None
|
|
119
|
+
elif isinstance(node, ast.Str): # Python 3.7 兼容
|
|
120
|
+
return str(node.s) if node.s else None
|
|
121
|
+
elif isinstance(node, ast.List):
|
|
122
|
+
return "[...]" # 列表类型
|
|
123
|
+
elif isinstance(node, ast.Dict):
|
|
124
|
+
return "{...}" # 字典类型
|
|
125
|
+
return None
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
插件结构验证器
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import BaseValidator, ValidationResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StructureValidator(BaseValidator):
|
|
9
|
+
"""插件结构验证器
|
|
10
|
+
|
|
11
|
+
检查插件的目录结构和必需文件
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# 必需的文件
|
|
15
|
+
REQUIRED_FILES = ["__init__.py", "plugin.py"]
|
|
16
|
+
|
|
17
|
+
# 推荐的文件
|
|
18
|
+
RECOMMENDED_FILES = ["README.md", "pyproject.toml", "requirements.txt"]
|
|
19
|
+
|
|
20
|
+
# 推荐的目录
|
|
21
|
+
RECOMMENDED_DIRS = ["tests", "docs"]
|
|
22
|
+
|
|
23
|
+
def validate(self) -> ValidationResult:
|
|
24
|
+
"""执行结构验证
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
ValidationResult: 验证结果
|
|
28
|
+
"""
|
|
29
|
+
# 获取插件名称
|
|
30
|
+
plugin_name = self._get_plugin_name()
|
|
31
|
+
if not plugin_name:
|
|
32
|
+
self.result.add_error(
|
|
33
|
+
"无法确定插件名称,请确保插件目录结构正确",
|
|
34
|
+
suggestion="插件应该有 plugin.py 文件",
|
|
35
|
+
)
|
|
36
|
+
return self.result
|
|
37
|
+
|
|
38
|
+
# 插件代码目录就是根目录
|
|
39
|
+
plugin_code_dir = self.plugin_path
|
|
40
|
+
|
|
41
|
+
# 检查必需的文件
|
|
42
|
+
for file_name in self.REQUIRED_FILES:
|
|
43
|
+
file_path = plugin_code_dir / file_name
|
|
44
|
+
if not file_path.exists():
|
|
45
|
+
self.result.add_error(
|
|
46
|
+
f"缺少必需文件: {file_name}",
|
|
47
|
+
file_path=str(file_path.relative_to(self.plugin_path)),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# 检查推荐的文件
|
|
51
|
+
for file_name in self.RECOMMENDED_FILES:
|
|
52
|
+
file_path = self.plugin_path / file_name
|
|
53
|
+
if not file_path.exists():
|
|
54
|
+
self.result.add_warning(
|
|
55
|
+
f"缺少推荐文件: {file_name}",
|
|
56
|
+
file_path=file_name,
|
|
57
|
+
suggestion=f"建议添加 {file_name} 以提供更好的文档或依赖管理",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# 检查推荐的目录
|
|
61
|
+
for dir_name in self.RECOMMENDED_DIRS:
|
|
62
|
+
dir_path = self.plugin_path / dir_name
|
|
63
|
+
if not dir_path.exists():
|
|
64
|
+
self.result.add_warning(
|
|
65
|
+
f"缺少推荐目录: {dir_name}/",
|
|
66
|
+
file_path=dir_name,
|
|
67
|
+
suggestion=f"建议添加 {dir_name}/ 目录",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return self.result
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""代码风格验证器
|
|
2
|
+
|
|
3
|
+
使用 ruff 检查代码风格问题
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .base import BaseValidator, ValidationResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StyleValidator(BaseValidator):
|
|
15
|
+
"""代码风格验证器
|
|
16
|
+
|
|
17
|
+
使用 ruff 检查代码风格和代码质量问题
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, plugin_path: Path, auto_fix: bool = False):
|
|
21
|
+
super().__init__(plugin_path)
|
|
22
|
+
self.auto_fix = auto_fix
|
|
23
|
+
|
|
24
|
+
def validate(self) -> ValidationResult:
|
|
25
|
+
"""执行代码风格检查"""
|
|
26
|
+
result = ValidationResult(
|
|
27
|
+
validator_name="StyleValidator",
|
|
28
|
+
success=True
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
plugin_name = self._get_plugin_name()
|
|
32
|
+
if not plugin_name:
|
|
33
|
+
result.add_error("无法确定插件名称")
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
# 检查 ruff 是否安装
|
|
37
|
+
if not self._is_ruff_installed():
|
|
38
|
+
result.add_warning(
|
|
39
|
+
"未安装 ruff,跳过代码风格检查",
|
|
40
|
+
suggestion="运行 'pip install ruff' 安装"
|
|
41
|
+
)
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
# 运行 ruff check
|
|
45
|
+
issues = self._run_ruff_check()
|
|
46
|
+
|
|
47
|
+
if issues:
|
|
48
|
+
for issue in issues:
|
|
49
|
+
result.add_warning(
|
|
50
|
+
issue["message"],
|
|
51
|
+
file_path=issue.get("file"),
|
|
52
|
+
line_number=issue.get("line"),
|
|
53
|
+
suggestion=issue.get("suggestion")
|
|
54
|
+
)
|
|
55
|
+
else:
|
|
56
|
+
result.add_info("代码风格检查通过,未发现问题")
|
|
57
|
+
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
def _is_ruff_installed(self) -> bool:
|
|
61
|
+
"""检查 ruff 是否安装"""
|
|
62
|
+
try:
|
|
63
|
+
subprocess.run(
|
|
64
|
+
["ruff", "--version"],
|
|
65
|
+
capture_output=True,
|
|
66
|
+
check=True
|
|
67
|
+
)
|
|
68
|
+
return True
|
|
69
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
def _run_ruff_check(self) -> list[dict[str, Any]]:
|
|
73
|
+
"""运行 ruff 检查
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
问题列表
|
|
77
|
+
"""
|
|
78
|
+
issues = []
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
# 构建命令
|
|
82
|
+
cmd = ["ruff", "check"]
|
|
83
|
+
if self.auto_fix:
|
|
84
|
+
cmd.append("--fix")
|
|
85
|
+
cmd.extend(["--output-format", "json", str(self.plugin_path)])
|
|
86
|
+
|
|
87
|
+
# 运行 ruff
|
|
88
|
+
result = subprocess.run(
|
|
89
|
+
cmd,
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
encoding='utf-8'
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# 解析输出
|
|
96
|
+
if result.stdout.strip():
|
|
97
|
+
try:
|
|
98
|
+
ruff_output = json.loads(result.stdout)
|
|
99
|
+
for item in ruff_output:
|
|
100
|
+
issues.append({
|
|
101
|
+
"file": str(Path(item["filename"]).relative_to(self.plugin_path)),
|
|
102
|
+
"line": item["location"]["row"],
|
|
103
|
+
"message": f"{item['code']}: {item['message']}",
|
|
104
|
+
"suggestion": self._get_fix_suggestion(item)
|
|
105
|
+
})
|
|
106
|
+
except json.JSONDecodeError:
|
|
107
|
+
# 如果不是 JSON 格式,尝试解析纯文本
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
# 不抛出异常,只记录问题
|
|
112
|
+
issues.append({
|
|
113
|
+
"file": None,
|
|
114
|
+
"line": None,
|
|
115
|
+
"message": f"运行 ruff 时出错: {e}",
|
|
116
|
+
"suggestion": None
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return issues
|
|
120
|
+
|
|
121
|
+
def _get_fix_suggestion(self, item: dict) -> str | None:
|
|
122
|
+
"""获取修复建议"""
|
|
123
|
+
if item.get("fix"):
|
|
124
|
+
return "可自动修复,使用 --auto-fix 选项"
|
|
125
|
+
return None
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""类型检查验证器
|
|
2
|
+
|
|
3
|
+
使用 mypy 进行类型检查
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .base import BaseValidator, ValidationResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TypeValidator(BaseValidator):
|
|
14
|
+
"""类型检查验证器
|
|
15
|
+
|
|
16
|
+
使用 mypy 进行静态类型检查
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, plugin_path: Path):
|
|
20
|
+
super().__init__(plugin_path)
|
|
21
|
+
# 尝试找到 MoFox 主项目路径
|
|
22
|
+
self.MoFox_root = self._find_mofox_root()
|
|
23
|
+
|
|
24
|
+
def validate(self) -> ValidationResult:
|
|
25
|
+
"""执行类型检查"""
|
|
26
|
+
result = ValidationResult(
|
|
27
|
+
validator_name="TypeValidator",
|
|
28
|
+
success=True
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
plugin_name = self._get_plugin_name()
|
|
32
|
+
if not plugin_name:
|
|
33
|
+
result.add_error("无法确定插件名称")
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
# 检查 mypy 是否安装
|
|
37
|
+
if not self._is_mypy_installed():
|
|
38
|
+
result.add_warning(
|
|
39
|
+
"未安装 mypy,跳过类型检查",
|
|
40
|
+
suggestion="运行 'pip install mypy' 安装"
|
|
41
|
+
)
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
# 运行 mypy
|
|
45
|
+
issues = self._run_mypy_check()
|
|
46
|
+
|
|
47
|
+
if issues:
|
|
48
|
+
for issue in issues:
|
|
49
|
+
# 根据严重程度决定是错误还是警告
|
|
50
|
+
if issue.get("severity") == "error":
|
|
51
|
+
result.add_error(
|
|
52
|
+
issue["message"],
|
|
53
|
+
file_path=issue.get("file"),
|
|
54
|
+
line_number=issue.get("line"),
|
|
55
|
+
suggestion=issue.get("suggestion")
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
result.add_warning(
|
|
59
|
+
issue["message"],
|
|
60
|
+
file_path=issue.get("file"),
|
|
61
|
+
line_number=issue.get("line"),
|
|
62
|
+
suggestion=issue.get("suggestion")
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
result.add_info("类型检查通过,未发现类型问题")
|
|
66
|
+
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
def _is_mypy_installed(self) -> bool:
|
|
70
|
+
"""检查 mypy 是否安装"""
|
|
71
|
+
try:
|
|
72
|
+
subprocess.run(
|
|
73
|
+
["mypy", "--version"],
|
|
74
|
+
capture_output=True,
|
|
75
|
+
check=True
|
|
76
|
+
)
|
|
77
|
+
return True
|
|
78
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def _find_mofox_root(self) -> Path | None:
|
|
82
|
+
"""尝试找到 MoFox 主项目根目录
|
|
83
|
+
|
|
84
|
+
从插件路径向上查找,直到找到包含 src/ 目录的父目录
|
|
85
|
+
"""
|
|
86
|
+
current = self.plugin_path.parent
|
|
87
|
+
|
|
88
|
+
# 最多向上查找 5 层
|
|
89
|
+
for _ in range(5):
|
|
90
|
+
if not current or current == current.parent:
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
# 检查是否存在 src/ 目录且包含 plugin_system 等模块
|
|
94
|
+
src_dir = current / "src"
|
|
95
|
+
if src_dir.exists() and src_dir.is_dir():
|
|
96
|
+
# 验证是否是 MMC 项目
|
|
97
|
+
if (src_dir / "plugin_system").exists() or (src_dir / "common").exists():
|
|
98
|
+
return current
|
|
99
|
+
|
|
100
|
+
current = current.parent
|
|
101
|
+
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def _run_mypy_check(self) -> list[dict]:
|
|
105
|
+
"""运行 mypy 检查
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
问题列表
|
|
109
|
+
"""
|
|
110
|
+
issues = []
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
# 构建命令
|
|
114
|
+
cmd = [
|
|
115
|
+
"mypy",
|
|
116
|
+
str(self.plugin_path),
|
|
117
|
+
"--no-error-summary",
|
|
118
|
+
"--show-column-numbers",
|
|
119
|
+
"--show-error-codes",
|
|
120
|
+
"--no-namespace-packages", # 避免包命名空间问题
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
# 如果找到了 MMC 主项目,添加到 Python 路径
|
|
124
|
+
if self.MoFox_root:
|
|
125
|
+
cmd.extend(["--python-path", str(self.MoFox_root)])
|
|
126
|
+
|
|
127
|
+
# 运行 mypy
|
|
128
|
+
result = subprocess.run(
|
|
129
|
+
cmd,
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# 解析输出
|
|
135
|
+
if result.stdout.strip():
|
|
136
|
+
for line in result.stdout.strip().split('\n'):
|
|
137
|
+
issue = self._parse_mypy_line(line)
|
|
138
|
+
if issue:
|
|
139
|
+
# 如果找不到 MoFox 根目录,过滤掉所有 src.* 模块的导入错误
|
|
140
|
+
if not self.MoFox_root:
|
|
141
|
+
# 检查是否是 src.* 模块的导入错误
|
|
142
|
+
if "Cannot find implementation" in issue["message"] and '"src.' in issue["message"]:
|
|
143
|
+
continue # 跳过这个错误
|
|
144
|
+
# 检查是否是因为基类是 Any 导致的错误(因为导入失败)
|
|
145
|
+
if "has type \"Any\"" in issue["message"]:
|
|
146
|
+
continue # 跳过这个错误
|
|
147
|
+
issues.append(issue)
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
issues.append({
|
|
151
|
+
"file": None,
|
|
152
|
+
"line": None,
|
|
153
|
+
"message": f"运行 mypy 时出错: {e}",
|
|
154
|
+
"severity": "error",
|
|
155
|
+
"suggestion": None
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
return issues
|
|
159
|
+
|
|
160
|
+
def _parse_mypy_line(self, line: str) -> dict | None:
|
|
161
|
+
"""解析 mypy 输出的一行
|
|
162
|
+
|
|
163
|
+
格式: file.py:123:45: error: Message [error-code]
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
line: mypy 输出的一行
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
解析后的问题字典,如果解析失败返回 None
|
|
170
|
+
"""
|
|
171
|
+
# 匹配 mypy 输出格式
|
|
172
|
+
pattern = r'^(.+?):(\d+):(?:\d+:)?\s+(error|warning|note):\s+(.+?)(?:\s+\[(.+?)\])?$'
|
|
173
|
+
match = re.match(pattern, line)
|
|
174
|
+
|
|
175
|
+
if not match:
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
file_path, line_num, severity, message, error_code = match.groups()
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
# 转换为相对路径
|
|
182
|
+
rel_path = str(Path(file_path).relative_to(self.plugin_path))
|
|
183
|
+
except ValueError:
|
|
184
|
+
rel_path = file_path
|
|
185
|
+
|
|
186
|
+
issue = {
|
|
187
|
+
"file": rel_path,
|
|
188
|
+
"line": int(line_num),
|
|
189
|
+
"message": message.strip(),
|
|
190
|
+
"severity": severity,
|
|
191
|
+
"suggestion": None
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# 添加错误代码到消息中
|
|
195
|
+
if error_code:
|
|
196
|
+
issue["message"] = f"[{error_code}] {issue['message']}"
|
|
197
|
+
|
|
198
|
+
# 根据常见错误提供建议
|
|
199
|
+
issue["suggestion"] = self._get_type_hint_suggestion(message, error_code)
|
|
200
|
+
|
|
201
|
+
return issue
|
|
202
|
+
|
|
203
|
+
def _get_type_hint_suggestion(self, message: str, error_code: str | None) -> str | None:
|
|
204
|
+
"""根据错误消息提供类型提示建议"""
|
|
205
|
+
if not error_code:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
suggestions = {
|
|
209
|
+
"no-untyped-def": "为函数添加类型注解",
|
|
210
|
+
"no-untyped-call": "被调用的函数缺少类型注解",
|
|
211
|
+
"assignment": "检查赋值的类型是否匹配",
|
|
212
|
+
"return-value": "检查返回值类型是否与声明一致",
|
|
213
|
+
"arg-type": "检查参数类型是否正确",
|
|
214
|
+
"attr-defined": "检查属性是否存在",
|
|
215
|
+
"name-defined": "检查名称是否定义",
|
|
216
|
+
"import": "检查导入是否正确"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for code, suggestion in suggestions.items():
|
|
220
|
+
if code in error_code:
|
|
221
|
+
return suggestion
|
|
222
|
+
|
|
223
|
+
return None
|