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,656 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Kotlin Language Plugin
4
+
5
+ Provides Kotlin-specific parsing and element extraction functionality.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ if TYPE_CHECKING:
11
+ import tree_sitter
12
+
13
+ from ..core.analysis_engine import AnalysisRequest
14
+ from ..models import AnalysisResult
15
+
16
+ from ..encoding_utils import extract_text_slice, safe_encode
17
+ from ..models import Class, Function, Import, Package, Variable
18
+ from ..plugins.base import ElementExtractor, LanguagePlugin
19
+ from ..utils import log_debug, log_error
20
+
21
+
22
+ class KotlinElementExtractor(ElementExtractor):
23
+ """Kotlin-specific element extractor"""
24
+
25
+ def __init__(self) -> None:
26
+ """Initialize the Kotlin element extractor."""
27
+ self.current_package: str = ""
28
+ self.current_file: str = ""
29
+ self.source_code: str = ""
30
+ self.content_lines: list[str] = []
31
+ self._node_text_cache: dict[int, str] = {}
32
+
33
+ def extract_functions(
34
+ self, tree: "tree_sitter.Tree", source_code: str
35
+ ) -> list[Function]:
36
+ """Extract Kotlin function declarations"""
37
+ self.source_code = source_code
38
+ self.content_lines = source_code.split("\n")
39
+ self._reset_caches()
40
+
41
+ functions: list[Function] = []
42
+
43
+ self._traverse_and_extract(
44
+ tree.root_node,
45
+ {"function_declaration": self._extract_function},
46
+ functions,
47
+ )
48
+
49
+ log_debug(f"Extracted {len(functions)} Kotlin functions")
50
+ return functions
51
+
52
+ def extract_classes(
53
+ self, tree: "tree_sitter.Tree", source_code: str
54
+ ) -> list[Class]:
55
+ """Extract Kotlin class declarations"""
56
+ self.source_code = source_code
57
+ self.content_lines = source_code.split("\n")
58
+ self._reset_caches()
59
+
60
+ # Extract package
61
+ self._extract_package(tree.root_node)
62
+
63
+ classes: list[Class] = []
64
+
65
+ extractors = {
66
+ "class_declaration": self._extract_class,
67
+ "object_declaration": self._extract_object,
68
+ }
69
+
70
+ self._traverse_and_extract(
71
+ tree.root_node,
72
+ extractors,
73
+ classes,
74
+ )
75
+
76
+ log_debug(f"Extracted {len(classes)} Kotlin classes")
77
+ return classes
78
+
79
+ def extract_variables(
80
+ self, tree: "tree_sitter.Tree", source_code: str
81
+ ) -> list[Variable]:
82
+ """Extract Kotlin properties"""
83
+ self.source_code = source_code
84
+ self.content_lines = source_code.split("\n")
85
+ self._reset_caches()
86
+
87
+ variables: list[Variable] = []
88
+
89
+ extractors = {
90
+ "property_declaration": self._extract_property,
91
+ }
92
+
93
+ self._traverse_and_extract(
94
+ tree.root_node,
95
+ extractors,
96
+ variables,
97
+ )
98
+
99
+ log_debug(f"Extracted {len(variables)} Kotlin properties")
100
+ return variables
101
+
102
+ def extract_imports(
103
+ self, tree: "tree_sitter.Tree", source_code: str
104
+ ) -> list[Import]:
105
+ """Extract Kotlin imports"""
106
+ self.source_code = source_code
107
+ self.content_lines = source_code.split("\n")
108
+ self._reset_caches()
109
+
110
+ imports: list[Import] = []
111
+
112
+ extractors = {
113
+ "import_header": self._extract_import,
114
+ }
115
+
116
+ self._traverse_and_extract(
117
+ tree.root_node,
118
+ extractors,
119
+ imports,
120
+ )
121
+
122
+ log_debug(f"Extracted {len(imports)} Kotlin imports")
123
+ return imports
124
+
125
+ def extract_packages(
126
+ self, tree: "tree_sitter.Tree", source_code: str
127
+ ) -> list[Package]:
128
+ """Extract Kotlin package"""
129
+ self.source_code = source_code
130
+ self.content_lines = source_code.split("\n")
131
+ self._reset_caches()
132
+
133
+ packages: list[Package] = []
134
+ self._extract_package(tree.root_node)
135
+ if self.current_package:
136
+ # Find package node if needed for lines, or just create from string
137
+ # We'll try to find the package_header node
138
+ for child in tree.root_node.children:
139
+ if child.type == "package_header":
140
+ pkg = Package(
141
+ name=self.current_package,
142
+ start_line=child.start_point[0] + 1,
143
+ end_line=child.end_point[0] + 1,
144
+ raw_text=self._get_node_text(child),
145
+ language="kotlin",
146
+ )
147
+ packages.append(pkg)
148
+ break
149
+
150
+ return packages
151
+
152
+ def _reset_caches(self) -> None:
153
+ """Reset performance caches"""
154
+ self._node_text_cache.clear()
155
+ # Keep current_package if already extracted?
156
+ # Usually safe to re-extract or clear.
157
+ if not self.source_code:
158
+ self.current_package = ""
159
+
160
+ def _traverse_and_extract(
161
+ self,
162
+ node: "tree_sitter.Node",
163
+ extractors: dict[str, Any],
164
+ results: list[Any],
165
+ ) -> None:
166
+ """Recursive traversal to find and extract elements"""
167
+ if node.type in extractors:
168
+ element = extractors[node.type](node)
169
+ if element:
170
+ results.append(element)
171
+
172
+ for child in node.children:
173
+ self._traverse_and_extract(child, extractors, results)
174
+
175
+ def _extract_package(self, node: "tree_sitter.Node") -> None:
176
+ """Extract package declaration"""
177
+ # Find package_header at top level usually
178
+ for child in node.children:
179
+ if child.type == "package_header":
180
+ # Check children for identifier
181
+ # package_header -> (package) (identifier)
182
+ for grandchild in child.children:
183
+ if (
184
+ grandchild.type == "identifier"
185
+ or grandchild.type == "simple_identifier"
186
+ ):
187
+ self.current_package = self._get_node_text(grandchild)
188
+ return
189
+ # Or maybe deeper if qualified name
190
+ if "identifier" in grandchild.type:
191
+ self.current_package = self._get_node_text(grandchild)
192
+ return
193
+
194
+ def _extract_function(self, node: "tree_sitter.Node") -> Function | None:
195
+ """Extract function information"""
196
+ try:
197
+ # name: simple_identifier
198
+ name = "anonymous"
199
+ # Try getting by field name first
200
+ name_node = node.child_by_field_name("name")
201
+ if name_node:
202
+ name = self._get_node_text(name_node)
203
+ else:
204
+ # Fallback to simple_identifier search
205
+ for child in node.children:
206
+ if child.type == "simple_identifier":
207
+ name = self._get_node_text(child)
208
+ break
209
+
210
+ start_line = node.start_point[0] + 1
211
+ end_line = node.end_point[0] + 1
212
+
213
+ # Parameters
214
+ parameters = []
215
+ params_node = node.child_by_field_name(
216
+ "parameters"
217
+ ) # function_value_parameters
218
+ if params_node:
219
+ for child in params_node.children:
220
+ if child.type == "parameter":
221
+ # parameter -> simple_identifier: type
222
+ param_name = ""
223
+ param_type = ""
224
+ for grandchild in child.children:
225
+ if grandchild.type == "simple_identifier":
226
+ param_name = self._get_node_text(grandchild)
227
+ elif (
228
+ "type" in grandchild.type
229
+ or grandchild.type == "user_type"
230
+ ):
231
+ param_type = self._get_node_text(grandchild)
232
+
233
+ if param_name:
234
+ parameters.append(
235
+ {"name": param_name, "type": param_type or "Any"}
236
+ )
237
+
238
+ # Return type
239
+ return_type = "Unit"
240
+ # search for return type, usually after :
241
+ # function_declaration -> ... (type)? ...
242
+ # Hard to find specific field without query, iterating children
243
+ # If we find a colon, next child might be type?
244
+ # Tree-sitter-kotlin structure: function_declaration can have children: modifiers, fun, simple_identifier, function_value_parameters, type (return type), function_body
245
+
246
+ for i, child in enumerate(node.children):
247
+ if child.type == ":":
248
+ # Next sibling should be return type
249
+ if i + 1 < len(node.children):
250
+ return_type = self._get_node_text(node.children[i + 1])
251
+ break
252
+
253
+ # Visibility and modifiers
254
+ visibility = "public"
255
+ is_suspend = False
256
+ modifiers_node = node.child_by_field_name("modifiers")
257
+ if modifiers_node:
258
+ mods = self._get_node_text(modifiers_node)
259
+ if "private" in mods:
260
+ visibility = "private"
261
+ elif "protected" in mods:
262
+ visibility = "protected"
263
+ elif "internal" in mods:
264
+ visibility = "internal"
265
+
266
+ if "suspend" in mods:
267
+ is_suspend = True
268
+
269
+ # Docstring
270
+ docstring = self._extract_docstring(node)
271
+
272
+ raw_text = self._get_node_text(node)
273
+
274
+ func = Function(
275
+ name=name,
276
+ start_line=start_line,
277
+ end_line=end_line,
278
+ raw_text=raw_text,
279
+ language="kotlin",
280
+ parameters=parameters,
281
+ return_type=return_type,
282
+ visibility=visibility,
283
+ docstring=docstring,
284
+ )
285
+ func.is_suspend = is_suspend
286
+ return func
287
+
288
+ except Exception as e:
289
+ log_error(f"Error extracting Kotlin function: {e}")
290
+ return None
291
+
292
+ def _extract_class(self, node: "tree_sitter.Node") -> Class | None:
293
+ """Extract class declaration"""
294
+ return self._extract_class_or_object(node, "class")
295
+
296
+ def _extract_object(self, node: "tree_sitter.Node") -> Class | None:
297
+ """Extract object declaration"""
298
+ return self._extract_class_or_object(node, "object")
299
+
300
+ def _extract_class_or_object(
301
+ self, node: "tree_sitter.Node", kind: str
302
+ ) -> Class | None:
303
+ """Generic extraction for class/object/interface"""
304
+ try:
305
+ name = "anonymous"
306
+ # Try getting by field name first
307
+ name_node = node.child_by_field_name("name")
308
+ if name_node:
309
+ name = self._get_node_text(name_node)
310
+ else:
311
+ for child in node.children:
312
+ if child.type == "simple_identifier":
313
+ name = self._get_node_text(child)
314
+ break
315
+
316
+ start_line = node.start_point[0] + 1
317
+ end_line = node.end_point[0] + 1
318
+
319
+ visibility = "public"
320
+ modifiers_node = node.child_by_field_name("modifiers")
321
+ if modifiers_node:
322
+ mods = self._get_node_text(modifiers_node)
323
+ if "private" in mods:
324
+ visibility = "private"
325
+ elif "protected" in mods:
326
+ visibility = "protected"
327
+ elif "internal" in mods:
328
+ visibility = "internal"
329
+
330
+ # Detect interface by checking for 'interface' keyword child node
331
+ # tree-sitter-kotlin parses both class and interface as class_declaration
332
+ # but includes 'interface' or 'class' keyword as a child node
333
+ if kind == "class":
334
+ for child in node.children:
335
+ if child.type == "interface":
336
+ kind = "interface"
337
+ break
338
+ elif child.type == "class":
339
+ # Explicitly a class, not interface
340
+ break
341
+
342
+ raw_text = self._get_node_text(node)
343
+
344
+ return Class(
345
+ name=name,
346
+ start_line=start_line,
347
+ end_line=end_line,
348
+ raw_text=raw_text,
349
+ language="kotlin",
350
+ class_type=kind,
351
+ visibility=visibility,
352
+ package_name=self.current_package,
353
+ )
354
+
355
+ except Exception as e:
356
+ log_error(f"Error extracting Kotlin class: {e}")
357
+ return None
358
+
359
+ def _extract_property(self, node: "tree_sitter.Node") -> Variable | None:
360
+ """Extract property declaration"""
361
+ try:
362
+ # var declaration or val declaration
363
+ is_val = False
364
+ is_var = False
365
+ text = self._get_node_text(node)
366
+ if text.startswith("val "):
367
+ is_val = True
368
+ elif text.startswith("var "):
369
+ is_var = True
370
+
371
+ # variable_declaration -> (modifiers)? (val/var) ...
372
+ # Need to find name
373
+ name = "unknown"
374
+
375
+ # Try getting by field name 'name' directly on property_declaration (might work in newer grammars)
376
+ name_node = node.child_by_field_name("name")
377
+ if name_node:
378
+ name = self._get_node_text(name_node)
379
+ else:
380
+ # Fallback: Iterate children
381
+ for child in node.children:
382
+ if child.type == "variable_declaration":
383
+ for grandchild in child.children:
384
+ if grandchild.type == "simple_identifier":
385
+ name = self._get_node_text(grandchild)
386
+ break
387
+ elif child.type == "simple_identifier":
388
+ name = self._get_node_text(child)
389
+ break
390
+
391
+ start_line = node.start_point[0] + 1
392
+ end_line = node.end_point[0] + 1
393
+
394
+ # Type?
395
+ prop_type = "Inferred"
396
+ # Look for : type
397
+
398
+ visibility = "public"
399
+ modifiers_node = node.child_by_field_name("modifiers")
400
+ if modifiers_node:
401
+ mods = self._get_node_text(modifiers_node)
402
+ if "private" in mods:
403
+ visibility = "private"
404
+
405
+ docstring = self._extract_docstring(node)
406
+ raw_text = self._get_node_text(node)
407
+
408
+ var = Variable(
409
+ name=name,
410
+ start_line=start_line,
411
+ end_line=end_line,
412
+ raw_text=raw_text,
413
+ language="kotlin",
414
+ variable_type=prop_type,
415
+ visibility=visibility,
416
+ docstring=docstring,
417
+ )
418
+ var.is_val = is_val
419
+ var.is_var = is_var
420
+
421
+ return var
422
+
423
+ except Exception as e:
424
+ log_error(f"Error extracting Kotlin property: {e}")
425
+ return None
426
+
427
+ def _extract_import(self, node: "tree_sitter.Node") -> Import | None:
428
+ """Extract import header"""
429
+ try:
430
+ # import_header -> 'import' identifier .*
431
+ raw_text = self._get_node_text(node)
432
+ start_line = node.start_point[0] + 1
433
+ end_line = node.end_point[0] + 1
434
+
435
+ # Parse name
436
+ parts = raw_text.split()
437
+ if len(parts) > 1:
438
+ name = parts[1]
439
+ else:
440
+ name = "unknown"
441
+
442
+ return Import(
443
+ name=name,
444
+ start_line=start_line,
445
+ end_line=end_line,
446
+ raw_text=raw_text,
447
+ language="kotlin",
448
+ import_statement=raw_text,
449
+ )
450
+ except Exception as e:
451
+ log_error(f"Error extracting Kotlin import: {e}")
452
+ return None
453
+
454
+ def _get_node_text(self, node: "tree_sitter.Node") -> str:
455
+ """Get node text with caching"""
456
+ node_id = id(node)
457
+ if node_id in self._node_text_cache:
458
+ return self._node_text_cache[node_id]
459
+
460
+ try:
461
+ start_byte = node.start_byte
462
+ end_byte = node.end_byte
463
+ encoding = "utf-8"
464
+ content_bytes = safe_encode("\n".join(self.content_lines), encoding)
465
+ text = extract_text_slice(content_bytes, start_byte, end_byte, encoding)
466
+ self._node_text_cache[node_id] = text
467
+ return text
468
+ except Exception:
469
+ return ""
470
+
471
+ def _extract_docstring(self, node: "tree_sitter.Node") -> str | None:
472
+ """Extract KDoc"""
473
+ # Similar to Rust/Java logic
474
+ return None
475
+
476
+
477
+ class KotlinPlugin(LanguagePlugin):
478
+ """Kotlin language plugin implementation"""
479
+
480
+ def __init__(self) -> None:
481
+ """Initialize the Kotlin language plugin."""
482
+ super().__init__()
483
+ self.extractor = KotlinElementExtractor()
484
+ self.language = "kotlin"
485
+ self.supported_extensions = self.get_file_extensions()
486
+ self._cached_language: Any | None = None
487
+
488
+ def get_language_name(self) -> str:
489
+ """Get the language name."""
490
+ return "kotlin"
491
+
492
+ def get_file_extensions(self) -> list[str]:
493
+ """Get supported file extensions."""
494
+ return [".kt", ".kts"]
495
+
496
+ def create_extractor(self) -> ElementExtractor:
497
+ """Create a new element extractor instance."""
498
+ return KotlinElementExtractor()
499
+
500
+ async def analyze_file(
501
+ self, file_path: str, request: "AnalysisRequest"
502
+ ) -> "AnalysisResult":
503
+ """Analyze Kotlin code and return structured results."""
504
+
505
+ from ..models import AnalysisResult
506
+
507
+ try:
508
+ from ..encoding_utils import read_file_safe
509
+
510
+ file_content, detected_encoding = read_file_safe(file_path)
511
+
512
+ # Get tree-sitter language and parse
513
+ language = self.get_tree_sitter_language()
514
+ if language is None:
515
+ return AnalysisResult(
516
+ file_path=file_path,
517
+ language="kotlin",
518
+ line_count=len(file_content.split("\n")),
519
+ elements=[],
520
+ source_code=file_content,
521
+ )
522
+
523
+ import tree_sitter
524
+
525
+ parser = tree_sitter.Parser()
526
+
527
+ # Set language
528
+ if hasattr(parser, "set_language"):
529
+ parser.set_language(language)
530
+ elif hasattr(parser, "language"):
531
+ parser.language = language
532
+ else:
533
+ parser = tree_sitter.Parser(language)
534
+
535
+ tree = parser.parse(file_content.encode("utf-8"))
536
+
537
+ # Extract elements
538
+ elements_dict = self.extract_elements(tree, file_content)
539
+
540
+ all_elements = []
541
+ all_elements.extend(elements_dict.get("functions", []))
542
+ all_elements.extend(elements_dict.get("classes", []))
543
+ all_elements.extend(elements_dict.get("variables", []))
544
+ all_elements.extend(elements_dict.get("imports", []))
545
+ all_elements.extend(elements_dict.get("packages", []))
546
+
547
+ node_count = (
548
+ self._count_tree_nodes(tree.root_node) if tree and tree.root_node else 0
549
+ )
550
+
551
+ # Get package
552
+ package = (
553
+ elements_dict.get("packages", [])[0]
554
+ if elements_dict.get("packages")
555
+ else None
556
+ )
557
+
558
+ return AnalysisResult(
559
+ file_path=file_path,
560
+ language="kotlin",
561
+ line_count=len(file_content.split("\n")),
562
+ elements=all_elements,
563
+ node_count=node_count,
564
+ source_code=file_content,
565
+ package=package,
566
+ )
567
+
568
+ except Exception as e:
569
+ log_error(f"Error analyzing Kotlin file {file_path}: {e}")
570
+ return AnalysisResult(
571
+ file_path=file_path,
572
+ language="kotlin",
573
+ line_count=0,
574
+ elements=[],
575
+ source_code="",
576
+ error_message=str(e),
577
+ success=False,
578
+ )
579
+
580
+ def _count_tree_nodes(self, node: Any) -> int:
581
+ """Recursively count nodes."""
582
+ if node is None:
583
+ return 0
584
+ count = 1
585
+ if hasattr(node, "children"):
586
+ for child in node.children:
587
+ count += self._count_tree_nodes(child)
588
+ return count
589
+
590
+ def get_tree_sitter_language(self) -> Any | None:
591
+ """Get the tree-sitter language for Kotlin."""
592
+ if self._cached_language is not None:
593
+ return self._cached_language
594
+
595
+ try:
596
+ import tree_sitter
597
+ import tree_sitter_kotlin
598
+
599
+ caps_or_lang = tree_sitter_kotlin.language()
600
+
601
+ if hasattr(caps_or_lang, "__class__") and "Language" in str(
602
+ type(caps_or_lang)
603
+ ):
604
+ self._cached_language = caps_or_lang
605
+ else:
606
+ try:
607
+ self._cached_language = tree_sitter.Language(caps_or_lang)
608
+ except Exception as e:
609
+ log_error(f"Failed to create Language object: {e}")
610
+ return None
611
+
612
+ return self._cached_language
613
+ except ImportError as e:
614
+ log_error(f"tree-sitter-kotlin not available: {e}")
615
+ return None
616
+ except Exception as e:
617
+ log_error(f"Failed to load tree-sitter language for Kotlin: {e}")
618
+ return None
619
+
620
+ def extract_elements(self, tree: Any | None, source_code: str) -> dict[str, Any]:
621
+ """Extract all elements."""
622
+ if tree is None:
623
+ return {
624
+ "functions": [],
625
+ "classes": [],
626
+ "variables": [],
627
+ "imports": [],
628
+ "packages": [],
629
+ }
630
+
631
+ try:
632
+ extractor = self.create_extractor()
633
+
634
+ return {
635
+ "functions": extractor.extract_functions(tree, source_code),
636
+ "classes": extractor.extract_classes(tree, source_code),
637
+ "variables": extractor.extract_variables(tree, source_code),
638
+ "imports": extractor.extract_imports(tree, source_code),
639
+ "packages": extractor.extract_packages(tree, source_code),
640
+ }
641
+
642
+ except Exception as e:
643
+ log_error(f"Error extracting elements: {e}")
644
+ return {
645
+ "functions": [],
646
+ "classes": [],
647
+ "variables": [],
648
+ "imports": [],
649
+ "packages": [],
650
+ }
651
+
652
+ def supports_file(self, file_path: str) -> bool:
653
+ """Check if this plugin supports the given file."""
654
+ return any(
655
+ file_path.lower().endswith(ext) for ext in self.get_file_extensions()
656
+ )