From 260eb23cb1df4db62fcd650f876737db79f378c7 Mon Sep 17 00:00:00 2001 From: shuiyu67 Date: Fri, 7 Aug 2026 09:14:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BB=BA=E8=AE=AE?= =?UTF-8?q?=E8=B4=A8=E9=87=8F=E6=89=93=E7=A3=A8=20(=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=E5=8F=91=E7=8E=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - W004: 修复代码里的「默认值」占位符 → ast.unparse 提取真实默认值 (def f(items=None): items = items if items is not None else []) - E004 del 场景: 专门建议「删了别再用 / 去掉 del」而非笼统赋初值 (del_flag 通过 analyzer._flow_del_names 传递) - 拼写纠错保持「你是不是想写」提示性质 (语义上不一定正确, 措辞已保守) - 304 passed 零回归 --- plugins/py_detect.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/plugins/py_detect.py b/plugins/py_detect.py index ddccb7a..f25d5ea 100644 --- a/plugins/py_detect.py +++ b/plugins/py_detect.py @@ -86,7 +86,8 @@ def _smart_explain(code: str, ctx: dict) -> Tuple[str, str]: func = ctx.get("func", "") arg = ctx.get("arg", "") if func and arg: - fix = (f"修复: 改写成 def {func}({arg}=None): {arg} = {arg} if {arg} is not None else 默认值" + default_text = ctx.get("default_text", "默认值") + fix = (f"修复: 改写成 def {func}({arg}=None): {arg} = {arg} if {arg} is not None else {default_text}" f"——避免所有调用共享同一个可变对象。") return title, fix @@ -138,8 +139,12 @@ def _smart_explain(code: str, ctx: dict) -> Tuple[str, str]: elif code == "E004": name = ctx.get("name", "") if name: - fix = (f"修复: {name} 在某条执行路径上可能没赋值就被使用(运行时 UnboundLocalError)。" - f"建议在使用前先赋初值: {name} = None(或按逻辑给默认值),或确保所有分支都赋值。") + if ctx.get("del_flag"): + fix = (f"修复: {name} 被 del 之后又使用(运行时 UnboundLocalError)。" + f"若不再需要该变量,删除 del 语句;若仍需使用,请去掉 del 或在使用后再删。") + else: + fix = (f"修复: {name} 在某条执行路径上可能没赋值就被使用(运行时 UnboundLocalError)。" + f"建议在使用前先赋初值: {name} = None(或按逻辑给默认值),或确保所有分支都赋值。") return title, fix return title, base_fix @@ -331,9 +336,13 @@ class _ScopeAnalyzer(ast.NodeVisitor): if n_defaults: for arg, d in zip(node.args.args[-n_defaults:], node.args.defaults): if isinstance(d, (ast.List, ast.Dict, ast.Set)): + try: + default_text = ast.unparse(d) + except Exception: + default_text = "默认值" self._add("W004", node.lineno, node.col_offset, extra=f"可变默认参数: {node.name}.{arg.arg}", - func=node.name, arg=arg.arg) + func=node.name, arg=arg.arg, default_text=default_text) for d in node.args.kw_defaults: if d is not None and isinstance(d, (ast.List, ast.Dict, ast.Set)): self._add("W004", node.lineno, node.col_offset, @@ -562,8 +571,10 @@ def _flow_maybe_report(node, locals_set, defined, analyzer): if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load): nid = node.id if nid in locals_set and nid not in defined: + del_names = getattr(analyzer, "_flow_del_names", set()) or set() analyzer._add("E004", node.lineno, node.col_offset, - extra=f"可能未定义: {nid}", name=nid) + extra=f"可能未定义: {nid}", name=nid, + del_flag=nid in del_names) def _flow_check_loads(node, locals_set, defined, analyzer): @@ -826,6 +837,14 @@ def _dataflow_check(tree, analyzer): if isinstance(sub, (ast.Global, ast.Nonlocal)): for n in sub.names: locals_set.discard(n) + # 收集被 del 的变量 (E004 建议区分「del 后使用」场景) + del_names = set() + for sub in ast.walk(fn): + if isinstance(sub, ast.Delete): + for t in sub.targets: + if isinstance(t, ast.Name): + del_names.add(t.id) + analyzer._flow_del_names = del_names defined = {a.arg for a in fn.args.posonlyargs + fn.args.args + fn.args.kwonlyargs} if fn.args.vararg: