tree-sitter-analyzer 1.0.0__py3-none-any.whl → 1.1.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 (29) hide show
  1. tree_sitter_analyzer/__init__.py +132 -132
  2. tree_sitter_analyzer/api.py +542 -542
  3. tree_sitter_analyzer/cli/commands/base_command.py +181 -181
  4. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -139
  5. tree_sitter_analyzer/cli/info_commands.py +124 -124
  6. tree_sitter_analyzer/cli_main.py +327 -327
  7. tree_sitter_analyzer/core/analysis_engine.py +584 -584
  8. tree_sitter_analyzer/core/query_service.py +162 -162
  9. tree_sitter_analyzer/file_handler.py +212 -212
  10. tree_sitter_analyzer/formatters/base_formatter.py +169 -169
  11. tree_sitter_analyzer/interfaces/cli.py +535 -535
  12. tree_sitter_analyzer/mcp/__init__.py +1 -1
  13. tree_sitter_analyzer/mcp/resources/__init__.py +1 -1
  14. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +16 -5
  15. tree_sitter_analyzer/mcp/server.py +655 -655
  16. tree_sitter_analyzer/mcp/tools/__init__.py +30 -30
  17. tree_sitter_analyzer/mcp/utils/__init__.py +2 -2
  18. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -569
  19. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -414
  20. tree_sitter_analyzer/output_manager.py +257 -257
  21. tree_sitter_analyzer/project_detector.py +330 -330
  22. tree_sitter_analyzer/security/boundary_manager.py +260 -260
  23. tree_sitter_analyzer/security/validator.py +257 -257
  24. tree_sitter_analyzer/table_formatter.py +710 -710
  25. tree_sitter_analyzer/utils.py +335 -335
  26. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/METADATA +11 -11
  27. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/RECORD +29 -29
  28. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/WHEEL +0 -0
  29. {tree_sitter_analyzer-1.0.0.dist-info → tree_sitter_analyzer-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -1,181 +1,181 @@
1
- #!/usr/bin/env python3
2
- """
3
- Base Command Class
4
-
5
- Abstract base class for all CLI commands implementing the Command Pattern.
6
- """
7
-
8
- import asyncio
9
- from abc import ABC, abstractmethod
10
- from argparse import Namespace
11
- from typing import Optional
12
-
13
- from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
14
- from ...file_handler import read_file_partial
15
- from ...language_detector import detect_language_from_file, is_language_supported
16
- from ...models import AnalysisResult
17
- from ...output_manager import output_error, output_info
18
- from ...project_detector import detect_project_root
19
- from ...security import SecurityValidator
20
-
21
-
22
- class BaseCommand(ABC):
23
- """
24
- Base class for all CLI commands.
25
-
26
- Implements common functionality like file validation, language detection,
27
- and analysis engine interaction.
28
- """
29
-
30
- def __init__(self, args: Namespace):
31
- """Initialize command with parsed arguments."""
32
- self.args = args
33
-
34
- # Detect project root with priority handling
35
- file_path = getattr(args, "file_path", None)
36
- explicit_root = getattr(args, "project_root", None)
37
- self.project_root = detect_project_root(file_path, explicit_root)
38
-
39
- # Initialize components with project root
40
- self.analysis_engine = get_analysis_engine(self.project_root)
41
- self.security_validator = SecurityValidator(self.project_root)
42
-
43
- def validate_file(self) -> bool:
44
- """Validate input file exists and is accessible."""
45
- if not hasattr(self.args, "file_path") or not self.args.file_path:
46
- output_error("File path not specified.")
47
- return False
48
-
49
- # Security validation
50
- is_valid, error_msg = self.security_validator.validate_file_path(
51
- self.args.file_path
52
- )
53
- if not is_valid:
54
- output_error(f"Invalid file path: {error_msg}")
55
- return False
56
-
57
- from pathlib import Path
58
-
59
- if not Path(self.args.file_path).exists():
60
- output_error("Invalid file path: file does not exist")
61
- return False
62
-
63
- return True
64
-
65
- def detect_language(self) -> str | None:
66
- """Detect or validate the target language."""
67
- if hasattr(self.args, "language") and self.args.language:
68
- # Sanitize language input
69
- sanitized_language = self.security_validator.sanitize_input(
70
- self.args.language, max_length=50
71
- )
72
- target_language = sanitized_language.lower()
73
- if (not hasattr(self.args, "table") or not self.args.table) and (
74
- not hasattr(self.args, "quiet") or not self.args.quiet
75
- ):
76
- output_info(f"INFO: Language explicitly specified: {target_language}")
77
- else:
78
- target_language = detect_language_from_file(self.args.file_path)
79
- if target_language == "unknown":
80
- output_error(
81
- f"ERROR: Could not determine language for file '{self.args.file_path}'."
82
- )
83
- return None
84
- else:
85
- if (not hasattr(self.args, "table") or not self.args.table) and (
86
- not hasattr(self.args, "quiet") or not self.args.quiet
87
- ):
88
- # Language auto-detected - only show in verbose mode
89
- pass
90
-
91
- # Language support validation
92
- if not is_language_supported(target_language):
93
- if target_language != "java":
94
- if (not hasattr(self.args, "table") or not self.args.table) and (
95
- not hasattr(self.args, "quiet") or not self.args.quiet
96
- ):
97
- output_info(
98
- "INFO: Trying with Java analysis engine. May not work correctly."
99
- )
100
- target_language = "java" # Fallback
101
-
102
- return str(target_language) if target_language else None
103
-
104
- async def analyze_file(self, language: str) -> Optional["AnalysisResult"]:
105
- """Perform file analysis using the unified analysis engine."""
106
- try:
107
- # Handle partial read if enabled
108
- if hasattr(self.args, "partial_read") and self.args.partial_read:
109
- try:
110
- partial_content = read_file_partial(
111
- self.args.file_path,
112
- start_line=self.args.start_line,
113
- end_line=getattr(self.args, "end_line", None),
114
- start_column=getattr(self.args, "start_column", None),
115
- end_column=getattr(self.args, "end_column", None),
116
- )
117
- if partial_content is None:
118
- output_error("Failed to read file partially")
119
- return None
120
- except Exception as e:
121
- output_error(f"Failed to read file partially: {e}")
122
- return None
123
-
124
- request = AnalysisRequest(
125
- file_path=self.args.file_path,
126
- language=language,
127
- include_complexity=True,
128
- include_details=True,
129
- )
130
- analysis_result = await self.analysis_engine.analyze(request)
131
-
132
- if not analysis_result or not analysis_result.success:
133
- error_msg = (
134
- analysis_result.error_message
135
- if analysis_result
136
- else "Unknown error"
137
- )
138
- output_error(f"Analysis failed: {error_msg}")
139
- return None
140
-
141
- return analysis_result
142
-
143
- except Exception as e:
144
- output_error(f"An error occurred during analysis: {e}")
145
- return None
146
-
147
- def execute(self) -> int:
148
- """
149
- Execute the command.
150
-
151
- Returns:
152
- int: Exit code (0 for success, 1 for failure)
153
- """
154
- # Validate inputs
155
- if not self.validate_file():
156
- return 1
157
-
158
- # Detect language
159
- language = self.detect_language()
160
- if not language:
161
- return 1
162
-
163
- # Execute the specific command
164
- try:
165
- return asyncio.run(self.execute_async(language))
166
- except Exception as e:
167
- output_error(f"An error occurred during command execution: {e}")
168
- return 1
169
-
170
- @abstractmethod
171
- async def execute_async(self, language: str) -> int:
172
- """
173
- Execute the command asynchronously.
174
-
175
- Args:
176
- language: Detected/specified target language
177
-
178
- Returns:
179
- int: Exit code (0 for success, 1 for failure)
180
- """
181
- pass
1
+ #!/usr/bin/env python3
2
+ """
3
+ Base Command Class
4
+
5
+ Abstract base class for all CLI commands implementing the Command Pattern.
6
+ """
7
+
8
+ import asyncio
9
+ from abc import ABC, abstractmethod
10
+ from argparse import Namespace
11
+ from typing import Optional
12
+
13
+ from ...core.analysis_engine import AnalysisRequest, get_analysis_engine
14
+ from ...file_handler import read_file_partial
15
+ from ...language_detector import detect_language_from_file, is_language_supported
16
+ from ...models import AnalysisResult
17
+ from ...output_manager import output_error, output_info
18
+ from ...project_detector import detect_project_root
19
+ from ...security import SecurityValidator
20
+
21
+
22
+ class BaseCommand(ABC):
23
+ """
24
+ Base class for all CLI commands.
25
+
26
+ Implements common functionality like file validation, language detection,
27
+ and analysis engine interaction.
28
+ """
29
+
30
+ def __init__(self, args: Namespace):
31
+ """Initialize command with parsed arguments."""
32
+ self.args = args
33
+
34
+ # Detect project root with priority handling
35
+ file_path = getattr(args, "file_path", None)
36
+ explicit_root = getattr(args, "project_root", None)
37
+ self.project_root = detect_project_root(file_path, explicit_root)
38
+
39
+ # Initialize components with project root
40
+ self.analysis_engine = get_analysis_engine(self.project_root)
41
+ self.security_validator = SecurityValidator(self.project_root)
42
+
43
+ def validate_file(self) -> bool:
44
+ """Validate input file exists and is accessible."""
45
+ if not hasattr(self.args, "file_path") or not self.args.file_path:
46
+ output_error("File path not specified.")
47
+ return False
48
+
49
+ # Security validation
50
+ is_valid, error_msg = self.security_validator.validate_file_path(
51
+ self.args.file_path
52
+ )
53
+ if not is_valid:
54
+ output_error(f"Invalid file path: {error_msg}")
55
+ return False
56
+
57
+ from pathlib import Path
58
+
59
+ if not Path(self.args.file_path).exists():
60
+ output_error("Invalid file path: file does not exist")
61
+ return False
62
+
63
+ return True
64
+
65
+ def detect_language(self) -> str | None:
66
+ """Detect or validate the target language."""
67
+ if hasattr(self.args, "language") and self.args.language:
68
+ # Sanitize language input
69
+ sanitized_language = self.security_validator.sanitize_input(
70
+ self.args.language, max_length=50
71
+ )
72
+ target_language = sanitized_language.lower()
73
+ if (not hasattr(self.args, "table") or not self.args.table) and (
74
+ not hasattr(self.args, "quiet") or not self.args.quiet
75
+ ):
76
+ output_info(f"INFO: Language explicitly specified: {target_language}")
77
+ else:
78
+ target_language = detect_language_from_file(self.args.file_path)
79
+ if target_language == "unknown":
80
+ output_error(
81
+ f"ERROR: Could not determine language for file '{self.args.file_path}'."
82
+ )
83
+ return None
84
+ else:
85
+ if (not hasattr(self.args, "table") or not self.args.table) and (
86
+ not hasattr(self.args, "quiet") or not self.args.quiet
87
+ ):
88
+ # Language auto-detected - only show in verbose mode
89
+ pass
90
+
91
+ # Language support validation
92
+ if not is_language_supported(target_language):
93
+ if target_language != "java":
94
+ if (not hasattr(self.args, "table") or not self.args.table) and (
95
+ not hasattr(self.args, "quiet") or not self.args.quiet
96
+ ):
97
+ output_info(
98
+ "INFO: Trying with Java analysis engine. May not work correctly."
99
+ )
100
+ target_language = "java" # Fallback
101
+
102
+ return str(target_language) if target_language else None
103
+
104
+ async def analyze_file(self, language: str) -> Optional["AnalysisResult"]:
105
+ """Perform file analysis using the unified analysis engine."""
106
+ try:
107
+ # Handle partial read if enabled
108
+ if hasattr(self.args, "partial_read") and self.args.partial_read:
109
+ try:
110
+ partial_content = read_file_partial(
111
+ self.args.file_path,
112
+ start_line=self.args.start_line,
113
+ end_line=getattr(self.args, "end_line", None),
114
+ start_column=getattr(self.args, "start_column", None),
115
+ end_column=getattr(self.args, "end_column", None),
116
+ )
117
+ if partial_content is None:
118
+ output_error("Failed to read file partially")
119
+ return None
120
+ except Exception as e:
121
+ output_error(f"Failed to read file partially: {e}")
122
+ return None
123
+
124
+ request = AnalysisRequest(
125
+ file_path=self.args.file_path,
126
+ language=language,
127
+ include_complexity=True,
128
+ include_details=True,
129
+ )
130
+ analysis_result = await self.analysis_engine.analyze(request)
131
+
132
+ if not analysis_result or not analysis_result.success:
133
+ error_msg = (
134
+ analysis_result.error_message
135
+ if analysis_result
136
+ else "Unknown error"
137
+ )
138
+ output_error(f"Analysis failed: {error_msg}")
139
+ return None
140
+
141
+ return analysis_result
142
+
143
+ except Exception as e:
144
+ output_error(f"An error occurred during analysis: {e}")
145
+ return None
146
+
147
+ def execute(self) -> int:
148
+ """
149
+ Execute the command.
150
+
151
+ Returns:
152
+ int: Exit code (0 for success, 1 for failure)
153
+ """
154
+ # Validate inputs
155
+ if not self.validate_file():
156
+ return 1
157
+
158
+ # Detect language
159
+ language = self.detect_language()
160
+ if not language:
161
+ return 1
162
+
163
+ # Execute the specific command
164
+ try:
165
+ return asyncio.run(self.execute_async(language))
166
+ except Exception as e:
167
+ output_error(f"An error occurred during command execution: {e}")
168
+ return 1
169
+
170
+ @abstractmethod
171
+ async def execute_async(self, language: str) -> int:
172
+ """
173
+ Execute the command asynchronously.
174
+
175
+ Args:
176
+ language: Detected/specified target language
177
+
178
+ Returns:
179
+ int: Exit code (0 for success, 1 for failure)
180
+ """
181
+ pass
@@ -1,139 +1,139 @@
1
- #!/usr/bin/env python3
2
- """
3
- Partial Read Command
4
-
5
- Handles partial file reading functionality, extracting specified line ranges.
6
- """
7
-
8
- from typing import TYPE_CHECKING, Any
9
-
10
- from ...file_handler import read_file_partial
11
- from ...output_manager import output_data, output_json, output_section
12
- from .base_command import BaseCommand
13
-
14
- if TYPE_CHECKING:
15
- pass
16
-
17
-
18
- class PartialReadCommand(BaseCommand):
19
- """Command for reading partial file content by line range."""
20
-
21
- def __init__(self, args: Any) -> None:
22
- """Initialize with arguments but skip base class analysis engine setup."""
23
- self.args = args
24
- # Don't call super().__init__() to avoid unnecessary analysis engine setup
25
-
26
- def validate_file(self) -> bool:
27
- """Validate input file exists and is accessible."""
28
- if not hasattr(self.args, "file_path") or not self.args.file_path:
29
- from ...output_manager import output_error
30
-
31
- output_error("File path not specified.")
32
- return False
33
-
34
- from pathlib import Path
35
-
36
- if not Path(self.args.file_path).exists():
37
- from ...output_manager import output_error
38
-
39
- output_error(f"File not found: {self.args.file_path}")
40
- return False
41
-
42
- return True
43
-
44
- def execute(self) -> int:
45
- """
46
- Execute partial read command.
47
-
48
- Returns:
49
- int: Exit code (0 for success, 1 for failure)
50
- """
51
- # Validate inputs
52
- if not self.validate_file():
53
- return 1
54
-
55
- # Validate partial read arguments
56
- if not self.args.start_line:
57
- from ...output_manager import output_error
58
-
59
- output_error("--start-line is required")
60
- return 1
61
-
62
- if self.args.start_line < 1:
63
- from ...output_manager import output_error
64
-
65
- output_error("--start-line must be 1 or greater")
66
- return 1
67
-
68
- if self.args.end_line and self.args.end_line < self.args.start_line:
69
- from ...output_manager import output_error
70
-
71
- output_error("--end-line must be greater than or equal to --start-line")
72
- return 1
73
-
74
- # Read partial content
75
- try:
76
- partial_content = read_file_partial(
77
- self.args.file_path,
78
- start_line=self.args.start_line,
79
- end_line=getattr(self.args, "end_line", None),
80
- start_column=getattr(self.args, "start_column", None),
81
- end_column=getattr(self.args, "end_column", None),
82
- )
83
-
84
- if partial_content is None:
85
- from ...output_manager import output_error
86
-
87
- output_error("Failed to read file partially")
88
- return 1
89
-
90
- # Output the result
91
- self._output_partial_content(partial_content)
92
- return 0
93
-
94
- except Exception as e:
95
- from ...output_manager import output_error
96
-
97
- output_error(f"Failed to read file partially: {e}")
98
- return 1
99
-
100
- def _output_partial_content(self, content: str) -> None:
101
- """Output the partial content in the specified format."""
102
- # Build result data
103
- result_data = {
104
- "file_path": self.args.file_path,
105
- "range": {
106
- "start_line": self.args.start_line,
107
- "end_line": getattr(self.args, "end_line", None),
108
- "start_column": getattr(self.args, "start_column", None),
109
- "end_column": getattr(self.args, "end_column", None),
110
- },
111
- "content": content,
112
- "content_length": len(content),
113
- }
114
-
115
- # Build range info for header
116
- range_info = f"Line {self.args.start_line}"
117
- if hasattr(self.args, "end_line") and self.args.end_line:
118
- range_info += f"-{self.args.end_line}"
119
-
120
- # Output format selection
121
- output_format = getattr(self.args, "output_format", "text")
122
-
123
- if output_format == "json":
124
- # Pure JSON output
125
- output_json(result_data)
126
- else:
127
- # Human-readable format with header
128
- output_section("Partial Read Result")
129
- output_data(f"File: {self.args.file_path}")
130
- output_data(f"Range: {range_info}")
131
- output_data(f"Characters read: {len(content)}")
132
- output_data("") # Empty line for separation
133
-
134
- # Output the actual content
135
- print(content, end="") # Use print to avoid extra formatting
136
-
137
- async def execute_async(self, language: str) -> int:
138
- """Not used for partial read command."""
139
- return self.execute()
1
+ #!/usr/bin/env python3
2
+ """
3
+ Partial Read Command
4
+
5
+ Handles partial file reading functionality, extracting specified line ranges.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from ...file_handler import read_file_partial
11
+ from ...output_manager import output_data, output_json, output_section
12
+ from .base_command import BaseCommand
13
+
14
+ if TYPE_CHECKING:
15
+ pass
16
+
17
+
18
+ class PartialReadCommand(BaseCommand):
19
+ """Command for reading partial file content by line range."""
20
+
21
+ def __init__(self, args: Any) -> None:
22
+ """Initialize with arguments but skip base class analysis engine setup."""
23
+ self.args = args
24
+ # Don't call super().__init__() to avoid unnecessary analysis engine setup
25
+
26
+ def validate_file(self) -> bool:
27
+ """Validate input file exists and is accessible."""
28
+ if not hasattr(self.args, "file_path") or not self.args.file_path:
29
+ from ...output_manager import output_error
30
+
31
+ output_error("File path not specified.")
32
+ return False
33
+
34
+ from pathlib import Path
35
+
36
+ if not Path(self.args.file_path).exists():
37
+ from ...output_manager import output_error
38
+
39
+ output_error(f"File not found: {self.args.file_path}")
40
+ return False
41
+
42
+ return True
43
+
44
+ def execute(self) -> int:
45
+ """
46
+ Execute partial read command.
47
+
48
+ Returns:
49
+ int: Exit code (0 for success, 1 for failure)
50
+ """
51
+ # Validate inputs
52
+ if not self.validate_file():
53
+ return 1
54
+
55
+ # Validate partial read arguments
56
+ if not self.args.start_line:
57
+ from ...output_manager import output_error
58
+
59
+ output_error("--start-line is required")
60
+ return 1
61
+
62
+ if self.args.start_line < 1:
63
+ from ...output_manager import output_error
64
+
65
+ output_error("--start-line must be 1 or greater")
66
+ return 1
67
+
68
+ if self.args.end_line and self.args.end_line < self.args.start_line:
69
+ from ...output_manager import output_error
70
+
71
+ output_error("--end-line must be greater than or equal to --start-line")
72
+ return 1
73
+
74
+ # Read partial content
75
+ try:
76
+ partial_content = read_file_partial(
77
+ self.args.file_path,
78
+ start_line=self.args.start_line,
79
+ end_line=getattr(self.args, "end_line", None),
80
+ start_column=getattr(self.args, "start_column", None),
81
+ end_column=getattr(self.args, "end_column", None),
82
+ )
83
+
84
+ if partial_content is None:
85
+ from ...output_manager import output_error
86
+
87
+ output_error("Failed to read file partially")
88
+ return 1
89
+
90
+ # Output the result
91
+ self._output_partial_content(partial_content)
92
+ return 0
93
+
94
+ except Exception as e:
95
+ from ...output_manager import output_error
96
+
97
+ output_error(f"Failed to read file partially: {e}")
98
+ return 1
99
+
100
+ def _output_partial_content(self, content: str) -> None:
101
+ """Output the partial content in the specified format."""
102
+ # Build result data
103
+ result_data = {
104
+ "file_path": self.args.file_path,
105
+ "range": {
106
+ "start_line": self.args.start_line,
107
+ "end_line": getattr(self.args, "end_line", None),
108
+ "start_column": getattr(self.args, "start_column", None),
109
+ "end_column": getattr(self.args, "end_column", None),
110
+ },
111
+ "content": content,
112
+ "content_length": len(content),
113
+ }
114
+
115
+ # Build range info for header
116
+ range_info = f"Line {self.args.start_line}"
117
+ if hasattr(self.args, "end_line") and self.args.end_line:
118
+ range_info += f"-{self.args.end_line}"
119
+
120
+ # Output format selection
121
+ output_format = getattr(self.args, "output_format", "text")
122
+
123
+ if output_format == "json":
124
+ # Pure JSON output
125
+ output_json(result_data)
126
+ else:
127
+ # Human-readable format with header
128
+ output_section("Partial Read Result")
129
+ output_data(f"File: {self.args.file_path}")
130
+ output_data(f"Range: {range_info}")
131
+ output_data(f"Characters read: {len(content)}")
132
+ output_data("") # Empty line for separation
133
+
134
+ # Output the actual content
135
+ print(content, end="") # Use print to avoid extra formatting
136
+
137
+ async def execute_async(self, language: str) -> int:
138
+ """Not used for partial read command."""
139
+ return self.execute()