From 10fca2d34c1992c2abd0329c506b8ff402647be7 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Tue, 4 Nov 2025 15:33:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0.=20=D0=92=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B5.=20=D0=A1=D1=82=D0=B0=D1=82=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=B0=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + check-graph.py | 164 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 check-graph.py diff --git a/README.md b/README.md index 3fc369dd..aa9e0393 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,4 @@ - После клонирования запустить скрипт `git-hooks-config.sh` - В среде **Windows** запуск скрипта `git-hooks-config.sh` производить из оболочки `git bash` - Нужно обновить `settings.json` +- поиск циклических зависимостей diff --git a/check-graph.py b/check-graph.py new file mode 100644 index 00000000..73edca93 --- /dev/null +++ b/check-graph.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +import os +import re +import networkx as nx + +# pip install networkx plotly numpy scipy + +def build_class_graph(directory): + """Builds a directed acyclic graph of class dependencies.""" + # Create a graph to store the dependencies + graph = nx.DiGraph() + dependencies = {} + + # Find all .gd files in the directory + for root, dirs, files in os.walk(directory): + for file in files: + if not file.endswith(".gd"): + continue + + file_path = os.path.join(root, file) + # Find the class name in the file + with open(file_path, "r", encoding="utf-8") as f: + contents = f.read() + match = re.search(r"^class_name\s+(\w+)", contents, re.MULTILINE) + if not match: + continue + + class_name = match.group(1) + graph.add_node(class_name) + + # Find the dependencies of the class + for dep_match in re.finditer( + r"^\s*extends\s+(\w+)", contents, re.MULTILINE + ): + dep_name = dep_match.group(1) + graph.add_edge(class_name, dep_name) + + # Find all uses of the class name in other files + for root2, dirs2, files2 in os.walk(directory): + for file2 in files2: + if not file2.endswith(".gd"): + continue + + if file_path == os.path.join(root2, file2): + continue + + with open(os.path.join(root2, file2), "r", encoding="utf-8") as f2: + contents2 = f2.read() + if re.search(rf"\b{class_name}\b", contents2): + print( + file2.ljust(30, " "), + f"--> class_name {class_name}".ljust(50, " "), + f" from {file}", + ) + graph.add_edge(class_name, file2) + dependencies[file2] = file + + pos = nx.planar_layout(graph) + + # Set the node positions in the graph + nx.set_node_attributes(graph, pos, "pos") + + return graph, dependencies + + +def debug_graph(graph): + """Builds and visualizes a graph of class dependencies in the directory.""" + import plotly.graph_objects as go + + # Create the plotly figure + fig = go.Figure() + + # Add the nodes to the figure + for node in graph.nodes(): + x, y = graph.nodes[node]["pos"] + fig.add_trace( + go.Scatter( + x=[x], + y=[y], + text=[node], + hovertext=[f"Class: {node}"], + mode="markers", + marker=dict( + symbol="circle", size=20, line=dict(width=1, color="black"), + ), + name=node, + ) + ) + + # Add the edges to the figure + for edge in graph.edges(): + x0, y0 = graph.nodes[edge[0]]["pos"] + x1, y1 = graph.nodes[edge[1]]["pos"] + fig.add_trace( + go.Scatter( + x=[x0, x1], + y=[y0, y1], + text=[f"{edge[0]} -> {edge[1]}", ""], + hovertext=[f"Depends on: {edge[1]}", ""], + mode="lines+markers+text", + line=dict(width=2, color="black"), + marker=dict(size=0), + textposition="middle right", + showlegend=True, + ) + ) + + # Customize the layout and style of the figure + fig.update_layout( + title="Class Dependency Graph", + title_font_size=24, + margin=dict(l=20, r=20, t=50, b=20), + hovermode="closest", + plot_bgcolor="white", + showlegend=True, + ) + + # Show the figure + fig.show() + + +def find_circular_dependencies(dependencies): + """Finds circular dependencies between classes in the directory.""" + + circular_dependencies = [] + + for file, dependency in dependencies.items(): + visited = set() + path = [file] + + while dependency and dependency not in visited: + visited.add(dependency) + path.append(dependency) + dependency = dependencies.get(dependency) + + # Circular check + if dependency in path: + start_index = path.index(dependency) + circular_dependency = path[start_index:] + if circular_dependency not in circular_dependencies: + circular_dependencies.append(circular_dependency) + break + + # Convert circular_dependencies to dependency paths + dependency_paths = [] + for circular_dependency in circular_dependencies: + dependency_path = " -> ".join(circular_dependency) + dependency_paths.append(dependency_path) + + return dependency_paths + + +# Example usage +graph, dependencies = build_class_graph("./") +debug_graph(graph) +circular_deps = find_circular_dependencies(dependencies) +print("----------------------------------") +if circular_deps: + print(" Circular dependencies found!") + for dep in circular_deps: + print("\t", dep) +else: + print(" No circular dependencies found.") +print("----------------------------------")