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.

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 -54
  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.0.dist-info}/METADATA +1 -1
  61. tree_sitter_analyzer-1.9.0.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.0.dist-info}/WHEEL +0 -0
  64. {tree_sitter_analyzer-1.8.4.dist-info → tree_sitter_analyzer-1.9.0.dist-info}/entry_points.txt +0 -0
@@ -11,7 +11,7 @@ Architecture:
11
11
  - Data Models: Generic and language-specific code element representations
12
12
  """
13
13
 
14
- __version__ = "1.8.4"
14
+ __version__ = "1.9.0"
15
15
  __author__ = "aisheng.yu"
16
16
  __email__ = "aimasteracc@gmail.com"
17
17
 
@@ -65,7 +65,7 @@ def analyze_file(
65
65
 
66
66
  # Perform the analysis
67
67
  analysis_result = engine.analyze_file(file_path, language)
68
-
68
+
69
69
  # Convert AnalysisResult to expected API format (same as analyze_code)
70
70
  result = {
71
71
  "success": analysis_result.success,
@@ -375,15 +375,15 @@ def detect_language(file_path: str | Path) -> str:
375
375
  # Handle invalid input
376
376
  if not file_path:
377
377
  return "unknown"
378
-
378
+
379
379
  engine = get_engine()
380
380
  # Use language_detector instead of language_registry
381
381
  result = engine.language_detector.detect_from_extension(str(file_path))
382
-
382
+
383
383
  # Ensure result is valid
384
384
  if not result or result.strip() == "":
385
385
  return "unknown"
386
-
386
+
387
387
  return result
388
388
  except Exception as e:
389
389
  log_error(f"Failed to detect language for {file_path}: {e}")
@@ -5,7 +5,7 @@ CLI Argument Validator
5
5
  Validates CLI argument combinations and provides clear error messages.
6
6
  """
7
7
 
8
- from typing import Any, Optional
8
+ from typing import Any
9
9
 
10
10
 
11
11
  class CLIArgumentValidator:
@@ -15,48 +15,60 @@ class CLIArgumentValidator:
15
15
  """Initialize the validator."""
16
16
  pass
17
17
 
18
- def validate_arguments(self, args: Any) -> Optional[str]:
18
+ def validate_arguments(self, args: Any) -> str | None:
19
19
  """
20
20
  Validate CLI argument combinations.
21
-
21
+
22
22
  Args:
23
23
  args: Parsed command line arguments
24
-
24
+
25
25
  Returns:
26
26
  None if valid, error message string if invalid
27
27
  """
28
28
  # Check for --table and --query-key combination
29
- table_specified = hasattr(args, 'table') and args.table is not None and args.table != ""
30
- query_key_specified = hasattr(args, 'query_key') and args.query_key is not None and args.query_key != ""
31
-
29
+ table_specified = (
30
+ hasattr(args, "table") and args.table is not None and args.table != ""
31
+ )
32
+ query_key_specified = (
33
+ hasattr(args, "query_key")
34
+ and args.query_key is not None
35
+ and args.query_key != ""
36
+ )
37
+
32
38
  if table_specified and query_key_specified:
33
39
  return "--table and --query-key cannot be used together. Use --query-key with --filter instead."
34
-
40
+
35
41
  # All validations passed
36
42
  return None
37
43
 
38
- def validate_table_query_exclusivity(self, args: Any) -> Optional[str]:
44
+ def validate_table_query_exclusivity(self, args: Any) -> str | None:
39
45
  """
40
46
  Validate that --table and --query-key are mutually exclusive.
41
-
47
+
42
48
  Args:
43
49
  args: Parsed command line arguments
44
-
50
+
45
51
  Returns:
46
52
  None if valid, error message string if invalid
47
53
  """
48
- table_specified = hasattr(args, 'table') and args.table is not None and args.table != ""
49
- query_key_specified = hasattr(args, 'query_key') and args.query_key is not None and args.query_key != ""
50
-
54
+ table_specified = (
55
+ hasattr(args, "table") and args.table is not None and args.table != ""
56
+ )
57
+ query_key_specified = (
58
+ hasattr(args, "query_key")
59
+ and args.query_key is not None
60
+ and args.query_key != ""
61
+ )
62
+
51
63
  if table_specified and query_key_specified:
