codeboarding 0.9.6__tar.gz → 0.10.0__tar.gz
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.
- {codeboarding-0.9.6/codeboarding.egg-info → codeboarding-0.10.0}/PKG-INFO +1 -1
- {codeboarding-0.9.6 → codeboarding-0.10.0/codeboarding.egg-info}/PKG-INFO +1 -1
- {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/SOURCES.txt +22 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/pyproject.toml +2 -2
- codeboarding-0.10.0/static_analyzer/engine/__init__.py +27 -0
- codeboarding-0.10.0/static_analyzer/engine/adapters/__init__.py +32 -0
- codeboarding-0.10.0/static_analyzer/engine/adapters/go_adapter.py +207 -0
- codeboarding-0.10.0/static_analyzer/engine/adapters/java_adapter.py +295 -0
- codeboarding-0.10.0/static_analyzer/engine/adapters/php_adapter.py +50 -0
- codeboarding-0.10.0/static_analyzer/engine/adapters/python_adapter.py +56 -0
- codeboarding-0.10.0/static_analyzer/engine/adapters/typescript_adapter.py +51 -0
- codeboarding-0.10.0/static_analyzer/engine/call_graph_builder.py +292 -0
- codeboarding-0.10.0/static_analyzer/engine/edge_build_context.py +18 -0
- codeboarding-0.10.0/static_analyzer/engine/edge_builder.py +506 -0
- codeboarding-0.10.0/static_analyzer/engine/hierarchy_builder.py +200 -0
- codeboarding-0.10.0/static_analyzer/engine/language_adapter.py +271 -0
- codeboarding-0.10.0/static_analyzer/engine/lsp_client.py +679 -0
- codeboarding-0.10.0/static_analyzer/engine/lsp_constants.py +33 -0
- codeboarding-0.10.0/static_analyzer/engine/models.py +101 -0
- codeboarding-0.10.0/static_analyzer/engine/progress.py +83 -0
- codeboarding-0.10.0/static_analyzer/engine/protocols.py +48 -0
- codeboarding-0.10.0/static_analyzer/engine/result_converter.py +133 -0
- codeboarding-0.10.0/static_analyzer/engine/source_inspector.py +203 -0
- codeboarding-0.10.0/static_analyzer/engine/symbol_table.py +328 -0
- codeboarding-0.10.0/static_analyzer/engine/utils.py +22 -0
- codeboarding-0.10.0/tests/test_pyproject_packages.py +52 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/LICENSE +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/PYPI.md +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/README.md +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/abstraction_agent.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/agent.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/agent_responses.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/change_status.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/cluster_methods_mixin.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/dependency_discovery.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/details_agent.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/llm_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/meta_agent.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/planner_agent.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/abstract_prompt_factory.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/claude_prompts.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/deepseek_prompts.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/gemini_flash_prompts.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/glm_prompts.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/gpt_prompts.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/kimi_prompts.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/prompt_factory.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/base.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/get_external_deps.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/get_method_invocations.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_cfg.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_docs.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_file.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_file_structure.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_git_diff.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_packages.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_source.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_structure.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/toolkit.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/validation.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/caching/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/caching/cache.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/caching/details_cache.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/caching/meta_cache.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/requires.txt +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/top_level.txt +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/core/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/core/plugin_loader.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/core/protocols.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/core/registry.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/analysis_json.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/diagram_generator.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/file_coverage.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/incremental_types.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/incremental_updater.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/io_utils.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/manifest.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/run_context.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/version.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/duckdb_crud.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/github_action.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/cohesion.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/coupling.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/function_size.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/god_class.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/inheritance.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/instability.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/unused_code_diagnostics.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/models.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health/runner.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/health_main.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/install.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/logging_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/main.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/callbacks.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/context.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/mixin.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/paths.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/stats.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/writers.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/html.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/html_template.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/markdown.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/mdx.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/sphinx.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/change_detector.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/errors.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/git_diff.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/ignore.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/method_diff.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/setup.cfg +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/analysis_cache.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/analysis_result.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/cluster_change_analyzer.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/cluster_helpers.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/cluster_relations.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/git_diff_analyzer.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/graph.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/incremental_orchestrator.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/java_utils.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/node.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/reference_resolve_mixin.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/scanner.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/typescript_config_scanner.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_github_action.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_install.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_logging_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_main.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_tool_registry.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_user_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_vscode_constants.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_windows_compatibility.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_windows_encoding.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/tool_registry.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/user_config.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/utils.py +0 -0
- {codeboarding-0.9.6 → codeboarding-0.10.0}/vscode_constants.py +0 -0
|
@@ -122,12 +122,34 @@ static_analyzer/programming_language.py
|
|
|
122
122
|
static_analyzer/reference_resolve_mixin.py
|
|
123
123
|
static_analyzer/scanner.py
|
|
124
124
|
static_analyzer/typescript_config_scanner.py
|
|
125
|
+
static_analyzer/engine/__init__.py
|
|
126
|
+
static_analyzer/engine/call_graph_builder.py
|
|
127
|
+
static_analyzer/engine/edge_build_context.py
|
|
128
|
+
static_analyzer/engine/edge_builder.py
|
|
129
|
+
static_analyzer/engine/hierarchy_builder.py
|
|
130
|
+
static_analyzer/engine/language_adapter.py
|
|
131
|
+
static_analyzer/engine/lsp_client.py
|
|
132
|
+
static_analyzer/engine/lsp_constants.py
|
|
133
|
+
static_analyzer/engine/models.py
|
|
134
|
+
static_analyzer/engine/progress.py
|
|
135
|
+
static_analyzer/engine/protocols.py
|
|
136
|
+
static_analyzer/engine/result_converter.py
|
|
137
|
+
static_analyzer/engine/source_inspector.py
|
|
138
|
+
static_analyzer/engine/symbol_table.py
|
|
139
|
+
static_analyzer/engine/utils.py
|
|
140
|
+
static_analyzer/engine/adapters/__init__.py
|
|
141
|
+
static_analyzer/engine/adapters/go_adapter.py
|
|
142
|
+
static_analyzer/engine/adapters/java_adapter.py
|
|
143
|
+
static_analyzer/engine/adapters/php_adapter.py
|
|
144
|
+
static_analyzer/engine/adapters/python_adapter.py
|
|
145
|
+
static_analyzer/engine/adapters/typescript_adapter.py
|
|
125
146
|
static_analyzer/lsp_client/__init__.py
|
|
126
147
|
static_analyzer/lsp_client/diagnostics.py
|
|
127
148
|
tests/test_github_action.py
|
|
128
149
|
tests/test_install.py
|
|
129
150
|
tests/test_logging_config.py
|
|
130
151
|
tests/test_main.py
|
|
152
|
+
tests/test_pyproject_packages.py
|
|
131
153
|
tests/test_tool_registry.py
|
|
132
154
|
tests/test_user_config.py
|
|
133
155
|
tests/test_vscode_constants.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codeboarding"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.0"
|
|
8
8
|
description = "Interactive Diagrams for Code"
|
|
9
9
|
readme = "PYPI.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -62,7 +62,7 @@ all = [
|
|
|
62
62
|
]
|
|
63
63
|
|
|
64
64
|
[tool.setuptools]
|
|
65
|
-
packages = ["agents", "agents.prompts", "agents.tools", "core", "diagram_analysis", "health", "health.checks", "monitoring", "output_generators", "repo_utils", "static_analyzer", "static_analyzer.lsp_client", "caching"]
|
|
65
|
+
packages = ["agents", "agents.prompts", "agents.tools", "core", "diagram_analysis", "health", "health.checks", "monitoring", "output_generators", "repo_utils", "static_analyzer", "static_analyzer.engine", "static_analyzer.engine.adapters", "static_analyzer.lsp_client", "caching"]
|
|
66
66
|
py-modules = ["constants", "utils", "vscode_constants", "logging_config", "duckdb_crud", "github_action", "tool_registry", "user_config", "main", "install"]
|
|
67
67
|
include-package-data = true
|
|
68
68
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Engine package - LSP-based static analysis using global symbol-first approach."""
|
|
2
|
+
|
|
3
|
+
from static_analyzer.engine.call_graph_builder import CallGraphBuilder
|
|
4
|
+
from static_analyzer.engine.hierarchy_builder import HierarchyBuilder
|
|
5
|
+
from static_analyzer.engine.language_adapter import LanguageAdapter
|
|
6
|
+
from static_analyzer.engine.lsp_client import LSPClient
|
|
7
|
+
from static_analyzer.engine.models import AnalysisResults, CallFlowGraph, Edge, LanguageAnalysisResult, SymbolInfo
|
|
8
|
+
from static_analyzer.engine.result_converter import convert_to_codeboarding_format
|
|
9
|
+
from static_analyzer.engine.source_inspector import SourceInspector
|
|
10
|
+
from static_analyzer.engine.symbol_table import SymbolTable
|
|
11
|
+
from static_analyzer.engine.utils import uri_to_path
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AnalysisResults",
|
|
15
|
+
"CallFlowGraph",
|
|
16
|
+
"CallGraphBuilder",
|
|
17
|
+
"Edge",
|
|
18
|
+
"HierarchyBuilder",
|
|
19
|
+
"LanguageAdapter",
|
|
20
|
+
"LanguageAnalysisResult",
|
|
21
|
+
"LSPClient",
|
|
22
|
+
"SourceInspector",
|
|
23
|
+
"SymbolInfo",
|
|
24
|
+
"SymbolTable",
|
|
25
|
+
"convert_to_codeboarding_format",
|
|
26
|
+
"uri_to_path",
|
|
27
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Language adapter registry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from static_analyzer.engine.language_adapter import LanguageAdapter
|
|
6
|
+
from static_analyzer.engine.adapters.go_adapter import GoAdapter
|
|
7
|
+
from static_analyzer.engine.adapters.java_adapter import JavaAdapter
|
|
8
|
+
from static_analyzer.engine.adapters.php_adapter import PHPAdapter
|
|
9
|
+
from static_analyzer.engine.adapters.python_adapter import PythonAdapter
|
|
10
|
+
from static_analyzer.engine.adapters.typescript_adapter import JavaScriptAdapter, TypeScriptAdapter
|
|
11
|
+
|
|
12
|
+
ADAPTER_REGISTRY: dict[str, type[LanguageAdapter]] = {
|
|
13
|
+
"Python": PythonAdapter,
|
|
14
|
+
"JavaScript": JavaScriptAdapter,
|
|
15
|
+
"TypeScript": TypeScriptAdapter,
|
|
16
|
+
"Go": GoAdapter,
|
|
17
|
+
"Java": JavaAdapter,
|
|
18
|
+
"PHP": PHPAdapter,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_adapter(language: str) -> LanguageAdapter:
|
|
23
|
+
"""Get an adapter instance for the given language."""
|
|
24
|
+
cls = ADAPTER_REGISTRY.get(language)
|
|
25
|
+
if cls is None:
|
|
26
|
+
raise ValueError(f"No adapter for language: {language}")
|
|
27
|
+
return cls()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_all_adapters() -> dict[str, LanguageAdapter]:
|
|
31
|
+
"""Get instances of all registered adapters."""
|
|
32
|
+
return {lang: cls() for lang, cls in ADAPTER_REGISTRY.items()}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""Go language adapter using gopls."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from repo_utils.ignore import RepoIgnoreManager, _ALWAYS_IGNORED_DIRS
|
|
10
|
+
from static_analyzer.engine.language_adapter import LanguageAdapter
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Matches patterns like "**/dirname/**" or "**/dirname/"
|
|
15
|
+
_RECURSIVE_DIR_RE = re.compile(r"^\*\*/([a-zA-Z0-9_\-]+)(?:/\*\*)?/?$")
|
|
16
|
+
# Matches patterns like "dirname/" (bare directory)
|
|
17
|
+
_BARE_DIR_RE = re.compile(r"^([a-zA-Z0-9_\-]+)/$")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _directory_filters_from_ignore_manager(ignore_manager: RepoIgnoreManager | None) -> list[str]:
|
|
21
|
+
"""Convert .codeboardingignore directory patterns to gopls directoryFilters.
|
|
22
|
+
|
|
23
|
+
Extracts directory-level patterns from the ignore manager's codeboardingignore
|
|
24
|
+
spec and converts them to gopls format (``-**/dirname``). File-level patterns
|
|
25
|
+
(e.g. ``*.test.*``) are skipped — gopls only supports directory filtering.
|
|
26
|
+
|
|
27
|
+
Also includes the always-ignored directories (node_modules, build, etc.).
|
|
28
|
+
"""
|
|
29
|
+
filters: list[str] = []
|
|
30
|
+
seen: set[str] = set()
|
|
31
|
+
|
|
32
|
+
# Always-ignored directories first
|
|
33
|
+
for dirname in sorted(_ALWAYS_IGNORED_DIRS):
|
|
34
|
+
key = dirname.lower()
|
|
35
|
+
if key not in seen:
|
|
36
|
+
seen.add(key)
|
|
37
|
+
filters.append(f"-**/{dirname}")
|
|
38
|
+
|
|
39
|
+
if ignore_manager is None:
|
|
40
|
+
return filters
|
|
41
|
+
|
|
42
|
+
# Parse the codeboardingignore patterns for directory entries
|
|
43
|
+
for line in ignore_manager._load_codeboardingignore_patterns():
|
|
44
|
+
pattern = line.strip()
|
|
45
|
+
if not pattern or pattern.startswith("#") or pattern.startswith("!"):
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
# Match "**/dirname/**" or "**/dirname/"
|
|
49
|
+
m = _RECURSIVE_DIR_RE.match(pattern)
|
|
50
|
+
if m:
|
|
51
|
+
dirname = m.group(1)
|
|
52
|
+
key = dirname.lower()
|
|
53
|
+
if key not in seen:
|
|
54
|
+
seen.add(key)
|
|
55
|
+
filters.append(f"-**/{dirname}")
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# Match "dirname/"
|
|
59
|
+
m = _BARE_DIR_RE.match(pattern)
|
|
60
|
+
if m:
|
|
61
|
+
dirname = m.group(1)
|
|
62
|
+
key = dirname.lower()
|
|
63
|
+
if key not in seen:
|
|
64
|
+
seen.add(key)
|
|
65
|
+
filters.append(f"-**/{dirname}")
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
return filters
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class GoAdapter(LanguageAdapter):
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def language(self) -> str:
|
|
75
|
+
return "Go"
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def file_extensions(self) -> tuple[str, ...]:
|
|
79
|
+
return (".go",)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def lsp_command(self) -> list[str]:
|
|
83
|
+
return ["gopls", "serve"]
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def language_id(self) -> str:
|
|
87
|
+
return "go"
|
|
88
|
+
|
|
89
|
+
def build_qualified_name(
|
|
90
|
+
self,
|
|
91
|
+
file_path: Path,
|
|
92
|
+
symbol_name: str,
|
|
93
|
+
symbol_kind: int,
|
|
94
|
+
parent_chain: list[tuple[str, int]],
|
|
95
|
+
project_root: Path,
|
|
96
|
+
detail: str = "",
|
|
97
|
+
) -> str:
|
|
98
|
+
rel = file_path.relative_to(project_root)
|
|
99
|
+
dir_parts = list(rel.parent.parts) if rel.parent != Path(".") else []
|
|
100
|
+
file_stem = rel.stem
|
|
101
|
+
module = ".".join(dir_parts + [file_stem]) if dir_parts else file_stem
|
|
102
|
+
|
|
103
|
+
if parent_chain:
|
|
104
|
+
receiver_name, receiver_kind = parent_chain[-1]
|
|
105
|
+
is_pointer = self._is_pointer_receiver(detail, receiver_name)
|
|
106
|
+
if is_pointer:
|
|
107
|
+
return f"{module}.(*{receiver_name}).{symbol_name}"
|
|
108
|
+
else:
|
|
109
|
+
return f"{module}.({receiver_name}).{symbol_name}"
|
|
110
|
+
return f"{module}.{symbol_name}"
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def _is_pointer_receiver(detail: str, receiver_name: str) -> bool:
|
|
114
|
+
if not detail:
|
|
115
|
+
return False
|
|
116
|
+
return f"*{receiver_name}" in detail or f"* {receiver_name}" in detail
|
|
117
|
+
|
|
118
|
+
def build_reference_key(self, qualified_name: str) -> str:
|
|
119
|
+
"""Preserve original casing for Go qualified names."""
|
|
120
|
+
return qualified_name
|
|
121
|
+
|
|
122
|
+
def get_lsp_init_options(self, ignore_manager: RepoIgnoreManager | None = None) -> dict:
|
|
123
|
+
"""Configure gopls for lower memory usage.
|
|
124
|
+
|
|
125
|
+
- ``directoryFilters``: derived from ``.codeboardingignore`` patterns
|
|
126
|
+
so user customizations automatically flow to gopls. Tells gopls to
|
|
127
|
+
skip directories entirely (never loaded into memory).
|
|
128
|
+
- ``analyses``: disables all optional static analyzers. We only need
|
|
129
|
+
documentSymbol and references for call-graph construction. Core
|
|
130
|
+
diagnostics (unused imports/variables) still work because they come
|
|
131
|
+
from the Go type-checker, not from analyzers.
|
|
132
|
+
"""
|
|
133
|
+
directory_filters = _directory_filters_from_ignore_manager(ignore_manager)
|
|
134
|
+
# gopls expects flat settings (no "gopls" wrapper) in initializationOptions.
|
|
135
|
+
# The "gopls" nesting seen in editor configs is an editor convention.
|
|
136
|
+
return {
|
|
137
|
+
"directoryFilters": directory_filters,
|
|
138
|
+
"analyses": {
|
|
139
|
+
"all": False,
|
|
140
|
+
# Re-enable unused-code analyzers for dead-code detection.
|
|
141
|
+
# Both are off by default and must be explicitly enabled.
|
|
142
|
+
# unusedparams: flags unused function parameters.
|
|
143
|
+
# unusedfunc: flags unused unexported functions.
|
|
144
|
+
# unusedvariable is intentionally omitted — it only provides
|
|
145
|
+
# quick-fixes for existing compiler errors, not new diagnostics.
|
|
146
|
+
"unusedparams": True,
|
|
147
|
+
"unusedfunc": True,
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
def get_workspace_settings(self) -> dict | None:
|
|
152
|
+
# gopls requests settings via workspace/configuration with section "gopls".
|
|
153
|
+
# The response must be a flat settings object (no "gopls" wrapper).
|
|
154
|
+
return {
|
|
155
|
+
"analyses": {
|
|
156
|
+
"unusedparams": True,
|
|
157
|
+
"unusedfunc": True,
|
|
158
|
+
},
|
|
159
|
+
"ui.diagnostic.staticcheck": True,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def get_lsp_env(self) -> dict[str, str]:
|
|
163
|
+
"""Tune Go GC for lower peak memory at the cost of more CPU.
|
|
164
|
+
|
|
165
|
+
``GOGC=50`` makes the garbage collector run more frequently (default
|
|
166
|
+
is 100), roughly halving peak heap size for memory-intensive workloads
|
|
167
|
+
like gopls indexing large repositories.
|
|
168
|
+
Avoids OOM errors on large codebases, especially in constrained environments like CI.
|
|
169
|
+
"""
|
|
170
|
+
return {"GOGC": "50"}
|
|
171
|
+
|
|
172
|
+
def discover_source_files(self, project_root: Path, ignore_manager: RepoIgnoreManager) -> list[Path]:
|
|
173
|
+
"""Discover Go source files, filtering out build-tag-constrained files.
|
|
174
|
+
|
|
175
|
+
Files with ``//go:build`` or ``// +build`` directives containing
|
|
176
|
+
negations (``!``) are excluded because gopls cannot resolve package
|
|
177
|
+
metadata for them, which causes errors during cross-reference queries.
|
|
178
|
+
"""
|
|
179
|
+
files = super().discover_source_files(project_root, ignore_manager)
|
|
180
|
+
filtered = [f for f in files if not self._has_excluding_build_tag(f)]
|
|
181
|
+
skipped = len(files) - len(filtered)
|
|
182
|
+
if skipped:
|
|
183
|
+
logger.info("Filtered %d Go files with excluding build tags", skipped)
|
|
184
|
+
return filtered
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _has_excluding_build_tag(file_path: Path) -> bool:
|
|
188
|
+
"""Check if a Go file has a build constraint with negation."""
|
|
189
|
+
try:
|
|
190
|
+
with open(file_path, "r", errors="replace") as f:
|
|
191
|
+
for line in f:
|
|
192
|
+
stripped = line.strip()
|
|
193
|
+
if not stripped or stripped.startswith("//"):
|
|
194
|
+
if stripped.startswith("//go:build ") or stripped.startswith("// +build "):
|
|
195
|
+
tag_expr = (
|
|
196
|
+
stripped.split(" ", 2)[-1]
|
|
197
|
+
if stripped.startswith("// +build")
|
|
198
|
+
else stripped[len("//go:build ") :]
|
|
199
|
+
)
|
|
200
|
+
if "!" in tag_expr:
|
|
201
|
+
return True
|
|
202
|
+
continue
|
|
203
|
+
# Stop at the first non-comment, non-blank line (typically "package ...")
|
|
204
|
+
break
|
|
205
|
+
except OSError:
|
|
206
|
+
pass
|
|
207
|
+
return False
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""Java language adapter using JDTLS."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import platform
|
|
8
|
+
import tempfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from static_analyzer.engine.language_adapter import LanguageAdapter
|
|
12
|
+
from static_analyzer.constants import NodeType
|
|
13
|
+
from static_analyzer.engine.lsp_constants import (
|
|
14
|
+
CALLABLE_KINDS,
|
|
15
|
+
CLASS_LIKE_KINDS,
|
|
16
|
+
EdgeStrategy,
|
|
17
|
+
)
|
|
18
|
+
from static_analyzer.java_utils import create_jdtls_command, find_java_21_or_later
|
|
19
|
+
from utils import get_config
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class JavaAdapter(LanguageAdapter):
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def language(self) -> str:
|
|
28
|
+
return "Java"
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def file_extensions(self) -> tuple[str, ...]:
|
|
32
|
+
return (".java",)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def lsp_command(self) -> list[str]:
|
|
36
|
+
return ["jdtls"]
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def language_id(self) -> str:
|
|
40
|
+
return "java"
|
|
41
|
+
|
|
42
|
+
def get_lsp_command(self, project_root: Path) -> list[str]:
|
|
43
|
+
"""Build the full JDTLS launch command.
|
|
44
|
+
|
|
45
|
+
JDTLS cannot be started with a simple binary name — it requires a Java
|
|
46
|
+
executable, the Equinox launcher JAR, a config directory, and a unique
|
|
47
|
+
workspace data directory. We resolve the jdtls_root from the tool
|
|
48
|
+
registry config and delegate to ``create_jdtls_command``.
|
|
49
|
+
"""
|
|
50
|
+
jdtls_root = self._find_jdtls_root()
|
|
51
|
+
if jdtls_root is None:
|
|
52
|
+
raise RuntimeError(
|
|
53
|
+
"JDTLS installation not found. Run `python install.py` or " "set the jdtls_root in the tool config."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
java_home = find_java_21_or_later()
|
|
57
|
+
if java_home is None:
|
|
58
|
+
raise RuntimeError("Java 21+ required to run JDTLS. Please install JDK 21 or later.")
|
|
59
|
+
|
|
60
|
+
workspace_dir = Path(tempfile.mkdtemp(prefix="jdtls-workspace-"))
|
|
61
|
+
heap_size = self._calculate_heap_size(project_root)
|
|
62
|
+
return create_jdtls_command(jdtls_root, workspace_dir, java_home, heap_size)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def _find_jdtls_root() -> Path | None:
|
|
66
|
+
"""Locate the JDTLS installation directory."""
|
|
67
|
+
# 1. Check tool registry config (set by install.py / resolve_config)
|
|
68
|
+
lsp_servers = get_config("lsp_servers")
|
|
69
|
+
java_entry = lsp_servers.get("java", {})
|
|
70
|
+
if root_str := java_entry.get("jdtls_root"):
|
|
71
|
+
root = Path(root_str)
|
|
72
|
+
if root.is_dir() and (root / "plugins").is_dir():
|
|
73
|
+
return root
|
|
74
|
+
|
|
75
|
+
# 2. Fallback to well-known locations (platform-specific)
|
|
76
|
+
well_known: list[Path] = [
|
|
77
|
+
Path.home() / ".jdtls",
|
|
78
|
+
Path.home() / ".codeboarding" / "servers" / "bin" / "jdtls",
|
|
79
|
+
]
|
|
80
|
+
if platform.system() == "Windows":
|
|
81
|
+
local_app_data = os.environ.get("LOCALAPPDATA", "")
|
|
82
|
+
if local_app_data:
|
|
83
|
+
well_known.append(Path(local_app_data) / "jdtls")
|
|
84
|
+
program_files = os.environ.get("ProgramFiles", "C:\\Program Files")
|
|
85
|
+
well_known.append(Path(program_files) / "jdtls")
|
|
86
|
+
else:
|
|
87
|
+
well_known.append(Path("/opt/jdtls"))
|
|
88
|
+
|
|
89
|
+
for location in well_known:
|
|
90
|
+
if location.is_dir() and (location / "plugins").is_dir():
|
|
91
|
+
logger.info(f"Found JDTLS at {location}")
|
|
92
|
+
return location
|
|
93
|
+
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _calculate_heap_size(project_root: Path) -> str:
|
|
98
|
+
"""Calculate appropriate JVM heap size based on file count and available system memory.
|
|
99
|
+
|
|
100
|
+
Caps the heap at ~50% of physical RAM to avoid starving the OS and other processes.
|
|
101
|
+
"""
|
|
102
|
+
jvm_files = (
|
|
103
|
+
list(project_root.rglob("*.java")) + list(project_root.rglob("*.kt")) + list(project_root.rglob("*.groovy"))
|
|
104
|
+
)
|
|
105
|
+
file_count = len(jvm_files)
|
|
106
|
+
if file_count < 100:
|
|
107
|
+
desired_gb = 1
|
|
108
|
+
elif file_count < 500:
|
|
109
|
+
desired_gb = 2
|
|
110
|
+
elif file_count < 2000:
|
|
111
|
+
desired_gb = 4
|
|
112
|
+
elif file_count < 5000:
|
|
113
|
+
desired_gb = 6
|
|
114
|
+
else:
|
|
115
|
+
desired_gb = 8
|
|
116
|
+
|
|
117
|
+
# Cap at 50% of available physical memory
|
|
118
|
+
try:
|
|
119
|
+
total_ram_bytes = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES")
|
|
120
|
+
total_ram_gb = total_ram_bytes / (1024**3)
|
|
121
|
+
max_heap_gb = max(1, int(total_ram_gb * 0.5))
|
|
122
|
+
desired_gb = min(desired_gb, max_heap_gb)
|
|
123
|
+
except (ValueError, OSError):
|
|
124
|
+
pass # os.sysconf not available (e.g. Windows) — use file-count estimate as-is
|
|
125
|
+
|
|
126
|
+
return f"{desired_gb}G"
|
|
127
|
+
|
|
128
|
+
def build_qualified_name(
|
|
129
|
+
self,
|
|
130
|
+
file_path: Path,
|
|
131
|
+
symbol_name: str,
|
|
132
|
+
symbol_kind: int,
|
|
133
|
+
parent_chain: list[tuple[str, int]],
|
|
134
|
+
project_root: Path,
|
|
135
|
+
detail: str = "",
|
|
136
|
+
) -> str:
|
|
137
|
+
rel = file_path.relative_to(project_root)
|
|
138
|
+
module = ".".join(rel.with_suffix("").parts)
|
|
139
|
+
clean_name = self._clean_symbol_name(symbol_name)
|
|
140
|
+
|
|
141
|
+
if parent_chain:
|
|
142
|
+
# In Java, the filename IS the top-level class name (e.g., Dog.java -> module "...Dog").
|
|
143
|
+
# Skip the first parent if it matches the last module component to avoid
|
|
144
|
+
# doubled names like "core.Dog.Dog.speak()" -> "core.Dog.speak()".
|
|
145
|
+
module_last = module.rsplit(".", 1)[-1] if "." in module else module
|
|
146
|
+
effective_parents = list(parent_chain)
|
|
147
|
+
if effective_parents and self._clean_symbol_name(effective_parents[0][0]) == module_last:
|
|
148
|
+
effective_parents = effective_parents[1:]
|
|
149
|
+
|
|
150
|
+
if effective_parents:
|
|
151
|
+
clean_parents = ".".join(self._clean_symbol_name(name) for name, _ in effective_parents)
|
|
152
|
+
return f"{module}.{clean_parents}.{clean_name}"
|
|
153
|
+
return f"{module}.{clean_name}"
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _clean_symbol_name(name: str) -> str:
|
|
157
|
+
"""Clean a JDTLS symbol name by stripping generics from params."""
|
|
158
|
+
stripped = name.strip()
|
|
159
|
+
paren_idx = stripped.find("(")
|
|
160
|
+
if paren_idx == -1:
|
|
161
|
+
return stripped
|
|
162
|
+
|
|
163
|
+
depth = 0
|
|
164
|
+
close_idx = -1
|
|
165
|
+
for i in range(paren_idx, len(stripped)):
|
|
166
|
+
if stripped[i] == "(":
|
|
167
|
+
depth += 1
|
|
168
|
+
elif stripped[i] == ")":
|
|
169
|
+
depth -= 1
|
|
170
|
+
if depth == 0:
|
|
171
|
+
close_idx = i
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
if close_idx == -1:
|
|
175
|
+
return stripped
|
|
176
|
+
|
|
177
|
+
method_name = stripped[:paren_idx]
|
|
178
|
+
params_raw = stripped[paren_idx + 1 : close_idx]
|
|
179
|
+
suffix = stripped[close_idx + 1 :].strip()
|
|
180
|
+
|
|
181
|
+
if suffix.startswith("<"):
|
|
182
|
+
suffix = ""
|
|
183
|
+
|
|
184
|
+
params = []
|
|
185
|
+
for param in JavaAdapter._split_params(params_raw):
|
|
186
|
+
param = param.strip()
|
|
187
|
+
if not param:
|
|
188
|
+
continue
|
|
189
|
+
params.append(JavaAdapter._strip_generics(param))
|
|
190
|
+
|
|
191
|
+
result = f"{method_name}({', '.join(params)})"
|
|
192
|
+
if suffix:
|
|
193
|
+
result += " " + suffix
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _strip_generics(param: str) -> str:
|
|
198
|
+
"""Strip generic type parameters: 'List<Animal>' -> 'List'."""
|
|
199
|
+
result = []
|
|
200
|
+
depth = 0
|
|
201
|
+
for ch in param:
|
|
202
|
+
if ch == "<":
|
|
203
|
+
depth += 1
|
|
204
|
+
elif ch == ">":
|
|
205
|
+
depth -= 1
|
|
206
|
+
elif depth == 0:
|
|
207
|
+
result.append(ch)
|
|
208
|
+
return "".join(result).strip()
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def _split_params(raw: str) -> list[str]:
|
|
212
|
+
"""Split parameter list respecting angle brackets."""
|
|
213
|
+
params = []
|
|
214
|
+
depth = 0
|
|
215
|
+
current: list[str] = []
|
|
216
|
+
for ch in raw:
|
|
217
|
+
if ch == "<":
|
|
218
|
+
depth += 1
|
|
219
|
+
current.append(ch)
|
|
220
|
+
elif ch == ">":
|
|
221
|
+
depth -= 1
|
|
222
|
+
current.append(ch)
|
|
223
|
+
elif ch == "," and depth == 0:
|
|
224
|
+
params.append("".join(current))
|
|
225
|
+
current = []
|
|
226
|
+
else:
|
|
227
|
+
current.append(ch)
|
|
228
|
+
if current:
|
|
229
|
+
params.append("".join(current))
|
|
230
|
+
return params
|
|
231
|
+
|
|
232
|
+
def extract_package(self, qualified_name: str) -> str:
|
|
233
|
+
parts = qualified_name.split(".")
|
|
234
|
+
|
|
235
|
+
java_idx = None
|
|
236
|
+
for i, part in enumerate(parts):
|
|
237
|
+
if part == "java" and i >= 1 and parts[i - 1] == "main":
|
|
238
|
+
java_idx = i
|
|
239
|
+
break
|
|
240
|
+
|
|
241
|
+
if java_idx is None:
|
|
242
|
+
try:
|
|
243
|
+
java_idx = parts.index("java")
|
|
244
|
+
except ValueError:
|
|
245
|
+
return parts[0]
|
|
246
|
+
|
|
247
|
+
after_root = parts[java_idx + 1 :]
|
|
248
|
+
if not after_root:
|
|
249
|
+
return parts[0]
|
|
250
|
+
|
|
251
|
+
pkg_parts = []
|
|
252
|
+
for part in after_root:
|
|
253
|
+
clean = part.split("(")[0]
|
|
254
|
+
if clean in ("package-info", "module-info"):
|
|
255
|
+
break
|
|
256
|
+
if clean and clean[0].isupper():
|
|
257
|
+
break
|
|
258
|
+
pkg_parts.append(part)
|
|
259
|
+
|
|
260
|
+
if pkg_parts:
|
|
261
|
+
return ".".join(pkg_parts)
|
|
262
|
+
return after_root[0]
|
|
263
|
+
|
|
264
|
+
def get_workspace_settings(self) -> dict | None:
|
|
265
|
+
# JDTLS defaults these to "ignore"; raising to "warning" is required
|
|
266
|
+
# for unused-code diagnostics.
|
|
267
|
+
return {
|
|
268
|
+
"java": {
|
|
269
|
+
"settings": {
|
|
270
|
+
"org.eclipse.jdt.core.compiler.problem.unusedImport": "warning",
|
|
271
|
+
"org.eclipse.jdt.core.compiler.problem.unusedLocal": "warning",
|
|
272
|
+
"org.eclipse.jdt.core.compiler.problem.unusedPrivateMember": "warning",
|
|
273
|
+
"org.eclipse.jdt.core.compiler.problem.deadCode": "warning",
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def edge_strategy(self) -> EdgeStrategy:
|
|
280
|
+
"""Use definition-based edges — JDTLS serializes references requests."""
|
|
281
|
+
return EdgeStrategy.DEFINITIONS
|
|
282
|
+
|
|
283
|
+
def should_track_for_edges(self, symbol_kind: int) -> bool:
|
|
284
|
+
return symbol_kind in (CALLABLE_KINDS | CLASS_LIKE_KINDS | {NodeType.VARIABLE, NodeType.CONSTANT})
|
|
285
|
+
|
|
286
|
+
def get_package_for_file(self, file_path: Path, project_root: Path) -> str:
|
|
287
|
+
"""Get Java package from file path by stripping src/main/java/ prefix."""
|
|
288
|
+
qname = self.build_qualified_name(file_path, "", 0, [], project_root)
|
|
289
|
+
return self.extract_package(qname)
|
|
290
|
+
|
|
291
|
+
def get_all_packages(self, source_files: list[Path], project_root: Path) -> set[str]:
|
|
292
|
+
packages: set[str] = set()
|
|
293
|
+
for f in source_files:
|
|
294
|
+
packages.add(self.get_package_for_file(f, project_root))
|
|
295
|
+
return packages
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""PHP language adapter using Intelephense."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from repo_utils.ignore import RepoIgnoreManager
|
|
8
|
+
from static_analyzer.engine.language_adapter import LanguageAdapter
|
|
9
|
+
from static_analyzer.constants import NodeType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PHPAdapter(LanguageAdapter):
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def language(self) -> str:
|
|
16
|
+
return "PHP"
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def file_extensions(self) -> tuple[str, ...]:
|
|
20
|
+
return (".php",)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def lsp_command(self) -> list[str]:
|
|
24
|
+
return ["intelephense", "--stdio"]
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def language_id(self) -> str:
|
|
28
|
+
return "php"
|
|
29
|
+
|
|
30
|
+
def extract_package(self, qualified_name: str) -> str:
|
|
31
|
+
return self._extract_deep_package(qualified_name)
|
|
32
|
+
|
|
33
|
+
def get_lsp_init_options(self, ignore_manager: RepoIgnoreManager | None = None) -> dict:
|
|
34
|
+
return {"clearCache": False}
|
|
35
|
+
|
|
36
|
+
def get_workspace_settings(self) -> dict | None:
|
|
37
|
+
# unusedSymbols is already true by default but we set it defensively.
|
|
38
|
+
return {
|
|
39
|
+
"intelephense": {
|
|
40
|
+
"diagnostics": {
|
|
41
|
+
"unusedSymbols": True,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def is_reference_worthy(self, symbol_kind: int) -> bool:
|
|
47
|
+
return super().is_reference_worthy(symbol_kind) or symbol_kind == NodeType.MODULE
|
|
48
|
+
|
|
49
|
+
def get_all_packages(self, source_files: list[Path], project_root: Path) -> set[str]:
|
|
50
|
+
return self._get_hierarchical_packages(source_files, project_root)
|