Skip to content

Commit 3c6afb2

Browse files
authored
Merge pull request #2 from FalkorDB/git-graph
Git graph
2 parents 19291d9 + b5bd8f3 commit 3c6afb2

38 files changed

+5682
-597
lines changed

.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FALKORDB_HOST=localhost
2+
FALKORDB_PORT=6379
3+

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
flask --app code_graph run --debug
2+
3+
Process local git repository, ignoring specific folder(s)
4+
5+
curl -X POST http://127.0.0.1:5000/process_local_repo -H "Content-Type: application/json" -d '{"repo": "/Users/roilipman/Dev/FalkorDB", "ignore": ["./.github", "./sbin", "./.git","./deps", "./bin", "./build"]}'
6+
7+
Process code coverage
8+
curl -X POST http://127.0.0.1:5000/process_code_coverage -H "Content-Type: application/json" -d '{"lcov": "/Users/roilipman/Dev/code_graph/code_graph/code_coverage/lcov/falkordb.lcov", "repo": "FalkorDB"}'
9+
10+
Process git information
11+
curl -X POST http://127.0.0.1:5000/process_git_history -H "Content-Type: application/json" -d '{"repo": "/Users/roilipman/Dev/falkorDB"}'

code_graph/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1-
from .analyzers.source_analyzer import *
1+
from .info import *
2+
from .llm import ask
3+
from .graph import *
4+
from .project import *
25
from .entities import *
3-
from .graph import Graph
6+
from .git_utils import *
7+
from .app import create_app
8+
from .code_coverage import *
9+
from .analyzers.source_analyzer import *
10+
from .auto_complete import prefix_search

code_graph/analyzers/c/analyzer.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import io
2-
from typing import Union, Optional
3-
from pathlib import Path
2+
import os
43
from ..utils import *
4+
from pathlib import Path
55
from ...entities import *
66
from ...graph import Graph
7+
from typing import Union, Optional
78
from ..analyzer import AbstractAnalyzer
89

910
import tree_sitter_c as tsc
@@ -347,13 +348,17 @@ def first_pass(self, path: Path, f: io.TextIOWrapper, graph:Graph) -> None:
347348
logger.info(f"Processing {path}")
348349

349350
# Create file entity
350-
file = File(str(path.parent), path.name, path.suffix)
351+
file = File(os.path.dirname(path), path.name, path.suffix)
351352
graph.add_file(file)
352353

353354
# Parse file
354355
source_code = f.read()
355356
tree = self.parser.parse(source_code)
356-
source_code = source_code.decode('utf-8')
357+
try:
358+
source_code = source_code.decode('utf-8')
359+
except Exception as e:
360+
logger.error(f"Failed decoding source code: {e}")
361+
source_code = ''
357362

358363
# Process function definitions
359364
query = C_LANGUAGE.query("(function_definition) @function")
@@ -412,7 +417,7 @@ def second_pass(self, path: Path, f: io.TextIOWrapper, graph: Graph) -> None:
412417
logger.info(f"Processing {path}")
413418

414419
# Get file entity
415-
file = graph.get_file(str(path.parent), path.name, path.suffix)
420+
file = graph.get_file(os.path.dirname(path), path.name, path.suffix)
416421
if file is None:
417422
logger.error(f"File entity not found for: {path}")
418423
return

code_graph/analyzers/python/analyzer.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import io
2-
from typing import Union, Optional
3-
from pathlib import Path
2+
import os
43
from ..utils import *
4+
from pathlib import Path
55
from ...entities import *
66
from ...graph import Graph
7+
from typing import Union, Optional
78
from ..analyzer import AbstractAnalyzer
89

