feat: py_detect L3 数据流引擎 (部分路径未定义就用 E004)
- _dataflow_check: 对每个函数收集局部变量(参数+赋值目标, 排除 global/nonlocal), 按路径合并扫描 - if/elif: 合并点 defined = 两分支交集 (只有都赋值才确定) - for/while: 循环体赋值可能 0 次执行 → 不贡献确定; 循环体内 target 确定有值; orelse 用进入前集合 - try: 主路径赋值算确定, except 分支独立不合并; with/delete/return/raise 处理 - 保守原则: 宁漏勿误, 只报确定会炸的路径 - E004 动态修复建议: 建议使用前赋初值 x = None 或确保所有分支赋值 - 版本 1.3.0; 新增 12 项数据流测试; 相关套件 275 passed 零回归 - 至此 L1 规则 + L2 jedi 语义 + L3 数据流 三代引擎齐备
This commit is contained in:
1 parent
4fb9392db4
commit
413f66deb8
2 files changed
+193
-1
No files matched your search
@@ -311,6 +311,62 @@ class TestJediBackend:
|
||||
assert "E002" not in {d["code"] for d in issues}
|
||||
|
||||
|
||||
# ── L3 数据流: 部分路径未定义就用 (E004) ──
|
||||
class TestDataFlow:
|
||||
def test_if_partial_path(self, pd):
|
||||
src = "def f(flag):\n if flag:\n result = 1\n return result\n"
|
||||
assert 4 in code_lines(pd.analyze(src, "t.py"), "E004")
|
||||
|
||||
def test_if_else_both_defined_ok(self, pd):
|
||||
src = "def f(flag):\n if flag:\n r = 1\n else:\n r = 2\n return r\n"
|
||||
assert "E004" not in {d["code"] for d in pd.analyze(src, "t.py")}
|
||||
|
||||
def test_for_zero_iteration(self, pd):
|
||||
src = "def f(items):\n for x in items:\n total = x\n return total\n"
|
||||
assert 4 in code_lines(pd.analyze(src, "t.py"), "E004")
|
||||
|
||||
def test_for_loop_var_defined_inside(self, pd):
|
||||
src = "def f(items):\n total = 0\n for x in items:\n total += x\n return total\n"
|
||||
assert "E004" not in {d["code"] for d in pd.analyze(src, "t.py")}
|
||||
|
||||
def test_sequential_ok(self, pd):
|
||||
src = "def f():\n result = 1\n return result\n"
|
||||
assert "E004" not in {d["code"] for d in pd.analyze(src, "t.py")}
|
||||
|
||||
def test_param_ok(self, pd):
|
||||
src = "def f(x):\n return x\n"
|
||||
assert "E004" not in {d["code"] for d in pd.analyze(src, "t.py")}
|
||||
|
||||
def test_global_in_function_ok(self, pd):
|
||||
src = "g = 1\ndef f():\n return g\n"
|
||||
assert "E004" not in {d["code"] for d in pd.analyze(src, "t.py")}
|
||||
|
||||
def test_augassign_before_def(self, pd):
|
||||
src = "def f():\n x = x + 1\n return x\n"
|
||||
assert 2 in code_lines(pd.analyze(src, "t.py"), "E004")
|
||||
|
||||
def test_elif_missing_branch(self, pd):
|
||||
src = "def f(a):\n if a > 0:\n v = 1\n elif a < 0:\n v = 2\n return v\n"
|
||||
assert 6 in code_lines(pd.analyze(src, "t.py"), "E004")
|
||||
|
||||
def test_del_then_use(self, pd):
|
||||
src = "def f():\n x = 1\n del x\n return x\n"
|
||||
assert 4 in code_lines(pd.analyze(src, "t.py"), "E004")
|
||||
|
||||
def test_class_method_path(self, pd):
|
||||
src = "class A:\n def m(self, flag):\n if flag:\n r = 1\n return r\n"
|
||||
assert 5 in code_lines(pd.analyze(src, "t.py"), "E004")
|
||||
|
||||
def test_e004_dynamic_fix(self, pd):
|
||||
src = "def f(flag):\n if flag:\n result = 1\n return result\n"
|
||||
issues = pd.analyze(src, "t.py")
|
||||
fix = ""
|
||||
for d in issues:
|
||||
if d["code"] == "E004":
|
||||
fix = d.get("fix", "")
|
||||
assert "result = None" in fix, fix
|
||||
|
||||
|
||||
# ── code_editor 写文件后自动检测集成 ──
|
||||
class TestAutoDetectIntegration:
|
||||
"""AI 用 code_editor 改完 .py 文件后,系统自动跑 py_detect"""
|
||||
|
||||
Reference in new issue
Block a user