Найдите узлы if, за которыми сразу следует узел повышения в Python с помощью libcst

сейчас я работаю над проектом для университетского курса. У меня есть несколько случайных функций, и у большинства из них где-то в коде есть оператор if-raise.

Я пытаюсь найти такие, но только эти 1 или 2 строки. Я преобразовываю функции в AST, а затем посещаю его с помощью libcst. Я расширяю класс посетителя, ищу узлы if, а затем сопоставляю узлы повышения. Однако это также соответствует и сохраняет операторы типа if-if-raise или if-else-raise.

Я надеюсь, что кто-нибудь может помочь мне в том, как изменить сопоставитель так, чтобы он соответствовал только узлам if, за которыми непосредственно следует 1 узел повышения. (Сопоставители подстановочных знаков последовательности были бы потрясающими, но, насколько я понимаю, их нельзя сопоставлять для поиска последовательностей узлов.)

      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)

Заранее благодарю за любую помощь.

2 ответа

Вы хотели бы что-то вроде этого:

      import libcst.matchers as m

maybe_more = m.AtLeastN(m.DoNotCare(), n=0)  # anything or nothing
raise_block = [m.Raise(), maybe_more]  # raise followed by anything or nothing
single_line_body = m.SimpleStatementSuite(body=raise_block)
multi_line_body = m.IndentedBlock(
    body=[m.SimpleStatementLine(body=raise_block), maybe_more]
)
if_raise = m.If(body=single_line_body | multi_line_body)

Это должно соответствовать всем следующимifзаявления:

      if foo: raise Bar(); blah()
if foo: raise Bar()
if foo:
    raise Bar()
if foo:
    raise Bar()
    blah()
if foo:
    raise Bar(); blah()

Вместо шаблона посетителя узла вы можете рекурсивно перемещаться по bodyатрибут каждого cstобъект. Таким образом, вы можете отслеживать свою глубину, проверять наличие братьев и сестер. 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,
#)]
Другие вопросы по тегам