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,204 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ File Output Manager Factory
4
+
5
+ This module provides a Managed Singleton Factory Pattern for FileOutputManager
6
+ to prevent duplicate initialization and ensure consistent instance management
7
+ across MCP tools.
8
+ """
9
+
10
+ import threading
11
+ from pathlib import Path
12
+ from typing import Dict, Optional
13
+
14
+ from ...utils import setup_logger
15
+ from .file_output_manager import FileOutputManager
16
+
17
+ # Set up logging
18
+ logger = setup_logger(__name__)
19
+
20
+
21
+ class FileOutputManagerFactory:
22
+ """
23
+ Factory class that manages FileOutputManager instances using a Managed Singleton
24
+ pattern. Each project root gets its own singleton instance, ensuring consistency
25
+ across MCP tools while preventing duplicate initialization.
26
+ """
27
+
28
+ # Class-level lock for thread safety
29
+ _lock = threading.RLock()
30
+
31
+ # Dictionary to store instances by project root
32
+ _instances: Dict[str, FileOutputManager] = {}
33
+
34
+ @classmethod
35
+ def get_instance(cls, project_root: Optional[str] = None) -> FileOutputManager:
36
+ """
37
+ Get or create a FileOutputManager instance for the specified project root.
38
+
39
+ This method implements the Managed Singleton pattern - one instance per
40
+ project root, ensuring consistency across all MCP tools.
41
+
42
+ Args:
43
+ project_root: Project root directory. If None, uses current working directory.
44
+
45
+ Returns:
46
+ FileOutputManager instance for the specified project root
47
+ """
48
+ # Normalize project root path
49
+ normalized_root = cls._normalize_project_root(project_root)
50
+
51
+ # Double-checked locking pattern for thread safety
52
+ if normalized_root not in cls._instances:
53
+ with cls._lock:
54
+ if normalized_root not in cls._instances:
55
+ logger.info(f"Creating new FileOutputManager instance for project root: {normalized_root}")
56
+ cls._instances[normalized_root] = FileOutputManager(normalized_root)
57
+ else:
58
+ logger.debug(f"Using existing FileOutputManager instance for project root: {normalized_root}")
59
+ else:
60
+ logger.debug(f"Using existing FileOutputManager instance for project root: {normalized_root}")
61
+
62
+ return cls._instances[normalized_root]
63
+
64
+ @classmethod
65
+ def _normalize_project_root(cls, project_root: Optional[str]) -> str:
66
+ """
67
+ Normalize project root path for consistent key generation.
68
+
69
+ Args:
70
+ project_root: Raw project root path
71
+
72
+ Returns:
73
+ Normalized absolute path string
74
+ """
75
+ if project_root is None:
76
+ return str(Path.cwd().resolve())
77
+
78
+ try:
79
+ return str(Path(project_root).resolve())
80
+ except Exception as e:
81
+ logger.warning(f"Failed to resolve project root path '{project_root}': {e}")
82
+ return str(Path.cwd().resolve())
83
+
84
+ @classmethod
85
+ def clear_instance(cls, project_root: Optional[str] = None) -> bool:
86
+ """
87
+ Clear a specific FileOutputManager instance from the factory.
88
+
89
+ This method is primarily for testing purposes or when you need to
90
+ force recreation of an instance.
91
+
92
+ Args:
93
+ project_root: Project root directory. If None, uses current working directory.
94
+
95
+ Returns:
96
+ True if instance was cleared, False if it didn't exist
97
+ """
98
+ normalized_root = cls._normalize_project_root(project_root)
99
+
100
+ with cls._lock:
101
+ if normalized_root in cls._instances:
102
+ logger.info(f"Clearing FileOutputManager instance for project root: {normalized_root}")
103
+ del cls._instances[normalized_root]
104
+ return True
105
+ else:
106
+ logger.debug(f"No FileOutputManager instance found for project root: {normalized_root}")
107
+ return False
108
+
109
+ @classmethod
110
+ def clear_all_instances(cls) -> int:
111
+ """
112
+ Clear all FileOutputManager instances from the factory.
113
+
114
+ This method is primarily for testing purposes or cleanup.
115
+
116
+ Returns:
117
+ Number of instances that were cleared
118
+ """
119
+ with cls._lock:
120
+ count = len(cls._instances)
121
+ if count > 0:
122
+ logger.info(f"Clearing all {count} FileOutputManager instances")
123
+ cls._instances.clear()
124
+ else:
125
+ logger.debug("No FileOutputManager instances to clear")
126
+ return count
127
+
128
+ @classmethod
129
+ def get_instance_count(cls) -> int:
130
+ """
131
+ Get the current number of managed instances.
132
+
133
+ Returns:
134
+ Number of currently managed FileOutputManager instances
135
+ """
136
+ with cls._lock:
137
+ return len(cls._instances)
138
+
139
+ @classmethod
140
+ def get_managed_project_roots(cls) -> list[str]:
141
+ """
142
+ Get list of all currently managed project roots.
143
+
144
+ Returns:
145
+ List of project root paths that have managed instances
146
+ """
147
+ with cls._lock:
148
+ return list(cls._instances.keys())
149
+
150
+ @classmethod
151
+ def update_project_root(cls, old_root: Optional[str], new_root: str) -> bool:
152
+ """
153
+ Update the project root for an existing instance.
154
+
155
+ This method moves an existing instance from one project root key to another,
156
+ and updates the instance's internal project root.
157
+
158
+ Args:
159
+ old_root: Current project root (None for current working directory)
160
+ new_root: New project root
161
+
162
+ Returns:
163
+ True if update was successful, False if old instance didn't exist
164
+ """
165
+ old_normalized = cls._normalize_project_root(old_root)
166
+ new_normalized = cls._normalize_project_root(new_root)
167
+
168
+ if old_normalized == new_normalized:
169
+ logger.debug(f"Project root update not needed: {old_normalized}")
170
+ return True
171
+
172
+ with cls._lock:
173
+ if old_normalized in cls._instances:
174
+ instance = cls._instances[old_normalized]
175
+
176
+ # Update the instance's internal project root
177
+ instance.set_project_root(new_root)
178
+
179
+ # Move to new key
180
+ cls._instances[new_normalized] = instance
181
+ del cls._instances[old_normalized]
182
+
183
+ logger.info(f"Updated FileOutputManager project root: {old_normalized} -> {new_normalized}")
184
+ return True
185
+ else:
186
+ logger.warning(f"No FileOutputManager instance found for old project root: {old_normalized}")
187
+ return False
188
+
189
+
190
+ # Convenience function for backward compatibility and ease of use
191
+ def get_file_output_manager(project_root: Optional[str] = None) -> FileOutputManager:
192
+ """
193
+ Convenience function to get a FileOutputManager instance.
194
+
195
+ This function provides a simple interface to the factory while maintaining
196
+ the singleton behavior per project root.
197
+
198
+ Args:
199
+ project_root: Project root directory. If None, uses current working directory.
200
+
201
+ Returns:
202
+ FileOutputManager instance for the specified project root
203
+ """
204
+ return FileOutputManagerFactory.get_instance(project_root)
@@ -4,12 +4,15 @@ File Output Manager for MCP Tools
4
4
 
5
5
  This module provides functionality to save analysis results to files with
6
6
  appropriate extensions based on content type, with security validation.
7
+
8
+ Enhanced with Managed Singleton Factory Pattern support for consistent
9
+ instance management across MCP tools.
7
10
  """
