java-functional-lsp 0.9.3__tar.gz → 0.9.6__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.
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/PKG-INFO +1 -1
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/pyproject.toml +1 -1
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/__init__.py +1 -1
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/proxy.py +22 -14
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/server.py +238 -8
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_proxy.py +39 -17
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_server.py +576 -10
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/uv.lock +1 -1
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.claude-plugin/plugin.json +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.githooks/pre-commit +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.githooks/pre-push +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/CODEOWNERS +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/SECURITY.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/dependabot.yml +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/workflows/publish.yml +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/workflows/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/workflows/stale.yml +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/workflows/test.yml +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/workflows/update-homebrew.yml +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.github/workflows/vscode-ext.yml +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/.gitignore +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/CONTRIBUTING.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/LICENSE +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/README.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/SKILL.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/commands/lint-java.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/editors/intellij/README.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/editors/intellij/lsp4ij-template.json +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/editors/vscode/.vscodeignore +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/editors/vscode/README.md +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/editors/vscode/package-lock.json +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/editors/vscode/package.json +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/editors/vscode/src/extension.ts +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/editors/vscode/tsconfig.json +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/hooks/hooks.json +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/hooks/java_linter_reminder.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/scripts/ensure-lsp.sh +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/scripts/generate-formula.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/__main__.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/analyzers/__init__.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/analyzers/base.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/cli.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/fixes.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/src/java_functional_lsp/merkle.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/__init__.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/conftest.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_base.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_cli.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_config.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_e2e.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_e2e_jdtls.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_exception_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_fixes.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_functional_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_merkle.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_merkle_proxy.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_mutation_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_null_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/tests/test_spring_checker.py +0 -0
- {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.6}/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.
|
|
3
|
+
Version: 0.9.6
|
|
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.
|
|
7
|
+
version = "0.9.6"
|
|
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" }
|
|
@@ -448,12 +448,12 @@ def _version_key(name: str) -> tuple[int, ...]:
|
|
|
448
448
|
def _find_maven_group_root(initial_module: Path, workspace_root: Path) -> Path:
|
|
449
449
|
"""Return the tightest Maven multi-module parent of *initial_module*.
|
|
450
450
|
|
|
451
|
-
Walks up from ``initial_module.parent`` (
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
``workspace_root``
|
|
456
|
-
|
|
451
|
+
Walks up from ``initial_module.parent`` toward (but NOT including)
|
|
452
|
+
``workspace_root``, returning the first ancestor whose ``pom.xml``
|
|
453
|
+
contains a ``<modules>`` section. Falls back to ``initial_module``
|
|
454
|
+
itself when no intermediate group pom exists (e.g. modules that sit
|
|
455
|
+
directly under ``workspace_root``), keeping the scope as tight as
|
|
456
|
+
possible and avoiding accidental full-monorepo indexing.
|
|
457
457
|
|
|
458
458
|
This keeps the jdtls index bounded to a module *group* (typically
|
|
459
459
|
5-20 modules) rather than the full IDE workspace (potentially 200+ modules)
|
|
@@ -463,11 +463,17 @@ def _find_maven_group_root(initial_module: Path, workspace_root: Path) -> Path:
|
|
|
463
463
|
before walking to avoid symlink-based boundary escapes.
|
|
464
464
|
"""
|
|
465
465
|
workspace_root = workspace_root.resolve()
|
|
466
|
-
|
|
467
|
-
|
|
466
|
+
resolved_module = initial_module.resolve()
|
|
467
|
+
current = resolved_module.parent
|
|
468
|
+
# Case 1: module is outside the workspace entirely — scope to workspace_root
|
|
469
|
+
# (also fires when initial_module == workspace_root, since parent is then
|
|
470
|
+
# outside workspace_root).
|
|
468
471
|
if not current.is_relative_to(workspace_root):
|
|
469
472
|
return workspace_root
|
|
470
|
-
|
|
473
|
+
# Walk up to (but NOT including) workspace_root. Inspecting workspace_root's
|
|
474
|
+
# own pom.xml would include the entire monorepo (e.g. 200+ modules) → OOM.
|
|
475
|
+
# `current.parent == current` detects the filesystem root (POSIX `/` or Windows drive).
|
|
476
|
+
while current not in (workspace_root, current.parent):
|
|
471
477
|
pom = current / "pom.xml"
|
|
472
478
|
if pom.is_file():
|
|
473
479
|
try:
|
|
@@ -475,10 +481,10 @@ def _find_maven_group_root(initial_module: Path, workspace_root: Path) -> Path:
|
|
|
475
481
|
return current
|
|
476
482
|
except OSError:
|
|
477
483
|
pass
|
|
478
|
-
if current in (workspace_root, current.parent):
|
|
479
|
-
break
|
|
480
484
|
current = current.parent
|
|
481
|
-
|
|
485
|
+
# Case 2: no intermediate group pom found — use the initial module as its own scope
|
|
486
|
+
# (tightest possible; avoids full-monorepo indexing for flat modules under workspace_root).
|
|
487
|
+
return resolved_module
|
|
482
488
|
|
|
483
489
|
|
|
484
490
|
@lru_cache(maxsize=256)
|
|
@@ -924,8 +930,10 @@ class JdtlsProxy:
|
|
|
924
930
|
Maven parent. This covers sibling modules (the common case for
|
|
925
931
|
cross-module navigation) without ballooning the index.
|
|
926
932
|
|
|
927
|
-
Falls back to the
|
|
928
|
-
multi-module parent is found.
|
|
933
|
+
Falls back to the initial module itself when no intermediate
|
|
934
|
+
multi-module parent is found (e.g. flat modules sitting directly
|
|
935
|
+
under the workspace root), keeping the jdtls scope as tight as
|
|
936
|
+
possible.
|
|
929
937
|
|
|
930
938
|
Removes all individually-added module folders to avoid double-indexing
|
|
931
939
|
with the newly added group root.
|
|
@@ -446,6 +446,20 @@ _JDTLS_CAPABILITIES: list[tuple[str, str, type[Any], dict[str, Any]]] = [
|
|
|
446
446
|
("definition", lsp.TEXT_DOCUMENT_DEFINITION, lsp.DefinitionRegistrationOptions, {}),
|
|
447
447
|
("references", lsp.TEXT_DOCUMENT_REFERENCES, lsp.ReferenceRegistrationOptions, {}),
|
|
448
448
|
("document-symbol", lsp.TEXT_DOCUMENT_DOCUMENT_SYMBOL, lsp.DocumentSymbolRegistrationOptions, {}),
|
|
449
|
+
("call-hierarchy", lsp.TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY, lsp.CallHierarchyRegistrationOptions, {}),
|
|
450
|
+
(
|
|
451
|
+
"signature-help",
|
|
452
|
+
lsp.TEXT_DOCUMENT_SIGNATURE_HELP,
|
|
453
|
+
lsp.SignatureHelpRegistrationOptions,
|
|
454
|
+
{"trigger_characters": ["(", ","], "retrigger_characters": [")"]},
|
|
455
|
+
),
|
|
456
|
+
("implementation", lsp.TEXT_DOCUMENT_IMPLEMENTATION, lsp.ImplementationRegistrationOptions, {}),
|
|
457
|
+
("type-definition", lsp.TEXT_DOCUMENT_TYPE_DEFINITION, lsp.TypeDefinitionRegistrationOptions, {}),
|
|
458
|
+
("declaration", lsp.TEXT_DOCUMENT_DECLARATION, lsp.DeclarationRegistrationOptions, {}),
|
|
459
|
+
("document-highlight", lsp.TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT, lsp.DocumentHighlightRegistrationOptions, {}),
|
|
460
|
+
("rename", lsp.TEXT_DOCUMENT_RENAME, lsp.RenameRegistrationOptions, {"prepare_provider": True}),
|
|
461
|
+
("type-hierarchy", lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY, lsp.TypeHierarchyRegistrationOptions, {}),
|
|
462
|
+
("workspace-symbol", lsp.WORKSPACE_SYMBOL, lsp.WorkspaceSymbolRegistrationOptions, {}),
|
|
449
463
|
]
|
|
450
464
|
|
|
451
465
|
# Maps LSP method → handler function for dynamic registration.
|
|
@@ -457,14 +471,20 @@ _jdtls_capabilities_registered = False
|
|
|
457
471
|
|
|
458
472
|
def _build_jdtls_registrations() -> list[lsp.Registration]:
|
|
459
473
|
"""Build LSP Registration objects for jdtls-dependent capabilities."""
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
474
|
+
result = []
|
|
475
|
+
for suffix, method, opts_cls, extra in _JDTLS_CAPABILITIES:
|
|
476
|
+
field_names = {f.name for f in getattr(opts_cls, "__attrs_attrs__", [])}
|
|
477
|
+
kwargs: dict[str, Any] = {**extra}
|
|
478
|
+
if "document_selector" in field_names:
|
|
479
|
+
kwargs["document_selector"] = _JAVA_SELECTOR
|
|
480
|
+
result.append(
|
|
481
|
+
lsp.Registration(
|
|
482
|
+
id=f"{_JDTLS_REG_PREFIX}{suffix}",
|
|
483
|
+
method=method,
|
|
484
|
+
register_options=_converter.unstructure(opts_cls(**kwargs)),
|
|
485
|
+
)
|
|
465
486
|
)
|
|
466
|
-
|
|
467
|
-
]
|
|
487
|
+
return result
|
|
468
488
|
|
|
469
489
|
|
|
470
490
|
async def _register_jdtls_capabilities() -> None:
|
|
@@ -499,7 +519,11 @@ async def _register_jdtls_capabilities() -> None:
|
|
|
499
519
|
return
|
|
500
520
|
|
|
501
521
|
_jdtls_capabilities_registered = True
|
|
502
|
-
logger.info(
|
|
522
|
+
logger.info(
|
|
523
|
+
"jdtls capabilities registered: completion, hover, definition, references, symbol, "
|
|
524
|
+
"call-hierarchy, signature-help, implementation, type-definition, declaration, "
|
|
525
|
+
"document-highlight, rename, type-hierarchy, workspace-symbol"
|
|
526
|
+
)
|
|
503
527
|
except Exception:
|
|
504
528
|
logger.warning("Failed to dynamically register jdtls capabilities", exc_info=True)
|
|
505
529
|
|
|
@@ -739,6 +763,198 @@ async def _on_document_symbol(params: lsp.DocumentSymbolParams) -> list[lsp.Docu
|
|
|
739
763
|
return None
|
|
740
764
|
|
|
741
765
|
|
|
766
|
+
async def _on_prepare_call_hierarchy(params: lsp.CallHierarchyPrepareParams) -> list[lsp.CallHierarchyItem] | None:
|
|
767
|
+
"""Forward prepareCallHierarchy request to jdtls."""
|
|
768
|
+
result = await _ensure_module_and_forward("textDocument/prepareCallHierarchy", params, params.text_document.uri)
|
|
769
|
+
if result is None:
|
|
770
|
+
return None
|
|
771
|
+
try:
|
|
772
|
+
return [_converter.structure(item, lsp.CallHierarchyItem) for item in result]
|
|
773
|
+
except Exception:
|
|
774
|
+
return None
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
async def _on_incoming_calls(
|
|
778
|
+
params: lsp.CallHierarchyIncomingCallsParams,
|
|
779
|
+
) -> list[lsp.CallHierarchyIncomingCall] | None:
|
|
780
|
+
"""Forward callHierarchy/incomingCalls request to jdtls."""
|
|
781
|
+
result = await _ensure_module_and_forward("callHierarchy/incomingCalls", params, params.item.uri)
|
|
782
|
+
if result is None:
|
|
783
|
+
return None
|
|
784
|
+
try:
|
|
785
|
+
return [_converter.structure(c, lsp.CallHierarchyIncomingCall) for c in result]
|
|
786
|
+
except Exception:
|
|
787
|
+
return None
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
async def _on_outgoing_calls(
|
|
791
|
+
params: lsp.CallHierarchyOutgoingCallsParams,
|
|
792
|
+
) -> list[lsp.CallHierarchyOutgoingCall] | None:
|
|
793
|
+
"""Forward callHierarchy/outgoingCalls request to jdtls."""
|
|
794
|
+
result = await _ensure_module_and_forward("callHierarchy/outgoingCalls", params, params.item.uri)
|
|
795
|
+
if result is None:
|
|
796
|
+
return None
|
|
797
|
+
try:
|
|
798
|
+
return [_converter.structure(c, lsp.CallHierarchyOutgoingCall) for c in result]
|
|
799
|
+
except Exception:
|
|
800
|
+
return None
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
async def _on_signature_help(params: lsp.SignatureHelpParams) -> lsp.SignatureHelp | None:
|
|
804
|
+
"""Forward signatureHelp request to jdtls."""
|
|
805
|
+
result = await _ensure_module_and_forward("textDocument/signatureHelp", params, params.text_document.uri)
|
|
806
|
+
if result is None:
|
|
807
|
+
return None
|
|
808
|
+
try:
|
|
809
|
+
return _converter.structure(result, lsp.SignatureHelp)
|
|
810
|
+
except Exception:
|
|
811
|
+
logger.debug("Failed to structure signatureHelp result", exc_info=True)
|
|
812
|
+
return None
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
async def _on_implementation(params: lsp.ImplementationParams) -> list[lsp.Location] | None:
|
|
816
|
+
"""Forward go-to-implementation request to jdtls."""
|
|
817
|
+
result = await _ensure_module_and_forward("textDocument/implementation", params, params.text_document.uri)
|
|
818
|
+
if result is None:
|
|
819
|
+
return None
|
|
820
|
+
try:
|
|
821
|
+
if isinstance(result, list):
|
|
822
|
+
return [_converter.structure(loc, lsp.Location) for loc in result]
|
|
823
|
+
return [_converter.structure(result, lsp.Location)]
|
|
824
|
+
except Exception:
|
|
825
|
+
logger.debug("Failed to structure implementation result", exc_info=True)
|
|
826
|
+
return None
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
async def _on_type_definition(params: lsp.TypeDefinitionParams) -> list[lsp.Location] | None:
|
|
830
|
+
"""Forward go-to-type-definition request to jdtls."""
|
|
831
|
+
result = await _ensure_module_and_forward("textDocument/typeDefinition", params, params.text_document.uri)
|
|
832
|
+
if result is None:
|
|
833
|
+
return None
|
|
834
|
+
try:
|
|
835
|
+
if isinstance(result, list):
|
|
836
|
+
return [_converter.structure(loc, lsp.Location) for loc in result]
|
|
837
|
+
return [_converter.structure(result, lsp.Location)]
|
|
838
|
+
except Exception:
|
|
839
|
+
logger.debug("Failed to structure typeDefinition result", exc_info=True)
|
|
840
|
+
return None
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
async def _on_declaration(params: lsp.DeclarationParams) -> list[lsp.Location] | None:
|
|
844
|
+
"""Forward go-to-declaration request to jdtls."""
|
|
845
|
+
result = await _ensure_module_and_forward("textDocument/declaration", params, params.text_document.uri)
|
|
846
|
+
if result is None:
|
|
847
|
+
return None
|
|
848
|
+
try:
|
|
849
|
+
if isinstance(result, list):
|
|
850
|
+
return [_converter.structure(loc, lsp.Location) for loc in result]
|
|
851
|
+
return [_converter.structure(result, lsp.Location)]
|
|
852
|
+
except Exception:
|
|
853
|
+
logger.debug("Failed to structure declaration result", exc_info=True)
|
|
854
|
+
return None
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
async def _on_document_highlight(params: lsp.DocumentHighlightParams) -> list[lsp.DocumentHighlight] | None:
|
|
858
|
+
"""Forward documentHighlight request to jdtls."""
|
|
859
|
+
result = await _ensure_module_and_forward("textDocument/documentHighlight", params, params.text_document.uri)
|
|
860
|
+
if result is None:
|
|
861
|
+
return None
|
|
862
|
+
try:
|
|
863
|
+
return [_converter.structure(h, lsp.DocumentHighlight) for h in result]
|
|
864
|
+
except Exception:
|
|
865
|
+
logger.debug("Failed to structure documentHighlight result", exc_info=True)
|
|
866
|
+
return None
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
async def _on_rename(params: lsp.RenameParams) -> lsp.WorkspaceEdit | None:
|
|
870
|
+
"""Forward rename request to jdtls."""
|
|
871
|
+
result = await _ensure_module_and_forward("textDocument/rename", params, params.text_document.uri)
|
|
872
|
+
if result is None:
|
|
873
|
+
return None
|
|
874
|
+
try:
|
|
875
|
+
return _converter.structure(result, lsp.WorkspaceEdit)
|
|
876
|
+
except Exception:
|
|
877
|
+
logger.debug("Failed to structure rename result", exc_info=True)
|
|
878
|
+
return None
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
async def _on_prepare_rename(
|
|
882
|
+
params: lsp.PrepareRenameParams,
|
|
883
|
+
) -> lsp.Range | lsp.PrepareRenamePlaceholder | lsp.PrepareRenameDefaultBehavior | None:
|
|
884
|
+
"""Forward prepareRename request to jdtls.
|
|
885
|
+
|
|
886
|
+
jdtls may return Range, PrepareRenamePlaceholder ({range, placeholder}),
|
|
887
|
+
or PrepareRenameDefaultBehavior ({defaultBehavior}) — try each in order.
|
|
888
|
+
"""
|
|
889
|
+
result = await _ensure_module_and_forward("textDocument/prepareRename", params, params.text_document.uri)
|
|
890
|
+
if result is None:
|
|
891
|
+
return None
|
|
892
|
+
for target_type in (lsp.PrepareRenamePlaceholder, lsp.PrepareRenameDefaultBehavior, lsp.Range):
|
|
893
|
+
try:
|
|
894
|
+
return _converter.structure(result, target_type) # type: ignore[return-value]
|
|
895
|
+
except Exception:
|
|
896
|
+
pass
|
|
897
|
+
logger.debug("Failed to structure prepareRename result as any known type", exc_info=False)
|
|
898
|
+
return None
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
async def _on_prepare_type_hierarchy(
|
|
902
|
+
params: lsp.TypeHierarchyPrepareParams,
|
|
903
|
+
) -> list[lsp.TypeHierarchyItem] | None:
|
|
904
|
+
"""Forward prepareTypeHierarchy request to jdtls."""
|
|
905
|
+
result = await _ensure_module_and_forward("textDocument/prepareTypeHierarchy", params, params.text_document.uri)
|
|
906
|
+
if result is None:
|
|
907
|
+
return None
|
|
908
|
+
try:
|
|
909
|
+
return [_converter.structure(item, lsp.TypeHierarchyItem) for item in result]
|
|
910
|
+
except Exception:
|
|
911
|
+
logger.debug("Failed to structure prepareTypeHierarchy result", exc_info=True)
|
|
912
|
+
return None
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
async def _on_type_hierarchy_supertypes(
|
|
916
|
+
params: lsp.TypeHierarchySupertypesParams,
|
|
917
|
+
) -> list[lsp.TypeHierarchyItem] | None:
|
|
918
|
+
"""Forward typeHierarchy/supertypes request to jdtls."""
|
|
919
|
+
result = await _ensure_module_and_forward("typeHierarchy/supertypes", params, params.item.uri)
|
|
920
|
+
if result is None:
|
|
921
|
+
return None
|
|
922
|
+
try:
|
|
923
|
+
return [_converter.structure(item, lsp.TypeHierarchyItem) for item in result]
|
|
924
|
+
except Exception:
|
|
925
|
+
logger.debug("Failed to structure typeHierarchy/supertypes result", exc_info=True)
|
|
926
|
+
return None
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
async def _on_type_hierarchy_subtypes(
|
|
930
|
+
params: lsp.TypeHierarchySubtypesParams,
|
|
931
|
+
) -> list[lsp.TypeHierarchyItem] | None:
|
|
932
|
+
"""Forward typeHierarchy/subtypes request to jdtls."""
|
|
933
|
+
result = await _ensure_module_and_forward("typeHierarchy/subtypes", params, params.item.uri)
|
|
934
|
+
if result is None:
|
|
935
|
+
return None
|
|
936
|
+
try:
|
|
937
|
+
return [_converter.structure(item, lsp.TypeHierarchyItem) for item in result]
|
|
938
|
+
except Exception:
|
|
939
|
+
logger.debug("Failed to structure typeHierarchy/subtypes result", exc_info=True)
|
|
940
|
+
return None
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
async def _on_workspace_symbol(params: lsp.WorkspaceSymbolParams) -> list[lsp.WorkspaceSymbol] | None:
|
|
944
|
+
"""Forward workspace/symbol request to jdtls (no text_document — use proxy directly)."""
|
|
945
|
+
proxy = server._proxy
|
|
946
|
+
if not proxy.is_available:
|
|
947
|
+
return None
|
|
948
|
+
result = await proxy.send_request("workspace/symbol", _serialize_params(params))
|
|
949
|
+
if result is None:
|
|
950
|
+
return None
|
|
951
|
+
try:
|
|
952
|
+
return [_converter.structure(s, lsp.WorkspaceSymbol) for s in result]
|
|
953
|
+
except Exception:
|
|
954
|
+
logger.debug("Failed to structure workspace/symbol result", exc_info=True)
|
|
955
|
+
return None
|
|
956
|
+
|
|
957
|
+
|
|
742
958
|
# Populate handler map for dynamic registration.
|
|
743
959
|
_JDTLS_HANDLERS.update(
|
|
744
960
|
{
|
|
@@ -747,6 +963,20 @@ _JDTLS_HANDLERS.update(
|
|
|
747
963
|
lsp.TEXT_DOCUMENT_DEFINITION: _on_definition,
|
|
748
964
|
lsp.TEXT_DOCUMENT_REFERENCES: _on_references,
|
|
749
965
|
lsp.TEXT_DOCUMENT_DOCUMENT_SYMBOL: _on_document_symbol,
|
|
966
|
+
lsp.TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY: _on_prepare_call_hierarchy,
|
|
967
|
+
lsp.CALL_HIERARCHY_INCOMING_CALLS: _on_incoming_calls,
|
|
968
|
+
lsp.CALL_HIERARCHY_OUTGOING_CALLS: _on_outgoing_calls,
|
|
969
|
+
lsp.TEXT_DOCUMENT_SIGNATURE_HELP: _on_signature_help,
|
|
970
|
+
lsp.TEXT_DOCUMENT_IMPLEMENTATION: _on_implementation,
|
|
971
|
+
lsp.TEXT_DOCUMENT_TYPE_DEFINITION: _on_type_definition,
|
|
972
|
+
lsp.TEXT_DOCUMENT_DECLARATION: _on_declaration,
|
|
973
|
+
lsp.TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT: _on_document_highlight,
|
|
974
|
+
lsp.TEXT_DOCUMENT_RENAME: _on_rename,
|
|
975
|
+
lsp.TEXT_DOCUMENT_PREPARE_RENAME: _on_prepare_rename,
|
|
976
|
+
lsp.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY: _on_prepare_type_hierarchy,
|
|
977
|
+
lsp.TYPE_HIERARCHY_SUPERTYPES: _on_type_hierarchy_supertypes,
|
|
978
|
+
lsp.TYPE_HIERARCHY_SUBTYPES: _on_type_hierarchy_subtypes,
|
|
979
|
+
lsp.WORKSPACE_SYMBOL: _on_workspace_symbol,
|
|
750
980
|
}
|
|
751
981
|
)
|
|
752
982
|
|
|
@@ -1131,17 +1131,24 @@ class TestLazyStart:
|
|
|
1131
1131
|
assert event["added"][0]["uri"] == group.as_uri()
|
|
1132
1132
|
assert event["added"][0]["uri"] != workspace_uri
|
|
1133
1133
|
|
|
1134
|
-
async def
|
|
1135
|
-
"""
|
|
1134
|
+
async def test_expand_noop_for_flat_module_directly_under_workspace(self, tmp_path: Path) -> None:
|
|
1135
|
+
"""No notification sent for flat modules sitting directly under workspace_root.
|
|
1136
|
+
|
|
1137
|
+
When there is no intermediate group pom between the module and workspace_root,
|
|
1138
|
+
_find_maven_group_root returns the module itself (not workspace_root). Since the
|
|
1139
|
+
module was already added, expand_full_workspace() is a no-op — which is correct:
|
|
1140
|
+
the scope is already as tight as possible and workspace_root's pom.xml
|
|
1141
|
+
(potentially 200+ modules → OOM) is never inspected.
|
|
1142
|
+
"""
|
|
1136
1143
|
from unittest.mock import AsyncMock
|
|
1137
1144
|
|
|
1138
1145
|
from java_functional_lsp.proxy import JdtlsProxy
|
|
1139
1146
|
|
|
1140
|
-
# Flat layout: workspace/module — no intermediate pom.xml
|
|
1147
|
+
# Flat layout: workspace/module — no intermediate pom.xml between module and workspace
|
|
1141
1148
|
workspace = tmp_path / "workspace"
|
|
1142
1149
|
module = workspace / "module"
|
|
1143
1150
|
module.mkdir(parents=True)
|
|
1144
|
-
# workspace/pom.xml has <modules>, but
|
|
1151
|
+
# workspace/pom.xml has <modules>, but the walk stops BEFORE reaching it
|
|
1145
1152
|
(workspace / "pom.xml").write_text("<project><modules><module>module</module></modules></project>")
|
|
1146
1153
|
|
|
1147
1154
|
module_uri = module.as_uri()
|
|
@@ -1155,9 +1162,9 @@ class TestLazyStart:
|
|
|
1155
1162
|
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1156
1163
|
await proxy.expand_full_workspace()
|
|
1157
1164
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
assert
|
|
1165
|
+
# No notification — module was already added and is already the tightest scope
|
|
1166
|
+
proxy.send_notification.assert_not_called() # type: ignore[attr-defined]
|
|
1167
|
+
assert proxy._workspace_expanded is True
|
|
1161
1168
|
|
|
1162
1169
|
async def test_expand_full_workspace_sends_notification(self) -> None:
|
|
1163
1170
|
from unittest.mock import AsyncMock
|
|
@@ -1172,28 +1179,43 @@ class TestLazyStart:
|
|
|
1172
1179
|
proxy.send_notification.assert_called_once() # type: ignore[attr-defined]
|
|
1173
1180
|
assert proxy._workspace_expanded is True
|
|
1174
1181
|
|
|
1175
|
-
async def test_expand_full_workspace_removes_initial_module(self) -> None:
|
|
1176
|
-
"""All modules in _states
|
|
1182
|
+
async def test_expand_full_workspace_removes_initial_module(self, tmp_path: Path) -> None:
|
|
1183
|
+
"""All modules in _states are removed when expanding to an intermediate group root."""
|
|
1177
1184
|
from unittest.mock import AsyncMock
|
|
1178
1185
|
|
|
1179
1186
|
from java_functional_lsp.proxy import JdtlsProxy
|
|
1180
1187
|
|
|
1188
|
+
# Layout: monorepo/group/module-a, monorepo/group/module-b
|
|
1189
|
+
# group/pom.xml has <modules> → group becomes the expansion target
|
|
1190
|
+
monorepo = tmp_path / "monorepo"
|
|
1191
|
+
group = monorepo / "group"
|
|
1192
|
+
module_a = group / "module-a"
|
|
1193
|
+
module_b = group / "module-b"
|
|
1194
|
+
for d in (module_a, module_b):
|
|
1195
|
+
d.mkdir(parents=True)
|
|
1196
|
+
(group / "pom.xml").write_text("<project><modules><module>module-a</module></modules></project>")
|
|
1197
|
+
|
|
1198
|
+
module_a_uri = module_a.as_uri()
|
|
1199
|
+
module_b_uri = module_b.as_uri()
|
|
1200
|
+
monorepo_uri = monorepo.as_uri()
|
|
1201
|
+
group_uri = group.as_uri()
|
|
1202
|
+
|
|
1181
1203
|
proxy = JdtlsProxy()
|
|
1182
1204
|
proxy._available = True
|
|
1183
|
-
proxy._original_root_uri =
|
|
1184
|
-
proxy._initial_module_uri =
|
|
1185
|
-
# Simulate
|
|
1186
|
-
proxy.modules.mark_added(
|
|
1187
|
-
proxy.modules.mark_added(
|
|
1205
|
+
proxy._original_root_uri = monorepo_uri
|
|
1206
|
+
proxy._initial_module_uri = module_a_uri
|
|
1207
|
+
# Simulate module-a added during start() and module-b via add_module_if_new
|
|
1208
|
+
proxy.modules.mark_added(module_a_uri)
|
|
1209
|
+
proxy.modules.mark_added(module_b_uri)
|
|
1188
1210
|
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1189
1211
|
await proxy.expand_full_workspace()
|
|
1190
1212
|
proxy.send_notification.assert_called_once() # type: ignore[attr-defined]
|
|
1191
1213
|
call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
|
|
1192
1214
|
event = call_args[1]["event"]
|
|
1193
1215
|
removed_uris = {e["uri"] for e in event["removed"]}
|
|
1194
|
-
assert
|
|
1195
|
-
assert
|
|
1196
|
-
assert event["added"][0]["uri"] ==
|
|
1216
|
+
assert module_a_uri in removed_uris
|
|
1217
|
+
assert module_b_uri in removed_uris
|
|
1218
|
+
assert event["added"][0]["uri"] == group_uri
|
|
1197
1219
|
|
|
1198
1220
|
async def test_expand_full_workspace_noop_when_not_available(self) -> None:
|
|
1199
1221
|
from unittest.mock import AsyncMock
|