java-functional-lsp 0.9.2__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.2 → java_functional_lsp-0.9.3}/PKG-INFO +1 -1
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/pyproject.toml +1 -1
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/__init__.py +1 -1
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/proxy.py +17 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_proxy.py +121 -2
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.claude-plugin/plugin.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.githooks/pre-commit +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.githooks/pre-push +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/CODEOWNERS +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/SECURITY.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/dependabot.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/workflows/publish.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/workflows/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/workflows/stale.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/workflows/test.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/workflows/update-homebrew.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.github/workflows/vscode-ext.yml +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/.gitignore +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/CONTRIBUTING.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/LICENSE +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/README.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/SKILL.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/commands/lint-java.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/editors/intellij/README.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/editors/intellij/lsp4ij-template.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/editors/vscode/.vscodeignore +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/editors/vscode/README.md +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/editors/vscode/package-lock.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/editors/vscode/package.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/editors/vscode/src/extension.ts +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/editors/vscode/tsconfig.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/hooks/hooks.json +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/hooks/java_linter_reminder.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/scripts/ensure-lsp.sh +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/scripts/generate-formula.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/__main__.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/__init__.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/base.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/cli.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/fixes.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/merkle.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/src/java_functional_lsp/server.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/__init__.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/conftest.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_base.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_cli.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_config.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_e2e.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_e2e_jdtls.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_exception_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_fixes.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_functional_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_merkle.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_merkle_proxy.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_mutation_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_null_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_server.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_spring_checker.py +0 -0
- {java_functional_lsp-0.9.2 → java_functional_lsp-0.9.3}/tests/test_suppress.py +0 -0
- {java_functional_lsp-0.9.2 → 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" }
|
|
@@ -530,6 +530,22 @@ def _compute_module_diff(
|
|
|
530
530
|
return (diff, current)
|
|
531
531
|
|
|
532
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
|
+
|
|
533
549
|
def _clear_cache_on_version_change(cache_root: Path) -> None:
|
|
534
550
|
"""Clear jdtls data cache when the server version changes.
|
|
535
551
|
|
|
@@ -747,6 +763,7 @@ class JdtlsProxy:
|
|
|
747
763
|
if result is None:
|
|
748
764
|
logger.error("jdtls initialize request failed or timed out")
|
|
749
765
|
await self.stop()
|
|
766
|
+
await asyncio.get_running_loop().run_in_executor(None, _wipe_data_dir, data_dir)
|
|
750
767
|
return False
|
|
751
768
|
|
|
752
769
|
self._jdtls_capabilities = result.get("capabilities", {})
|
|
@@ -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."""
|
|
@@ -1167,7 +1229,8 @@ class TestLazyStart:
|
|
|
1167
1229
|
proxy._jdtls_on_path = True
|
|
1168
1230
|
captured: dict[str, Any] = {}
|
|
1169
1231
|
|
|
1170
|
-
async def capturing_start(
|
|
1232
|
+
async def capturing_start(_params: Any, *, module_root_uri: str | None = None, config: Any = None) -> bool:
|
|
1233
|
+
_ = config
|
|
1171
1234
|
captured["module_root_uri"] = module_root_uri
|
|
1172
1235
|
return False
|
|
1173
1236
|
|
|
@@ -1191,7 +1254,8 @@ class TestLazyStart:
|
|
|
1191
1254
|
|
|
1192
1255
|
captured: dict[str, Any] = {}
|
|
1193
1256
|
|
|
1194
|
-
async def capturing_start(
|
|
1257
|
+
async def capturing_start(_params: Any, *, module_root_uri: str | None = None, config: Any = None) -> bool:
|
|
1258
|
+
_ = config
|
|
1195
1259
|
captured["module_root_uri"] = module_root_uri
|
|
1196
1260
|
return False
|
|
1197
1261
|
|
|
@@ -1215,3 +1279,58 @@ class TestLazyStart:
|
|
|
1215
1279
|
module_root = "file:///workspace/monorepo/module-a"
|
|
1216
1280
|
module_hash = hashlib.sha256(module_root.encode()).hexdigest()[:12]
|
|
1217
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.2 → java_functional_lsp-0.9.3}/.github/ISSUE_TEMPLATE/bug-report.md
RENAMED
|
File without changes
|
{java_functional_lsp-0.9.2 → 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.2 → 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.2 → 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.2 → 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.2 → 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
|