8
11
 
9
12
  import json
10
13
  import os
11
14
  from pathlib import Path
12
- from typing import Any
15
+ from typing import Any, Optional
13
16
 
14
17
  from ...utils import setup_logger
15
18
 
@@ -21,9 +24,12 @@ class FileOutputManager:
21
24
  """
22
25
  Manages file output for analysis results with automatic extension detection
23
26
  and security validation.
27
+
28
+ Enhanced with factory method support for consistent instance management
29
+ across MCP tools while maintaining full backward compatibility.
24
30
  """
25
31
 
26
- def __init__(self, project_root: str | None = None):
32
+ def __init__(self, project_root: Optional[str] = None):
27
33
  """
28
34
  Initialize the file output manager.
29
35
 
@@ -33,6 +39,50 @@ class FileOutputManager:
33
39
  self.project_root = project_root
34
40
  self._output_path = None
35
41
  self._initialize_output_path()
42
+
43
+ @classmethod
44
+ def get_managed_instance(cls, project_root: Optional[str] = None) -> 'FileOutputManager':
45
+ """
46
+ Get a managed FileOutputManager instance using the factory pattern.
47
+
48
+ This method provides access to the Managed Singleton Factory Pattern,
49
+ ensuring one instance per project root for optimal resource usage
50
+ and consistency across MCP tools.
51
+
52
+ Args:
53
+ project_root: Project root directory. If None, uses current working directory.
54
+
55
+ Returns:
56
+ FileOutputManager instance managed by the factory
57
+
58
+ Note:
59
+ This method requires the factory module to be available. If the factory
60
+ is not available, it falls back to creating a new instance directly.
61
+ """
62
+ try:
63
+ # Import here to avoid circular imports
64
+ from .file_output_factory import FileOutputManagerFactory
65
+ return FileOutputManagerFactory.get_instance(project_root)
66
+ except ImportError as e:
67
+ logger.warning(f"Factory not available, creating new instance directly: {e}")
68
+ return cls(project_root)
69
+
70
+ @classmethod
71
+ def create_instance(cls, project_root: Optional[str] = None) -> 'FileOutputManager':
72
+ """
73
+ Create a new FileOutputManager instance directly (bypass factory).
74
+
75
+ This method creates a new instance without using the factory pattern.
76
+ Use this when you specifically need a separate instance that won't
77
+ be managed by the factory.
78
+
79
+ Args:
80
+ project_root: Project root directory. If None, uses current working directory.
81
+
82
+ Returns:
83
+ New FileOutputManager instance
84
+ """
85
+ return cls(project_root)
36
86
 
37
87
  def _initialize_output_path(self) -> None:
38
88
  """Initialize the output path from environment variables or project root."""
@@ -150,6 +150,59 @@ class Package(CodeElement):
150
150
  element_type: str = "package"
151
151
 
152
152
 
153
+ # ========================================
154
+ # HTML/CSS-Specific Models
155
+ # ========================================
156
+
157
+
158
+ @dataclass(frozen=False)
159
+ class MarkupElement(CodeElement):
160
+ """
161
+ HTML要素を表現するデータモデル。
162
+ CodeElementを継承し、マークアップ固有の属性を追加する。
163
+ """
164
+
165
+ tag_name: str = ""
166
+ attributes: dict[str, str] = field(default_factory=dict)
167
+ parent: "MarkupElement | None" = None
168
+ children: list["MarkupElement"] = field(default_factory=list)
169
+ element_class: str = "" # 分類システムのカテゴリ (例: 'structure', 'media', 'form')
170
+ element_type: str = "html_element"
171
+
172
+ def to_summary_item(self) -> dict[str, Any]:
173
+ """Return dictionary for summary item"""
174
+ return {
175
+ "name": self.name,
176
+ "tag_name": self.tag_name,
177
+ "type": "html_element",
178
+ "element_class": self.element_class,
179
+ "lines": {"start": self.start_line, "end": self.end_line},
180
+ }
181
+
182
+
183
+ @dataclass(frozen=False)
184
+ class StyleElement(CodeElement):
185
+ """
186
+ CSSルールを表現するデータモデル。
187
+ CodeElementを継承する。
188
+ """
189
+
190
+ selector: str = ""
191
+ properties: dict[str, str] = field(default_factory=dict)
192
+ element_class: str = "" # 分類システムのカテゴリ (例: 'layout', 'typography', 'color')
193
+ element_type: str = "css_rule"
194
+
195
+ def to_summary_item(self) -> dict[str, Any]:
196
+ """Return dictionary for summary item"""
197
+ return {
198
+ "name": self.name,
199
+ "selector": self.selector,
200
+ "type": "css_rule",
201
+ "element_class": self.element_class,
202
+ "lines": {"start": self.start_line, "end": self.end_line},
203
+ }
204
+
205
+
153
206
  # ========================================
154
207
  # Java-Specific Models
155
208
  # ========================================
@@ -28,7 +28,7 @@ class OutputManager:
28
28
  """Output warning message"""
29
29
  if not self.quiet:
30
30
  print(f"WARNING: {message}", file=sys.stderr)
31
- log_warning(message)
31
+ log_warning(message)
32
32
 
33
33
  def error(self, message: str) -> None:
34
34
  """Output error message"""
@@ -178,6 +178,56 @@ class LanguagePlugin(ABC):
178
178
  """
