tree-sitter-analyzer 0.7.0__py3-none-any.whl → 0.8.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.

Files changed (69) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/__main__.py +11 -11
  3. tree_sitter_analyzer/api.py +533 -533
  4. tree_sitter_analyzer/cli/__init__.py +39 -39
  5. tree_sitter_analyzer/cli/__main__.py +12 -12
  6. tree_sitter_analyzer/cli/commands/__init__.py +26 -26
  7. tree_sitter_analyzer/cli/commands/advanced_command.py +88 -88
  8. tree_sitter_analyzer/cli/commands/base_command.py +160 -160
  9. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  10. tree_sitter_analyzer/cli/commands/partial_read_command.py +141 -141
  11. tree_sitter_analyzer/cli/commands/query_command.py +81 -81
  12. tree_sitter_analyzer/cli/commands/structure_command.py +138 -138
  13. tree_sitter_analyzer/cli/commands/summary_command.py +101 -101
  14. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  15. tree_sitter_analyzer/cli/info_commands.py +121 -121
  16. tree_sitter_analyzer/cli_main.py +297 -297
  17. tree_sitter_analyzer/core/__init__.py +15 -15
  18. tree_sitter_analyzer/core/analysis_engine.py +555 -555
  19. tree_sitter_analyzer/core/cache_service.py +320 -320
  20. tree_sitter_analyzer/core/engine.py +566 -566
  21. tree_sitter_analyzer/core/parser.py +293 -293
  22. tree_sitter_analyzer/encoding_utils.py +459 -459
  23. tree_sitter_analyzer/exceptions.py +406 -337
  24. tree_sitter_analyzer/file_handler.py +210 -210
  25. tree_sitter_analyzer/formatters/__init__.py +1 -1
  26. tree_sitter_analyzer/formatters/base_formatter.py +167 -167
  27. tree_sitter_analyzer/formatters/formatter_factory.py +78 -78
  28. tree_sitter_analyzer/interfaces/__init__.py +9 -9
  29. tree_sitter_analyzer/interfaces/cli.py +528 -528
  30. tree_sitter_analyzer/interfaces/cli_adapter.py +343 -343
  31. tree_sitter_analyzer/interfaces/mcp_adapter.py +206 -206
  32. tree_sitter_analyzer/interfaces/mcp_server.py +425 -405
  33. tree_sitter_analyzer/languages/__init__.py +10 -10
  34. tree_sitter_analyzer/languages/javascript_plugin.py +446 -446
  35. tree_sitter_analyzer/languages/python_plugin.py +755 -755
  36. tree_sitter_analyzer/mcp/__init__.py +31 -31
  37. tree_sitter_analyzer/mcp/resources/__init__.py +44 -44
  38. tree_sitter_analyzer/mcp/resources/code_file_resource.py +209 -209
  39. tree_sitter_analyzer/mcp/server.py +346 -333
  40. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  41. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +654 -654
  42. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +247 -247
  43. tree_sitter_analyzer/mcp/tools/base_tool.py +54 -54
  44. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +300 -300
  45. tree_sitter_analyzer/mcp/tools/table_format_tool.py +362 -362
  46. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +543 -543
  47. tree_sitter_analyzer/mcp/utils/__init__.py +107 -107
  48. tree_sitter_analyzer/mcp/utils/error_handler.py +549 -549
  49. tree_sitter_analyzer/output_manager.py +253 -253
  50. tree_sitter_analyzer/plugins/__init__.py +280 -280
  51. tree_sitter_analyzer/plugins/base.py +529 -529
  52. tree_sitter_analyzer/plugins/manager.py +379 -379
  53. tree_sitter_analyzer/queries/__init__.py +26 -26
  54. tree_sitter_analyzer/queries/java.py +391 -391
  55. tree_sitter_analyzer/queries/javascript.py +148 -148
  56. tree_sitter_analyzer/queries/python.py +285 -285
  57. tree_sitter_analyzer/queries/typescript.py +229 -229
  58. tree_sitter_analyzer/query_loader.py +257 -257
  59. tree_sitter_analyzer/security/__init__.py +22 -0
  60. tree_sitter_analyzer/security/boundary_manager.py +237 -0
  61. tree_sitter_analyzer/security/regex_checker.py +292 -0
  62. tree_sitter_analyzer/security/validator.py +224 -0
  63. tree_sitter_analyzer/table_formatter.py +652 -589
  64. tree_sitter_analyzer/utils.py +277 -277
  65. {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/METADATA +4 -1
  66. tree_sitter_analyzer-0.8.0.dist-info/RECORD +76 -0
  67. tree_sitter_analyzer-0.7.0.dist-info/RECORD +0 -72
  68. {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/WHEEL +0 -0
  69. {tree_sitter_analyzer-0.7.0.dist-info → tree_sitter_analyzer-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -1,337 +1,406 @@
1
- #!/usr/bin/env python3
2
- """
3
- Tree-sitter Analyzer Custom Exceptions
4
-
5
- Unified exception handling system for consistent error management
6
- across the entire framework.
7
- """
8
-
9
- from pathlib import Path
10
- from typing import Any
11
-
12
-
13
- class TreeSitterAnalyzerError(Exception):
14
- """Base exception for all tree-sitter analyzer errors."""
15
-
16
- def __init__(
17
- self,
18
- message: str,
19
- error_code: str | None = None,
20
- context: dict[str, Any] | None = None,
21
- ) -> None:
22
- super().__init__(message)
23
- self.message = message
24
- self.error_code = error_code or self.__class__.__name__
25
- self.context = context or {}
26
-
27
- def to_dict(self) -> dict[str, Any]:
28
- """Convert exception to dictionary format."""
29
- return {
30
- "error_type": self.__class__.__name__,
31
- "error_code": self.error_code,
32
- "message": self.message,
33
- "context": self.context,
34
- }
35
-
36
-
37
- class AnalysisError(TreeSitterAnalyzerError):
38
- """Raised when file analysis fails."""
39
-
40
- def __init__(
41
- self,
42
- message: str,
43
- file_path: str | Path | None = None,
44
- language: str | None = None,
45
- **kwargs: Any,
46
- ) -> None:
47
- context = kwargs.get("context", {})
48
- if file_path:
49
- context["file_path"] = str(file_path)
50
- if language:
51
- context["language"] = language
52
- super().__init__(message, context=context, **kwargs)
53
-
54
-
55
- class ParseError(TreeSitterAnalyzerError):
56
- """Raised when parsing fails."""
57
-
58
- def __init__(
59
- self,
60
- message: str,
61
- language: str | None = None,
62
- source_info: dict[str, Any] | None = None,
63
- **kwargs: Any,
64
- ) -> None:
65
- context = kwargs.get("context", {})
66
- if language:
67
- context["language"] = language
68
- if source_info:
69
- context.update(source_info)
70
- super().__init__(message, context=context, **kwargs)
71
-
72
-
73
- class LanguageNotSupportedError(TreeSitterAnalyzerError):
74
- """Raised when a language is not supported."""
75
-
76
- def __init__(
77
- self, language: str, supported_languages: list[str] | None = None, **kwargs: Any
78
- ) -> None:
79
- message = f"Language '{language}' is not supported"
80
- context = kwargs.get("context", {})
81
- context["language"] = language
82
- if supported_languages:
83
- context["supported_languages"] = supported_languages
84
- message += f". Supported languages: {', '.join(supported_languages)}"
85
- super().__init__(message, context=context, **kwargs)
86
-
87
-
88
- class PluginError(TreeSitterAnalyzerError):
89
- """Raised when plugin operations fail."""
90
-
91
- def __init__(
92
- self,
93
- message: str,
94
- plugin_name: str | None = None,
95
- operation: str | None = None,
96
- **kwargs: Any,
97
- ) -> None:
98
- context = kwargs.get("context", {})
99
- if plugin_name:
100
- context["plugin_name"] = plugin_name
101
- if operation:
102
- context["operation"] = operation
103
- super().__init__(message, context=context, **kwargs)
104
-
105
-
106
- class QueryError(TreeSitterAnalyzerError):
107
- """Raised when query execution fails."""
108
-
109
- def __init__(
110
- self,
111
- message: str,
112
- query_name: str | None = None,
113
- query_string: str | None = None,
114
- language: str | None = None,
115
- **kwargs: Any,
116
- ) -> None:
117
- context = kwargs.get("context", {})
118
- if query_name:
119
- context["query_name"] = query_name
120
- if query_string:
121
- context["query_string"] = query_string
122
- if language:
123
- context["language"] = language
124
- super().__init__(message, context=context, **kwargs)
125
-
126
-
127
- class FileHandlingError(TreeSitterAnalyzerError):
128
- """Raised when file operations fail."""
129
-
130
- def __init__(
131
- self,
132
- message: str,
133
- file_path: str | Path | None = None,
134
- operation: str | None = None,
135
- **kwargs: Any,
136
- ) -> None:
137
- context = kwargs.get("context", {})
138
- if file_path:
139
- context["file_path"] = str(file_path)
140
- if operation:
141
- context["operation"] = operation
142
- super().__init__(message, context=context, **kwargs)
143
-
144
-
145
- class ConfigurationError(TreeSitterAnalyzerError):
146
- """Raised when configuration is invalid."""
147
-
148
- def __init__(
149
- self,
150
- message: str,
151
- config_key: str | None = None,
152
- config_value: Any | None = None,
153
- **kwargs: Any,
154
- ) -> None:
155
- context = kwargs.get("context", {})
156
- if config_key:
157
- context["config_key"] = config_key
158
- if config_value is not None:
159
- context["config_value"] = config_value
160
- super().__init__(message, context=context, **kwargs)
161
-
162
-
163
- class ValidationError(TreeSitterAnalyzerError):
164
- """Raised when validation fails."""
165
-
166
- def __init__(
167
- self,
168
- message: str,
169
- validation_type: str | None = None,
170
- invalid_value: Any | None = None,
171
- **kwargs: Any,
172
- ) -> None:
173
- context = kwargs.get("context", {})
174
- if validation_type:
175
- context["validation_type"] = validation_type
176
- if invalid_value is not None:
177
- context["invalid_value"] = invalid_value
178
- super().__init__(message, context=context, **kwargs)
179
-
180
-
181
- class MCPError(TreeSitterAnalyzerError):
182
- """Raised when MCP operations fail."""
183
-
184
- def __init__(
185
- self,
186
- message: str,
187
- tool_name: str | None = None,
188
- resource_uri: str | None = None,
189
- **kwargs: Any,
190
- ) -> None:
191
- context = kwargs.get("context", {})
192
- if tool_name:
193
- context["tool_name"] = tool_name
194
- if resource_uri:
195
- context["resource_uri"] = resource_uri
196
- super().__init__(message, context=context, **kwargs)
197
-
198
-
199
- # Exception handling utilities
200
- def handle_exception(
201
- exception: Exception,
202
- context: dict[str, Any] | None = None,
203
- reraise_as: type[Exception] | None = None,
204
- ) -> None:
205
- """
206
- Handle exceptions with optional context and re-raising.
207
-
208
- Args:
209
- exception: The original exception
210
- context: Additional context information
211
- reraise_as: Exception class to re-raise as
212
- """
213
- from .utils import log_error
214
-
215
- # Log the original exception
216
- error_context = context or {}
217
- if hasattr(exception, "context"):
218
- error_context.update(exception.context)
219
-
220
- log_error(f"Exception handled: {exception}", extra=error_context)
221
-
222
- # Re-raise as different exception type if requested
223
- if reraise_as and not isinstance(exception, reraise_as):
224
- if issubclass(reraise_as, TreeSitterAnalyzerError):
225
- raise reraise_as(str(exception), context=error_context)
226
- else:
227
- raise reraise_as(str(exception))
228
-
229
- # Re-raise original exception
230
- raise exception
231
-
232
-
233
- def safe_execute(
234
- func: Any,
235
- *args: Any,
236
- default_return: Any = None,
237
- exception_types: tuple[type[Exception], ...] = (Exception,),
238
- log_errors: bool = True,
239
- **kwargs: Any,
240
- ) -> Any:
241
- """
242
- Safely execute a function with exception handling.
243
-
244
- Args:
245
- func: Function to execute
246
- *args: Function arguments
247
- default_return: Value to return on exception
248
- exception_types: Exception types to catch
249
- log_errors: Whether to log errors
250
- **kwargs: Function keyword arguments
251
-
252
- Returns:
253
- Function result or default_return on exception
254
- """
255
- try:
256
- return func(*args, **kwargs)
257
- except exception_types as e:
258
- if log_errors:
259
- from .utils import log_error
260
-
261
- log_error(f"Safe execution failed for {func.__name__}: {e}")
262
- return default_return
263
-
264
-
265
- def create_error_response(
266
- exception: Exception, include_traceback: bool = False
267
- ) -> dict[str, Any]:
268
- """
269
- Create standardized error response dictionary.
270
-
271
- Args:
272
- exception: The exception to convert
273
- include_traceback: Whether to include traceback
274
-
275
- Returns:
276
- Error response dictionary
277
- """
278
- import traceback
279
-
280
- response: dict[str, Any] = {
281
- "success": False,
282
- "error": {"type": exception.__class__.__name__, "message": str(exception)},
283
- }
284
-
285
- # Add context if available
286
- if hasattr(exception, "context"):
287
- response["error"]["context"] = exception.context
288
-
289
- # Add error code if available
290
- if hasattr(exception, "error_code"):
291
- response["error"]["code"] = exception.error_code
292
-
293
- # Add traceback if requested
294
- if include_traceback:
295
- response["error"]["traceback"] = traceback.format_exc()
296
-
297
- return response
298
-
299
-
300
- # Decorator for exception handling
301
- def handle_exceptions(
302
- default_return: Any = None,
303
- exception_types: tuple[type[Exception], ...] = (Exception,),
304
- reraise_as: type[Exception] | None = None,
305
- log_errors: bool = True,
306
- ) -> Any:
307
- """
308
- Decorator for automatic exception handling.
309
-
310
- Args:
311
- default_return: Value to return on exception
312
- exception_types: Exception types to catch
313
- reraise_as: Exception class to re-raise as
314
- log_errors: Whether to log errors
315
- """
316
-
317
- def decorator(func: Any) -> Any:
318
- def wrapper(*args: Any, **kwargs: Any) -> Any:
319
- try:
320
- return func(*args, **kwargs)
321
- except exception_types as e:
322
- if log_errors:
323
- from .utils import log_error
324
-
325
- log_error(f"Exception in {func.__name__}: {e}")
326
-
327
- if reraise_as:
328
- if issubclass(reraise_as, TreeSitterAnalyzerError):
329
- raise reraise_as(str(e)) from e
330
- else:
331
- raise reraise_as(str(e)) from e
332
-
333
- return default_return
334
-
335
- return wrapper
336
-
337
- return decorator
1
+ #!/usr/bin/env python3
2
+ """
3
+ Tree-sitter Analyzer Custom Exceptions
4
+
5
+ Unified exception handling system for consistent error management
6
+ across the entire framework.
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ class TreeSitterAnalyzerError(Exception):
14
+ """Base exception for all tree-sitter analyzer errors."""
15
+
16
+ def __init__(
17
+ self,
18
+ message: str,
19
+ error_code: str | None = None,
20
+ context: dict[str, Any] | None = None,
21
+ ) -> None:
22
+ super().__init__(message)
23
+ self.message = message
24
+ self.error_code = error_code or self.__class__.__name__
25
+ self.context = context or {}
26
+
27
+ def to_dict(self) -> dict[str, Any]:
28
+ """Convert exception to dictionary format."""
29
+ return {
30
+ "error_type": self.__class__.__name__,
31
+ "error_code": self.error_code,
32
+ "message": self.message,
33
+ "context": self.context,
34
+ }
35
+
36
+
37
+ class AnalysisError(TreeSitterAnalyzerError):
38
+ """Raised when file analysis fails."""
39
+
40
+ def __init__(
41
+ self,
42
+ message: str,
43
+ file_path: str | Path | None = None,
44
+ language: str | None = None,
45
+ **kwargs: Any,
46
+ ) -> None:
47
+ context = kwargs.get("context", {})
48
+ if file_path:
49
+ context["file_path"] = str(file_path)
50
+ if language:
51
+ context["language"] = language
52
+ super().__init__(message, context=context, **kwargs)
53
+
54
+
55
+ class ParseError(TreeSitterAnalyzerError):
56
+ """Raised when parsing fails."""
57
+
58
+ def __init__(
59
+ self,
60
+ message: str,
61
+ language: str | None = None,
62
+ source_info: dict[str, Any] | None = None,
63
+ **kwargs: Any,
64
+ ) -> None:
65
+ context = kwargs.get("context", {})
66
+ if language:
67
+ context["language"] = language
68
+ if source_info:
69
+ context.update(source_info)
70
+ super().__init__(message, context=context, **kwargs)
71
+
72
+
73
+ class LanguageNotSupportedError(TreeSitterAnalyzerError):
74
+ """Raised when a language is not supported."""
75
+
76
+ def __init__(
77
+ self, language: str, supported_languages: list[str] | None = None, **kwargs: Any
78
+ ) -> None:
79
+ message = f"Language '{language}' is not supported"
80
+ context = kwargs.get("context", {})
81
+ context["language"] = language
82
+ if supported_languages:
83
+ context["supported_languages"] = supported_languages
84
+ message += f". Supported languages: {', '.join(supported_languages)}"
85
+ super().__init__(message, context=context, **kwargs)
86
+
87
+
88
+ class PluginError(TreeSitterAnalyzerError):
89
+ """Raised when plugin operations fail."""
90
+
91
+ def __init__(
92
+ self,
93
+ message: str,
94
+ plugin_name: str | None = None,
95
+ operation: str | None = None,
96
+ **kwargs: Any,
97
+ ) -> None:
98
+ context = kwargs.get("context", {})
99
+ if plugin_name:
100
+ context["plugin_name"] = plugin_name
101
+ if operation:
102
+ context["operation"] = operation
103
+ super().__init__(message, context=context, **kwargs)
104
+
105
+
106
+ class QueryError(TreeSitterAnalyzerError):
107
+ """Raised when query execution fails."""
108
+
109
+ def __init__(
110
+ self,
111
+ message: str,
112
+ query_name: str | None = None,
113
+ query_string: str | None = None,
114
+ language: str | None = None,
115
+ **kwargs: Any,
116
+ ) -> None:
117
+ context = kwargs.get("context", {})
118
+ if query_name:
119
+ context["query_name"] = query_name
120
+ if query_string:
121
+ context["query_string"] = query_string
122
+ if language:
123
+ context["language"] = language
124
+ super().__init__(message, context=context, **kwargs)
125
+
126
+
127
+ class FileHandlingError(TreeSitterAnalyzerError):
128
+ """Raised when file operations fail."""
129
+
130
+ def __init__(
131
+ self,
132
+ message: str,
133
+ file_path: str | Path | None = None,
134
+ operation: str | None = None,
135
+ **kwargs: Any,
136
+ ) -> None:
137
+ context = kwargs.get("context", {})
138
+ if file_path:
139
+ context["file_path"] = str(file_path)
140
+ if operation:
141
+ context["operation"] = operation
142
+ super().__init__(message, context=context, **kwargs)
143
+
144
+
145
+ class ConfigurationError(TreeSitterAnalyzerError):
146
+ """Raised when configuration is invalid."""
147
+
148
+ def __init__(
149
+ self,
150
+ message: str,
151
+ config_key: str | None = None,
152
+ config_value: Any | None = None,
153
+ **kwargs: Any,
154
+ ) -> None:
155
+ context = kwargs.get("context", {})
156
+ if config_key:
157
+ context["config_key"] = config_key
158
+ if config_value is not None:
159
+ context["config_value"] = config_value
160
+ super().__init__(message, context=context, **kwargs)
161
+
162
+
163
+ class ValidationError(TreeSitterAnalyzerError):
164
+ """Raised when validation fails."""
165
+
166
+ def __init__(
167
+ self,
168
+ message: str,
169
+ validation_type: str | None = None,
170
+ invalid_value: Any | None = None,
171
+ **kwargs: Any,
172
+ ) -> None:
173
+ context = kwargs.get("context", {})
174
+ if validation_type:
175
+ context["validation_type"] = validation_type
176
+ if invalid_value is not None:
177
+ context["invalid_value"] = invalid_value
178
+ super().__init__(message, context=context, **kwargs)
179
+
180
+
181
+ class MCPError(TreeSitterAnalyzerError):
182
+ """Raised when MCP operations fail."""
183
+
184
+ def __init__(
185
+ self,
186
+ message: str,
187
+ tool_name: str | None = None,
188
+ resource_uri: str | None = None,
189
+ **kwargs: Any,
190
+ ) -> None:
191
+ context = kwargs.get("context", {})
192
+ if tool_name:
193
+ context["tool_name"] = tool_name
194
+ if resource_uri:
195
+ context["resource_uri"] = resource_uri
196
+ super().__init__(message, context=context, **kwargs)
197
+
198
+
199
+ # Exception handling utilities
200
+ def handle_exception(
201
+ exception: Exception,
202
+ context: dict[str, Any] | None = None,
203
+ reraise_as: type[Exception] | None = None,
204
+ ) -> None:
205
+ """
206
+ Handle exceptions with optional context and re-raising.
207
+
208
+ Args:
209
+ exception: The original exception
210
+ context: Additional context information
211
+ reraise_as: Exception class to re-raise as
212
+ """
213
+ from .utils import log_error
214
+
215
+ # Log the original exception
216
+ error_context = context or {}
217
+ if hasattr(exception, "context"):
218
+ error_context.update(exception.context)
219
+
220
+ log_error(f"Exception handled: {exception}", extra=error_context)
221
+
222
+ # Re-raise as different exception type if requested
223
+ if reraise_as and not isinstance(exception, reraise_as):
224
+ if issubclass(reraise_as, TreeSitterAnalyzerError):
225
+ raise reraise_as(str(exception), context=error_context)
226
+ else:
227
+ raise reraise_as(str(exception))
228
+
229
+ # Re-raise original exception
230
+ raise exception
231
+
232
+
233
+ def safe_execute(
234
+ func: Any,
235
+ *args: Any,
236
+ default_return: Any = None,
237
+ exception_types: tuple[type[Exception], ...] = (Exception,),
238
+ log_errors: bool = True,
239
+ **kwargs: Any,
240
+ ) -> Any:
241
+ """
242
+ Safely execute a function with exception handling.
243
+
244
+ Args:
245
+ func: Function to execute
246
+ *args: Function arguments
247
+ default_return: Value to return on exception
248
+ exception_types: Exception types to catch
249
+ log_errors: Whether to log errors
250
+ **kwargs: Function keyword arguments
251
+
252
+ Returns:
253
+ Function result or default_return on exception
254
+ """
255
+ try:
256
+ return func(*args, **kwargs)
257
+ except exception_types as e:
258
+ if log_errors:
259
+ from .utils import log_error
260
+
261
+ log_error(f"Safe execution failed for {func.__name__}: {e}")
262
+ return default_return
263
+
264
+
265
+ def create_error_response(
266
+ exception: Exception, include_traceback: bool = False
267
+ ) -> dict[str, Any]:
268
+ """
269
+ Create standardized error response dictionary.
270
+
271
+ Args:
272
+ exception: The exception to convert
273
+ include_traceback: Whether to include traceback
274
+
275
+ Returns:
276
+ Error response dictionary
277
+ """
278
+ import traceback
279
+
280
+ response: dict[str, Any] = {
281
+ "success": False,
282
+ "error": {"type": exception.__class__.__name__, "message": str(exception)},
283
+ }
284
+
285
+ # Add context if available
286
+ if hasattr(exception, "context"):
287
+ response["error"]["context"] = exception.context
288
+
289
+ # Add error code if available
290
+ if hasattr(exception, "error_code"):
291
+ response["error"]["code"] = exception.error_code
292
+
293
+ # Add traceback if requested
294
+ if include_traceback:
295
+ response["error"]["traceback"] = traceback.format_exc()
296
+
297
+ return response
298
+
299
+
300
+ # Decorator for exception handling
301
+ def handle_exceptions(
302
+ default_return: Any = None,
303
+ exception_types: tuple[type[Exception], ...] = (Exception,),
304
+ reraise_as: type[Exception] | None = None,
305
+ log_errors: bool = True,
306
+ ) -> Any:
307
+ """
308
+ Decorator for automatic exception handling.
309
+
310
+ Args:
311
+ default_return: Value to return on exception
312
+ exception_types: Exception types to catch
313
+ reraise_as: Exception class to re-raise as
314
+ log_errors: Whether to log errors
315
+ """
316
+
317
+ def decorator(func: Any) -> Any:
318
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
319
+ try:
320
+ return func(*args, **kwargs)
321
+ except exception_types as e:
322
+ if log_errors:
323
+ from .utils import log_error
324
+
325
+ log_error(f"Exception in {func.__name__}: {e}")
326
+
327
+ if reraise_as:
328
+ if issubclass(reraise_as, TreeSitterAnalyzerError):
329
+ raise reraise_as(str(e)) from e
330
+ else:
331
+ raise reraise_as(str(e)) from e
332
+
333
+ return default_return
334
+
335
+ return wrapper
336
+
337
+ return decorator
338
+
339
+
340
+ class SecurityError(TreeSitterAnalyzerError):
341
+ """Raised when security validation fails."""
342
+
343
+ def __init__(
344
+ self,
345
+ message: str,
346
+ security_type: str | None = None,
347
+ file_path: str | Path | None = None,
348
+ **kwargs: Any,
349
+ ) -> None:
350
+ context = kwargs.get("context", {})
351
+ if security_type:
352
+ context["security_type"] = security_type
353
+ if file_path:
354
+ context["file_path"] = str(file_path)
355
+
356
+ super().__init__(message, context=context, **kwargs)
357
+ self.security_type = security_type
358
+ self.file_path = str(file_path) if file_path else None
359
+
360
+
361
+ class PathTraversalError(SecurityError):
362
+ """Raised when path traversal attack is detected."""
363
+
364
+ def __init__(
365
+ self,
366
+ message: str,
367
+ attempted_path: str | None = None,
368
+ **kwargs: Any,
369
+ ) -> None:
370
+ context = kwargs.get("context", {})
371
+ if attempted_path:
372
+ context["attempted_path"] = attempted_path
373
+
374
+ super().__init__(
375
+ message,
376
+ security_type="path_traversal",
377
+ context=context,
378
+ **kwargs
379
+ )
380
+ self.attempted_path = attempted_path
381
+
382
+
383
+ class RegexSecurityError(SecurityError):
384
+ """Raised when unsafe regex pattern is detected."""
385
+
386
+ def __init__(
387
+ self,
388
+ message: str,
389
+ pattern: str | None = None,
390
+ dangerous_construct: str | None = None,
391
+ **kwargs: Any,
392
+ ) -> None:
393
+ context = kwargs.get("context", {})
394
+ if pattern:
395
+ context["pattern"] = pattern
396
+ if dangerous_construct:
397
+ context["dangerous_construct"] = dangerous_construct
398
+
399
+ super().__init__(
400
+ message,
401
+ security_type="regex_security",
402
+ context=context,
403
+ **kwargs
404
+ )
405
+ self.pattern = pattern
406
+ self.dangerous_construct = dangerous_construct