diff --git a/api/analyzers/c/analyzer.py b/api/analyzers/c/analyzer.py index 09a5e26..7a04811 100644 --- a/api/analyzers/c/analyzer.py +++ b/api/analyzers/c/analyzer.py @@ -315,6 +315,36 @@ def process_struct_specifier(self, parent: File, node: Node, path: Path, # Connect parent to entity graph.connect_entities('DEFINES', parent.id, entity.id) + def process_include_directive(self, parent: File, node: Node, path: Path, graph: Graph) -> None: + """ + Processes an include directive node to create an edge between files. + + Args: + parent (File): The parent File object. + node (Node): The AST node representing the include directive. + path (Path): The file path where the include directive is found. + graph (Graph): The Graph object to which the file entities and edges will be added. + + Returns: + None + """ + + assert(node.type == 'include_directive') + + # Extract the included file path + included_file_node = node.child_by_field_name('path') + if included_file_node is None: + return + + included_file_path = included_file_node.text.decode('utf-8').strip('"<>') + + # Create file entity for the included file + included_file = File(os.path.dirname(path), included_file_path, os.path.splitext(included_file_path)[1]) + graph.add_file(included_file) + + # Connect the parent file to the included file + graph.connect_entities('INCLUDES', parent.id, included_file.id) + def first_pass(self, path: Path, f: io.TextIOWrapper, graph:Graph) -> None: """ Perform the first pass processing of a C source file or header file. @@ -388,6 +418,15 @@ def first_pass(self, path: Path, f: io.TextIOWrapper, graph:Graph) -> None: for node in structs: self.process_struct_specifier(file, node, path, graph) + # Process include directives + query = C_LANGUAGE.query("(preprocessor_directive (include_directive) @include)") + captures = query.captures(tree.root_node) + + if 'include' in captures: + includes = captures['include'] + for node in includes: + self.process_include_directive(file, node, path, graph) + def second_pass(self, path: Path, f: io.TextIOWrapper, graph: Graph) -> None: """ Perform the second pass processing of a C source file or header file to establish function call relationships. diff --git a/tests/test_c_analyzer.py b/tests/test_c_analyzer.py index 76b19bf..c7f42bd 100644 --- a/tests/test_c_analyzer.py +++ b/tests/test_c_analyzer.py @@ -60,3 +60,10 @@ def test_analyzer(self): self.assertIn('add', callers) self.assertIn('main', callers) + # Test for include_directive edge creation + included_file = g.get_file('', 'myheader.h', '.h') + self.assertIsNotNone(included_file) + + includes = g.get_neighbors([f.id], rel='INCLUDES') + included_files = [node['properties']['name'] for node in includes['nodes']] + self.assertIn('myheader.h', included_files)