910
import tree_sitter_python as tspython
@@ -74,7 +75,7 @@ def process_class_definition(self, node: Node, path: Path) -> tuple[Class, list[
7475

7576
return (c, inherited_classes)
7677

77-
def process_function_definition(self, node: Node, path: Path) -> Function:
78+
def process_function_definition(self, node: Node, path: Path, source_code: str) -> Function:
7879
"""
7980
Processes a function definition node from the syntax tree and extracts relevant information.
8081
@@ -153,7 +154,8 @@ def process_function_definition(self, node: Node, path: Path) -> Function:
153154
ret_type = return_type.text.decode('utf-8') if return_type else None
154155

155156
# Create Function object
156-
f = Function(str(path), function_name, docstring, ret_type, '', start_line, end_line)
157+
src = source_code[node.start_byte:node.end_byte]
158+
f = Function(str(path), function_name, docstring, ret_type, src, start_line, end_line)
157159

158160
# Add arguments to Function object
159161
for arg in args:
@@ -162,7 +164,7 @@ def process_function_definition(self, node: Node, path: Path) -> Function:
162164
return f
163165

164166
def first_pass_traverse(self, parent: Union[File,Class,Function], node: Node,
165-
path: Path, graph: Graph) -> None:
167+
path: Path, graph: Graph, source_code: str) -> None:
166168
"""
167169
Recursively traverses a syntax tree node, processes class and function definitions,
168170
and connects them in a graph representation.
@@ -197,7 +199,7 @@ def first_pass_traverse(self, parent: Union[File,Class,Function], node: Node,
197199
graph.add_class(entity)
198200

199201
elif node.type == "function_definition":
200-
entity = self.process_function_definition(node, path)
202+
entity = self.process_function_definition(node, path, source_code)
201203
# Add Function object to the graph
202204
graph.add_function(entity)
203205

@@ -208,7 +210,7 @@ def first_pass_traverse(self, parent: Union[File,Class,Function], node: Node,
208210

209211
# Recursivly visit child nodes
210212
for child in node.children:
211-
self.first_pass_traverse(parent, child, path, graph)
213+
self.first_pass_traverse(parent, child, path, graph, source_code)
212214

213215
def first_pass(self, path: Path, f: io.TextIOWrapper, graph:Graph) -> None:
214216
"""
@@ -226,15 +228,20 @@ def first_pass(self, path: Path, f: io.TextIOWrapper, graph:Graph) -> None:
226228
logger.info(f"Python Processing {path}")
227229

228230
# Create file entity
229-
file = File(str(path.parent), path.name, path.suffix)
231+
file = File(os.path.dirname(path), path.name, path.suffix)
230232
graph.add_file(file)
231233

232234
# Parse file
233-
content = f.read()
234-
tree = self.parser.parse(content)
235+
source_code = f.read()
236+
tree = self.parser.parse(source_code)
237+
try:
238+
source_code = source_code.decode('utf-8')
239+
except Exception as e:
240+
logger.error(f"Failed decoding source code: {e}")
241+
source_code = ''
235242

236243
# Walk thought the AST
237-
self.first_pass_traverse(file, tree.root_node, path, graph)
244+
self.first_pass_traverse(file, tree.root_node, path, graph, source_code)
238245

239246
def process_function_call(self, node) -> Optional[str]:
240247
"""
@@ -323,7 +330,7 @@ def process_inheritance(self, cls: Class, super_classes: list[str],
323330
graph.connect_entities('INHERITS', cls.id, _super_class.id)
324331

325332
def second_pass_traverse(self, parent: Union[File, Class, Function],
326-
node: Node, path: Path, graph: Graph) -> None:
333+
node: Node, path: Path, graph: Graph, source_code: str) -> None:
327334
"""
328335
Traverse the AST nodes during the second pass and process each node accordingly.
329336
@@ -340,7 +347,9 @@ def second_pass_traverse(self, parent: Union[File, Class, Function],
340347
parent = cls
341348

342349
elif node.type == "function_definition":
343-
func = self.process_function_definition(node, path)
350+
# TODO: simply extract function name, no need to parse entire function
351+
# see C analyzer
352+
func = self.process_function_definition(node, path, source_code)
344353
parent = graph.get_function_by_name(func.name)
345354
elif node.type == "call":
346355
callee = self.process_function_call(node)
@@ -349,7 +358,7 @@ def second_pass_traverse(self, parent: Union[File, Class, Function],
349358

350359
# Recursivly visit child nodes
351360
for child in node.children:
352-
self.second_pass_traverse(parent, child, path, graph)
361+
self.second_pass_traverse(parent, child, path, graph, source_code)
353362

354363
def second_pass(self, path: Path, f: io.TextIOWrapper, graph: Graph) -> None:
355364
"""
@@ -367,17 +376,17 @@ def second_pass(self, path: Path, f: io.TextIOWrapper, graph: Graph) -> None:
367376
logger.info(f"Processing {path}")
368377

369378
# Get file entity
370-
file = graph.get_file(str(path.parent), path.name, path.suffix)
379+
file = graph.get_file(os.path.dirname(path), path.name, path.suffix)
371380
if file is None:
372381
logger.error(f"File entity not found for: {path}")
373382
return
374383

375384
try:
376385
# Parse file
377-
content = f.read()
378-
tree = self.parser.parse(content)
386+
source_code = f.read()
387+
tree = self.parser.parse(source_code)
379388

380389
# Walk thought the AST
381-
self.second_pass_traverse(file, tree.root_node, path, graph)
390+
self.second_pass_traverse(file, tree.root_node, path, graph, source_code)
382391
except Exception as e:
383392
logger.error(f"Failed to process file {path}: {e}")

0 commit comments

Comments
 (0)