java-functional-lsp 0.9.18__tar.gz → 0.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/PKG-INFO +1 -1
  2. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/pyproject.toml +1 -1
  3. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/__init__.py +1 -1
  4. java_functional_lsp-0.10.0/src/java_functional_lsp/capabilities/__init__.py +21 -0
  5. java_functional_lsp-0.10.0/src/java_functional_lsp/capabilities/handler_wiring.py +69 -0
  6. java_functional_lsp-0.10.0/src/java_functional_lsp/capabilities/negotiator.py +53 -0
  7. java_functional_lsp-0.10.0/src/java_functional_lsp/capabilities/probe.py +77 -0
  8. java_functional_lsp-0.10.0/src/java_functional_lsp/capabilities/registry.py +191 -0
  9. java_functional_lsp-0.10.0/src/java_functional_lsp/capabilities/static_builder.py +41 -0
  10. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/server.py +59 -72
  11. java_functional_lsp-0.10.0/tests/test_capabilities/__init__.py +0 -0
  12. java_functional_lsp-0.10.0/tests/test_capabilities/test_handler_wiring.py +138 -0
  13. java_functional_lsp-0.10.0/tests/test_capabilities/test_negotiator.py +78 -0
  14. java_functional_lsp-0.10.0/tests/test_capabilities/test_probe.py +108 -0
  15. java_functional_lsp-0.10.0/tests/test_capabilities/test_registry.py +61 -0
  16. java_functional_lsp-0.10.0/tests/test_capabilities/test_static_builder.py +79 -0
  17. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_e2e.py +148 -0
  18. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_server.py +129 -24
  19. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/uv.lock +1 -1
  20. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.claude-plugin/plugin.json +0 -0
  21. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.githooks/pre-commit +0 -0
  22. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.githooks/pre-push +0 -0
  23. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/CODEOWNERS +0 -0
  24. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  25. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  26. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/SECURITY.md +0 -0
  28. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/dependabot.yml +0 -0
  29. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/release-drafter.yml +0 -0
  30. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/workflows/publish.yml +0 -0
  31. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/workflows/release-drafter.yml +0 -0
  32. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/workflows/stale.yml +0 -0
  33. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/workflows/test.yml +0 -0
  34. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/workflows/update-homebrew.yml +0 -0
  35. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.github/workflows/vscode-ext.yml +0 -0
  36. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/.gitignore +0 -0
  37. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/CONTRIBUTING.md +0 -0
  38. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/LICENSE +0 -0
  39. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/README.md +0 -0
  40. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/SKILL.md +0 -0
  41. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/commands/lint-java.md +0 -0
  42. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/editors/intellij/README.md +0 -0
  43. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/editors/intellij/lsp4ij-template.json +0 -0
  44. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/editors/vscode/.vscodeignore +0 -0
  45. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/editors/vscode/README.md +0 -0
  46. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/editors/vscode/package-lock.json +0 -0
  47. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/editors/vscode/package.json +0 -0
  48. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/editors/vscode/src/extension.ts +0 -0
  49. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/editors/vscode/tsconfig.json +0 -0
  50. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/hooks/hooks.json +0 -0
  51. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/hooks/java_linter_reminder.py +0 -0
  52. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/scripts/ensure-lsp.sh +0 -0
  53. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/scripts/generate-formula.py +0 -0
  54. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/__main__.py +0 -0
  55. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/analyzers/__init__.py +0 -0
  56. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/analyzers/base.py +0 -0
  57. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
  58. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
  59. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
  60. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
  61. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
  62. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/cli.py +0 -0
  63. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/fixes.py +0 -0
  64. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/merkle.py +0 -0
  65. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/src/java_functional_lsp/proxy.py +0 -0
  66. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/__init__.py +0 -0
  67. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/conftest.py +0 -0
  68. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_base.py +0 -0
  69. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_cli.py +0 -0
  70. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_config.py +0 -0
  71. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_e2e_jdtls.py +0 -0
  72. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_exception_checker.py +0 -0
  73. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_fixes.py +0 -0
  74. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_functional_checker.py +0 -0
  75. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_merkle.py +0 -0
  76. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_merkle_proxy.py +0 -0
  77. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_mutation_checker.py +0 -0
  78. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_null_checker.py +0 -0
  79. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_proxy.py +0 -0
  80. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_spring_checker.py +0 -0
  81. {java_functional_lsp-0.9.18 → java_functional_lsp-0.10.0}/tests/test_suppress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-functional-lsp