52
64
  return "--table and --query-key cannot be used together. Use --query-key with --filter instead."
53
-
65
+
54
66
  return None
55
67
 
56
68
  def get_usage_examples(self) -> str:
57
69
  """
58
70
  Get usage examples for correct argument combinations.
59
-
71
+
60
72
  Returns:
61
73
  String containing usage examples
62
74
  """
@@ -74,4 +86,4 @@ uv run python -m tree_sitter_analyzer examples/BigService.java --query-key metho
74
86
 
75
87
  # Invalid combination (will cause error):
76
88
  uv run python -m tree_sitter_analyzer examples/BigService.java --table full --query-key methods
77
- """
89
+ """
@@ -14,8 +14,8 @@ from ...constants import (
14
14
  ELEMENT_TYPE_VARIABLE,
15
15
  get_element_type,
16
16
  )
17
- from ...output_manager import output_data, output_json, output_section
18
17
  from ...formatters.language_formatter_factory import create_language_formatter
18
+ from ...output_manager import output_data, output_json, output_section
19
19
  from .base_command import BaseCommand
20
20
 
21
21
  if TYPE_CHECKING:
@@ -163,8 +163,10 @@ class AdvancedCommand(BaseCommand):
163
163
  formatter = create_language_formatter(analysis_result.language)
164
164
  if formatter:
165
165
  # Use language-specific formatter
166
- output_format = getattr(self.args, 'output_format', 'json')
167
- formatted_output = formatter.format_advanced(self._convert_to_formatter_format(analysis_result), output_format)
166
+ output_format = getattr(self.args, "output_format", "json")
167
+ formatted_output = formatter.format_advanced(
168
+ self._convert_to_formatter_format(analysis_result), output_format
169
+ )
168
170
  print(formatted_output)
169
171
  return
170
172
 
@@ -220,7 +222,7 @@ class AdvancedCommand(BaseCommand):
220
222
  "line_range": {
221
223
  "start": getattr(element, "start_line", 0),
222
224
  "end": getattr(element, "end_line", 0),
223
- }
225
+ },
224
226
  }
225
227
  for element in analysis_result.elements
226
228
  ],
@@ -231,7 +233,7 @@ class AdvancedCommand(BaseCommand):
231
233
  "language": analysis_result.language,
232
234
  "file_path": analysis_result.file_path,
233
235
  "analyzer_version": "2.0.0",
234
- }
236
+ },
235
237
  }
236
238
 
237
239
  def _output_text_analysis(self, analysis_result: "AnalysisResult") -> None:
@@ -15,8 +15,8 @@ from ...constants import (
15
15
  ELEMENT_TYPE_VARIABLE,
16
16
  is_element_of_type,
17
17
  )
18
- from ...output_manager import output_data, output_json, output_section
19
18
  from ...formatters.language_formatter_factory import create_language_formatter
19
+ from ...output_manager import output_data, output_json, output_section
20
20
  from .base_command import BaseCommand
21
21
 
22
22
  if TYPE_CHECKING:
@@ -40,7 +40,9 @@ class StructureCommand(BaseCommand):
40
40
  formatter = create_language_formatter(analysis_result.language)
41
41
  if formatter:
42
42
  # Use language-specific formatter
43
- formatted_output = formatter.format_structure(self._convert_to_formatter_format(analysis_result))
43
+ formatted_output = formatter.format_structure(
44
+ self._convert_to_formatter_format(analysis_result)
45
+ )
44
46
  print(formatted_output)
45
47
  return
46
48
 
@@ -58,7 +60,7 @@ class StructureCommand(BaseCommand):
58
60
  def _convert_to_formatter_format(self, analysis_result: "AnalysisResult") -> dict:
59
61
  """Convert AnalysisResult to format expected by formatters."""
60
62
  from ...constants import get_element_type
