tree-sitter-analyzer 0.9.2__py3-none-any.whl → 0.9.4__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 (37) hide show
  1. tree_sitter_analyzer/__init__.py +1 -1
  2. tree_sitter_analyzer/cli/commands/base_command.py +2 -3
  3. tree_sitter_analyzer/cli/commands/default_command.py +18 -18
  4. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -141
  5. tree_sitter_analyzer/cli/commands/query_command.py +92 -88
  6. tree_sitter_analyzer/cli/commands/table_command.py +235 -235
  7. tree_sitter_analyzer/cli/info_commands.py +121 -121
  8. tree_sitter_analyzer/cli_main.py +307 -303
  9. tree_sitter_analyzer/core/analysis_engine.py +584 -576
  10. tree_sitter_analyzer/core/cache_service.py +6 -5
  11. tree_sitter_analyzer/core/query.py +502 -502
  12. tree_sitter_analyzer/encoding_utils.py +6 -2
  13. tree_sitter_analyzer/exceptions.py +400 -406
  14. tree_sitter_analyzer/formatters/java_formatter.py +291 -291
  15. tree_sitter_analyzer/formatters/python_formatter.py +259 -259
  16. tree_sitter_analyzer/interfaces/cli.py +1 -1
  17. tree_sitter_analyzer/interfaces/cli_adapter.py +3 -3
  18. tree_sitter_analyzer/interfaces/mcp_server.py +426 -425
  19. tree_sitter_analyzer/language_detector.py +398 -398
  20. tree_sitter_analyzer/language_loader.py +224 -224
  21. tree_sitter_analyzer/languages/java_plugin.py +1202 -1202
  22. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +559 -555
  23. tree_sitter_analyzer/mcp/server.py +30 -9
  24. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +21 -4
  25. tree_sitter_analyzer/mcp/tools/table_format_tool.py +22 -4
  26. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -567
  27. tree_sitter_analyzer/models.py +470 -470
  28. tree_sitter_analyzer/project_detector.py +330 -317
  29. tree_sitter_analyzer/security/__init__.py +22 -22
  30. tree_sitter_analyzer/security/boundary_manager.py +243 -237
  31. tree_sitter_analyzer/security/regex_checker.py +297 -292
  32. tree_sitter_analyzer/table_formatter.py +703 -652
  33. tree_sitter_analyzer/utils.py +53 -22
  34. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/METADATA +13 -13
  35. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/RECORD +37 -37
  36. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/WHEEL +0 -0
  37. {tree_sitter_analyzer-0.9.2.dist-info → tree_sitter_analyzer-0.9.4.dist-info}/entry_points.txt +0 -0
@@ -1,406 +1,400 @@
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
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, security_type="path_traversal", context=context, **kwargs
376
+ )
377
+ self.attempted_path = attempted_path
378
+
379
+
380
+ class RegexSecurityError(SecurityError):
381
+ """Raised when unsafe regex pattern is detected."""
382
+
383
+ def __init__(
384
+ self,
385
+ message: str,
386
+ pattern: str | None = None,
387
+ dangerous_construct: str | None = None,
388
+ **kwargs: Any,
389
+ ) -> None:
390
+ context = kwargs.get("context", {})
391
+ if pattern:
392
+ context["pattern"] = pattern
393
+ if dangerous_construct:
394
+ context["dangerous_construct"] = dangerous_construct
395
+
396
+ super().__init__(
397
+ message, security_type="regex_security", context=context, **kwargs
398
+ )
399
+ self.pattern = pattern
400
+ self.dangerous_construct = dangerous_construct