3
- Version: 0.9.18
3
+ Version: 0.10.0
4
4
  Summary: Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions
5
5
  Project-URL: Homepage, https://github.com/aviadshiber/java-functional-lsp
6
6
  Project-URL: Repository, https://github.com/aviadshiber/java-functional-lsp
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "java-functional-lsp"
7
- version = "0.9.18"
7
+ version = "0.10.0"
8
8
  description = "Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,3 +1,3 @@
1
1
  """java-functional-lsp: A Java LSP server enforcing functional programming best practices."""
2
2
 
3
- __version__ = "0.9.18"
3
+ __version__ = "0.10.0"
@@ -0,0 +1,21 @@
1
+ """Capability negotiation — splits jdtls features into static-advertised vs
2
+ dynamically-registered based on what the LSP client claims to support."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from .handler_wiring import HandlerWiring
7
+ from .negotiator import CapabilityNegotiator, NegotiationResult
8
+ from .probe import ClientCapabilityProbe
9
+ from .registry import REGISTRY, CapabilityEntry, all_methods
10
+ from .static_builder import StaticCapabilityBuilder
11
+
12
+ __all__ = [
13
+ "REGISTRY",
14
+ "CapabilityEntry",
15
+ "CapabilityNegotiator",
16
+ "ClientCapabilityProbe",
17
+ "HandlerWiring",
18
+ "NegotiationResult",
19
+ "StaticCapabilityBuilder",
20
+ "all_methods",
21
+ ]
@@ -0,0 +1,69 @@
1
+ """Wire jdtls passthrough handlers into pygls' feature dispatcher.
2
+
3
+ When `server.feature(method)(handler)` is called, pygls registers the handler so
4
+ incoming requests for that method reach our code. The same call also influences
5
+ pygls' auto-advertised capabilities, but we're not relying on that — we own the
6
+ ServerCapabilities object explicitly via StaticCapabilityBuilder.
7
+
8
+ `wire_eager` and `wire_lazy` share their body. Different names exist so the
9
+ call-site intent (during `on_initialize` vs after jdtls warm-up) is self-evident.
10
+ That readability is the SRP win.
11
+
12
+ Idempotency: pygls' FeatureManager raises FeatureAlreadyRegisteredError if a
13
+ method is registered twice. Tests use a module-level `server` singleton across
14
+ many initialize cycles, so we skip already-registered methods rather than
15
+ re-raising. In production the same guard is harmless — the second call is just
16
+ a no-op.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from collections.abc import Callable, Mapping, Sequence
22
+ from typing import Any
23
+
24
+ from .registry import CapabilityEntry
25
+
26
+
27
+ class HandlerWiring:
28
+ """Registers pygls feature handlers for capability entries."""
29
+
30
+ def __init__(self, server: Any, handlers: Mapping[str, Callable[..., Any]]) -> None:
31
+ self._server = server
32
+ self._handlers = handlers
33
+
34
+ def wire_eager(self, entries: Sequence[CapabilityEntry]) -> None:
35
+ """Wire handlers immediately (call from on_initialize for static features)."""
36
+ self._wire(entries)
37
+
38
+ def wire_lazy(self, entries: Sequence[CapabilityEntry]) -> None:
39
+ """Wire handlers after jdtls warm-up (call before client/registerCapability)."""
40
+ self._wire(entries)
41
+
42
+ def _wire(self, entries: Sequence[CapabilityEntry]) -> None:
43
+ for entry in entries:
44
+ for method in self._methods_for(entry):
45
+ handler = self._handlers.get(method)
46
+ if handler is None:
47
+ continue
48
+ if self._already_registered(method):
49
+ continue
50
+ self._server.feature(method)(handler)
51
+
52
+ def _already_registered(self, method: str) -> bool:
53
+ """Check whether pygls already has a handler bound for `method`.
54
+
55
+ We probe `server.protocol.fm._features` (pygls FeatureManager). If the
56
+ attribute path isn't there (custom server stand-ins in tests), assume
57
+ not-registered and let the caller proceed.
58
+ """
59
+ protocol = getattr(self._server, "protocol", None)
60
+ fm = getattr(protocol, "fm", None) if protocol is not None else None
61
+ features = getattr(fm, "_features", None) if fm is not None else None
62
+ if features is None:
63
+ return False
64
+ return method in features
65
+
66
+ @staticmethod
67
+ def _methods_for(entry: CapabilityEntry) -> tuple[str, ...]:
68
+ """Return parent method + any sub-methods that ride along with it."""
69
+ return (entry.lsp_method, *entry.sub_methods)
@@ -0,0 +1,53 @@
1
+ """Decide which capabilities to advertise statically vs dynamically.
2
+
3
+ Given the registry and a probe, split entries into two groups:
4
+ - dynamic: client advertised dynamicRegistration support → register via
5
+ client/registerCapability after jdtls warms up. Preserves PR #44 behavior
6
+ (don't claim hover before jdtls is ready, so the IDE keeps showing its
7
+ own diagnostic tooltips).
8
+ - static: client did not → advertise in the InitializeResult so clients that
9
+ ignore client/registerCapability (like Claude Code) still route correctly.
10
+
11
+ This module is pure logic — no I/O, no mutation of pygls state. That keeps it
12
+ trivially unit-testable with hand-built probes.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Sequence
18
+ from dataclasses import dataclass
19
+
20
+ from .probe import ClientCapabilityProbe
21
+ from .registry import REGISTRY, CapabilityEntry
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class NegotiationResult:
26
+ """Output of CapabilityNegotiator.negotiate.
27
+
28
+ static: entries to advertise in InitializeResult.ServerCapabilities.
29
+ dynamic: entries to register later via client/registerCapability.
30
+ """
31
+
32
+ static: tuple[CapabilityEntry, ...]
33
+ dynamic: tuple[CapabilityEntry, ...]
34
+
35
+
36
+ class CapabilityNegotiator:
37
+ """Splits a capability registry into static/dynamic subsets per client probe."""
38
+
39
+ def __init__(
40
+ self,
41
+ probe: ClientCapabilityProbe,
42
+ registry: Sequence[CapabilityEntry] = REGISTRY,
43
+ ) -> None:
44
+ self._registry = tuple(registry)
45
+ self._probe = probe
46
+
47
+ def negotiate(self) -> NegotiationResult:
48
+ static: list[CapabilityEntry] = []
49
+ dynamic: list[CapabilityEntry] = []
50
+ for entry in self._registry:
51
+ target = dynamic if self._probe.supports_dynamic(entry.client_cap_path) else static
52
+ target.append(entry)
53
+ return NegotiationResult(static=tuple(static), dynamic=tuple(dynamic))
@@ -0,0 +1,77 @@
1
+ """Read-only inspection of the client's declared capabilities.
2
+
3
+ Pygls hands `on_initialize` a `lsp.InitializeParams` whose `capabilities` field is
4
+ an attrs-based `ClientCapabilities` object. We walk a path like
5
+ `("text_document", "hover")` and return whether the client opted in to LSP
6
+ `dynamicRegistration` for that feature.
7
+
8
+ We intentionally accept both attrs-dataclass shapes and dicts at every level
9
+ because some clients send vendor-specific extensions as raw dicts that pygls
10
+ leaves untouched. Returning `False` (rather than raising) on missing nodes
11
+ matches the LSP spec's default: absence of `dynamicRegistration` means the
12
+ client does not advertise support.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Mapping
18
+ from functools import cache
19
+ from typing import Any
20
+
21
+ import lsprotocol.types as lsp
22
+
23
+
24
+ class ClientCapabilityProbe:
25
+ """Wraps `params.capabilities` and answers per-feature support questions."""
26
+
27
+ def __init__(self, params: lsp.InitializeParams) -> None:
28
+ self._caps: Any = params.capabilities
29
+
30
+ def supports_dynamic(self, path: tuple[str, ...]) -> bool:
31
+ """Return True if the client claims dynamicRegistration support for `path`.
32
+
33
+ `path` walks into ClientCapabilities — e.g., ("text_document", "hover")
34
+ means `params.capabilities.text_document.hover.dynamic_registration`.
35
+ Empty path or a missing intermediate node yields False (no support).
36
+ """
37
+ if not path:
38
+ return False
39
+ node: Any = self._caps
40
+ for key in path:
41
+ node = _walk(node, key)
42
+ if node is None:
43
+ return False
44
+ return _read_dynamic_registration(node)
45
+
46
+
47
+ def _walk(node: Any, key: str) -> Any:
48
+ """Get a child of `node` by `key`, supporting both attrs/dataclass and dict shapes."""
49
+ if node is None:
50
+ return None
51
+ if isinstance(node, Mapping):
52
+ # Try snake_case first, then the camelCase the wire protocol uses.
53
+ if key in node:
54
+ return node[key]
55
+ return node.get(_snake_to_camel(key))
56
+ return getattr(node, key, None)
57
+
58
+
59
+ def _read_dynamic_registration(node: Any) -> bool:
60
+ """Return whether `node.dynamicRegistration` is truthy."""
61
+ if node is None:
62
+ return False
63
+ if isinstance(node, Mapping):
64
+ return bool(node.get("dynamic_registration") or node.get("dynamicRegistration"))
65
+ return bool(getattr(node, "dynamic_registration", None))
66
+
67
+
68
+ @cache
69
+ def _snake_to_camel(key: str) -> str:
70
+ """`text_document` -> `textDocument`. Idempotent for already-camel names.
71
+
72
+ Cached because the LSP key space is bounded (~20 field names) but this is
73
+ called per-segment, per-entry, per-initialize when a client sends raw-dict
74
+ capabilities — repeated string allocation otherwise.
75
+ """
76
+ head, *tail = key.split("_")
77
+ return head + "".join(part.capitalize() for part in tail)
@@ -0,0 +1,191 @@
1
+ """Capability registry — single source of truth for jdtls-dependent LSP features.
2
+
3
+ Each entry pairs an LSP method with everything needed to advertise it both ways:
4
+ - statically in the InitializeResult.ServerCapabilities (for clients that
5
+ ignore client/registerCapability, e.g., Claude Code)
6
+ - dynamically via client/registerCapability after jdtls warms up (for clients
7
+ that respect dynamic registration, e.g., VS Code, IntelliJ-LSP4IJ — preserves
8
+ PR #44 behavior of not suppressing IDE diagnostic tooltips).
9
+
10
+ The registry is data only. Decision logic lives in negotiator.py.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from collections.abc import Callable
16
+ from dataclasses import dataclass, field
17
+ from typing import Any
18
+
19
+ import lsprotocol.types as lsp
20
+
21
+ _JAVA_SELECTOR = [lsp.TextDocumentFilterLanguage(language="java")]
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class CapabilityEntry:
26
+ """Metadata for one jdtls-dependent LSP capability.
27
+
28
+ Attributes:
29
+ id_suffix: Used to build the unique Registration.id for dynamic registration.
30
+ lsp_method: The LSP method this capability gates (e.g., "textDocument/hover").
31
+ static_field: Name of the kwarg on lsp.ServerCapabilities for static advertisement.
32
+ client_cap_path: Path to the client capability that signals dynamic-registration
33
+ support (e.g., ("text_document", "hover")). Compared against
34
+ params.capabilities at initialize time.
35
+ registration_options_factory: Builds the RegistrationOptions object used in
36
+ client/registerCapability for the dynamic path.
37
+ static_value_factory: Builds the value to assign to ServerCapabilities[static_field].
38
+ Most are simply `True`; some are options objects (CompletionOptions etc.).
39
+ sub_methods: LSP methods that share this capability's advertisement but need
40
+ their own pygls handler (e.g., callHierarchy/incomingCalls). These are
41
+ wired alongside lsp_method whenever the entry is activated (static or dynamic).
42
+ """
43
+
44
+ id_suffix: str
45
+ lsp_method: str
46
+ static_field: str
47
+ client_cap_path: tuple[str, ...]
48
+ registration_options_factory: Callable[[], Any]
49
+ static_value_factory: Callable[[], Any]
50
+ sub_methods: tuple[str, ...] = field(default_factory=tuple)
51
+
52
+
53
+ REGISTRY: tuple[CapabilityEntry, ...] = (
54
+ CapabilityEntry(
55
+ id_suffix="completion",
56
+ lsp_method=lsp.TEXT_DOCUMENT_COMPLETION,
57
+ static_field="completion_provider",
58
+ client_cap_path=("text_document", "completion"),
59
+ registration_options_factory=lambda: lsp.CompletionRegistrationOptions(
60
+ document_selector=_JAVA_SELECTOR, trigger_characters=["."]
61
+ ),
62
+ static_value_factory=lambda: lsp.CompletionOptions(trigger_characters=["."]),
63
+ ),
64
+ CapabilityEntry(
65
+ id_suffix="hover",
66
+ lsp_method=lsp.TEXT_DOCUMENT_HOVER,
67
+ static_field="hover_provider",
68
+ client_cap_path=("text_document", "hover"),
69
+ registration_options_factory=lambda: lsp.HoverRegistrationOptions(document_selector=_JAVA_SELECTOR),
70
+ static_value_factory=lambda: True,
71
+ ),
72
+ CapabilityEntry(
73
+ id_suffix="definition",
74
+ lsp_method=lsp.TEXT_DOCUMENT_DEFINITION,
75
+ static_field="definition_provider",
76
+ client_cap_path=("text_document", "definition"),
77
+ registration_options_factory=lambda: lsp.DefinitionRegistrationOptions(document_selector=_JAVA_SELECTOR),
78
+ static_value_factory=lambda: True,
79
+ ),
80
+ CapabilityEntry(
81
+ id_suffix="references",
82
+ lsp_method=lsp.TEXT_DOCUMENT_REFERENCES,
83
+ static_field="references_provider",
84
+ client_cap_path=("text_document", "references"),
85
+ registration_options_factory=lambda: lsp.ReferenceRegistrationOptions(document_selector=_JAVA_SELECTOR),
86
+ static_value_factory=lambda: True,
87
+ ),
88
+ CapabilityEntry(
89
+ id_suffix="document-symbol",
90
+ lsp_method=lsp.TEXT_DOCUMENT_DOCUMENT_SYMBOL,
91
+ static_field="document_symbol_provider",
92
+ client_cap_path=("text_document", "document_symbol"),
93
+ registration_options_factory=lambda: lsp.DocumentSymbolRegistrationOptions(document_selector=_JAVA_SELECTOR),
94
+ static_value_factory=lambda: True,
95
+ ),
96
+ CapabilityEntry(
97
+ id_suffix="call-hierarchy",
98
+ lsp_method=lsp.TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY,
99
+ static_field="call_hierarchy_provider",
100
+ client_cap_path=("text_document", "call_hierarchy"),
101
+ registration_options_factory=lambda: lsp.CallHierarchyRegistrationOptions(document_selector=_JAVA_SELECTOR),
102
+ static_value_factory=lambda: True,
103
+ sub_methods=(lsp.CALL_HIERARCHY_INCOMING_CALLS, lsp.CALL_HIERARCHY_OUTGOING_CALLS),
104
+ ),
105
+ CapabilityEntry(
106
+ id_suffix="signature-help",
107
+ lsp_method=lsp.TEXT_DOCUMENT_SIGNATURE_HELP,
108
+ static_field="signature_help_provider",
109
+ client_cap_path=("text_document", "signature_help"),
110
+ registration_options_factory=lambda: lsp.SignatureHelpRegistrationOptions(
111
+ document_selector=_JAVA_SELECTOR,
112
+ trigger_characters=["(", ","],
113
+ retrigger_characters=[")"],
114
+ ),
115
+ static_value_factory=lambda: lsp.SignatureHelpOptions(
116
+ trigger_characters=["(", ","], retrigger_characters=[")"]
117
+ ),
118
+ ),
119
+ CapabilityEntry(
120
+ id_suffix="implementation",
121
+ lsp_method=lsp.TEXT_DOCUMENT_IMPLEMENTATION,
122
+ static_field="implementation_provider",
123
+ client_cap_path=("text_document", "implementation"),
124
+ registration_options_factory=lambda: lsp.ImplementationRegistrationOptions(document_selector=_JAVA_SELECTOR),
125
+ static_value_factory=lambda: True,
126
+ ),
127
+ CapabilityEntry(
128
+ id_suffix="type-definition",
129
+ lsp_method=lsp.TEXT_DOCUMENT_TYPE_DEFINITION,
130
+ static_field="type_definition_provider",
131
+ client_cap_path=("text_document", "type_definition"),
132
+ registration_options_factory=lambda: lsp.TypeDefinitionRegistrationOptions(document_selector=_JAVA_SELECTOR),
133
+ static_value_factory=lambda: True,
134
+ ),
135
+ CapabilityEntry(
136
+ id_suffix="declaration",
137
+ lsp_method=lsp.TEXT_DOCUMENT_DECLARATION,
138
+ static_field="declaration_provider",
139
+ client_cap_path=("text_document", "declaration"),
140
+ registration_options_factory=lambda: lsp.DeclarationRegistrationOptions(document_selector=_JAVA_SELECTOR),
141
+ static_value_factory=lambda: True,
142
+ ),
143
+ CapabilityEntry(
144
+ id_suffix="document-highlight",
145
+ lsp_method=lsp.TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT,
146
+ static_field="document_highlight_provider",
147
+ client_cap_path=("text_document", "document_highlight"),
148
+ registration_options_factory=lambda: lsp.DocumentHighlightRegistrationOptions(document_selector=_JAVA_SELECTOR),
149
+ static_value_factory=lambda: True,
150
+ ),
151
+ CapabilityEntry(
152
+ id_suffix="rename",
153
+ lsp_method=lsp.TEXT_DOCUMENT_RENAME,
154
+ static_field="rename_provider",
155
+ client_cap_path=("text_document", "rename"),
156
+ registration_options_factory=lambda: lsp.RenameRegistrationOptions(
157
+ document_selector=_JAVA_SELECTOR, prepare_provider=True
158
+ ),
159
+ static_value_factory=lambda: lsp.RenameOptions(prepare_provider=True),
160
+ sub_methods=(lsp.TEXT_DOCUMENT_PREPARE_RENAME,),
161
+ ),
162
+ CapabilityEntry(
163
+ id_suffix="type-hierarchy",
164
+ lsp_method=lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY,
165
+ static_field="type_hierarchy_provider",
166
+ client_cap_path=("text_document", "type_hierarchy"),
167
+ registration_options_factory=lambda: lsp.TypeHierarchyRegistrationOptions(document_selector=_JAVA_SELECTOR),
168
+ static_value_factory=lambda: True,
169
+ sub_methods=(lsp.TYPE_HIERARCHY_SUPERTYPES, lsp.TYPE_HIERARCHY_SUBTYPES),
170
+ ),
171
+ CapabilityEntry(
172
+ id_suffix="workspace-symbol",
173
+ lsp_method=lsp.WORKSPACE_SYMBOL,
174
+ static_field="workspace_symbol_provider",
175
+ client_cap_path=("workspace", "symbol"),
176
+ # Lambda kept (vs bare class) for consistency with the other entries' Callable[[], Any]
177
+ # contract — defends against a silent semantic change if WorkspaceSymbolRegistrationOptions
178
+ # ever gains a required constructor arg.
179
+ registration_options_factory=lambda: lsp.WorkspaceSymbolRegistrationOptions(), # noqa: PLW0108
180
+ static_value_factory=lambda: True,
181
+ ),
182
+ )
183
+
184
+
185
+ def all_methods(entries: tuple[CapabilityEntry, ...] = REGISTRY) -> tuple[str, ...]:
186
+ """Return every LSP method (parent + sub) reachable from `entries`."""
187
+ methods: list[str] = []
188
+ for entry in entries:
189
+ methods.append(entry.lsp_method)
190
+ methods.extend(entry.sub_methods)
191
+ return tuple(methods)
@@ -0,0 +1,41 @@
1
+ """Assemble the lsp.ServerCapabilities returned in InitializeResult.
2
+
3
+ Single responsibility: take the static-advertised entries from the negotiator and
4
+ build a `ServerCapabilities` object combining them with the always-static base
5
+ (text_document_sync + code_action_provider for our QuickFix custom diagnostics).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Sequence
11
+ from typing import Any
12
+
13
+ import lsprotocol.types as lsp
14
+
15
+ from .registry import CapabilityEntry
16
+
17
+ _TEXT_DOC_SYNC = lsp.TextDocumentSyncOptions(
18
+ open_close=True,
19
+ change=lsp.TextDocumentSyncKind.Full,
20
+ save=lsp.SaveOptions(include_text=True),
21
+ )
22
+
23
+ _CODE_ACTION_OPTIONS = lsp.CodeActionOptions(code_action_kinds=[lsp.CodeActionKind.QuickFix])
24
+
25
+
26
+ class StaticCapabilityBuilder:
27
+ """Builds the ServerCapabilities object for the InitializeResult.
28
+
29
+ The base capabilities (text_document_sync, code_action_provider) are always
30
+ advertised because they are fully owned by this server (custom diagnostics
31
+ + QuickFix code actions) — they don't depend on jdtls warm-up.
32
+ """
33
+
34
+ def build(self, static_entries: Sequence[CapabilityEntry]) -> lsp.ServerCapabilities:
35
+ kwargs: dict[str, Any] = {
36
+ "text_document_sync": _TEXT_DOC_SYNC,
37
+ "code_action_provider": _CODE_ACTION_OPTIONS,
38
+ }
39
+ for entry in static_entries:
40
+ kwargs[entry.static_field] = entry.static_value_factory()
41
+ return lsp.ServerCapabilities(**kwargs)