61
-
63
+
62
64
  return {
63
65
  "file_path": analysis_result.file_path,
64
66
  "language": analysis_result.language,
@@ -82,7 +84,7 @@ class StructureCommand(BaseCommand):
82
84
  "line_range": {
83
85
  "start": getattr(element, "start_line", 0),
84
86
  "end": getattr(element, "end_line", 0),
85
- }
87
+ },
86
88
  }
87
89
  for element in analysis_result.elements
88
90
  ],
@@ -91,7 +93,7 @@ class StructureCommand(BaseCommand):
91
93
  "language": analysis_result.language,
92
94
  "file_path": analysis_result.file_path,
93
95
  "analyzer_version": "2.0.0",
94
- }
96
+ },
95
97
  }
96
98
 
97
99
  def _convert_to_legacy_format(self, analysis_result: "AnalysisResult") -> dict:
@@ -14,8 +14,8 @@ from ...constants import (
14
14
  ELEMENT_TYPE_VARIABLE,
15
15
  is_element_of_type,
16
16
  )
17
- from ...output_manager import output_data, output_json, output_section
18
17
  from ...formatters.language_formatter_factory import create_language_formatter
18
+ from ...output_manager import output_data, output_json, output_section
19
19
  from .base_command import BaseCommand
20
20
 
21
21
  if TYPE_CHECKING:
@@ -39,7 +39,9 @@ class SummaryCommand(BaseCommand):
39
39
  formatter = create_language_formatter(analysis_result.language)
40
40
  if formatter:
41
41
  # Use language-specific formatter
42
- formatted_output = formatter.format_summary(self._convert_to_formatter_format(analysis_result))
42
+ formatted_output = formatter.format_summary(
43
+ self._convert_to_formatter_format(analysis_result)
44
+ )
43
45
  print(formatted_output)
44
46
  return
45
47
 
@@ -106,10 +108,12 @@ class SummaryCommand(BaseCommand):
106
108
  else:
107
109
  self._output_text_format(summary_data, requested_types)
108
110
 
109
- def _convert_to_formatter_format(self, analysis_result: "AnalysisResult") -> dict[str, Any]:
111
+ def _convert_to_formatter_format(
112
+ self, analysis_result: "AnalysisResult"
113
+ ) -> dict[str, Any]:
110
114
  """Convert AnalysisResult to format expected by formatters."""
111
115
  from ...constants import get_element_type
112
-
116
+
113
117
  return {
114
118
  "file_path": analysis_result.file_path,
115
119
  "language": analysis_result.language,
@@ -133,7 +137,7 @@ class SummaryCommand(BaseCommand):
133
137
  "line_range": {
134
138
  "start": getattr(element, "start_line", 0),
135
139
  "end": getattr(element, "end_line", 0),
136
- }
140
+ },
137
141
  }
138
142
  for element in analysis_result.elements
139
143
  ],
@@ -142,7 +146,7 @@ class SummaryCommand(BaseCommand):
142
146
  "language": analysis_result.language,
143
147
  "file_path": analysis_result.file_path,
144
148
  "analyzer_version": "2.0.0",
145
- }
149
+ },
146
150
  }
147
151
 
148
152
  def _output_text_format(self, summary_data: dict, requested_types: list) -> None:
@@ -16,9 +16,9 @@ from ...constants import (
16
16
  ELEMENT_TYPE_VARIABLE,
17
17
  get_element_type,
18
18
  )
19
- from ...output_manager import output_error, output_info
20
- from ...table_formatter import create_table_formatter
21
19
  from ...formatters.language_formatter_factory import create_language_formatter
20
+ from ...output_manager import output_error
21
+ from ...table_formatter import create_table_formatter
22
22
  from .base_command import BaseCommand
23
23
 
24
24
 
@@ -41,8 +41,10 @@ class TableCommand(BaseCommand):
41
41
  formatter = create_language_formatter(analysis_result.language)
42
42
  if formatter:
43
43
  # Use language-specific formatter
