tree-sitter-analyzer 0.9.9__py3-none-any.whl → 1.1.0__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 +11 -2
- tree_sitter_analyzer/cli/commands/base_command.py +2 -2
- tree_sitter_analyzer/cli/commands/partial_read_command.py +2 -2
- tree_sitter_analyzer/cli/info_commands.py +7 -4
- tree_sitter_analyzer/cli_main.py +1 -2
- tree_sitter_analyzer/core/analysis_engine.py +2 -2
- tree_sitter_analyzer/core/query_service.py +162 -162
- tree_sitter_analyzer/file_handler.py +6 -4
- tree_sitter_analyzer/formatters/base_formatter.py +6 -4
- tree_sitter_analyzer/mcp/__init__.py +1 -1
- tree_sitter_analyzer/mcp/resources/__init__.py +1 -1
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +16 -5
- tree_sitter_analyzer/mcp/server.py +16 -11
- tree_sitter_analyzer/mcp/tools/__init__.py +1 -1
- tree_sitter_analyzer/mcp/utils/__init__.py +2 -2
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -569
- tree_sitter_analyzer/mcp/utils/path_resolver.py +240 -20
- tree_sitter_analyzer/output_manager.py +7 -3
- tree_sitter_analyzer/project_detector.py +27 -27
- tree_sitter_analyzer/security/boundary_manager.py +37 -28
- tree_sitter_analyzer/security/validator.py +23 -12
- tree_sitter_analyzer/table_formatter.py +6 -4
- tree_sitter_analyzer/utils.py +1 -2
- {tree_sitter_analyzer-0.9.9.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/METADATA +38 -12
- {tree_sitter_analyzer-0.9.9.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/RECORD +28 -28
- {tree_sitter_analyzer-0.9.9.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-0.9.9.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/entry_points.txt +0 -0
tree_sitter_analyzer/__init__.py
CHANGED
tree_sitter_analyzer/api.py
CHANGED
|
@@ -126,11 +126,20 @@ def analyze_file(
|
|
|
126
126
|
except FileNotFoundError as e:
|
|
127
127
|
# Re-raise FileNotFoundError for tests that expect it
|
|
128
128
|
raise e
|
|
129
|
+
except (ValueError, TypeError, OSError) as e:
|
|
130
|
+
# Handle specific expected errors
|
|
131
|
+
log_error(f"API analyze_file failed with {type(e).__name__}: {e}")
|
|
132
|
+
return {
|
|
133
|
+
"success": False,
|
|
134
|
+
"error": f"{type(e).__name__}: {str(e)}",
|
|
135
|
+
"file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
|
|
136
|
+
}
|
|
129
137
|
except Exception as e:
|
|
130
|
-
|
|
138
|
+
# Handle unexpected errors
|
|
139
|
+
log_error(f"API analyze_file failed with unexpected error: {e}")
|
|
131
140
|
return {
|
|
132
141
|
"success": False,
|
|
133
|
-
"error": str(e),
|
|
142
|
+
"error": f"Unexpected error: {str(e)}",
|
|
134
143
|
"file_info": {"path": str(file_path), "exists": Path(file_path).exists()},
|
|
135
144
|
}
|
|
136
145
|
|
|
@@ -54,9 +54,9 @@ class BaseCommand(ABC):
|
|
|
54
54
|
output_error(f"Invalid file path: {error_msg}")
|
|
55
55
|
return False
|
|
56
56
|
|
|
57
|
-
import
|
|
57
|
+
from pathlib import Path
|
|
58
58
|
|
|
59
|
-
if not
|
|
59
|
+
if not Path(self.args.file_path).exists():
|
|
60
60
|
output_error("Invalid file path: file does not exist")
|
|
61
61
|
return False
|
|
62
62
|
|
|
@@ -31,9 +31,9 @@ class PartialReadCommand(BaseCommand):
|
|
|
31
31
|
output_error("File path not specified.")
|
|
32
32
|
return False
|
|
33
33
|
|
|
34
|
-
import
|
|
34
|
+
from pathlib import Path
|
|
35
35
|
|
|
36
|
-
if not
|
|
36
|
+
if not Path(self.args.file_path).exists():
|
|
37
37
|
from ...output_manager import output_error
|
|
38
38
|
|
|
39
39
|
output_error(f"File not found: {self.args.file_path}")
|
|
@@ -112,10 +112,13 @@ class ShowExtensionsCommand(InfoCommand):
|
|
|
112
112
|
def execute(self) -> int:
|
|
113
113
|
output_list("Supported file extensions:")
|
|
114
114
|
supported_extensions = detector.get_supported_extensions()
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
# Use more efficient chunking with itertools.islice
|
|
116
|
+
from itertools import islice
|
|
117
|
+
|
|
118
|
+
chunk_size = 8
|
|
119
|
+
for i in range(0, len(supported_extensions), chunk_size):
|
|
120
|
+
chunk = list(islice(supported_extensions, i, i + chunk_size))
|
|
121
|
+
line = " " + " ".join(f"{ext:<6}" for ext in chunk)
|
|
119
122
|
output_list(line)
|
|
120
123
|
output_info(f"\nTotal {len(supported_extensions)} extensions supported")
|
|
121
124
|
return 0
|
tree_sitter_analyzer/cli_main.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import logging
|
|
6
|
+
import os
|
|
6
7
|
import sys
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -268,8 +269,6 @@ def handle_special_commands(args: argparse.Namespace) -> int | None:
|
|
|
268
269
|
def main() -> None:
|
|
269
270
|
"""Main entry point for the CLI."""
|
|
270
271
|
# Early check for quiet mode to set environment variable before any imports
|
|
271
|
-
import os
|
|
272
|
-
|
|
273
272
|
if "--quiet" in sys.argv:
|
|
274
273
|
os.environ["LOG_LEVEL"] = "ERROR"
|
|
275
274
|
else:
|
|
@@ -395,9 +395,9 @@ class UnifiedAnalysisEngine:
|
|
|
395
395
|
Detected language name
|
|
396
396
|
"""
|
|
397
397
|
# 簡易的な拡張子ベース検出
|
|
398
|
-
import
|
|
398
|
+
from pathlib import Path
|
|
399
399
|
|
|
400
|
-
|
|
400
|
+
ext = Path(file_path).suffix
|
|
401
401
|
|
|
402
402
|
language_map = {
|
|
403
403
|
".java": "java",
|
|
@@ -1,162 +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
|
|
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
|
|
@@ -5,7 +5,7 @@ File Handler Module
|
|
|
5
5
|
This module provides file reading functionality with encoding detection and fallback.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
from pathlib import Path
|
|
9
9
|
|
|
10
10
|
from .encoding_utils import read_file_safe
|
|
11
11
|
from .utils import log_error, log_info, log_warning
|
|
@@ -21,7 +21,7 @@ def detect_language_from_extension(file_path: str) -> str:
|
|
|
21
21
|
Returns:
|
|
22
22
|
Language name or 'unknown' if not recognized
|
|
23
23
|
"""
|
|
24
|
-
extension =
|
|
24
|
+
extension = Path(file_path).suffix.lower()
|
|
25
25
|
|
|
26
26
|
extension_map = {
|
|
27
27
|
".java": "java",
|
|
@@ -56,7 +56,8 @@ def read_file_with_fallback(file_path: str) -> bytes | None:
|
|
|
56
56
|
File content as bytes, or None if file doesn't exist
|
|
57
57
|
"""
|
|
58
58
|
# Check file existence first
|
|
59
|
-
|
|
59
|
+
file_obj = Path(file_path)
|
|
60
|
+
if not file_obj.exists():
|
|
60
61
|
log_error(f"File does not exist: {file_path}")
|
|
61
62
|
return None
|
|
62
63
|
|
|
@@ -93,7 +94,8 @@ def read_file_partial(
|
|
|
93
94
|
Selected content string, or None on error
|
|
94
95
|
"""
|
|
95
96
|
# Check file existence
|
|
96
|
-
|
|
97
|
+
file_obj = Path(file_path)
|
|
98
|
+
if not file_obj.exists():
|
|
97
99
|
log_error(f"File does not exist: {file_path}")
|
|
98
100
|
return None
|
|
99
101
|
|
|
@@ -5,7 +5,6 @@ Base formatter for language-specific table formatting.
|
|
|
5
5
|
|
|
6
6
|
import csv
|
|
7
7
|
import io
|
|
8
|
-
import os
|
|
9
8
|
from abc import ABC, abstractmethod
|
|
10
9
|
from typing import Any
|
|
11
10
|
|
|
@@ -18,12 +17,15 @@ class BaseTableFormatter(ABC):
|
|
|
18
17
|
|
|
19
18
|
def _get_platform_newline(self) -> str:
|
|
20
19
|
"""Get platform-specific newline code"""
|
|
21
|
-
|
|
20
|
+
import os
|
|
21
|
+
|
|
22
|
+
return "\r\n" if os.name == "nt" else "\n" # Windows uses \r\n, others use \n
|
|
22
23
|
|
|
23
24
|
def _convert_to_platform_newlines(self, text: str) -> str:
|
|
24
25
|
"""Convert regular \n to platform-specific newline code"""
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
platform_newline = self._get_platform_newline()
|
|
27
|
+
if platform_newline != "\n":
|
|
28
|
+
return text.replace("\n", platform_newline)
|
|
27
29
|
return text
|
|
28
30
|
|
|
29
31
|
def format_structure(self, structure_data: dict[str, Any]) -> str:
|
|
@@ -19,7 +19,7 @@ from .code_file_resource import CodeFileResource
|
|
|
19
19
|
from .project_stats_resource import ProjectStatsResource
|
|
20
20
|
|
|
21
21
|
# Resource metadata
|
|
22
|
-
__version__ = "1.
|
|
22
|
+
__version__ = "1.1.0"
|
|
23
23
|
__author__ = "Tree-Sitter Analyzer Team"
|
|
24
24
|
|
|
25
25
|
# MCP Resource capabilities
|
|
@@ -123,8 +123,8 @@ class ProjectStatsResource:
|
|
|
123
123
|
|
|
124
124
|
self._project_path = project_path
|
|
125
125
|
|
|
126
|
-
#
|
|
127
|
-
|
|
126
|
+
# Note: analysis_engine is already initialized in __init__
|
|
127
|
+
# No need to reinitialize here
|
|
128
128
|
|
|
129
129
|
logger.debug(f"Set project path to: {project_path}")
|
|
130
130
|
|
|
@@ -336,16 +336,27 @@ class ProjectStatsResource:
|
|
|
336
336
|
|
|
337
337
|
# Use appropriate analyzer based on language
|
|
338
338
|
if language == "java":
|
|
339
|
-
# Use
|
|
340
|
-
|
|
341
|
-
file_analysis = await self._advanced_analyzer.analyze_file(
|
|
339
|
+
# Use analysis engine for Java
|
|
340
|
+
file_analysis = await self.analysis_engine.analyze_file(
|
|
342
341
|
str(file_path)
|
|
343
342
|
)
|
|
344
343
|
if file_analysis and hasattr(file_analysis, "methods"):
|
|
344
|
+
# Extract complexity from methods if available
|
|
345
345
|
complexity = sum(
|
|
346
346
|
method.complexity_score or 0
|
|
347
347
|
for method in file_analysis.methods
|
|
348
348
|
)
|
|
349
|
+
elif file_analysis and hasattr(file_analysis, "elements"):
|
|
350
|
+
# Extract complexity from elements for new architecture
|
|
351
|
+
methods = [
|
|
352
|
+
e
|
|
353
|
+
for e in file_analysis.elements
|
|
354
|
+
if hasattr(e, "complexity_score")
|
|
355
|
+
]
|
|
356
|
+
complexity = sum(
|
|
357
|
+
getattr(method, "complexity_score", 0) or 0
|
|
358
|
+
for method in methods
|
|
359
|
+
)
|
|
349
360
|
else:
|
|
350
361
|
complexity = 0
|
|
351
362
|
else:
|
|
@@ -11,6 +11,7 @@ import asyncio
|
|
|
11
11
|
import json
|
|
12
12
|
import os
|
|
13
13
|
import sys
|
|
14
|
+
from pathlib import Path as PathClass
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
16
17
|
try:
|
|
@@ -141,15 +142,13 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
141
142
|
include_details = arguments.get("include_details", False)
|
|
142
143
|
|
|
143
144
|
# Resolve relative path against project root for consistent behavior
|
|
144
|
-
import os
|
|
145
|
-
|
|
146
145
|
base_root = getattr(
|
|
147
146
|
getattr(self.security_validator, "boundary_manager", None),
|
|
148
147
|
"project_root",
|
|
149
148
|
None,
|
|
150
149
|
)
|
|
151
|
-
if not
|
|
152
|
-
resolved_path =
|
|
150
|
+
if not PathClass(file_path).is_absolute() and base_root:
|
|
151
|
+
resolved_path = str((PathClass(base_root) / file_path).resolve())
|
|
153
152
|
else:
|
|
154
153
|
resolved_path = file_path
|
|
155
154
|
|
|
@@ -159,13 +158,11 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
159
158
|
raise ValueError(f"Invalid file path: {error_msg}")
|
|
160
159
|
|
|
161
160
|
# Use analysis engine directly
|
|
162
|
-
from pathlib import Path
|
|
163
|
-
|
|
164
161
|
from ..core.analysis_engine import AnalysisRequest
|
|
165
162
|
from ..language_detector import detect_language_from_file
|
|
166
163
|
|
|
167
164
|
# Validate file exists
|
|
168
|
-
if not
|
|
165
|
+
if not PathClass(resolved_path).exists():
|
|
169
166
|
raise FileNotFoundError(f"File not found: {file_path}")
|
|
170
167
|
|
|
171
168
|
# Detect language if not specified
|
|
@@ -404,7 +401,7 @@ class TreeSitterAnalyzerMCPServer:
|
|
|
404
401
|
raise ValueError(
|
|
405
402
|
"project_path parameter is required and must be a string"
|
|
406
403
|
)
|
|
407
|
-
if not
|
|
404
|
+
if not PathClass(project_path).is_dir():
|
|
408
405
|
raise ValueError(f"Project path does not exist: {project_path}")
|
|
409
406
|
self.set_project_path(project_path)
|
|
410
407
|
result = {"status": "success", "project_root": project_path}
|
|
@@ -596,8 +593,12 @@ async def main() -> None:
|
|
|
596
593
|
if args.project_root:
|
|
597
594
|
project_root = args.project_root
|
|
598
595
|
# Priority 2: Environment variable
|
|
599
|
-
elif
|
|
600
|
-
|
|
596
|
+
elif (
|
|
597
|
+
PathClass.cwd()
|
|
598
|
+
.joinpath(os.environ.get("TREE_SITTER_PROJECT_ROOT", ""))
|
|
599
|
+
.exists()
|
|
600
|
+
):
|
|
601
|
+
project_root = os.environ.get("TREE_SITTER_PROJECT_ROOT")
|
|
601
602
|
# Priority 3: Auto-detection from current directory
|
|
602
603
|
else:
|
|
603
604
|
project_root = detect_project_root()
|
|
@@ -608,7 +609,11 @@ async def main() -> None:
|
|
|
608
609
|
)
|
|
609
610
|
|
|
610
611
|
# Validate existence; if invalid, fall back to auto-detected root
|
|
611
|
-
if
|
|
612
|
+
if (
|
|
613
|
+
not project_root
|
|
614
|
+
or invalid_placeholder
|
|
615
|
+
or not PathClass(project_root).is_dir()
|
|
616
|
+
):
|
|
612
617
|
detected = detect_project_root()
|
|
613
618
|
try:
|
|
614
619
|
logger.warning(
|