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

Files changed (64) hide show
  1. tree_sitter_analyzer/__init__.py +1 -1
  2. tree_sitter_analyzer/api.py +4 -4
  3. tree_sitter_analyzer/cli/argument_validator.py +29 -17
  4. tree_sitter_analyzer/cli/commands/advanced_command.py +7 -5
  5. tree_sitter_analyzer/cli/commands/structure_command.py +7 -5
  6. tree_sitter_analyzer/cli/commands/summary_command.py +10 -6
  7. tree_sitter_analyzer/cli/commands/table_command.py +8 -7
  8. tree_sitter_analyzer/cli/info_commands.py +1 -1
  9. tree_sitter_analyzer/cli_main.py +3 -2
  10. tree_sitter_analyzer/core/analysis_engine.py +5 -5
  11. tree_sitter_analyzer/core/cache_service.py +3 -1
  12. tree_sitter_analyzer/core/query.py +17 -5
  13. tree_sitter_analyzer/core/query_service.py +1 -1
  14. tree_sitter_analyzer/encoding_utils.py +3 -3
  15. tree_sitter_analyzer/exceptions.py +61 -50
  16. tree_sitter_analyzer/file_handler.py +3 -0
  17. tree_sitter_analyzer/formatters/base_formatter.py +10 -5
  18. tree_sitter_analyzer/formatters/formatter_registry.py +83 -68
  19. tree_sitter_analyzer/formatters/html_formatter.py +90 -64
  20. tree_sitter_analyzer/formatters/javascript_formatter.py +21 -16
  21. tree_sitter_analyzer/formatters/language_formatter_factory.py +7 -6
  22. tree_sitter_analyzer/formatters/markdown_formatter.py +247 -124
  23. tree_sitter_analyzer/formatters/python_formatter.py +61 -38
  24. tree_sitter_analyzer/formatters/typescript_formatter.py +113 -45
  25. tree_sitter_analyzer/interfaces/mcp_server.py +2 -2
  26. tree_sitter_analyzer/language_detector.py +6 -6
  27. tree_sitter_analyzer/language_loader.py +3 -1
  28. tree_sitter_analyzer/languages/css_plugin.py +120 -61
  29. tree_sitter_analyzer/languages/html_plugin.py +159 -62
  30. tree_sitter_analyzer/languages/java_plugin.py +42 -34
  31. tree_sitter_analyzer/languages/javascript_plugin.py +59 -30
  32. tree_sitter_analyzer/languages/markdown_plugin.py +402 -368
  33. tree_sitter_analyzer/languages/python_plugin.py +111 -64
  34. tree_sitter_analyzer/languages/typescript_plugin.py +241 -132
  35. tree_sitter_analyzer/mcp/server.py +22 -18
  36. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +13 -8
  37. tree_sitter_analyzer/mcp/tools/base_tool.py +2 -2
  38. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +232 -26
  39. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +31 -23
  40. tree_sitter_analyzer/mcp/tools/list_files_tool.py +21 -19
  41. tree_sitter_analyzer/mcp/tools/query_tool.py +17 -18
  42. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +30 -31
  43. tree_sitter_analyzer/mcp/tools/search_content_tool.py +131 -77
  44. tree_sitter_analyzer/mcp/tools/table_format_tool.py +29 -16
  45. tree_sitter_analyzer/mcp/utils/file_output_factory.py +64 -51
  46. tree_sitter_analyzer/mcp/utils/file_output_manager.py +34 -24
  47. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +8 -4
  48. tree_sitter_analyzer/models.py +7 -5
  49. tree_sitter_analyzer/plugins/base.py +9 -7
  50. tree_sitter_analyzer/plugins/manager.py +1 -0
  51. tree_sitter_analyzer/queries/css.py +2 -21
  52. tree_sitter_analyzer/queries/html.py +2 -15
  53. tree_sitter_analyzer/queries/markdown.py +30 -41
  54. tree_sitter_analyzer/queries/python.py +20 -5
  55. tree_sitter_analyzer/query_loader.py +5 -5
  56. tree_sitter_analyzer/security/validator.py +114 -86
  57. tree_sitter_analyzer/utils/__init__.py +58 -28
  58. tree_sitter_analyzer/utils/tree_sitter_compat.py +72 -65
  59. tree_sitter_analyzer/utils.py +26 -15
  60. {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.1.dist-info}/METADATA +23 -6
  61. tree_sitter_analyzer-1.9.1.dist-info/RECORD +109 -0
  62. tree_sitter_analyzer-1.8.4.dist-info/RECORD +0 -109
  63. {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.1.dist-info}/WHEEL +0 -0
  64. {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.1.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(f"Symbolic link detected in original path: {original_path}")
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(original_path):
125
- log_warning(f"Junction or reparse point detected in original path: {original_path}")
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(full_path):
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(self, path: str, base_path: str | None = None) -> tuple[bool, str]:
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, 'FILE_ATTRIBUTE_REPARSE_POINT'):
350
- return bool(path_stat.st_file_attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT)
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 False, f"Windows drive letters are not allowed on {platform.system()} system"
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("_", "") or
470
- "PYTEST_CURRENT_TEST" in os.environ or
471
- "CI" in os.environ or
472
- "GITHUB_ACTIONS" in os.environ or
473
- any("test" in arg.lower() for arg in os.sys.argv if hasattr(os, 'sys'))
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(f"Path is under temp directory {temp_dir} - allowed in test environment")
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 (file_name.startswith(("tmp", "temp")) or
501
- "_test_" in file_name or
502
- file_name.endswith(("_test.py", "_test.js", ".tmp"))):
503
- log_debug("Temporary test file pattern detected - allowed in test environment")
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("Path is under system temp directory - allowed in test environment")
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(pattern in norm_path for pattern in traversal_patterns[:2]) or norm_path.startswith(traversal_patterns[2]):
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(self, file_path: str, base_path: str | None) -> tuple[bool, str]:
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
- False,
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, 'utils.py')
23
- spec = importlib.util.spec_from_file_location("tree_sitter_analyzer_utils", utils_path)
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
- setup_logger, log_debug, log_error, log_warning, log_info, log_performance, QuietMode, safe_print, LoggingContext, setup_performance_logger, create_performance_logger = _import_logging_functions()
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("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
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
- 'TreeSitterQueryCompat',
100
- 'get_node_text_safe',
101
- 'log_api_info',
102
- 'setup_logger',
103
- 'log_debug',
104
- 'log_error',
105
- 'log_warning',
106
- 'log_info',
107
- 'log_performance',
108
- 'QuietMode',
109
- 'safe_print',
110
- 'LoggingContext',
111
- 'setup_performance_logger',
112
- 'create_performance_logger'
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
+ ]