java-functional-lsp 0.7.6__tar.gz → 0.7.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/PKG-INFO +1 -1
  2. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/pyproject.toml +1 -1
  3. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/__init__.py +1 -1
  4. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/proxy.py +16 -2
  5. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/server.py +12 -4
  6. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_server.py +1 -4
  7. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.claude-plugin/plugin.json +0 -0
  8. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.githooks/pre-commit +0 -0
  9. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.githooks/pre-push +0 -0
  10. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/CODEOWNERS +0 -0
  11. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  12. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  13. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  14. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/SECURITY.md +0 -0
  15. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/dependabot.yml +0 -0
  16. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/release-drafter.yml +0 -0
  17. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/workflows/publish.yml +0 -0
  18. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/workflows/release-drafter.yml +0 -0
  19. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/workflows/stale.yml +0 -0
  20. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/workflows/test.yml +0 -0
  21. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/workflows/update-homebrew.yml +0 -0
  22. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.github/workflows/vscode-ext.yml +0 -0
  23. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/.gitignore +0 -0
  24. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/CONTRIBUTING.md +0 -0
  25. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/LICENSE +0 -0
  26. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/README.md +0 -0
  27. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/SKILL.md +0 -0
  28. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/commands/lint-java.md +0 -0
  29. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/editors/intellij/README.md +0 -0
  30. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/editors/intellij/lsp4ij-template.json +0 -0
  31. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/editors/vscode/.vscodeignore +0 -0
  32. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/editors/vscode/README.md +0 -0
  33. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/editors/vscode/package-lock.json +0 -0
  34. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/editors/vscode/package.json +0 -0
  35. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/editors/vscode/src/extension.ts +0 -0
  36. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/editors/vscode/tsconfig.json +0 -0
  37. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/hooks/hooks.json +0 -0
  38. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/hooks/java_linter_reminder.py +0 -0
  39. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/scripts/ensure-lsp.sh +0 -0
  40. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/scripts/generate-formula.py +0 -0
  41. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/__main__.py +0 -0
  42. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/analyzers/__init__.py +0 -0
  43. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/analyzers/base.py +0 -0
  44. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
  45. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
  46. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
  47. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
  48. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
  49. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/cli.py +0 -0
  50. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/src/java_functional_lsp/fixes.py +0 -0
  51. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/__init__.py +0 -0
  52. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/conftest.py +0 -0
  53. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_base.py +0 -0
  54. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_cli.py +0 -0
  55. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_config.py +0 -0
  56. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_e2e.py +0 -0
  57. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_e2e_jdtls.py +0 -0
  58. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_exception_checker.py +0 -0
  59. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_fixes.py +0 -0
  60. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_functional_checker.py +0 -0
  61. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_mutation_checker.py +0 -0
  62. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_null_checker.py +0 -0
  63. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_proxy.py +0 -0
  64. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_spring_checker.py +0 -0
  65. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/tests/test_suppress.py +0 -0
  66. {java_functional_lsp-0.7.6 → java_functional_lsp-0.7.7}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-functional-lsp
3
- Version: 0.7.6
3
+ Version: 0.7.7
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.7.6"
7
+ version = "0.7.7"
8
8
  description = "Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,3 +1,3 @@
1
1
  """java-functional-lsp: A Java LSP server enforcing functional programming best practices."""
2
2
 
3
- __version__ = "0.7.6"
3
+ __version__ = "0.7.7"
@@ -12,6 +12,7 @@ import platform
12
12
  import re
13
13
  import shutil
14
14
  import subprocess
15
+ import time
15
16
  from collections import deque
16
17
  from collections.abc import Callable, Mapping
17
18
  from functools import lru_cache
@@ -22,6 +23,7 @@ logger = logging.getLogger(__name__)
22
23
 
23
24
  REQUEST_TIMEOUT = 30.0 # seconds — per-request timeout for normal operations
24
25
  _INITIALIZE_TIMEOUT = 120.0 # seconds — module-scoped init can still be slow (Maven classpath resolution)
26
+ _START_RETRY_COOLDOWN = 300.0 # seconds — retry jdtls startup after transient failure
25
27
  DEFAULT_JVM_MAX_HEAP = "4g"
26
28
  _STDERR_LINE_MAX = 1000
27
29
 
@@ -448,6 +450,7 @@ class JdtlsProxy:
448
450
  self._start_lock = asyncio.Lock()
449
451
  self._starting = False
450
452
  self._start_failed = False
453
+ self._start_failed_at: float | None = None
451
454
  self._jdtls_on_path = False
452
455
  self._lazy_start_fired = False
453
456
  self._queued_notifications: deque[tuple[str, Any]] = deque(maxlen=_MAX_QUEUED_NOTIFICATIONS)
@@ -566,12 +569,21 @@ class JdtlsProxy:
566
569
  """Start jdtls lazily, scoped to the module containing *file_uri*.
567
570
 
568
571
  Thread-safe: uses asyncio.Lock to prevent double-start from rapid
569
- didOpen calls. Sets ``_start_failed`` on failure to prevent retries.
572
+ didOpen calls. Sets ``_start_failed`` on failure to prevent retries,
573
+ but allows retry after a cooldown period (5 minutes) so transient
574
+ failures (Maven Central timeout, JVM OOM) don't permanently disable jdtls.
570
575
  """
571
576
  if self._available:
