kcode-pi 0.1.6 → 0.1.8
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/README.md +40 -5
- package/docs/DEVELOPMENT.md +2 -0
- package/extensions/kingdee-harness.ts +139 -1
- package/extensions/kingdee-tools.ts +43 -1
- package/package.json +2 -1
- package/skills/kd-check/SKILL.md +1 -2
- package/skills/kd-cosmic-dev/SKILL.md +3 -3
- package/skills/kd-cosmic-review/SKILL.md +2 -2
- package/skills/kd-cosmic-unittest/SKILL.md +2 -2
- package/skills/kd-debug/SKILL.md +1 -2
- package/skills/kd-execute/SKILL.md +2 -2
- package/skills/kd-gen/SKILL.md +2 -1
- package/skills/kd-plan/SKILL.md +3 -3
- package/src/harness/gates.ts +14 -1
- package/src/harness/state.ts +44 -1
- package/src/harness/types.ts +14 -0
- package/src/official/kingdee-skills.ts +60 -13
- package/src/rules/checker.ts +143 -0
- package/src/tools/sdk-signature.ts +309 -0
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/SKILL.md +2 -2
- package/vendor/kingdee-skills/ok-cosmic/SKILL.md +52 -101
- package/vendor/kingdee-skills/ok-cosmic/agents/openai.yaml +4 -4
- package/vendor/kingdee-skills/ok-cosmic/manifest.json +21 -20
- package/vendor/kingdee-skills/ok-cosmic/ok-cosmic-intro.html +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/a-layer-rules.json +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/anti-patterns.md +2 -2
- package/vendor/kingdee-skills/ok-cosmic/rules/coding-preferences.md +4 -4
- package/vendor/kingdee-skills/ok-cosmic/rules/constraints.md +3 -3
- package/vendor/kingdee-skills/ok-cosmic/rules/decision-matrix.md +8 -8
- package/vendor/kingdee-skills/ok-cosmic/rules/intent-routing.md +1 -1
- package/vendor/kingdee-skills/ok-cosmic/rules/post-check.md +19 -18
- package/vendor/kingdee-skills/ok-ksql/SKILL.md +9 -9
- package/vendor/kingdee-skills/ok-ksql/manifest.json +2 -1
- package/vendor/kingdee-skills/ok-ksql/references/ksql-datafix.md +2 -2
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/pattern-matcher.py +0 -336
- package/vendor/kingdee-skills/kingdee-cosmic-reviewer/scripts/review-score-calculator.py +0 -121
- package/vendor/kingdee-skills/ok-cosmic/CHANGELOG.md +0 -295
- package/vendor/kingdee-skills/ok-cosmic/README.md +0 -460
- package/vendor/kingdee-skills/ok-cosmic/requirements.txt +0 -2
- package/vendor/kingdee-skills/ok-cosmic/scripts/config_loader.py +0 -204
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-api-knowledge.py +0 -910
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-basedata-query.py +0 -359
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-config-check.py +0 -181
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-extpoints-query.py +0 -389
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-form-metadata.py +0 -856
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-check.py +0 -262
- package/vendor/kingdee-skills/ok-cosmic/scripts/cosmic-post-lint.py +0 -293
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/__init__.py +0 -2
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/base.py +0 -393
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/resource_check.py +0 -176
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/scene_check.py +0 -375
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/style_check.py +0 -434
- package/vendor/kingdee-skills/ok-cosmic/scripts/lint/verify_check.py +0 -36
- package/vendor/kingdee-skills/ok-cosmic/scripts/route_client.py +0 -186
- package/vendor/kingdee-skills/ok-cosmic/scripts/script_utils.py +0 -40
- package/vendor/kingdee-skills/ok-cosmic/scripts/sqlite_cache.py +0 -142
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-commons.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/cuslib/kd-cd-cosmic-features.jar +0 -0
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-mac.sh +0 -18
- package/vendor/kingdee-skills/ok-cosmic/setup/setup-windows.bat +0 -53
- package/vendor/kingdee-skills/ok-cosmic/setup/setup.jar +0 -0
- package/vendor/kingdee-skills/ok-ksql/scripts/ksql_lint.py +0 -363
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""场景错配检查 (SCENE-*) — 来源: anti-patterns.md 场景错配表"""
|
|
3
|
-
|
|
4
|
-
import re
|
|
5
|
-
from typing import List, Optional, Set, Tuple
|
|
6
|
-
|
|
7
|
-
from .base import (
|
|
8
|
-
LintIssue,
|
|
9
|
-
Severity,
|
|
10
|
-
analyze_java_context,
|
|
11
|
-
analyze_plugin_context,
|
|
12
|
-
classify_lines,
|
|
13
|
-
code_for_structure,
|
|
14
|
-
detect_listener_interfaces,
|
|
15
|
-
detect_plugin_type,
|
|
16
|
-
parse_java,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
INITIALIZE_UI_PATTERN = re.compile(
|
|
21
|
-
r"getView\s*\(\)\s*\.\s*"
|
|
22
|
-
r"(setEnable|setVisible|updateView|show\w+|setFocus|setMustInput|setCaption)\s*\("
|
|
23
|
-
)
|
|
24
|
-
INITIALIZE_LISTENER_PATTERN = re.compile(r"add\w*Listener[s]?\s*\(")
|
|
25
|
-
BIND_MUTATION_PATTERN = re.compile(
|
|
26
|
-
r"(?:(?:getModel\s*\(\)|\bmodel)\s*\.\s*"
|
|
27
|
-
r"(setValue|deleteEntryRow|createNewEntryRow|insertEntryRow|batchCreateNewEntryRow|setDataEntity)\s*\()"
|
|
28
|
-
r"|(?:\.\s*setValueFast\s*\()"
|
|
29
|
-
)
|
|
30
|
-
META_TABLE_PATTERN = re.compile(r"\bt_meta_[A-Za-z0-9_]+\b", re.IGNORECASE)
|
|
31
|
-
ENTITY_METADATA_CREATE_PATTERN = re.compile(
|
|
32
|
-
r"EntityMetadataCache\s*\.\s*getDataEntityType\s*\([^)]*\)\s*\.\s*createInstance\s*\("
|
|
33
|
-
)
|
|
34
|
-
# SCENE-012: propertyChanged 中无条件 setValue 检测
|
|
35
|
-
_PC_SETVALUE_PATTERN = re.compile(
|
|
36
|
-
r"(?:getModel\s*\(\)\s*\.\s*setValue|model\s*\.\s*setValue|\.\s*setValueFast)\s*\("
|
|
37
|
-
)
|
|
38
|
-
_PC_GUARD_PATTERN = re.compile(r"getProperty\s*\(\s*\)|getChangeSet\s*\(\s*\)")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def check(filepath: str, lines: List[str]) -> List[LintIssue]:
|
|
42
|
-
"""执行场景错配检查,返回问题列表。"""
|
|
43
|
-
issues: List[LintIssue] = []
|
|
44
|
-
listeners = detect_listener_interfaces(lines)
|
|
45
|
-
tree, lang = parse_java(lines)
|
|
46
|
-
method_context, _ = analyze_java_context(lines, tree=tree)
|
|
47
|
-
plugin_context = analyze_plugin_context(lines)
|
|
48
|
-
skip_lines = classify_lines(lines)
|
|
49
|
-
|
|
50
|
-
# 有状态上下文
|
|
51
|
-
_model_alias_vars: Set[str] = set() # 追踪操作插件中 getModel() 赋值的变量名
|
|
52
|
-
current_method: Optional[str] = None
|
|
53
|
-
|
|
54
|
-
# ── 逐行检查 ──
|
|
55
|
-
for i, line in enumerate(lines):
|
|
56
|
-
lineno = i + 1
|
|
57
|
-
code_line = code_for_structure(line)
|
|
58
|
-
method_name = (method_context[i] or "").lower()
|
|
59
|
-
is_op_line = plugin_context[i] == "op"
|
|
60
|
-
|
|
61
|
-
if method_context[i] != current_method:
|
|
62
|
-
_model_alias_vars.clear()
|
|
63
|
-
current_method = method_context[i]
|
|
64
|
-
|
|
65
|
-
# SCENE-001: 操作插件不应调 getView()
|
|
66
|
-
if is_op_line and not skip_lines[i]:
|
|
67
|
-
if re.search(r"this\s*\.\s*getView\s*\(\s*\)", code_line) or re.search(r"getView\(\)", code_line):
|
|
68
|
-
if not re.search(r"(log\.|getContextSample\s*\()", code_line):
|
|
69
|
-
issues.append(LintIssue(
|
|
70
|
-
file=filepath, line=lineno,
|
|
71
|
-
severity=Severity.ERROR,
|
|
72
|
-
rule_id="SCENE-001",
|
|
73
|
-
message="操作插件中调用 getView():操作插件无 UI 上下文",
|
|
74
|
-
fix_hint="使用 log 记录日志或 OpUtils.addErrorMessage() 报告错误",
|
|
75
|
-
source_line=line.strip(),
|
|
76
|
-
))
|
|
77
|
-
|
|
78
|
-
# SCENE-002: 操作插件不应通过 model 操作
|
|
79
|
-
if is_op_line and not skip_lines[i]:
|
|
80
|
-
if re.search(r"this\s*\.\s*getModel\s*\(\s*\)\s*\.\s*setValue", code_line):
|
|
81
|
-
issues.append(LintIssue(
|
|
82
|
-
file=filepath, line=lineno,
|
|
83
|
-
severity=Severity.ERROR,
|
|
84
|
-
rule_id="SCENE-002",
|
|
85
|
-
message="操作插件中调用 getModel().setValue():操作插件不通过 model 操作",
|
|
86
|
-
fix_hint="直接操作 DynamicObject 数据包: dataEntity.set(\"field\", value)",
|
|
87
|
-
source_line=line.strip(),
|
|
88
|
-
))
|
|
89
|
-
else:
|
|
90
|
-
# 追踪 model 别名: Type varName = this.getModel() 或 getModel()
|
|
91
|
-
_model_assign = re.search(
|
|
92
|
-
r'\b(\w+)\s*=\s*(?:this\s*\.\s*)?getModel\s*\(\s*\)', code_line
|
|
93
|
-
)
|
|
94
|
-
if _model_assign:
|
|
95
|
-
_model_alias_vars.add(_model_assign.group(1))
|
|
96
|
-
# 检测 model 别名调用 setValue
|
|
97
|
-
for _mvar in _model_alias_vars:
|
|
98
|
-
if re.search(rf'\b{re.escape(_mvar)}\s*\.\s*setValue\s*\(', code_line):
|
|
99
|
-
issues.append(LintIssue(
|
|
100
|
-
file=filepath, line=lineno,
|
|
101
|
-
severity=Severity.ERROR,
|
|
102
|
-
rule_id="SCENE-002",
|
|
103
|
-
message=f"操作插件中通过 model 变量 '{_mvar}' 调用 setValue():操作插件不通过 model 操作",
|
|
104
|
-
fix_hint="直接操作 DynamicObject 数据包: dataEntity.set(\"field\", value)",
|
|
105
|
-
source_line=line.strip(),
|
|
106
|
-
))
|
|
107
|
-
break
|
|
108
|
-
|
|
109
|
-
# SCENE-003: registerListener 中读数据
|
|
110
|
-
if method_name == "registerlistener" and re.search(r"(getModel|getValue)\s*\(", code_line) and not skip_lines[i]:
|
|
111
|
-
issues.append(LintIssue(
|
|
112
|
-
file=filepath, line=lineno,
|
|
113
|
-
severity=Severity.WARNING,
|
|
114
|
-
rule_id="SCENE-003",
|
|
115
|
-
message="在 registerListener 中调用 getValue():此时数据尚未绑定",
|
|
116
|
-
fix_hint="推迟到 afterBindData() 中处理",
|
|
117
|
-
source_line=line.strip(),
|
|
118
|
-
))
|
|
119
|
-
|
|
120
|
-
# SCENE-006: initialize 中注册监听
|
|
121
|
-
if method_name == "initialize" and INITIALIZE_LISTENER_PATTERN.search(code_line):
|
|
122
|
-
issues.append(LintIssue(
|
|
123
|
-
file=filepath, line=lineno,
|
|
124
|
-
severity=Severity.ERROR,
|
|
125
|
-
rule_id="SCENE-006",
|
|
126
|
-
message="禁止在 initialize() 中注册监听事件",
|
|
127
|
-
fix_hint="将 addXxxListener(...) 挪到 registerListener() 中",
|
|
128
|
-
source_line=line.strip(),
|
|
129
|
-
))
|
|
130
|
-
|
|
131
|
-
# SCENE-007: initialize 中做 UI 状态逻辑
|
|
132
|
-
if method_name == "initialize" and INITIALIZE_UI_PATTERN.search(code_line):
|
|
133
|
-
issues.append(LintIssue(
|
|
134
|
-
file=filepath, line=lineno,
|
|
135
|
-
severity=Severity.ERROR,
|
|
136
|
-
rule_id="SCENE-007",
|
|
137
|
-
message="禁止在 initialize() 中处理 UI 状态或弹窗逻辑",
|
|
138
|
-
fix_hint="将界面控制逻辑挪到 afterBindData() 或正确的 UI 事件中",
|
|
139
|
-
source_line=line.strip(),
|
|
140
|
-
))
|
|
141
|
-
|
|
142
|
-
# SCENE-008: before/afterBindData 中修改数据对象
|
|
143
|
-
if method_name in {"beforebinddata", "afterbinddata"} and BIND_MUTATION_PATTERN.search(code_line):
|
|
144
|
-
issues.append(LintIssue(
|
|
145
|
-
file=filepath, line=lineno,
|
|
146
|
-
severity=Severity.ERROR,
|
|
147
|
-
rule_id="SCENE-008",
|
|
148
|
-
message="禁止在 beforeBindData()/afterBindData() 中修改数据对象",
|
|
149
|
-
fix_hint="将 setValue/分录增删等数据变更挪到 createNewData、afterCreateNewData、propertyChanged 或正确的业务事件",
|
|
150
|
-
source_line=line.strip(),
|
|
151
|
-
))
|
|
152
|
-
|
|
153
|
-
# SCENE-009: 直接访问平台元数据表
|
|
154
|
-
if META_TABLE_PATTERN.search(code_line) and not skip_lines[i]:
|
|
155
|
-
issues.append(LintIssue(
|
|
156
|
-
file=filepath, line=lineno,
|
|
157
|
-
severity=Severity.ERROR,
|
|
158
|
-
rule_id="SCENE-009",
|
|
159
|
-
message="业务代码禁止直接访问平台元数据表 t_meta_xxx",
|
|
160
|
-
fix_hint="使用元数据脚本或平台 API 获取字段/实体信息,不要直接查 t_meta_xxx",
|
|
161
|
-
source_line=line.strip(),
|
|
162
|
-
))
|
|
163
|
-
|
|
164
|
-
# SCENE-010: 用其他实体 createInstance() 构造引用对象
|
|
165
|
-
if ENTITY_METADATA_CREATE_PATTERN.search(code_line):
|
|
166
|
-
issues.append(LintIssue(
|
|
167
|
-
file=filepath, line=lineno,
|
|
168
|
-
severity=Severity.ERROR,
|
|
169
|
-
rule_id="SCENE-010",
|
|
170
|
-
message="直接用 EntityMetadataCache.getDataEntityType(...).createInstance() 构造对象,容易出现引用类型不一致",
|
|
171
|
-
fix_hint="优先通过当前实体属性复杂类型或当前实体元数据 createInstance()",
|
|
172
|
-
source_line=line.strip(),
|
|
173
|
-
))
|
|
174
|
-
|
|
175
|
-
# ── 方法级检查: propertyChanged 无条件 setValue ──
|
|
176
|
-
_check_unguarded_setvalue_in_property_changed(filepath, lines, method_context, skip_lines, issues)
|
|
177
|
-
|
|
178
|
-
# ── 文件级检查: Listener 注册 ──
|
|
179
|
-
if listeners:
|
|
180
|
-
has_register = any(
|
|
181
|
-
re.search(r"void\s+registerListener\s*\(", l) for l in lines
|
|
182
|
-
)
|
|
183
|
-
if not has_register:
|
|
184
|
-
# SCENE-004: 实现了 Listener 但没有 registerListener 方法
|
|
185
|
-
issues.append(LintIssue(
|
|
186
|
-
file=filepath, line=1,
|
|
187
|
-
severity=Severity.WARNING,
|
|
188
|
-
rule_id="SCENE-004",
|
|
189
|
-
message=f"实现了 Listener 接口 ({', '.join(listeners)}) 但未找到 registerListener 方法",
|
|
190
|
-
fix_hint="在 registerListener() 中调用 addXxxListener() 注册监听",
|
|
191
|
-
))
|
|
192
|
-
else:
|
|
193
|
-
# SCENE-005: 有 registerListener 但没有 addXxxListener 调用
|
|
194
|
-
# 使用 AST 树精确定位 registerListener 方法体
|
|
195
|
-
has_add_listener = False
|
|
196
|
-
try:
|
|
197
|
-
import tree_sitter
|
|
198
|
-
q = tree_sitter.Query(lang, "(method_declaration name: (identifier) @name body: (block) @body)")
|
|
199
|
-
for match in q.matches(tree.root_node):
|
|
200
|
-
if len(match) > 1 and isinstance(match[1], dict):
|
|
201
|
-
match_dict = match[1]
|
|
202
|
-
m_name_nodes = match_dict.get("name", [])
|
|
203
|
-
m_body_nodes = match_dict.get("body", [])
|
|
204
|
-
if m_name_nodes and m_body_nodes:
|
|
205
|
-
if m_name_nodes[0].text.decode('utf-8') == "registerListener":
|
|
206
|
-
raw_body_text = m_body_nodes[0].text.decode('utf-8')
|
|
207
|
-
clean_body_lines = [code_for_structure(l) for l in raw_body_text.splitlines()]
|
|
208
|
-
body_text = "\n".join(clean_body_lines)
|
|
209
|
-
if re.search(r"add\w*Listener[s]?\s*\(", body_text):
|
|
210
|
-
has_add_listener = True
|
|
211
|
-
except Exception:
|
|
212
|
-
# 降级:如果 AST 失败,假设通过避免误报
|
|
213
|
-
has_add_listener = True
|
|
214
|
-
|
|
215
|
-
if not has_add_listener:
|
|
216
|
-
issues.append(LintIssue(
|
|
217
|
-
file=filepath, line=1,
|
|
218
|
-
severity=Severity.WARNING,
|
|
219
|
-
rule_id="SCENE-005",
|
|
220
|
-
message=f"实现了 Listener 接口 ({', '.join(listeners)}) 但 registerListener 中未调用 addXxxListener()",
|
|
221
|
-
fix_hint="在 registerListener() 中调用对应的 addXxxListener(this) 注册",
|
|
222
|
-
))
|
|
223
|
-
|
|
224
|
-
# ── 文件级检查: 继承型插件 @Override 不调 super ──
|
|
225
|
-
is_op, is_ui, is_other = detect_plugin_type(lines)
|
|
226
|
-
is_inheritable = is_op or is_ui or is_other
|
|
227
|
-
if is_inheritable:
|
|
228
|
-
_check_missing_super(filepath, lines, plugin_context, issues, tree=tree, lang=lang)
|
|
229
|
-
|
|
230
|
-
return issues
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def _check_unguarded_setvalue_in_property_changed(
|
|
234
|
-
filepath: str, lines: List[str],
|
|
235
|
-
method_context: List, skip_lines: List[bool], issues: List[LintIssue],
|
|
236
|
-
):
|
|
237
|
-
"""SCENE-012: propertyChanged 中调用 setValue 但未判断 getProperty() 或 getChangeSet(),死循环风险。"""
|
|
238
|
-
# 收集 propertyChanged 方法体的行范围
|
|
239
|
-
ranges: List[Tuple[int, int]] = []
|
|
240
|
-
start: int = -1
|
|
241
|
-
for i, mc in enumerate(method_context):
|
|
242
|
-
if mc and mc.lower() == "propertychanged":
|
|
243
|
-
if start < 0:
|
|
244
|
-
start = i
|
|
245
|
-
else:
|
|
246
|
-
if start >= 0:
|
|
247
|
-
ranges.append((start, i))
|
|
248
|
-
start = -1
|
|
249
|
-
if start >= 0:
|
|
250
|
-
ranges.append((start, len(lines)))
|
|
251
|
-
|
|
252
|
-
for s, e in ranges:
|
|
253
|
-
has_set_value = False
|
|
254
|
-
has_guard = False
|
|
255
|
-
first_sv_line: int = -1
|
|
256
|
-
for i in range(s, e):
|
|
257
|
-
if skip_lines[i]:
|
|
258
|
-
continue
|
|
259
|
-
code = code_for_structure(lines[i])
|
|
260
|
-
if _PC_SETVALUE_PATTERN.search(code):
|
|
261
|
-
if not has_set_value:
|
|
262
|
-
first_sv_line = i
|
|
263
|
-
has_set_value = True
|
|
264
|
-
if _PC_GUARD_PATTERN.search(code):
|
|
265
|
-
has_guard = True
|
|
266
|
-
if has_set_value and not has_guard:
|
|
267
|
-
issues.append(LintIssue(
|
|
268
|
-
file=filepath, line=first_sv_line + 1,
|
|
269
|
-
severity=Severity.ERROR,
|
|
270
|
-
rule_id="SCENE-012",
|
|
271
|
-
message="propertyChanged 中调用 setValue 但未判断 e.getProperty().getName(),可能导致死循环(栈溢出)",
|
|
272
|
-
fix_hint="先用 e.getProperty().getName().equals(\"目标字段\") 守卫,赋值前比对新旧值避免无限递归",
|
|
273
|
-
source_line=lines[first_sv_line].strip(),
|
|
274
|
-
))
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
# 继承型插件生命周期方法(需要调 super 的方法名,全小写)
|
|
278
|
-
_LIFECYCLE_METHODS = {
|
|
279
|
-
"initialize", "registerlistener", "preopenform",
|
|
280
|
-
"createnewdata", "aftercreatenewdata", "loaddata", "afterloaddata",
|
|
281
|
-
"beforebinddata", "afterbinddata", "aftercopydata",
|
|
282
|
-
"propertychanged", "beforedooperation", "afterdooperation",
|
|
283
|
-
"beforeitemclick", "itemclick", "beforeclick", "click",
|
|
284
|
-
"beforef7select", "confirmcallback", "closedcallback",
|
|
285
|
-
"clientcallback", "beforeclosed", "customevent",
|
|
286
|
-
"afteraddrow", "afterdeleterow", "afterdeleteentry", "destory",
|
|
287
|
-
"onpreparepropertys", "onaddvalidators",
|
|
288
|
-
"beforeexecuteoperationtransaction", "beginoperationtransaction",
|
|
289
|
-
"endoperationtransaction", "afterexecuteoperationtransaction",
|
|
290
|
-
"onreturnoperation",
|
|
291
|
-
"setfilter", "beforedoselectrow",
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
_OVERRIDE_PATTERN = re.compile(r"@Override")
|
|
295
|
-
_METHOD_NAME_PATTERN = re.compile(
|
|
296
|
-
r"(?:public|protected)\s+\w[\w<>\[\], ?.]*\s+([A-Za-z_]\w*)\s*\("
|
|
297
|
-
)
|
|
298
|
-
_SUPER_CALL_PATTERN_TEMPLATE = r"\bsuper\s*\.\s*{method}\s*\("
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
def _check_missing_super(filepath: str, lines: List[str],
|
|
302
|
-
plugin_context: List, issues: List[LintIssue],
|
|
303
|
-
tree=None, lang=None):
|
|
304
|
-
"""检测继承型插件中 @Override 生命周期方法是否遗漏 super 调用。"""
|
|
305
|
-
try:
|
|
306
|
-
import tree_sitter
|
|
307
|
-
if tree is None or lang is None:
|
|
308
|
-
tree, lang = parse_java(lines)
|
|
309
|
-
|
|
310
|
-
q = tree_sitter.Query(lang, """
|
|
311
|
-
(method_declaration
|
|
312
|
-
name: (identifier) @name
|
|
313
|
-
body: (block) @body
|
|
314
|
-
) @method
|
|
315
|
-
""")
|
|
316
|
-
|
|
317
|
-
for match in q.matches(tree.root_node):
|
|
318
|
-
if len(match) < 2 or not isinstance(match[1], dict):
|
|
319
|
-
continue
|
|
320
|
-
match_dict = match[1]
|
|
321
|
-
|
|
322
|
-
method_nodes = match_dict.get("method", [])
|
|
323
|
-
if not method_nodes: continue
|
|
324
|
-
method_node = method_nodes[0]
|
|
325
|
-
|
|
326
|
-
# Check @Override
|
|
327
|
-
is_override = False
|
|
328
|
-
for child in method_node.children:
|
|
329
|
-
if child.type == "modifiers":
|
|
330
|
-
for mod in child.children:
|
|
331
|
-
if mod.type == "marker_annotation" and "Override" in mod.text.decode('utf-8'):
|
|
332
|
-
is_override = True
|
|
333
|
-
|
|
334
|
-
if not is_override:
|
|
335
|
-
continue
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
# Get Method Name
|
|
339
|
-
name_nodes = match_dict.get("name", [])
|
|
340
|
-
if not name_nodes: continue
|
|
341
|
-
method_name = name_nodes[0].text.decode('utf-8')
|
|
342
|
-
|
|
343
|
-
if method_name.lower() not in _LIFECYCLE_METHODS:
|
|
344
|
-
continue
|
|
345
|
-
|
|
346
|
-
# Check Plugin Context for this method
|
|
347
|
-
# Just take the context of the line where method name is
|
|
348
|
-
method_line = name_nodes[0].start_point[0]
|
|
349
|
-
if method_line < len(plugin_context) and plugin_context[method_line] not in {"op", "ui", "other"}:
|
|
350
|
-
continue
|
|
351
|
-
|
|
352
|
-
# Check if super.methodName() exists in the body
|
|
353
|
-
body_nodes = match_dict.get("body", [])
|
|
354
|
-
if not body_nodes: continue
|
|
355
|
-
raw_body_text = body_nodes[0].text.decode('utf-8')
|
|
356
|
-
|
|
357
|
-
clean_body_lines = [code_for_structure(l) for l in raw_body_text.splitlines()]
|
|
358
|
-
body_text = "\n".join(clean_body_lines)
|
|
359
|
-
|
|
360
|
-
super_pattern = re.compile(
|
|
361
|
-
_SUPER_CALL_PATTERN_TEMPLATE.format(method=re.escape(method_name))
|
|
362
|
-
)
|
|
363
|
-
|
|
364
|
-
if not super_pattern.search(body_text):
|
|
365
|
-
issues.append(LintIssue(
|
|
366
|
-
file=filepath, line=method_line + 1,
|
|
367
|
-
severity=Severity.WARNING,
|
|
368
|
-
rule_id="SCENE-011",
|
|
369
|
-
message=f"继承型插件 @Override {method_name}() 未调用 super.{method_name}(),基类初始化逻辑可能不执行",
|
|
370
|
-
fix_hint=f"在方法体首行添加 super.{method_name}(...);接口型插件(如 IWorkflowPlugin)无需此调用",
|
|
371
|
-
source_line=method_name,
|
|
372
|
-
))
|
|
373
|
-
except Exception:
|
|
374
|
-
# 降级:如果 AST 失败,不做检查,避免由于解析失败导致的误报
|
|
375
|
-
pass
|