44
- table_type = getattr(self.args, 'table', 'full')
45
- formatted_output = formatter.format_table(self._convert_to_formatter_format(analysis_result), table_type)
44
+ table_type = getattr(self.args, "table", "full")
45
+ formatted_output = formatter.format_table(
46
+ self._convert_to_formatter_format(analysis_result), table_type
47
+ )
46
48
  self._output_table(formatted_output)
47
49
  return 0
48
50
 
@@ -68,7 +70,6 @@ class TableCommand(BaseCommand):
68
70
  output_error(f"An error occurred during table format analysis: {e}")
69
71
  return 1
70
72
 
71
-
72
73
  def _convert_to_formatter_format(self, analysis_result: Any) -> dict[str, Any]:
73
74
  """Convert AnalysisResult to format expected by formatters."""
74
75
  return {
@@ -94,7 +95,7 @@ class TableCommand(BaseCommand):
94
95
  "line_range": {
95
96
  "start": getattr(element, "start_line", 0),
96
97
  "end": getattr(element, "end_line", 0),
97
- }
98
+ },
98
99
  }
99
100
  for element in analysis_result.elements
100
101
  ],
@@ -103,7 +104,7 @@ class TableCommand(BaseCommand):
103
104
  "language": analysis_result.language,
104
105
  "file_path": analysis_result.file_path,
105
106
  "analyzer_version": "2.0.0",
106
- }
107
+ },
107
108
  }
108
109
 
