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.
- {codeboarding-0.15.4/codeboarding.egg-info → codeboarding-0.15.5}/PKG-INFO +1 -1
- {codeboarding-0.15.4 → codeboarding-0.15.5/codeboarding.egg-info}/PKG-INFO +1 -1
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/installers.py +32 -7
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/java.py +23 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/pyproject.toml +1 -1
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/java_adapter.py +43 -6
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/edge_builder.py +39 -5
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/hierarchy_builder.py +38 -22
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/language_adapter.py +13 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/lsp_client.py +6 -1
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/protocols.py +6 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/source_inspector.py +36 -3
- {codeboarding-0.15.4 → codeboarding-0.15.5}/LICENSE +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/PYPI.md +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/README.md +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/agent_responses.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/change_status.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/component_ownership.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/constants.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/content_hash.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/file_index_models.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/incremental_results.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_config.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_errors.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_renderers/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_renderers/call_graph.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/llm_renderers/scope.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/model_capabilities.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/planner_agent.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/prompts/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/prompts/profiles.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/prompts/shared.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/relation_edges.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/retry.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/scope_analysis_agent.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/scope_ids.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tools/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tools/base.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tools/get_method_calls.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tools/read_file.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/agents/tree_planner_agent.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/SOURCES.txt +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/dependency_links.txt +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/entry_points.txt +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/requires.txt +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding.egg-info/top_level.txt +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/bootstrap.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/commands/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/commands/full_analysis.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/commands/incremental_analysis.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/commands/partial_analysis.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/github_action.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/main.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/render.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_cli/view_instructions.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/analysis.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/fingerprint_diff.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/orchestration.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/sources/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/sources/local.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/codeboarding_workflows/sources/remote.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/analysis_json.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/cluster_relations.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/diagram_generator.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/exceptions.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/file_coverage.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/file_index.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/incremental_changes.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/incremental_update.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/io_utils.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/reference_resolver.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/run_context.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/run_mode.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/scope_assembly.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/scope_plan.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/diagram_analysis/tree_shape.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/checks/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/checks/circular_deps.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/checks/function_size.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/checks/unused_code_diagnostics.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/config.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/main.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/models.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/plugins/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/plugins/plugin_loader.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/plugins/protocols.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/plugins/registry.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/health/runner.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/clustering_ids.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/constants.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/install.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/logging_config.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/callbacks.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/context.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/mixin.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/paths.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/stats.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/device_id.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/events.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/schemas.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/telemetry/service.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/monitoring/writers.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/change_detector.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/errors.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/git_ops.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/ignore.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/repo_utils/path_utils.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/grammars.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/kotlin-server-1.json +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/manifest.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/paths.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/tool_registry/registry.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/user_config.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/utils.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/infra/vscode_constants.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/html.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/html_template.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/markdown.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/mdx.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/rendering.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/output_generators/sphinx.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/setup.cfg +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/analysis_cache.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/analysis_result.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/cfg/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/cfg/call_graph.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/cfg/edge.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/cfg/location_key.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/exceptions.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/models.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/draft.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/frontier.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/inventory.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/replay.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/spec.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/names/tokens.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/clustering/service.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/config.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/csharp_config_scanner.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/dotnet_sdk.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/dotnet_solution.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/cpp_adapter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/csharp_adapter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/go_adapter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/kotlin_adapter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/php_adapter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/python_adapter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/rust_adapter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/adapters/typescript_adapter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/call_graph_builder.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/cpp_templates.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/edge_build_context.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/lsp_constants.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/lsp_recycler.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/models.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/process_memory.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/progress.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/result_converter.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/symbol_table.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/engine/utils.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/exceptions.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/external_calls.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/graph_definitions.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/incremental_orchestrator.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/internal_references.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/java_config_scanner.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/java_utils.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/language_results.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/lsp_client/__init__.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/lsp_client/diagnostics.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/node.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/programming_language.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/scanner.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/typescript_config_scanner.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/index.js +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/typescript_plugin/node_modules/codeboarding-workspace-sources/package.json +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/typescript_workspace.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/static_analyzer/workspace_dependencies.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_devcontainer.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_persisted_analysis_artifacts.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_pyproject_packages.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_view_instructions.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_windows_compatibility.py +0 -0
- {codeboarding-0.15.4 → codeboarding-0.15.5}/tests/test_windows_encoding.py +0 -0
|
@@ -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
|
|
566
|
-
"""Whether an archive tool is extracted in full: its launcher
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
@@ -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
|
-
"""
|
|
338
|
+
"""An internal error thrown from JDT's compiler or code assist leaves that one call site's edge out.
|
|
306
339
|
|
|
307
|
-
Why:
|
|
308
|
-
A
|
|
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
|
|
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
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1492
|
-
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|