tree-sitter-analyzer 1.0.0__py3-none-any.whl → 1.1.1__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 +132 -132
- tree_sitter_analyzer/api.py +542 -542
- tree_sitter_analyzer/cli/commands/base_command.py +181 -181
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -139
- tree_sitter_analyzer/cli/info_commands.py +124 -124
- tree_sitter_analyzer/cli_main.py +327 -327
- tree_sitter_analyzer/core/analysis_engine.py +584 -584
- tree_sitter_analyzer/core/query_service.py +162 -162
- tree_sitter_analyzer/file_handler.py +212 -212
- tree_sitter_analyzer/formatters/base_formatter.py +169 -169
- tree_sitter_analyzer/interfaces/cli.py +535 -535
- tree_sitter_analyzer/mcp/__init__.py +1 -1
- tree_sitter_analyzer/mcp/resources/__init__.py +0 -1
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +16 -5
- tree_sitter_analyzer/mcp/server.py +655 -655
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -30
- tree_sitter_analyzer/mcp/utils/__init__.py +1 -2
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -569
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -414
- tree_sitter_analyzer/output_manager.py +257 -257
- tree_sitter_analyzer/project_detector.py +330 -330
- tree_sitter_analyzer/security/boundary_manager.py +260 -260
- tree_sitter_analyzer/security/validator.py +257 -257
- tree_sitter_analyzer/table_formatter.py +710 -710
- tree_sitter_analyzer/utils.py +335 -335
- {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.1.dist-info}/METADATA +12 -12
- {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.1.dist-info}/RECORD +29 -29
- {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.1.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,212 +1,212 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
File Handler Module
|
|
4
|
-
|
|
5
|
-
This module provides file reading functionality with encoding detection and fallback.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
from .encoding_utils import read_file_safe
|
|
11
|
-
from .utils import log_error, log_info, log_warning
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def detect_language_from_extension(file_path: str) -> str:
|
|
15
|
-
"""
|
|
16
|
-
Detect programming language from file extension
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
file_path: File path to analyze
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
Language name or 'unknown' if not recognized
|
|
23
|
-
"""
|
|
24
|
-
extension = Path(file_path).suffix.lower()
|
|
25
|
-
|
|
26
|
-
extension_map = {
|
|
27
|
-
".java": "java",
|
|
28
|
-
".py": "python",
|
|
29
|
-
".js": "javascript",
|
|
30
|
-
".ts": "typescript",
|
|
31
|
-
".cpp": "cpp",
|
|
32
|
-
".c": "c",
|
|
33
|
-
".h": "c",
|
|
34
|
-
".hpp": "cpp",
|
|
35
|
-
".cs": "csharp",
|
|
36
|
-
".go": "go",
|
|
37
|
-
".rs": "rust",
|
|
38
|
-
".rb": "ruby",
|
|
39
|
-
".php": "php",
|
|
40
|
-
".kt": "kotlin",
|
|
41
|
-
".scala": "scala",
|
|
42
|
-
".swift": "swift",
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return extension_map.get(extension, "unknown")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def read_file_with_fallback(file_path: str) -> bytes | None:
|
|
49
|
-
"""
|
|
50
|
-
Read file with encoding fallback using unified encoding utilities
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
file_path: Path to the file to read
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
File content as bytes, or None if file doesn't exist
|
|
57
|
-
"""
|
|
58
|
-
# Check file existence first
|
|
59
|
-
file_obj = Path(file_path)
|
|
60
|
-
if not file_obj.exists():
|
|
61
|
-
log_error(f"File does not exist: {file_path}")
|
|
62
|
-
return None
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
content, detected_encoding = read_file_safe(file_path)
|
|
66
|
-
log_info(
|
|
67
|
-
f"Successfully read file {file_path} with encoding: {detected_encoding}"
|
|
68
|
-
)
|
|
69
|
-
return content.encode("utf-8")
|
|
70
|
-
|
|
71
|
-
except Exception as e:
|
|
72
|
-
log_error(f"Failed to read file {file_path}: {e}")
|
|
73
|
-
return None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def read_file_partial(
|
|
77
|
-
file_path: str,
|
|
78
|
-
start_line: int,
|
|
79
|
-
end_line: int | None = None,
|
|
80
|
-
start_column: int | None = None,
|
|
81
|
-
end_column: int | None = None,
|
|
82
|
-
) -> str | None:
|
|
83
|
-
"""
|
|
84
|
-
Read partial file content by line/column range
|
|
85
|
-
|
|
86
|
-
Args:
|
|
87
|
-
file_path: Path to file
|
|
88
|
-
start_line: Start line (1-based)
|
|
89
|
-
end_line: End line (1-based, None means EOF)
|
|
90
|
-
start_column: Start column (0-based, optional)
|
|
91
|
-
end_column: End column (0-based, optional)
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Selected content string, or None on error
|
|
95
|
-
"""
|
|
96
|
-
# Check file existence
|
|
97
|
-
file_obj = Path(file_path)
|
|
98
|
-
if not file_obj.exists():
|
|
99
|
-
log_error(f"File does not exist: {file_path}")
|
|
100
|
-
return None
|
|
101
|
-
|
|
102
|
-
# Parameter validation
|
|
103
|
-
if start_line < 1:
|
|
104
|
-
log_error(f"Invalid start_line: {start_line}. Line numbers start from 1.")
|
|
105
|
-
return None
|
|
106
|
-
|
|
107
|
-
if end_line is not None and end_line < start_line:
|
|
108
|
-
log_error(f"Invalid range: end_line ({end_line}) < start_line ({start_line})")
|
|
109
|
-
return None
|
|
110
|
-
|
|
111
|
-
try:
|
|
112
|
-
# Read whole file safely
|
|
113
|
-
content, detected_encoding = read_file_safe(file_path)
|
|
114
|
-
|
|
115
|
-
# Split to lines
|
|
116
|
-
lines = content.splitlines(keepends=True)
|
|
117
|
-
total_lines = len(lines)
|
|
118
|
-
|
|
119
|
-
# Adjust line indexes
|
|
120
|
-
start_idx = start_line - 1 # convert to 0-based
|
|
121
|
-
end_idx = min(
|
|
122
|
-
end_line - 1 if end_line is not None else total_lines - 1, total_lines - 1
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
# Range check
|
|
126
|
-
if start_idx >= total_lines:
|
|
127
|
-
log_warning(
|
|
128
|
-
f"start_line ({start_line}) exceeds file length ({total_lines})"
|
|
129
|
-
)
|
|
130
|
-
return ""
|
|
131
|
-
|
|
132
|
-
# Select lines
|
|
133
|
-
selected_lines = lines[start_idx : end_idx + 1]
|
|
134
|
-
|
|
135
|
-
# Handle column range
|
|
136
|
-
if start_column is not None or end_column is not None:
|
|
137
|
-
processed_lines = []
|
|
138
|
-
for i, line in enumerate(selected_lines):
|
|
139
|
-
# Strip newline for processing
|
|
140
|
-
line_content = line.rstrip("\r\n")
|
|
141
|
-
|
|
142
|
-
if i == 0 and start_column is not None:
|
|
143
|
-
# First line: apply start_column
|
|
144
|
-
line_content = (
|
|
145
|
-
line_content[start_column:]
|
|
146
|
-
if start_column < len(line_content)
|
|
147
|
-
else ""
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
if i == len(selected_lines) - 1 and end_column is not None:
|
|
151
|
-
# Last line: apply end_column
|
|
152
|
-
if i == 0 and start_column is not None:
|
|
153
|
-
# Single line: apply both start and end columns
|
|
154
|
-
col_end = (
|
|
155
|
-
end_column - start_column
|
|
156
|
-
if end_column >= start_column
|
|
157
|
-
else 0
|
|
158
|
-
)
|
|
159
|
-
line_content = line_content[:col_end] if col_end > 0 else ""
|
|
160
|
-
else:
|
|
161
|
-
line_content = (
|
|
162
|
-
line_content[:end_column]
|
|
163
|
-
if end_column < len(line_content)
|
|
164
|
-
else line_content
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
# Preserve original newline (except last line)
|
|
168
|
-
if i < len(selected_lines) - 1:
|
|
169
|
-
# Detect original newline char of the line
|
|
170
|
-
original_line = lines[start_idx + i]
|
|
171
|
-
if original_line.endswith("\r\n"):
|
|
172
|
-
line_content += "\r\n"
|
|
173
|
-
elif original_line.endswith("\n"):
|
|
174
|
-
line_content += "\n"
|
|
175
|
-
elif original_line.endswith("\r"):
|
|
176
|
-
line_content += "\r"
|
|
177
|
-
|
|
178
|
-
processed_lines.append(line_content)
|
|
179
|
-
|
|
180
|
-
result = "".join(processed_lines)
|
|
181
|
-
else:
|
|
182
|
-
# No column range: join lines directly
|
|
183
|
-
result = "".join(selected_lines)
|
|
184
|
-
|
|
185
|
-
log_info(
|
|
186
|
-
f"Successfully read partial file {file_path}: "
|
|
187
|
-
f"lines {start_line}-{end_line or total_lines}"
|
|
188
|
-
f"{f', columns {start_column}-{end_column}' if start_column is not None or end_column is not None else ''}"
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
return result
|
|
192
|
-
|
|
193
|
-
except Exception as e:
|
|
194
|
-
log_error(f"Failed to read partial file {file_path}: {e}")
|
|
195
|
-
return None
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def read_file_lines_range(
|
|
199
|
-
file_path: str, start_line: int, end_line: int | None = None
|
|
200
|
-
) -> str | None:
|
|
201
|
-
"""
|
|
202
|
-
指定した行番号範囲でファイルの一部を読み込み(列指定なし)
|
|
203
|
-
|
|
204
|
-
Args:
|
|
205
|
-
file_path: 読み込むファイルのパス
|
|
206
|
-
start_line: 開始行番号(1ベース)
|
|
207
|
-
end_line: 終了行番号(Noneの場合はファイル末尾まで、1ベース)
|
|
208
|
-
|
|
209
|
-
Returns:
|
|
210
|
-
指定範囲のファイル内容(文字列)、エラーの場合はNone
|
|
211
|
-
"""
|
|
212
|
-
return read_file_partial(file_path, start_line, end_line)
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
File Handler Module
|
|
4
|
+
|
|
5
|
+
This module provides file reading functionality with encoding detection and fallback.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .encoding_utils import read_file_safe
|
|
11
|
+
from .utils import log_error, log_info, log_warning
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def detect_language_from_extension(file_path: str) -> str:
|
|
15
|
+
"""
|
|
16
|
+
Detect programming language from file extension
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
file_path: File path to analyze
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Language name or 'unknown' if not recognized
|
|
23
|
+
"""
|
|
24
|
+
extension = Path(file_path).suffix.lower()
|
|
25
|
+
|
|
26
|
+
extension_map = {
|
|
27
|
+
".java": "java",
|
|
28
|
+
".py": "python",
|
|
29
|
+
".js": "javascript",
|
|
30
|
+
".ts": "typescript",
|
|
31
|
+
".cpp": "cpp",
|
|
32
|
+
".c": "c",
|
|
33
|
+
".h": "c",
|
|
34
|
+
".hpp": "cpp",
|
|
35
|
+
".cs": "csharp",
|
|
36
|
+
".go": "go",
|
|
37
|
+
".rs": "rust",
|
|
38
|
+
".rb": "ruby",
|
|
39
|
+
".php": "php",
|
|
40
|
+
".kt": "kotlin",
|
|
41
|
+
".scala": "scala",
|
|
42
|
+
".swift": "swift",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return extension_map.get(extension, "unknown")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def read_file_with_fallback(file_path: str) -> bytes | None:
|
|
49
|
+
"""
|
|
50
|
+
Read file with encoding fallback using unified encoding utilities
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
file_path: Path to the file to read
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
File content as bytes, or None if file doesn't exist
|
|
57
|
+
"""
|
|
58
|
+
# Check file existence first
|
|
59
|
+
file_obj = Path(file_path)
|
|
60
|
+
if not file_obj.exists():
|
|
61
|
+
log_error(f"File does not exist: {file_path}")
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
content, detected_encoding = read_file_safe(file_path)
|
|
66
|
+
log_info(
|
|
67
|
+
f"Successfully read file {file_path} with encoding: {detected_encoding}"
|
|
68
|
+
)
|
|
69
|
+
return content.encode("utf-8")
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
log_error(f"Failed to read file {file_path}: {e}")
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def read_file_partial(
|
|
77
|
+
file_path: str,
|
|
78
|
+
start_line: int,
|
|
79
|
+
end_line: int | None = None,
|
|
80
|
+
start_column: int | None = None,
|
|
81
|
+
end_column: int | None = None,
|
|
82
|
+
) -> str | None:
|
|
83
|
+
"""
|
|
84
|
+
Read partial file content by line/column range
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
file_path: Path to file
|
|
88
|
+
start_line: Start line (1-based)
|
|
89
|
+
end_line: End line (1-based, None means EOF)
|
|
90
|
+
start_column: Start column (0-based, optional)
|
|
91
|
+
end_column: End column (0-based, optional)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Selected content string, or None on error
|
|
95
|
+
"""
|
|
96
|
+
# Check file existence
|
|
97
|
+
file_obj = Path(file_path)
|
|
98
|
+
if not file_obj.exists():
|
|
99
|
+
log_error(f"File does not exist: {file_path}")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
# Parameter validation
|
|
103
|
+
if start_line < 1:
|
|
104
|
+
log_error(f"Invalid start_line: {start_line}. Line numbers start from 1.")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
if end_line is not None and end_line < start_line:
|
|
108
|
+
log_error(f"Invalid range: end_line ({end_line}) < start_line ({start_line})")
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
# Read whole file safely
|
|
113
|
+
content, detected_encoding = read_file_safe(file_path)
|
|
114
|
+
|
|
115
|
+
# Split to lines
|
|
116
|
+
lines = content.splitlines(keepends=True)
|
|
117
|
+
total_lines = len(lines)
|
|
118
|
+
|
|
119
|
+
# Adjust line indexes
|
|
120
|
+
start_idx = start_line - 1 # convert to 0-based
|
|
121
|
+
end_idx = min(
|
|
122
|
+
end_line - 1 if end_line is not None else total_lines - 1, total_lines - 1
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Range check
|
|
126
|
+
if start_idx >= total_lines:
|
|
127
|
+
log_warning(
|
|
128
|
+
f"start_line ({start_line}) exceeds file length ({total_lines})"
|
|
129
|
+
)
|
|
130
|
+
return ""
|
|
131
|
+
|
|
132
|
+
# Select lines
|
|
133
|
+
selected_lines = lines[start_idx : end_idx + 1]
|
|
134
|
+
|
|
135
|
+
# Handle column range
|
|
136
|
+
if start_column is not None or end_column is not None:
|
|
137
|
+
processed_lines = []
|
|
138
|
+
for i, line in enumerate(selected_lines):
|
|
139
|
+
# Strip newline for processing
|
|
140
|
+
line_content = line.rstrip("\r\n")
|
|
141
|
+
|
|
142
|
+
if i == 0 and start_column is not None:
|
|
143
|
+
# First line: apply start_column
|
|
144
|
+
line_content = (
|
|
145
|
+
line_content[start_column:]
|
|
146
|
+
if start_column < len(line_content)
|
|
147
|
+
else ""
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if i == len(selected_lines) - 1 and end_column is not None:
|
|
151
|
+
# Last line: apply end_column
|
|
152
|
+
if i == 0 and start_column is not None:
|
|
153
|
+
# Single line: apply both start and end columns
|
|
154
|
+
col_end = (
|
|
155
|
+
end_column - start_column
|
|
156
|
+
if end_column >= start_column
|
|
157
|
+
else 0
|
|
158
|
+
)
|
|
159
|
+
line_content = line_content[:col_end] if col_end > 0 else ""
|
|
160
|
+
else:
|
|
161
|
+
line_content = (
|
|
162
|
+
line_content[:end_column]
|
|
163
|
+
if end_column < len(line_content)
|
|
164
|
+
else line_content
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Preserve original newline (except last line)
|
|
168
|
+
if i < len(selected_lines) - 1:
|
|
169
|
+
# Detect original newline char of the line
|
|
170
|
+
original_line = lines[start_idx + i]
|
|
171
|
+
if original_line.endswith("\r\n"):
|
|
172
|
+
line_content += "\r\n"
|
|
173
|
+
elif original_line.endswith("\n"):
|
|
174
|
+
line_content += "\n"
|
|
175
|
+
elif original_line.endswith("\r"):
|
|
176
|
+
line_content += "\r"
|
|
177
|
+
|
|
178
|
+
processed_lines.append(line_content)
|
|
179
|
+
|
|
180
|
+
result = "".join(processed_lines)
|
|
181
|
+
else:
|
|
182
|
+
# No column range: join lines directly
|
|
183
|
+
result = "".join(selected_lines)
|
|
184
|
+
|
|
185
|
+
log_info(
|
|
186
|
+
f"Successfully read partial file {file_path}: "
|
|
187
|
+
f"lines {start_line}-{end_line or total_lines}"
|
|
188
|
+
f"{f', columns {start_column}-{end_column}' if start_column is not None or end_column is not None else ''}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return result
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
log_error(f"Failed to read partial file {file_path}: {e}")
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def read_file_lines_range(
|
|
199
|
+
file_path: str, start_line: int, end_line: int | None = None
|
|
200
|
+
) -> str | None:
|
|
201
|
+
"""
|
|
202
|
+
指定した行番号範囲でファイルの一部を読み込み(列指定なし)
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
file_path: 読み込むファイルのパス
|
|
206
|
+
start_line: 開始行番号(1ベース)
|
|
207
|
+
end_line: 終了行番号(Noneの場合はファイル末尾まで、1ベース)
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
指定範囲のファイル内容(文字列)、エラーの場合はNone
|
|
211
|
+
"""
|
|
212
|
+
return read_file_partial(file_path, start_line, end_line)
|