Spaces:
Sleeping
Sleeping
| import ast | |
| from dataclasses import dataclass | |
| class Graph: | |
| nodes: list[dict[str, str]] | |
| edges: list[dict[str, str]] | |
| def _call_target_name(node: ast.AST) -> str | None: | |
| if isinstance(node, ast.Name): | |
| return node.id | |
| if isinstance(node, ast.Attribute) and isinstance(node.attr, str): | |
| return node.attr | |
| return None | |
| def build_graph(code: str) -> Graph: | |
| tree = ast.parse(code) | |
| functions = {} | |
| classes: dict[str, list[str]] = {} | |
| for node in tree.body: | |
| if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): | |
| functions[node.name] = node | |
| elif isinstance(node, ast.ClassDef): | |
| method_names = [] | |
| for child in node.body: | |
| if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)): | |
| method_names.append(child.name) | |
| classes[node.name] = method_names | |
| nodes: list[dict[str, str]] = [] | |
| edges: list[dict[str, str]] = [] | |
| for class_name, method_names in classes.items(): | |
| nodes.append({"id": class_name, "label": class_name, "type": "class"}) | |
| for method_name in method_names: | |
| method_id = f"{class_name}.{method_name}" | |
| nodes.append({"id": method_id, "label": method_name, "type": "method"}) | |
| edges.append({"source": class_name, "target": method_id, "type": "contains"}) | |
| for func_name in functions: | |
| nodes.append({"id": func_name, "label": func_name, "type": "function"}) | |
| known_nodes = {node["id"] for node in nodes} | |
| call_edges = set() | |
| def add_call_edge(source: str, target: str) -> None: | |
| if source == target: | |
| return | |
| if target not in known_nodes: | |
| return | |
| call_edges.add((source, target)) | |
| for func_name, func_node in functions.items(): | |
| for call in [n for n in ast.walk(func_node) if isinstance(n, ast.Call)]: | |
| target = _call_target_name(call.func) | |
| if target is None: | |
| continue | |
| if target in functions: | |
| add_call_edge(func_name, target) | |
| for class_name, method_names in classes.items(): | |
| for node in tree.body: | |
| if isinstance(node, ast.ClassDef) and node.name == class_name: | |
| for child in node.body: | |
| if not isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)): | |
| continue | |
| source_id = f"{class_name}.{child.name}" | |
| for call in [n for n in ast.walk(child) if isinstance(n, ast.Call)]: | |
| target = call.func | |
| if isinstance(target, ast.Name): | |
| if target.id in functions: | |
| add_call_edge(source_id, target.id) | |
| continue | |
| if isinstance(target, ast.Attribute): | |
| if isinstance(target.value, ast.Name): | |
| if target.value.id == "self": | |
| target_id = f"{class_name}.{target.attr}" | |
| add_call_edge(source_id, target_id) | |
| elif target.value.id in classes: | |
| target_id = f"{target.value.id}.{target.attr}" | |
| add_call_edge(source_id, target_id) | |
| for source, target in sorted(call_edges): | |
| edges.append({"source": source, "target": target, "type": "calls"}) | |
| return Graph(nodes=nodes, edges=edges) | |