java-functional-lsp 0.9.1__tar.gz → 0.9.3__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.1 → java_functional_lsp-0.9.3}/PKG-INFO +1 -1
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/pyproject.toml +1 -1
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/__init__.py +1 -1
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/proxy.py +76 -5
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_proxy.py +181 -2
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.claude-plugin/plugin.json +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.githooks/pre-commit +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.githooks/pre-push +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/CODEOWNERS +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/SECURITY.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/dependabot.yml +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/workflows/publish.yml +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/workflows/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/workflows/stale.yml +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/workflows/test.yml +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/workflows/update-homebrew.yml +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/workflows/vscode-ext.yml +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.gitignore +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/CONTRIBUTING.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/LICENSE +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/README.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/SKILL.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/commands/lint-java.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/intellij/README.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/intellij/lsp4ij-template.json +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/vscode/.vscodeignore +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/vscode/README.md +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/vscode/package-lock.json +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/vscode/package.json +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/vscode/src/extension.ts +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/vscode/tsconfig.json +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/hooks/hooks.json +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/hooks/java_linter_reminder.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/scripts/ensure-lsp.sh +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/scripts/generate-formula.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/__main__.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/__init__.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/base.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/cli.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/fixes.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/merkle.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/server.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/__init__.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/conftest.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_base.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_cli.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_config.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_e2e.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_e2e_jdtls.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_exception_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_fixes.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_functional_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_merkle.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_merkle_proxy.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_mutation_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_null_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_server.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_spring_checker.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/tests/test_suppress.py +0 -0
- {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/uv.lock +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.3
|
|
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.3"
|
|
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" }
|
|
@@ -445,6 +445,42 @@ def _version_key(name: str) -> tuple[int, ...]:
|
|
|
445
445
|
return tuple(parts)
|
|
446
446
|
|
|
447
447
|
|
|
448
|
+
def _find_maven_group_root(initial_module: Path, workspace_root: Path) -> Path:
|
|
449
|
+
"""Return the tightest Maven multi-module parent of *initial_module*.
|
|
450
|
+
|
|
451
|
+
Walks up from ``initial_module.parent`` (the module itself is skipped —
|
|
452
|
+
we want its *parent* group, not the module's own pom) toward (and
|
|
453
|
+
including) ``workspace_root``, returning the first ancestor whose
|
|
454
|
+
``pom.xml`` contains a ``<modules>`` section. Falls back to
|
|
455
|
+
``workspace_root`` when no such intermediate parent exists (e.g. flat
|
|
456
|
+
single-level monorepos).
|
|
457
|
+
|
|
458
|
+
This keeps the jdtls index bounded to a module *group* (typically
|
|
459
|
+
5-20 modules) rather than the full IDE workspace (potentially 200+ modules)
|
|
460
|
+
which would cause ``OutOfMemoryError: Java heap space`` during indexing.
|
|
461
|
+
|
|
462
|
+
Both *initial_module* and *workspace_root* are resolved to real paths
|
|
463
|
+
before walking to avoid symlink-based boundary escapes.
|
|
464
|
+
"""
|
|
465
|
+
workspace_root = workspace_root.resolve()
|
|
466
|
+
current = initial_module.resolve().parent
|
|
467
|
+
# Bail immediately if the initial module is outside the workspace.
|
|
468
|
+
if not current.is_relative_to(workspace_root):
|
|
469
|
+
return workspace_root
|
|
470
|
+
while True:
|
|
471
|
+
pom = current / "pom.xml"
|
|
472
|
+
if pom.is_file():
|
|
473
|
+
try:
|
|
474
|
+
if "<modules>" in pom.read_text(encoding="utf-8", errors="ignore"):
|
|
475
|
+
return current
|
|
476
|
+
except OSError:
|
|
477
|
+
pass
|
|
478
|
+
if current in (workspace_root, current.parent):
|
|
479
|
+
break
|
|
480
|
+
current = current.parent
|
|
481
|
+
return workspace_root
|
|
482
|
+
|
|
483
|
+
|
|
448
484
|
@lru_cache(maxsize=256)
|
|
449
485
|
def _module_snapshot_path(module_uri: str) -> Path:
|
|
450
486
|
"""Return the path for the persistent snapshot file for *module_uri*.
|
|
@@ -494,6 +530,22 @@ def _compute_module_diff(
|
|
|
494
530
|
return (diff, current)
|
|
495
531
|
|
|
496
532
|
|
|
533
|
+
def _wipe_data_dir(data_dir: Path) -> None:
|
|
534
|
+
"""Remove *data_dir* so the next jdtls startup gets a clean cold start.
|
|
535
|
+
|
|
536
|
+
Called after an initialize timeout to recover from a corrupted OSGi
|
|
537
|
+
data-dir (e.g. left behind after an OOM crash).
|
|
538
|
+
"""
|
|
539
|
+
shutil.rmtree(data_dir, ignore_errors=True)
|
|
540
|
+
if data_dir.exists():
|
|
541
|
+
logger.warning(
|
|
542
|
+
"jdtls: failed to wipe data-dir %s — next retry may hang again",
|
|
543
|
+
_redact_path(str(data_dir)),
|
|
544
|
+
)
|
|
545
|
+
else:
|
|
546
|
+
logger.info("jdtls: wiped data-dir %s after init timeout", _redact_path(str(data_dir)))
|
|
547
|
+
|
|
548
|
+
|
|
497
549
|
def _clear_cache_on_version_change(cache_root: Path) -> None:
|
|
498
550
|
"""Clear jdtls data cache when the server version changes.
|
|
499
551
|
|
|
@@ -711,6 +763,7 @@ class JdtlsProxy:
|
|
|
711
763
|
if result is None:
|
|
712
764
|
logger.error("jdtls initialize request failed or timed out")
|
|
713
765
|
await self.stop()
|
|
766
|
+
await asyncio.get_running_loop().run_in_executor(None, _wipe_data_dir, data_dir)
|
|
714
767
|
return False
|
|
715
768
|
|
|
716
769
|
self._jdtls_capabilities = result.get("capabilities", {})
|
|
@@ -862,16 +915,34 @@ class JdtlsProxy:
|
|
|
862
915
|
return module_uri
|
|
863
916
|
|
|
864
917
|
async def expand_full_workspace(self) -> None:
|
|
865
|
-
"""Expand jdtls workspace to the
|
|
918
|
+
"""Expand jdtls workspace to the nearest Maven module-group root.
|
|
866
919
|
|
|
867
|
-
|
|
868
|
-
|
|
920
|
+
Instead of expanding all the way to the IDE workspace root (which may
|
|
921
|
+
contain hundreds of Maven modules and cause jdtls OOM), this walks up
|
|
922
|
+
from the initial module to find the *nearest* ancestor directory whose
|
|
923
|
+
``pom.xml`` declares ``<modules>`` — i.e. the tightest multi-module
|
|
924
|
+
Maven parent. This covers sibling modules (the common case for
|
|
925
|
+
cross-module navigation) without ballooning the index.
|
|
926
|
+
|
|
927
|
+
Falls back to the full IDE workspace root when no intermediate
|
|
928
|
+
multi-module parent is found.
|
|
929
|
+
|
|
930
|
+
Removes all individually-added module folders to avoid double-indexing
|
|
931
|
+
with the newly added group root.
|
|
869
932
|
"""
|
|
870
933
|
if self._workspace_expanded or not self._available or not self._original_root_uri:
|
|
871
934
|
return
|
|
872
935
|
from pygls.uris import from_fs_path, to_fs_path
|
|
873
936
|
|
|
874
|
-
|
|
937
|
+
workspace_path = to_fs_path(self._original_root_uri) or self._original_root_uri
|
|
938
|
+
|
|
939
|
+
# Find the tightest multi-module Maven parent of the initial module.
|
|
940
|
+
group_root_path: str = workspace_path
|
|
941
|
+
if self._initial_module_uri:
|
|
942
|
+
initial_path = to_fs_path(self._initial_module_uri) or self._initial_module_uri
|
|
943
|
+
group_root_path = str(_find_maven_group_root(Path(initial_path), Path(workspace_path)))
|
|
944
|
+
|
|
945
|
+
root_path = group_root_path
|
|
875
946
|
root_uri = from_fs_path(root_path) or self._original_root_uri
|
|
876
947
|
if self.modules.was_added(root_uri):
|
|
877
948
|
self._workspace_expanded = True
|
|
@@ -887,7 +958,7 @@ class JdtlsProxy:
|
|
|
887
958
|
p = to_fs_path(uri) or uri
|
|
888
959
|
removed.append({"uri": uri, "name": Path(p).name})
|
|
889
960
|
|
|
890
|
-
logger.info("jdtls: expanding to
|
|
961
|
+
logger.info("jdtls: expanding workspace to module group %s", _redact_path(root_path))
|
|
891
962
|
await self.send_notification(
|
|
892
963
|
_WORKSPACE_DID_CHANGE_FOLDERS,
|
|
893
964
|
{"event": {"added": [{"uri": root_uri, "name": Path(root_path).name}], "removed": removed}},
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import json
|
|
7
7
|
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
9
10
|
from unittest.mock import MagicMock, patch
|
|
10
11
|
|
|
@@ -850,6 +851,7 @@ class TestStartPassesEnvToSubprocess:
|
|
|
850
851
|
patch.object(JdtlsProxy, "_stderr_reader", side_effect=fake_stderr_reader),
|
|
851
852
|
patch.object(JdtlsProxy, "send_request", side_effect=fake_send_request),
|
|
852
853
|
patch.object(JdtlsProxy, "stop", side_effect=lambda: None),
|
|
854
|
+
patch.object(proxy_mod, "_wipe_data_dir"), # avoid blocking rmtree in tests
|
|
853
855
|
):
|
|
854
856
|
ok = await proxy.start({"rootUri": f"file://{tmp_path}"})
|
|
855
857
|
|
|
@@ -858,6 +860,67 @@ class TestStartPassesEnvToSubprocess:
|
|
|
858
860
|
# The crucial assertion: env= was passed through to the subprocess call.
|
|
859
861
|
assert captured["env"] == sentinel_env
|
|
860
862
|
|
|
863
|
+
@pytest.mark.asyncio
|
|
864
|
+
async def test_wipe_runs_in_executor_on_init_timeout(self, tmp_path: Any) -> None:
|
|
865
|
+
"""_wipe_data_dir must be dispatched via run_in_executor to avoid blocking the event loop."""
|
|
866
|
+
from unittest.mock import patch
|
|
867
|
+
|
|
868
|
+
import java_functional_lsp.proxy as proxy_mod
|
|
869
|
+
|
|
870
|
+
proxy = JdtlsProxy()
|
|
871
|
+
proxy._jdtls_on_path = True
|
|
872
|
+
|
|
873
|
+
fake_process = MagicMock()
|
|
874
|
+
fake_process.pid = 99999
|
|
875
|
+
fake_process.stdout = MagicMock()
|
|
876
|
+
fake_process.stderr = MagicMock()
|
|
877
|
+
fake_process.returncode = 0
|
|
878
|
+
|
|
879
|
+
async def fake_wait() -> int:
|
|
880
|
+
return 0
|
|
881
|
+
|
|
882
|
+
fake_process.wait = fake_wait
|
|
883
|
+
|
|
884
|
+
async def fake_create_subprocess(*_a: Any, **_kw: Any) -> Any:
|
|
885
|
+
return fake_process
|
|
886
|
+
|
|
887
|
+
async def fake_reader_loop(_: Any) -> None:
|
|
888
|
+
return None
|
|
889
|
+
|
|
890
|
+
async def fake_stderr_reader(_: Any) -> None:
|
|
891
|
+
return None
|
|
892
|
+
|
|
893
|
+
async def fake_send_request(*_a: Any, **_kw: Any) -> None:
|
|
894
|
+
return None # simulate initialize timeout
|
|
895
|
+
|
|
896
|
+
wipe_calls: list[Any] = []
|
|
897
|
+
|
|
898
|
+
async def fake_run_in_executor(_executor: Any, fn: Any, *args: Any) -> Any:
|
|
899
|
+
if fn is proxy_mod._wipe_data_dir:
|
|
900
|
+
wipe_calls.append((fn, args))
|
|
901
|
+
return None
|
|
902
|
+
# Other blocking calls (e.g. _blocking_startup) must run for real
|
|
903
|
+
return fn(*args)
|
|
904
|
+
|
|
905
|
+
fake_loop = MagicMock()
|
|
906
|
+
fake_loop.run_in_executor = fake_run_in_executor
|
|
907
|
+
|
|
908
|
+
with (
|
|
909
|
+
patch.object(proxy_mod.shutil, "which", return_value="/fake/jdtls"),
|
|
910
|
+
patch.object(proxy_mod, "build_jdtls_env", return_value={}),
|
|
911
|
+
patch.object(proxy_mod.asyncio, "create_subprocess_exec", side_effect=fake_create_subprocess),
|
|
912
|
+
patch.object(JdtlsProxy, "_reader_loop", side_effect=fake_reader_loop),
|
|
913
|
+
patch.object(JdtlsProxy, "_stderr_reader", side_effect=fake_stderr_reader),
|
|
914
|
+
patch.object(JdtlsProxy, "send_request", side_effect=fake_send_request),
|
|
915
|
+
patch.object(JdtlsProxy, "stop", side_effect=lambda: None),
|
|
916
|
+
patch("java_functional_lsp.proxy.asyncio.get_running_loop", return_value=fake_loop),
|
|
917
|
+
):
|
|
918
|
+
ok = await proxy.start({"rootUri": f"file://{tmp_path}"})
|
|
919
|
+
|
|
920
|
+
assert ok is False
|
|
921
|
+
assert len(wipe_calls) == 1
|
|
922
|
+
assert wipe_calls[0][0] is proxy_mod._wipe_data_dir
|
|
923
|
+
|
|
861
924
|
|
|
862
925
|
class TestFindModuleRoot:
|
|
863
926
|
"""Tests for find_module_root — build-file detection for module scoping."""
|
|
@@ -1037,6 +1100,65 @@ class TestLazyStart:
|
|
|
1037
1100
|
assert result2 is None # Already known
|
|
1038
1101
|
assert proxy.send_notification.call_count == 1 # type: ignore[attr-defined]
|
|
1039
1102
|
|
|
1103
|
+
async def test_expand_uses_maven_group_root_not_workspace_root(self, tmp_path: Path) -> None:
|
|
1104
|
+
"""expand_full_workspace expands to nearest multi-module Maven parent, not the full IDE root."""
|
|
1105
|
+
from unittest.mock import AsyncMock
|
|
1106
|
+
|
|
1107
|
+
from java_functional_lsp.proxy import JdtlsProxy
|
|
1108
|
+
|
|
1109
|
+
# Layout: workspace/group/module — group/pom.xml has <modules>
|
|
1110
|
+
workspace = tmp_path / "workspace"
|
|
1111
|
+
group = workspace / "group"
|
|
1112
|
+
module = group / "module"
|
|
1113
|
+
module.mkdir(parents=True)
|
|
1114
|
+
(group / "pom.xml").write_text("<project><modules><module>module</module></modules></project>")
|
|
1115
|
+
(workspace / "pom.xml").write_text("<project><modules><module>group</module></modules></project>")
|
|
1116
|
+
|
|
1117
|
+
module_uri = module.as_uri()
|
|
1118
|
+
workspace_uri = workspace.as_uri()
|
|
1119
|
+
|
|
1120
|
+
proxy = JdtlsProxy()
|
|
1121
|
+
proxy._available = True
|
|
1122
|
+
proxy._original_root_uri = workspace_uri
|
|
1123
|
+
proxy._initial_module_uri = module_uri
|
|
1124
|
+
proxy.modules.mark_added(module_uri)
|
|
1125
|
+
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1126
|
+
await proxy.expand_full_workspace()
|
|
1127
|
+
|
|
1128
|
+
call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
|
|
1129
|
+
event = call_args[1]["event"]
|
|
1130
|
+
# Should expand to group/, NOT workspace/
|
|
1131
|
+
assert event["added"][0]["uri"] == group.as_uri()
|
|
1132
|
+
assert event["added"][0]["uri"] != workspace_uri
|
|
1133
|
+
|
|
1134
|
+
async def test_expand_falls_back_to_workspace_root_when_no_group_pom(self, tmp_path: Path) -> None:
|
|
1135
|
+
"""Falls back to the IDE workspace root when no intermediate multi-module parent exists."""
|
|
1136
|
+
from unittest.mock import AsyncMock
|
|
1137
|
+
|
|
1138
|
+
from java_functional_lsp.proxy import JdtlsProxy
|
|
1139
|
+
|
|
1140
|
+
# Flat layout: workspace/module — no intermediate pom.xml
|
|
1141
|
+
workspace = tmp_path / "workspace"
|
|
1142
|
+
module = workspace / "module"
|
|
1143
|
+
module.mkdir(parents=True)
|
|
1144
|
+
# workspace/pom.xml has <modules>, but there's nothing in between
|
|
1145
|
+
(workspace / "pom.xml").write_text("<project><modules><module>module</module></modules></project>")
|
|
1146
|
+
|
|
1147
|
+
module_uri = module.as_uri()
|
|
1148
|
+
workspace_uri = workspace.as_uri()
|
|
1149
|
+
|
|
1150
|
+
proxy = JdtlsProxy()
|
|
1151
|
+
proxy._available = True
|
|
1152
|
+
proxy._original_root_uri = workspace_uri
|
|
1153
|
+
proxy._initial_module_uri = module_uri
|
|
1154
|
+
proxy.modules.mark_added(module_uri)
|
|
1155
|
+
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1156
|
+
await proxy.expand_full_workspace()
|
|
1157
|
+
|
|
1158
|
+
call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
|
|
1159
|
+
event = call_args[1]["event"]
|
|
1160
|
+
assert event["added"][0]["uri"] == workspace_uri
|
|
1161
|
+
|
|
1040
1162
|
async def test_expand_full_workspace_sends_notification(self) -> None:
|
|
1041
1163
|
from unittest.mock import AsyncMock
|
|
1042
1164
|
|
|
@@ -1107,7 +1229,8 @@ class TestLazyStart:
|
|
|
1107
1229
|
proxy._jdtls_on_path = True
|
|
1108
1230
|
captured: dict[str, Any] = {}
|
|
1109
1231
|
|
|
1110
|
-
async def capturing_start(
|
|
1232
|
+
async def capturing_start(_params: Any, *, module_root_uri: str | None = None, config: Any = None) -> bool:
|
|
1233
|
+
_ = config
|
|
1111
1234
|
captured["module_root_uri"] = module_root_uri
|
|
1112
1235
|
return False
|
|
1113
1236
|
|
|
@@ -1131,7 +1254,8 @@ class TestLazyStart:
|
|
|
1131
1254
|
|
|
1132
1255
|
captured: dict[str, Any] = {}
|
|
1133
1256
|
|
|
1134
|
-
async def capturing_start(
|
|
1257
|
+
async def capturing_start(_params: Any, *, module_root_uri: str | None = None, config: Any = None) -> bool:
|
|
1258
|
+
_ = config
|
|
1135
1259
|
captured["module_root_uri"] = module_root_uri
|
|
1136
1260
|
return False
|
|
1137
1261
|
|
|
@@ -1155,3 +1279,58 @@ class TestLazyStart:
|
|
|
1155
1279
|
module_root = "file:///workspace/monorepo/module-a"
|
|
1156
1280
|
module_hash = hashlib.sha256(module_root.encode()).hexdigest()[:12]
|
|
1157
1281
|
assert expected_hash != module_hash
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
# ---------------------------------------------------------------------------
|
|
1285
|
+
# _wipe_data_dir
|
|
1286
|
+
# ---------------------------------------------------------------------------
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
class TestWipeDataDir:
|
|
1290
|
+
def test_wipe_removes_directory(self, tmp_path: Path) -> None:
|
|
1291
|
+
"""Directory is removed and info is logged."""
|
|
1292
|
+
from java_functional_lsp.proxy import _wipe_data_dir
|
|
1293
|
+
|
|
1294
|
+
data_dir = tmp_path / "data"
|
|
1295
|
+
data_dir.mkdir()
|
|
1296
|
+
(data_dir / "some_file.dat").write_text("x")
|
|
1297
|
+
|
|
1298
|
+
_wipe_data_dir(data_dir)
|
|
1299
|
+
|
|
1300
|
+
assert not data_dir.exists()
|
|
1301
|
+
|
|
1302
|
+
def test_wipe_logs_warning_when_removal_fails(self, tmp_path: Path) -> None:
|
|
1303
|
+
"""Warning is logged when rmtree silently fails (dir still present)."""
|
|
1304
|
+
from unittest.mock import patch
|
|
1305
|
+
|
|
1306
|
+
from java_functional_lsp.proxy import _wipe_data_dir
|
|
1307
|
+
|
|
1308
|
+
data_dir = tmp_path / "data"
|
|
1309
|
+
data_dir.mkdir()
|
|
1310
|
+
|
|
1311
|
+
with patch("shutil.rmtree"): # no-op — dir remains
|
|
1312
|
+
with patch("java_functional_lsp.proxy.logger") as mock_logger:
|
|
1313
|
+
_wipe_data_dir(data_dir)
|
|
1314
|
+
mock_logger.warning.assert_called_once()
|
|
1315
|
+
# info must NOT be called when wipe failed
|
|
1316
|
+
mock_logger.info.assert_not_called()
|
|
1317
|
+
|
|
1318
|
+
def test_wipe_logs_info_on_success(self, tmp_path: Path) -> None:
|
|
1319
|
+
"""Info is logged (not warning) when directory is successfully removed."""
|
|
1320
|
+
from unittest.mock import patch
|
|
1321
|
+
|
|
1322
|
+
from java_functional_lsp.proxy import _wipe_data_dir
|
|
1323
|
+
|
|
1324
|
+
data_dir = tmp_path / "data"
|
|
1325
|
+
data_dir.mkdir()
|
|
1326
|
+
|
|
1327
|
+
with patch("java_functional_lsp.proxy.logger") as mock_logger:
|
|
1328
|
+
_wipe_data_dir(data_dir)
|
|
1329
|
+
mock_logger.info.assert_called_once()
|
|
1330
|
+
mock_logger.warning.assert_not_called()
|
|
1331
|
+
|
|
1332
|
+
def test_wipe_nonexistent_dir_does_not_raise(self, tmp_path: Path) -> None:
|
|
1333
|
+
"""Calling wipe on a non-existent dir is safe."""
|
|
1334
|
+
from java_functional_lsp.proxy import _wipe_data_dir
|
|
1335
|
+
|
|
1336
|
+
_wipe_data_dir(tmp_path / "ghost") # should not raise
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/ISSUE_TEMPLATE/bug-report.md
RENAMED
|
File without changes
|
{java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/ISSUE_TEMPLATE/feature-request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/workflows/release-drafter.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/.github/workflows/update-homebrew.yml
RENAMED
|
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
|
{java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/editors/intellij/lsp4ij-template.json
RENAMED
|
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
|
{java_functional_lsp-0.9.1 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/base.py
RENAMED
|
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
|