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.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. 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
+ )