109
110
  def _convert_to_structure_format(
@@ -101,7 +101,7 @@ class ShowLanguagesCommand(InfoCommand):
101
101
  info = detector.get_language_info(language)
102
102
  extensions = ", ".join(info["extensions"][:5])
103
103
  if len(info["extensions"]) > 5:
104
- extensions += f", ... ({len(info['extensions'])-5} more)"
104
+ extensions += f", ... ({len(info['extensions']) - 5} more)"
105
105
  output_list(f" {language:<12} - Extensions: {extensions}")
106
106
  return 0
107
107
 
@@ -7,6 +7,8 @@ import os
7
7
  import sys
8
8
  from typing import Any
9
9
 
10
+ from .cli.argument_validator import CLIArgumentValidator
11
+
10
12
  # Import command classes
11
13
  from .cli.commands import (
12
14
  AdvancedCommand,
@@ -25,7 +27,6 @@ from .cli.info_commands import (
25
27
  )
26
28
  from .output_manager import output_error, output_info, output_list
27
29
  from .query_loader import query_loader
28
- from .cli.argument_validator import CLIArgumentValidator
29
30
 
30
31
 
31
32
  class CLICommandFactory:
@@ -34,7 +35,7 @@ class CLICommandFactory:
34
35
  @staticmethod
35
36
  def create_command(args: argparse.Namespace) -> Any:
36
37
  """Create appropriate command based on arguments."""
37
-
38
+
38
39
  # Validate argument combinations first
39
40
  validator = CLIArgumentValidator()
40
41
  validation_error = validator.validate_arguments(args)
@@ -207,7 +207,7 @@ class UnifiedAnalysisEngine:
207
207
  _instances: dict[str, "UnifiedAnalysisEngine"] = {}
208
208
  _lock: threading.Lock = threading.Lock()
209
209
 
210
- def __new__(cls, project_root: str = None) -> "UnifiedAnalysisEngine":
210
+ def __new__(cls, project_root: str | None = None) -> "UnifiedAnalysisEngine":
211
211
  """Singleton instance sharing (project_root aware)"""
212
212
  # Create a key based on project_root for different instances
213
213
  instance_key = project_root or "default"
@@ -218,13 +218,13 @@ class UnifiedAnalysisEngine:
218
218
  instance = super().__new__(cls)
219
219
  cls._instances[instance_key] = instance
220
220
  # Mark as not initialized for this instance
221
- instance._initialized = False
221
+ instance._initialized: bool = False
222
222
 
223
223
  return cls._instances[instance_key]
224
224
 
225
- def __init__(self, project_root: str = None) -> None:
225
+ def __init__(self, project_root: str | None = None) -> None:
226
226
  """Initialize (executed only once per instance)"""
227
- if hasattr(self, "_initialized") and self._initialized:
227
+ if hasattr(self, "_initialized") and getattr(self, "_initialized", False):
228
228
  return
229
229
 
230
230
  self._cache_service = CacheService()
@@ -566,7 +566,7 @@ class MockLanguagePlugin:
566
566
  )
567
567
 
568
568
 
569
- def get_analysis_engine(project_root: str = None) -> UnifiedAnalysisEngine:
569
+ def get_analysis_engine(project_root: str | None = None) -> UnifiedAnalysisEngine:
570
570
  """
571
571
  Get unified analysis engine instance
572
572
 
@@ -20,7 +20,7 @@ from typing import Any
20
20
 
21
21
  from cachetools import LRUCache, TTLCache
22
22
 
23
- from ..utils import log_debug, log_error, log_info
23
+ from ..utils import log_debug, log_info
24
24
 
25
25
 
26
26
  @dataclass(frozen=True)
@@ -227,6 +227,7 @@ class CacheService:
227
227
 
228
228
  # Only log if not in quiet mode (check log level)
229
229
  import logging
230
+
230
231
  if logging.getLogger("tree_sitter_analyzer").level <= logging.INFO:
231
232
  log_info("All caches cleared")
232
233
 
@@ -320,6 +321,7 @@ class CacheService:
320
321
  try:
321
322
  # Only clear if not in shutdown mode
322
323
  import sys
324
+
323
325
  if sys.meta_path is not None: # Check if Python is not shutting down
324
326
  # Clear caches without logging to avoid shutdown issues
325
327
  with self._lock:
@@ -78,14 +78,22 @@ class QueryExecutor:
78
78
  if not language_name:
79
79
  language_name = getattr(language, "_name", None)
80
80
  if not language_name:
81
- language_name = str(language).split('.')[-1] if hasattr(language, '__class__') else None
82
-
81
+ language_name = (
82
+ str(language).split(".")[-1]
83
+ if hasattr(language, "__class__")
84
+ else None
85
+ )
86
+
83
87
  # Ensure we have a valid language name
84
- if not language_name or language_name.strip() == "" or language_name == "None":
88
+ if (
89
+ not language_name
90
+ or language_name.strip() == ""
91
+ or language_name == "None"
92
+ ):
85
93
  language_name = "unknown"
86
94
  else:
87
95
  language_name = language_name.strip().lower()
88
-
96
+
89
97
  query_string = self._query_loader.get_query(language_name, query_name)
90
98
  if query_string is None:
91
99
  return self._create_error_result(
@@ -244,7 +252,11 @@ class QueryExecutor:
244
252
  if isinstance(capture, tuple) and len(capture) == 2:
245
253
  node, name = capture
246
254
  # Handle dictionary format (legacy API compatibility)
247
- elif isinstance(capture, dict) and "node" in capture and "name" in capture:
255
+ elif (
256
+ isinstance(capture, dict)
257
+ and "node" in capture
258
+ and "name" in capture
259
+ ):
248
260
  node = capture["node"]
249
261
  name = capture["name"]
250
262
  else:
@@ -298,7 +298,7 @@ class QueryService:
298
298
  node_type = getattr(node, "type", "")
299
299
  if not isinstance(node_type, str):
300
300
  node_type = str(node_type)
301
-
301
+
302
302
  # Generic node type matching (support both singular and plural forms)
303
303
  if query_key in ("function", "functions") and "function" in node_type:
304
304
  captures.append((node, query_key))
@@ -72,9 +72,9 @@ class EncodingCache:
72
72
  max_size: Maximum number of cached entries
73
73
  ttl_seconds: Time-to-live for cache entries in seconds
74
74
  """
75
- self._cache: dict[str, tuple[str, float]] = (
76
- {}
77
- ) # file_path -> (encoding, timestamp)
75
+ self._cache: dict[
76
+ str, tuple[str, float]
77
+ ] = {} # file_path -> (encoding, timestamp)
78
78
  self._lock = threading.RLock()
79
79
  self._max_size = max_size
80
80
  self._ttl_seconds = ttl_seconds