Skip to content
This repository was archived by the owner on Mar 10, 2026. It is now read-only.

Commit 6ea8ab5

Browse files
gkorlandCopilot
andcommitted
Address PR review feedback
- Guard child_by_field_name('name') against None in get_entity_name - Fix Path.glob() always-truthy bug in add_dependencies (use any()) - Fix resolve_symbol return type: -> list[Entity] in abstract class and all analyzers - Remove redundant else branch in resolve_method - Resolve path in analyze_sources to prevent traversal (path.resolve()) - Add setUp/tearDown to test class for graph cleanup - Add IMPLEMENTS relationship assertion for ConsoleLogger -> ILogger Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 67a2ba4 commit 6ea8ab5

File tree

6 files changed

+30
-20
lines changed

6 files changed

+30
-20
lines changed

api/analyzers/analyzer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def add_symbols(self, entity: Entity) -> None:
127127
pass
128128

129129
@abstractmethod
130-
def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: Path, path: Path, key: str, symbol: Node) -> Entity:
130+
def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: Path, path: Path, key: str, symbol: Node) -> list[Entity]:
131131
"""
132132
Resolve a symbol to an entity.
133133
@@ -138,7 +138,7 @@ def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_
138138
symbol (Node): The symbol node.
139139
140140
Returns:
141-
Entity: The entity.
141+
list[Entity]: The resolved entities.
142142
"""
143143

144144
pass

api/analyzers/csharp/analyzer.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _captures(self, pattern: str, node: Node) -> dict:
2626
def add_dependencies(self, path: Path, files: list[Path]):
2727
if Path(f"{path}/temp_deps_cs").is_dir():
2828
return
29-
if Path(f"{path}").glob("*.csproj") or Path(f"{path}").glob("*.sln"):
29+
if any(Path(f"{path}").glob("*.csproj")) or any(Path(f"{path}").glob("*.sln")):
3030
subprocess.run(["dotnet", "restore"], cwd=str(path))
3131

3232
def get_entity_label(self, node: Node) -> str:
@@ -47,7 +47,10 @@ def get_entity_label(self, node: Node) -> str:
4747
def get_entity_name(self, node: Node) -> str:
4848
if node.type in ['class_declaration', 'interface_declaration', 'enum_declaration',
4949
'struct_declaration', 'method_declaration', 'constructor_declaration']:
50-
return node.child_by_field_name('name').text.decode('utf-8')
50+
name_node = node.child_by_field_name('name')
51+
if name_node is None:
52+
return ''
53+
return name_node.text.decode('utf-8')
5154
raise ValueError(f"Unknown entity type: {node.type}")
5255

5356
def get_entity_docstring(self, node: Node) -> Optional[str]:
@@ -114,10 +117,6 @@ def resolve_method(self, files: dict[Path, File], lsp: SyncLanguageServer, file_
114117
func_node = node.child_by_field_name('function')
115118
if func_node and func_node.type == 'member_access_expression':
116119
func_node = func_node.child_by_field_name('name')
117-
elif func_node and func_node.type == 'identifier':
118-
pass
119-
else:
120-
func_node = node.child_by_field_name('function')
121120
if func_node:
122121
node = func_node
123122
for file, resolved_node in self.resolve(files, lsp, file_path, path, node):
@@ -128,7 +127,7 @@ def resolve_method(self, files: dict[Path, File], lsp: SyncLanguageServer, file_
128127
res.append(file.entities[method_dec])
129128
return res
130129

131-
def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: Path, path: Path, key: str, symbol: Node) -> Entity:
130+
def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: Path, path: Path, key: str, symbol: Node) -> list[Entity]:
132131
if key in ["implement_interface", "base_class", "extend_interface", "parameters", "return_type"]:
133132
return self.resolve_type(files, lsp, file_path, path, symbol)
134133
elif key in ["call"]:

api/analyzers/java/analyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def resolve_method(self, files: dict[Path, File], lsp: SyncLanguageServer, file_
125125
res.append(file.entities[method_dec])
126126
return res
127127

128-
def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: Path, path: Path, key: str, symbol: Node) -> Entity:
128+
def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: Path, path: Path, key: str, symbol: Node) -> list[Entity]:
129129
if key in ["implement_interface", "base_class", "extend_interface", "parameters", "return_type"]:
130130
return self.resolve_type(files, lsp, file_path, path, symbol)
131131
elif key in ["call"]:

