tree-sitter-analyzer 1.7.5__py3-none-any.whl → 1.8.2__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 (47) hide show
  1. tree_sitter_analyzer/__init__.py +1 -1
  2. tree_sitter_analyzer/api.py +26 -32
  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/exceptions.py +334 -0
  10. tree_sitter_analyzer/file_handler.py +16 -1
  11. tree_sitter_analyzer/formatters/formatter_registry.py +355 -0
  12. tree_sitter_analyzer/formatters/html_formatter.py +462 -0
  13. tree_sitter_analyzer/formatters/language_formatter_factory.py +3 -0
  14. tree_sitter_analyzer/formatters/markdown_formatter.py +1 -1
  15. tree_sitter_analyzer/interfaces/mcp_server.py +3 -1
  16. tree_sitter_analyzer/language_detector.py +91 -7
  17. tree_sitter_analyzer/languages/css_plugin.py +390 -0
  18. tree_sitter_analyzer/languages/html_plugin.py +395 -0
  19. tree_sitter_analyzer/languages/java_plugin.py +116 -0
  20. tree_sitter_analyzer/languages/javascript_plugin.py +113 -0
  21. tree_sitter_analyzer/languages/markdown_plugin.py +266 -46
  22. tree_sitter_analyzer/languages/python_plugin.py +176 -33
  23. tree_sitter_analyzer/languages/typescript_plugin.py +130 -1
  24. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +68 -3
  25. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +32 -7
  26. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +10 -0
  27. tree_sitter_analyzer/mcp/tools/list_files_tool.py +9 -0
  28. tree_sitter_analyzer/mcp/tools/query_tool.py +100 -52
  29. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +98 -14
  30. tree_sitter_analyzer/mcp/tools/search_content_tool.py +9 -0
  31. tree_sitter_analyzer/mcp/tools/table_format_tool.py +37 -13
  32. tree_sitter_analyzer/models.py +53 -0
  33. tree_sitter_analyzer/output_manager.py +1 -1
  34. tree_sitter_analyzer/plugins/base.py +50 -0
  35. tree_sitter_analyzer/plugins/manager.py +5 -1
  36. tree_sitter_analyzer/queries/css.py +634 -0
  37. tree_sitter_analyzer/queries/html.py +556 -0
  38. tree_sitter_analyzer/queries/markdown.py +54 -164
  39. tree_sitter_analyzer/query_loader.py +16 -3
  40. tree_sitter_analyzer/security/validator.py +343 -46
  41. tree_sitter_analyzer/utils/__init__.py +113 -0
  42. tree_sitter_analyzer/utils/tree_sitter_compat.py +282 -0
  43. tree_sitter_analyzer/utils.py +62 -24
  44. {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/METADATA +136 -14
  45. {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/RECORD +47 -38
  46. {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/entry_points.txt +2 -0
  47. {tree_sitter_analyzer-1.7.5.dist-info → tree_sitter_analyzer-1.8.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CSS Language Plugin
4
+
5
+ True CSS parser using tree-sitter-css for comprehensive CSS analysis.
6
+ Provides CSS-specific analysis capabilities including rule extraction,
7
+ selector parsing, and property analysis.
8
+ """
9
+
10
+ import logging
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from ..models import AnalysisResult, StyleElement
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 CssElementExtractor(ElementExtractor):
26
+ """CSS-specific element extractor using tree-sitter-css"""
27
+
28
+ def __init__(self):
29
+ self.property_categories = {
30
+ # CSS プロパティの分類システム
31
+ "layout": ["display", "position", "float", "clear", "overflow", "visibility", "z-index"],
32
+ "box_model": ["width", "height", "margin", "padding", "border", "box-sizing"],
33
+ "typography": ["font", "color", "text", "line-height", "letter-spacing", "word-spacing"],
34
+ "background": ["background", "background-color", "background-image", "background-position", "background-size"],
35
+ "flexbox": ["flex", "justify-content", "align-items", "align-content", "flex-direction", "flex-wrap"],
36
+ "grid": ["grid", "grid-template", "grid-area", "grid-column", "grid-row"],
37
+ "animation": ["animation", "transition", "transform", "keyframes"],
38
+ "responsive": ["media", "min-width", "max-width", "min-height", "max-height"],
39
+ "other": []
40
+ }
41
+
42
+ def extract_functions(self, tree: "tree_sitter.Tree", source_code: str) -> list:
43
+ """CSS doesn't have functions in the traditional sense, return empty list"""
44
+ return []
45
+
46
+ def extract_classes(self, tree: "tree_sitter.Tree", source_code: str) -> list:
47
+ """CSS doesn't have classes in the traditional sense, return empty list"""
48
+ return []
49
+
50
+ def extract_variables(self, tree: "tree_sitter.Tree", source_code: str) -> list:
51
+ """CSS doesn't have variables (except custom properties), return empty list"""
52
+ return []
53
+
54
+ def extract_imports(self, tree: "tree_sitter.Tree", source_code: str) -> list:
55
+ """CSS doesn't have imports in the traditional sense, return empty list"""
56
+ return []
57
+
58
+ def extract_css_rules(self, tree: "tree_sitter.Tree", source_code: str) -> list[StyleElement]:
59
+ """Extract CSS rules using tree-sitter-css parser"""
60
+ elements = []
61
+
62
+ try:
63
+ if hasattr(tree, "root_node"):
64
+ self._traverse_for_css_rules(tree.root_node, elements, source_code)
65
+ except Exception as e:
66
+ log_error(f"Error in CSS rule extraction: {e}")
67
+
68
+ return elements
69
+
70
+ def _traverse_for_css_rules(
71
+ self,
72
+ node: "tree_sitter.Node",
73
+ elements: list[StyleElement],
74
+ source_code: str
75
+ ) -> None:
76
+ """Traverse tree to find CSS rules using tree-sitter-css grammar"""
77
+ if hasattr(node, "type") and self._is_css_rule_node(node.type):
78
+ try:
79
+ element = self._create_style_element(node, source_code)
80
+ if element:
81
+ elements.append(element)
82
+ except Exception as e:
83
+ log_debug(f"Failed to extract CSS rule: {e}")
84
+
85
+ # Continue traversing children
86
+ if hasattr(node, "children"):
87
+ for child in node.children:
88
+ self._traverse_for_css_rules(child, elements, source_code)
89
+
90
+ def _is_css_rule_node(self, node_type: str) -> bool:
91
+ """Check if a node type represents a CSS rule in tree-sitter-css grammar"""
92
+ css_rule_types = [
93
+ "rule_set",
94
+ "at_rule",
95
+ "media_statement",
96
+ "import_statement",
97
+ "keyframes_statement",
98
+ "supports_statement",
99
+ "font_face_statement",
100
+ "page_statement",
101
+ "charset_statement",
102
+ "namespace_statement"
103
+ ]
104
+ return node_type in css_rule_types
105
+
106
+ def _create_style_element(
107
+ self,
108
+ node: "tree_sitter.Node",
109
+ source_code: str
110
+ ) -> StyleElement | None:
111
+ """Create StyleElement from tree-sitter node using tree-sitter-css grammar"""
112
+ try:
113
+ # Extract selector and properties based on node type
114
+ if node.type == "rule_set":
115
+ selector = self._extract_selector(node, source_code)
116
+ properties = self._extract_properties(node, source_code)
117
+ element_class = self._classify_rule(properties)
118
+ name = selector or "unknown_rule"
119
+ elif node.type in ["at_rule", "media_statement", "import_statement", "keyframes_statement"]:
120
+ selector = self._extract_at_rule_name(node, source_code)
121
+ properties = {}
122
+ element_class = "at_rule"
123
+ name = selector or "unknown_at_rule"
124
+ else:
125
+ selector = self._extract_node_text(node, source_code)[:50]
126
+ properties = {}
127
+ element_class = "other"
128
+ name = selector or "unknown"
129
+
130
+ # Extract raw text
131
+ raw_text = self._extract_node_text(node, source_code)
132
+
133
+ # Create StyleElement
134
+ element = StyleElement(
135
+ name=name,
136
+ start_line=node.start_point[0] + 1 if hasattr(node, "start_point") else 0,
137
+ end_line=node.end_point[0] + 1 if hasattr(node, "end_point") else 0,
138
+ raw_text=raw_text,
139
+ language="css",
140
+ selector=selector,
141
+ properties=properties,
142
+ element_class=element_class
143
+ )
144
+
145
+ return element
146
+
147
+ except Exception as e:
148
+ log_debug(f"Failed to create StyleElement: {e}")
149
+ return None
150
+
151
+ def _extract_selector(self, node: "tree_sitter.Node", source_code: str) -> str:
152
+ """Extract selector from CSS rule_set node using tree-sitter-css grammar"""
153
+ try:
154
+ if hasattr(node, "children"):
155
+ for child in node.children:
156
+ if hasattr(child, "type") and child.type == "selectors":
157
+ return self._extract_node_text(child, source_code).strip()
158
+
159
+ # Fallback: extract from beginning of node text
160
+ node_text = self._extract_node_text(node, source_code)
161
+ if "{" in node_text:
162
+ return node_text.split("{")[0].strip()
163
+
164
+ return "unknown"
165
+ except Exception:
166
+ return "unknown"
167
+
168
+ def _extract_properties(self, node: "tree_sitter.Node", source_code: str) -> dict[str, str]:
169
+ """Extract properties from CSS rule_set node using tree-sitter-css grammar"""
170
+ properties = {}
171
+
172
+ try:
173
+ if hasattr(node, "children"):
174
+ for child in node.children:
175
+ if hasattr(child, "type") and child.type == "block":
176
+ # Look for declarations within the block
177
+ for grandchild in child.children:
178
+ if hasattr(grandchild, "type") and grandchild.type == "declaration":
179
+ prop_name, prop_value = self._parse_declaration(grandchild, source_code)
180
+ if prop_name:
181
+ properties[prop_name] = prop_value
182
+ except Exception as e:
183
+ log_debug(f"Failed to extract properties: {e}")
184
+
185
+ return properties
186
+
187
+ def _parse_declaration(self, decl_node: "tree_sitter.Node", source_code: str) -> tuple[str, str]:
188
+ """Parse individual CSS declaration using tree-sitter-css grammar"""
189
+ try:
190
+ prop_name = ""
191
+ prop_value = ""
192
+
193
+ if hasattr(decl_node, "children"):
194
+ for child in decl_node.children:
195
+ if hasattr(child, "type"):
196
+ if child.type == "property_name":
197
+ prop_name = self._extract_node_text(child, source_code).strip()
198
+ elif child.type in ["value", "values"]:
199
+ prop_value = self._extract_node_text(child, source_code).strip()
200
+
201
+ # Fallback to simple parsing
202
+ if not prop_name:
203
+ decl_text = self._extract_node_text(decl_node, source_code)
204
+ if ":" in decl_text:
205
+ parts = decl_text.split(":", 1)
206
+ prop_name = parts[0].strip()
207
+ prop_value = parts[1].strip().rstrip(";")
208
+
209
+ return prop_name, prop_value
210
+ except Exception:
211
+ return "", ""
212
+
213
+ def _extract_at_rule_name(self, node: "tree_sitter.Node", source_code: str) -> str:
214
+ """Extract at-rule name from CSS at-rule node"""
215
+ try:
216
+ node_text = self._extract_node_text(node, source_code)
217
+ if node_text.startswith("@"):
218
+ # Extract @rule-name part
219
+ parts = node_text.split()
220
+ if parts:
221
+ return parts[0]
222
+ return node_text[:50] # Truncate for readability
223
+ except Exception:
224
+ return "unknown"
225
+
226
+ def _classify_rule(self, properties: dict[str, str]) -> str:
227
+ """Classify CSS rule based on properties"""
228
+ if not properties:
229
+ return "other"
230
+
231
+ # Count properties in each category
232
+ category_scores = {category: 0 for category in self.property_categories}
233
+
234
+ for prop_name in properties.keys():
235
+ prop_name_lower = prop_name.lower()
236
+ for category, props in self.property_categories.items():
237
+ if any(prop in prop_name_lower for prop in props):
238
+ category_scores[category] += 1
239
+
240
+ # Return category with highest score
241
+ best_category = max(category_scores, key=category_scores.get)
242
+ return best_category if category_scores[best_category] > 0 else "other"
243
+
244
+ def _extract_node_text(self, node: "tree_sitter.Node", source_code: str) -> str:
245
+ """Extract text content from a tree-sitter node"""
246
+ try:
247
+ if hasattr(node, "start_byte") and hasattr(node, "end_byte"):
248
+ source_bytes = source_code.encode("utf-8")
249
+ node_bytes = source_bytes[node.start_byte : node.end_byte]
250
+ return node_bytes.decode("utf-8", errors="replace")
251
+ return ""
252
+ except Exception as e:
253
+ log_debug(f"Failed to extract node text: {e}")
254
+ return ""
255
+
256
+
257
+ class CssPlugin(LanguagePlugin):
258
+ """CSS language plugin using tree-sitter-css for true CSS parsing"""
259
+
260
+ def get_language_name(self) -> str:
261
+ return "css"
262
+
263
+ def get_file_extensions(self) -> list[str]:
264
+ return [".css", ".scss", ".sass", ".less"]
265
+
266
+ def create_extractor(self) -> ElementExtractor:
267
+ return CssElementExtractor()
268
+
269
+ def get_supported_element_types(self) -> list[str]:
270
+ return ["css_rule"]
271
+
272
+ def get_queries(self) -> dict[str, str]:
273
+ """Return CSS-specific tree-sitter queries"""
274
+ from ..queries.css import CSS_QUERIES
275
+ return CSS_QUERIES
276
+
277
+ def execute_query_strategy(self, query_key: str | None, language: str) -> str | None:
278
+ """Execute query strategy for CSS"""
279
+ if language != "css":
280
+ return None
281
+
282
+ queries = self.get_queries()
283
+ return queries.get(query_key) if query_key else None
284
+
285
+ def get_element_categories(self) -> dict[str, list[str]]:
286
+ """Return CSS element categories for query execution"""
287
+ return {
288
+ "layout": ["rule_set"],
289
+ "box_model": ["rule_set"],
290
+ "typography": ["rule_set"],
291
+ "background": ["rule_set"],
292
+ "flexbox": ["rule_set"],
293
+ "grid": ["rule_set"],
294
+ "animation": ["rule_set"],
295
+ "responsive": ["media_statement"],
296
+ "at_rules": ["at_rule"],
297
+ "other": ["rule_set"]
298
+ }
299
+
300
+ async def analyze_file(
301
+ self, file_path: str, request: "AnalysisRequest"
302
+ ) -> "AnalysisResult":
303
+ """Analyze CSS file using tree-sitter-css parser"""
304
+ from ..core.analysis_engine import UnifiedAnalysisEngine
305
+ from ..encoding_utils import read_file_safe
306
+
307
+ try:
308
+ # Read file content
309
+ content, encoding = read_file_safe(file_path)
310
+
311
+ # Create analysis engine
312
+ engine = UnifiedAnalysisEngine()
313
+
314
+ # Use tree-sitter-css for parsing
315
+ try:
316
+ import tree_sitter_css as ts_css
317
+ import tree_sitter
318
+
319
+ # Get CSS language
320
+ CSS_LANGUAGE = tree_sitter.Language(ts_css.language())
321
+
322
+ # Create parser
323
+ parser = tree_sitter.Parser()
324
+ parser.language = CSS_LANGUAGE
325
+
326
+ # Parse the CSS content
327
+ tree = parser.parse(content.encode('utf-8'))
328
+
329
+ # Extract elements using the extractor
330
+ extractor = self.create_extractor()
331
+ elements = extractor.extract_css_rules(tree, content)
332
+
333
+ log_info(f"Extracted {len(elements)} CSS rules from {file_path}")
334
+
335
+ return AnalysisResult(
336
+ file_path=file_path,
337
+ language="css",
338
+ line_count=len(content.splitlines()),
339
+ elements=elements,
340
+ node_count=len(elements),
341
+ query_results={},
342
+ source_code=content,
343
+ success=True,
344
+ error_message=None
345
+ )
346
+
347
+ except ImportError:
348
+ log_error("tree-sitter-css not available, falling back to basic parsing")
349
+ # Fallback to basic parsing
350
+ lines = content.splitlines()
351
+ line_count = len(lines)
352
+
353
+ # Create basic StyleElement for the CSS document
354
+ css_element = StyleElement(
355
+ name="css",
356
+ start_line=1,
357
+ end_line=line_count,
358
+ raw_text=content[:200] + "..." if len(content) > 200 else content,
359
+ language="css",
360
+ selector="*",
361
+ properties={},
362
+ element_class="other"
363
+ )
364
+ elements = [css_element]
365
+
366
+ return AnalysisResult(
367
+ file_path=file_path,
368
+ language="css",
369
+ line_count=line_count,
370
+ elements=elements,
371
+ node_count=len(elements),
372
+ query_results={},
373
+ source_code=content,
374
+ success=True,
375
+ error_message=None
376
+ )
377
+
378
+ except Exception as e:
379
+ log_error(f"Failed to analyze CSS file {file_path}: {e}")
380
+ return AnalysisResult(
381
+ file_path=file_path,
382
+ language="css",
383
+ line_count=0,
384
+ elements=[],
385
+ node_count=0,
386
+ query_results={},
387
+ source_code="",
388
+ success=False,
389
+ error_message=str(e)
390
+ )