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.

@@ -11,7 +11,7 @@ Architecture:
11
11
  - Data Models: Generic and language-specific code element representations
12
12
  """
13
13
 
14
- __version__ = "0.9.3"
14
+ __version__ = "0.9.6"
15
15
  __author__ = "aisheng.yu"
16
16
  __email__ = "aimasteracc@gmail.com"
17
17
 
@@ -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
- try:
26
- query_to_execute = query_loader.get_query(language, sanitized_query_key)
27
- if query_to_execute is None:
28
- output_error(
29
- f"Query '{sanitized_query_key}' not found for language '{language}'"
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
- # Perform analysis
50
- analysis_result = await self.analyze_file(language)
51
- if not analysis_result:
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":
@@ -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
- logger.warning(f"Potentially dangerous path traversal in: {file_path}")
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 (three core tools)
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.5
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
  [![Python Version](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://python.org)
139
139
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
140
- [![Tests](https://img.shields.io/badge/tests-1358%20passed-brightgreen.svg)](#quality-assurance)
141
- [![Coverage](https://img.shields.io/badge/coverage-74.19%25-green.svg)](#quality-assurance)
140
+ [![Tests](https://img.shields.io/badge/tests-1420%20passed-brightgreen.svg)](#quality-assurance)
141
+ [![Coverage](https://img.shields.io/badge/coverage-74.36%25-green.svg)](#quality-assurance)
142
142
  [![Quality](https://img.shields.io/badge/quality-enterprise%20grade-blue.svg)](#quality-assurance)
143
143
  [![PyPI](https://img.shields.io/pypi/v/tree-sitter-analyzer.svg)](https://pypi.org/project/tree-sitter-analyzer/)
144
144
  [![GitHub Stars](https://img.shields.io/github/stars/aimasteracc/tree-sitter-analyzer.svg?style=social)](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,358 Tests** - 100% pass rate ✅
486
- - **74.54% Code Coverage** - Industry-leading level
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.4)**
491
- - ✅ **Test Suite Completely Stable** - Fixed all historical issues
492
- - ✅ **Formatter Module Breakthrough** - Coverage significantly improved
493
- - ✅ **Error Handling Optimization** - Enterprise-grade exception handling
494
- - ✅ **100+ New Comprehensive Tests** - Covering critical modules
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**: 97.78% (Excellent)
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=ElX-1qL0OI5EuUf2XVkdqvLuWwRgTVYDGDnyeuLUBww,3067
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=R03sPnWD_39u1wxpr7nd97EHe8jlp2npViiPbfm_cHo,9644
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=vpvCKxKGOaOHiNV_7UjW7JV5tAGAAx7AB9sV2EbLEyc,3612
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=e_Enu0MoEuWISFW0XQxcU-_db5jwCQY5txfVkqTO-7U,24788
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=POhQ1xPMiGBVVm6AfOQNKM29svDLvlGLA97ZPQgVoHw,6253
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=NZTSzFADlGrm3MD0oIkOdkN_6wP2pGZpNs0Rb7I_mcw,12249
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.5.dist-info/METADATA,sha256=j7tFOFQLvwciwHpyidLtuy4t8oKp_rkhLuKWgBMzgg8,20191
75
- tree_sitter_analyzer-0.9.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
76
- tree_sitter_analyzer-0.9.5.dist-info/entry_points.txt,sha256=U4tfLGXgCWubKm2PyEb3zxhQ2pm7zVotMyfyS0CodD8,486
77
- tree_sitter_analyzer-0.9.5.dist-info/RECORD,,
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,,