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.
Files changed (159) hide show
  1. {codeboarding-0.9.6/codeboarding.egg-info → codeboarding-0.10.0}/PKG-INFO +1 -1
  2. {codeboarding-0.9.6 → codeboarding-0.10.0/codeboarding.egg-info}/PKG-INFO +1 -1
  3. {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/SOURCES.txt +22 -0
  4. {codeboarding-0.9.6 → codeboarding-0.10.0}/pyproject.toml +2 -2
  5. codeboarding-0.10.0/static_analyzer/engine/__init__.py +27 -0
  6. codeboarding-0.10.0/static_analyzer/engine/adapters/__init__.py +32 -0
  7. codeboarding-0.10.0/static_analyzer/engine/adapters/go_adapter.py +207 -0
  8. codeboarding-0.10.0/static_analyzer/engine/adapters/java_adapter.py +295 -0
  9. codeboarding-0.10.0/static_analyzer/engine/adapters/php_adapter.py +50 -0
  10. codeboarding-0.10.0/static_analyzer/engine/adapters/python_adapter.py +56 -0
  11. codeboarding-0.10.0/static_analyzer/engine/adapters/typescript_adapter.py +51 -0
  12. codeboarding-0.10.0/static_analyzer/engine/call_graph_builder.py +292 -0
  13. codeboarding-0.10.0/static_analyzer/engine/edge_build_context.py +18 -0
  14. codeboarding-0.10.0/static_analyzer/engine/edge_builder.py +506 -0
  15. codeboarding-0.10.0/static_analyzer/engine/hierarchy_builder.py +200 -0
  16. codeboarding-0.10.0/static_analyzer/engine/language_adapter.py +271 -0
  17. codeboarding-0.10.0/static_analyzer/engine/lsp_client.py +679 -0
  18. codeboarding-0.10.0/static_analyzer/engine/lsp_constants.py +33 -0
  19. codeboarding-0.10.0/static_analyzer/engine/models.py +101 -0
  20. codeboarding-0.10.0/static_analyzer/engine/progress.py +83 -0
  21. codeboarding-0.10.0/static_analyzer/engine/protocols.py +48 -0
  22. codeboarding-0.10.0/static_analyzer/engine/result_converter.py +133 -0
  23. codeboarding-0.10.0/static_analyzer/engine/source_inspector.py +203 -0
  24. codeboarding-0.10.0/static_analyzer/engine/symbol_table.py +328 -0
  25. codeboarding-0.10.0/static_analyzer/engine/utils.py +22 -0
  26. codeboarding-0.10.0/tests/test_pyproject_packages.py +52 -0
  27. {codeboarding-0.9.6 → codeboarding-0.10.0}/LICENSE +0 -0
  28. {codeboarding-0.9.6 → codeboarding-0.10.0}/PYPI.md +0 -0
  29. {codeboarding-0.9.6 → codeboarding-0.10.0}/README.md +0 -0
  30. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/__init__.py +0 -0
  31. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/abstraction_agent.py +0 -0
  32. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/agent.py +0 -0
  33. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/agent_responses.py +0 -0
  34. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/change_status.py +0 -0
  35. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/cluster_methods_mixin.py +0 -0
  36. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/constants.py +0 -0
  37. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/dependency_discovery.py +0 -0
  38. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/details_agent.py +0 -0
  39. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/llm_config.py +0 -0
  40. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/meta_agent.py +0 -0
  41. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/planner_agent.py +0 -0
  42. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/__init__.py +0 -0
  43. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/abstract_prompt_factory.py +0 -0
  44. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/claude_prompts.py +0 -0
  45. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/deepseek_prompts.py +0 -0
  46. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/gemini_flash_prompts.py +0 -0
  47. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/glm_prompts.py +0 -0
  48. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/gpt_prompts.py +0 -0
  49. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/kimi_prompts.py +0 -0
  50. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/prompts/prompt_factory.py +0 -0
  51. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/__init__.py +0 -0
  52. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/base.py +0 -0
  53. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/get_external_deps.py +0 -0
  54. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/get_method_invocations.py +0 -0
  55. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_cfg.py +0 -0
  56. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_docs.py +0 -0
  57. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_file.py +0 -0
  58. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_file_structure.py +0 -0
  59. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_git_diff.py +0 -0
  60. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_packages.py +0 -0
  61. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_source.py +0 -0
  62. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/read_structure.py +0 -0
  63. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/tools/toolkit.py +0 -0
  64. {codeboarding-0.9.6 → codeboarding-0.10.0}/agents/validation.py +0 -0
  65. {codeboarding-0.9.6 → codeboarding-0.10.0}/caching/__init__.py +0 -0
  66. {codeboarding-0.9.6 → codeboarding-0.10.0}/caching/cache.py +0 -0
  67. {codeboarding-0.9.6 → codeboarding-0.10.0}/caching/details_cache.py +0 -0
  68. {codeboarding-0.9.6 → codeboarding-0.10.0}/caching/meta_cache.py +0 -0
  69. {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/dependency_links.txt +0 -0
  70. {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/entry_points.txt +0 -0
  71. {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/requires.txt +0 -0
  72. {codeboarding-0.9.6 → codeboarding-0.10.0}/codeboarding.egg-info/top_level.txt +0 -0
  73. {codeboarding-0.9.6 → codeboarding-0.10.0}/constants.py +0 -0
  74. {codeboarding-0.9.6 → codeboarding-0.10.0}/core/__init__.py +0 -0
  75. {codeboarding-0.9.6 → codeboarding-0.10.0}/core/plugin_loader.py +0 -0
  76. {codeboarding-0.9.6 → codeboarding-0.10.0}/core/protocols.py +0 -0
  77. {codeboarding-0.9.6 → codeboarding-0.10.0}/core/registry.py +0 -0
  78. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/__init__.py +0 -0
  79. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/analysis_json.py +0 -0
  80. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/diagram_generator.py +0 -0
  81. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/file_coverage.py +0 -0
  82. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/incremental_types.py +0 -0
  83. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/incremental_updater.py +0 -0
  84. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/io_utils.py +0 -0
  85. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/manifest.py +0 -0
  86. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/run_context.py +0 -0
  87. {codeboarding-0.9.6 → codeboarding-0.10.0}/diagram_analysis/version.py +0 -0
  88. {codeboarding-0.9.6 → codeboarding-0.10.0}/duckdb_crud.py +0 -0
  89. {codeboarding-0.9.6 → codeboarding-0.10.0}/github_action.py +0 -0
  90. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/__init__.py +0 -0
  91. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/__init__.py +0 -0
  92. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/circular_deps.py +0 -0
  93. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/cohesion.py +0 -0
  94. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/coupling.py +0 -0
  95. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/function_size.py +0 -0
  96. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/god_class.py +0 -0
  97. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/inheritance.py +0 -0
  98. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/instability.py +0 -0
  99. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/checks/unused_code_diagnostics.py +0 -0
  100. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/config.py +0 -0
  101. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/constants.py +0 -0
  102. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/models.py +0 -0
  103. {codeboarding-0.9.6 → codeboarding-0.10.0}/health/runner.py +0 -0
  104. {codeboarding-0.9.6 → codeboarding-0.10.0}/health_main.py +0 -0
  105. {codeboarding-0.9.6 → codeboarding-0.10.0}/install.py +0 -0
  106. {codeboarding-0.9.6 → codeboarding-0.10.0}/logging_config.py +0 -0
  107. {codeboarding-0.9.6 → codeboarding-0.10.0}/main.py +0 -0
  108. {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/__init__.py +0 -0
  109. {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/callbacks.py +0 -0
  110. {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/context.py +0 -0
  111. {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/mixin.py +0 -0
  112. {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/paths.py +0 -0
  113. {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/stats.py +0 -0
  114. {codeboarding-0.9.6 → codeboarding-0.10.0}/monitoring/writers.py +0 -0
  115. {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/__init__.py +0 -0
  116. {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/html.py +0 -0
  117. {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/html_template.py +0 -0
  118. {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/markdown.py +0 -0
  119. {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/mdx.py +0 -0
  120. {codeboarding-0.9.6 → codeboarding-0.10.0}/output_generators/sphinx.py +0 -0
  121. {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/__init__.py +0 -0
  122. {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/change_detector.py +0 -0
  123. {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/errors.py +0 -0
  124. {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/git_diff.py +0 -0
  125. {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/ignore.py +0 -0
  126. {codeboarding-0.9.6 → codeboarding-0.10.0}/repo_utils/method_diff.py +0 -0
  127. {codeboarding-0.9.6 → codeboarding-0.10.0}/setup.cfg +0 -0
  128. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/__init__.py +0 -0
  129. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/analysis_cache.py +0 -0
  130. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/analysis_result.py +0 -0
  131. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/cluster_change_analyzer.py +0 -0
  132. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/cluster_helpers.py +0 -0
  133. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/cluster_relations.py +0 -0
  134. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/constants.py +0 -0
  135. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/git_diff_analyzer.py +0 -0
  136. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/graph.py +0 -0
  137. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/incremental_orchestrator.py +0 -0
  138. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/java_config_scanner.py +0 -0
  139. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/java_utils.py +0 -0
  140. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/lsp_client/__init__.py +0 -0
  141. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/lsp_client/diagnostics.py +0 -0
  142. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/node.py +0 -0
  143. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/programming_language.py +0 -0
  144. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/reference_resolve_mixin.py +0 -0
  145. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/scanner.py +0 -0
  146. {codeboarding-0.9.6 → codeboarding-0.10.0}/static_analyzer/typescript_config_scanner.py +0 -0
  147. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_github_action.py +0 -0
  148. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_install.py +0 -0
  149. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_logging_config.py +0 -0
  150. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_main.py +0 -0
  151. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_tool_registry.py +0 -0
  152. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_user_config.py +0 -0
  153. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_vscode_constants.py +0 -0
  154. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_windows_compatibility.py +0 -0
  155. {codeboarding-0.9.6 → codeboarding-0.10.0}/tests/test_windows_encoding.py +0 -0
  156. {codeboarding-0.9.6 → codeboarding-0.10.0}/tool_registry.py +0 -0
  157. {codeboarding-0.9.6 → codeboarding-0.10.0}/user_config.py +0 -0
  158. {codeboarding-0.9.6 → codeboarding-0.10.0}/utils.py +0 -0
  159. {codeboarding-0.9.6 → codeboarding-0.10.0}/vscode_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.9.6
3
+ Version: 0.10.0
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.9.6
3
+ Version: 0.10.0
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -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.9.6"
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)