java-functional-lsp 0.9.2__tar.gz → 0.9.4__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.2 → java_functional_lsp-0.9.4}/PKG-INFO +1 -1
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/pyproject.toml +1 -1
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/__init__.py +1 -1
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/proxy.py +39 -14
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_proxy.py +160 -19
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.claude-plugin/plugin.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.githooks/pre-commit +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.githooks/pre-push +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/CODEOWNERS +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/SECURITY.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/dependabot.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/workflows/publish.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/workflows/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/workflows/stale.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/workflows/test.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/workflows/update-homebrew.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.github/workflows/vscode-ext.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.gitignore +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/CONTRIBUTING.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/LICENSE +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/README.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/SKILL.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/commands/lint-java.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/editors/intellij/README.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/editors/intellij/lsp4ij-template.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/editors/vscode/.vscodeignore +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/editors/vscode/README.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/editors/vscode/package-lock.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/editors/vscode/package.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/editors/vscode/src/extension.ts +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/editors/vscode/tsconfig.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/hooks/hooks.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/hooks/java_linter_reminder.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/scripts/ensure-lsp.sh +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/scripts/generate-formula.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/__main__.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/__init__.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/base.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/cli.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/fixes.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/merkle.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/src/java_functional_lsp/server.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/__init__.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/conftest.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_base.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_cli.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_config.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_e2e.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_e2e_jdtls.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_exception_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_fixes.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_functional_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_merkle.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_merkle_proxy.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_mutation_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_null_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_server.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_spring_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/tests/test_suppress.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/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.4
|
|
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.4"
|
|
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)
|
|
@@ -530,6 +536,22 @@ def _compute_module_diff(
|
|
|
530
536
|
return (diff, current)
|
|
531
537
|
|
|
532
538
|
|
|
539
|
+
def _wipe_data_dir(data_dir: Path) -> None:
|
|
540
|
+
"""Remove *data_dir* so the next jdtls startup gets a clean cold start.
|
|
541
|
+
|
|
542
|
+
Called after an initialize timeout to recover from a corrupted OSGi
|
|
543
|
+
data-dir (e.g. left behind after an OOM crash).
|
|
544
|
+
"""
|
|
545
|
+
shutil.rmtree(data_dir, ignore_errors=True)
|
|
546
|
+
if data_dir.exists():
|
|
547
|
+
logger.warning(
|
|
548
|
+
"jdtls: failed to wipe data-dir %s — next retry may hang again",
|
|
549
|
+
_redact_path(str(data_dir)),
|
|
550
|
+
)
|
|
551
|
+
else:
|
|
552
|
+
logger.info("jdtls: wiped data-dir %s after init timeout", _redact_path(str(data_dir)))
|
|
553
|
+
|
|
554
|
+
|
|
533
555
|
def _clear_cache_on_version_change(cache_root: Path) -> None:
|
|
534
556
|
"""Clear jdtls data cache when the server version changes.
|
|
535
557
|
|
|
@@ -747,6 +769,7 @@ class JdtlsProxy:
|
|
|
747
769
|
if result is None:
|
|
748
770
|
logger.error("jdtls initialize request failed or timed out")
|
|
749
771
|
await self.stop()
|
|
772
|
+
await asyncio.get_running_loop().run_in_executor(None, _wipe_data_dir, data_dir)
|
|
750
773
|
return False
|
|
751
774
|
|
|
752
775
|
self._jdtls_capabilities = result.get("capabilities", {})
|
|
@@ -907,8 +930,10 @@ class JdtlsProxy:
|
|
|
907
930
|
Maven parent. This covers sibling modules (the common case for
|
|
908
931
|
cross-module navigation) without ballooning the index.
|
|
909
932
|
|
|
910
|
-
Falls back to the
|
|
911
|
-
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.
|
|
912
937
|
|
|
913
938
|
Removes all individually-added module folders to avoid double-indexing
|
|
914
939
|
with the newly added group root.
|
|
@@ -851,6 +851,7 @@ class TestStartPassesEnvToSubprocess:
|
|
|
851
851
|
patch.object(JdtlsProxy, "_stderr_reader", side_effect=fake_stderr_reader),
|
|
852
852
|
patch.object(JdtlsProxy, "send_request", side_effect=fake_send_request),
|
|
853
853
|
patch.object(JdtlsProxy, "stop", side_effect=lambda: None),
|
|
854
|
+
patch.object(proxy_mod, "_wipe_data_dir"), # avoid blocking rmtree in tests
|
|
854
855
|
):
|
|
855
856
|
ok = await proxy.start({"rootUri": f"file://{tmp_path}"})
|
|
856
857
|
|
|
@@ -859,6 +860,67 @@ class TestStartPassesEnvToSubprocess:
|
|
|
859
860
|
# The crucial assertion: env= was passed through to the subprocess call.
|
|
860
861
|
assert captured["env"] == sentinel_env
|
|
861
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
|
+
|
|
862
924
|
|
|
863
925
|
class TestFindModuleRoot:
|
|
864
926
|
"""Tests for find_module_root — build-file detection for module scoping."""
|
|
@@ -1069,17 +1131,24 @@ class TestLazyStart:
|
|
|
1069
1131
|
assert event["added"][0]["uri"] == group.as_uri()
|
|
1070
1132
|
assert event["added"][0]["uri"] != workspace_uri
|
|
1071
1133
|
|
|
1072
|
-
async def
|
|
1073
|
-
"""
|
|
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
|
+
"""
|
|
1074
1143
|
from unittest.mock import AsyncMock
|
|
1075
1144
|
|
|
1076
1145
|
from java_functional_lsp.proxy import JdtlsProxy
|
|
1077
1146
|
|
|
1078
|
-
# Flat layout: workspace/module — no intermediate pom.xml
|
|
1147
|
+
# Flat layout: workspace/module — no intermediate pom.xml between module and workspace
|
|
1079
1148
|
workspace = tmp_path / "workspace"
|
|
1080
1149
|
module = workspace / "module"
|
|
1081
1150
|
module.mkdir(parents=True)
|
|
1082
|
-
# workspace/pom.xml has <modules>, but
|
|
1151
|
+
# workspace/pom.xml has <modules>, but the walk stops BEFORE reaching it
|
|
1083
1152
|
(workspace / "pom.xml").write_text("<project><modules><module>module</module></modules></project>")
|
|
1084
1153
|
|
|
1085
1154
|
module_uri = module.as_uri()
|
|
@@ -1093,9 +1162,9 @@ class TestLazyStart:
|
|
|
1093
1162
|
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1094
1163
|
await proxy.expand_full_workspace()
|
|
1095
1164
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
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
|
|
1099
1168
|
|
|
1100
1169
|
async def test_expand_full_workspace_sends_notification(self) -> None:
|
|
1101
1170
|
from unittest.mock import AsyncMock
|
|
@@ -1110,28 +1179,43 @@ class TestLazyStart:
|
|
|
1110
1179
|
proxy.send_notification.assert_called_once() # type: ignore[attr-defined]
|
|
1111
1180
|
assert proxy._workspace_expanded is True
|
|
1112
1181
|
|
|
1113
|
-
async def test_expand_full_workspace_removes_initial_module(self) -> None:
|
|
1114
|
-
"""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."""
|
|
1115
1184
|
from unittest.mock import AsyncMock
|
|
1116
1185
|
|
|
1117
1186
|
from java_functional_lsp.proxy import JdtlsProxy
|
|
1118
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
|
+
|
|
1119
1203
|
proxy = JdtlsProxy()
|
|
1120
1204
|
proxy._available = True
|
|
1121
|
-
proxy._original_root_uri =
|
|
1122
|
-
proxy._initial_module_uri =
|
|
1123
|
-
# Simulate
|
|
1124
|
-
proxy.modules.mark_added(
|
|
1125
|
-
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)
|
|
1126
1210
|
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1127
1211
|
await proxy.expand_full_workspace()
|
|
1128
1212
|
proxy.send_notification.assert_called_once() # type: ignore[attr-defined]
|
|
1129
1213
|
call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
|
|
1130
1214
|
event = call_args[1]["event"]
|
|
1131
1215
|
removed_uris = {e["uri"] for e in event["removed"]}
|
|
1132
|
-
assert
|
|
1133
|
-
assert
|
|
1134
|
-
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
|
|
1135
1219
|
|
|
1136
1220
|
async def test_expand_full_workspace_noop_when_not_available(self) -> None:
|
|
1137
1221
|
from unittest.mock import AsyncMock
|
|
@@ -1167,7 +1251,8 @@ class TestLazyStart:
|
|
|
1167
1251
|
proxy._jdtls_on_path = True
|
|
1168
1252
|
captured: dict[str, Any] = {}
|
|
1169
1253
|
|
|
1170
|
-
async def capturing_start(
|
|
1254
|
+
async def capturing_start(_params: Any, *, module_root_uri: str | None = None, config: Any = None) -> bool:
|
|
1255
|
+
_ = config
|
|
1171
1256
|
captured["module_root_uri"] = module_root_uri
|
|
1172
1257
|
return False
|
|
1173
1258
|
|
|
@@ -1191,7 +1276,8 @@ class TestLazyStart:
|
|
|
1191
1276
|
|
|
1192
1277
|
captured: dict[str, Any] = {}
|
|
1193
1278
|
|
|
1194
|
-
async def capturing_start(
|
|
1279
|
+
async def capturing_start(_params: Any, *, module_root_uri: str | None = None, config: Any = None) -> bool:
|
|
1280
|
+
_ = config
|
|
1195
1281
|
captured["module_root_uri"] = module_root_uri
|
|
1196
1282
|
return False
|
|
1197
1283
|
|
|
@@ -1215,3 +1301,58 @@ class TestLazyStart:
|
|
|
1215
1301
|
module_root = "file:///workspace/monorepo/module-a"
|
|
1216
1302
|
module_hash = hashlib.sha256(module_root.encode()).hexdigest()[:12]
|
|
1217
1303
|
assert expected_hash != module_hash
|
|
1304
|
+
|
|
1305
|
+
|
|
1306
|
+
# ---------------------------------------------------------------------------
|
|
1307
|
+
# _wipe_data_dir
|
|
1308
|
+
# ---------------------------------------------------------------------------
|
|
1309
|
+
|
|
1310
|
+
|
|
1311
|
+
class TestWipeDataDir:
|
|
1312
|
+
def test_wipe_removes_directory(self, tmp_path: Path) -> None:
|
|
1313
|
+
"""Directory is removed and info is logged."""
|
|
1314
|
+
from java_functional_lsp.proxy import _wipe_data_dir
|
|
1315
|
+
|
|
1316
|
+
data_dir = tmp_path / "data"
|
|
1317
|
+
data_dir.mkdir()
|
|
1318
|
+
(data_dir / "some_file.dat").write_text("x")
|
|
1319
|
+
|
|
1320
|
+
_wipe_data_dir(data_dir)
|
|
1321
|
+
|
|
1322
|
+
assert not data_dir.exists()
|
|
1323
|
+
|
|
1324
|
+
def test_wipe_logs_warning_when_removal_fails(self, tmp_path: Path) -> None:
|
|
1325
|
+
"""Warning is logged when rmtree silently fails (dir still present)."""
|
|
1326
|
+
from unittest.mock import patch
|
|
1327
|
+
|
|
1328
|
+
from java_functional_lsp.proxy import _wipe_data_dir
|
|
1329
|
+
|
|
1330
|
+
data_dir = tmp_path / "data"
|
|
1331
|
+
data_dir.mkdir()
|
|
1332
|
+
|
|
1333
|
+
with patch("shutil.rmtree"): # no-op — dir remains
|
|
1334
|
+
with patch("java_functional_lsp.proxy.logger") as mock_logger:
|
|
1335
|
+
_wipe_data_dir(data_dir)
|
|
1336
|
+
mock_logger.warning.assert_called_once()
|
|
1337
|
+
# info must NOT be called when wipe failed
|
|
1338
|
+
mock_logger.info.assert_not_called()
|
|
1339
|
+
|
|
1340
|
+
def test_wipe_logs_info_on_success(self, tmp_path: Path) -> None:
|
|
1341
|
+
"""Info is logged (not warning) when directory is successfully removed."""
|
|
1342
|
+
from unittest.mock import patch
|
|
1343
|
+
|
|
1344
|
+
from java_functional_lsp.proxy import _wipe_data_dir
|
|
1345
|
+
|
|
1346
|
+
data_dir = tmp_path / "data"
|
|
1347
|
+
data_dir.mkdir()
|
|
1348
|
+
|
|
1349
|
+
with patch("java_functional_lsp.proxy.logger") as mock_logger:
|
|
1350
|
+
_wipe_data_dir(data_dir)
|
|
1351
|
+
mock_logger.info.assert_called_once()
|
|
1352
|
+
mock_logger.warning.assert_not_called()
|
|
1353
|
+
|
|
1354
|
+
def test_wipe_nonexistent_dir_does_not_raise(self, tmp_path: Path) -> None:
|
|
1355
|
+
"""Calling wipe on a non-existent dir is safe."""
|
|
1356
|
+
from java_functional_lsp.proxy import _wipe_data_dir
|
|
1357
|
+
|
|
1358
|
+
_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.2 → java_functional_lsp-0.9.4}/.github/ISSUE_TEMPLATE/bug-report.md
RENAMED
|
File without changes
|
{java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.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.2 → java_functional_lsp-0.9.4}/.github/workflows/release-drafter.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.9.2 → java_functional_lsp-0.9.4}/.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.2 → java_functional_lsp-0.9.4}/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.2 → java_functional_lsp-0.9.4}/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
|