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.
- tree_sitter_analyzer/__init__.py +132 -0
- tree_sitter_analyzer/__main__.py +11 -0
- tree_sitter_analyzer/api.py +853 -0
- tree_sitter_analyzer/cli/__init__.py +39 -0
- tree_sitter_analyzer/cli/__main__.py +12 -0
- tree_sitter_analyzer/cli/argument_validator.py +89 -0
- tree_sitter_analyzer/cli/commands/__init__.py +26 -0
- tree_sitter_analyzer/cli/commands/advanced_command.py +226 -0
- tree_sitter_analyzer/cli/commands/base_command.py +181 -0
- tree_sitter_analyzer/cli/commands/default_command.py +18 -0
- tree_sitter_analyzer/cli/commands/find_and_grep_cli.py +188 -0
- tree_sitter_analyzer/cli/commands/list_files_cli.py +133 -0
- tree_sitter_analyzer/cli/commands/partial_read_command.py +139 -0
- tree_sitter_analyzer/cli/commands/query_command.py +109 -0
- tree_sitter_analyzer/cli/commands/search_content_cli.py +161 -0
- tree_sitter_analyzer/cli/commands/structure_command.py +156 -0
- tree_sitter_analyzer/cli/commands/summary_command.py +116 -0
- tree_sitter_analyzer/cli/commands/table_command.py +414 -0
- tree_sitter_analyzer/cli/info_commands.py +124 -0
- tree_sitter_analyzer/cli_main.py +472 -0
- tree_sitter_analyzer/constants.py +85 -0
- tree_sitter_analyzer/core/__init__.py +15 -0
- tree_sitter_analyzer/core/analysis_engine.py +580 -0
- tree_sitter_analyzer/core/cache_service.py +333 -0
- tree_sitter_analyzer/core/engine.py +585 -0
- tree_sitter_analyzer/core/parser.py +293 -0
- tree_sitter_analyzer/core/query.py +605 -0
- tree_sitter_analyzer/core/query_filter.py +200 -0
- tree_sitter_analyzer/core/query_service.py +340 -0
- tree_sitter_analyzer/encoding_utils.py +530 -0
- tree_sitter_analyzer/exceptions.py +747 -0
- tree_sitter_analyzer/file_handler.py +246 -0
- tree_sitter_analyzer/formatters/__init__.py +1 -0
- tree_sitter_analyzer/formatters/base_formatter.py +201 -0
- tree_sitter_analyzer/formatters/csharp_formatter.py +367 -0
- tree_sitter_analyzer/formatters/formatter_config.py +197 -0
- tree_sitter_analyzer/formatters/formatter_factory.py +84 -0
- tree_sitter_analyzer/formatters/formatter_registry.py +377 -0
- tree_sitter_analyzer/formatters/formatter_selector.py +96 -0
- tree_sitter_analyzer/formatters/go_formatter.py +368 -0
- tree_sitter_analyzer/formatters/html_formatter.py +498 -0
- tree_sitter_analyzer/formatters/java_formatter.py +423 -0
- tree_sitter_analyzer/formatters/javascript_formatter.py +611 -0
- tree_sitter_analyzer/formatters/kotlin_formatter.py +268 -0
- tree_sitter_analyzer/formatters/language_formatter_factory.py +123 -0
- tree_sitter_analyzer/formatters/legacy_formatter_adapters.py +228 -0
- tree_sitter_analyzer/formatters/markdown_formatter.py +725 -0
- tree_sitter_analyzer/formatters/php_formatter.py +301 -0
- tree_sitter_analyzer/formatters/python_formatter.py +830 -0
- tree_sitter_analyzer/formatters/ruby_formatter.py +278 -0
- tree_sitter_analyzer/formatters/rust_formatter.py +233 -0
- tree_sitter_analyzer/formatters/sql_formatter_wrapper.py +689 -0
- tree_sitter_analyzer/formatters/sql_formatters.py +536 -0
- tree_sitter_analyzer/formatters/typescript_formatter.py +543 -0
- tree_sitter_analyzer/formatters/yaml_formatter.py +462 -0
- tree_sitter_analyzer/interfaces/__init__.py +9 -0
- tree_sitter_analyzer/interfaces/cli.py +535 -0
- tree_sitter_analyzer/interfaces/cli_adapter.py +359 -0
- tree_sitter_analyzer/interfaces/mcp_adapter.py +224 -0
- tree_sitter_analyzer/interfaces/mcp_server.py +428 -0
- tree_sitter_analyzer/language_detector.py +553 -0
- tree_sitter_analyzer/language_loader.py +271 -0
- tree_sitter_analyzer/languages/__init__.py +10 -0
- tree_sitter_analyzer/languages/csharp_plugin.py +1076 -0
- tree_sitter_analyzer/languages/css_plugin.py +449 -0
- tree_sitter_analyzer/languages/go_plugin.py +836 -0
- tree_sitter_analyzer/languages/html_plugin.py +496 -0
- tree_sitter_analyzer/languages/java_plugin.py +1299 -0
- tree_sitter_analyzer/languages/javascript_plugin.py +1622 -0
- tree_sitter_analyzer/languages/kotlin_plugin.py +656 -0
- tree_sitter_analyzer/languages/markdown_plugin.py +1928 -0
- tree_sitter_analyzer/languages/php_plugin.py +862 -0
- tree_sitter_analyzer/languages/python_plugin.py +1636 -0
- tree_sitter_analyzer/languages/ruby_plugin.py +757 -0
- tree_sitter_analyzer/languages/rust_plugin.py +673 -0
- tree_sitter_analyzer/languages/sql_plugin.py +2444 -0
- tree_sitter_analyzer/languages/typescript_plugin.py +1892 -0
- tree_sitter_analyzer/languages/yaml_plugin.py +695 -0
- tree_sitter_analyzer/legacy_table_formatter.py +860 -0
- tree_sitter_analyzer/mcp/__init__.py +34 -0
- tree_sitter_analyzer/mcp/resources/__init__.py +43 -0
- tree_sitter_analyzer/mcp/resources/code_file_resource.py +208 -0
- tree_sitter_analyzer/mcp/resources/project_stats_resource.py +586 -0
- tree_sitter_analyzer/mcp/server.py +869 -0
- tree_sitter_analyzer/mcp/tools/__init__.py +28 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool.py +779 -0
- tree_sitter_analyzer/mcp/tools/analyze_scale_tool_cli_compatible.py +291 -0
- tree_sitter_analyzer/mcp/tools/base_tool.py +139 -0
- tree_sitter_analyzer/mcp/tools/fd_rg_utils.py +816 -0
- tree_sitter_analyzer/mcp/tools/find_and_grep_tool.py +686 -0
- tree_sitter_analyzer/mcp/tools/list_files_tool.py +413 -0
- tree_sitter_analyzer/mcp/tools/output_format_validator.py +148 -0
- tree_sitter_analyzer/mcp/tools/query_tool.py +443 -0
- tree_sitter_analyzer/mcp/tools/read_partial_tool.py +464 -0
- tree_sitter_analyzer/mcp/tools/search_content_tool.py +836 -0
- tree_sitter_analyzer/mcp/tools/table_format_tool.py +572 -0
- tree_sitter_analyzer/mcp/tools/universal_analyze_tool.py +653 -0
- tree_sitter_analyzer/mcp/utils/__init__.py +113 -0
- tree_sitter_analyzer/mcp/utils/error_handler.py +569 -0
- tree_sitter_analyzer/mcp/utils/file_output_factory.py +217 -0
- tree_sitter_analyzer/mcp/utils/file_output_manager.py +322 -0
- tree_sitter_analyzer/mcp/utils/gitignore_detector.py +358 -0
- tree_sitter_analyzer/mcp/utils/path_resolver.py +414 -0
- tree_sitter_analyzer/mcp/utils/search_cache.py +343 -0
- tree_sitter_analyzer/models.py +840 -0
- tree_sitter_analyzer/mypy_current_errors.txt +2 -0
- tree_sitter_analyzer/output_manager.py +255 -0
- tree_sitter_analyzer/platform_compat/__init__.py +3 -0
- tree_sitter_analyzer/platform_compat/adapter.py +324 -0
- tree_sitter_analyzer/platform_compat/compare.py +224 -0
- tree_sitter_analyzer/platform_compat/detector.py +67 -0
- tree_sitter_analyzer/platform_compat/fixtures.py +228 -0
- tree_sitter_analyzer/platform_compat/profiles.py +217 -0
- tree_sitter_analyzer/platform_compat/record.py +55 -0
- tree_sitter_analyzer/platform_compat/recorder.py +155 -0
- tree_sitter_analyzer/platform_compat/report.py +92 -0
- tree_sitter_analyzer/plugins/__init__.py +280 -0
- tree_sitter_analyzer/plugins/base.py +647 -0
- tree_sitter_analyzer/plugins/manager.py +384 -0
- tree_sitter_analyzer/project_detector.py +328 -0
- tree_sitter_analyzer/queries/__init__.py +27 -0
- tree_sitter_analyzer/queries/csharp.py +216 -0
- tree_sitter_analyzer/queries/css.py +615 -0
- tree_sitter_analyzer/queries/go.py +275 -0
- tree_sitter_analyzer/queries/html.py +543 -0
- tree_sitter_analyzer/queries/java.py +402 -0
- tree_sitter_analyzer/queries/javascript.py +724 -0
- tree_sitter_analyzer/queries/kotlin.py +192 -0
- tree_sitter_analyzer/queries/markdown.py +258 -0
- tree_sitter_analyzer/queries/php.py +95 -0
- tree_sitter_analyzer/queries/python.py +859 -0
- tree_sitter_analyzer/queries/ruby.py +92 -0
- tree_sitter_analyzer/queries/rust.py +223 -0
- tree_sitter_analyzer/queries/sql.py +555 -0
- tree_sitter_analyzer/queries/typescript.py +871 -0
- tree_sitter_analyzer/queries/yaml.py +236 -0
- tree_sitter_analyzer/query_loader.py +272 -0
- tree_sitter_analyzer/security/__init__.py +22 -0
- tree_sitter_analyzer/security/boundary_manager.py +277 -0
- tree_sitter_analyzer/security/regex_checker.py +297 -0
- tree_sitter_analyzer/security/validator.py +599 -0
- tree_sitter_analyzer/table_formatter.py +782 -0
- tree_sitter_analyzer/utils/__init__.py +53 -0
- tree_sitter_analyzer/utils/logging.py +433 -0
- tree_sitter_analyzer/utils/tree_sitter_compat.py +289 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/METADATA +485 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/RECORD +149 -0
- tree_sitter_analyzer-1.9.17.1.dist-info/WHEEL +4 -0
- 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,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
|