Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions src/wardline/scanner/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,16 +453,25 @@ def _bind_call_site_arguments_to_parameters(
return result

def _iter_l2_body_nodes(node: ast.FunctionDef | ast.AsyncFunctionDef) -> Iterator[ast.AST]:
def walk(current: ast.AST) -> Iterator[ast.AST]:
for child in ast.iter_child_nodes(current):
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)):
stack: list[ast.AST] = list(reversed(node.body))
skip_types = (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Lambda)
while stack:
curr = stack.pop()
yield curr

# Inline ast.iter_child_nodes to avoid function call overhead
for field_name in reversed(curr._fields):
try:
field_value = getattr(curr, field_name)
except AttributeError:
continue
yield child
yield from walk(child)

for stmt in node.body:
yield stmt
yield from walk(stmt)
if isinstance(field_value, list):
for item in reversed(field_value):
if isinstance(item, ast.AST) and not isinstance(item, skip_types):
stack.append(item)
elif isinstance(field_value, ast.AST) and not isinstance(field_value, skip_types):
stack.append(field_value)

def _assignment_targets(node: ast.AST) -> list[ast.expr]:
if isinstance(node, ast.Assign):
Expand Down