572
577
  return True
573
- if self._start_failed or not self._jdtls_on_path:
578
+ if not self._jdtls_on_path:
574
579
  return False
580
+ if self._start_failed:
581
+ if self._start_failed_at and (time.monotonic() - self._start_failed_at > _START_RETRY_COOLDOWN):
582
+ self._start_failed = False
583
+ self._start_failed_at = None
584
+ logger.info("jdtls: retrying after previous failure (cooldown elapsed)")
585
+ else:
586
+ return False
575
587
 
576
588
  async with self._start_lock:
577
589
  if self._available:
@@ -583,10 +595,12 @@ class JdtlsProxy:
583
595
  started = await self.start(init_params, module_root_uri=module_uri)
584
596
  if not started:
585
597
  self._start_failed = True
598
+ self._start_failed_at = time.monotonic()
586
599
  self._queued_notifications.clear()
587
600
  return started
588
601
  except Exception:
589
602
  self._start_failed = True
603
+ self._start_failed_at = time.monotonic()
590
604
  self._queued_notifications.clear()
591
605
  raise
592
606
  finally:
@@ -66,9 +66,17 @@ class JavaFunctionalLspServer(LanguageServer):
66
66
  self._proxy = JdtlsProxy(on_diagnostics=self._on_jdtls_diagnostics)
67
67
 
68
68
  def _on_jdtls_diagnostics(self, uri: str, diagnostics: list[Any]) -> None:
69
- """Called when jdtls publishes diagnostics — merge with custom and re-publish."""
69
+ """Called when jdtls publishes diagnostics — merge with custom and re-publish.
70
+
71
+ Also marks the file's module as READY, since receiving diagnostics from
72
+ jdtls is a reliable signal that the module has been indexed (more reliable
73
+ than a first non-None response which may be semantically empty).
74
+ """
70
75
  if not uri.endswith(".java"):
71
76
  return
77
+ module_uri = _resolve_module_uri(uri)
78
+ if module_uri:
79
+ self._proxy.modules.mark_ready(module_uri)
72
80
  try:
73
81
  _analyze_and_publish(uri)
74
82
  except Exception as e:
@@ -406,8 +414,9 @@ async def _lazy_start_jdtls(file_uri: str) -> None:
406
414
  """Background task: start jdtls scoped to the module containing *file_uri*.
407
415
 
408
416
  Runs in the background so ``on_did_open`` returns immediately with custom
409
- diagnostics. After jdtls initializes, registers capabilities, flushes
410
- queued notifications, and schedules workspace expansion.
417
+ diagnostics. After jdtls initializes, registers capabilities and flushes
418
+ queued notifications. Workspace expansion is NOT done eagerly — modules
419
+ are loaded on-demand via ``add_module_if_new()`` as files are opened.
411
420
  """
412
421
  try:
413
422
  started = await server._proxy.ensure_started(server._init_params, file_uri)
@@ -415,7 +424,6 @@ async def _lazy_start_jdtls(file_uri: str) -> None:
415
424
  logger.info("jdtls proxy active — full Java language support enabled")
416
425
  await _register_jdtls_capabilities()
417
426
  await server._proxy.flush_queued_notifications()
418
- await _expand_workspace_background()
419
427
  except Exception:
420
428
  logger.warning("jdtls lazy start failed", exc_info=True)
421
429
 
@@ -415,7 +415,7 @@ class TestServerInternals:
415
415
  mock_reg.assert_not_called()
416
416
 
417
417
  async def test_lazy_start_jdtls_success(self, caplog: Any) -> None:
418
- """_lazy_start_jdtls logs success, flushes queue, and expands workspace."""
418
+ """_lazy_start_jdtls logs success and flushes queue (no eager expansion)."""
419
419
  import logging
420
420
  from unittest.mock import AsyncMock, MagicMock, patch
421
421
 
@@ -424,7 +424,6 @@ class TestServerInternals:
424
424
  from java_functional_lsp.server import server as srv
425
425
 
426
426
  mock_flush = AsyncMock()
427
- mock_expand = AsyncMock()
428
427
  old_flag = srv_mod._jdtls_capabilities_registered
429
428
  srv_mod._jdtls_capabilities_registered = False
430
429
  try:
@@ -432,7 +431,6 @@ class TestServerInternals:
432
431
  caplog.at_level(logging.INFO, logger="java_functional_lsp.server"),
433
432
  patch.object(srv._proxy, "ensure_started", AsyncMock(return_value=True)),
434
433
  patch.object(srv._proxy, "flush_queued_notifications", mock_flush),
435
- patch.object(srv._proxy, "expand_full_workspace", mock_expand),
436
434
  patch.object(srv, "feature", MagicMock(return_value=lambda fn: fn)),
437
435
  patch.object(srv, "client_register_capability_async", AsyncMock()),
438
436
  ):
@@ -441,7 +439,6 @@ class TestServerInternals:
441
439
  srv_mod._jdtls_capabilities_registered = old_flag
442
440
  assert any("jdtls proxy active" in r.getMessage() for r in caplog.records)
443
441
  mock_flush.assert_called_once()
444
- mock_expand.assert_called_once()
445
442
 
446
443
  async def test_lazy_start_jdtls_failure_logged(self, caplog: Any) -> None:
447
444
  """_lazy_start_jdtls logs warning on exception."""