tree-sitter-analyzer 1.7.7__py3-none-any.whl → 1.8.3__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.

Potentially problematic release.


This version of tree-sitter-analyzer might be problematic. Click here for more details.

Files changed (42) hide show
  1. tree_sitter_analyzer/__init__.py +1 -1
  2. tree_sitter_analyzer/api.py +23 -30
  3. tree_sitter_analyzer/cli/argument_validator.py +77 -0
  4. tree_sitter_analyzer/cli/commands/table_command.py +7 -2
  5. tree_sitter_analyzer/cli_main.py +17 -3
  6. tree_sitter_analyzer/core/cache_service.py +15 -5
  7. tree_sitter_analyzer/core/query.py +33 -22
  8. tree_sitter_analyzer/core/query_service.py +179 -154
  9. tree_sitter_analyzer/formatters/formatter_registry.py +355 -0
  10. tree_sitter_analyzer/formatters/html_formatter.py +462 -0
  11. tree_sitter_analyzer/formatters/language_formatter_factory.py +3 -0
  12. tree_sitter_analyzer/formatters/markdown_formatter.py +1 -1
  13. tree_sitter_analyzer/language_detector.py +80 -7
  14. tree_sitter_analyzer/languages/css_plugin.py +390 -0
  15. tree_sitter_analyzer/languages/html_plugin.py +395 -0
  16. tree_sitter_analyzer/languages/java_plugin.py +116 -0
  17. tree_sitter_analyzer/languages/javascript_plugin.py +113 -0
  18. tree_sitter_analyzer/languages/markdown_plugin.py +266 -46
  19. tree_sitter_analyzer/languages/python_plugin.py +176 -33
  20. tree_sitter_analyzer/languages/typescript_plugin.py +130 -1
  21. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +12 -1
  22. tree_sitter_analyzer/mcp/tools/query_tool.py +101 -60
  23. tree_sitter_analyzer/mcp/tools/search_content_tool.py +12 -1
  24. tree_sitter_analyzer/mcp/tools/table_format_tool.py +26 -12
  25. tree_sitter_analyzer/mcp/utils/file_output_factory.py +204 -0
  26. tree_sitter_analyzer/mcp/utils/file_output_manager.py +52 -2
  27. tree_sitter_analyzer/models.py +53 -0
  28. tree_sitter_analyzer/output_manager.py +1 -1
  29. tree_sitter_analyzer/plugins/base.py +50 -0
  30. tree_sitter_analyzer/plugins/manager.py +5 -1
  31. tree_sitter_analyzer/queries/css.py +634 -0
  32. tree_sitter_analyzer/queries/html.py +556 -0
  33. tree_sitter_analyzer/queries/markdown.py +54 -164
  34. tree_sitter_analyzer/query_loader.py +16 -3
  35. tree_sitter_analyzer/security/validator.py +182 -44
  36. tree_sitter_analyzer/utils/__init__.py +113 -0
  37. tree_sitter_analyzer/utils/tree_sitter_compat.py +282 -0
  38. tree_sitter_analyzer/utils.py +62 -24
  39. {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/METADATA +135 -31
  40. {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/RECORD +42 -32
  41. {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/entry_points.txt +2 -0
  42. {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ HTML Language Plugin
4
+
5
+ True HTML parser using tree-sitter-html for comprehensive HTML analysis.
6
+ Provides HTML-specific analysis capabilities including element extraction,
7
+ attribute parsing, and document structure analysis.
8
+ """
9
+
10
+ import logging
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from ..models import AnalysisResult, MarkupElement
14
+ from ..plugins.base import ElementExtractor, LanguagePlugin
15
+ from ..utils import log_debug, log_error, log_info
16
+
17
+ if TYPE_CHECKING:
18
+ import tree_sitter
19
+
20
+ from ..core.analysis_engine import AnalysisRequest
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class HtmlElementExtractor(ElementExtractor):
26
+ """HTML-specific element extractor using tree-sitter-html"""
27
+
28
+ def __init__(self):
29
+ self.element_categories = {
30
+ # HTML要素の分類システム
31
+ "structure": ["html", "body", "div", "span", "section", "article", "aside", "nav", "main", "header", "footer"],
32
+ "heading": ["h1", "h2", "h3", "h4", "h5", "h6"],
33
+ "text": ["p", "a", "strong", "em", "b", "i", "u", "small", "mark", "del", "ins", "sub", "sup"],
34
+ "list": ["ul", "ol", "li", "dl", "dt", "dd"],
35
+ "media": ["img", "video", "audio", "source", "track", "canvas", "svg", "picture"],
36
+ "form": ["form", "input", "textarea", "button", "select", "option", "optgroup", "label", "fieldset", "legend"],
37
+ "table": ["table", "thead", "tbody", "tfoot", "tr", "td", "th", "caption", "colgroup", "col"],
38
+ "metadata": ["head", "title", "meta", "link", "style", "script", "noscript", "base"]
39
+ }
40
+
41
+ def extract_functions(self, tree: "tree_sitter.Tree", source_code: str) -> list:
42
+ """HTML doesn't have functions, return empty list"""
43
+ return []
44
+
45
+ def extract_classes(self, tree: "tree_sitter.Tree", source_code: str) -> list:
46
+ """HTML doesn't have classes in the traditional sense, return empty list"""
47
+ return []
48
+
49
+ def extract_variables(self, tree: "tree_sitter.Tree", source_code: str) -> list:
50
+ """HTML doesn't have variables, return empty list"""
51
+ return []
52
+
53
+ def extract_imports(self, tree: "tree_sitter.Tree", source_code: str) -> list:
54
+ """HTML doesn't have imports, return empty list"""
55
+ return []
56
+
57
+ def extract_html_elements(self, tree: "tree_sitter.Tree", source_code: str) -> list[MarkupElement]:
58
+ """Extract HTML elements using tree-sitter-html parser"""
59
+ elements = []
60
+
61
+ try:
62
+ if hasattr(tree, "root_node"):
63
+ self._traverse_for_html_elements(tree.root_node, elements, source_code, None)
64
+ except Exception as e:
65
+ log_error(f"Error in HTML element extraction: {e}")
66
+
67
+ return elements
68
+
69
+ def _traverse_for_html_elements(
70
+ self,
71
+ node: "tree_sitter.Node",
72
+ elements: list[MarkupElement],
73
+ source_code: str,
74
+ parent: MarkupElement | None
75
+ ) -> None:
76
+ """Traverse tree to find HTML elements using tree-sitter-html grammar"""
77
+ if hasattr(node, "type") and self._is_html_element_node(node.type):
78
+ try:
79
+ element = self._create_markup_element(node, source_code, parent)
80
+ if element:
81
+ elements.append(element)
82
+
83
+ # Process children with this element as parent
84
+ if hasattr(node, "children"):
85
+ for child in node.children:
86
+ self._traverse_for_html_elements(child, elements, source_code, element)
87
+ return
88
+ except Exception as e:
89
+ log_debug(f"Failed to extract HTML element: {e}")
90
+
91
+ # Continue traversing children if this node is not an HTML element
92
+ if hasattr(node, "children"):
93
+ for child in node.children:
94
+ self._traverse_for_html_elements(child, elements, source_code, parent)
95
+
96
+ def _is_html_element_node(self, node_type: str) -> bool:
97
+ """Check if a node type represents an HTML element in tree-sitter-html grammar"""
98
+ html_element_types = [
99
+ "element",
100
+ "start_tag",
101
+ "self_closing_tag",
102
+ "script_element",
103
+ "style_element",
104
+ "void_element"
105
+ ]
106
+ return node_type in html_element_types
107
+
108
+ def _create_markup_element(
109
+ self,
110
+ node: "tree_sitter.Node",
111
+ source_code: str,
112
+ parent: MarkupElement | None
113
+ ) -> MarkupElement | None:
114
+ """Create MarkupElement from tree-sitter node using tree-sitter-html grammar"""
115
+ try:
116
+ # Extract tag name using tree-sitter-html structure
117
+ tag_name = self._extract_tag_name(node, source_code)
118
+ if not tag_name:
119
+ return None
120
+
121
+ # Extract attributes using tree-sitter-html structure
122
+ attributes = self._extract_attributes(node, source_code)
123
+
124
+ # Determine element class based on tag name
125
+ element_class = self._classify_element(tag_name)
126
+
127
+ # Extract text content
128
+ raw_text = self._extract_node_text(node, source_code)
129
+
130
+ # Create MarkupElement
131
+ element = MarkupElement(
132
+ name=tag_name,
133
+ start_line=node.start_point[0] + 1 if hasattr(node, "start_point") else 0,
134
+ end_line=node.end_point[0] + 1 if hasattr(node, "end_point") else 0,
135
+ raw_text=raw_text,
136
+ language="html",
137
+ tag_name=tag_name,
138
+ attributes=attributes,
139
+ parent=parent,
140
+ children=[],
141
+ element_class=element_class
142
+ )
143
+
144
+ # Add to parent's children if parent exists
145
+ if parent:
146
+ parent.children.append(element)
147
+
148
+ return element
149
+
150
+ except Exception as e:
151
+ log_debug(f"Failed to create MarkupElement: {e}")
152
+ return None
153
+
154
+ def _extract_tag_name(self, node: "tree_sitter.Node", source_code: str) -> str:
155
+ """Extract tag name from HTML element node using tree-sitter-html grammar"""
156
+ try:
157
+ # For tree-sitter-html, tag names are in specific child nodes
158
+ if hasattr(node, "children"):
159
+ for child in node.children:
160
+ if hasattr(child, "type"):
161
+ # Handle different node types in tree-sitter-html
162
+ if child.type == "tag_name":
163
+ return self._extract_node_text(child, source_code).strip()
164
+ elif child.type in ["start_tag", "self_closing_tag"]:
165
+ # Look for tag_name within start_tag or self_closing_tag
166
+ for grandchild in child.children:
167
+ if hasattr(grandchild, "type") and grandchild.type == "tag_name":
168
+ return self._extract_node_text(grandchild, source_code).strip()
169
+
170
+ # Fallback: try to extract from node text
171
+ node_text = self._extract_node_text(node, source_code)
172
+ if node_text.startswith("<"):
173
+ # Extract tag name from <tagname ...> pattern
174
+ tag_part = node_text.split(">")[0].split()[0]
175
+ return tag_part.lstrip("<").rstrip(">")
176
+
177
+ return "unknown"
178
+ except Exception:
179
+ return "unknown"
180
+
181
+ def _extract_attributes(self, node: "tree_sitter.Node", source_code: str) -> dict[str, str]:
182
+ """Extract attributes from HTML element node using tree-sitter-html grammar"""
183
+ attributes = {}
184
+
185
+ try:
186
+ if hasattr(node, "children"):
187
+ for child in node.children:
188
+ if hasattr(child, "type"):
189
+ # Handle attribute nodes in tree-sitter-html
190
+ if child.type == "attribute":
191
+ attr_name, attr_value = self._parse_attribute(child, source_code)
192
+ if attr_name:
193
+ attributes[attr_name] = attr_value
194
+ elif child.type in ["start_tag", "self_closing_tag"]:
195
+ # Look for attributes within start_tag or self_closing_tag
196
+ for grandchild in child.children:
197
+ if hasattr(grandchild, "type") and grandchild.type == "attribute":
198
+ attr_name, attr_value = self._parse_attribute(grandchild, source_code)
199
+ if attr_name:
200
+ attributes[attr_name] = attr_value
201
+ except Exception as e:
202
+ log_debug(f"Failed to extract attributes: {e}")
203
+
204
+ return attributes
205
+
206
+ def _parse_attribute(self, attr_node: "tree_sitter.Node", source_code: str) -> tuple[str, str]:
207
+ """Parse individual attribute node using tree-sitter-html grammar"""
208
+ try:
209
+ # In tree-sitter-html, attributes have specific structure
210
+ attr_name = ""
211
+ attr_value = ""
212
+
213
+ if hasattr(attr_node, "children"):
214
+ for child in attr_node.children:
215
+ if hasattr(child, "type"):
216
+ if child.type == "attribute_name":
217
+ attr_name = self._extract_node_text(child, source_code).strip()
218
+ elif child.type == "quoted_attribute_value":
219
+ attr_value = self._extract_node_text(child, source_code).strip().strip('"').strip("'")
220
+ elif child.type == "attribute_value":
221
+ attr_value = self._extract_node_text(child, source_code).strip()
222
+
223
+ # Fallback to simple parsing
224
+ if not attr_name:
225
+ attr_text = self._extract_node_text(attr_node, source_code)
226
+ if "=" in attr_text:
227
+ name, value = attr_text.split("=", 1)
228
+ attr_name = name.strip()
229
+ attr_value = value.strip().strip('"').strip("'")
230
+ else:
231
+ # Boolean attribute
232
+ attr_name = attr_text.strip()
233
+ attr_value = ""
234
+
235
+ return attr_name, attr_value
236
+ except Exception:
237
+ return "", ""
238
+
239
+ def _classify_element(self, tag_name: str) -> str:
240
+ """Classify HTML element based on tag name"""
241
+ tag_name_lower = tag_name.lower()
242
+
243
+ for category, tags in self.element_categories.items():
244
+ if tag_name_lower in tags:
245
+ return category
246
+
247
+ return "unknown"
248
+
249
+ def _extract_node_text(self, node: "tree_sitter.Node", source_code: str) -> str:
250
+ """Extract text content from a tree-sitter node"""
251
+ try:
252
+ if hasattr(node, "start_byte") and hasattr(node, "end_byte"):
253
+ source_bytes = source_code.encode("utf-8")
254
+ node_bytes = source_bytes[node.start_byte : node.end_byte]
255
+ return node_bytes.decode("utf-8", errors="replace")
256
+ return ""
257
+ except Exception as e:
258
+ log_debug(f"Failed to extract node text: {e}")
259
+ return ""
260
+
261
+
262
+ class HtmlPlugin(LanguagePlugin):
263
+ """HTML language plugin using tree-sitter-html for true HTML parsing"""
264
+
265
+ def get_language_name(self) -> str:
266
+ return "html"
267
+
268
+ def get_file_extensions(self) -> list[str]:
269
+ return [".html", ".htm", ".xhtml"]
270
+
271
+ def create_extractor(self) -> ElementExtractor:
272
+ return HtmlElementExtractor()
273
+
274
+ def get_supported_element_types(self) -> list[str]:
275
+ return ["html_element"]
276
+
277
+ def get_queries(self) -> dict[str, str]:
278
+ """Return HTML-specific tree-sitter queries"""
279
+ from ..queries.html import HTML_QUERIES
280
+ return HTML_QUERIES
281
+
282
+ def execute_query_strategy(self, query_key: str | None, language: str) -> str | None:
283
+ """Execute query strategy for HTML"""
284
+ if language != "html":
285
+ return None
286
+
287
+ queries = self.get_queries()
288
+ return queries.get(query_key) if query_key else None
289
+
290
+ def get_element_categories(self) -> dict[str, list[str]]:
291
+ """Return HTML element categories for query execution"""
292
+ return {
293
+ "structure": ["element"],
294
+ "heading": ["element"],
295
+ "text": ["element"],
296
+ "list": ["element"],
297
+ "media": ["element"],
298
+ "form": ["element"],
299
+ "table": ["element"],
300
+ "metadata": ["element"]
301
+ }
302
+
303
+ async def analyze_file(
304
+ self, file_path: str, request: "AnalysisRequest"
305
+ ) -> "AnalysisResult":
306
+ """Analyze HTML file using tree-sitter-html parser"""
307
+ from ..core.analysis_engine import UnifiedAnalysisEngine
308
+ from ..encoding_utils import read_file_safe
309
+
310
+ try:
311
+ # Read file content
312
+ content, encoding = read_file_safe(file_path)
313
+
314
+ # Create analysis engine
315
+ engine = UnifiedAnalysisEngine()
316
+
317
+ # Use tree-sitter-html for parsing
318
+ try:
319
+ import tree_sitter_html as ts_html
320
+ import tree_sitter
321
+
322
+ # Get HTML language
323
+ HTML_LANGUAGE = tree_sitter.Language(ts_html.language())
324
+
325
+ # Create parser
326
+ parser = tree_sitter.Parser()
327
+ parser.language = HTML_LANGUAGE
328
+
329
+ # Parse the HTML content
330
+ tree = parser.parse(content.encode('utf-8'))
331
+
332
+ # Extract elements using the extractor
333
+ extractor = self.create_extractor()
334
+ elements = extractor.extract_html_elements(tree, content)
335
+
336
+ log_info(f"Extracted {len(elements)} HTML elements from {file_path}")
337
+
338
+ return AnalysisResult(
339
+ file_path=file_path,
340
+ language="html",
341
+ line_count=len(content.splitlines()),
342
+ elements=elements,
343
+ node_count=len(elements),
344
+ query_results={},
345
+ source_code=content,
346
+ success=True,
347
+ error_message=None
348
+ )
349
+
350
+ except ImportError:
351
+ log_error("tree-sitter-html not available, falling back to basic parsing")
352
+ # Fallback to basic parsing
353
+ lines = content.splitlines()
354
+ line_count = len(lines)
355
+
356
+ # Create basic MarkupElement for the HTML document
357
+ html_element = MarkupElement(
358
+ name="html",
359
+ start_line=1,
360
+ end_line=line_count,
361
+ raw_text=content[:200] + "..." if len(content) > 200 else content,
362
+ language="html",
363
+ tag_name="html",
364
+ attributes={},
365
+ parent=None,
366
+ children=[],
367
+ element_class="structure"
368
+ )
369
+ elements = [html_element]
370
+
371
+ return AnalysisResult(
372
+ file_path=file_path,
373
+ language="html",
374
+ line_count=line_count,
375
+ elements=elements,
376
+ node_count=len(elements),
377
+ query_results={},
378
+ source_code=content,
379
+ success=True,
380
+ error_message=None
381
+ )
382
+
383
+ except Exception as e:
384
+ log_error(f"Failed to analyze HTML file {file_path}: {e}")
385
+ return AnalysisResult(
386
+ file_path=file_path,
387
+ language="html",
388
+ line_count=0,
389
+ elements=[],
390
+ node_count=0,
391
+ query_results={},
392
+ source_code="",
393
+ success=False,
394
+ error_message=str(e)
395
+ )
@@ -1191,6 +1191,122 @@ class JavaPlugin(LanguagePlugin):
1191
1191
  "supported_queries": self.get_supported_queries(),
1192
1192
  }
1193
1193
 
1194
+ def execute_query_strategy(self, tree: "tree_sitter.Tree", source_code: str, query_key: str) -> list[dict]:
1195
+ """
1196
+ Execute query strategy for Java language
1197
+
1198
+ Args:
1199
+ tree: Tree-sitter tree object
1200
+ source_code: Source code string
1201
+ query_key: Query key to execute
1202
+
1203
+ Returns:
1204
+ List of query results
1205
+ """
1206
+ # Use the extractor to get elements based on query_key
1207
+ extractor = self.get_extractor()
1208
+
1209
+ # Map query keys to extraction methods
1210
+ if query_key in ["method", "methods", "function", "functions"]:
1211
+ elements = extractor.extract_functions(tree, source_code)
1212
+ elif query_key in ["class", "classes"]:
1213
+ elements = extractor.extract_classes(tree, source_code)
1214
+ elif query_key in ["field", "fields", "variable", "variables"]:
1215
+ elements = extractor.extract_variables(tree, source_code)
1216
+ elif query_key in ["import", "imports"]:
1217
+ elements = extractor.extract_imports(tree, source_code)
1218
+ elif query_key in ["package", "packages"]:
1219
+ elements = extractor.extract_packages(tree, source_code)
1220
+ elif query_key in ["annotation", "annotations"]:
1221
+ elements = extractor.extract_annotations(tree, source_code)
1222
+ else:
1223
+ # For unknown query keys, return empty list
1224
+ return []
1225
+
1226
+ # Convert elements to query result format
1227
+ results = []
1228
+ for element in elements:
1229
+ result = {
1230
+ "capture_name": query_key,
1231
+ "node_type": self._get_node_type_for_element(element),
1232
+ "start_line": element.start_line,
1233
+ "end_line": element.end_line,
1234
+ "text": element.raw_text,
1235
+ "name": element.name,
1236
+ }
1237
+ results.append(result)
1238
+
1239
+ return results
1240
+
1241
+ def _get_node_type_for_element(self, element) -> str:
1242
+ """Get appropriate node type for element"""
1243
+ from ..models import Function, Class, Variable, Import, Package
1244
+
1245
+ if isinstance(element, Function):
1246
+ return "method_declaration" if not element.is_constructor else "constructor_declaration"
1247
+ elif isinstance(element, Class):
1248
+ if element.class_type == "interface":
1249
+ return "interface_declaration"
1250
+ elif element.class_type == "enum":
1251
+ return "enum_declaration"
1252
+ else:
1253
+ return "class_declaration"
1254
+ elif isinstance(element, Variable):
1255
+ return "field_declaration"
1256
+ elif isinstance(element, Import):
1257
+ return "import_declaration"
1258
+ elif isinstance(element, Package):
1259
+ return "package_declaration"
1260
+ else:
1261
+ return "unknown"
1262
+
1263
+ def get_element_categories(self) -> dict[str, list[str]]:
1264
+ """
1265
+ Get element categories mapping query keys to node types
1266
+
1267
+ Returns:
1268
+ Dictionary mapping query keys to lists of node types
1269
+ """
1270
+ return {
1271
+ # Method-related queries
1272
+ "method": ["method_declaration"],
1273
+ "methods": ["method_declaration"],
1274
+ "constructor": ["constructor_declaration"],
1275
+ "constructors": ["constructor_declaration"],
1276
+
1277
+ # Class-related queries
1278
+ "class": ["class_declaration"],
1279
+ "classes": ["class_declaration"],
1280
+ "interface": ["interface_declaration"],
1281
+ "interfaces": ["interface_declaration"],
1282
+ "enum": ["enum_declaration"],
1283
+ "enums": ["enum_declaration"],
1284
+
1285
+ # Field-related queries
1286
+ "field": ["field_declaration"],
1287
+ "fields": ["field_declaration"],
1288
+
1289
+ # Import-related queries
1290
+ "import": ["import_declaration"],
1291
+ "imports": ["import_declaration"],
1292
+
1293
+ # Package-related queries
1294
+ "package": ["package_declaration"],
1295
+ "packages": ["package_declaration"],
1296
+
1297
+ # Annotation-related queries
1298
+ "annotation": ["annotation", "marker_annotation"],
1299
+ "annotations": ["annotation", "marker_annotation"],
1300
+
1301
+ # Generic queries
1302
+ "all_elements": [
1303
+ "method_declaration", "constructor_declaration",
1304
+ "class_declaration", "interface_declaration", "enum_declaration",
1305
+ "field_declaration", "import_declaration", "package_declaration",
1306
+ "annotation", "marker_annotation"
1307
+ ],
1308
+ }
1309
+
1194
1310
  async def analyze_file(
1195
1311
  self, file_path: str, request: "AnalysisRequest"
1196
1312
  ) -> "AnalysisResult":
@@ -1440,6 +1440,119 @@ class JavaScriptPlugin(LanguagePlugin):
1440
1440
  ],
1441
1441
  }
1442
1442
 
1443
+ def execute_query_strategy(self, tree: "tree_sitter.Tree", source_code: str, query_key: str) -> list[dict]:
1444
+ """
1445
+ Execute query strategy for JavaScript language
1446
+
1447
+ Args:
1448
+ tree: Tree-sitter tree object
1449
+ source_code: Source code string
1450
+ query_key: Query key to execute
1451
+
1452
+ Returns:
1453
+ List of query results
1454
+ """
1455
+ # Use the extractor to get elements based on query_key
1456
+ extractor = self.get_extractor()
1457
+
1458
+ # Map query keys to extraction methods
1459
+ if query_key in ["function", "functions", "async_function", "async_functions", "arrow_function", "arrow_functions", "method", "methods", "constructor", "constructors"]:
1460
+ elements = extractor.extract_functions(tree, source_code)
1461
+ elif query_key in ["class", "classes", "react_component", "react_components"]:
1462
+ elements = extractor.extract_classes(tree, source_code)
1463
+ elif query_key in ["variable", "variables"]:
1464
+ elements = extractor.extract_variables(tree, source_code)
1465
+ elif query_key in ["import", "imports"]:
1466
+ elements = extractor.extract_imports(tree, source_code)
1467
+ else:
1468
+ # For unknown query keys, return empty list
1469
+ return []
1470
+
1471
+ # Convert elements to query result format
1472
+ results = []
1473
+ for element in elements:
1474
+ result = {
1475
+ "capture_name": query_key,
1476
+ "node_type": self._get_node_type_for_element(element),
1477
+ "start_line": element.start_line,
1478
+ "end_line": element.end_line,
1479
+ "text": element.raw_text,
1480
+ "name": element.name,
1481
+ }
1482
+ results.append(result)
1483
+
1484
+ return results
1485
+
1486
+ def _get_node_type_for_element(self, element) -> str:
1487
+ """Get appropriate node type for element"""
1488
+ from ..models import Function, Class, Variable, Import
1489
+
1490
+ if isinstance(element, Function):
1491
+ if hasattr(element, 'is_arrow') and element.is_arrow:
1492
+ return "arrow_function"
1493
+ elif hasattr(element, 'is_method') and element.is_method:
1494
+ return "method_definition"
1495
+ else:
1496
+ return "function_declaration"
1497
+ elif isinstance(element, Class):
1498
+ return "class_declaration"
1499
+ elif isinstance(element, Variable):
1500
+ return "variable_declaration"
1501
+ elif isinstance(element, Import):
1502
+ return "import_statement"
1503
+ else:
1504
+ return "unknown"
1505
+
1506
+ def get_element_categories(self) -> dict[str, list[str]]:
1507
+ """
1508
+ Get element categories mapping query keys to node types
1509
+
1510
+ Returns:
1511
+ Dictionary mapping query keys to lists of node types
1512
+ """
1513
+ return {
1514
+ # Function-related queries
1515
+ "function": ["function_declaration", "function_expression"],
1516
+ "functions": ["function_declaration", "function_expression"],
1517
+ "async_function": ["function_declaration", "function_expression"],
1518
+ "async_functions": ["function_declaration", "function_expression"],
1519
+ "arrow_function": ["arrow_function"],
1520
+ "arrow_functions": ["arrow_function"],
1521
+ "method": ["method_definition"],
1522
+ "methods": ["method_definition"],
1523
+ "constructor": ["method_definition"],
1524
+ "constructors": ["method_definition"],
1525
+
1526
+ # Class-related queries
1527
+ "class": ["class_declaration", "class_expression"],
1528
+ "classes": ["class_declaration", "class_expression"],
1529
+
1530
+ # Variable-related queries
1531
+ "variable": ["variable_declaration", "lexical_declaration"],
1532
+ "variables": ["variable_declaration", "lexical_declaration"],
1533
+
1534
+ # Import/Export-related queries
1535
+ "import": ["import_statement"],
1536
+ "imports": ["import_statement"],
1537
+ "export": ["export_statement"],
1538
+ "exports": ["export_statement"],
1539
+
1540
+ # React-specific queries
1541
+ "react_component": ["class_declaration", "function_declaration"],
1542
+ "react_components": ["class_declaration", "function_declaration"],
1543
+ "react_hook": ["function_declaration"],
1544
+ "react_hooks": ["function_declaration"],
1545
+ "jsx_element": ["jsx_element", "jsx_self_closing_element"],
1546
+ "jsx_elements": ["jsx_element", "jsx_self_closing_element"],
1547
+
1548
+ # Generic queries
1549
+ "all_elements": [
1550
+ "function_declaration", "function_expression", "arrow_function", "method_definition",
1551
+ "class_declaration", "class_expression", "variable_declaration", "lexical_declaration",
1552
+ "import_statement", "export_statement", "jsx_element", "jsx_self_closing_element"
1553
+ ],
1554
+ }
1555
+
1443
1556
  async def analyze_file(
1444
1557
  self, file_path: str, request: AnalysisRequest
1445
1558
  ) -> AnalysisResult: