tree-sitter-analyzer 1.9.17.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.
Files changed (149) hide show
  1. tree_sitter_analyzer/__init__.py +132 -0
  2. tree_sitter_analyzer/__main__.py +11 -0
  3. tree_sitter_analyzer/api.py +853 -0
  4. tree_sitter_analyzer/cli/__init__.py +39 -0
  5. tree_sitter_analyzer/cli/__main__.py +12 -0
  6. tree_sitter_analyzer/cli/argument_validator.py +89 -0
  7. tree_sitter_analyzer/cli/commands/__init__.py +26 -0
  8. tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
  9. tree_sitter_analyzer/cli/commands/base_command.py +181 -0
  10. tree_sitter_analyzer/cli/commands/default_command.py +18 -0
  11. tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
  12. tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
  13. tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
  14. tree_sitter_analyzer/cli/commands/query_command.py +109 -0
  15. tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
  16. tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
  17. tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
  18. tree_sitter_analyzer/cli/commands/table_command.py +414 -0
  19. tree_sitter_analyzer/cli/info_commands.py +124 -0
  20. tree_sitter_analyzer/cli_main.py +472 -0
  21. tree_sitter_analyzer/constants.py +85 -0
  22. tree_sitter_analyzer/core/__init__.py +15 -0
  23. tree_sitter_analyzer/core/analysis_engine.py +580 -0
  24. tree_sitter_analyzer/core/cache_service.py +333 -0
  25. tree_sitter_analyzer/core/engine.py +585 -0
  26. tree_sitter_analyzer/core/parser.py +293 -0
  27. tree_sitter_analyzer/core/query.py +605 -0
  28. tree_sitter_analyzer/core/query_filter.py +200 -0
  29. tree_sitter_analyzer/core/query_service.py +340 -0
  30. tree_sitter_analyzer/encoding_utils.py +530 -0
  31. tree_sitter_analyzer/exceptions.py +747 -0
  32. tree_sitter_analyzer/file_handler.py +246 -0
  33. tree_sitter_analyzer/formatters/__init__.py +1 -0
  34. tree_sitter_analyzer/formatters/base_formatter.py +201 -0
  35. tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
  36. tree_sitter_analyzer/formatters/formatter_config.py +197 -0
  37. tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
  38. tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
  39. tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
  40. tree_sitter_analyzer/formatters/go_formatter.py +368 -0
  41. tree_sitter_analyzer/formatters/html_formatter.py +498 -0
  42. tree_sitter_analyzer/formatters/java_formatter.py +423 -0
  43. tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
  44. tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
  45. tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
  46. tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
  47. tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
  48. tree_sitter_analyzer/formatters/php_formatter.py +301 -0
  49. tree_sitter_analyzer/formatters/python_formatter.py +830 -0
  50. tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
  51. tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
  52. tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
  53. tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
  54. tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
  55. tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
  56. tree_sitter_analyzer/interfaces/__init__.py +9 -0
  57. tree_sitter_analyzer/interfaces/cli.py +535 -0
  58. tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
  59. tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
  60. tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
  61. tree_sitter_analyzer/language_detector.py +553 -0
  62. tree_sitter_analyzer/language_loader.py +271 -0
  63. tree_sitter_analyzer/languages/__init__.py +10 -0
  64. tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
  65. tree_sitter_analyzer/languages/css_plugin.py +449 -0
  66. tree_sitter_analyzer/languages/go_plugin.py +836 -0
  67. tree_sitter_analyzer/languages/html_plugin.py +496 -0
  68. tree_sitter_analyzer/languages/java_plugin.py +1299 -0
  69. tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
  70. tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
  71. tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
  72. tree_sitter_analyzer/languages/php_plugin.py +862 -0
  73. tree_sitter_analyzer/languages/python_plugin.py +1636 -0
  74. tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
  75. tree_sitter_analyzer/languages/rust_plugin.py +673 -0
  76. tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
  77. tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
  78. tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
  79. tree_sitter_analyzer/legacy_table_formatter.py +860 -0
  80. tree_sitter_analyzer/mcp/__init__.py +34 -0
  81. tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
  82. tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
  83. tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
  84. tree_sitter_analyzer/mcp/server.py +869 -0
  85. tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
  86. tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
  87. tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
  88. tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
  89. tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
  90. tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
  91. tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
  92. tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
  93. tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
  94. tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
  95. tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
  96. tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
  97. tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
  98. tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
  99. tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
  100. tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
  101. tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
  102. tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
  103. tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
  104. tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
  105. tree_sitter_analyzer/models.py +840 -0
  106. tree_sitter_analyzer/mypy_current_errors.txt +2 -0
  107. tree_sitter_analyzer/output_manager.py +255 -0
  108. tree_sitter_analyzer/platform_compat/__init__.py +3 -0
  109. tree_sitter_analyzer/platform_compat/adapter.py +324 -0
  110. tree_sitter_analyzer/platform_compat/compare.py +224 -0
  111. tree_sitter_analyzer/platform_compat/detector.py +67 -0
  112. tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
  113. tree_sitter_analyzer/platform_compat/profiles.py +217 -0
  114. tree_sitter_analyzer/platform_compat/record.py +55 -0
  115. tree_sitter_analyzer/platform_compat/recorder.py +155 -0
  116. tree_sitter_analyzer/platform_compat/report.py +92 -0
  117. tree_sitter_analyzer/plugins/__init__.py +280 -0
  118. tree_sitter_analyzer/plugins/base.py +647 -0
  119. tree_sitter_analyzer/plugins/manager.py +384 -0
  120. tree_sitter_analyzer/project_detector.py +328 -0
  121. tree_sitter_analyzer/queries/__init__.py +27 -0
  122. tree_sitter_analyzer/queries/csharp.py +216 -0
  123. tree_sitter_analyzer/queries/css.py +615 -0
  124. tree_sitter_analyzer/queries/go.py +275 -0
  125. tree_sitter_analyzer/queries/html.py +543 -0
  126. tree_sitter_analyzer/queries/java.py +402 -0
  127. tree_sitter_analyzer/queries/javascript.py +724 -0
  128. tree_sitter_analyzer/queries/kotlin.py +192 -0
  129. tree_sitter_analyzer/queries/markdown.py +258 -0
  130. tree_sitter_analyzer/queries/php.py +95 -0
  131. tree_sitter_analyzer/queries/python.py +859 -0
  132. tree_sitter_analyzer/queries/ruby.py +92 -0
  133. tree_sitter_analyzer/queries/rust.py +223 -0
  134. tree_sitter_analyzer/queries/sql.py +555 -0
  135. tree_sitter_analyzer/queries/typescript.py +871 -0
  136. tree_sitter_analyzer/queries/yaml.py +236 -0
  137. tree_sitter_analyzer/query_loader.py +272 -0
  138. tree_sitter_analyzer/security/__init__.py +22 -0
  139. tree_sitter_analyzer/security/boundary_manager.py +277 -0
  140. tree_sitter_analyzer/security/regex_checker.py +297 -0
  141. tree_sitter_analyzer/security/validator.py +599 -0
  142. tree_sitter_analyzer/table_formatter.py +782 -0
  143. tree_sitter_analyzer/utils/__init__.py +53 -0
  144. tree_sitter_analyzer/utils/logging.py +433 -0
  145. tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
  146. tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
  147. tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
  148. tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
  149. tree_sitter_analyzer-1.9.17.1.dist-info/entry_points.txt +25 -0
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI Package
4
+
5
+ Command-line interface components using the Command Pattern.
6
+ """
7
+
8
+ from .info_commands import (
9
+ DescribeQueryCommand,
10
+ InfoCommand,
11
+ ListQueriesCommand,
12
+ ShowExtensionsCommand,
13
+ ShowLanguagesCommand,
14
+ )
15
+
16
+ # Modern framework imports
17
+ try:
18
+ from ..cli_main import main
19
+ from ..core.analysis_engine import get_analysis_engine
20
+ from ..query_loader import QueryLoader
21
+
22
+ query_loader = QueryLoader()
23
+ except ImportError:
24
+ # Minimal fallback for import safety
25
+ get_analysis_engine = None # type: ignore
26
+ main = None # type: ignore
27
+ query_loader = None # type: ignore
28
+
29
+ __all__ = [
30
+ "InfoCommand",
31
+ "ListQueriesCommand",
32
+ "DescribeQueryCommand",
33
+ "ShowLanguagesCommand",
34
+ "ShowExtensionsCommand",
35
+ # Core framework exports
36
+ "query_loader",
37
+ "get_analysis_engine",
38
+ "main",
39
+ ]
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI Module Entry Point
4
+
5
+ This module allows the CLI to be executed as a module with:
6
+ python -m tree_sitter_analyzer.cli
7
+ """
8
+
9
+ from ..cli_main import main
10
+
11
+ if __name__ == "__main__":
12
+ main()
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI Argument Validator
4
+
5
+ Validates CLI argument combinations and provides clear error messages.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+
11
+ class CLIArgumentValidator:
12
+ """Validator for CLI argument combinations."""
13
+
14
+ def __init__(self) -> None:
15
+ """Initialize the validator."""
16
+ pass
17
+
18
+ def validate_arguments(self, args: Any) -> str | None:
19
+ """
20
+ Validate CLI argument combinations.
21
+
22
+ Args:
23
+ args: Parsed command line arguments
24
+
25
+ Returns:
26
+ None if valid, error message string if invalid
27
+ """
28
+ # Check for --table and --query-key combination
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
+
38
+ if table_specified and query_key_specified:
39
+ return "--table and --query-key cannot be used together. Use --query-key with --filter instead."
40
+
41
+ # All validations passed
42
+ return None
43
+
44
+ def validate_table_query_exclusivity(self, args: Any) -> str | None:
45
+ """
46
+ Validate that --table and --query-key are mutually exclusive.
47
+
48
+ Args:
49
+ args: Parsed command line arguments
50
+
51
+ Returns:
52
+ None if valid, error message string if invalid
53
+ """
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
+
63
+ if table_specified and query_key_specified:
64
+ return "--table and --query-key cannot be used together. Use --query-key with --filter instead."
65
+
66
+ return None
67
+
68
+ def get_usage_examples(self) -> str:
69
+ """
70
+ Get usage examples for correct argument combinations.
71
+
72
+ Returns:
73
+ String containing usage examples
74
+ """
75
+ return """
76
+ Correct usage examples:
77
+
78
+ # Use table format only:
79
+ uv run python -m tree_sitter_analyzer examples/BigService.java --table full
80
+
81
+ # Use query-key only:
82
+ uv run python -m tree_sitter_analyzer examples/BigService.java --query-key methods
83
+
84
+ # Use query-key with filter:
85
+ uv run python -m tree_sitter_analyzer examples/BigService.java --query-key methods --filter "name=main"
86
+
87
+ # Invalid combination (will cause error):
88
+ uv run python -m tree_sitter_analyzer examples/BigService.java --table full --query-key methods
89
+ """
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI Commands Package
4
+
5
+ This package contains all command implementations for the CLI interface.
6
+ """
7
+
8
+ from .advanced_command import AdvancedCommand
9
+ from .base_command import BaseCommand
10
+ from .default_command import DefaultCommand
11
+ from .partial_read_command import PartialReadCommand
12
+ from .query_command import QueryCommand
13
+ from .structure_command import StructureCommand
14
+ from .summary_command import SummaryCommand
15
+ from .table_command import TableCommand
16
+
17
+ __all__ = [
18
+ "BaseCommand",
19
+ "AdvancedCommand",
20
+ "DefaultCommand",
21
+ "PartialReadCommand",
22
+ "QueryCommand",
23
+ "StructureCommand",
24
+ "SummaryCommand",
25
+ "TableCommand",
26
+ ]
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Advanced Command
4
+
5
+ Handles advanced analysis functionality.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ from ...constants import (
11
+ ELEMENT_TYPE_CLASS,
12
+ ELEMENT_TYPE_FUNCTION,
13
+ ELEMENT_TYPE_IMPORT,
14
+ ELEMENT_TYPE_VARIABLE,
15
+ get_element_type,
16
+ )
17
+ from ...output_manager import output_data, output_json, output_section
18
+ from .base_command import BaseCommand
19
+
20
+ if TYPE_CHECKING:
21
+ from ...models import AnalysisResult
22
+
23
+
24
+ class AdvancedCommand(BaseCommand):
25
+ """Command for advanced analysis."""
26
+
27
+ async def execute_async(self, language: str) -> int:
28
+ analysis_result = await self.analyze_file(language)
29
+ if not analysis_result:
30
+ return 1
31
+
32
+ if hasattr(self.args, "statistics") and self.args.statistics:
33
+ self._output_statistics(analysis_result)
34
+ else:
35
+ self._output_full_analysis(analysis_result)
36
+
37
+ return 0
38
+
39
+ def _calculate_file_metrics(self, file_path: str, language: str) -> dict[str, int]:
40
+ """
41
+ Calculate accurate file metrics including line counts.
42
+
43
+ Args:
44
+ file_path: Path to the file to analyze
45
+ language: Programming language
46
+
47
+ Returns:
48
+ Dictionary containing file metrics
49
+ """
50
+ try:
51
+ from ...encoding_utils import read_file_safe
52
+
53
+ content, _ = read_file_safe(file_path)
54
+
55
+ lines = content.split("\n")
56
+ total_lines = len(lines)
57
+
58
+ # Remove empty line at the end if file ends with newline
59
+ if lines and not lines[-1]:
60
+ total_lines -= 1
61
+
62
+ # Count different types of lines
63
+ code_lines = 0
64
+ comment_lines = 0
65
+ blank_lines = 0
66
+ in_multiline_comment = False
67
+
68
+ for line in lines:
69
+ stripped = line.strip()
70
+
71
+ # Check for blank lines first
72
+ if not stripped:
73
+ blank_lines += 1
74
+ continue
75
+
76
+ # Check if we're in a multi-line comment
77
+ if in_multiline_comment:
78
+ comment_lines += 1
79
+ # Check if this line ends the multi-line comment
80
+ if "*/" in stripped:
81
+ in_multiline_comment = False
82
+ continue
83
+
84
+ # Check for multi-line comment start
85
+ if stripped.startswith("/**") or stripped.startswith("/*"):
86
+ comment_lines += 1
87
+ # Check if this line also ends the comment
88
+ if "*/" not in stripped:
89
+ in_multiline_comment = True
90
+ continue
91
+
92
+ # Check for single-line comments
93
+ if stripped.startswith("//"):
94
+ comment_lines += 1
95
+ continue
96
+
97
+ # Check for JavaDoc continuation lines (lines starting with * but not */)
98
+ if stripped.startswith("*") and not stripped.startswith("*/"):
99
+ comment_lines += 1
100
+ continue
101
+
102
+ # Check for other comment types based on language
103
+ if (
104
+ language == "python"
105
+ and stripped.startswith("#")
106
+ or language == "sql"
107
+ and stripped.startswith("--")
108
+ ):
109
+ comment_lines += 1
110
+ continue
111
+ elif language in ["html", "xml"] and stripped.startswith("<!--"):
112
+ comment_lines += 1
113
+ if "-->" not in stripped:
114
+ in_multiline_comment = True
115
+ continue
116
+
117
+ # If not a comment, it's code
118
+ code_lines += 1
119
+
120
+ # Ensure the sum equals total_lines (handle any rounding errors)
121
+ calculated_total = code_lines + comment_lines + blank_lines
122
+ if calculated_total != total_lines:
123
+ # Adjust blank_lines to match total (since it's most likely to be off by 1)
124
+ blank_lines = total_lines - code_lines - comment_lines
125
+ # Ensure blank_lines is not negative
126
+ blank_lines = max(0, blank_lines)
127
+
128
+ return {
129
+ "total_lines": total_lines,
130
+ "code_lines": code_lines,
131
+ "comment_lines": comment_lines,
132
+ "blank_lines": blank_lines,
133
+ }
134
+ except Exception:
135
+ # Fallback to basic counting if file read fails
136
+ return {
137
+ "total_lines": 0,
138
+ "code_lines": 0,
139
+ "comment_lines": 0,
140
+ "blank_lines": 0,
141
+ }
142
+
143
+ def _output_statistics(self, analysis_result: "AnalysisResult") -> None:
144
+ """Output statistics only."""
145
+ stats = {
146
+ "line_count": analysis_result.line_count,
147
+ "element_count": len(analysis_result.elements),
148
+ "node_count": analysis_result.node_count,
149
+ "language": analysis_result.language,
150
+ }
151
+ output_section("Statistics")
152
+ if self.args.output_format == "json":
153
+ output_json(stats)
154
+ else:
155
+ for key, value in stats.items():
156
+ output_data(f"{key}: {value}")
157
+
158
+ def _output_full_analysis(self, analysis_result: "AnalysisResult") -> None:
159
+ """Output full analysis results."""
160
+ output_section("Advanced Analysis Results")
161
+ if self.args.output_format == "json":
162
+ result_dict = {
163
+ "file_path": analysis_result.file_path,
164
+ "language": analysis_result.language,
165
+ "line_count": analysis_result.line_count,
166
+ "element_count": len(analysis_result.elements),
167
+ "node_count": analysis_result.node_count,
168
+ "elements": [
169
+ {
170
+ "name": getattr(element, "name", str(element)),
171
+ "type": get_element_type(element),
172
+ "start_line": getattr(element, "start_line", 0),
173
+ "end_line": getattr(element, "end_line", 0),
174
+ }
175
+ for element in analysis_result.elements
176
+ ],
177
+ "success": analysis_result.success,
178
+ "analysis_time": analysis_result.analysis_time,
179
+ }
180
+ output_json(result_dict)
181
+ else:
182
+ self._output_text_analysis(analysis_result)
183
+
184
+ def _output_text_analysis(self, analysis_result: "AnalysisResult") -> None:
185
+ """Output analysis in text format."""
186
+ output_data(f"File: {analysis_result.file_path}")
187
+ output_data("Package: (default)")
188
+ output_data(f"Lines: {analysis_result.line_count}")
189
+
190
+ element_counts: dict[str, int] = {}
191
+ complexity_scores: list[int] = []
192
+
193
+ for element in analysis_result.elements:
194
+ element_type = get_element_type(element)
195
+ element_counts[element_type] = element_counts.get(element_type, 0) + 1
196
+
197
+ # Collect complexity scores for methods
198
+ if element_type == ELEMENT_TYPE_FUNCTION:
199
+ complexity = getattr(element, "complexity_score", 1)
200
+ complexity_scores.append(complexity)
201
+
202
+ # Calculate accurate file metrics
203
+ file_metrics = self._calculate_file_metrics(
204
+ analysis_result.file_path, analysis_result.language
205
+ )
206
+
207
+ # Calculate complexity statistics
208
+ total_complexity = sum(complexity_scores) if complexity_scores else 0
209
+ avg_complexity = (
210
+ total_complexity / len(complexity_scores) if complexity_scores else 0
211
+ )
212
+ max_complexity = max(complexity_scores) if complexity_scores else 0
213
+
214
+ output_data(f"Classes: {element_counts.get(ELEMENT_TYPE_CLASS, 0)}")
215
+ output_data(f"Methods: {element_counts.get(ELEMENT_TYPE_FUNCTION, 0)}")
216
+ output_data(f"Fields: {element_counts.get(ELEMENT_TYPE_VARIABLE, 0)}")
217
+ output_data(f"Imports: {element_counts.get(ELEMENT_TYPE_IMPORT, 0)}")
218
+ output_data("Annotations: 0")
219
+
220
+ # Add detailed metrics using accurate calculations
221
+ output_data(f"Code Lines: {file_metrics['code_lines']}")
222
+ output_data(f"Comment Lines: {file_metrics['comment_lines']}")
223
+ output_data(f"Blank Lines: {file_metrics['blank_lines']}")
224
+ output_data(f"Total Complexity: {total_complexity}")
225
+ output_data(f"Average Complexity: {avg_complexity:.2f}")
226
+ output_data(f"Max Complexity: {max_complexity}")
@@ -0,0 +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
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Default Command
4
+
5
+ Handles default analysis when no specific command is specified.
6
+ """
7
+
8
+ from ...output_manager import output_error
9
+ from .base_command import BaseCommand
10
+
11
+
12
+ class DefaultCommand(BaseCommand):
13
+ """Default command that shows error when no specific command is given."""
14
+
15
+ async def execute_async(self, language: str) -> int:
16
+ """Execute default command - show error for missing options."""
17
+ output_error("Please specify a query or --advanced option")
18
+ return 1