tree-sitter-analyzer 1.9.17.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
|
@@ -0,0 +1,1076 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
C# Language Plugin
|
|
4
|
+
|
|
5
|
+
Provides C#-specific parsing and element extraction functionality.
|
|
6
|
+
Supports extraction of classes, interfaces, records, methods, properties, fields, and using directives.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Iterator
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import tree_sitter
|
|
14
|
+
|
|
15
|
+
from ..core.analysis_engine import AnalysisRequest
|
|
16
|
+
from ..models import AnalysisResult
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import tree_sitter
|
|
20
|
+
|
|
21
|
+
TREE_SITTER_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
TREE_SITTER_AVAILABLE = False
|
|
24
|
+
|
|
25
|
+
from ..models import Class, Function, Import, Variable
|
|
26
|
+
from ..plugins.base import ElementExtractor, LanguagePlugin
|
|
27
|
+
from ..utils import log_debug, log_error
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CSharpElementExtractor(ElementExtractor):
|
|
31
|
+
"""
|
|
32
|
+
C#-specific element extractor.
|
|
33
|
+
|
|
34
|
+
This extractor parses C# AST and extracts code elements, mapping them
|
|
35
|
+
to the unified element model:
|
|
36
|
+
- Classes, Interfaces, Records, Enums, Structs → Class elements
|
|
37
|
+
- Methods, Constructors, Properties → Function elements
|
|
38
|
+
- Fields, Constants, Events → Variable elements
|
|
39
|
+
- Using directives → Import elements
|
|
40
|
+
|
|
41
|
+
The extractor handles modern C# syntax including:
|
|
42
|
+
- C# 8+ nullable reference types
|
|
43
|
+
- C# 9+ records
|
|
44
|
+
- Async/await patterns
|
|
45
|
+
- Attributes (annotations)
|
|
46
|
+
- Generic types
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Initialize the C# element extractor.
|
|
52
|
+
|
|
53
|
+
Sets up internal state for source code processing and performance
|
|
54
|
+
optimization caches for node text extraction.
|
|
55
|
+
"""
|
|
56
|
+
super().__init__()
|
|
57
|
+
self.source_code: str = ""
|
|
58
|
+
self.content_lines: list[str] = []
|
|
59
|
+
self.current_namespace: str = ""
|
|
60
|
+
|
|
61
|
+
# Performance optimization caches
|
|
62
|
+
self._node_text_cache: dict[int, str] = {}
|
|
63
|
+
self._processed_nodes: set[int] = set()
|
|
64
|
+
self._element_cache: dict[tuple[int, str], Any] = {}
|
|
65
|
+
self._file_encoding: str | None = None
|
|
66
|
+
self._attribute_cache: dict[int, list[dict[str, Any]]] = {}
|
|
67
|
+
|
|
68
|
+
def _reset_caches(self) -> None:
|
|
69
|
+
"""Reset all internal caches for a new file analysis."""
|
|
70
|
+
self._node_text_cache.clear()
|
|
71
|
+
self._processed_nodes.clear()
|
|
72
|
+
self._element_cache.clear()
|
|
73
|
+
self._attribute_cache.clear()
|
|
74
|
+
self.current_namespace = ""
|
|
75
|
+
|
|
76
|
+
def _get_node_text_optimized(self, node: "tree_sitter.Node") -> str:
|
|
77
|
+
"""
|
|
78
|
+
Get text content of a node with caching for performance.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
node: Tree-sitter node to extract text from
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Text content of the node as string
|
|
85
|
+
"""
|
|
86
|
+
# Use node position as cache key instead of object id for deterministic behavior
|
|
87
|
+
cache_key = (node.start_byte, node.end_byte)
|
|
88
|
+
if cache_key in self._node_text_cache:
|
|
89
|
+
return self._node_text_cache[cache_key]
|
|
90
|
+
|
|
91
|
+
# Extract text directly from source code string
|
|
92
|
+
text = self.source_code[node.start_byte : node.end_byte]
|
|
93
|
+
self._node_text_cache[cache_key] = text
|
|
94
|
+
return text
|
|
95
|
+
|
|
96
|
+
def _extract_namespace(self, node: "tree_sitter.Node") -> None:
|
|
97
|
+
"""
|
|
98
|
+
Extract namespace from the AST and set current_namespace.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
node: Root node of the AST
|
|
102
|
+
"""
|
|
103
|
+
if node.type == "namespace_declaration":
|
|
104
|
+
name_node = node.child_by_field_name("name")
|
|
105
|
+
if name_node:
|
|
106
|
+
self.current_namespace = self._get_node_text_optimized(name_node)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# Recursively search for namespace
|
|
110
|
+
for child in node.children:
|
|
111
|
+
if child.type == "namespace_declaration":
|
|
112
|
+
name_node = child.child_by_field_name("name")
|
|
113
|
+
if name_node:
|
|
114
|
+
self.current_namespace = self._get_node_text_optimized(name_node)
|
|
115
|
+
return
|
|
116
|
+
elif child.child_count > 0:
|
|
117
|
+
self._extract_namespace(child)
|
|
118
|
+
|
|
119
|
+
def _extract_modifiers(self, node: "tree_sitter.Node") -> list[str]:
|
|
120
|
+
"""
|
|
121
|
+
Extract modifiers from a declaration node.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
node: Declaration node (class, method, field, etc.)
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of modifier strings (e.g., ["public", "static", "async"])
|
|
128
|
+
"""
|
|
129
|
+
modifiers: list[str] = []
|
|
130
|
+
for child in node.children:
|
|
131
|
+
if child.type == "modifier":
|
|
132
|
+
modifier_text = self._get_node_text_optimized(child)
|
|
133
|
+
modifiers.append(modifier_text)
|
|
134
|
+
return modifiers
|
|
135
|
+
|
|
136
|
+
def _determine_visibility(self, modifiers: list[str]) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Determine visibility from modifiers.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
modifiers: List of modifier strings
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Visibility string ("public", "private", "protected", "internal")
|
|
145
|
+
"""
|
|
146
|
+
if "public" in modifiers:
|
|
147
|
+
return "public"
|
|
148
|
+
elif "private" in modifiers:
|
|
149
|
+
return "private"
|
|
150
|
+
elif "protected" in modifiers:
|
|
151
|
+
return "protected"
|
|
152
|
+
elif "internal" in modifiers:
|
|
153
|
+
return "internal"
|
|
154
|
+
else:
|
|
155
|
+
return "private" # Default to private in C#
|
|
156
|
+
|
|
157
|
+
def _extract_attributes(self, node: "tree_sitter.Node") -> list[dict[str, Any]]:
|
|
158
|
+
"""
|
|
159
|
+
Extract attributes (annotations) from a node.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
node: Node that may have attributes
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
List of attribute dictionaries with name, line, and text
|
|
166
|
+
"""
|
|
167
|
+
node_id = id(node)
|
|
168
|
+
if node_id in self._attribute_cache:
|
|
169
|
+
return self._attribute_cache[node_id]
|
|
170
|
+
|
|
171
|
+
attributes: list[dict[str, Any]] = []
|
|
172
|
+
|
|
173
|
+
# Look for attribute_list nodes before the declaration
|
|
174
|
+
prev_sibling = node.prev_sibling
|
|
175
|
+
while prev_sibling:
|
|
176
|
+
if prev_sibling.type == "attribute_list":
|
|
177
|
+
attr_text = self._get_node_text_optimized(prev_sibling)
|
|
178
|
+
attributes.append(
|
|
179
|
+
{
|
|
180
|
+
"name": attr_text.strip("[]"),
|
|
181
|
+
"line": prev_sibling.start_point[0] + 1,
|
|
182
|
+
"text": attr_text,
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
elif prev_sibling.type not in ["comment", "line_comment", "block_comment"]:
|
|
186
|
+
break
|
|
187
|
+
prev_sibling = prev_sibling.prev_sibling
|
|
188
|
+
|
|
189
|
+
attributes.reverse() # Restore original order
|
|
190
|
+
self._attribute_cache[node_id] = attributes
|
|
191
|
+
return attributes
|
|
192
|
+
|
|
193
|
+
def _extract_type_name(self, type_node: Optional["tree_sitter.Node"]) -> str:
|
|
194
|
+
"""
|
|
195
|
+
Extract type name from a type node, handling generic types and nullable types.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
type_node: Type node from the AST
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Type name as string (e.g., "int", "List<string>", "string?")
|
|
202
|
+
"""
|
|
203
|
+
if not type_node:
|
|
204
|
+
return "void"
|
|
205
|
+
|
|
206
|
+
return self._get_node_text_optimized(type_node)
|
|
207
|
+
|
|
208
|
+
def _extract_parameters(
|
|
209
|
+
self, params_node: Optional["tree_sitter.Node"]
|
|
210
|
+
) -> list[str]:
|
|
211
|
+
"""
|
|
212
|
+
Extract method parameters.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
params_node: Parameter list node
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
List of parameter strings (e.g., ["int id", "string name"])
|
|
219
|
+
"""
|
|
220
|
+
if not params_node:
|
|
221
|
+
return []
|
|
222
|
+
|
|
223
|
+
parameters: list[str] = []
|
|
224
|
+
for child in params_node.children:
|
|
225
|
+
if child.type == "parameter":
|
|
226
|
+
param_text = self._get_node_text_optimized(child)
|
|
227
|
+
parameters.append(param_text)
|
|
228
|
+
|
|
229
|
+
return parameters
|
|
230
|
+
|
|
231
|
+
def _traverse_iterative(
|
|
232
|
+
self, root_node: "tree_sitter.Node"
|
|
233
|
+
) -> Iterator["tree_sitter.Node"]:
|
|
234
|
+
"""
|
|
235
|
+
Iteratively traverse AST nodes to avoid stack overflow on large files.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
root_node: Root node to start traversal from
|
|
239
|
+
|
|
240
|
+
Yields:
|
|
241
|
+
Tree-sitter nodes in depth-first order
|
|
242
|
+
"""
|
|
243
|
+
stack = [root_node]
|
|
244
|
+
while stack:
|
|
245
|
+
node = stack.pop()
|
|
246
|
+
yield node
|
|
247
|
+
# Add children in reverse order to maintain left-to-right traversal
|
|
248
|
+
stack.extend(reversed(list(node.children)))
|
|
249
|
+
|
|
250
|
+
def extract_classes(
|
|
251
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
252
|
+
) -> list[Class]:
|
|
253
|
+
"""
|
|
254
|
+
Extract classes, interfaces, records, enums, and structs.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
tree: Tree-sitter AST tree parsed from C# source
|
|
258
|
+
source_code: Original C# source code as string
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
List of Class objects representing all class-like declarations
|
|
262
|
+
"""
|
|
263
|
+
self.source_code = source_code or ""
|
|
264
|
+
self.content_lines = self.source_code.split("\n")
|
|
265
|
+
self._reset_caches()
|
|
266
|
+
|
|
267
|
+
classes: list[Class] = []
|
|
268
|
+
|
|
269
|
+
if tree is None or tree.root_node is None:
|
|
270
|
+
return classes
|
|
271
|
+
|
|
272
|
+
# Extract namespace first
|
|
273
|
+
self._extract_namespace(tree.root_node)
|
|
274
|
+
|
|
275
|
+
# Extract all class-like declarations
|
|
276
|
+
for node in self._traverse_iterative(tree.root_node):
|
|
277
|
+
if node.type in [
|
|
278
|
+
"class_declaration",
|
|
279
|
+
"interface_declaration",
|
|
280
|
+
"record_declaration",
|
|
281
|
+
"enum_declaration",
|
|
282
|
+
"struct_declaration",
|
|
283
|
+
]:
|
|
284
|
+
class_obj = self._extract_class_declaration(node)
|
|
285
|
+
if class_obj:
|
|
286
|
+
classes.append(class_obj)
|
|
287
|
+
|
|
288
|
+
# Sort by start line for deterministic output
|
|
289
|
+
classes.sort(key=lambda c: c.start_line)
|
|
290
|
+
|
|
291
|
+
return classes
|
|
292
|
+
|
|
293
|
+
def _extract_class_declaration(self, node: "tree_sitter.Node") -> Class | None:
|
|
294
|
+
"""
|
|
295
|
+
Extract a single class declaration.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
node: Class declaration node
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
Class object or None if extraction fails
|
|
302
|
+
"""
|
|
303
|
+
try:
|
|
304
|
+
# Get class name
|
|
305
|
+
name_node = node.child_by_field_name("name")
|
|
306
|
+
if not name_node:
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
class_name = self._get_node_text_optimized(name_node)
|
|
310
|
+
|
|
311
|
+
# Get modifiers and visibility
|
|
312
|
+
modifiers = self._extract_modifiers(node)
|
|
313
|
+
visibility = self._determine_visibility(modifiers)
|
|
314
|
+
|
|
315
|
+
# Get attributes
|
|
316
|
+
attributes = self._extract_attributes(node)
|
|
317
|
+
|
|
318
|
+
# Get base class and interfaces
|
|
319
|
+
base_list_node = node.child_by_field_name("bases")
|
|
320
|
+
superclass = None
|
|
321
|
+
interfaces: list[str] = []
|
|
322
|
+
|
|
323
|
+
if base_list_node:
|
|
324
|
+
base_items = [
|
|
325
|
+
self._get_node_text_optimized(child)
|
|
326
|
+
for child in base_list_node.children
|
|
327
|
+
if child.type
|
|
328
|
+
in ["type_identifier", "generic_name", "qualified_name"]
|
|
329
|
+
]
|
|
330
|
+
if base_items:
|
|
331
|
+
if node.type == "interface_declaration":
|
|
332
|
+
interfaces = base_items
|
|
333
|
+
else:
|
|
334
|
+
superclass = base_items[0]
|
|
335
|
+
interfaces = base_items[1:] if len(base_items) > 1 else []
|
|
336
|
+
|
|
337
|
+
# Get full qualified name
|
|
338
|
+
full_name = (
|
|
339
|
+
f"{self.current_namespace}.{class_name}"
|
|
340
|
+
if self.current_namespace
|
|
341
|
+
else class_name
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Get raw text
|
|
345
|
+
raw_text = self._get_node_text_optimized(node)
|
|
346
|
+
|
|
347
|
+
# Determine class type
|
|
348
|
+
class_type_map = {
|
|
349
|
+
"class_declaration": "class",
|
|
350
|
+
"interface_declaration": "interface",
|
|
351
|
+
"record_declaration": "record",
|
|
352
|
+
"enum_declaration": "enum",
|
|
353
|
+
"struct_declaration": "struct",
|
|
354
|
+
}
|
|
355
|
+
class_type = class_type_map.get(node.type, "class")
|
|
356
|
+
|
|
357
|
+
return Class(
|
|
358
|
+
name=class_name,
|
|
359
|
+
start_line=node.start_point[0] + 1,
|
|
360
|
+
end_line=node.end_point[0] + 1,
|
|
361
|
+
raw_text=raw_text,
|
|
362
|
+
full_qualified_name=full_name,
|
|
363
|
+
superclass=superclass,
|
|
364
|
+
interfaces=interfaces,
|
|
365
|
+
modifiers=modifiers,
|
|
366
|
+
visibility=visibility,
|
|
367
|
+
annotations=attributes,
|
|
368
|
+
class_type=class_type,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
except Exception as e:
|
|
372
|
+
log_error(f"Error extracting class declaration: {e}")
|
|
373
|
+
return None
|
|
374
|
+
|
|
375
|
+
def extract_functions(
|
|
376
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
377
|
+
) -> list[Function]:
|
|
378
|
+
"""
|
|
379
|
+
Extract methods, constructors, and properties.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
tree: Tree-sitter AST tree parsed from C# source
|
|
383
|
+
source_code: Original C# source code as string
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
List of Function objects representing methods, constructors, and properties
|
|
387
|
+
"""
|
|
388
|
+
self.source_code = source_code or ""
|
|
389
|
+
self.content_lines = self.source_code.split("\n")
|
|
390
|
+
self._reset_caches()
|
|
391
|
+
|
|
392
|
+
functions: list[Function] = []
|
|
393
|
+
|
|
394
|
+
if tree is None or tree.root_node is None:
|
|
395
|
+
return functions
|
|
396
|
+
|
|
397
|
+
# Extract namespace first
|
|
398
|
+
self._extract_namespace(tree.root_node)
|
|
399
|
+
|
|
400
|
+
# Extract methods, constructors, and properties
|
|
401
|
+
for node in self._traverse_iterative(tree.root_node):
|
|
402
|
+
if node.type == "method_declaration":
|
|
403
|
+
func = self._extract_method(node)
|
|
404
|
+
if func:
|
|
405
|
+
functions.append(func)
|
|
406
|
+
elif node.type == "constructor_declaration":
|
|
407
|
+
func = self._extract_constructor(node)
|
|
408
|
+
if func:
|
|
409
|
+
functions.append(func)
|
|
410
|
+
elif node.type == "property_declaration":
|
|
411
|
+
func = self._extract_property(node)
|
|
412
|
+
if func:
|
|
413
|
+
functions.append(func)
|
|
414
|
+
|
|
415
|
+
# Sort by start line for deterministic output
|
|
416
|
+
functions.sort(key=lambda f: f.start_line)
|
|
417
|
+
|
|
418
|
+
return functions
|
|
419
|
+
|
|
420
|
+
def _extract_method(self, node: "tree_sitter.Node") -> Function | None:
|
|
421
|
+
"""
|
|
422
|
+
Extract a method declaration.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
node: Method declaration node
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Function object or None if extraction fails
|
|
429
|
+
"""
|
|
430
|
+
try:
|
|
431
|
+
# Get method name
|
|
432
|
+
name_node = node.child_by_field_name("name")
|
|
433
|
+
if not name_node:
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
method_name = self._get_node_text_optimized(name_node)
|
|
437
|
+
|
|
438
|
+
# Get modifiers and visibility
|
|
439
|
+
modifiers = self._extract_modifiers(node)
|
|
440
|
+
visibility = self._determine_visibility(modifiers)
|
|
441
|
+
|
|
442
|
+
# Check if async
|
|
443
|
+
is_async = "async" in modifiers
|
|
444
|
+
|
|
445
|
+
# Get attributes
|
|
446
|
+
attributes = self._extract_attributes(node)
|
|
447
|
+
|
|
448
|
+
# Get return type
|
|
449
|
+
type_node = node.child_by_field_name("type")
|
|
450
|
+
return_type = self._extract_type_name(type_node)
|
|
451
|
+
|
|
452
|
+
# Get parameters
|
|
453
|
+
params_node = node.child_by_field_name("parameters")
|
|
454
|
+
parameters = self._extract_parameters(params_node)
|
|
455
|
+
|
|
456
|
+
# Get raw text
|
|
457
|
+
raw_text = self._get_node_text_optimized(node)
|
|
458
|
+
|
|
459
|
+
# Calculate complexity (simplified)
|
|
460
|
+
complexity_score = self._calculate_complexity(node)
|
|
461
|
+
|
|
462
|
+
return Function(
|
|
463
|
+
name=method_name,
|
|
464
|
+
start_line=node.start_point[0] + 1,
|
|
465
|
+
end_line=node.end_point[0] + 1,
|
|
466
|
+
raw_text=raw_text,
|
|
467
|
+
parameters=parameters,
|
|
468
|
+
return_type=return_type,
|
|
469
|
+
modifiers=modifiers,
|
|
470
|
+
visibility=visibility,
|
|
471
|
+
is_async=is_async,
|
|
472
|
+
annotations=attributes,
|
|
473
|
+
complexity_score=complexity_score,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
except Exception as e:
|
|
477
|
+
log_error(f"Error extracting method: {e}")
|
|
478
|
+
return None
|
|
479
|
+
|
|
480
|
+
def _extract_constructor(self, node: "tree_sitter.Node") -> Function | None:
|
|
481
|
+
"""
|
|
482
|
+
Extract a constructor declaration.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
node: Constructor declaration node
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
Function object or None if extraction fails
|
|
489
|
+
"""
|
|
490
|
+
try:
|
|
491
|
+
# Get constructor name
|
|
492
|
+
name_node = node.child_by_field_name("name")
|
|
493
|
+
if not name_node:
|
|
494
|
+
return None
|
|
495
|
+
|
|
496
|
+
constructor_name = self._get_node_text_optimized(name_node)
|
|
497
|
+
|
|
498
|
+
# Get modifiers and visibility
|
|
499
|
+
modifiers = self._extract_modifiers(node)
|
|
500
|
+
visibility = self._determine_visibility(modifiers)
|
|
501
|
+
|
|
502
|
+
# Get attributes
|
|
503
|
+
attributes = self._extract_attributes(node)
|
|
504
|
+
|
|
505
|
+
# Get parameters
|
|
506
|
+
params_node = node.child_by_field_name("parameters")
|
|
507
|
+
parameters = self._extract_parameters(params_node)
|
|
508
|
+
|
|
509
|
+
# Get raw text
|
|
510
|
+
raw_text = self._get_node_text_optimized(node)
|
|
511
|
+
|
|
512
|
+
return Function(
|
|
513
|
+
name=constructor_name,
|
|
514
|
+
start_line=node.start_point[0] + 1,
|
|
515
|
+
end_line=node.end_point[0] + 1,
|
|
516
|
+
raw_text=raw_text,
|
|
517
|
+
parameters=parameters,
|
|
518
|
+
return_type="void",
|
|
519
|
+
modifiers=modifiers,
|
|
520
|
+
visibility=visibility,
|
|
521
|
+
is_constructor=True,
|
|
522
|
+
annotations=attributes,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
except Exception as e:
|
|
526
|
+
log_error(f"Error extracting constructor: {e}")
|
|
527
|
+
return None
|
|
528
|
+
|
|
529
|
+
def _extract_property(self, node: "tree_sitter.Node") -> Function | None:
|
|
530
|
+
"""
|
|
531
|
+
Extract a property declaration.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
node: Property declaration node
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Function object with is_property=True or None if extraction fails
|
|
538
|
+
"""
|
|
539
|
+
try:
|
|
540
|
+
# Get property name
|
|
541
|
+
name_node = node.child_by_field_name("name")
|
|
542
|
+
if not name_node:
|
|
543
|
+
return None
|
|
544
|
+
|
|
545
|
+
property_name = self._get_node_text_optimized(name_node)
|
|
546
|
+
|
|
547
|
+
# Get modifiers and visibility
|
|
548
|
+
modifiers = self._extract_modifiers(node)
|
|
549
|
+
visibility = self._determine_visibility(modifiers)
|
|
550
|
+
|
|
551
|
+
# Get attributes
|
|
552
|
+
attributes = self._extract_attributes(node)
|
|
553
|
+
|
|
554
|
+
# Get property type
|
|
555
|
+
type_node = node.child_by_field_name("type")
|
|
556
|
+
property_type = self._extract_type_name(type_node)
|
|
557
|
+
|
|
558
|
+
# Get raw text
|
|
559
|
+
raw_text = self._get_node_text_optimized(node)
|
|
560
|
+
|
|
561
|
+
# Check for getter/setter (for future use)
|
|
562
|
+
# has_getter = False
|
|
563
|
+
# has_setter = False
|
|
564
|
+
# for child in node.children:
|
|
565
|
+
# if child.type == "accessor_list":
|
|
566
|
+
# for accessor in child.children:
|
|
567
|
+
# if accessor.type == "get_accessor_declaration":
|
|
568
|
+
# has_getter = True
|
|
569
|
+
# elif accessor.type == "set_accessor_declaration":
|
|
570
|
+
# has_setter = True
|
|
571
|
+
# elif accessor.type == "init_accessor_declaration":
|
|
572
|
+
# has_setter = True # init is a special setter
|
|
573
|
+
|
|
574
|
+
return Function(
|
|
575
|
+
name=property_name,
|
|
576
|
+
start_line=node.start_point[0] + 1,
|
|
577
|
+
end_line=node.end_point[0] + 1,
|
|
578
|
+
raw_text=raw_text,
|
|
579
|
+
parameters=[],
|
|
580
|
+
return_type=property_type,
|
|
581
|
+
modifiers=modifiers,
|
|
582
|
+
visibility=visibility,
|
|
583
|
+
is_property=True,
|
|
584
|
+
annotations=attributes,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
except Exception as e:
|
|
588
|
+
log_error(f"Error extracting property: {e}")
|
|
589
|
+
return None
|
|
590
|
+
|
|
591
|
+
def _calculate_complexity(self, node: "tree_sitter.Node") -> int:
|
|
592
|
+
"""
|
|
593
|
+
Calculate cyclomatic complexity of a method.
|
|
594
|
+
|
|
595
|
+
Args:
|
|
596
|
+
node: Method node
|
|
597
|
+
|
|
598
|
+
Returns:
|
|
599
|
+
Complexity score (1 + number of decision points)
|
|
600
|
+
"""
|
|
601
|
+
complexity = 1
|
|
602
|
+
decision_keywords = {
|
|
603
|
+
"if_statement",
|
|
604
|
+
"switch_statement",
|
|
605
|
+
"for_statement",
|
|
606
|
+
"foreach_statement",
|
|
607
|
+
"while_statement",
|
|
608
|
+
"do_statement",
|
|
609
|
+
"catch_clause",
|
|
610
|
+
"conditional_expression", # ternary operator
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
for child in self._traverse_iterative(node):
|
|
614
|
+
if child.type in decision_keywords:
|
|
615
|
+
complexity += 1
|
|
616
|
+
|
|
617
|
+
return complexity
|
|
618
|
+
|
|
619
|
+
def extract_variables(
|
|
620
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
621
|
+
) -> list[Variable]:
|
|
622
|
+
"""
|
|
623
|
+
Extract fields, constants, and events.
|
|
624
|
+
|
|
625
|
+
Args:
|
|
626
|
+
tree: Tree-sitter AST tree parsed from C# source
|
|
627
|
+
source_code: Original C# source code as string
|
|
628
|
+
|
|
629
|
+
Returns:
|
|
630
|
+
List of Variable objects representing fields
|
|
631
|
+
"""
|
|
632
|
+
self.source_code = source_code or ""
|
|
633
|
+
self.content_lines = self.source_code.split("\n")
|
|
634
|
+
self._reset_caches()
|
|
635
|
+
|
|
636
|
+
variables: list[Variable] = []
|
|
637
|
+
|
|
638
|
+
if tree is None or tree.root_node is None:
|
|
639
|
+
return variables
|
|
640
|
+
|
|
641
|
+
# Extract fields
|
|
642
|
+
for node in self._traverse_iterative(tree.root_node):
|
|
643
|
+
if node.type == "field_declaration":
|
|
644
|
+
vars_list = self._extract_field(node)
|
|
645
|
+
variables.extend(vars_list)
|
|
646
|
+
elif node.type == "event_field_declaration":
|
|
647
|
+
vars_list = self._extract_event(node)
|
|
648
|
+
variables.extend(vars_list)
|
|
649
|
+
|
|
650
|
+
# Sort by start line for deterministic output
|
|
651
|
+
variables.sort(key=lambda v: v.start_line)
|
|
652
|
+
|
|
653
|
+
return variables
|
|
654
|
+
|
|
655
|
+
def _extract_field(self, node: "tree_sitter.Node") -> list[Variable]:
|
|
656
|
+
"""
|
|
657
|
+
Extract field declarations.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
node: Field declaration node
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
List of Variable objects (can be multiple if multiple variables declared)
|
|
664
|
+
"""
|
|
665
|
+
variables: list[Variable] = []
|
|
666
|
+
|
|
667
|
+
try:
|
|
668
|
+
# Get modifiers
|
|
669
|
+
modifiers = self._extract_modifiers(node)
|
|
670
|
+
visibility = self._determine_visibility(modifiers)
|
|
671
|
+
|
|
672
|
+
# Check if constant or readonly
|
|
673
|
+
is_constant = "const" in modifiers
|
|
674
|
+
# is_readonly = "readonly" in modifiers # For future use
|
|
675
|
+
|
|
676
|
+
# Get attributes
|
|
677
|
+
attributes = self._extract_attributes(node)
|
|
678
|
+
|
|
679
|
+
# Get field type
|
|
680
|
+
type_node = None
|
|
681
|
+
for child in node.children:
|
|
682
|
+
if child.type == "variable_declaration":
|
|
683
|
+
type_node = child.child_by_field_name("type")
|
|
684
|
+
break
|
|
685
|
+
|
|
686
|
+
field_type = self._extract_type_name(type_node)
|
|
687
|
+
|
|
688
|
+
# Get variable declarators
|
|
689
|
+
for child in node.children:
|
|
690
|
+
if child.type == "variable_declaration":
|
|
691
|
+
for declarator in child.children:
|
|
692
|
+
if declarator.type == "variable_declarator":
|
|
693
|
+
name_node = declarator.child_by_field_name("name")
|
|
694
|
+
if name_node:
|
|
695
|
+
field_name = self._get_node_text_optimized(name_node)
|
|
696
|
+
raw_text = self._get_node_text_optimized(node)
|
|
697
|
+
|
|
698
|
+
variables.append(
|
|
699
|
+
Variable(
|
|
700
|
+
name=field_name,
|
|
701
|
+
start_line=node.start_point[0] + 1,
|
|
702
|
+
end_line=node.end_point[0] + 1,
|
|
703
|
+
raw_text=raw_text,
|
|
704
|
+
variable_type=field_type,
|
|
705
|
+
modifiers=modifiers,
|
|
706
|
+
visibility=visibility,
|
|
707
|
+
is_constant=is_constant,
|
|
708
|
+
annotations=attributes,
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
except Exception as e:
|
|
713
|
+
log_error(f"Error extracting field: {e}")
|
|
714
|
+
|
|
715
|
+
return variables
|
|
716
|
+
|
|
717
|
+
def _extract_event(self, node: "tree_sitter.Node") -> list[Variable]:
|
|
718
|
+
"""
|
|
719
|
+
Extract event field declarations.
|
|
720
|
+
|
|
721
|
+
Args:
|
|
722
|
+
node: Event field declaration node
|
|
723
|
+
|
|
724
|
+
Returns:
|
|
725
|
+
List of Variable objects representing events
|
|
726
|
+
"""
|
|
727
|
+
variables: list[Variable] = []
|
|
728
|
+
|
|
729
|
+
try:
|
|
730
|
+
# Get modifiers
|
|
731
|
+
modifiers = self._extract_modifiers(node)
|
|
732
|
+
modifiers.append("event") # Mark as event
|
|
733
|
+
visibility = self._determine_visibility(modifiers)
|
|
734
|
+
|
|
735
|
+
# Get attributes
|
|
736
|
+
attributes = self._extract_attributes(node)
|
|
737
|
+
|
|
738
|
+
# Get event type
|
|
739
|
+
type_node = None
|
|
740
|
+
for child in node.children:
|
|
741
|
+
if child.type == "variable_declaration":
|
|
742
|
+
type_node = child.child_by_field_name("type")
|
|
743
|
+
break
|
|
744
|
+
|
|
745
|
+
event_type = self._extract_type_name(type_node)
|
|
746
|
+
|
|
747
|
+
# Get variable declarators
|
|
748
|
+
for child in node.children:
|
|
749
|
+
if child.type == "variable_declaration":
|
|
750
|
+
for declarator in child.children:
|
|
751
|
+
if declarator.type == "variable_declarator":
|
|
752
|
+
name_node = declarator.child_by_field_name("name")
|
|
753
|
+
if name_node:
|
|
754
|
+
event_name = self._get_node_text_optimized(name_node)
|
|
755
|
+
raw_text = self._get_node_text_optimized(node)
|
|
756
|
+
|
|
757
|
+
variables.append(
|
|
758
|
+
Variable(
|
|
759
|
+
name=event_name,
|
|
760
|
+
start_line=node.start_point[0] + 1,
|
|
761
|
+
end_line=node.end_point[0] + 1,
|
|
762
|
+
raw_text=raw_text,
|
|
763
|
+
variable_type=event_type,
|
|
764
|
+
modifiers=modifiers,
|
|
765
|
+
visibility=visibility,
|
|
766
|
+
annotations=attributes,
|
|
767
|
+
)
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
except Exception as e:
|
|
771
|
+
log_error(f"Error extracting event: {e}")
|
|
772
|
+
|
|
773
|
+
return variables
|
|
774
|
+
|
|
775
|
+
def extract_imports(
|
|
776
|
+
self, tree: "tree_sitter.Tree", source_code: str
|
|
777
|
+
) -> list[Import]:
|
|
778
|
+
"""
|
|
779
|
+
Extract using directives.
|
|
780
|
+
|
|
781
|
+
Args:
|
|
782
|
+
tree: Tree-sitter AST tree parsed from C# source
|
|
783
|
+
source_code: Original C# source code as string
|
|
784
|
+
|
|
785
|
+
Returns:
|
|
786
|
+
List of Import objects representing using directives
|
|
787
|
+
"""
|
|
788
|
+
self.source_code = source_code or ""
|
|
789
|
+
self.content_lines = self.source_code.split("\n")
|
|
790
|
+
|
|
791
|
+
imports: list[Import] = []
|
|
792
|
+
|
|
793
|
+
if tree is None or tree.root_node is None:
|
|
794
|
+
return imports
|
|
795
|
+
|
|
796
|
+
# Extract using directives
|
|
797
|
+
for node in self._traverse_iterative(tree.root_node):
|
|
798
|
+
if node.type == "using_directive":
|
|
799
|
+
import_obj = self._extract_using_directive(node)
|
|
800
|
+
if import_obj:
|
|
801
|
+
imports.append(import_obj)
|
|
802
|
+
|
|
803
|
+
# Sort by start line for deterministic output
|
|
804
|
+
imports.sort(key=lambda i: i.start_line)
|
|
805
|
+
|
|
806
|
+
return imports
|
|
807
|
+
|
|
808
|
+
def _extract_using_directive(self, node: "tree_sitter.Node") -> Import | None:
|
|
809
|
+
"""
|
|
810
|
+
Extract a using directive.
|
|
811
|
+
|
|
812
|
+
Args:
|
|
813
|
+
node: Using directive node
|
|
814
|
+
|
|
815
|
+
Returns:
|
|
816
|
+
Import object or None if extraction fails
|
|
817
|
+
"""
|
|
818
|
+
try:
|
|
819
|
+
# Get the namespace or type being imported
|
|
820
|
+
name_node = node.child_by_field_name("name")
|
|
821
|
+
if not name_node:
|
|
822
|
+
# Try to find qualified_name or identifier
|
|
823
|
+
for child in node.children:
|
|
824
|
+
if child.type in ["qualified_name", "identifier", "name_equals"]:
|
|
825
|
+
name_node = child
|
|
826
|
+
break
|
|
827
|
+
|
|
828
|
+
if not name_node:
|
|
829
|
+
return None
|
|
830
|
+
|
|
831
|
+
import_name = self._get_node_text_optimized(name_node)
|
|
832
|
+
|
|
833
|
+
# Check if it's a static using
|
|
834
|
+
is_static = False
|
|
835
|
+
for child in node.children:
|
|
836
|
+
if (
|
|
837
|
+
child.type == "static"
|
|
838
|
+
or self._get_node_text_optimized(child) == "static"
|
|
839
|
+
):
|
|
840
|
+
is_static = True
|
|
841
|
+
break
|
|
842
|
+
|
|
843
|
+
# Get raw text
|
|
844
|
+
raw_text = self._get_node_text_optimized(node)
|
|
845
|
+
|
|
846
|
+
return Import(
|
|
847
|
+
name=import_name,
|
|
848
|
+
start_line=node.start_point[0] + 1,
|
|
849
|
+
end_line=node.end_point[0] + 1,
|
|
850
|
+
raw_text=raw_text,
|
|
851
|
+
module_name=import_name,
|
|
852
|
+
is_static=is_static,
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
except Exception as e:
|
|
856
|
+
log_error(f"Error extracting using directive: {e}")
|
|
857
|
+
return None
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
class CSharpPlugin(LanguagePlugin):
|
|
861
|
+
"""
|
|
862
|
+
C# language plugin implementation.
|
|
863
|
+
|
|
864
|
+
This plugin provides C# language support for tree-sitter-analyzer,
|
|
865
|
+
enabling analysis of C# source files including modern C# features
|
|
866
|
+
like records, nullable reference types, and async/await patterns.
|
|
867
|
+
"""
|
|
868
|
+
|
|
869
|
+
def __init__(self) -> None:
|
|
870
|
+
"""Initialize the C# plugin."""
|
|
871
|
+
super().__init__()
|
|
872
|
+
self.extractor = CSharpElementExtractor()
|
|
873
|
+
self.language = "csharp"
|
|
874
|
+
self.supported_extensions = [".cs"]
|
|
875
|
+
self._cached_language: Any | None = None
|
|
876
|
+
|
|
877
|
+
def get_language_name(self) -> str:
|
|
878
|
+
"""
|
|
879
|
+
Get the language name.
|
|
880
|
+
|
|
881
|
+
Returns:
|
|
882
|
+
Language name as string: "csharp"
|
|
883
|
+
"""
|
|
884
|
+
return "csharp"
|
|
885
|
+
|
|
886
|
+
def get_file_extensions(self) -> list[str]:
|
|
887
|
+
"""
|
|
888
|
+
Get supported file extensions.
|
|
889
|
+
|
|
890
|
+
Returns:
|
|
891
|
+
List of file extensions: [".cs"]
|
|
892
|
+
"""
|
|
893
|
+
return [".cs"]
|
|
894
|
+
|
|
895
|
+
def create_extractor(self) -> ElementExtractor:
|
|
896
|
+
"""
|
|
897
|
+
Create a new C# element extractor instance.
|
|
898
|
+
|
|
899
|
+
Returns:
|
|
900
|
+
CSharpElementExtractor instance
|
|
901
|
+
"""
|
|
902
|
+
return CSharpElementExtractor()
|
|
903
|
+
|
|
904
|
+
def get_queries(self) -> dict[str, str]:
|
|
905
|
+
"""
|
|
906
|
+
Return C#-specific tree-sitter queries.
|
|
907
|
+
|
|
908
|
+
Returns:
|
|
909
|
+
Dictionary of query names to query strings
|
|
910
|
+
"""
|
|
911
|
+
from ..queries.csharp import CSHARP_QUERIES
|
|
912
|
+
|
|
913
|
+
return CSHARP_QUERIES
|
|
914
|
+
|
|
915
|
+
def execute_query_strategy(
|
|
916
|
+
self, query_key: str | None, language: str
|
|
917
|
+
) -> str | None:
|
|
918
|
+
"""
|
|
919
|
+
Execute query strategy for C#.
|
|
920
|
+
|
|
921
|
+
Args:
|
|
922
|
+
query_key: Query key to execute
|
|
923
|
+
language: Language name
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
Query string or None if not applicable
|
|
927
|
+
"""
|
|
928
|
+
if language != "csharp":
|
|
929
|
+
return None
|
|
930
|
+
|
|
931
|
+
queries = self.get_queries()
|
|
932
|
+
return queries.get(query_key) if query_key else None
|
|
933
|
+
|
|
934
|
+
def get_element_categories(self) -> dict[str, list[str]]:
|
|
935
|
+
"""
|
|
936
|
+
Return C# element categories for query execution.
|
|
937
|
+
|
|
938
|
+
Returns:
|
|
939
|
+
Dictionary of category names to element types
|
|
940
|
+
"""
|
|
941
|
+
return {
|
|
942
|
+
"classes": ["class", "interface", "record", "enum", "struct"],
|
|
943
|
+
"methods": ["method", "constructor"],
|
|
944
|
+
"properties": ["property", "auto_property", "computed_property"],
|
|
945
|
+
"fields": ["field", "const_field", "readonly_field", "event"],
|
|
946
|
+
"imports": ["using", "static_using"],
|
|
947
|
+
"attributes": ["attribute", "http_attribute", "authorize_attribute"],
|
|
948
|
+
"async": ["async_method"],
|
|
949
|
+
"linq": ["linq_query", "from_clause", "where_clause", "select_clause"],
|
|
950
|
+
"control_flow": [
|
|
951
|
+
"if_statement",
|
|
952
|
+
"for_statement",
|
|
953
|
+
"foreach_statement",
|
|
954
|
+
"while_statement",
|
|
955
|
+
"switch_statement",
|
|
956
|
+
"try_statement",
|
|
957
|
+
],
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
def get_tree_sitter_language(self) -> Any | None:
|
|
961
|
+
"""
|
|
962
|
+
Load tree-sitter-c-sharp language.
|
|
963
|
+
|
|
964
|
+
Returns:
|
|
965
|
+
Tree-sitter Language object or None if loading fails
|
|
966
|
+
"""
|
|
967
|
+
if self._cached_language is not None:
|
|
968
|
+
return self._cached_language
|
|
969
|
+
|
|
970
|
+
try:
|
|
971
|
+
import tree_sitter_c_sharp
|
|
972
|
+
|
|
973
|
+
lang = tree_sitter_c_sharp.language()
|
|
974
|
+
|
|
975
|
+
# Handle both old and new tree-sitter API
|
|
976
|
+
if hasattr(lang, "__class__") and "Language" in str(type(lang)):
|
|
977
|
+
self._cached_language = lang
|
|
978
|
+
else:
|
|
979
|
+
self._cached_language = tree_sitter.Language(lang)
|
|
980
|
+
|
|
981
|
+
log_debug("Successfully loaded tree-sitter-c-sharp language")
|
|
982
|
+
return self._cached_language
|
|
983
|
+
|
|
984
|
+
except ImportError as e:
|
|
985
|
+
log_error(f"tree-sitter-c-sharp not available: {e}")
|
|
986
|
+
log_error("Install with: pip install tree-sitter-c-sharp")
|
|
987
|
+
return None
|
|
988
|
+
except Exception as e:
|
|
989
|
+
log_error(f"Failed to load tree-sitter language for C#: {e}")
|
|
990
|
+
return None
|
|
991
|
+
|
|
992
|
+
async def analyze_file(
|
|
993
|
+
self, file_path: str, request: "AnalysisRequest"
|
|
994
|
+
) -> "AnalysisResult":
|
|
995
|
+
"""
|
|
996
|
+
Analyze a C# file and extract all elements.
|
|
997
|
+
|
|
998
|
+
Args:
|
|
999
|
+
file_path: Path to the C# file to analyze
|
|
1000
|
+
request: Analysis request containing file options
|
|
1001
|
+
|
|
1002
|
+
Returns:
|
|
1003
|
+
AnalysisResult with extracted elements
|
|
1004
|
+
"""
|
|
1005
|
+
from ..encoding_utils import read_file_safe
|
|
1006
|
+
from ..models import AnalysisResult
|
|
1007
|
+
|
|
1008
|
+
try:
|
|
1009
|
+
# Read file content
|
|
1010
|
+
source_code, encoding = read_file_safe(file_path)
|
|
1011
|
+
self.extractor._file_encoding = encoding
|
|
1012
|
+
|
|
1013
|
+
# Get tree-sitter language
|
|
1014
|
+
language = self.get_tree_sitter_language()
|
|
1015
|
+
if not language:
|
|
1016
|
+
log_error("Failed to load C# language")
|
|
1017
|
+
return AnalysisResult(
|
|
1018
|
+
file_path=file_path,
|
|
1019
|
+
language="csharp",
|
|
1020
|
+
elements=[],
|
|
1021
|
+
success=False,
|
|
1022
|
+
error_message="Failed to load C# language",
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
# Parse the source code
|
|
1026
|
+
parser = tree_sitter.Parser()
|
|
1027
|
+
|
|
1028
|
+
# Set language using the appropriate method
|
|
1029
|
+
if hasattr(parser, "set_language"):
|
|
1030
|
+
parser.set_language(language)
|
|
1031
|
+
elif hasattr(parser, "language"):
|
|
1032
|
+
parser.language = language
|
|
1033
|
+
else:
|
|
1034
|
+
parser = tree_sitter.Parser(language)
|
|
1035
|
+
|
|
1036
|
+
tree = parser.parse(source_code.encode("utf-8"))
|
|
1037
|
+
|
|
1038
|
+
# Extract all elements
|
|
1039
|
+
classes = self.extractor.extract_classes(tree, source_code)
|
|
1040
|
+
functions = self.extractor.extract_functions(tree, source_code)
|
|
1041
|
+
variables = self.extractor.extract_variables(tree, source_code)
|
|
1042
|
+
imports = self.extractor.extract_imports(tree, source_code)
|
|
1043
|
+
|
|
1044
|
+
# Combine all elements into a single list
|
|
1045
|
+
elements: list[Any] = []
|
|
1046
|
+
elements.extend(classes)
|
|
1047
|
+
elements.extend(functions)
|
|
1048
|
+
elements.extend(variables)
|
|
1049
|
+
elements.extend(imports)
|
|
1050
|
+
|
|
1051
|
+
# Count AST nodes
|
|
1052
|
+
node_count = sum(
|
|
1053
|
+
1 for _ in self.extractor._traverse_iterative(tree.root_node)
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
# Count lines
|
|
1057
|
+
line_count = len(source_code.split("\n"))
|
|
1058
|
+
|
|
1059
|
+
return AnalysisResult(
|
|
1060
|
+
file_path=file_path,
|
|
1061
|
+
language="csharp",
|
|
1062
|
+
elements=elements,
|
|
1063
|
+
node_count=node_count,
|
|
1064
|
+
line_count=line_count,
|
|
1065
|
+
source_code=source_code,
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
except Exception as e:
|
|
1069
|
+
log_error(f"Error analyzing C# file {file_path}: {e}")
|
|
1070
|
+
return AnalysisResult(
|
|
1071
|
+
file_path=file_path,
|
|
1072
|
+
language="csharp",
|
|
1073
|
+
elements=[],
|
|
1074
|
+
success=False,
|
|
1075
|
+
error_message=str(e),
|
|
1076
|
+
)
|