Skip to content

Commit 57ed7da

Browse files
gkorlandCopilot
andcommitted
fix(analyzers): restore C# support, fix return types, improve delegation heuristic
- Restore C# analyzer registration in SourceAnalyzer (removed unintentionally) - Fix resolve_symbol return type annotation in Java/Python analyzers (-> list[Entity]) - Improve Kotlin delegation specifier handling: use constructor_invocation as class signal instead of blindly treating first specifier as base_class Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 32581a7 commit 57ed7da

File tree

4 files changed

+28
-22
lines changed

4 files changed

+28
-22
lines changed

api/analyzers/java/analyzer.py

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

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

api/analyzers/kotlin/analyzer.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,12 @@ def get_entity_docstring(self, node: Node) -> Optional[str]:
5959
def get_entity_types(self) -> list[str]:
6060
return ['class_declaration', 'object_declaration', 'function_declaration']
6161

62-
def _get_delegation_types(self, entity: Entity) -> list:
63-
"""Extract type identifiers from delegation specifiers in order."""
62+
def _get_delegation_types(self, entity: Entity) -> list[tuple]:
63+
"""Extract type identifiers from delegation specifiers in order.
64+
65+
Returns list of (node, is_constructor_invocation) tuples.
66+
constructor_invocation indicates a superclass; plain user_type indicates an interface.
67+
"""
6468
types = []
6569
for child in entity.node.children:
6670
if child.type == 'delegation_specifiers':
@@ -72,27 +76,26 @@ def _get_delegation_types(self, entity: Entity) -> list:
7276
if s.type == 'user_type':
7377
for id_node in s.children:
7478
if id_node.type == 'identifier':
75-
types.append(id_node)
79+
types.append((id_node, True))
7680
elif sub.type == 'user_type':
7781
for id_node in sub.children:
7882
if id_node.type == 'identifier':
79-
types.append(id_node)
83+
types.append((id_node, False))
8084
return types
8185

8286
def add_symbols(self, entity: Entity) -> None:
8387
if entity.node.type == 'class_declaration':
8488
types = self._get_delegation_types(entity)
85-
if types:
86-
# First one is the superclass (base_class)
87-
entity.add_symbol("base_class", types[0])
88-
# Remaining are interfaces
89-
for iface in types[1:]:
90-
entity.add_symbol("implement_interface", iface)
89+
for node, is_class in types:
90+
if is_class:
91+
entity.add_symbol("base_class", node)
92+
else:
93+
entity.add_symbol("implement_interface", node)
9194

9295
elif entity.node.type == 'object_declaration':
9396
types = self._get_delegation_types(entity)
94-
for t in types:
95-
entity.add_symbol("implement_interface", t)
97+
for node, _ in types:
98+
entity.add_symbol("implement_interface", node)
9699

97100
elif entity.node.type == 'function_declaration':
98101
# Find function calls

api/analyzers/python/analyzer.py

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

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

api/analyzers/source_analyzer.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from ..graph import Graph
99
from .analyzer import AbstractAnalyzer
1010
# from .c.analyzer import CAnalyzer
11+
from .csharp.analyzer import CSharpAnalyzer
1112
from .java.analyzer import JavaAnalyzer
1213
from .kotlin.analyzer import KotlinAnalyzer
1314
from .python.analyzer import PythonAnalyzer
@@ -26,6 +27,7 @@
2627
# '.h': CAnalyzer(),
2728
'.py': PythonAnalyzer(),
2829
'.java': JavaAnalyzer(),
30+
'.cs': CSharpAnalyzer(),
2931
'.kt': KotlinAnalyzer(),
3032
'.kts': KotlinAnalyzer()}
3133

@@ -139,14 +141,15 @@ def second_pass(self, graph: Graph, files: list[Path], path: Path) -> None:
139141
lsps[".py"] = SyncLanguageServer.create(config, logger, str(path))
140142
else:
141143
lsps[".py"] = NullLanguageServer()
142-
if any(path.rglob('*.kt')) or any(path.rglob('*.kts')):
143-
# For now, use NullLanguageServer for Kotlin as we need to set up kotlin-language-server
144-
lsps[".kt"] = NullLanguageServer()
145-
lsps[".kts"] = NullLanguageServer()
144+
if any(path.rglob('*.cs')):
145+
config = MultilspyConfig.from_dict({"code_language": "csharp"})
146+
lsps[".cs"] = SyncLanguageServer.create(config, logger, str(path))
146147
else:
147-
lsps[".kt"] = NullLanguageServer()
148-
lsps[".kts"] = NullLanguageServer()
149-
with lsps[".java"].start_server(), lsps[".py"].start_server(), lsps[".kt"].start_server(), lsps[".kts"].start_server():
148+
lsps[".cs"] = NullLanguageServer()
149+
# For now, use NullLanguageServer for Kotlin as kotlin-language-server setup is not yet integrated
150+
lsps[".kt"] = NullLanguageServer()
151+
lsps[".kts"] = NullLanguageServer()
152+
with lsps[".java"].start_server(), lsps[".py"].start_server(), lsps[".cs"].start_server(), lsps[".kt"].start_server(), lsps[".kts"].start_server():
150153
files_len = len(self.files)
151154
for i, file_path in enumerate(files):
152155
file = self.files[file_path]
@@ -174,7 +177,7 @@ def analyze_files(self, files: list[Path], path: Path, graph: Graph) -> None:
174177

175178
def analyze_sources(self, path: Path, ignore: list[str], graph: Graph) -> None:
176179
path = path.resolve()
177-
files = list(path.rglob("*.java")) + list(path.rglob("*.py")) + list(path.rglob("*.kt")) + list(path.rglob("*.kts"))
180+
files = list(path.rglob("*.java")) + list(path.rglob("*.py")) + list(path.rglob("*.cs")) + list(path.rglob("*.kt")) + list(path.rglob("*.kts"))
178181
# First pass analysis of the source code
179182
self.first_pass(path, files, ignore, graph)
180183

0 commit comments

Comments
 (0)