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.
- tree_sitter_analyzer/__init__.py +1 -1
- tree_sitter_analyzer/api.py +23 -30
- tree_sitter_analyzer/cli/argument_validator.py +77 -0
- tree_sitter_analyzer/cli/commands/table_command.py +7 -2
- tree_sitter_analyzer/cli_main.py +17 -3
- tree_sitter_analyzer/core/cache_service.py +15 -5
- tree_sitter_analyzer/core/query.py +33 -22
- tree_sitter_analyzer/core/query_service.py +179 -154
- tree_sitter_analyzer/formatters/formatter_registry.py +355 -0
- tree_sitter_analyzer/formatters/html_formatter.py +462 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +3 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +1 -1
- tree_sitter_analyzer/language_detector.py +80 -7
- tree_sitter_analyzer/languages/css_plugin.py +390 -0
- tree_sitter_analyzer/languages/html_plugin.py +395 -0
- tree_sitter_analyzer/languages/java_plugin.py +116 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +113 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +266 -46
- tree_sitter_analyzer/languages/python_plugin.py +176 -33
- tree_sitter_analyzer/languages/typescript_plugin.py +130 -1
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +12 -1
- tree_sitter_analyzer/mcp/tools/query_tool.py +101 -60
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +12 -1
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +26 -12
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +204 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +52 -2
- tree_sitter_analyzer/models.py +53 -0
- tree_sitter_analyzer/output_manager.py +1 -1
- tree_sitter_analyzer/plugins/base.py +50 -0
- tree_sitter_analyzer/plugins/manager.py +5 -1
- tree_sitter_analyzer/queries/css.py +634 -0
- tree_sitter_analyzer/queries/html.py +556 -0
- tree_sitter_analyzer/queries/markdown.py +54 -164
- tree_sitter_analyzer/query_loader.py +16 -3
- tree_sitter_analyzer/security/validator.py +182 -44
- tree_sitter_analyzer/utils/__init__.py +113 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +282 -0
- tree_sitter_analyzer/utils.py +62 -24
- {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/METADATA +135 -31
- {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/RECORD +42 -32
- {tree_sitter_analyzer-1.7.7.dist-info → tree_sitter_analyzer-1.8.3.dist-info}/entry_points.txt +2 -0
- {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
|
|
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."""
|
tree_sitter_analyzer/models.py
CHANGED
|
@@ -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
|
# ========================================
|
|
@@ -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
|
-
|
|
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]:
|