Доработка. В процессе. Статическая проверка
This commit is contained in:
164
check-graph.py
Normal file
164
check-graph.py
Normal file
@@ -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("----------------------------------")
|
||||
Reference in New Issue
Block a user