| 12 | |
| 13 | |
| 14 | class VariableVisitor(ast.NodeVisitor): |
| 15 | def __init__(self) -> None: |
| 16 | self.global_vars = set() # Variables defined globally |
| 17 | self.used_global_vars = set() # Variables used and defined globally |
| 18 | self.imported_modules = set() # Local names introduced by imports (aliases) |
| 19 | self.imported_packages = set() # Top-level package names from import sources |
| 20 | self.scope_stack = [] # Stack to track scopes |
| 21 | self.function_globals = set() # Global variables declared in current function |
| 22 | |
| 23 | def current_scope_is_global(self): |
| 24 | # If the scope stack is empty, we are at the global level |
| 25 | return not self.scope_stack |
| 26 | |
| 27 | def visit_Global(self, node): |
| 28 | for name in node.names: |
| 29 | self.function_globals.add(name) |
| 30 | self.generic_visit(node) |
| 31 | |
| 32 | def visit_Assign(self, node): |
| 33 | for target in node.targets: |
| 34 | if isinstance(target, ast.Name): |
| 35 | if self.current_scope_is_global(): |
| 36 | self.global_vars.add(target.id) |
| 37 | self.generic_visit(node) |
| 38 | |
| 39 | def visit_AugAssign(self, node): |
| 40 | if isinstance(node.target, ast.Name): |
| 41 | if self.current_scope_is_global(): |
| 42 | self.global_vars.add(node.target.id) |
| 43 | self.generic_visit(node) |
| 44 | |
| 45 | def visit_AnnAssign(self, node): |
| 46 | target = node.target |
| 47 | if isinstance(target, ast.Name) and self.current_scope_is_global(): |
| 48 | self.global_vars.add(target.id) |
| 49 | self.generic_visit(node) |
| 50 | |
| 51 | def visit_NamedExpr(self, node): |
| 52 | if isinstance(node.target, ast.Name) and self.current_scope_is_global(): |
| 53 | self.global_vars.add(node.target.id) |
| 54 | self.generic_visit(node) |
| 55 | |
| 56 | def visit_ClassDef(self, node): |
| 57 | if self.current_scope_is_global(): |
| 58 | self.global_vars.add(node.name) |
| 59 | self.scope_stack.append(node.name) # Enter class scope |
| 60 | self.generic_visit(node) |
| 61 | self.scope_stack.pop() # Exit class scope |
| 62 | |
| 63 | def visit_FunctionDef(self, node): |
| 64 | if self.current_scope_is_global(): |
| 65 | self.global_vars.add(node.name) |
| 66 | |
| 67 | prev_function_globals = self.function_globals |
| 68 | self.function_globals = set() |
| 69 | |
| 70 | self.scope_stack.append(node.name) # Enter function scope |
| 71 | self.generic_visit(node) |
no outgoing calls
no test coverage detected