tree-sitter-analyzer 1.8.4__py3-none-any.whl → 1.9.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 +4 -4
- tree_sitter_analyzer/cli/argument_validator.py +29 -17
- tree_sitter_analyzer/cli/commands/advanced_command.py +7 -5
- tree_sitter_analyzer/cli/commands/structure_command.py +7 -5
- tree_sitter_analyzer/cli/commands/summary_command.py +10 -6
- tree_sitter_analyzer/cli/commands/table_command.py +8 -7
- tree_sitter_analyzer/cli/info_commands.py +1 -1
- tree_sitter_analyzer/cli_main.py +3 -2
- tree_sitter_analyzer/core/analysis_engine.py +5 -5
- tree_sitter_analyzer/core/cache_service.py +3 -1
- tree_sitter_analyzer/core/query.py +17 -5
- tree_sitter_analyzer/core/query_service.py +1 -1
- tree_sitter_analyzer/encoding_utils.py +3 -3
- tree_sitter_analyzer/exceptions.py +61 -50
- tree_sitter_analyzer/file_handler.py +3 -0
- tree_sitter_analyzer/formatters/base_formatter.py +10 -5
- tree_sitter_analyzer/formatters/formatter_registry.py +83 -68
- tree_sitter_analyzer/formatters/html_formatter.py +90 -54
- tree_sitter_analyzer/formatters/javascript_formatter.py +21 -16
- tree_sitter_analyzer/formatters/language_formatter_factory.py +7 -6
- tree_sitter_analyzer/formatters/markdown_formatter.py +247 -124
- tree_sitter_analyzer/formatters/python_formatter.py +61 -38
- tree_sitter_analyzer/formatters/typescript_formatter.py +113 -45
- tree_sitter_analyzer/interfaces/mcp_server.py +2 -2
- tree_sitter_analyzer/language_detector.py +6 -6
- tree_sitter_analyzer/language_loader.py +3 -1
- tree_sitter_analyzer/languages/css_plugin.py +120 -61
- tree_sitter_analyzer/languages/html_plugin.py +159 -62
- tree_sitter_analyzer/languages/java_plugin.py +42 -34
- tree_sitter_analyzer/languages/javascript_plugin.py +59 -30
- tree_sitter_analyzer/languages/markdown_plugin.py +402 -368
- tree_sitter_analyzer/languages/python_plugin.py +111 -64
- tree_sitter_analyzer/languages/typescript_plugin.py +241 -132
- tree_sitter_analyzer/mcp/server.py +22 -18
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +13 -8
- tree_sitter_analyzer/mcp/tools/base_tool.py +2 -2
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +232 -26
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +31 -23
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +21 -19
- tree_sitter_analyzer/mcp/tools/query_tool.py +17 -18
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +30 -31
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +131 -77
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +29 -16
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +64 -51
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +34 -24
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +8 -4
- tree_sitter_analyzer/models.py +7 -5
- tree_sitter_analyzer/plugins/base.py +9 -7
- tree_sitter_analyzer/plugins/manager.py +1 -0
- tree_sitter_analyzer/queries/css.py +2 -21
- tree_sitter_analyzer/queries/html.py +2 -15
- tree_sitter_analyzer/queries/markdown.py +30 -41
- tree_sitter_analyzer/queries/python.py +20 -5
- tree_sitter_analyzer/query_loader.py +5 -5
- tree_sitter_analyzer/security/validator.py +114 -86
- tree_sitter_analyzer/utils/__init__.py +58 -28
- tree_sitter_analyzer/utils/tree_sitter_compat.py +72 -65
- tree_sitter_analyzer/utils.py +26 -15
- {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.0.dist-info}/METADATA +1 -1
- tree_sitter_analyzer-1.9.0.dist-info/RECORD +109 -0
- tree_sitter_analyzer-1.8.4.dist-info/RECORD +0 -109
- {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.0.dist-info}/WHEEL +0 -0
- {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.0.dist-info}/entry_points.txt +0 -0
|
@@ -117,41 +117,49 @@ class SecurityValidator:
|
|
|
117
117
|
is_symlink = original_path.is_symlink()
|
|
118
118
|
log_debug(f"original_path.is_symlink() = {is_symlink}")
|
|
119
119
|
if is_symlink:
|
|
120
|
-
log_warning(
|
|
120
|
+
log_warning(
|
|
121
|
+
f"Symbolic link detected in original path: {original_path}"
|
|
122
|
+
)
|
|
121
123
|
return False, "Symbolic links are not allowed"
|
|
122
|
-
|
|
124
|
+
|
|
123
125
|
# Additional check for Windows junctions and reparse points (only if exists)
|
|
124
|
-
if original_path.exists() and self._is_junction_or_reparse_point(
|
|
125
|
-
|
|
126
|
+
if original_path.exists() and self._is_junction_or_reparse_point(
|
|
127
|
+
original_path
|
|
128
|
+
):
|
|
129
|
+
log_warning(
|
|
130
|
+
f"Junction or reparse point detected in original path: {original_path}"
|
|
131
|
+
)
|
|
126
132
|
return False, "Junctions and reparse points are not allowed"
|
|
127
|
-
|
|
133
|
+
|
|
128
134
|
except (OSError, PermissionError) as e:
|
|
129
135
|
# If we can't check symlink status, continue with other checks
|
|
130
136
|
log_debug(f"Exception checking symlink status: {e}")
|
|
131
137
|
pass
|
|
132
|
-
|
|
138
|
+
|
|
133
139
|
# Then check the full path (base_path + file_path) if base_path is provided
|
|
134
140
|
if base_path:
|
|
135
141
|
norm_path = str(Path(file_path))
|
|
136
142
|
full_path = Path(base_path) / norm_path
|
|
137
|
-
|
|
143
|
+
|
|
138
144
|
# Check if the full path is a symlink or junction
|
|
139
145
|
try:
|
|
140
146
|
# Check for symlinks even if the file doesn't exist yet (broken symlinks)
|
|
141
147
|
if full_path.is_symlink():
|
|
142
148
|
log_warning(f"Symbolic link detected: {full_path}")
|
|
143
149
|
return False, "Symbolic links are not allowed"
|
|
144
|
-
|
|
150
|
+
|
|
145
151
|
# Additional check for Windows junctions and reparse points (only if exists)
|
|
146
|
-
if full_path.exists() and self._is_junction_or_reparse_point(
|
|
152
|
+
if full_path.exists() and self._is_junction_or_reparse_point(
|
|
153
|
+
full_path
|
|
154
|
+
):
|
|
147
155
|
log_warning(f"Junction or reparse point detected: {full_path}")
|
|
148
156
|
return False, "Junctions and reparse points are not allowed"
|
|
149
|
-
|
|
157
|
+
|
|
150
158
|
except (OSError, PermissionError):
|
|
151
159
|
# If we can't check symlink status due to permissions, be cautious
|
|
152
160
|
log_warning(f"Cannot verify symlink status for: {full_path}")
|
|
153
161
|
pass
|
|
154
|
-
|
|
162
|
+
|
|
155
163
|
# Check parent directories for junctions (Windows-specific security measure)
|
|
156
164
|
try:
|
|
157
165
|
if self._has_junction_in_path(full_path):
|
|
@@ -163,7 +171,7 @@ class SecurityValidator:
|
|
|
163
171
|
else:
|
|
164
172
|
# For absolute paths or when no base_path is provided, use original_path
|
|
165
173
|
full_path = original_path
|
|
166
|
-
|
|
174
|
+
|
|
167
175
|
# Check parent directories for junctions
|
|
168
176
|
try:
|
|
169
177
|
if self._has_junction_in_path(full_path):
|
|
@@ -299,14 +307,16 @@ class SecurityValidator:
|
|
|
299
307
|
log_warning(f"Glob pattern validation error: {e}")
|
|
300
308
|
return False, f"Validation error: {str(e)}"
|
|
301
309
|
|
|
302
|
-
def validate_path(
|
|
310
|
+
def validate_path(
|
|
311
|
+
self, path: str, base_path: str | None = None
|
|
312
|
+
) -> tuple[bool, str]:
|
|
303
313
|
"""
|
|
304
314
|
Alias for validate_file_path for backward compatibility.
|
|
305
|
-
|
|
315
|
+
|
|
306
316
|
Args:
|
|
307
317
|
path: Path to validate
|
|
308
318
|
base_path: Optional base path for relative path validation
|
|
309
|
-
|
|
319
|
+
|
|
310
320
|
Returns:
|
|
311
321
|
Tuple of (is_valid, error_message)
|
|
312
322
|
"""
|
|
@@ -315,11 +325,11 @@ class SecurityValidator:
|
|
|
315
325
|
def is_safe_path(self, path: str, base_path: str | None = None) -> bool:
|
|
316
326
|
"""
|
|
317
327
|
Check if a path is safe (backward compatibility method).
|
|
318
|
-
|
|
328
|
+
|
|
319
329
|
Args:
|
|
320
330
|
path: Path to check
|
|
321
331
|
base_path: Optional base path for relative path validation
|
|
322
|
-
|
|
332
|
+
|
|
323
333
|
Returns:
|
|
324
334
|
True if path is safe, False otherwise
|
|
325
335
|
"""
|
|
@@ -329,191 +339,208 @@ class SecurityValidator:
|
|
|
329
339
|
def _is_junction_or_reparse_point(self, path: Path) -> bool:
|
|
330
340
|
"""
|
|
331
341
|
Check if a path is a Windows junction or reparse point.
|
|
332
|
-
|
|
342
|
+
|
|
333
343
|
Args:
|
|
334
344
|
path: Path to check
|
|
335
|
-
|
|
345
|
+
|
|
336
346
|
Returns:
|
|
337
347
|
True if the path is a junction or reparse point
|
|
338
348
|
"""
|
|
339
349
|
try:
|
|
340
350
|
import platform
|
|
351
|
+
|
|
341
352
|
if platform.system() != "Windows":
|
|
342
353
|
return False
|
|
343
|
-
|
|
354
|
+
|
|
344
355
|
# On Windows, check for reparse points using stat
|
|
345
356
|
import stat
|
|
357
|
+
|
|
346
358
|
if path.exists():
|
|
347
359
|
path_stat = path.stat()
|
|
348
360
|
# Check if it has the reparse point attribute
|
|
349
|
-
if hasattr(stat,
|
|
350
|
-
return bool(
|
|
351
|
-
|
|
361
|
+
if hasattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT"):
|
|
362
|
+
return bool(
|
|
363
|
+
path_stat.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT
|
|
364
|
+
)
|
|
365
|
+
|
|
352
366
|
# Alternative method using Windows API
|
|
353
367
|
try:
|
|
354
368
|
import ctypes
|
|
355
369
|
from ctypes import wintypes
|
|
356
|
-
|
|
370
|
+
|
|
357
371
|
# GetFileAttributesW function
|
|
358
372
|
_GetFileAttributesW = ctypes.windll.kernel32.GetFileAttributesW
|
|
359
373
|
_GetFileAttributesW.argtypes = [wintypes.LPCWSTR]
|
|
360
374
|
_GetFileAttributesW.restype = wintypes.DWORD
|
|
361
|
-
|
|
375
|
+
|
|
362
376
|
FILE_ATTRIBUTE_REPARSE_POINT = 0x400
|
|
363
377
|
INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
|
|
364
|
-
|
|
378
|
+
|
|
365
379
|
attributes = _GetFileAttributesW(str(path))
|
|
366
380
|
if attributes != INVALID_FILE_ATTRIBUTES:
|
|
367
381
|
return bool(attributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
|
368
|
-
|
|
382
|
+
|
|
369
383
|
except (ImportError, AttributeError, OSError):
|
|
370
384
|
pass
|
|
371
|
-
|
|
385
|
+
|
|
372
386
|
except Exception:
|
|
373
387
|
# If any error occurs, assume it's not a junction for safety
|
|
374
388
|
pass
|
|
375
|
-
|
|
389
|
+
|
|
376
390
|
return False
|
|
377
391
|
|
|
378
392
|
def _has_junction_in_path(self, path: Path) -> bool:
|
|
379
393
|
"""
|
|
380
394
|
Check if any parent directory in the path is a junction.
|
|
381
|
-
|
|
395
|
+
|
|
382
396
|
Args:
|
|
383
397
|
path: Path to check
|
|
384
|
-
|
|
398
|
+
|
|
385
399
|
Returns:
|
|
386
400
|
True if any parent directory is a junction
|
|
387
401
|
"""
|
|
388
402
|
try:
|
|
389
403
|
current_path = path.resolve() if path.exists() else path
|
|
390
|
-
|
|
404
|
+
|
|
391
405
|
# Check each parent directory
|
|
392
406
|
for parent in current_path.parents:
|
|
393
407
|
if self._is_junction_or_reparse_point(parent):
|
|
394
408
|
return True
|
|
395
|
-
|
|
409
|
+
|
|
396
410
|
except Exception:
|
|
397
411
|
# If any error occurs, assume no junctions for safety
|
|
398
412
|
pass
|
|
399
|
-
|
|
413
|
+
|
|
400
414
|
return False
|
|
401
415
|
|
|
402
416
|
def _validate_windows_drive_letter(self, file_path: str) -> tuple[bool, str]:
|
|
403
417
|
"""
|
|
404
418
|
Validate Windows drive letter on non-Windows systems.
|
|
405
|
-
|
|
419
|
+
|
|
406
420
|
Args:
|
|
407
421
|
file_path: File path to validate
|
|
408
|
-
|
|
422
|
+
|
|
409
423
|
Returns:
|
|
410
424
|
Tuple of (is_valid, error_message)
|
|
411
425
|
"""
|
|
412
426
|
import platform
|
|
413
|
-
|
|
427
|
+
|
|
414
428
|
if (
|
|
415
429
|
len(file_path) > 1
|
|
416
430
|
and file_path[1] == ":"
|
|
417
431
|
and platform.system() != "Windows"
|
|
418
432
|
):
|
|
419
|
-
return
|
|
420
|
-
|
|
433
|
+
return (
|
|
434
|
+
False,
|
|
435
|
+
f"Windows drive letters are not allowed on {platform.system()} system",
|
|
436
|
+
)
|
|
437
|
+
|
|
421
438
|
return True, ""
|
|
422
439
|
|
|
423
440
|
def _validate_absolute_path(self, file_path: str) -> tuple[bool, str]:
|
|
424
441
|
"""
|
|
425
442
|
Validate absolute path with project boundary and test environment checks.
|
|
426
|
-
|
|
443
|
+
|
|
427
444
|
Args:
|
|
428
445
|
file_path: Absolute file path to validate
|
|
429
|
-
|
|
446
|
+
|
|
430
447
|
Returns:
|
|
431
448
|
Tuple of (is_valid, error_message)
|
|
432
449
|
"""
|
|
433
450
|
log_debug(f"Processing absolute path: {file_path}")
|
|
434
|
-
|
|
451
|
+
|
|
435
452
|
# Check project boundaries first (highest priority)
|
|
436
453
|
if self.boundary_manager and self.boundary_manager.project_root:
|
|
437
454
|
if not self.boundary_manager.is_within_project(file_path):
|
|
438
455
|
return False, "Absolute path must be within project directory"
|
|
439
456
|
log_debug("Absolute path is within project boundaries")
|
|
440
457
|
return True, ""
|
|
441
|
-
|
|
458
|
+
|
|
442
459
|
# If no project boundaries, check test environment allowances
|
|
443
460
|
is_test_allowed, error = self._check_test_environment_access(file_path)
|
|
444
461
|
if not is_test_allowed:
|
|
445
462
|
return False, error
|
|
446
|
-
|
|
463
|
+
|
|
447
464
|
log_debug("Absolute path allowed in test environment")
|
|
448
465
|
return True, ""
|
|
449
466
|
|
|
450
467
|
def _check_test_environment_access(self, file_path: str) -> tuple[bool, str]:
|
|
451
468
|
"""
|
|
452
469
|
Check if absolute path access is allowed in test/development environment.
|
|
453
|
-
|
|
470
|
+
|
|
454
471
|
This method allows access to system temporary directories when no project
|
|
455
472
|
boundaries are configured, which is common in test environments.
|
|
456
|
-
|
|
473
|
+
|
|
457
474
|
Args:
|
|
458
475
|
file_path: File path to check
|
|
459
|
-
|
|
476
|
+
|
|
460
477
|
Returns:
|
|
461
478
|
Tuple of (is_allowed, error_message)
|
|
462
479
|
"""
|
|
463
|
-
import tempfile
|
|
464
480
|
import os
|
|
465
|
-
|
|
481
|
+
import tempfile
|
|
482
|
+
|
|
466
483
|
try:
|
|
467
484
|
# Check if we're in a test environment
|
|
468
485
|
is_test_env = (
|
|
469
|
-
"pytest" in os.environ.get("_", "")
|
|
470
|
-
"PYTEST_CURRENT_TEST" in os.environ
|
|
471
|
-
"CI" in os.environ
|
|
472
|
-
"GITHUB_ACTIONS" in os.environ
|
|
473
|
-
any(
|
|
486
|
+
"pytest" in os.environ.get("_", "")
|
|
487
|
+
or "PYTEST_CURRENT_TEST" in os.environ
|
|
488
|
+
or "CI" in os.environ
|
|
489
|
+
or "GITHUB_ACTIONS" in os.environ
|
|
490
|
+
or any(
|
|
491
|
+
"test" in arg.lower() for arg in os.sys.argv if hasattr(os, "sys")
|
|
492
|
+
)
|
|
474
493
|
)
|
|
475
|
-
|
|
494
|
+
|
|
476
495
|
if is_test_env:
|
|
477
496
|
log_debug("Test environment detected - allowing temporary file access")
|
|
478
|
-
|
|
497
|
+
|
|
479
498
|
# Allow access to common temporary directories
|
|
480
499
|
temp_dirs = [
|
|
481
500
|
Path(tempfile.gettempdir()).resolve(),
|
|
482
501
|
Path("/tmp").resolve() if Path("/tmp").exists() else None,
|
|
483
502
|
Path("/var/tmp").resolve() if Path("/var/tmp").exists() else None,
|
|
484
503
|
]
|
|
485
|
-
|
|
504
|
+
|
|
486
505
|
real_path = Path(file_path).resolve()
|
|
487
506
|
log_debug(f"Checking test environment access: {real_path}")
|
|
488
|
-
|
|
507
|
+
|
|
489
508
|
for temp_dir in temp_dirs:
|
|
490
509
|
if temp_dir and temp_dir.exists():
|
|
491
510
|
try:
|
|
492
511
|
real_path.relative_to(temp_dir)
|
|
493
|
-
log_debug(
|
|
512
|
+
log_debug(
|
|
513
|
+
f"Path is under temp directory {temp_dir} - allowed in test environment"
|
|
514
|
+
)
|
|
494
515
|
return True, ""
|
|
495
516
|
except ValueError:
|
|
496
517
|
continue
|
|
497
|
-
|
|
518
|
+
|
|
498
519
|
# In test environment, also allow access to files that start with temp file patterns
|
|
499
520
|
file_name = Path(file_path).name
|
|
500
|
-
if (
|
|
501
|
-
"
|
|
502
|
-
|
|
503
|
-
|
|
521
|
+
if (
|
|
522
|
+
file_name.startswith(("tmp", "temp"))
|
|
523
|
+
or "_test_" in file_name
|
|
524
|
+
or file_name.endswith(("_test.py", "_test.js", ".tmp"))
|
|
525
|
+
):
|
|
526
|
+
log_debug(
|
|
527
|
+
"Temporary test file pattern detected - allowed in test environment"
|
|
528
|
+
)
|
|
504
529
|
return True, ""
|
|
505
|
-
|
|
530
|
+
|
|
506
531
|
# Fallback to original temp directory check
|
|
507
532
|
temp_dir = Path(tempfile.gettempdir()).resolve()
|
|
508
533
|
real_path = Path(file_path).resolve()
|
|
509
|
-
|
|
534
|
+
|
|
510
535
|
log_debug(f"Checking test environment access: {real_path} under {temp_dir}")
|
|
511
|
-
|
|
536
|
+
|
|
512
537
|
# Allow access under system temp directory (safe sandbox)
|
|
513
538
|
real_path.relative_to(temp_dir)
|
|
514
|
-
log_debug(
|
|
539
|
+
log_debug(
|
|
540
|
+
"Path is under system temp directory - allowed in test environment"
|
|
541
|
+
)
|
|
515
542
|
return True, ""
|
|
516
|
-
|
|
543
|
+
|
|
517
544
|
except ValueError:
|
|
518
545
|
return False, "Absolute file paths are not allowed"
|
|
519
546
|
except Exception as e:
|
|
@@ -523,45 +550,46 @@ class SecurityValidator:
|
|
|
523
550
|
def _validate_path_traversal(self, file_path: str) -> tuple[bool, str]:
|
|
524
551
|
"""
|
|
525
552
|
Validate file path for directory traversal attempts.
|
|
526
|
-
|
|
553
|
+
|
|
527
554
|
Args:
|
|
528
555
|
file_path: File path to validate
|
|
529
|
-
|
|
556
|
+
|
|
530
557
|
Returns:
|
|
531
558
|
Tuple of (is_valid, error_message)
|
|
532
559
|
"""
|
|
533
560
|
norm_path = str(Path(file_path))
|
|
534
|
-
|
|
561
|
+
|
|
535
562
|
# Check for various path traversal patterns
|
|
536
|
-
traversal_patterns = ["..\\"
|
|
537
|
-
|
|
538
|
-
if any(
|
|
563
|
+
traversal_patterns = ["..\\", "../", ".."]
|
|
564
|
+
|
|
565
|
+
if any(
|
|
566
|
+
pattern in norm_path for pattern in traversal_patterns[:2]
|
|
567
|
+
) or norm_path.startswith(traversal_patterns[2]):
|
|
539
568
|
log_warning(f"Path traversal attempt detected: {file_path} -> {norm_path}")
|
|
540
569
|
return False, "Directory traversal not allowed"
|
|
541
|
-
|
|
570
|
+
|
|
542
571
|
return True, ""
|
|
543
572
|
|
|
544
|
-
def _validate_project_boundary(
|
|
573
|
+
def _validate_project_boundary(
|
|
574
|
+
self, file_path: str, base_path: str | None
|
|
575
|
+
) -> tuple[bool, str]:
|
|
545
576
|
"""
|
|
546
577
|
Validate file path against project boundaries when base_path is provided.
|
|
547
|
-
|
|
578
|
+
|
|
548
579
|
Args:
|
|
549
580
|
file_path: File path to validate
|
|
550
581
|
base_path: Base path for relative path validation
|
|
551
|
-
|
|
582
|
+
|
|
552
583
|
Returns:
|
|
553
584
|
Tuple of (is_valid, error_message)
|
|
554
585
|
"""
|
|
555
586
|
if not (self.boundary_manager and base_path):
|
|
556
587
|
return True, ""
|
|
557
|
-
|
|
588
|
+
|
|
558
589
|
norm_path = str(Path(file_path))
|
|
559
590
|
full_path = str(Path(base_path) / norm_path)
|
|
560
|
-
|
|
591
|
+
|
|
561
592
|
if not self.boundary_manager.is_within_project(full_path):
|
|
562
|
-
return (
|
|
563
|
-
|
|
564
|
-
"Access denied. File path must be within project directory"
|
|
565
|
-
)
|
|
566
|
-
|
|
593
|
+
return (False, "Access denied. File path must be within project directory")
|
|
594
|
+
|
|
567
595
|
return True, ""
|
|
@@ -9,21 +9,23 @@ including tree-sitter API compatibility.
|
|
|
9
9
|
# Import from tree-sitter compatibility module
|
|
10
10
|
from .tree_sitter_compat import TreeSitterQueryCompat, get_node_text_safe, log_api_info
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
# Re-export logging functions from the parent utils module
|
|
13
14
|
# We need to import these dynamically to avoid circular imports
|
|
14
15
|
def _import_logging_functions():
|
|
15
16
|
"""Dynamically import logging functions to avoid circular imports."""
|
|
16
|
-
import sys
|
|
17
17
|
import importlib.util
|
|
18
18
|
import os
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
# Import the utils.py file from the parent directory
|
|
21
21
|
parent_dir = os.path.dirname(os.path.dirname(__file__))
|
|
22
|
-
utils_path = os.path.join(parent_dir,
|
|
23
|
-
spec = importlib.util.spec_from_file_location(
|
|
22
|
+
utils_path = os.path.join(parent_dir, "utils.py")
|
|
23
|
+
spec = importlib.util.spec_from_file_location(
|
|
24
|
+
"tree_sitter_analyzer_utils", utils_path
|
|
25
|
+
)
|
|
24
26
|
utils_module = importlib.util.module_from_spec(spec)
|
|
25
27
|
spec.loader.exec_module(utils_module)
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
return (
|
|
28
30
|
utils_module.setup_logger,
|
|
29
31
|
utils_module.log_debug,
|
|
@@ -35,79 +37,107 @@ def _import_logging_functions():
|
|
|
35
37
|
utils_module.safe_print,
|
|
36
38
|
utils_module.LoggingContext,
|
|
37
39
|
utils_module.setup_performance_logger,
|
|
38
|
-
utils_module.create_performance_logger
|
|
40
|
+
utils_module.create_performance_logger,
|
|
39
41
|
)
|
|
40
42
|
|
|
43
|
+
|
|
41
44
|
# Import logging functions
|
|
42
45
|
try:
|
|
43
|
-
|
|
46
|
+
(
|
|
47
|
+
setup_logger,
|
|
48
|
+
log_debug,
|
|
49
|
+
log_error,
|
|
50
|
+
log_warning,
|
|
51
|
+
log_info,
|
|
52
|
+
log_performance,
|
|
53
|
+
QuietMode,
|
|
54
|
+
safe_print,
|
|
55
|
+
LoggingContext,
|
|
56
|
+
setup_performance_logger,
|
|
57
|
+
create_performance_logger,
|
|
58
|
+
) = _import_logging_functions()
|
|
44
59
|
except Exception:
|
|
45
60
|
# Fallback logging functions if import fails
|
|
46
61
|
def setup_logger(name="tree_sitter_analyzer", level=30):
|
|
47
62
|
import logging
|
|
63
|
+
|
|
48
64
|
logger = logging.getLogger(name)
|
|
49
65
|
if not logger.handlers:
|
|
50
66
|
handler = logging.StreamHandler()
|
|
51
|
-
formatter = logging.Formatter(
|
|
67
|
+
formatter = logging.Formatter(
|
|
68
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
69
|
+
)
|
|
52
70
|
handler.setFormatter(formatter)
|
|
53
71
|
logger.addHandler(handler)
|
|
54
72
|
logger.setLevel(level)
|
|
55
73
|
return logger
|
|
74
|
+
|
|
56
75
|
def log_debug(msg, *args, **kwargs):
|
|
57
76
|
pass
|
|
77
|
+
|
|
58
78
|
def log_error(msg, *args, **kwargs):
|
|
59
79
|
print(f"ERROR: {msg}", *args)
|
|
80
|
+
|
|
60
81
|
def log_warning(msg, *args, **kwargs):
|
|
61
82
|
print(f"WARNING: {msg}", *args)
|
|
83
|
+
|
|
62
84
|
def log_info(msg, *args, **kwargs):
|
|
63
85
|
print(f"INFO: {msg}", *args)
|
|
86
|
+
|
|
64
87
|
def log_performance(operation, execution_time=None, details=None):
|
|
65
88
|
pass
|
|
66
|
-
|
|
89
|
+
|
|
67
90
|
# Fallback QuietMode class
|
|
68
91
|
class QuietMode:
|
|
69
92
|
def __init__(self, enabled=True):
|
|
70
93
|
self.enabled = enabled
|
|
94
|
+
|
|
71
95
|
def __enter__(self):
|
|
72
96
|
return self
|
|
97
|
+
|
|
73
98
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
74
99
|
pass
|
|
75
|
-
|
|
100
|
+
|
|
76
101
|
# Fallback LoggingContext class
|
|
77
102
|
class LoggingContext:
|
|
78
103
|
def __init__(self, enabled=True, level=None):
|
|
79
104
|
self.enabled = enabled
|
|
80
105
|
self.level = level
|
|
106
|
+
|
|
81
107
|
def __enter__(self):
|
|
82
108
|
return self
|
|
109
|
+
|
|
83
110
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
84
111
|
pass
|
|
85
|
-
|
|
112
|
+
|
|
86
113
|
def setup_performance_logger():
|
|
87
114
|
import logging
|
|
115
|
+
|
|
88
116
|
return logging.getLogger("performance")
|
|
89
|
-
|
|
117
|
+
|
|
90
118
|
def create_performance_logger(name):
|
|
91
119
|
import logging
|
|
120
|
+
|
|
92
121
|
return logging.getLogger(f"{name}.performance")
|
|
93
|
-
|
|
122
|
+
|
|
94
123
|
def safe_print(message, level="info", quiet=False):
|
|
95
124
|
if not quiet:
|
|
96
125
|
print(message)
|
|
97
126
|
|
|
127
|
+
|
|
98
128
|
__all__ = [
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
]
|
|
129
|
+
"TreeSitterQueryCompat",
|
|
130
|
+
"get_node_text_safe",
|
|
131
|
+
"log_api_info",
|
|
132
|
+
"setup_logger",
|
|
133
|
+
"log_debug",
|
|
134
|
+
"log_error",
|
|
135
|
+
"log_warning",
|
|
136
|
+
"log_info",
|
|
137
|
+
"log_performance",
|
|
138
|
+
"QuietMode",
|
|
139
|
+
"safe_print",
|
|
140
|
+
"LoggingContext",
|
|
141
|
+
"setup_performance_logger",
|
|
142
|
+
"create_performance_logger",
|
|
143
|
+
]
|