179
179
  pass
180
180
 
181
+ def get_supported_element_types(self) -> list[str]:
182
+ """
183
+ Return list of supported CodeElement types.
184
+
185
+ Returns:
186
+ List of element types (e.g., ["function", "class", "variable"])
187
+ """
188
+ return ["function", "class", "variable", "import"]
189
+
190
+ def get_queries(self) -> dict[str, str]:
191
+ """
192
+ Return language-specific tree-sitter queries.
193
+
194
+ Returns:
195
+ Dictionary mapping query names to query strings
196
+ """
197
+ return {}
198
+
199
+ def execute_query_strategy(self, query_key: str | None, language: str) -> str | None:
200
+ """
201
+ Execute query strategy for this language plugin.
202
+
203
+ Args:
204
+ query_key: Query key to execute
205
+ language: Programming language
206
+
207
+ Returns:
208
+ Query string or None if not supported
209
+ """
210
+ queries = self.get_queries()
211
+ return queries.get(query_key) if query_key else None
212
+
213
+ def get_formatter_map(self) -> dict[str, str]:
214
+ """
215
+ Return mapping of format types to formatter class names.
216
+
217
+ Returns:
218
+ Dictionary mapping format names to formatter classes
219
+ """
220
+ return {}
221
+
222
+ def get_element_categories(self) -> dict[str, list[str]]:
223
+ """
224
+ Return element categories for HTML/CSS languages.
225
+
226
+ Returns:
227
+ Dictionary mapping category names to element lists
228
+ """
229
+ return {}
230
+
181
231
  def is_applicable(self, file_path: str) -> bool:
182
232
  """
183
233
  Check if this plugin is applicable for the given file.
@@ -64,7 +64,11 @@ class PluginManager:
64
64
  log_debug(f"Skipping duplicate plugin for language: {language}")
65
65
 
66
66
  final_plugins = list(unique_plugins.values())
67
- log_info(f"Successfully loaded {len(final_plugins)} plugins")
67
+ # Only log if not in CLI mode (check if we're in quiet mode)
68
+ import os
69
+ log_level = os.environ.get("LOG_LEVEL", "WARNING")
70
+ if log_level != "ERROR":
71
+ log_info(f"Successfully loaded {len(final_plugins)} plugins")
68
72
  return final_plugins
69
73
 
70
74
  def _load_from_entry_points(self) -> list[LanguagePlugin]: