codeboarding 0.15.4__tar.gz → 0.15.5__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 (200) hide show
  1. {codeboarding-0.15.4/codeboarding.egg-info → codeboarding-0.15.5}/PKG-INFO +1 -1
  2. {codeboarding-0.15.4 → codeboarding-0.15.5/codeboarding.egg-info}/PKG-INFO +1 -1
  3. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/installers.py +32 -7
  4. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/java.py +23 -0
  5. {codeboarding-0.15.4 → codeboarding-0.15.5}/pyproject.toml +1 -1
  6. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/java_adapter.py +43 -6
  7. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/edge_builder.py +39 -5
  8. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/hierarchy_builder.py +38 -22
  9. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/language_adapter.py +13 -0
  10. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/lsp_client.py +6 -1
  11. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/protocols.py +6 -0
  12. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/source_inspector.py +36 -3
  13. {codeboarding-0.15.4 → codeboarding-0.15.5}/LICENSE +0 -0
  14. {codeboarding-0.15.4 → codeboarding-0.15.5}/PYPI.md +0 -0
  15. {codeboarding-0.15.4 → codeboarding-0.15.5}/README.md +0 -0
  16. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/__init__.py +0 -0
  17. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/agent_responses.py +0 -0
  18. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/change_status.py +0 -0
  19. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/component_ownership.py +0 -0
  20. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/constants.py +0 -0
  21. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/content_hash.py +0 -0
  22. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/file_index_models.py +0 -0
  23. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/incremental_results.py +0 -0
  24. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_config.py +0 -0
  25. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_errors.py +0 -0
  26. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_renderers/__init__.py +0 -0
  27. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_renderers/call_graph.py +0 -0
  28. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_renderers/scope.py +0 -0
  29. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/model_capabilities.py +0 -0
  30. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/planner_agent.py +0 -0
  31. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/prompts/__init__.py +0 -0
  32. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/prompts/profiles.py +0 -0
  33. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/prompts/shared.py +0 -0
  34. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/relation_edges.py +0 -0
  35. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/retry.py +0 -0
  36. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/scope_analysis_agent.py +0 -0
  37. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/scope_ids.py +0 -0
  38. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tools/__init__.py +0 -0
  39. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tools/base.py +0 -0
  40. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tools/get_method_calls.py +0 -0
  41. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tools/read_file.py +0 -0
  42. {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tree_planner_agent.py +0 -0
  43. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/SOURCES.txt +0 -0
  44. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/dependency_links.txt +0 -0
  45. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/entry_points.txt +0 -0
  46. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/requires.txt +0 -0
  47. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/top_level.txt +0 -0
  48. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/__init__.py +0 -0
  49. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/bootstrap.py +0 -0
  50. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/commands/__init__.py +0 -0
  51. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/commands/full_analysis.py +0 -0
  52. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/commands/incremental_analysis.py +0 -0
  53. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/commands/partial_analysis.py +0 -0
  54. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/github_action.py +0 -0
  55. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/main.py +0 -0
  56. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/render.py +0 -0
  57. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/view_instructions.py +0 -0
  58. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/__init__.py +0 -0
  59. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/analysis.py +0 -0
  60. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/fingerprint_diff.py +0 -0
  61. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/orchestration.py +0 -0
  62. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/sources/__init__.py +0 -0
  63. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/sources/local.py +0 -0
  64. {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/sources/remote.py +0 -0
  65. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/__init__.py +0 -0
  66. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/analysis_json.py +0 -0
  67. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/cluster_relations.py +0 -0
  68. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/diagram_generator.py +0 -0
  69. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/exceptions.py +0 -0
  70. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/file_coverage.py +0 -0
  71. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/file_index.py +0 -0
  72. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/incremental_changes.py +0 -0
  73. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/incremental_update.py +0 -0
  74. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/io_utils.py +0 -0
  75. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/reference_resolver.py +0 -0
  76. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/run_context.py +0 -0
  77. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/run_mode.py +0 -0
  78. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/scope_assembly.py +0 -0
  79. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/scope_plan.py +0 -0
  80. {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/tree_shape.py +0 -0
  81. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/__init__.py +0 -0
  82. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/checks/__init__.py +0 -0
  83. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/checks/circular_deps.py +0 -0
  84. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/checks/function_size.py +0 -0
  85. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/checks/unused_code_diagnostics.py +0 -0
  86. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/config.py +0 -0
  87. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/main.py +0 -0
  88. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/models.py +0 -0
  89. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/plugins/__init__.py +0 -0
  90. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/plugins/plugin_loader.py +0 -0
  91. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/plugins/protocols.py +0 -0
  92. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/plugins/registry.py +0 -0
  93. {codeboarding-0.15.4 → codeboarding-0.15.5}/health/runner.py +0 -0
  94. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/__init__.py +0 -0
  95. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/clustering_ids.py +0 -0
  96. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/constants.py +0 -0
  97. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/install.py +0 -0
  98. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/logging_config.py +0 -0
  99. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/__init__.py +0 -0
  100. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/callbacks.py +0 -0
  101. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/context.py +0 -0
  102. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/mixin.py +0 -0
  103. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/paths.py +0 -0
  104. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/stats.py +0 -0
  105. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/__init__.py +0 -0
  106. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/device_id.py +0 -0
  107. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/events.py +0 -0
  108. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/schemas.py +0 -0
  109. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/service.py +0 -0
  110. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/writers.py +0 -0
  111. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/__init__.py +0 -0
  112. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/change_detector.py +0 -0
  113. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/errors.py +0 -0
  114. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/git_ops.py +0 -0
  115. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/ignore.py +0 -0
  116. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/path_utils.py +0 -0
  117. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/__init__.py +0 -0
  118. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/grammars.py +0 -0
  119. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/kotlin-server-1.json +0 -0
  120. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/manifest.py +0 -0
  121. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/paths.py +0 -0
  122. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/registry.py +0 -0
  123. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/user_config.py +0 -0
  124. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/utils.py +0 -0
  125. {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/vscode_constants.py +0 -0
  126. {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/__init__.py +0 -0
  127. {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/html.py +0 -0
  128. {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/html_template.py +0 -0
  129. {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/markdown.py +0 -0
  130. {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/mdx.py +0 -0
  131. {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/rendering.py +0 -0
  132. {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/sphinx.py +0 -0
  133. {codeboarding-0.15.4 → codeboarding-0.15.5}/setup.cfg +0 -0
  134. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/__init__.py +0 -0
  135. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/analysis_cache.py +0 -0
  136. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/analysis_result.py +0 -0
  137. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/cfg/__init__.py +0 -0
  138. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/cfg/call_graph.py +0 -0
  139. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/cfg/edge.py +0 -0
  140. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/cfg/location_key.py +0 -0
  141. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/__init__.py +0 -0
  142. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/exceptions.py +0 -0
  143. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/models.py +0 -0
  144. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/__init__.py +0 -0
  145. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/draft.py +0 -0
  146. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/frontier.py +0 -0
  147. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/inventory.py +0 -0
  148. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/replay.py +0 -0
  149. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/spec.py +0 -0
  150. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/tokens.py +0 -0
  151. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/service.py +0 -0
  152. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/config.py +0 -0
  153. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/csharp_config_scanner.py +0 -0
  154. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/dotnet_sdk.py +0 -0
  155. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/dotnet_solution.py +0 -0
  156. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/__init__.py +0 -0
  157. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/__init__.py +0 -0
  158. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/cpp_adapter.py +0 -0
  159. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/csharp_adapter.py +0 -0
  160. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/go_adapter.py +0 -0
  161. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/kotlin_adapter.py +0 -0
  162. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/php_adapter.py +0 -0
  163. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/python_adapter.py +0 -0
  164. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/rust_adapter.py +0 -0
  165. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
  166. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/call_graph_builder.py +0 -0
  167. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/cpp_templates.py +0 -0
  168. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/edge_build_context.py +0 -0
  169. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/lsp_constants.py +0 -0
  170. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/lsp_recycler.py +0 -0
  171. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/models.py +0 -0
  172. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/process_memory.py +0 -0
  173. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/progress.py +0 -0
  174. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/result_converter.py +0 -0
  175. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/symbol_table.py +0 -0
  176. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/utils.py +0 -0
  177. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/exceptions.py +0 -0
  178. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/external_calls.py +0 -0
  179. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/graph_definitions.py +0 -0
  180. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/incremental_orchestrator.py +0 -0
  181. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/internal_references.py +0 -0
  182. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/java_config_scanner.py +0 -0
  183. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/java_utils.py +0 -0
  184. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/language_results.py +0 -0
  185. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/lsp_client/__init__.py +0 -0
  186. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/lsp_client/diagnostics.py +0 -0
  187. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/node.py +0 -0
  188. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/programming_language.py +0 -0
  189. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/scanner.py +0 -0
  190. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/typescript_config_scanner.py +0 -0
  191. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/index.js +0 -0
  192. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/package.json +0 -0
  193. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/typescript_workspace.py +0 -0
  194. {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/workspace_dependencies.py +0 -0
  195. {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_devcontainer.py +0 -0
  196. {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_persisted_analysis_artifacts.py +0 -0
  197. {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_pyproject_packages.py +0 -0
  198. {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_view_instructions.py +0 -0
  199. {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_windows_compatibility.py +0 -0
  200. {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_windows_encoding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeboarding
3
- Version: 0.15.4
3
+ Version: 0.15.5
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.15.4
3
+ Version: 0.15.5
4
4
  Summary: Interactive Diagrams for Code
5
5
  Author: CodeBoarding Team
6
6
  License-Expression: MIT
@@ -562,23 +562,48 @@ def current_tool(dep: ToolDependency, target_dir: Path | None = None) -> ToolDep
562
562
  return replace(dep, source=replace(dep.source, tag=installed)) if installed else dep
563
563
 
564
564
 
565
- def archive_tool_is_installed(target_dir: Path, dep: ToolDependency) -> bool:
566
- """Whether an archive tool is extracted in full: its launcher at the pinned version, or JDTLS's ``plugins/``."""
565
+ def _archive_tool_is_extracted(target_dir: Path, dep: ToolDependency) -> bool:
566
+ """Whether an archive tool is extracted in full, at any version: its launcher, or JDTLS's ``plugins/``."""
567
567
  launcher = archive_launcher_path(target_dir, dep)
568
568
  if launcher is not None:
569
- stamp = target_dir / "bin" / dep.archive_subdir / ARCHIVE_VERSION_STAMP
570
- return launcher.is_file() and dep.source is not None and _read_stamp(stamp) == dep.source.tag
569
+ return launcher.is_file()
571
570
  return (target_dir / "bin" / dep.archive_subdir / "plugins").is_dir()
572
571
 
573
572
 
573
+ def archive_tool_is_installed(target_dir: Path, dep: ToolDependency) -> bool:
574
+ """Whether an archive tool is extracted in full at the pinned version.
575
+
576
+ Why the stamp for JDTLS too: with ``plugins/`` alone, an install from before a pin bump counted as current
577
+ forever, and kept analysing with a server too old to know the project's Java version.
578
+ """
579
+ stamp = target_dir / "bin" / dep.archive_subdir / ARCHIVE_VERSION_STAMP
580
+ return (
581
+ _archive_tool_is_extracted(target_dir, dep)
582
+ and dep.source is not None
583
+ and _read_stamp(stamp) == archive_stamp(dep.source)
584
+ )
585
+
586
+
587
+ def archive_stamp(source: ToolSource) -> str:
588
+ """What an install of *source* writes into its stamp: the tag, and the build where the pin names one.
589
+
590
+ Why the build: JDTLS pins snapshots, and a bump can change only the build.
591
+ """
592
+ if isinstance(source, UpstreamToolSource) and source.build:
593
+ return f"{source.tag}-{source.build}"
594
+ return source.tag
595
+
596
+
574
597
  def archive_tool_is_usable(target_dir: Path, dep: ToolDependency) -> bool:
575
598
  """Whether a run can start an archive tool as it is on disk.
576
599
 
577
- kotlin-lsp does not have to be at the latest pin for that, which also keeps a build that a failed update left in
578
- place. This asks on every run, whatever its languages, so it must not read main's pin.
600
+ kotlin-lsp and JDTLS do not have to be at the latest pin for that, which also keeps a build that a failed update
601
+ left in place. This asks on every run, whatever its languages, so it must not read main's pin.
579
602
  """
580
603
  if dep.key == "kotlin":
581
604
  return _usable_kotlin_lsp_build(target_dir, dep) is not None
605
+ if archive_launcher_path(target_dir, dep) is None:
606
+ return _archive_tool_is_extracted(target_dir, dep)
582
607
  return archive_tool_is_installed(target_dir, dep)
583
608
 
584
609
 
@@ -639,7 +664,7 @@ def install_archive_tool(
639
664
  shutil.rmtree(extract_dir, ignore_errors=True)
640
665
  os.replace(root, extract_dir)
641
666
  shutil.rmtree(staging, ignore_errors=True)
642
- (extract_dir / ARCHIVE_VERSION_STAMP).write_text(dep.source.tag)
667
+ (extract_dir / ARCHIVE_VERSION_STAMP).write_text(archive_stamp(dep.source))
643
668
  logger.info("%s installed successfully", dep.key)
644
669
  except Exception:
645
670
  logger.exception("%s installation failed", dep.key)
@@ -1,5 +1,6 @@
1
1
  """Locating a Java 21+ runtime on the host."""
2
2
 
3
+ import functools
3
4
  import logging
4
5
  import os
5
6
  import platform
@@ -49,6 +50,14 @@ def detect_java_installations() -> list[Path]:
49
50
  if java_home := os.getenv("JAVA_HOME"):
50
51
  candidates.append(Path(java_home))
51
52
 
53
+ # GitHub-hosted runners preinstall several JDKs and name each one JAVA_HOME_<major>_<arch>.
54
+ candidates.extend(Path(value) for name, value in os.environ.items() if re.fullmatch(r"JAVA_HOME_\d+_\w+", name))
55
+
56
+ # JDKs Gradle provisioned for a build's toolchain, nested one or two folders deep by vendor.
57
+ gradle_jdks = Path(os.getenv("GRADLE_USER_HOME") or Path.home() / ".gradle") / "jdks"
58
+ for pattern in ("*", "*/*", "*/*/Contents/Home"):
59
+ candidates.extend(gradle_jdks.glob(pattern))
60
+
52
61
  # Platform-specific search paths
53
62
  system = platform.system()
54
63
 
@@ -94,6 +103,20 @@ def detect_java_installations() -> list[Path]:
94
103
  return unique_jdks
95
104
 
96
105
 
106
+ @functools.cache
107
+ def java_runtimes() -> dict[int, Path]:
108
+ """One installed JDK per Java major version, for JDTLS to compile each project against the release it targets.
109
+
110
+ Why: JDTLS knows only the JDK it runs on, so a project built for a newer release than that has no system library,
111
+ and every JDK type in it is unresolved.
112
+ """
113
+ runtimes: dict[int, Path] = {}
114
+ for jdk in detect_java_installations():
115
+ runtimes.setdefault(get_java_version(str(jdk / "bin" / "java")), jdk)
116
+ runtimes.pop(0, None)
117
+ return runtimes
118
+
119
+
97
120
  def find_java_21_or_later() -> Path | None:
98
121
  """
99
122
  Find a Java 21+ installation.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codeboarding"
7
- version = "0.15.4"
7
+ version = "0.15.5"
8
8
  description = "Interactive Diagrams for Code"
9
9
  readme = "PYPI.md"
10
10
  license = "MIT"
@@ -13,13 +13,15 @@ from pathlib import Path
13
13
 
14
14
  from static_analyzer.config import Language, NodeType
15
15
  from static_analyzer.engine.language_adapter import LanguageAdapter
16
- from static_analyzer.engine.lsp_client import ErrorVerdict
16
+ from static_analyzer.engine.lsp_client import LSP_INTERNAL_ERROR, ErrorVerdict
17
17
  from static_analyzer.engine.lsp_constants import (
18
18
  CALLABLE_KINDS,
19
19
  CLASS_LIKE_KINDS,
20
20
  )
21
21
  from static_analyzer.engine.utils import total_ram_gb
22
22
  from static_analyzer.java_utils import create_jdtls_command, find_java_21_or_later
23
+ from infra.repo_utils.ignore import RepoIgnoreManager
24
+ from infra.tool_registry.java import java_runtimes
23
25
  from infra.utils import CODEBOARDING_DIR_NAME, get_config
24
26
 
25
27
  logger = logging.getLogger(__name__)
@@ -29,6 +31,8 @@ _CLASSPATH_ENTRY = re.compile(
29
31
  rb"(<classpathentry\b[^>]*[^/]>)((?:(?!<classpathentry\b).)*?</classpathentry>)", re.DOTALL
30
32
  )
31
33
  _INCLUSION_FILTER = re.compile(rb'\s+including="[^"]*"')
34
+ # Indexing is one-off and grows with the classpath; a server that cannot search within this is broken.
35
+ _SEARCH_INDEX_TIMEOUT_SECONDS = 1800
32
36
 
33
37
  # The languages a JVM source root can be named for, in ``src/<source set>/<language>``.
34
38
 
@@ -48,6 +52,21 @@ class JavaAdapter(LanguageAdapter):
48
52
  """
49
53
  return True
50
54
 
55
+ @property
56
+ def source_names_every_subtype(self) -> bool:
57
+ """A subclass, an anonymous class body or an enum constant body names its base; a lambda implements only
58
+ an interface, which is always asked about."""
59
+ return True
60
+
61
+ @property
62
+ def search_index_timeout(self) -> int | None:
63
+ """JDTLS indexes the classpath, the JDK included, on its first search.
64
+
65
+ Why: on JabRef that took 72-105s, longer than a request may take, so the first round of implementation queries
66
+ timed out and ended the run.
67
+ """
68
+ return _SEARCH_INDEX_TIMEOUT_SECONDS
69
+
51
70
  @property
52
71
  def language(self) -> str:
53
72
  return "Java"
@@ -273,6 +292,11 @@ class JavaAdapter(LanguageAdapter):
273
292
  finally:
274
293
  self._restore_import_filters()
275
294
 
295
+ def get_lsp_init_options(self, ignore_manager: RepoIgnoreManager | None = None) -> dict:
296
+ """The settings at startup too: a Maven import runs before the first settings change, and with
297
+ ``<release>25</release>`` and no JDK 25 registered by then it reports the release as not found."""
298
+ return {"settings": self.get_workspace_settings()}
299
+
276
300
  def get_workspace_settings(self) -> dict | None:
277
301
  # JDTLS defaults these to "ignore"; raising to "warning" is required
278
302
  # for unused-code diagnostics.
@@ -283,7 +307,16 @@ class JavaAdapter(LanguageAdapter):
283
307
  "org.eclipse.jdt.core.compiler.problem.unusedLocal": "warning",
284
308
  "org.eclipse.jdt.core.compiler.problem.unusedPrivateMember": "warning",
285
309
  "org.eclipse.jdt.core.compiler.problem.deadCode": "warning",
286
- }
310
+ },
311
+ # A project's JDK types resolve only against a JDK at least as new as the release it targets, and
312
+ # JDTLS knows only the one it runs on unless told of the rest. JabRef targets Java 25.
313
+ "configuration": {
314
+ "runtimes": [
315
+ {"name": "JavaSE-1.8" if major == 8 else f"JavaSE-{major}", "path": str(home)}
316
+ for major, home in sorted(java_runtimes().items())
317
+ if major >= 8
318
+ ]
319
+ },
287
320
  }
288
321
  }
289
322
 
@@ -302,13 +335,17 @@ class JavaAdapter(LanguageAdapter):
302
335
  return symbol_kind in (CALLABLE_KINDS | CLASS_LIKE_KINDS | {NodeType.VARIABLE, NodeType.CONSTANT})
303
336
 
304
337
  def error_verdict(self, error: dict) -> ErrorVerdict:
305
- """A crash in JDT's code selection is final for that call site, and its edge is left out with a warning.
338
+ """An internal error thrown from JDT's compiler or code assist leaves that one call site's edge out.
306
339
 
307
- Why: asking again crashes the same way, so one site the compiler cannot walk must not cost the whole run.
308
- A timeout or a lost server is not this, and still fails it.
340
+ Why: it is a function of the source alone, so asking again crashes the same way. The first frame past the
341
+ JDK's own decides. A cancellation or interruption, a JVM error, or a crash in JDT LS's own code still fails.
309
342
  """
310
343
  data = str(error.get("data", ""))
311
- if re.search(r"Caused by: java\.lang\.NullPointerException.*\n\s*at \S*codeassist\.SelectionEngine", data):
344
+ if error.get("code") == LSP_INTERNAL_ERROR and re.search(
345
+ r"Caused by: java\.lang\.(?!InterruptedException)\w+Exception\b.*\n(?:\s*at java\.base/.*\n)*"
346
+ r"\s*at org\.eclipse\.jdt\.internal\.(?:compiler|codeassist)\.",
347
+ data,
348
+ ):
312
349
  logger.warning(f"JDTLS crashed resolving a call site, leaving its edge out: {data}")
313
350
  return ErrorVerdict.DECLINED
314
351
  return ErrorVerdict.FAILURE
@@ -233,7 +233,7 @@ def build_edges_via_definitions(
233
233
  )
234
234
 
235
235
  total_impl_resolved = _resolve_implementations(
236
- adapter, ctx, resolution.edge_set, resolution.impl_queries_pending, index
236
+ adapter, ctx, resolution.edge_set, resolution.impl_queries_pending, index, source_files
237
237
  )
238
238
 
239
239
  total_iterated = 0
@@ -446,6 +446,7 @@ def _resolve_implementations(
446
446
  edge_set: EdgeMap,
447
447
  impl_queries_pending: list[ImplementationQuery],
448
448
  index: SymbolIndex,
449
+ source_files: list[Path],
449
450
  ) -> int:
450
451
  """Phase 2b: Resolve implementations for polymorphic call targets.
451
452
 
@@ -453,23 +454,56 @@ def _resolve_implementations(
453
454
  """
454
455
  st = ctx.symbol_table
455
456
 
457
+ # A member is only implemented in a type deriving from its owner, so a class no source derives from has nothing
458
+ # to find. Why: on JabRef 9 in 10 targets were such, and JDTLS took ~0.8s on each to say so, 2.3h in all.
459
+ # An interface is always asked: a lambda implements one without naming it. Read like the dispatch index, the
460
+ # known declarations' files too, so a warm start still sees a subclass in an unchanged file.
461
+ derived: set[str] = set()
462
+ if adapter.source_names_every_subtype:
463
+ files = set(source_files) | {
464
+ sym.file_path for sym in chain(st.symbols.values(), st.known) if adapter.is_class_like(sym.kind)
465
+ }
466
+ derived = set(chain.from_iterable(ctx.source_inspector.find_derived_type_names(f) for f in files))
467
+
456
468
  target_pos_to_callers: dict[tuple[str, int, int], list[tuple[str, CallSite]]] = {}
469
+ underived = 0
457
470
  for query in impl_queries_pending:
471
+ owner = st.symbols.get(parent_qualified_name(query.target_qname))
472
+ if (
473
+ adapter.source_names_every_subtype
474
+ and owner is not None
475
+ and adapter.is_class_like(owner.kind)
476
+ and owner.kind != NodeType.INTERFACE
477
+ # Source names a base without its type parameters; servers name a generic type with them.
478
+ and owner.name.split("<")[0].strip() not in derived
479
+ ):
480
+ underived += 1
481
+ continue
458
482
  tgt_key = (str(query.target_file), query.target_line, query.target_char)
459
483
  target_pos_to_callers.setdefault(tgt_key, []).append((query.caller_qname, query.call_site))
460
484
 
461
485
  targets = list(target_pos_to_callers)
462
486
  logger.info(
463
- "Phase 2b (implementations): %d unique targets from %d pending queries", len(targets), len(impl_queries_pending)
487
+ "Phase 2b (implementations): %d unique targets from %d pending queries (%d on a class nothing derives from)",
488
+ len(targets),
489
+ len(impl_queries_pending),
490
+ underived,
464
491
  )
465
492
 
466
493
  total_impl_resolved = 0
467
494
  # Why a round at a time here: on a large repository this is the longest phase, and progress is only
468
495
  # reported between rounds.
469
496
  pbar = ProgressLogger("Phase 2b (impl)", len(targets), unit="target")
470
- for start in range(0, len(targets), REQUEST_BATCH_SIZE):
471
- batch = targets[start : start + REQUEST_BATCH_SIZE]
472
- impl_results = ctx.lsp.send_implementation_batch([(Path(file), line, char) for file, line, char in batch])
497
+ index_timeout = adapter.search_index_timeout
498
+ start = 0
499
+ while start < len(targets):
500
+ # A server that builds its search index on its first search gets that search alone, with the time indexing
501
+ # takes; the rounds after it are answered from the index.
502
+ batch = targets[start : start + (1 if start == 0 and index_timeout else REQUEST_BATCH_SIZE)]
503
+ impl_results = ctx.lsp.send_implementation_batch(
504
+ [(Path(file), line, char) for file, line, char in batch], timeout=index_timeout if start == 0 else None
505
+ )
506
+ start += len(batch)
473
507
  for tgt_key, impls in zip(batch, impl_results):
474
508
  callers = target_pos_to_callers[tgt_key]
475
509
  for impl_result in impls:
@@ -11,6 +11,7 @@ from static_analyzer.config import NodeType
11
11
  from static_analyzer.engine.language_adapter import LanguageAdapter
12
12
  from static_analyzer.engine.lsp_client import LSPClient, MethodNotFoundError
13
13
  from static_analyzer.engine.models import SymbolInfo
14
+ from static_analyzer.engine.progress import ProgressLogger
14
15
  from static_analyzer.engine.source_inspector import SourceInspector
15
16
  from static_analyzer.engine.symbol_table import SymbolTable
16
17
  from static_analyzer.engine.utils import definition_location, uri_to_path
@@ -62,7 +63,18 @@ class HierarchyBuilder:
62
63
  return hierarchy
63
64
 
64
65
  type_hierarchy_supported = False
66
+ # Why a line per round: one class at a time, JabRef's 2,701 took over half an hour without one.
67
+ pbar = ProgressLogger("Hierarchy", len(class_symbols), unit="type")
65
68
  for sym in class_symbols:
69
+ pbar.update(1)
70
+ # A class body on an enum constant subclasses its enum; preparing it lands on the constant, not a type.
71
+ # Its name is ``{...}``, dots and all, so the enum is what precedes the constant and it.
72
+ if [kind for _, kind in sym.parent_chain[-2:]] == [NodeType.ENUM, NodeType.ENUM_MEMBER]:
73
+ enum_name = sym.qualified_name.removesuffix(f".{sym.parent_chain[-1][0]}.{sym.name}")
74
+ if enum_name in hierarchy:
75
+ hierarchy[sym.qualified_name]["superclasses"].append(enum_name)
76
+ hierarchy[enum_name]["subclasses"].append(sym.qualified_name)
77
+ continue
66
78
  try:
67
79
  items = self._lsp.type_hierarchy_prepare(sym.file_path, sym.start_line, sym.start_char)
68
80
  if not items:
@@ -70,8 +82,17 @@ class HierarchyBuilder:
70
82
  type_hierarchy_supported = True
71
83
  item = items[0]
72
84
 
85
+ # Supertypes alone: every class here is asked, so each subclass link is found from its subclass.
86
+ # Why not subtypes as well: JDTLS re-resolves every potential subtype for each, with every file
87
+ # open; on JabRef that was all but a minute of the half hour, for the same links.
73
88
  try:
74
- supertypes = self._lsp.type_hierarchy_supertypes(item)
89
+ prepared = self._resolve_type_hierarchy_item(item)
90
+ if prepared is not None and prepared != sym.qualified_name:
91
+ # An anonymous class is named by the type it instantiates, so preparing it lands on that
92
+ # type, its base. Its supertypes would be the base's own.
93
+ supertypes = [item]
94
+ else:
95
+ supertypes = self._lsp.type_hierarchy_supertypes(item)
75
96
  for st in supertypes:
76
97
  super_name = self._resolve_type_hierarchy_item(st)
77
98
  if super_name and super_name in hierarchy:
@@ -83,26 +104,14 @@ class HierarchyBuilder:
83
104
  pass
84
105
  except Exception as e:
85
106
  logger.debug("Failed to get supertypes for %s: %s", sym.qualified_name, e)
86
-
87
- try:
88
- subtypes = self._lsp.type_hierarchy_subtypes(item)
89
- for st in subtypes:
90
- sub_name = self._resolve_type_hierarchy_item(st)
91
- if sub_name and sub_name in hierarchy:
92
- if sub_name not in hierarchy[sym.qualified_name]["subclasses"]:
93
- hierarchy[sym.qualified_name]["subclasses"].append(sub_name)
94
- if sym.qualified_name not in hierarchy[sub_name]["superclasses"]:
95
- hierarchy[sub_name]["superclasses"].append(sym.qualified_name)
96
- except MethodNotFoundError:
97
- pass
98
- except Exception as e:
99
- logger.debug("Failed to get subtypes for %s: %s", sym.qualified_name, e)
100
107
  except MethodNotFoundError:
101
108
  logger.info("Type hierarchy not supported by server, skipping remaining classes")
102
109
  break
103
110
  except Exception as e:
104
111
  logger.debug("Type hierarchy not supported for %s: %s", sym.qualified_name, e)
105
112
 
113
+ pbar.finish()
114
+
106
115
  if not type_hierarchy_supported and self._adapter.resolves_bases_by_definition:
107
116
  logger.info("Type hierarchy not supported, resolving the bases written in source")
108
117
  self._resolve_bases_by_definition(class_symbols, hierarchy)
@@ -121,7 +130,9 @@ class HierarchyBuilder:
121
130
 
122
131
  def _resolve_type_hierarchy_item(self, item: dict) -> str | None:
123
132
  """Resolve a type hierarchy item to a qualified name in our symbol table."""
124
- name = item.get("name", "")
133
+ # Names compare without type arguments: a supertype comes back as its use names it, ``TypeAdapter<Object>``,
134
+ # not as declared, ``TypeAdapter<T>``. Asking supertypes alone, that lost every link to a generic base.
135
+ name = item.get("name", "").split("<")[0]
125
136
  uri = item.get("uri", "")
126
137
  sel_range = item.get("selectionRange", item.get("range", {}))
127
138
  start = sel_range.get("start", {})
@@ -131,15 +142,20 @@ class HierarchyBuilder:
131
142
  if file_path is None:
132
143
  return None
133
144
 
134
- file_key = str(file_path)
135
- for sym in self._symbol_table.file_symbols.get(file_key, []):
136
- if sym.name == name and sym.start_line == line:
145
+ symbols = self._symbol_table.file_symbols.get(str(file_path), [])
146
+ # The declaration's own position first: a name is not unique, every anonymous class in a file shares one.
147
+ for sym in symbols:
148
+ if self._adapter.is_class_like(sym.kind) and (sym.start_line, sym.start_char) == (
149
+ line,
150
+ start.get("character"),
151
+ ):
137
152
  return sym.qualified_name
138
- if sym.name == name and abs(sym.start_line - line) <= 1:
153
+ for sym in symbols:
154
+ if sym.name.split("<")[0] == name and abs(sym.start_line - line) <= 1:
139
155
  return sym.qualified_name
140
156
 
141
- for sym in self._symbol_table.file_symbols.get(file_key, []):
142
- if sym.name == name and self._adapter.is_class_like(sym.kind):
157
+ for sym in symbols:
158
+ if sym.name.split("<")[0] == name and self._adapter.is_class_like(sym.kind):
143
159
  return sym.qualified_name
144
160
  return None
145
161
 
@@ -267,6 +267,11 @@ class LanguageAdapter(ABC):
267
267
  """Raise if a ready signal represents an unusable workspace state."""
268
268
  return None
269
269
 
270
+ @property
271
+ def search_index_timeout(self) -> int | None:
272
+ """Seconds the server's first search may take, for a server that builds its search index on it; None if not."""
273
+ return None
274
+
270
275
  def get_lsp_env(self, project_root: Path | None = None, source_files: Sequence[Path] = ()) -> dict[str, str]:
271
276
  """Return extra environment variables for the LSP server process.
272
277
 
@@ -401,6 +406,14 @@ class LanguageAdapter(ABC):
401
406
  neither ``textDocument/implementation`` nor ``typeHierarchy``."""
402
407
  return False
403
408
 
409
+ @property
410
+ def source_names_every_subtype(self) -> bool:
411
+ """Whether every type deriving from another names it in source, as
412
+ ``SourceInspector.find_derived_type_names`` reads it. Then a member of a
413
+ class nothing derives from is owed no ``textDocument/implementation``
414
+ query. Not so where inheritance is implicit (Go) or not yet read (TS)."""
415
+ return False
416
+
404
417
  @property
405
418
  def drain_probe_round_seconds(self) -> int | None:
406
419
  """Seconds after which the didOpen drain probe is asked again, or None to wait the whole budget
@@ -33,6 +33,7 @@ LSP_STDERR_TAIL_LINES = 32
33
33
  # JSON AST nodes retain more Python objects than tree-sitter's native nodes.
34
34
  LSP_AST_NODE_BUDGET = 50_000
35
35
  LSP_METHOD_NOT_FOUND = -32601
36
+ LSP_INTERNAL_ERROR = -32603
36
37
  # Codes LSP (-32899..-32800) and JSON-RPC (-32768..-32000) reserve for a request that went unserved;
37
38
  # any other code is the server declining for itself, as gopls does for a free function.
38
39
  LSP_RESERVED_ERROR_CODE_RANGES = ((-32899, -32800), (-32768, -32000))
@@ -923,6 +924,9 @@ class LSPClient:
923
924
  continue
924
925
 
925
926
  pending.discard(msg_id) # type: ignore[arg-type]
927
+ # The timeout is for a server gone quiet, not for the whole round: JDTLS answers a round one request
928
+ # at a time, and on JabRef 50 implementation queries took longer than it while answering throughout.
929
+ deadline = time.monotonic() + timeout
926
930
 
927
931
  if "error" in msg:
928
932
  err = msg["error"]
@@ -943,7 +947,8 @@ class LSPClient:
943
947
  if self._error_verdict is not None and isinstance(err, dict) and _is_protocol_failure(err):
944
948
  verdict = self._error_verdict(err)
945
949
  if _is_protocol_failure(err) and verdict is not ErrorVerdict.DECLINED:
946
- unserved[msg_id] = err_msg # type: ignore[index]
950
+ # The server's own trace is the only record of why; "Internal error." alone can't be diagnosed.
951
+ unserved[msg_id] = f"{err_msg} {err['data']}" if err.get("data") else err_msg # type: ignore[index]
947
952
  if _is_retryable(err) or verdict is ErrorVerdict.TRANSIENT:
948
953
  retryable.add(msg_id) # type: ignore[arg-type]
949
954
  else:
@@ -46,6 +46,9 @@ class EdgeBuildAdapter(Protocol):
46
46
  @property
47
47
  def expands_virtual_dispatch(self) -> bool: ...
48
48
 
49
+ @property
50
+ def source_names_every_subtype(self) -> bool: ...
51
+
49
52
  @property
50
53
  def constructor_calls_resolve_to_class(self) -> bool: ...
51
54
 
@@ -61,6 +64,9 @@ class EdgeBuildAdapter(Protocol):
61
64
  @property
62
65
  def resolves_collection_initializers(self) -> bool: ...
63
66
 
67
+ @property
68
+ def search_index_timeout(self) -> int | None: ...
69
+
64
70
  def should_track_for_edges(self, symbol_kind: int) -> bool: ...
65
71
 
66
72
  def is_class_like(self, symbol_kind: int) -> bool: ...
@@ -301,7 +301,7 @@ _KOTLIN_PARAMETER_LIST_NODE_TYPES = frozenset({"function_value_parameters"})
301
301
  _KOTLIN_PARAMETER_PART_NODE_TYPES = frozenset({"simple_identifier", "modifiers", "binding_pattern_kind"})
302
302
  _ARGUMENT_HOLDER_NODE_TYPES = frozenset({"call_expression", "constructor_invocation", "constructor_delegation_call"})
303
303
  _KOTLIN_DOC_COMMENT_NODE_TYPES = frozenset({"multiline_comment"})
304
- _KOTLIN_TYPE_ARGUMENTS = re.compile(r"<[^<>]*>")
304
+ _TYPE_ARGUMENTS = re.compile(r"<[^<>]*>")
305
305
  # A package segment is lower case; an outer type's name before a nested one is kept, so
306
306
  # ``PublicKeyCredentialEntry.Builder`` and ``PasswordCredentialEntry.Builder`` stay apart.
307
307
  _KOTLIN_PACKAGE_QUALIFIER = re.compile(r"\b[a-z_][\w`]*\.(?=[\w`])")
@@ -1346,6 +1346,39 @@ class SourceInspector:
1346
1346
  declarations.append((text(name_node), bases))
1347
1347
  return declarations
1348
1348
 
1349
+ def find_derived_type_names(self, file_path: Path) -> set[str]:
1350
+ """Every type name something in the file derives from.
1351
+
1352
+ That is a declared type's bases, the type an anonymous class body extends, and the enum a Java enum constant
1353
+ with a body subclasses. Why: a member is only ever implemented in a type that derives from its owner, so an
1354
+ owner no file names here has no implementation to ask the server for.
1355
+ """
1356
+ parsed = self._parse(file_path)
1357
+ if parsed is None:
1358
+ return set()
1359
+
1360
+ def simple_name(node: TreeSitterNode) -> str:
1361
+ # ``Outer<T>.@Nullable Factory<Map.Entry<K, V>>`` names ``Factory``: drop the type arguments, innermost
1362
+ # first, then the qualifier, then any annotation written before the name.
1363
+ written = parsed.content[node.start_byte : node.end_byte].decode("utf8", "replace")
1364
+ while _TYPE_ARGUMENTS.search(written):
1365
+ written = _TYPE_ARGUMENTS.sub("", written)
1366
+ return written.rsplit(".", 1)[-1].split()[-1]
1367
+
1368
+ names: set[str] = set()
1369
+ for node in self._walk(parsed.tree.root_node):
1370
+ if node.type in _TYPE_DECLARATION_NODE_TYPES:
1371
+ names.update(simple_name(base) for base in self._base_type_nodes(node))
1372
+ if not any(child.type == "class_body" for child in node.named_children):
1373
+ continue
1374
+ if node.type == "object_creation_expression" and (created := node.child_by_field_name("type")):
1375
+ names.add(simple_name(created))
1376
+ elif node.type == "enum_constant" and node.parent is not None and node.parent.parent is not None:
1377
+ enum = node.parent.parent.child_by_field_name("name")
1378
+ if enum is not None:
1379
+ names.add(simple_name(enum))
1380
+ return names
1381
+
1349
1382
  def find_base_type_sites(self, file_path: Path) -> list[tuple[tuple[int, int], list[tuple[int, int]]]]:
1350
1383
  """Each type the file declares, by its name's position, with the positions naming its bases.
1351
1384
 
@@ -1488,8 +1521,8 @@ class SourceInspector:
1488
1521
  def render_type(node: TreeSitterNode) -> str:
1489
1522
  # A function type's arrow is set aside first: its ``>`` would otherwise close a type argument.
1490
1523
  rendered = " ".join(text(node).split()).replace("->", "\0")
1491
- while _KOTLIN_TYPE_ARGUMENTS.search(rendered):
1492
- rendered = _KOTLIN_TYPE_ARGUMENTS.sub("", rendered)
1524
+ while _TYPE_ARGUMENTS.search(rendered):
1525
+ rendered = _TYPE_ARGUMENTS.sub("", rendered)
1493
1526
  return _KOTLIN_PACKAGE_QUALIFIER.sub("", rendered).replace("\0", "->")
1494
1527
 
1495
1528
  def parameter_types(node: TreeSitterNode, receiver: TreeSitterNode | None) -> str:
File without changes
File without changes
File without changes