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,836 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Go Language Plugin
4
+
5
+ Provides Go-specific parsing and element extraction functionality.
6
+ Supports packages, functions, methods, structs, interfaces, type aliases,
7
+ const/var declarations, goroutines, and channels.
8
+ """
9
+
10
+ import re
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ if TYPE_CHECKING:
14
+ import tree_sitter
15
+
16
+ from ..core.analysis_engine import AnalysisRequest
17
+ from ..models import AnalysisResult
18
+
19
+ from ..encoding_utils import extract_text_slice, safe_encode
20
+ from ..models import Class, Function, Import, Package, Variable
21
+ from ..plugins.base import ElementExtractor, LanguagePlugin
22
+ from ..utils import log_debug, log_error
23
+
24
+
25
+ class GoElementExtractor(ElementExtractor):
26
+ """Go-specific element extractor"""
27
+
28
+ def __init__(self) -> None:
29
+ """Initialize the Go element extractor."""
30
+ self.current_package: str = ""
31
+ self.current_file: str = ""
32
+ self.source_code: str = ""
33
+ self.content_lines: list[str] = []
34
+ self._node_text_cache: dict[int, str] = {}
35
+ # Go-specific metadata
36
+ self.goroutines: list[dict[str, Any]] = []
37
+ self.channels: list[dict[str, Any]] = []
38
+ self.defers: list[dict[str, Any]] = []
39
+
40
+ def extract_functions(
41
+ self, tree: "tree_sitter.Tree", source_code: str
42
+ ) -> list[Function]:
43
+ """Extract Go function and method declarations"""
44
+ self.source_code = source_code
45
+ self.content_lines = source_code.split("\n")
46
+ self._reset_caches()
47
+
48
+ functions: list[Function] = []
49
+
50
+ extractors = {
51
+ "function_declaration": self._extract_function,
52
+ "method_declaration": self._extract_method,
53
+ }
54
+
55
+ self._traverse_and_extract(tree.root_node, extractors, functions)
56
+
57
+ log_debug(f"Extracted {len(functions)} Go functions/methods")
58
+ return functions
59
+
60
+ def extract_classes(
61
+ self, tree: "tree_sitter.Tree", source_code: str
62
+ ) -> list[Class]:
63
+ """Extract Go struct and interface definitions"""
64
+ self.source_code = source_code
65
+ self.content_lines = source_code.split("\n")
66
+ self._reset_caches()
67
+
68
+ classes: list[Class] = []
69
+
70
+ # Extract type declarations (struct, interface, type alias)
71
+ self._traverse_for_types(tree.root_node, classes)
72
+
73
+ log_debug(f"Extracted {len(classes)} Go structs/interfaces")
74
+ return classes
75
+
76
+ def extract_variables(
77
+ self, tree: "tree_sitter.Tree", source_code: str
78
+ ) -> list[Variable]:
79
+ """Extract Go const and var declarations"""
80
+ self.source_code = source_code
81
+ self.content_lines = source_code.split("\n")
82
+ self._reset_caches()
83
+
84
+ variables: list[Variable] = []
85
+
86
+ extractors = {
87
+ "const_declaration": self._extract_const_declaration,
88
+ "var_declaration": self._extract_var_declaration,
89
+ }
90
+
91
+ self._traverse_and_extract(tree.root_node, extractors, variables)
92
+
93
+ log_debug(f"Extracted {len(variables)} Go const/var declarations")
94
+ return variables
95
+
96
+ def extract_imports(
97
+ self, tree: "tree_sitter.Tree", source_code: str
98
+ ) -> list[Import]:
99
+ """Extract Go import declarations"""
100
+ self.source_code = source_code
101
+ self.content_lines = source_code.split("\n")
102
+ self._reset_caches()
103
+
104
+ imports: list[Import] = []
105
+
106
+ extractors = {
107
+ "import_declaration": self._extract_import_declaration,
108
+ }
109
+
110
+ self._traverse_and_extract(tree.root_node, extractors, imports)
111
+
112
+ log_debug(f"Extracted {len(imports)} Go imports")
113
+ return imports
114
+
115
+ def extract_packages(
116
+ self, tree: "tree_sitter.Tree", source_code: str
117
+ ) -> list[Package]:
118
+ """Extract Go package declaration"""
119
+ self.source_code = source_code
120
+ self.content_lines = source_code.split("\n")
121
+ self._reset_caches()
122
+
123
+ packages: list[Package] = []
124
+
125
+ for child in tree.root_node.children:
126
+ if child.type == "package_clause":
127
+ pkg = self._extract_package(child)
128
+ if pkg:
129
+ packages.append(pkg)
130
+ self.current_package = pkg.name
131
+ break
132
+
133
+ log_debug(f"Extracted {len(packages)} Go packages")
134
+ return packages
135
+
136
+ def _reset_caches(self) -> None:
137
+ """Reset performance caches"""
138
+ self._node_text_cache.clear()
139
+ self.goroutines.clear()
140
+ self.channels.clear()
141
+ self.defers.clear()
142
+
143
+ def _traverse_and_extract(
144
+ self,
145
+ node: "tree_sitter.Node",
146
+ extractors: dict[str, Any],
147
+ results: list[Any],
148
+ ) -> None:
149
+ """Recursive traversal to find and extract elements"""
150
+ if node.type in extractors:
151
+ element = extractors[node.type](node)
152
+ if element:
153
+ if isinstance(element, list):
154
+ results.extend(element)
155
+ else:
156
+ results.append(element)
157
+
158
+ # Also detect goroutines, channels, defers
159
+ if node.type == "go_statement":
160
+ self._extract_goroutine(node)
161
+ elif node.type == "send_statement":
162
+ self._extract_channel_operation(node, "send")
163
+ elif node.type == "defer_statement":
164
+ self._extract_defer(node)
165
+
166
+ for child in node.children:
167
+ self._traverse_and_extract(child, extractors, results)
168
+
169
+ def _traverse_for_types(
170
+ self, node: "tree_sitter.Node", results: list[Class]
171
+ ) -> None:
172
+ """Traverse to find type declarations"""
173
+ if node.type == "type_declaration":
174
+ classes = self._extract_type_declaration(node)
175
+ if classes:
176
+ results.extend(classes)
177
+
178
+ for child in node.children:
179
+ self._traverse_for_types(child, results)
180
+
181
+ def _extract_package(self, node: "tree_sitter.Node") -> Package | None:
182
+ """Extract package declaration"""
183
+ try:
184
+ for child in node.children:
185
+ if child.type == "package_identifier":
186
+ name = self._get_node_text(child)
187
+ return Package(
188
+ name=name,
189
+ start_line=node.start_point[0] + 1,
190
+ end_line=node.end_point[0] + 1,
191
+ raw_text=self._get_node_text(node),
192
+ language="go",
193
+ )
194
+ return None
195
+ except Exception as e:
196
+ log_error(f"Error extracting Go package: {e}")
197
+ return None
198
+
199
+ def _extract_import_declaration(
200
+ self, node: "tree_sitter.Node"
201
+ ) -> list[Import] | None:
202
+ """Extract import declaration (may contain multiple imports)"""
203
+ imports: list[Import] = []
204
+ try:
205
+ # Find import_spec_list or single import_spec
206
+ for child in node.children:
207
+ if child.type == "import_spec_list":
208
+ for spec in child.children:
209
+ if spec.type == "import_spec":
210
+ imp = self._extract_import_spec(spec)
211
+ if imp:
212
+ imports.append(imp)
213
+ elif child.type == "import_spec":
214
+ imp = self._extract_import_spec(child)
215
+ if imp:
216
+ imports.append(imp)
217
+
218
+ return imports if imports else None
219
+ except Exception as e:
220
+ log_error(f"Error extracting Go import: {e}")
221
+ return None
222
+
223
+ def _extract_import_spec(self, node: "tree_sitter.Node") -> Import | None:
224
+ """Extract single import spec"""
225
+ try:
226
+ raw_text = self._get_node_text(node)
227
+ start_line = node.start_point[0] + 1
228
+ end_line = node.end_point[0] + 1
229
+
230
+ # Extract path and optional alias
231
+ alias = None
232
+ path = None
233
+
234
+ for child in node.children:
235
+ if child.type == "package_identifier":
236
+ alias = self._get_node_text(child)
237
+ elif child.type == "blank_identifier":
238
+ alias = "_"
239
+ elif child.type == "dot":
240
+ alias = "."
241
+ elif child.type == "interpreted_string_literal":
242
+ path = self._get_node_text(child).strip('"')
243
+
244
+ if path:
245
+ # Extract package name from path
246
+ name = path.split("/")[-1] if "/" in path else path
247
+ return Import(
248
+ name=name,
249
+ start_line=start_line,
250
+ end_line=end_line,
251
+ raw_text=raw_text,
252
+ language="go",
253
+ module_name=path,
254
+ import_statement=raw_text,
255
+ alias=alias,
256
+ )
257
+ return None
258
+ except Exception as e:
259
+ log_error(f"Error extracting Go import spec: {e}")
260
+ return None
261
+
262
+ def _extract_function(self, node: "tree_sitter.Node") -> Function | None:
263
+ """Extract function declaration"""
264
+ try:
265
+ name_node = node.child_by_field_name("name")
266
+ if not name_node:
267
+ return None
268
+
269
+ name = self._get_node_text(name_node)
270
+ if not name:
271
+ return None
272
+
273
+ start_line = node.start_point[0] + 1
274
+ end_line = node.end_point[0] + 1
275
+
276
+ # Parameters
277
+ parameters = self._extract_parameters(node)
278
+
279
+ # Return type
280
+ return_type = self._extract_return_type(node)
281
+
282
+ # Visibility (exported if starts with uppercase)
283
+ visibility = "public" if name[0].isupper() else "private"
284
+
285
+ # Docstring
286
+ docstring = self._extract_docstring(node)
287
+
288
+ raw_text = self._get_node_text(node)
289
+
290
+ return Function(
291
+ name=name,
292
+ start_line=start_line,
293
+ end_line=end_line,
294
+ raw_text=raw_text,
295
+ language="go",
296
+ parameters=parameters,
297
+ return_type=return_type,
298
+ visibility=visibility,
299
+ docstring=docstring,
300
+ is_public=visibility == "public",
301
+ )
302
+ except Exception as e:
303
+ log_error(f"Error extracting Go function: {e}")
304
+ return None
305
+
306
+ def _extract_method(self, node: "tree_sitter.Node") -> Function | None:
307
+ """Extract method declaration (function with receiver)"""
308
+ try:
309
+ name_node = node.child_by_field_name("name")
310
+ if not name_node:
311
+ return None
312
+
313
+ name = self._get_node_text(name_node)
314
+ if not name:
315
+ return None
316
+
317
+ start_line = node.start_point[0] + 1
318
+ end_line = node.end_point[0] + 1
319
+
320
+ # Receiver
321
+ receiver = None
322
+ receiver_type = None
323
+ receiver_node = node.child_by_field_name("receiver")
324
+ if receiver_node:
325
+ receiver_text = self._get_node_text(receiver_node)
326
+ # Parse receiver: (r *ReceiverType) or (r ReceiverType)
327
+ match = re.search(r"\(\s*(\w+)\s+(\*?\w+)\s*\)", receiver_text)
328
+ if match:
329
+ receiver = match.group(1)
330
+ receiver_type = match.group(2)
331
+
332
+ # Parameters
333
+ parameters = self._extract_parameters(node)
334
+
335
+ # Return type
336
+ return_type = self._extract_return_type(node)
337
+
338
+ # Visibility
339
+ visibility = "public" if name[0].isupper() else "private"
340
+
341
+ # Docstring
342
+ docstring = self._extract_docstring(node)
343
+
344
+ raw_text = self._get_node_text(node)
345
+
346
+ func = Function(
347
+ name=name,
348
+ start_line=start_line,
349
+ end_line=end_line,
350
+ raw_text=raw_text,
351
+ language="go",
352
+ parameters=parameters,
353
+ return_type=return_type,
354
+ visibility=visibility,
355
+ docstring=docstring,
356
+ is_public=visibility == "public",
357
+ )
358
+ # Attach Go-specific method attributes
359
+ func.receiver = receiver
360
+ func.receiver_type = receiver_type
361
+ func.is_method = True
362
+
363
+ return func
364
+ except Exception as e:
365
+ log_error(f"Error extracting Go method: {e}")
366
+ return None
367
+
368
+ def _extract_parameters(self, node: "tree_sitter.Node") -> list[str]:
369
+ """Extract function/method parameters"""
370
+ parameters = []
371
+ params_node = node.child_by_field_name("parameters")
372
+ if params_node:
373
+ for child in params_node.children:
374
+ if child.type == "parameter_declaration":
375
+ param_text = self._get_node_text(child)
376
+ parameters.append(param_text)
377
+ return parameters
378
+
379
+ def _extract_return_type(self, node: "tree_sitter.Node") -> str:
380
+ """Extract function/method return type"""
381
+ result_node = node.child_by_field_name("result")
382
+ if result_node:
383
+ return self._get_node_text(result_node)
384
+ return ""
385
+
386
+ def _extract_type_declaration(self, node: "tree_sitter.Node") -> list[Class]:
387
+ """Extract type declaration (struct, interface, type alias)"""
388
+ classes: list[Class] = []
389
+ try:
390
+ for child in node.children:
391
+ if child.type == "type_spec":
392
+ cls = self._extract_type_spec(child)
393
+ if cls:
394
+ classes.append(cls)
395
+ except Exception as e:
396
+ log_error(f"Error extracting Go type declaration: {e}")
397
+ return classes
398
+
399
+ def _extract_type_spec(self, node: "tree_sitter.Node") -> Class | None:
400
+ """Extract single type spec"""
401
+ try:
402
+ name_node = node.child_by_field_name("name")
403
+ type_node = node.child_by_field_name("type")
404
+
405
+ if not name_node:
406
+ return None
407
+
408
+ name = self._get_node_text(name_node)
409
+ if not name:
410
+ return None
411
+
412
+ start_line = node.start_point[0] + 1
413
+ end_line = node.end_point[0] + 1
414
+
415
+ # Determine type kind
416
+ class_type = "type"
417
+ if type_node:
418
+ if type_node.type == "struct_type":
419
+ class_type = "struct"
420
+ elif type_node.type == "interface_type":
421
+ class_type = "interface"
422
+ else:
423
+ class_type = "type_alias"
424
+
425
+ # Visibility
426
+ visibility = "public" if name[0].isupper() else "private"
427
+
428
+ # Docstring
429
+ docstring = self._extract_docstring(node)
430
+
431
+ raw_text = self._get_node_text(node)
432
+
433
+ # For struct, extract embedded types (interfaces)
434
+ interfaces: list[str] = []
435
+ if type_node and type_node.type == "struct_type":
436
+ interfaces = self._extract_embedded_types(type_node)
437
+
438
+ return Class(
439
+ name=name,
440
+ start_line=start_line,
441
+ end_line=end_line,
442
+ raw_text=raw_text,
443
+ language="go",
444
+ class_type=class_type,
445
+ visibility=visibility,
446
+ docstring=docstring,
447
+ interfaces=interfaces,
448
+ )
449
+ except Exception as e:
450
+ log_error(f"Error extracting Go type spec: {e}")
451
+ return None
452
+
453
+ def _extract_embedded_types(self, struct_node: "tree_sitter.Node") -> list[str]:
454
+ """Extract embedded types from struct"""
455
+ embedded: list[str] = []
456
+ for child in struct_node.children:
457
+ if child.type == "field_declaration_list":
458
+ for field in child.children:
459
+ if field.type == "field_declaration":
460
+ # Check if it's an embedded field (no name, just type)
461
+ has_name = False
462
+ type_text = None
463
+ for fc in field.children:
464
+ if fc.type == "field_identifier":
465
+ has_name = True
466
+ elif fc.type in ["type_identifier", "qualified_type"]:
467
+ type_text = self._get_node_text(fc)
468
+ if not has_name and type_text:
469
+ embedded.append(type_text)
470
+ return embedded
471
+
472
+ def _extract_const_declaration(
473
+ self, node: "tree_sitter.Node"
474
+ ) -> list[Variable] | None:
475
+ """Extract const declaration"""
476
+ return self._extract_var_or_const(node, is_const=True)
477
+
478
+ def _extract_var_declaration(
479
+ self, node: "tree_sitter.Node"
480
+ ) -> list[Variable] | None:
481
+ """Extract var declaration"""
482
+ return self._extract_var_or_const(node, is_const=False)
483
+
484
+ def _extract_var_or_const(
485
+ self, node: "tree_sitter.Node", is_const: bool
486
+ ) -> list[Variable] | None:
487
+ """Extract var or const declaration"""
488
+ variables: list[Variable] = []
489
+ try:
490
+ for child in node.children:
491
+ if child.type in ["const_spec", "var_spec"]:
492
+ vars_from_spec = self._extract_var_spec(child, is_const)
493
+ if vars_from_spec:
494
+ variables.extend(vars_from_spec)
495
+ except Exception as e:
496
+ log_error(f"Error extracting Go {'const' if is_const else 'var'}: {e}")
497
+ return variables if variables else None
498
+
499
+ def _extract_var_spec(
500
+ self, node: "tree_sitter.Node", is_const: bool
501
+ ) -> list[Variable]:
502
+ """Extract single var/const spec"""
503
+ variables: list[Variable] = []
504
+ try:
505
+ start_line = node.start_point[0] + 1
506
+ end_line = node.end_point[0] + 1
507
+ raw_text = self._get_node_text(node)
508
+
509
+ # Extract names and type
510
+ names: list[str] = []
511
+ var_type = ""
512
+
513
+ for child in node.children:
514
+ if child.type == "identifier":
515
+ names.append(self._get_node_text(child))
516
+ elif child.type in [
517
+ "type_identifier",
518
+ "pointer_type",
519
+ "array_type",
520
+ "slice_type",
521
+ "map_type",
522
+ "channel_type",
523
+ "qualified_type",
524
+ ]:
525
+ var_type = self._get_node_text(child)
526
+
527
+ for name in names:
528
+ visibility = "public" if name[0].isupper() else "private"
529
+ variables.append(
530
+ Variable(
531
+ name=name,
532
+ start_line=start_line,
533
+ end_line=end_line,
534
+ raw_text=raw_text,
535
+ language="go",
536
+ variable_type=var_type,
537
+ visibility=visibility,
538
+ is_constant=is_const,
539
+ )
540
+ )
541
+ except Exception as e:
542
+ log_error(f"Error extracting Go var spec: {e}")
543
+ return variables
544
+
545
+ def _extract_goroutine(self, node: "tree_sitter.Node") -> None:
546
+ """Extract goroutine invocation"""
547
+ try:
548
+ self.goroutines.append(
549
+ {
550
+ "line": node.start_point[0] + 1,
551
+ "text": self._get_node_text(node),
552
+ }
553
+ )
554
+ except Exception as e:
555
+ log_error(f"Error extracting goroutine: {e}")
556
+
557
+ def _extract_channel_operation(
558
+ self, node: "tree_sitter.Node", op_type: str
559
+ ) -> None:
560
+ """Extract channel operation"""
561
+ try:
562
+ self.channels.append(
563
+ {
564
+ "type": op_type,
565
+ "line": node.start_point[0] + 1,
566
+ "text": self._get_node_text(node),
567
+ }
568
+ )
569
+ except Exception as e:
570
+ log_error(f"Error extracting channel operation: {e}")
571
+
572
+ def _extract_defer(self, node: "tree_sitter.Node") -> None:
573
+ """Extract defer statement"""
574
+ try:
575
+ self.defers.append(
576
+ {
577
+ "line": node.start_point[0] + 1,
578
+ "text": self._get_node_text(node),
579
+ }
580
+ )
581
+ except Exception as e:
582
+ log_error(f"Error extracting defer: {e}")
583
+
584
+ def _extract_docstring(self, node: "tree_sitter.Node") -> str | None:
585
+ """Extract doc comments preceding the node"""
586
+ # In Go, doc comments are // comments immediately before the declaration
587
+ start_line = node.start_point[0]
588
+ if start_line == 0:
589
+ return None
590
+
591
+ docs: list[str] = []
592
+ line_idx = start_line - 1
593
+
594
+ # Ensure line_idx is within valid range
595
+ if line_idx >= len(self.content_lines):
596
+ line_idx = len(self.content_lines) - 1
597
+
598
+ while line_idx >= 0:
599
+ line = self.content_lines[line_idx].strip()
600
+ if line.startswith("//"):
601
+ docs.insert(0, line[2:].strip())
602
+ line_idx -= 1
603
+ elif line == "":
604
+ line_idx -= 1
605
+ else:
606
+ break
607
+
608
+ return "\n".join(docs) if docs else None
609
+
610
+ def _get_node_text(self, node: "tree_sitter.Node") -> str:
611
+ """Get node text with caching"""
612
+ node_id = id(node)
613
+ if node_id in self._node_text_cache:
614
+ return self._node_text_cache[node_id]
615
+
616
+ try:
617
+ start_byte = node.start_byte
618
+ end_byte = node.end_byte
619
+ encoding = "utf-8"
620
+ content_bytes = safe_encode("\n".join(self.content_lines), encoding)
621
+ text = extract_text_slice(content_bytes, start_byte, end_byte, encoding)
622
+ self._node_text_cache[node_id] = text
623
+ return text
624
+ except Exception:
625
+ return ""
626
+
627
+
628
+ class GoPlugin(LanguagePlugin):
629
+ """Go language plugin implementation"""
630
+
631
+ def __init__(self) -> None:
632
+ """Initialize the Go language plugin."""
633
+ super().__init__()
634
+ self.extractor = GoElementExtractor()
635
+ self.language = "go"
636
+ self.supported_extensions = self.get_file_extensions()
637
+ self._cached_language: Any | None = None
638
+
639
+ def get_language_name(self) -> str:
640
+ """Get the language name."""
641
+ return "go"
642
+
643
+ def get_file_extensions(self) -> list[str]:
644
+ """Get supported file extensions."""
645
+ return [".go"]
646
+
647
+ def create_extractor(self) -> ElementExtractor:
648
+ """Create a new element extractor instance."""
649
+ return GoElementExtractor()
650
+
651
+ def get_supported_element_types(self) -> list[str]:
652
+ """Get supported element types for Go."""
653
+ return [
654
+ "package",
655
+ "import",
656
+ "function",
657
+ "method",
658
+ "struct",
659
+ "interface",
660
+ "type_alias",
661
+ "const",
662
+ "var",
663
+ "goroutine",
664
+ "channel",
665
+ ]
666
+
667
+ def get_queries(self) -> dict[str, str]:
668
+ """Get Go-specific tree-sitter queries."""
669
+ from ..queries.go import GO_QUERIES
670
+
671
+ return GO_QUERIES
672
+
673
+ async def analyze_file(
674
+ self, file_path: str, request: "AnalysisRequest"
675
+ ) -> "AnalysisResult":
676
+ """Analyze Go code and return structured results."""
677
+ from ..models import AnalysisResult
678
+
679
+ try:
680
+ from ..encoding_utils import read_file_safe
681
+
682
+ file_content, detected_encoding = read_file_safe(file_path)
683
+
684
+ # Get tree-sitter language and parse
685
+ language = self.get_tree_sitter_language()
686
+ if language is None:
687
+ return AnalysisResult(
688
+ file_path=file_path,
689
+ language="go",
690
+ line_count=len(file_content.split("\n")),
691
+ elements=[],
692
+ source_code=file_content,
693
+ )
694
+
695
+ import tree_sitter
696
+
697
+ parser = tree_sitter.Parser()
698
+
699
+ # Set language (handle different tree-sitter versions)
700
+ if hasattr(parser, "set_language"):
701
+ parser.set_language(language)
702
+ elif hasattr(parser, "language"):
703
+ parser.language = language
704
+ else:
705
+ parser = tree_sitter.Parser(language)
706
+
707
+ tree = parser.parse(file_content.encode("utf-8"))
708
+
709
+ # Extract elements
710
+ elements_dict = self.extract_elements(tree, file_content)
711
+
712
+ all_elements = []
713
+ all_elements.extend(elements_dict.get("packages", []))
714
+ all_elements.extend(elements_dict.get("imports", []))
715
+ all_elements.extend(elements_dict.get("functions", []))
716
+ all_elements.extend(elements_dict.get("classes", []))
717
+ all_elements.extend(elements_dict.get("variables", []))
718
+
719
+ # Count nodes
720
+ node_count = (
721
+ self._count_tree_nodes(tree.root_node) if tree and tree.root_node else 0
722
+ )
723
+
724
+ result = AnalysisResult(
725
+ file_path=file_path,
726
+ language="go",
727
+ line_count=len(file_content.split("\n")),
728
+ elements=all_elements,
729
+ node_count=node_count,
730
+ source_code=file_content,
731
+ )
732
+
733
+ # Attach Go-specific metadata
734
+ result.goroutines = self.extractor.goroutines
735
+ result.channels = self.extractor.channels
736
+ result.defers = self.extractor.defers
737
+
738
+ return result
739
+
740
+ except Exception as e:
741
+ log_error(f"Error analyzing Go file {file_path}: {e}")
742
+ return AnalysisResult(
743
+ file_path=file_path,
744
+ language="go",
745
+ line_count=0,
746
+ elements=[],
747
+ source_code="",
748
+ error_message=str(e),
749
+ success=False,
750
+ )
751
+
752
+ def _count_tree_nodes(self, node: Any) -> int:
753
+ """Recursively count nodes."""
754
+ if node is None:
755
+ return 0
756
+ count = 1
757
+ if hasattr(node, "children"):
758
+ for child in node.children:
759
+ count += self._count_tree_nodes(child)
760
+ return count
761
+
762
+ def get_tree_sitter_language(self) -> Any | None:
763
+ """Get the tree-sitter language for Go."""
764
+ if self._cached_language is not None:
765
+ return self._cached_language
766
+
767
+ try:
768
+ import tree_sitter
769
+ import tree_sitter_go
770
+
771
+ caps_or_lang = tree_sitter_go.language()
772
+
773
+ if hasattr(caps_or_lang, "__class__") and "Language" in str(
774
+ type(caps_or_lang)
775
+ ):
776
+ self._cached_language = caps_or_lang
777
+ else:
778
+ try:
779
+ self._cached_language = tree_sitter.Language(caps_or_lang)
780
+ except Exception as e:
781
+ log_error(f"Failed to create Language object: {e}")
782
+ return None
783
+
784
+ return self._cached_language
785
+ except ImportError as e:
786
+ log_error(f"tree-sitter-go not available: {e}")
787
+ return None
788
+ except Exception as e:
789
+ log_error(f"Failed to load tree-sitter language for Go: {e}")
790
+ return None
791
+
792
+ def extract_elements(self, tree: Any | None, source_code: str) -> dict[str, Any]:
793
+ """Extract all elements from Go source code."""
794
+ if tree is None:
795
+ return {
796
+ "packages": [],
797
+ "imports": [],
798
+ "functions": [],
799
+ "classes": [],
800
+ "variables": [],
801
+ }
802
+
803
+ try:
804
+ extractor = self.create_extractor()
805
+
806
+ result = {
807
+ "packages": extractor.extract_packages(tree, source_code),
808
+ "imports": extractor.extract_imports(tree, source_code),
809
+ "functions": extractor.extract_functions(tree, source_code),
810
+ "classes": extractor.extract_classes(tree, source_code),
811
+ "variables": extractor.extract_variables(tree, source_code),
812
+ }
813
+
814
+ # Capture Go-specific metadata
815
+ if isinstance(extractor, GoElementExtractor):
816
+ self.extractor.goroutines = extractor.goroutines
817
+ self.extractor.channels = extractor.channels
818
+ self.extractor.defers = extractor.defers
819
+
820
+ return result
821
+
822
+ except Exception as e:
823
+ log_error(f"Error extracting Go elements: {e}")
824
+ return {
825
+ "packages": [],
826
+ "imports": [],
827
+ "functions": [],
828
+ "classes": [],
829
+ "variables": [],
830
+ }
831
+
832
+ def supports_file(self, file_path: str) -> bool:
833
+ """Check if this plugin supports the given file."""
834
+ return any(
835
+ file_path.lower().endswith(ext) for ext in self.get_file_extensions()
836
+ )