diff --git a/codegen-on-oss/codegen_on_oss/analyzers/analyzer.py b/codegen-on-oss/codegen_on_oss/analyzers/analyzer.py index 3471380d8..f5ff1c7ba 100644 --- a/codegen-on-oss/codegen_on_oss/analyzers/analyzer.py +++ b/codegen-on-oss/codegen_on_oss/analyzers/analyzer.py @@ -480,8 +480,8 @@ def analyze( Dictionary containing analysis results """ if not self.base_codebase: - raise ValueError("Base codebase is missing") - + raise ValueError("Base codebase is missing") # noqa: TRY003 + # Convert string analysis types to enums if analysis_types: analysis_types = [ @@ -693,7 +693,7 @@ def generate_report(self, report_type: str = "summary") -> str: Report as a string """ if not self.results: - raise ValueError("No analysis results available") + raise ValueError("No analysis results available") # noqa: TRY003 if report_type == "summary": return self._generate_summary_report() @@ -702,7 +702,7 @@ def generate_report(self, report_type: str = "summary") -> str: elif report_type == "issues": return self._generate_issues_report() else: - raise ValueError(f"Unknown report type: {report_type}") + raise ValueError(f"Unknown report type: {report_type}") # noqa: TRY003 def _generate_summary_report(self) -> str: """Generate a summary report.""" @@ -1000,8 +1000,8 @@ def main(): report = manager.generate_report(args.report_type) print(report) - except Exception as e: - logger.exception(f"Error: {e}") + except Exception: + logger.exception("Error occurred") # Fixed TRY401 error import traceback traceback.print_exc() diff --git a/codegen-on-oss/codegen_on_oss/analyzers/visualization/README.md b/codegen-on-oss/codegen_on_oss/analyzers/visualization/README.md new file mode 100644 index 000000000..f5f02e514 --- /dev/null +++ b/codegen-on-oss/codegen_on_oss/analyzers/visualization/README.md @@ -0,0 +1,118 @@ +# Code Visualization Tools + +This directory contains tools for visualizing various aspects of codebases, including: + +- Code structure (call graphs, dependency graphs, etc.) +- Code quality (dead code, complexity, etc.) +- Analysis results (issues, PR comparisons, etc.) + +## Visualization Types + +### Call Graph Visualizations + +1. **Basic Call Graph**: Visualize function calls starting from a specific function + ```python + visualizer.visualize_call_graph(function_name="main", max_depth=3) + ``` + +2. **Call Graph From Node**: Enhanced call graph visualization with more detailed tracing + ```python + visualizer.visualize_call_graph_from_node( + function_name="main", + max_depth=3, + graph_external_modules=False + ) + ``` + +3. **Filtered Call Graph**: Visualize call graph with filtering by method names + ```python + visualizer.visualize_call_graph_filter( + class_name="MyClass", + method_filters=["get", "post", "patch", "delete"], + skip_test_files=True + ) + ``` + +4. **Call Paths Between Nodes**: Visualize all paths between two functions + ```python + visualizer.visualize_call_paths_between_nodes( + start_func_name="start_func", + end_func_name="end_func" + ) + ``` + +### Code Quality Visualizations + +1. **Dead Code**: Visualize unused code in the codebase + ```python + visualizer.visualize_dead_code(path_filter="src/") + ``` + +2. **Dead Code Graph**: Enhanced dead code visualization with dependency tracking + ```python + visualizer.visualize_dead_code_graph( + skip_test_files=True, + skip_decorated=True + ) + ``` + +3. **Cyclomatic Complexity**: Visualize code complexity + ```python + visualizer.visualize_cyclomatic_complexity(path_filter="src/") + ``` + +### Other Visualizations + +1. **Dependency Graph**: Visualize dependencies between symbols + ```python + visualizer.visualize_dependency_graph(symbol_name="MyClass") + ``` + +2. **Blast Radius**: Visualize the impact of changing a symbol + ```python + visualizer.visualize_blast_radius(symbol_name="important_function") + ``` + +3. **Class Methods**: Visualize methods in a class and their relationships + ```python + visualizer.visualize_class_methods(class_name="MyClass") + ``` + +4. **Module Dependencies**: Visualize dependencies between modules + ```python + visualizer.visualize_module_dependencies(module_path="src/module/") + ``` + +5. **Issues Heatmap**: Visualize issues in the codebase + ```python + visualizer.visualize_issues_heatmap(severity="error") + ``` + +6. **PR Comparison**: Visualize changes in a pull request + ```python + visualizer.visualize_pr_comparison() + ``` + +## Configuration + +You can configure the visualization output format, colors, and other options: + +```python +from codegen_on_oss.analyzers.visualization.visualizer import VisualizationConfig, OutputFormat + +config = VisualizationConfig( + output_format=OutputFormat.PNG, # Options: JSON, PNG, SVG, HTML, DOT + output_directory="visualization_output", + max_depth=5, + ignore_external=True, + ignore_tests=True, + layout_algorithm="spring", # Options: spring, kamada_kawai, spectral +) + +visualizer = CodebaseVisualizer(config=config) +``` + +## Examples + +See the `examples/visualization_examples.py` file for complete examples of using these visualization tools. + diff --git a/codegen-on-oss/codegen_on_oss/analyzers/visualization/call_graph_from_node.py b/codegen-on-oss/codegen_on_oss/analyzers/visualization/call_graph_from_node.py new file mode 100644 index 000000000..b3b5335e7 --- /dev/null +++ b/codegen-on-oss/codegen_on_oss/analyzers/visualization/call_graph_from_node.py @@ -0,0 +1,424 @@ +#!/usr/bin/env python3 +""" +Call Graph From Node Visualization + +This module provides visualization capabilities for generating a call graph +starting from a specific function or method. +""" + +import logging +from typing import Any + +import networkx as nx + +from ..context.class_definition import Class +from ..context.codebase import CodebaseType +from ..context.detached_symbols.function_call import FunctionCall +from ..context.external_module import ExternalModule +from ..context.function import Function +from ..context.interfaces.callable import Callable +from .visualizer import BaseVisualizer, OutputFormat, VisualizationType + +logger = logging.getLogger(__name__) + + +class CallGraphFromNode(BaseVisualizer): + """ + Visualizer for generating a call graph starting from a specific function. + + This class creates a directed call graph for a given function. Starting from the + specified function, it recursively iterates through its function calls and the + functions called by them, building a graph of the call paths to a maximum depth. + """ + + def __init__(self, codebase: CodebaseType | None = None, **kwargs): + """ + Initialize the CallGraphFromNode visualizer. + + Args: + codebase: Codebase instance to visualize + **kwargs: Additional configuration options + """ + super().__init__(**kwargs) + self.codebase = codebase + + def visualize( # noqa: C901 + self, + function_name: str, + max_depth: int = 5, + graph_external_modules: bool = False, + ): + """ + Generate a call graph visualization starting from a specific function. + + Args: + function_name: Name of the function to start the call graph from + max_depth: Maximum depth of the call graph + graph_external_modules: Whether to include external module calls in the graph + + Returns: + Visualization data or path to saved file + """ + # Create a directed graph + G = nx.DiGraph() + + # Find the function in the codebase + function_to_trace = None + for func in self.codebase.functions: + if func.name == function_name: + function_to_trace = func + break + + if not function_to_trace: + logger.error(f"Function {function_name} not found in codebase") + return None + + # Add the starting node + G.add_node(function_to_trace, color="yellow") + + # Recursive function to create the call graph + def create_downstream_call_trace( + parent: FunctionCall | Function | None = None, depth: int = 0 + ): + """ + Creates call graph for parent function. + + This function recurses through the call graph of a function and creates a visualization. + + Args: + parent: The function for which a call graph will be created + depth: The current depth of the recursive stack + """ + # If the maximum recursive depth has been exceeded, return + if max_depth <= depth: + return + + if isinstance(parent, FunctionCall): + src_call, src_func = parent, parent.function_definition + else: + src_call, src_func = parent, parent + + # Iterate over all call paths of the symbol + for call in src_func.function_calls: + # The symbol being called + func = call.function_definition + + # Ignore direct recursive calls + if func.name == src_func.name: + continue + + # If the function being called is not from an external module + if not isinstance(func, ExternalModule): + # Add `call` to the graph and an edge from `src_call` to `call` + G.add_node(call) + G.add_edge(src_call, call) + + # Recursive call to function call + create_downstream_call_trace(call, depth + 1) + elif graph_external_modules: + # Add `call` to the graph and an edge from `src_call` to `call` + G.add_node(call) + G.add_edge(src_call, call) + + # Start the recursive traversal + create_downstream_call_trace(function_to_trace) + + # Set the graph for visualization + self.graph = G + + # Generate visualization data + if self.config.output_format == OutputFormat.JSON: + data = self._convert_graph_to_json() + return self._save_visualization( + VisualizationType.CALL_GRAPH, function_name, data + ) + else: + fig = self._plot_graph() + return self._save_visualization( + VisualizationType.CALL_GRAPH, function_name, fig + ) + + +class CallGraphFilter(BaseVisualizer): + """ + Visualizer for generating a filtered call graph. + + This class creates a call graph that filters nodes based on specific criteria, + such as method names or file paths. + """ + + def __init__(self, codebase: CodebaseType | None = None, **kwargs): + """ + Initialize the CallGraphFilter visualizer. + + Args: + codebase: Codebase instance to visualize + **kwargs: Additional configuration options + """ + super().__init__(**kwargs) + self.codebase = codebase + + def visualize( # noqa: C901 + self, + class_name: str, + method_filters: list[str] | None = None, + skip_test_files: bool = True, + max_depth: int = 5, + ): + """ + Generate a filtered call graph visualization. + + Args: + class_name: Name of the class to visualize methods from + method_filters: List of method names to include (e.g., ['get', 'post', 'patch', 'delete']) + skip_test_files: Whether to skip test files + max_depth: Maximum depth of the call graph + + Returns: + Visualization data or path to saved file + """ + # Create a directed graph + G = nx.DiGraph() + + # Find the class in the codebase + cls = None + for c in self.codebase.classes: + if c.name == class_name: + cls = c + break + + if not cls: + logger.error(f"Class {class_name} not found in codebase") + return None + + # Find a function that uses the class to start tracing from + func_to_trace = None + for func in self.codebase.functions: + if skip_test_files and "test" in func.file.filepath: + continue + + for call in func.function_calls: + if ( + hasattr(call.function_definition, "parent_class") + and call.function_definition.parent_class == cls + ): + func_to_trace = func + break + + if func_to_trace: + break + + if not func_to_trace: + logger.error(f"No function found that uses class {class_name}") + return None + + # Add the main function as a node + G.add_node(func_to_trace, color="red") + + # Define a recursive function to traverse function calls + def create_filtered_downstream_call_trace( + parent: FunctionCall | Function, current_depth, max_depth + ): + if current_depth > max_depth: + return + + # If parent is of type Function + if isinstance(parent, Function): + # Set both src_call, src_func to parent + src_call, src_func = parent, parent + else: + # Get the function definition of parent + src_call, src_func = parent, parent.function_definition + + # Iterate over all call paths of the symbol + for call in src_func.function_calls: + # The symbol being called + func = call.function_definition + + # Skip class declarations if configured + if isinstance(func, Class): + continue + + # If the function being called is not from an external module and is not defined in a test file + if not isinstance(func, ExternalModule) and not ( + skip_test_files and func.file.filepath.startswith("test") + ): + # Add `call` to the graph and an edge from `src_call` to `call` + metadata: dict[str, Any] = {} + if ( + isinstance(func, Function) + and func.is_method + and func.parent_class == cls + ): + # Only include methods that match the filter + if method_filters and func.name not in method_filters: + continue + + name = f"{func.parent_class.name}.{func.name}" + metadata = {"color": "yellow", "name": name} + + G.add_node(call, **metadata) + G.add_edge( + src_call, call, symbol=cls + ) # Add edge from current to successor + + # Recursively add successors of the current symbol + create_filtered_downstream_call_trace( + call, current_depth + 1, max_depth + ) + + # Start the recursive traversal + create_filtered_downstream_call_trace(func_to_trace, 1, max_depth) + + # Set the graph for visualization + self.graph = G + + # Generate visualization data + if self.config.output_format == OutputFormat.JSON: + data = self._convert_graph_to_json() + return self._save_visualization( + VisualizationType.CALL_GRAPH, f"{class_name}_filtered", data + ) + else: + fig = self._plot_graph() + return self._save_visualization( + VisualizationType.CALL_GRAPH, f"{class_name}_filtered", fig + ) + + +class CallPathsBetweenNodes(BaseVisualizer): + """ + Visualizer for generating paths between two functions in the call graph. + + This class creates a visualization of all simple paths between a start function + and an end function in the call graph. + """ + + def __init__(self, codebase: CodebaseType | None = None, **kwargs): + """ + Initialize the CallPathsBetweenNodes visualizer. + + Args: + codebase: Codebase instance to visualize + **kwargs: Additional configuration options + """ + super().__init__(**kwargs) + self.codebase = codebase + + def visualize(self, start_func_name: str, end_func_name: str, max_depth: int = 5): # noqa: C901 + """ + Generate a visualization of call paths between two functions. + + Args: + start_func_name: Name of the starting function + end_func_name: Name of the ending function + max_depth: Maximum depth for path exploration + + Returns: + Visualization data or path to saved file + """ + # Create a directed graph + G = nx.DiGraph() + + # Find the start and end functions in the codebase + start_func = None + end_func = None + + for func in self.codebase.functions: + if func.name == start_func_name: + start_func = func + elif func.name == end_func_name: + end_func = func + + if start_func and end_func: + break + + if not start_func: + logger.error(f"Start function {start_func_name} not found in codebase") + return None + + if not end_func: + logger.error(f"End function {end_func_name} not found in codebase") + return None + + # Set starting node as blue + G.add_node(start_func, color="blue") + # Set ending node as red + G.add_node(end_func, color="red") + + # Define a recursive function to traverse function calls + def create_downstream_call_trace( + parent: FunctionCall | Function, end: Callable, current_depth, max_depth + ): + if current_depth > max_depth: + return + + # If parent is of type Function + if isinstance(parent, Function): + # Set both src_call, src_func to parent + src_call, src_func = parent, parent + else: + # Get the function definition of parent + src_call, src_func = parent, parent.function_definition + + # Iterate over all call paths of the symbol + for call in src_func.function_calls: + # The symbol being called + func = call.function_definition + + # Ignore direct recursive calls + if func.name == src_func.name: + continue + + # If the function being called is not from an external module + if not isinstance(func, ExternalModule): + # Add `call` to the graph and an edge from `src_call` to `call` + G.add_node(call) + G.add_edge(src_call, call) + + if func == end: + G.add_edge(call, end) + return + + # Recursive call to function call + create_downstream_call_trace( + call, end, current_depth + 1, max_depth + ) + + # Start the recursive traversal + create_downstream_call_trace(start_func, end_func, 1, max_depth) + + # Find all the simple paths between start and end + try: + all_paths = list(nx.all_simple_paths(G, source=start_func, target=end_func)) + + # Collect all nodes that are part of these paths + nodes_in_paths = set() + for path in all_paths: + nodes_in_paths.update(path) + + # Create a new subgraph with only the nodes in the paths + G = G.subgraph(nodes_in_paths) + except nx.NetworkXNoPath: + logger.warning( + f"No path found between {start_func_name} and {end_func_name}" + ) + + # Set the graph for visualization + self.graph = G + + # Generate visualization data + if self.config.output_format == OutputFormat.JSON: + data = self._convert_graph_to_json() + return self._save_visualization( + VisualizationType.CALL_GRAPH, + f"{start_func_name}_to_{end_func_name}", + data, + ) + else: + fig = self._plot_graph() + return self._save_visualization( + VisualizationType.CALL_GRAPH, + f"{start_func_name}_to_{end_func_name}", + fig, + ) diff --git a/codegen-on-oss/codegen_on_oss/analyzers/visualization/codebase_visualizer.py b/codegen-on-oss/codegen_on_oss/analyzers/visualization/codebase_visualizer.py index 52f77eade..e3845688a 100644 --- a/codegen-on-oss/codegen_on_oss/analyzers/visualization/codebase_visualizer.py +++ b/codegen-on-oss/codegen_on_oss/analyzers/visualization/codebase_visualizer.py @@ -14,6 +14,8 @@ from .analysis_visualizer import AnalysisVisualizer from .code_visualizer import CodeVisualizer +from .call_graph_from_node import CallGraphFromNode, CallGraphFilter, CallPathsBetweenNodes +from .dead_code import DeadCodeVisualizer from .visualizer import ( OutputFormat, VisualizationConfig, @@ -66,6 +68,27 @@ def __init__(self, analyzer=None, codebase=None, context=None, config=None): context=self.context, config=self.config, ) + + # Initialize new specialized visualizers + self.call_graph_from_node = CallGraphFromNode( + codebase=self.codebase, + config=self.config, + ) + + self.call_graph_filter = CallGraphFilter( + codebase=self.codebase, + config=self.config, + ) + + self.call_paths_between_nodes = CallPathsBetweenNodes( + codebase=self.codebase, + config=self.config, + ) + + self.dead_code_visualizer = DeadCodeVisualizer( + codebase=self.codebase, + config=self.config, + ) # Create visualization directory if specified if self.config.output_directory: @@ -226,7 +249,7 @@ def visualize_module_dependencies(self, module_path: str): def visualize_dead_code(self, path_filter: str | None = None): """Convenience method for dead code visualization.""" return self.visualize(VisualizationType.DEAD_CODE, path_filter=path_filter) - + def visualize_cyclomatic_complexity(self, path_filter: str | None = None): """Convenience method for cyclomatic complexity visualization.""" return self.visualize( @@ -242,6 +265,40 @@ def visualize_issues_heatmap(self, severity=None, path_filter: str | None = None def visualize_pr_comparison(self): """Convenience method for PR comparison visualization.""" return self.visualize(VisualizationType.PR_COMPARISON) + + # New convenience methods for the added visualizers + def visualize_call_graph_from_node(self, function_name: str, max_depth: int = 5, graph_external_modules: bool = False): + """Convenience method for call graph from node visualization.""" + return self.call_graph_from_node.visualize( + function_name=function_name, + max_depth=max_depth, + graph_external_modules=graph_external_modules + ) + + def visualize_call_graph_filter(self, class_name: str, method_filters: list[str] = None, + skip_test_files: bool = True, max_depth: int = 5): + """Convenience method for filtered call graph visualization.""" + return self.call_graph_filter.visualize( + class_name=class_name, + method_filters=method_filters, + skip_test_files=skip_test_files, + max_depth=max_depth + ) + + def visualize_call_paths_between_nodes(self, start_func_name: str, end_func_name: str, max_depth: int = 5): + """Convenience method for call paths between nodes visualization.""" + return self.call_paths_between_nodes.visualize( + start_func_name=start_func_name, + end_func_name=end_func_name, + max_depth=max_depth + ) + + def visualize_dead_code_graph(self, skip_test_files: bool = True, skip_decorated: bool = True): + """Convenience method for dead code graph visualization.""" + return self.dead_code_visualizer.visualize( + skip_test_files=skip_test_files, + skip_decorated=skip_decorated + ) # Command-line interface diff --git a/codegen-on-oss/codegen_on_oss/analyzers/visualization/dead_code.py b/codegen-on-oss/codegen_on_oss/analyzers/visualization/dead_code.py new file mode 100644 index 000000000..58aaee1ac --- /dev/null +++ b/codegen-on-oss/codegen_on_oss/analyzers/visualization/dead_code.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +""" +Dead Code Visualization + +This module provides visualization capabilities for identifying and visualizing +dead code in a codebase, including unused functions, classes, and second-order +dead code (code that is only used by other dead code). +""" + +import logging + +import networkx as nx + +from ..context.codebase import CodebaseType +from ..context.function import Function +from ..context.import_resolution import Import +from ..context.symbol import Symbol +from .visualizer import BaseVisualizer, OutputFormat, VisualizationType + +logger = logging.getLogger(__name__) + + +class DeadCodeVisualizer(BaseVisualizer): + """ + Visualizer for identifying and visualizing dead code in a codebase. + + This class identifies functions and classes that have no usages and are not in test + files or decorated. These are considered 'dead code' and are added to a directed graph. + The visualizer then explores the dependencies of these dead code functions, adding them + to the graph as well. This process helps to identify not only directly unused code + but also code that might only be used by other dead code (second-order dead code). + """ + + def __init__(self, codebase: CodebaseType | None = None, **kwargs): + """ + Initialize the DeadCodeVisualizer. + + Args: + codebase: Codebase instance to visualize + **kwargs: Additional configuration options + """ + super().__init__(**kwargs) + self.codebase = codebase + + def visualize(self, skip_test_files: bool = True, skip_decorated: bool = True): # noqa: C901 + """ + Generate a visualization of dead code in the codebase. + + Args: + skip_test_files: Whether to skip functions in test files + skip_decorated: Whether to skip decorated functions + + Returns: + Visualization data or path to saved file + """ + # Create a directed graph to visualize dead and second-order dead code + G = nx.DiGraph() + + # First, identify all dead code + dead_code: list[Function] = [] + + # Iterate through all functions in the codebase + for function in self.codebase.functions: + # Filter down functions + if skip_test_files and "test" in function.file.filepath: + continue + + if skip_decorated and function.decorators: + continue + + # Check if the function has no usages + if not function.symbol_usages: + # Add the function to the dead code list + dead_code.append(function) + # Add the function to the graph as dead code + G.add_node(function, color="red") + + # Now, find second-order dead code + for symbol in dead_code: + # Get all usages of the dead code symbol + for dep in symbol.dependencies: + if isinstance(dep, Import): + dep = dep.imported_symbol + if isinstance(dep, Symbol) and not ( + skip_test_files and "test" in dep.name + ): + G.add_node(dep) + G.add_edge(symbol, dep, color="red") + for usage_symbol in dep.symbol_usages: + if isinstance(usage_symbol, Function) and not ( + skip_test_files and "test" in usage_symbol.name + ): + G.add_edge(usage_symbol, dep) + + # Set the graph for visualization + self.graph = G + + # Generate visualization data + if self.config.output_format == OutputFormat.JSON: + data = self._convert_graph_to_json() + return self._save_visualization( + VisualizationType.DEAD_CODE, "codebase_dead_code", data + ) + else: + fig = self._plot_graph() + return self._save_visualization( + VisualizationType.DEAD_CODE, "codebase_dead_code", fig + ) diff --git a/codegen-on-oss/codegen_on_oss/analyzers/visualization/visualizer.py b/codegen-on-oss/codegen_on_oss/analyzers/visualization/visualizer.py index 81f4f61be..2e379cf09 100644 --- a/codegen-on-oss/codegen_on_oss/analyzers/visualization/visualizer.py +++ b/codegen-on-oss/codegen_on_oss/analyzers/visualization/visualizer.py @@ -18,7 +18,6 @@ try: import matplotlib.pyplot as plt import networkx as nx - from matplotlib.colors import LinearSegmentedColormap except ImportError: logging.warning( "Visualization dependencies not found. Please install them with: pip install networkx matplotlib" @@ -83,20 +82,29 @@ class VisualizationConfig: class BaseVisualizer: """ - Base visualizer providing common functionality for different visualization types. + Base class for all visualizers. - This class implements the core operations needed for visualization, including - graph creation, node and edge management, and output generation. + This class provides common functionality for all visualizers, including + configuration, graph creation, and visualization output. """ - def __init__(self, config: VisualizationConfig | None = None): + def __init__(self, config: VisualizationConfig | None = None, **kwargs): """ - Initialize the BaseVisualizer. + Initialize the base visualizer. Args: - config: Visualization configuration options + config: Visualization configuration + **kwargs: Additional configuration options """ self.config = config or VisualizationConfig() + self.graph: nx.Graph | None = None + self.current_visualization_type: VisualizationType | None = None + self.current_entity_name: str | None = None + + # Apply any additional configuration options + for key, value in kwargs.items(): + if hasattr(self.config, key): + setattr(self.config, key, value) # Create visualization directory if specified if self.config.output_directory: @@ -105,10 +113,6 @@ def __init__(self, config: VisualizationConfig | None = None): # Initialize graph for visualization self.graph = nx.DiGraph() - # Tracking current visualization - self.current_visualization_type = None - self.current_entity_name = None - def _initialize_graph(self): """Initialize a fresh graph for visualization.""" self.graph = nx.DiGraph() @@ -260,12 +264,17 @@ def _convert_graph_to_json(self): # Add other attributes for key, value in attrs.items(): - if key not in ["name", "type", "color", "file_path", "original_node"]: - if ( - isinstance(value, str | int | float | bool | list | dict) - or value is None - ): - node_data[key] = value + if key not in [ + "name", + "type", + "color", + "file_path", + "original_node", + ] and ( + isinstance(value, str | int | float | bool | list | dict) + or value is None + ): + node_data[key] = value nodes.append(node_data) diff --git a/codegen-on-oss/examples/visualization_examples.py b/codegen-on-oss/examples/visualization_examples.py new file mode 100644 index 000000000..001c3271f --- /dev/null +++ b/codegen-on-oss/examples/visualization_examples.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Visualization Examples + +This script demonstrates how to use the visualization capabilities of the codegen-on-oss +package to generate various visualizations of code structure and analysis. +""" + +import os +import sys +import logging + +# Add the parent directory to the path to import the package +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from codegen_on_oss.analyzers.visualization.codebase_visualizer import CodebaseVisualizer +from codegen_on_oss.analyzers.visualization.visualizer import VisualizationConfig, OutputFormat + +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[logging.StreamHandler()], +) +logger = logging.getLogger(__name__) + + +def main(): + """ + Main function to demonstrate visualization capabilities. + """ + # Create a visualization config + config = VisualizationConfig( + output_format=OutputFormat.PNG, + output_directory="visualization_output", + max_depth=5, + ignore_external=True, + ignore_tests=True, + ) + + # Create a visualizer + visualizer = CodebaseVisualizer(config=config) + + # Example 1: Call Graph from Node + logger.info("Generating call graph from node visualization...") + result = visualizer.visualize_call_graph_from_node( + function_name="main", # Replace with an actual function name in your codebase + max_depth=3, + graph_external_modules=False, + ) + logger.info(f"Call graph visualization saved to: {result}") + + # Example 2: Call Graph Filter + logger.info("Generating filtered call graph visualization...") + result = visualizer.visualize_call_graph_filter( + class_name="CodebaseVisualizer", # Replace with an actual class name in your codebase + method_filters=["visualize", "visualize_call_graph", "visualize_dead_code"], + skip_test_files=True, + max_depth=3, + ) + logger.info(f"Filtered call graph visualization saved to: {result}") + + # Example 3: Call Paths Between Nodes + logger.info("Generating call paths visualization...") + result = visualizer.visualize_call_paths_between_nodes( + start_func_name="main", # Replace with an actual function name in your codebase + end_func_name="visualize", # Replace with an actual function name in your codebase + max_depth=5, + ) + logger.info(f"Call paths visualization saved to: {result}") + + # Example 4: Dead Code Graph + logger.info("Generating dead code visualization...") + result = visualizer.visualize_dead_code_graph( + skip_test_files=True, + skip_decorated=True, + ) + logger.info(f"Dead code visualization saved to: {result}") + + +if __name__ == "__main__": + main() +