使用 libcs​​t 在 Python 中查找紧跟 raise 节点的 if 节点

Find if-nodes that are immediately followed by a raise-node in Python with libcst

现在我正在为大学课程做一个项目。我得到了一些随机函数,其中大部分在代码中的某处都有一个 if-raise-statement。

我试图找到那些,但只有那 1 或 2 行。我将函数转换为 AST,然后使用 libcs​​t 访问它。我扩展了访问者 class,搜索 if-nodes,然后匹配 raise-nodes。然而,这也匹配并保存类似 if-if-raise 或 if-else-raise 的语句。

我希望有人能帮助我如何修改匹配器以只匹配if-nodes直接跟在1个raise节点。 (序列通配符匹配器会很棒,但据我所知,它们无法匹配以查找节点序列。)

import libcst as cst
import libcst.matchers as m

class FindIfRaise(cst.CSTVisitor):

    if_raise = [] 

    # INIT
    def __init__(self):
        self.if_raise = []

    def visit_If(self, node: cst.If):
        try:
            if m.findall(node, m.Raise()):
                self.if_raise.append(node)

在此先感谢您的帮助。

您可以递归遍历 cst,而不是节点访问者模式,通过每个 cst 对象的 body 属性进行导航。这样您就可以跟踪您的深度,检查同级 if 语句,并且仅在满足所需条件时生成 raise 语句:

import libcst as cst
def walk(ct, p = []):
  bd = ct
  while (not isinstance(bd:=getattr(bd, 'body', []), list)): pass
  for i, t in enumerate(bd):
     if isinstance(t, cst._nodes.statement.Raise):
        f = False
        for i in p[::-1]:
           if not isinstance(i, (cst._nodes.statement.IndentedBlock, cst._nodes.statement.SimpleStatementLine)):
              f = isinstance(i, cst._nodes.statement.If)
              break
        if f: yield t
     elif isinstance(t, cst._nodes.statement.If):
        if t.orelse is None and (i == len(bd) - 1 or not isinstance(bd[i + 1], cst._nodes.statement.If)):
           yield from walk(t, p + [t])
     else:
         yield from walk(t, p + [t])
s = """
if something:
   raise Exception
if something_else:
   pass
"""
print([*walk(cst.parse_module(s))]) #[], since `if something` is followed by another if-statement
s1 = """
if something:
   raise Exception
elif something_else:
   pass
"""
print([*walk(cst.parse_module(s1))]) #[], since `if something` is followed by an elif-statement
s2 = """
if something:
   raise Exception
for i in range(10): pass
"""
print([*walk(cst.parse_module(s2))]) #[Raise(
#    exc=Name(
#        value='Exception',
#        lpar=[],
#        rpar=[],
#    ),
#    cause=None,
#    whitespace_after_raise=SimpleWhitespace(
#        value=' ',
#    ),
#    semicolon=MaybeSentinel.DEFAULT,
#)]