api/analyzers/python/analyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def resolve_method(self, files: dict[Path, File], lsp: SyncLanguageServer, file_
114114
res.append(file.entities[method_dec])
115115
return res
116116

117-
def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: Path, path: Path, key: str, symbol: Node) -> Entity:
117+
def resolve_symbol(self, files: dict[Path, File], lsp: SyncLanguageServer, file_path: Path, path: Path, key: str, symbol: Node) -> list[Entity]:
118118
if key in ["base_class", "parameters", "return_type"]:
119119
return self.resolve_type(files, lsp, file_path, path, symbol)
120120
elif key in ["call"]:

api/analyzers/source_analyzer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ def analyze_files(self, files: list[Path], path: Path, graph: Graph) -> None:
173173
self.second_pass(graph, files, path)
174174

175175
def analyze_sources(self, path: Path, ignore: list[str], graph: Graph) -> None:
176+
path = path.resolve()
176177
files = list(path.rglob("*.java")) + list(path.rglob("*.py")) + list(path.rglob("*.cs"))
177178
# First pass analysis of the source code
178179
self.first_pass(path, files, ignore, graph)

tests/test_csharp_analyzer.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55

66

77
class Test_CSharp_Analyzer(unittest.TestCase):
8+
def setUp(self):
9+
self.g = Graph("csharp")
10+
11+
def tearDown(self):
12+
self.g.delete()
13+
814
def test_analyzer(self):
915
analyzer = SourceAnalyzer()
1016

@@ -19,41 +25,45 @@ def test_analyzer(self):
1925
path = os.path.join(path, 'csharp')
2026
path = str(path)
2127

22-
g = Graph("csharp")
23-
analyzer.analyze_local_folder(path, g)
28+
analyzer.analyze_local_folder(path, self.g)
2429

2530
# Verify ILogger interface was detected
2631
q = "MATCH (n:Interface {name: 'ILogger'}) RETURN n LIMIT 1"
27-
res = g._query(q).result_set
32+
res = self.g._query(q).result_set
2833
self.assertEqual(len(res), 1)
2934

3035
# Verify ConsoleLogger class was detected
3136
q = "MATCH (n:Class {name: 'ConsoleLogger'}) RETURN n LIMIT 1"
32-
res = g._query(q).result_set
37+
res = self.g._query(q).result_set
3338
self.assertEqual(len(res), 1)
3439

3540
# Verify Task class was detected
3641
q = "MATCH (n:Class {name: 'Task'}) RETURN n LIMIT 1"
37-
res = g._query(q).result_set
42+
res = self.g._query(q).result_set
3843
self.assertEqual(len(res), 1)
3944

4045
# Verify methods were detected
4146
for method_name in ['Log', 'Execute', 'Abort']:
4247
q = "MATCH (n {name: $name}) RETURN n LIMIT 1"
43-
res = g._query(q, {'name': method_name}).result_set
48+
res = self.g._query(q, {'name': method_name}).result_set
4449
self.assertGreaterEqual(len(res), 1, f"Method {method_name} not found")
4550

4651
# Verify Constructor was detected
4752
q = "MATCH (n:Constructor {name: 'Task'}) RETURN n LIMIT 1"
48-
res = g._query(q).result_set
53+
res = self.g._query(q).result_set
4954
self.assertEqual(len(res), 1)
5055

5156
# Verify DEFINES relationships exist (File -> Class/Interface)
5257
q = "MATCH (f:File)-[:DEFINES]->(n) RETURN count(n)"
53-
res = g._query(q).result_set
58+
res = self.g._query(q).result_set
5459
self.assertGreater(res[0][0], 0)
5560

5661
# Verify class defines methods
5762
q = "MATCH (c:Class {name: 'Task'})-[:DEFINES]->(m) RETURN count(m)"
58-
res = g._query(q).result_set
63+
res = self.g._query(q).result_set
5964
self.assertGreater(res[0][0], 0)
65+
66+
# Verify ConsoleLogger implements ILogger
67+
q = "MATCH (c:Class {name: 'ConsoleLogger'})-[:IMPLEMENTS]->(i:Interface {name: 'ILogger'}) RETURN c, i LIMIT 1"
68+
res = self.g._query(q).result_set
69+
self.assertEqual(len(res), 1)

0 commit comments

Comments
 (0)