tree-sitter-analyzer 0.9.5__py3-none-any.whl → 0.9.6__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/cli/commands/query_command.py +50 -35
- tree_sitter_analyzer/cli_main.py +21 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +162 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +1 -2
- tree_sitter_analyzer/mcp/server.py +7 -1
- tree_sitter_analyzer/mcp/tools/query_tool.py +238 -0
- tree_sitter_analyzer/queries/java.py +5 -0
- {tree_sitter_analyzer-0.9.5.dist-info → tree_sitter_analyzer-0.9.6.dist-info}/METADATA +77 -13
- {tree_sitter_analyzer-0.9.5.dist-info → tree_sitter_analyzer-0.9.6.dist-info}/RECORD +13 -10
- {tree_sitter_analyzer-0.9.5.dist-info → tree_sitter_analyzer-0.9.6.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-0.9.5.dist-info → tree_sitter_analyzer-0.9.6.dist-info}/entry_points.txt +0 -0
tree_sitter_analyzer/__init__.py
CHANGED
|
@@ -5,14 +5,50 @@ Query Command
|
|
|
5
5
|
Handles query execution functionality.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from ...core.query_service import QueryService
|
|
8
9
|
from ...output_manager import output_data, output_error, output_info, output_json
|
|
9
|
-
from ...query_loader import query_loader
|
|
10
10
|
from .base_command import BaseCommand
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class QueryCommand(BaseCommand):
|
|
14
14
|
"""Command for executing queries."""
|
|
15
15
|
|
|
16
|
+
def __init__(self, args):
|
|
17
|
+
"""Initialize the query command with QueryService."""
|
|
18
|
+
super().__init__(args)
|
|
19
|
+
self.query_service = QueryService()
|
|
20
|
+
|
|
21
|
+
async def execute_query(
|
|
22
|
+
self, language: str, query: str, query_name: str = "custom"
|
|
23
|
+
) -> list[dict] | None:
|
|
24
|
+
"""Execute a specific tree-sitter query using QueryService."""
|
|
25
|
+
try:
|
|
26
|
+
# Get filter expression if provided
|
|
27
|
+
filter_expression = getattr(self.args, "filter", None)
|
|
28
|
+
|
|
29
|
+
if query_name != "custom":
|
|
30
|
+
# Use predefined query key
|
|
31
|
+
results = await self.query_service.execute_query(
|
|
32
|
+
self.args.file_path,
|
|
33
|
+
language,
|
|
34
|
+
query_key=query_name,
|
|
35
|
+
filter_expression=filter_expression,
|
|
36
|
+
)
|
|
37
|
+
else:
|
|
38
|
+
# Use custom query string
|
|
39
|
+
results = await self.query_service.execute_query(
|
|
40
|
+
self.args.file_path,
|
|
41
|
+
language,
|
|
42
|
+
query_string=query,
|
|
43
|
+
filter_expression=filter_expression,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return results
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
output_error(f"Query execution failed: {e}")
|
|
50
|
+
return None
|
|
51
|
+
|
|
16
52
|
async def execute_async(self, language: str) -> int:
|
|
17
53
|
# Get the query to execute
|
|
18
54
|
query_to_execute = None
|
|
@@ -22,16 +58,16 @@ class QueryCommand(BaseCommand):
|
|
|
22
58
|
sanitized_query_key = self.security_validator.sanitize_input(
|
|
23
59
|
self.args.query_key, max_length=100
|
|
24
60
|
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return 1
|
|
32
|
-
except ValueError as e:
|
|
33
|
-
output_error(f"{e}")
|
|
61
|
+
# Check if query exists
|
|
62
|
+
available_queries = self.query_service.get_available_queries(language)
|
|
63
|
+
if sanitized_query_key not in available_queries:
|
|
64
|
+
output_error(
|
|
65
|
+
f"Query '{sanitized_query_key}' not found for language '{language}'"
|
|
66
|
+
)
|
|
34
67
|
return 1
|
|
68
|
+
# Store query name - QueryService will resolve the query string
|
|
69
|
+
query_to_execute = sanitized_query_key # This is actually the query key now
|
|
70
|
+
query_name = sanitized_query_key
|
|
35
71
|
elif hasattr(self.args, "query_string") and self.args.query_string:
|
|
36
72
|
# Security check for query string (potential regex patterns)
|
|
37
73
|
is_safe, error_msg = self.security_validator.regex_checker.validate_pattern(
|
|
@@ -41,38 +77,17 @@ class QueryCommand(BaseCommand):
|
|
|
41
77
|
output_error(f"Unsafe query pattern: {error_msg}")
|
|
42
78
|
return 1
|
|
43
79
|
query_to_execute = self.args.query_string
|
|
80
|
+
query_name = "custom"
|
|
44
81
|
|
|
45
82
|
if not query_to_execute:
|
|
46
83
|
output_error("No query specified.")
|
|
47
84
|
return 1
|
|
48
85
|
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
if
|
|
86
|
+
# Execute specific query
|
|
87
|
+
results = await self.execute_query(language, query_to_execute, query_name)
|
|
88
|
+
if results is None:
|
|
52
89
|
return 1
|
|
53
90
|
|
|
54
|
-
# Process query results
|
|
55
|
-
results = []
|
|
56
|
-
if hasattr(analysis_result, "query_results") and analysis_result.query_results:
|
|
57
|
-
results = analysis_result.query_results.get("captures", [])
|
|
58
|
-
else:
|
|
59
|
-
# Create basic results from elements
|
|
60
|
-
if hasattr(analysis_result, "elements") and analysis_result.elements:
|
|
61
|
-
for element in analysis_result.elements:
|
|
62
|
-
results.append(
|
|
63
|
-
{
|
|
64
|
-
"capture_name": getattr(
|
|
65
|
-
element, "__class__", type(element)
|
|
66
|
-
).__name__.lower(),
|
|
67
|
-
"node_type": getattr(
|
|
68
|
-
element, "__class__", type(element)
|
|
69
|
-
).__name__,
|
|
70
|
-
"start_line": getattr(element, "start_line", 0),
|
|
71
|
-
"end_line": getattr(element, "end_line", 0),
|
|
72
|
-
"content": getattr(element, "name", str(element)),
|
|
73
|
-
}
|
|
74
|
-
)
|
|
75
|
-
|
|
76
91
|
# Output results
|
|
77
92
|
if results:
|
|
78
93
|
if self.args.output_format == "json":
|
tree_sitter_analyzer/cli_main.py
CHANGED
|
@@ -46,6 +46,13 @@ class CLICommandFactory:
|
|
|
46
46
|
if args.show_supported_extensions:
|
|
47
47
|
return ShowExtensionsCommand(args)
|
|
48
48
|
|
|
49
|
+
if args.filter_help:
|
|
50
|
+
from tree_sitter_analyzer.core.query_filter import QueryFilter
|
|
51
|
+
|
|
52
|
+
filter_service = QueryFilter()
|
|
53
|
+
output_info(filter_service.get_filter_help())
|
|
54
|
+
return None # This will exit with code 0
|
|
55
|
+
|
|
49
56
|
# File analysis commands (require file path)
|
|
50
57
|
if not args.file_path:
|
|
51
58
|
return None
|
|
@@ -95,12 +102,23 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
95
102
|
"--query-string", help="Directly specify Tree-sitter query to execute"
|
|
96
103
|
)
|
|
97
104
|
|
|
105
|
+
# Query filter options
|
|
106
|
+
parser.add_argument(
|
|
107
|
+
"--filter",
|
|
108
|
+
help="Filter query results (e.g., 'name=main', 'name=~get*,public=true')",
|
|
109
|
+
)
|
|
110
|
+
|
|
98
111
|
# Information options
|
|
99
112
|
parser.add_argument(
|
|
100
113
|
"--list-queries",
|
|
101
114
|
action="store_true",
|
|
102
115
|
help="Display list of available query keys",
|
|
103
116
|
)
|
|
117
|
+
parser.add_argument(
|
|
118
|
+
"--filter-help",
|
|
119
|
+
action="store_true",
|
|
120
|
+
help="Display help for query filter syntax",
|
|
121
|
+
)
|
|
104
122
|
parser.add_argument(
|
|
105
123
|
"--describe-query", help="Display description of specified query key"
|
|
106
124
|
)
|
|
@@ -287,6 +305,9 @@ def main() -> None:
|
|
|
287
305
|
if command:
|
|
288
306
|
exit_code = command.execute()
|
|
289
307
|
sys.exit(exit_code)
|
|
308
|
+
elif command is None and hasattr(args, "filter_help") and args.filter_help:
|
|
309
|
+
# filter_help was processed successfully
|
|
310
|
+
sys.exit(0)
|
|
290
311
|
else:
|
|
291
312
|
if not args.file_path:
|
|
292
313
|
output_error("File path not specified.")
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Query Filter Service
|
|
4
|
+
|
|
5
|
+
Provides post-processing filtering for query results, supporting filtering by name, parameters, and other conditions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class QueryFilter:
|
|
13
|
+
"""Query result filter"""
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def filter_results(
|
|
19
|
+
self, results: list[dict[str, Any]], filter_expression: str
|
|
20
|
+
) -> list[dict[str, Any]]:
|
|
21
|
+
"""
|
|
22
|
+
Filter query results based on filter expression
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
results: Original query results
|
|
26
|
+
filter_expression: Filter expression supporting multiple formats:
|
|
27
|
+
- "name=main" - Exact name match
|
|
28
|
+
- "name~auth*" - Pattern name match
|
|
29
|
+
- "params=0" - Filter by parameter count
|
|
30
|
+
- "static=true" - Filter by modifier
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Filtered results list
|
|
34
|
+
"""
|
|
35
|
+
if not filter_expression:
|
|
36
|
+
return results
|
|
37
|
+
|
|
38
|
+
# Parse filter expression
|
|
39
|
+
filters = self._parse_filter_expression(filter_expression)
|
|
40
|
+
|
|
41
|
+
filtered_results = []
|
|
42
|
+
for result in results:
|
|
43
|
+
if self._matches_filters(result, filters):
|
|
44
|
+
filtered_results.append(result)
|
|
45
|
+
|
|
46
|
+
return filtered_results
|
|
47
|
+
|
|
48
|
+
def _parse_filter_expression(self, expression: str) -> dict[str, Any]:
|
|
49
|
+
"""Parse filter expression"""
|
|
50
|
+
filters = {}
|
|
51
|
+
|
|
52
|
+
# Support multiple conditions separated by commas
|
|
53
|
+
conditions = expression.split(",")
|
|
54
|
+
|
|
55
|
+
for condition in conditions:
|
|
56
|
+
condition = condition.strip()
|
|
57
|
+
|
|
58
|
+
if "=" in condition:
|
|
59
|
+
key, value = condition.split("=", 1)
|
|
60
|
+
key = key.strip()
|
|
61
|
+
value = value.strip()
|
|
62
|
+
|
|
63
|
+
# Handle pattern matching
|
|
64
|
+
if value.startswith("~"):
|
|
65
|
+
filters[key] = {"type": "pattern", "value": value[1:]}
|
|
66
|
+
else:
|
|
67
|
+
filters[key] = {"type": "exact", "value": value}
|
|
68
|
+
|
|
69
|
+
return filters
|
|
70
|
+
|
|
71
|
+
def _matches_filters(self, result: dict[str, Any], filters: dict[str, Any]) -> bool:
|
|
72
|
+
"""Check if result matches all filter conditions"""
|
|
73
|
+
for filter_key, filter_config in filters.items():
|
|
74
|
+
if not self._matches_single_filter(result, filter_key, filter_config):
|
|
75
|
+
return False
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
def _matches_single_filter(
|
|
79
|
+
self, result: dict[str, Any], filter_key: str, filter_config: dict[str, Any]
|
|
80
|
+
) -> bool:
|
|
81
|
+
"""Check single filter condition"""
|
|
82
|
+
filter_type = filter_config["type"]
|
|
83
|
+
filter_value = filter_config["value"]
|
|
84
|
+
|
|
85
|
+
if filter_key == "name":
|
|
86
|
+
return self._match_name(result, filter_type, filter_value)
|
|
87
|
+
elif filter_key == "params":
|
|
88
|
+
return self._match_params(result, filter_type, filter_value)
|
|
89
|
+
elif filter_key == "static":
|
|
90
|
+
return self._match_modifier(result, "static", filter_value)
|
|
91
|
+
elif filter_key == "public":
|
|
92
|
+
return self._match_modifier(result, "public", filter_value)
|
|
93
|
+
elif filter_key == "private":
|
|
94
|
+
return self._match_modifier(result, "private", filter_value)
|
|
95
|
+
elif filter_key == "protected":
|
|
96
|
+
return self._match_modifier(result, "protected", filter_value)
|
|
97
|
+
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
def _match_name(self, result: dict[str, Any], match_type: str, value: str) -> bool:
|
|
101
|
+
"""Match method name"""
|
|
102
|
+
content = result.get("content", "")
|
|
103
|
+
|
|
104
|
+
# Extract method name
|
|
105
|
+
method_name = self._extract_method_name(content)
|
|
106
|
+
|
|
107
|
+
if match_type == "exact":
|
|
108
|
+
return method_name == value
|
|
109
|
+
elif match_type == "pattern":
|
|
110
|
+
# Support wildcard patterns
|
|
111
|
+
pattern = value.replace("*", ".*")
|
|
112
|
+
return re.match(pattern, method_name, re.IGNORECASE) is not None
|
|
113
|
+
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def _match_params(
|
|
117
|
+
self, result: dict[str, Any], match_type: str, value: str
|
|
118
|
+
) -> bool:
|
|
119
|
+
"""Match parameter count"""
|
|
120
|
+
content = result.get("content", "")
|
|
121
|
+
param_count = self._count_parameters(content)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
target_count = int(value)
|
|
125
|
+
return param_count == target_count
|
|
126
|
+
except ValueError:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
def _match_modifier(
|
|
130
|
+
self, result: dict[str, Any], modifier: str, value: str
|
|
131
|
+
) -> bool:
|
|
132
|
+
"""Match modifier"""
|
|
133
|
+
content = result.get("content", "")
|
|
134
|
+
has_modifier = modifier in content
|
|
135
|
+
|
|
136
|
+
return (value.lower() == "true") == has_modifier
|
|
137
|
+
|
|
138
|
+
def _extract_method_name(self, content: str) -> str:
|
|
139
|
+
"""Extract method name from content"""
|
|
140
|
+
# Match method declaration patterns
|
|
141
|
+
patterns = [
|
|
142
|
+
r"(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(", # Java method
|
|
143
|
+
r"def\s+(\w+)\s*\(", # Python method
|
|
144
|
+
r"function\s+(\w+)\s*\(", # JavaScript function
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
for pattern in patterns:
|
|
148
|
+
match = re.search(pattern, content)
|
|
149
|
+
if match:
|
|
150
|
+
return match.group(1)
|
|
151
|
+
|
|
152
|
+
return "unknown"
|
|
153
|
+
|
|
154
|
+
def _count_parameters(self, content: str) -> int:
|
|
155
|
+
"""Count method parameters"""
|
|
156
|
+
# Find parameter list
|
|
157
|
+
match = re.search(r"\(([^)]*)\)", content)
|
|
158
|
+
if not match:
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
params_str = match.group(1).strip()
|
|
162
|
+
if not params_str:
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
# Simple parameter counting (by comma separation)
|
|
166
|
+
# Note: This is a simple implementation, doesn't handle generics etc.
|
|
167
|
+
params = [p.strip() for p in params_str.split(",") if p.strip()]
|
|
168
|
+
return len(params)
|
|
169
|
+
|
|
170
|
+
def get_filter_help(self) -> str:
|
|
171
|
+
"""Get filter help information"""
|
|
172
|
+
return """
|
|
173
|
+
Filter Syntax Help:
|
|
174
|
+
|
|
175
|
+
Basic Syntax:
|
|
176
|
+
--filter "key=value" # Exact match
|
|
177
|
+
--filter "key=~pattern" # Pattern match (supports wildcard *)
|
|
178
|
+
--filter "key1=value1,key2=value2" # Multiple conditions (AND logic)
|
|
179
|
+
|
|
180
|
+
Supported filter keys:
|
|
181
|
+
name - Method/function name
|
|
182
|
+
e.g.: name=main, name=~auth*, name=~get*
|
|
183
|
+
|
|
184
|
+
params - Number of parameters
|
|
185
|
+
e.g.: params=0, params=2
|
|
186
|
+
|
|
187
|
+
static - Whether it is a static method
|
|
188
|
+
e.g.: static=true, static=false
|
|
189
|
+
|
|
190
|
+
public - Whether it is a public method
|
|
191
|
+
e.g.: public=true, public=false
|
|
192
|
+
|
|
193
|
+
private - Whether it is a private method
|
|
194
|
+
e.g.: private=true, private=false
|
|
195
|
+
|
|
196
|
+
Examples:
|
|
197
|
+
--query-key methods --filter "name=main"
|
|
198
|
+
--query-key methods --filter "name=~get*,public=true"
|
|
199
|
+
--query-key methods --filter "params=0,static=true"
|
|
200
|
+
"""
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Query Service
|
|
4
|
+
|
|
5
|
+
Unified query service for both CLI and MCP interfaces to avoid code duplication.
|
|
6
|
+
Provides core tree-sitter query functionality including predefined and custom queries.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ..encoding_utils import read_file_safe
|
|
13
|
+
from ..query_loader import query_loader
|
|
14
|
+
from .parser import Parser
|
|
15
|
+
from .query_filter import QueryFilter
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QueryService:
|
|
21
|
+
"""Unified query service providing tree-sitter query functionality"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, project_root: str | None = None) -> None:
|
|
24
|
+
"""Initialize the query service"""
|
|
25
|
+
self.project_root = project_root
|
|
26
|
+
self.parser = Parser()
|
|
27
|
+
self.filter = QueryFilter()
|
|
28
|
+
|
|
29
|
+
async def execute_query(
|
|
30
|
+
self,
|
|
31
|
+
file_path: str,
|
|
32
|
+
language: str,
|
|
33
|
+
query_key: str | None = None,
|
|
34
|
+
query_string: str | None = None,
|
|
35
|
+
filter_expression: str | None = None,
|
|
36
|
+
) -> list[dict[str, Any]] | None:
|
|
37
|
+
"""
|
|
38
|
+
Execute a query
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
file_path: Path to the file to analyze
|
|
42
|
+
language: Programming language
|
|
43
|
+
query_key: Predefined query key (e.g., 'methods', 'class')
|
|
44
|
+
query_string: Custom query string (e.g., '(method_declaration) @method')
|
|
45
|
+
filter_expression: Filter expression (e.g., 'name=main', 'name=~get*,public=true')
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of query results, each containing capture_name, node_type, start_line, end_line, content
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If neither query_key nor query_string is provided
|
|
52
|
+
FileNotFoundError: If file doesn't exist
|
|
53
|
+
Exception: If query execution fails
|
|
54
|
+
"""
|
|
55
|
+
if not query_key and not query_string:
|
|
56
|
+
raise ValueError("Must provide either query_key or query_string")
|
|
57
|
+
|
|
58
|
+
if query_key and query_string:
|
|
59
|
+
raise ValueError("Cannot provide both query_key and query_string")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
# Read file content
|
|
63
|
+
content, encoding = read_file_safe(file_path)
|
|
64
|
+
|
|
65
|
+
# Parse file
|
|
66
|
+
parse_result = self.parser.parse_code(content, language, file_path)
|
|
67
|
+
if not parse_result or not parse_result.tree:
|
|
68
|
+
raise Exception("Failed to parse file")
|
|
69
|
+
|
|
70
|
+
tree = parse_result.tree
|
|
71
|
+
language_obj = tree.language if hasattr(tree, "language") else None
|
|
72
|
+
if not language_obj:
|
|
73
|
+
raise Exception(f"Language object not available for {language}")
|
|
74
|
+
|
|
75
|
+
# Get query string
|
|
76
|
+
if query_key:
|
|
77
|
+
query_string = query_loader.get_query(language, query_key)
|
|
78
|
+
if not query_string:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"Query '{query_key}' not found for language '{language}'"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Execute tree-sitter query
|
|
84
|
+
ts_query = language_obj.query(query_string)
|
|
85
|
+
captures = ts_query.captures(tree.root_node)
|
|
86
|
+
|
|
87
|
+
# Process capture results
|
|
88
|
+
results = []
|
|
89
|
+
if isinstance(captures, dict):
|
|
90
|
+
# New tree-sitter API returns dictionary
|
|
91
|
+
for capture_name, nodes in captures.items():
|
|
92
|
+
for node in nodes:
|
|
93
|
+
results.append(self._create_result_dict(node, capture_name))
|
|
94
|
+
else:
|
|
95
|
+
# Old tree-sitter API returns list of tuples
|
|
96
|
+
for capture in captures:
|
|
97
|
+
if isinstance(capture, tuple) and len(capture) == 2:
|
|
98
|
+
node, name = capture
|
|
99
|
+
results.append(self._create_result_dict(node, name))
|
|
100
|
+
|
|
101
|
+
# Apply filters
|
|
102
|
+
if filter_expression and results:
|
|
103
|
+
results = self.filter.filter_results(results, filter_expression)
|
|
104
|
+
|
|
105
|
+
return results
|
|
106
|
+
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.error(f"Query execution failed: {e}")
|
|
109
|
+
raise
|
|
110
|
+
|
|
111
|
+
def _create_result_dict(self, node: Any, capture_name: str) -> dict[str, Any]:
|
|
112
|
+
"""
|
|
113
|
+
Create result dictionary from tree-sitter node
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
node: tree-sitter node
|
|
117
|
+
capture_name: capture name
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Result dictionary
|
|
121
|
+
"""
|
|
122
|
+
return {
|
|
123
|
+
"capture_name": capture_name,
|
|
124
|
+
"node_type": node.type if hasattr(node, "type") else "unknown",
|
|
125
|
+
"start_line": (
|
|
126
|
+
node.start_point[0] + 1 if hasattr(node, "start_point") else 0
|
|
127
|
+
),
|
|
128
|
+
"end_line": node.end_point[0] + 1 if hasattr(node, "end_point") else 0,
|
|
129
|
+
"content": (
|
|
130
|
+
node.text.decode("utf-8", errors="replace")
|
|
131
|
+
if hasattr(node, "text") and node.text
|
|
132
|
+
else ""
|
|
133
|
+
),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def get_available_queries(self, language: str) -> list[str]:
|
|
137
|
+
"""
|
|
138
|
+
Get available query keys for specified language
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
language: Programming language
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List of available query keys
|
|
145
|
+
"""
|
|
146
|
+
return query_loader.list_queries(language)
|
|
147
|
+
|
|
148
|
+
def get_query_description(self, language: str, query_key: str) -> str | None:
|
|
149
|
+
"""
|
|
150
|
+
Get description for query key
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
language: Programming language
|
|
154
|
+
query_key: Query key
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Query description, or None if not found
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
return query_loader.get_query_description(language, query_key)
|
|
161
|
+
except Exception:
|
|
162
|
+
return None
|
|
@@ -135,9 +135,8 @@ class CodeFileResource:
|
|
|
135
135
|
raise ValueError("File path contains null bytes")
|
|
136
136
|
|
|
137
137
|
# Check for potentially dangerous path traversal
|
|
138
|
-
# normalized_path = Path(file_path).resolve() # Not used currently
|
|
139
138
|
if ".." in file_path:
|
|
140
|
-
|
|
139
|
+
raise ValueError(f"Path traversal not allowed: {file_path}")
|
|
141
140
|
|
|
142
141
|
# Additional security checks could be added here
|
|
143
142
|
# For example, restricting to certain directories
|
|
@@ -51,6 +51,7 @@ from ..utils import setup_logger
|
|
|
51
51
|
from . import MCP_INFO
|
|
52
52
|
from .resources import CodeFileResource, ProjectStatsResource
|
|
53
53
|
from .tools.base_tool import MCPTool
|
|
54
|
+
from .tools.query_tool import QueryTool
|
|
54
55
|
from .tools.read_partial_tool import ReadPartialTool
|
|
55
56
|
from .tools.table_format_tool import TableFormatTool
|
|
56
57
|
|
|
@@ -77,7 +78,8 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
77
78
|
self.security_validator = SecurityValidator(project_root)
|
|
78
79
|
# Use unified analysis engine instead of deprecated AdvancedAnalyzer
|
|
79
80
|
|
|
80
|
-
# Initialize MCP tools with security validation (
|
|
81
|
+
# Initialize MCP tools with security validation (four core tools)
|
|
82
|
+
self.query_tool = QueryTool(project_root) # query_code
|
|
81
83
|
self.read_partial_tool: MCPTool = ReadPartialTool(
|
|
82
84
|
project_root
|
|
83
85
|
) # extract_code_section
|
|
@@ -324,6 +326,7 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
324
326
|
"additionalProperties": False,
|
|
325
327
|
},
|
|
326
328
|
),
|
|
329
|
+
Tool(**self.query_tool.get_tool_definition()),
|
|
327
330
|
]
|
|
328
331
|
|
|
329
332
|
logger.info(f"Returning {len(tools)} tools: {[t.name for t in tools]}")
|
|
@@ -406,6 +409,9 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
406
409
|
self.set_project_path(project_path)
|
|
407
410
|
result = {"status": "success", "project_root": project_path}
|
|
408
411
|
|
|
412
|
+
elif name == "query_code":
|
|
413
|
+
result = await self.query_tool.execute(arguments)
|
|
414
|
+
|
|
409
415
|
else:
|
|
410
416
|
raise ValueError(f"Unknown tool: {name}")
|
|
411
417
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Query Tool for MCP
|
|
4
|
+
|
|
5
|
+
MCP tool providing tree-sitter query functionality using unified QueryService.
|
|
6
|
+
Supports both predefined query keys and custom query strings.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ...core.query_service import QueryService
|
|
13
|
+
from ...language_detector import detect_language_from_file
|
|
14
|
+
from ...security import SecurityValidator
|
|
15
|
+
from ..utils.error_handler import handle_mcp_errors
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class QueryTool:
|
|
21
|
+
"""MCP query tool providing tree-sitter query functionality"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, project_root: str | None = None) -> None:
|
|
24
|
+
"""Initialize query tool"""
|
|
25
|
+
self.project_root = project_root
|
|
26
|
+
self.query_service = QueryService(project_root)
|
|
27
|
+
self.security_validator = SecurityValidator(project_root)
|
|
28
|
+
|
|
29
|
+
def get_tool_definition(self) -> dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Get MCP tool definition
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Tool definition dictionary
|
|
35
|
+
"""
|
|
36
|
+
return {
|
|
37
|
+
"name": "query_code",
|
|
38
|
+
"description": "Execute tree-sitter queries on code files to extract specific code elements",
|
|
39
|
+
"inputSchema": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"file_path": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Path to the code file to query (relative to project root)",
|
|
45
|
+
},
|
|
46
|
+
"language": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Programming language (optional, auto-detected if not provided)",
|
|
49
|
+
},
|
|
50
|
+
"query_key": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Predefined query key (e.g., 'methods', 'class', 'functions')",
|
|
53
|
+
},
|
|
54
|
+
"query_string": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "Custom tree-sitter query string (e.g., '(method_declaration) @method')",
|
|
57
|
+
},
|
|
58
|
+
"filter": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "Filter expression to refine results (e.g., 'name=main', 'name=~get*,public=true')",
|
|
61
|
+
},
|
|
62
|
+
"output_format": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"enum": ["json", "summary"],
|
|
65
|
+
"default": "json",
|
|
66
|
+
"description": "Output format",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
"required": ["file_path"],
|
|
70
|
+
"anyOf": [
|
|
71
|
+
{"required": ["query_key"]},
|
|
72
|
+
{"required": ["query_string"]},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@handle_mcp_errors
|
|
78
|
+
async def execute(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
79
|
+
"""
|
|
80
|
+
Execute query tool
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
arguments: Tool arguments
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Query results
|
|
87
|
+
"""
|
|
88
|
+
# Validate input parameters
|
|
89
|
+
file_path = arguments.get("file_path")
|
|
90
|
+
if not file_path:
|
|
91
|
+
raise ValueError("file_path is required")
|
|
92
|
+
|
|
93
|
+
# Security validation
|
|
94
|
+
validated_path = self.security_validator.validate_file_path(file_path)
|
|
95
|
+
|
|
96
|
+
# Get query parameters
|
|
97
|
+
query_key = arguments.get("query_key")
|
|
98
|
+
query_string = arguments.get("query_string")
|
|
99
|
+
filter_expression = arguments.get("filter")
|
|
100
|
+
output_format = arguments.get("output_format", "json")
|
|
101
|
+
|
|
102
|
+
if not query_key and not query_string:
|
|
103
|
+
raise ValueError("Either query_key or query_string must be provided")
|
|
104
|
+
|
|
105
|
+
if query_key and query_string:
|
|
106
|
+
raise ValueError("Cannot provide both query_key and query_string")
|
|
107
|
+
|
|
108
|
+
# Detect language
|
|
109
|
+
language = arguments.get("language")
|
|
110
|
+
if not language:
|
|
111
|
+
language = detect_language_from_file(validated_path)
|
|
112
|
+
if not language:
|
|
113
|
+
raise ValueError(f"Could not detect language for file: {file_path}")
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
# Execute query
|
|
117
|
+
results = await self.query_service.execute_query(
|
|
118
|
+
validated_path, language, query_key, query_string, filter_expression
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
if not results:
|
|
122
|
+
return {
|
|
123
|
+
"success": True,
|
|
124
|
+
"message": "No results found matching the query",
|
|
125
|
+
"results": [],
|
|
126
|
+
"count": 0,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Format output
|
|
130
|
+
if output_format == "summary":
|
|
131
|
+
return self._format_summary(results, query_key or "custom", language)
|
|
132
|
+
else:
|
|
133
|
+
return {
|
|
134
|
+
"success": True,
|
|
135
|
+
"results": results,
|
|
136
|
+
"count": len(results),
|
|
137
|
+
"file_path": file_path,
|
|
138
|
+
"language": language,
|
|
139
|
+
"query": query_key or query_string,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"Query execution failed: {e}")
|
|
144
|
+
return {
|
|
145
|
+
"success": False,
|
|
146
|
+
"error": str(e),
|
|
147
|
+
"file_path": file_path,
|
|
148
|
+
"language": language,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
def _format_summary(
|
|
152
|
+
self, results: list[dict[str, Any]], query_type: str, language: str
|
|
153
|
+
) -> dict[str, Any]:
|
|
154
|
+
"""
|
|
155
|
+
Format summary output
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
results: Query results
|
|
159
|
+
query_type: Query type
|
|
160
|
+
language: Programming language
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Summary formatted results
|
|
164
|
+
"""
|
|
165
|
+
# Group by capture name
|
|
166
|
+
by_capture = {}
|
|
167
|
+
for result in results:
|
|
168
|
+
capture_name = result["capture_name"]
|
|
169
|
+
if capture_name not in by_capture:
|
|
170
|
+
by_capture[capture_name] = []
|
|
171
|
+
by_capture[capture_name].append(result)
|
|
172
|
+
|
|
173
|
+
# Create summary
|
|
174
|
+
summary = {
|
|
175
|
+
"success": True,
|
|
176
|
+
"query_type": query_type,
|
|
177
|
+
"language": language,
|
|
178
|
+
"total_count": len(results),
|
|
179
|
+
"captures": {},
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for capture_name, items in by_capture.items():
|
|
183
|
+
summary["captures"][capture_name] = {
|
|
184
|
+
"count": len(items),
|
|
185
|
+
"items": [
|
|
186
|
+
{
|
|
187
|
+
"name": self._extract_name_from_content(item["content"]),
|
|
188
|
+
"line_range": f"{item['start_line']}-{item['end_line']}",
|
|
189
|
+
"node_type": item["node_type"],
|
|
190
|
+
}
|
|
191
|
+
for item in items
|
|
192
|
+
],
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return summary
|
|
196
|
+
|
|
197
|
+
def _extract_name_from_content(self, content: str) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Extract name from content (simple heuristic method)
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
content: Code content
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Extracted name
|
|
206
|
+
"""
|
|
207
|
+
# Simple name extraction logic, can be improved as needed
|
|
208
|
+
lines = content.strip().split("\n")
|
|
209
|
+
if lines:
|
|
210
|
+
first_line = lines[0].strip()
|
|
211
|
+
# Extract method names, class names, etc.
|
|
212
|
+
import re
|
|
213
|
+
|
|
214
|
+
# Match common declaration patterns
|
|
215
|
+
patterns = [
|
|
216
|
+
r"(?:public|private|protected)?\s*(?:static)?\s*(?:class|interface)\s+(\w+)", # class/interface
|
|
217
|
+
r"(?:public|private|protected)?\s*(?:static)?\s*\w+\s+(\w+)\s*\(", # method
|
|
218
|
+
r"(\w+)\s*\(", # simple function call
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
for pattern in patterns:
|
|
222
|
+
match = re.search(pattern, first_line)
|
|
223
|
+
if match:
|
|
224
|
+
return match.group(1)
|
|
225
|
+
|
|
226
|
+
return "unnamed"
|
|
227
|
+
|
|
228
|
+
def get_available_queries(self, language: str) -> list[str]:
|
|
229
|
+
"""
|
|
230
|
+
Get available query keys
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
language: Programming language
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
List of available query keys
|
|
237
|
+
"""
|
|
238
|
+
return self.query_service.get_available_queries(language)
|
|
@@ -356,6 +356,11 @@ ALL_QUERIES["functions"] = {
|
|
|
356
356
|
"description": "Search all function/method declarations (alias for method)",
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
ALL_QUERIES["methods"] = {
|
|
360
|
+
"query": JAVA_QUERIES["method"],
|
|
361
|
+
"description": "Search all method declarations (alias for method)",
|
|
362
|
+
}
|
|
363
|
+
|
|
359
364
|
ALL_QUERIES["classes"] = {
|
|
360
365
|
"query": JAVA_QUERIES["class"],
|
|
361
366
|
"description": "Search all class declarations (alias for class)",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tree-sitter-analyzer
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.6
|
|
4
4
|
Summary: Extensible multi-language code analyzer framework using Tree-sitter with dynamic plugin architecture
|
|
5
5
|
Project-URL: Homepage, https://github.com/aimasteracc/tree-sitter-analyzer
|
|
6
6
|
Project-URL: Documentation, https://github.com/aimasteracc/tree-sitter-analyzer#readme
|
|
@@ -137,8 +137,8 @@ Description-Content-Type: text/markdown
|
|
|
137
137
|
|
|
138
138
|
[](https://python.org)
|
|
139
139
|
[](LICENSE)
|
|
140
|
-
[](#quality-assurance)
|
|
141
|
+
[](#quality-assurance)
|
|
142
142
|
[](#quality-assurance)
|
|
143
143
|
[](https://pypi.org/project/tree-sitter-analyzer/)
|
|
144
144
|
[](https://github.com/aimasteracc/tree-sitter-analyzer)
|
|
@@ -359,11 +359,49 @@ Parameters: {"file_path": "examples/BigService.java", "start_line": 100, "end_li
|
|
|
359
359
|
}
|
|
360
360
|
```
|
|
361
361
|
|
|
362
|
+
#### 🔍 **Step 4: Smart Query Filtering (NEW!)**
|
|
363
|
+
|
|
364
|
+
**Find specific methods:**
|
|
365
|
+
```
|
|
366
|
+
Use MCP tool query_code to precisely find code elements
|
|
367
|
+
Parameters: {"file_path": "examples/BigService.java", "query_key": "methods", "filter": "name=main"}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Find authentication-related methods:**
|
|
371
|
+
```
|
|
372
|
+
Use MCP tool query_code to find authentication methods
|
|
373
|
+
Parameters: {"file_path": "examples/BigService.java", "query_key": "methods", "filter": "name=~auth*"}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Find parameterless public methods:**
|
|
377
|
+
```
|
|
378
|
+
Use MCP tool query_code to find getter methods
|
|
379
|
+
Parameters: {"file_path": "examples/BigService.java", "query_key": "methods", "filter": "params=0,public=true"}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Return Format:**
|
|
383
|
+
```json
|
|
384
|
+
{
|
|
385
|
+
"success": true,
|
|
386
|
+
"results": [
|
|
387
|
+
{
|
|
388
|
+
"capture_name": "method",
|
|
389
|
+
"node_type": "method_declaration",
|
|
390
|
+
"start_line": 1385,
|
|
391
|
+
"end_line": 1418,
|
|
392
|
+
"content": "public static void main(String[] args) { ... }"
|
|
393
|
+
}
|
|
394
|
+
],
|
|
395
|
+
"count": 1
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
362
399
|
#### 💡 **Important Notes**
|
|
363
400
|
- **Parameter Format**: Use snake_case (`file_path`, `start_line`, `end_line`)
|
|
364
401
|
- **Path Handling**: Relative paths auto-resolve to project root
|
|
365
402
|
- **Security Protection**: Tool automatically performs project boundary checks
|
|
366
|
-
- **Workflow**: Recommended to use in order: Step 1 → 2 → 3
|
|
403
|
+
- **Workflow**: Recommended to use in order: Step 1 → 2 → 4 (Query Filtering) → 3 (Precise Extraction)
|
|
404
|
+
- **Filter Syntax**: Supports `name=value`, `name=~pattern*`, `params=number`, `static/public/private=true/false`
|
|
367
405
|
|
|
368
406
|
### 🛠️ CLI Command Examples
|
|
369
407
|
|
|
@@ -379,6 +417,22 @@ uv run python -m tree_sitter_analyzer examples/BigService.java --partial-read --
|
|
|
379
417
|
|
|
380
418
|
# Silent mode (display results only)
|
|
381
419
|
uv run python -m tree_sitter_analyzer examples/BigService.java --table=full --quiet
|
|
420
|
+
|
|
421
|
+
# 🔍 Query filtering examples (NEW!)
|
|
422
|
+
# Find specific methods
|
|
423
|
+
uv run python -m tree_sitter_analyzer examples/BigService.java --query-key methods --filter "name=main"
|
|
424
|
+
|
|
425
|
+
# Find authentication-related methods
|
|
426
|
+
uv run python -m tree_sitter_analyzer examples/BigService.java --query-key methods --filter "name=~auth*"
|
|
427
|
+
|
|
428
|
+
# Find parameterless public methods
|
|
429
|
+
uv run python -m tree_sitter_analyzer examples/BigService.java --query-key methods --filter "params=0,public=true"
|
|
430
|
+
|
|
431
|
+
# Find static methods
|
|
432
|
+
uv run python -m tree_sitter_analyzer examples/BigService.java --query-key methods --filter "static=true"
|
|
433
|
+
|
|
434
|
+
# View filter syntax help
|
|
435
|
+
uv run python -m tree_sitter_analyzer --filter-help
|
|
382
436
|
```
|
|
383
437
|
|
|
384
438
|
---
|
|
@@ -398,6 +452,15 @@ Get insights without reading complete files:
|
|
|
398
452
|
- Include position metadata
|
|
399
453
|
- Support efficient processing of large files
|
|
400
454
|
|
|
455
|
+
### 🔍 **Advanced Query Filtering**
|
|
456
|
+
Powerful code element querying and filtering system:
|
|
457
|
+
- **Exact matching**: `--filter "name=main"` Find specific methods
|
|
458
|
+
- **Pattern matching**: `--filter "name=~auth*"` Find authentication-related methods
|
|
459
|
+
- **Parameter filtering**: `--filter "params=2"` Find methods with specific parameter counts
|
|
460
|
+
- **Modifier filtering**: `--filter "static=true,public=true"` Find static public methods
|
|
461
|
+
- **Compound conditions**: `--filter "name=~get*,params=0,public=true"` Combine multiple conditions
|
|
462
|
+
- **CLI/MCP consistency**: Same filtering syntax in command line and AI assistants
|
|
463
|
+
|
|
401
464
|
### 🔗 **AI Assistant Integration**
|
|
402
465
|
Deep integration via MCP protocol:
|
|
403
466
|
- Claude Desktop
|
|
@@ -482,16 +545,16 @@ Tree-sitter Analyzer automatically detects and protects project boundaries:
|
|
|
482
545
|
## 🏆 Quality Assurance
|
|
483
546
|
|
|
484
547
|
### 📊 **Quality Metrics**
|
|
485
|
-
- **1,
|
|
486
|
-
- **74.
|
|
548
|
+
- **1,420 Tests** - 100% pass rate ✅
|
|
549
|
+
- **74.36% Code Coverage** - Industry-leading level
|
|
487
550
|
- **Zero Test Failures** - Complete CI/CD ready
|
|
488
551
|
- **Cross-platform Compatible** - Windows, macOS, Linux
|
|
489
552
|
|
|
490
|
-
### ⚡ **Latest Quality Achievements (v0.9.
|
|
491
|
-
- ✅ **
|
|
492
|
-
- ✅ **
|
|
493
|
-
- ✅ **
|
|
494
|
-
- ✅ **
|
|
553
|
+
### ⚡ **Latest Quality Achievements (v0.9.6)**
|
|
554
|
+
- ✅ **Smart Query Filtering System** - 62 new tests all passed
|
|
555
|
+
- ✅ **Unified Architecture Design** - QueryService eliminates code duplication
|
|
556
|
+
- ✅ **CI Test Fixes** - All platforms test stable
|
|
557
|
+
- ✅ **Multi-language Documentation** - Complete updates in EN/ZH/JA
|
|
495
558
|
|
|
496
559
|
### ⚙️ **Running Tests**
|
|
497
560
|
```bash
|
|
@@ -507,9 +570,10 @@ uv run pytest tests/test_mcp_server_initialization.py -v
|
|
|
507
570
|
|
|
508
571
|
### 📈 **Coverage Highlights**
|
|
509
572
|
- **Language Detector**: 98.41% (Excellent)
|
|
510
|
-
- **CLI Main Entry**:
|
|
573
|
+
- **CLI Main Entry**: 94.36% (Excellent)
|
|
574
|
+
- **Query Filtering System**: 96.06% (Excellent)
|
|
575
|
+
- **Query Service**: 86.25% (Good)
|
|
511
576
|
- **Error Handling**: 82.76% (Good)
|
|
512
|
-
- **Security Framework**: 78%+ (Reliable)
|
|
513
577
|
|
|
514
578
|
---
|
|
515
579
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
tree_sitter_analyzer/__init__.py,sha256=
|
|
1
|
+
tree_sitter_analyzer/__init__.py,sha256=JBnnkxDQchD9fes65p315c62-Pat2QTvNGRKaeumtDo,3067
|
|
2
2
|
tree_sitter_analyzer/__main__.py,sha256=Zl79tpe4UaMu-7yeztc06tgP0CVMRnvGgas4ZQP5SCs,228
|
|
3
3
|
tree_sitter_analyzer/api.py,sha256=naRtGuZ27AIVfn6Rid0zQcHDI71UpO9Nh4NQM9JyD3c,16954
|
|
4
|
-
tree_sitter_analyzer/cli_main.py,sha256=
|
|
4
|
+
tree_sitter_analyzer/cli_main.py,sha256=4uIwje6n2qQvTsX4RBgqFgznxJs8RcCnS1GQjrR5IGQ,10354
|
|
5
5
|
tree_sitter_analyzer/encoding_utils.py,sha256=NHkqOt7GdrxgMsd3k0rVxSO0mWJZXODw3uwl2Qk9ufc,14803
|
|
6
6
|
tree_sitter_analyzer/exceptions.py,sha256=AZryCQyKXekAg8lQZd3zqULnjhCKovBNNpnUlNGDhcI,11615
|
|
7
7
|
tree_sitter_analyzer/file_handler.py,sha256=nUD17QIdOJ2bnXekuo-QzGQFv0f2rxCSJi-zWeQFSAs,6636
|
|
@@ -21,7 +21,7 @@ tree_sitter_analyzer/cli/commands/advanced_command.py,sha256=xDZI4zKTMHNdf7fc_QN
|
|
|
21
21
|
tree_sitter_analyzer/cli/commands/base_command.py,sha256=o_xjKhn2J5bbGeNC980tALozT1WlsttI0owhLbV2hDM,6597
|
|
22
22
|
tree_sitter_analyzer/cli/commands/default_command.py,sha256=RAR_eaOK3EndIqU7QL5UAn44mwyhItTN7aUaKL1WmSc,524
|
|
23
23
|
tree_sitter_analyzer/cli/commands/partial_read_command.py,sha256=RGZsEBybu6PhOQDHBXuhs6l4ZlDcFaxj2bNOvSqKY80,4605
|
|
24
|
-
tree_sitter_analyzer/cli/commands/query_command.py,sha256=
|
|
24
|
+
tree_sitter_analyzer/cli/commands/query_command.py,sha256=VFuCFJxffjSUrMa7NB_KJmMexUnJmnazpTDbw-i9Ulw,4003
|
|
25
25
|
tree_sitter_analyzer/cli/commands/structure_command.py,sha256=0iJwjOgtW838hXleXogWhbu6eQFfrLR1QgKe5PFBqvU,5220
|
|
26
26
|
tree_sitter_analyzer/cli/commands/summary_command.py,sha256=02WA3sOzfT83FVT6sW7nK04zVcZ9Qj_1S0WloqlTnFk,3602
|
|
27
27
|
tree_sitter_analyzer/cli/commands/table_command.py,sha256=GvGpLZ_YwE5syufNMfvjPrzwt-bXUtAF7EoOQ0Dr_O0,9441
|
|
@@ -31,6 +31,8 @@ tree_sitter_analyzer/core/cache_service.py,sha256=0oZGuTpeHBKYNdnqAOVi6VlQukYMIH
|
|
|
31
31
|
tree_sitter_analyzer/core/engine.py,sha256=VFXGowDj6EfjFSh2MQDkQIc-4ISXaOg38n4lUhVY5Os,18721
|
|
32
32
|
tree_sitter_analyzer/core/parser.py,sha256=qT3yIlTRdod4tf_2o1hU_B-GYGukyM2BtaFxzSoxois,9293
|
|
33
33
|
tree_sitter_analyzer/core/query.py,sha256=UhQxUmQitFobe2YM7Ifhi3X2WdHVb02KzeBunueYJ4I,16397
|
|
34
|
+
tree_sitter_analyzer/core/query_filter.py,sha256=PvGztAZFooFNZe6iHNmbg6RUNtMvq6f6hBZFzllig6Y,6591
|
|
35
|
+
tree_sitter_analyzer/core/query_service.py,sha256=j9v3w2j3axhxzFEbZLovDNT2h6kF4PWbJVL_PmKl3ho,5542
|
|
34
36
|
tree_sitter_analyzer/formatters/__init__.py,sha256=yVb4HF_4EEPRwTf3y3-vM2NllrhykG3zlvQhN-6dB4c,31
|
|
35
37
|
tree_sitter_analyzer/formatters/base_formatter.py,sha256=Uv6uVgUKwbBn6of26bnvr4u6CmX2ka1a405VL17CGFU,5763
|
|
36
38
|
tree_sitter_analyzer/formatters/formatter_factory.py,sha256=mCnAbEHycoSttSuF4dU78hzcxyg-h57bo0_bj00zw58,2069
|
|
@@ -46,14 +48,15 @@ tree_sitter_analyzer/languages/java_plugin.py,sha256=ZTtDgyUdv-b9fFvCMEL3xS-yr0r
|
|
|
46
48
|
tree_sitter_analyzer/languages/javascript_plugin.py,sha256=k14kHfi5II9MRTsVuy0NQq5l2KZYncCnM1Q6T1c5q_U,15844
|
|
47
49
|
tree_sitter_analyzer/languages/python_plugin.py,sha256=MJ03F_Nv-nmInIkEFmPyEXYhyGbLHyr5kCbj2taEDYk,29144
|
|
48
50
|
tree_sitter_analyzer/mcp/__init__.py,sha256=Dj4aqt_6inDKQ1xmA6DNMNDQBHzToqBjSWazdAWZgCY,944
|
|
49
|
-
tree_sitter_analyzer/mcp/server.py,sha256=
|
|
51
|
+
tree_sitter_analyzer/mcp/server.py,sha256=93baLte6ub2rnxr5EKfeknpN1t2BWraTm6Mk4imvHVU,25068
|
|
50
52
|
tree_sitter_analyzer/mcp/resources/__init__.py,sha256=SWnK8liTQkuQXlVgyRP9mf5HzpqmqohShrC98xlNnDc,1389
|
|
51
|
-
tree_sitter_analyzer/mcp/resources/code_file_resource.py,sha256=
|
|
53
|
+
tree_sitter_analyzer/mcp/resources/code_file_resource.py,sha256=ZX5ZYSJfylBedpL80kTDlco2YZqgRMb5f3OW0VvOVRM,6166
|
|
52
54
|
tree_sitter_analyzer/mcp/resources/project_stats_resource.py,sha256=V5-daZ99SU4rOt7qLk9GmhkzdXJpEINBobNlT14ojYY,19441
|
|
53
55
|
tree_sitter_analyzer/mcp/tools/__init__.py,sha256=u7JrSLwE95y50mfjSus6HRdtdhkNbVrW8jP4AooawEU,762
|
|
54
56
|
tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py,sha256=cVBhq4_KxwcqdmA5s9iZLH4YDy7T_TKLNNdqqaGKnFU,27369
|
|
55
57
|
tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py,sha256=mssed7bEfGeGxW4mOf7dg8BDS1oqHLolIBNX9DaZ3DM,8997
|
|
56
58
|
tree_sitter_analyzer/mcp/tools/base_tool.py,sha256=FVSMgKIliQ5EBVQEfjYwWeqzWt9OqOFDr3dyACIDxig,1210
|
|
59
|
+
tree_sitter_analyzer/mcp/tools/query_tool.py,sha256=FBsxsWhoSYA0ql_IaMGSLR6vmg3M4b1rDsYZsZuVGWQ,7996
|
|
57
60
|
tree_sitter_analyzer/mcp/tools/read_partial_tool.py,sha256=MkAeXc-n-8fxRGtE3RQ_yZOPQ1lzdUppVFKrRzBPogU,11790
|
|
58
61
|
tree_sitter_analyzer/mcp/tools/table_format_tool.py,sha256=flFEcDOozJSJOtsQVHsk5eTYsutBi_T_2ft9XUpceSM,15896
|
|
59
62
|
tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py,sha256=RSbztW_b07DL5e1an4BE--snX744v4KmlRGab5bGj6U,21677
|
|
@@ -63,7 +66,7 @@ tree_sitter_analyzer/plugins/__init__.py,sha256=ITE9bTz7NO4axnn8g5Z-1_ydhSLT0RnY
|
|
|
63
66
|
tree_sitter_analyzer/plugins/base.py,sha256=FMRAOtjtDutNV8RnB6cmFgdvcjxKRAbrrzqldBBT1yk,17167
|
|
64
67
|
tree_sitter_analyzer/plugins/manager.py,sha256=PyEY3jeuCBpDVqguWhaAu7nzUZM17_pI6wml2e0Hamo,12535
|
|
65
68
|
tree_sitter_analyzer/queries/__init__.py,sha256=dwDDc7PCw_UWruxSeJ8uEBjY0O5uLDBI5YqyvBhbnN0,696
|
|
66
|
-
tree_sitter_analyzer/queries/java.py,sha256=
|
|
69
|
+
tree_sitter_analyzer/queries/java.py,sha256=avaPFeHz3Ig-yTdCDKfUKaG-5sktOEkrXG2p9_mEZVs,12388
|
|
67
70
|
tree_sitter_analyzer/queries/javascript.py,sha256=pnXrgISwDE5GhPHDbUKEGI3thyLmebTeQt-l_-x4qT8,3962
|
|
68
71
|
tree_sitter_analyzer/queries/python.py,sha256=L33KRUyV3sAvA3_HFkPyGgtiq0ygSpNY_n2YojodPlc,7570
|
|
69
72
|
tree_sitter_analyzer/queries/typescript.py,sha256=eersyAF7TladuCWa8WE_-cO9YTF1LUSjLIl-tk2fZDo,6708
|
|
@@ -71,7 +74,7 @@ tree_sitter_analyzer/security/__init__.py,sha256=ZTqTt24hsljCpTXAZpJC57L7MU5lJLT
|
|
|
71
74
|
tree_sitter_analyzer/security/boundary_manager.py,sha256=CUQWU5j1zdjEbN9UmArcYkq9HbemhttQzk0pVk-vxZs,8153
|
|
72
75
|
tree_sitter_analyzer/security/regex_checker.py,sha256=jWK6H8PTPgzbwRPfK_RZ8bBTS6rtEbgjY5vr3YWjQ_U,9616
|
|
73
76
|
tree_sitter_analyzer/security/validator.py,sha256=UPAPcrnmI2mNzbYOm0MabnJMGllK6HlOQ9KX-2bRfgU,8986
|
|
74
|
-
tree_sitter_analyzer-0.9.
|
|
75
|
-
tree_sitter_analyzer-0.9.
|
|
76
|
-
tree_sitter_analyzer-0.9.
|
|
77
|
-
tree_sitter_analyzer-0.9.
|
|
77
|
+
tree_sitter_analyzer-0.9.6.dist-info/METADATA,sha256=_bMzqpiKdK1seg16PqwJhmF2vxPGsPL3HkB48XSNhVg,22608
|
|
78
|
+
tree_sitter_analyzer-0.9.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
79
|
+
tree_sitter_analyzer-0.9.6.dist-info/entry_points.txt,sha256=U4tfLGXgCWubKm2PyEb3zxhQ2pm7zVotMyfyS0CodD8,486
|
|
80
|
+
tree_sitter_analyzer-0.9.6.dist-info/RECORD,,
|
|
File without changes
|
{tree_sitter_analyzer-0.9.5.dist-info → tree_sitter_analyzer-0.9.6.dist-info}/entry_points.txt
RENAMED
|
File without changes
|