java-functional-lsp 0.9.7__tar.gz → 0.9.9__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 (69) hide show
  1. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/PKG-INFO +1 -1
  2. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/pyproject.toml +1 -1
  3. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/__init__.py +1 -1
  4. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/server.py +39 -11
  5. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_server.py +49 -1
  6. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.claude-plugin/plugin.json +0 -0
  7. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.githooks/pre-commit +0 -0
  8. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.githooks/pre-push +0 -0
  9. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/CODEOWNERS +0 -0
  10. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  11. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  12. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/SECURITY.md +0 -0
  14. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/dependabot.yml +0 -0
  15. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/release-drafter.yml +0 -0
  16. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/workflows/publish.yml +0 -0
  17. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/workflows/release-drafter.yml +0 -0
  18. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/workflows/stale.yml +0 -0
  19. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/workflows/test.yml +0 -0
  20. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/workflows/update-homebrew.yml +0 -0
  21. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.github/workflows/vscode-ext.yml +0 -0
  22. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/.gitignore +0 -0
  23. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/CONTRIBUTING.md +0 -0
  24. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/LICENSE +0 -0
  25. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/README.md +0 -0
  26. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/SKILL.md +0 -0
  27. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/commands/lint-java.md +0 -0
  28. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/editors/intellij/README.md +0 -0
  29. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/editors/intellij/lsp4ij-template.json +0 -0
  30. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/editors/vscode/.vscodeignore +0 -0
  31. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/editors/vscode/README.md +0 -0
  32. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/editors/vscode/package-lock.json +0 -0
  33. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/editors/vscode/package.json +0 -0
  34. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/editors/vscode/src/extension.ts +0 -0
  35. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/editors/vscode/tsconfig.json +0 -0
  36. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/hooks/hooks.json +0 -0
  37. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/hooks/java_linter_reminder.py +0 -0
  38. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/scripts/ensure-lsp.sh +0 -0
  39. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/scripts/generate-formula.py +0 -0
  40. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/__main__.py +0 -0
  41. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/analyzers/__init__.py +0 -0
  42. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/analyzers/base.py +0 -0
  43. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
  44. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
  45. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
  46. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
  47. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
  48. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/cli.py +0 -0
  49. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/fixes.py +0 -0
  50. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/merkle.py +0 -0
  51. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/src/java_functional_lsp/proxy.py +0 -0
  52. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/__init__.py +0 -0
  53. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/conftest.py +0 -0
  54. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_base.py +0 -0
  55. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_cli.py +0 -0
  56. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_config.py +0 -0
  57. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_e2e.py +0 -0
  58. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_e2e_jdtls.py +0 -0
  59. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_exception_checker.py +0 -0
  60. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_fixes.py +0 -0
  61. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_functional_checker.py +0 -0
  62. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_merkle.py +0 -0
  63. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_merkle_proxy.py +0 -0
  64. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_mutation_checker.py +0 -0
  65. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_null_checker.py +0 -0
  66. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_proxy.py +0 -0
  67. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_spring_checker.py +0 -0
  68. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/tests/test_suppress.py +0 -0
  69. {java_functional_lsp-0.9.7 → java_functional_lsp-0.9.9}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-functional-lsp
3
- Version: 0.9.7
3
+ Version: 0.9.9
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"
7
+ version = "0.9.9"
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.9.7"
3
+ __version__ = "0.9.9"
@@ -76,13 +76,18 @@ class JavaFunctionalLspServer(LanguageServer):
76
76
  self._skip_jdtls_registration: bool = False
77
77
  self._init_generation: int = 0
78
78
 
79
- def _on_jdtls_diagnostics(self, uri: str, _diagnostics: list[Any]) -> None:
79
+ def _on_jdtls_diagnostics(self, uri: str, diagnostics: list[Any]) -> None:
80
80
  """Called when jdtls publishes diagnostics — merge with custom and re-publish.
81
81
 
82
82
  Also marks the file's module as READY, since receiving diagnostics from
83
83
  jdtls is a reliable signal that the module has been indexed (more reliable
84
84
  than a first non-None response which may be semantically empty).
85
85
 
86
+ Exception: if any diagnostic carries ``_JDTLS_NON_PROJECT_CODE`` (code 16),
87
+ jdtls has opened the file but hasn't resolved its Maven project yet — only
88
+ syntax errors are available. We skip the READY transition and wait for the
89
+ follow-up publish (after Maven import completes) that arrives without code 16.
90
+
86
91
  When a module transitions to READY, fires ``_apply_module_diff`` to
87
92
  notify jdtls about any externally changed files (e.g., after git pull).
88
93
  """
@@ -90,12 +95,18 @@ class JavaFunctionalLspServer(LanguageServer):
90
95
  return
91
96
  module_uri = _resolve_module_uri(uri)
92
97
  if module_uri:
93
- already_ready = self._proxy.modules.is_ready(module_uri)
94
- self._proxy.modules.mark_ready(module_uri)
95
- if not already_ready:
96
- # First READY transition only — subsequent diagnostics for the same
97
- # module are no-ops to avoid spawning O(files) redundant tasks.
98
- _fire_and_forget(_apply_module_diff(self._proxy, module_uri))
98
+ is_non_project = any(
99
+ str(d.get("code", "")) == _JDTLS_NON_PROJECT_CODE for d in diagnostics if isinstance(d, dict)
100
+ )
101
+ if is_non_project:
102
+ logger.info("jdtls: skipping READY for %s (non-project file, Maven import pending)", Path(uri).name)
103
+ else:
104
+ already_ready = self._proxy.modules.is_ready(module_uri)
105
+ self._proxy.modules.mark_ready(module_uri)
106
+ if not already_ready:
107
+ # First READY transition only — subsequent diagnostics for the same
108
+ # module are no-ops to avoid spawning O(files) redundant tasks.
109
+ _fire_and_forget(_apply_module_diff(self._proxy, module_uri))
99
110
  try:
100
111
  _analyze_and_publish(uri)
101
112
  except FileNotFoundError:
@@ -419,7 +430,7 @@ def on_initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
419
430
 
420
431
 
421
432
  @server.feature(lsp.INITIALIZED)
422
- async def on_initialized(_params: lsp.InitializedParams) -> None:
433
+ async def on_initialized(_: lsp.InitializedParams) -> None:
423
434
  """Check jdtls availability; actual start deferred to first didOpen."""
424
435
  logger.info(
425
436
  "java-functional-lsp initialized (rules: %s)",
@@ -672,6 +683,12 @@ _LIGHTWEIGHT_METHODS: frozenset[str] = frozenset(
672
683
  }
673
684
  )
674
685
 
686
+ # jdtls diagnostic code for files it opened but hasn't resolved into a Maven project yet.
687
+ # When this appears in a publishDiagnostics batch, jdtls has syntax info only — no classpath,
688
+ # no cross-file resolution. We must NOT mark the module READY yet; wait for a re-publish
689
+ # without code 16 (after Maven project import finishes).
690
+ _JDTLS_NON_PROJECT_CODE: str = "16"
691
+
675
692
 
676
693
  async def _ensure_module_and_forward(
677
694
  method: str,
@@ -696,12 +713,18 @@ async def _ensure_module_and_forward(
696
713
  return None
697
714
 
698
715
  module_uri = _resolve_module_uri(file_uri)
716
+ short_mod = Path(to_fs_path(module_uri) or module_uri).name if module_uri else "<none>"
699
717
 
700
718
  # Hot path: module already confirmed working.
701
719
  if module_uri and proxy.modules.is_ready(module_uri):
702
- return await proxy.send_request(method, _serialize_params(params))
720
+ logger.info("jdtls: %s [hot] module=%s", method, short_mod)
721
+ result = await proxy.send_request(method, _serialize_params(params))
722
+ if result is None:
723
+ logger.info("jdtls: %s [hot] → null (jdtls has no result for this file_uri)", method)
724
+ return result
703
725
 
704
726
  # Cold path: add module if unknown, then wait for ready.
727
+ logger.info("jdtls: %s [cold] module=%s expanded=%s", method, short_mod, proxy._workspace_expanded)
705
728
  new_module_uri = await proxy.add_module_if_new(file_uri)
706
729
 
707
730
  serialized = _serialize_params(params)
@@ -719,11 +742,13 @@ async def _ensure_module_and_forward(
719
742
  # If a concurrent request succeeds, Event.set() wakes us early.
720
743
  wait_uri = new_module_uri or module_uri
721
744
  if wait_uri and not proxy.modules.is_ready(wait_uri):
745
+ logger.info("jdtls: %s [cold] waiting for module %s to become ready", method, short_mod)
722
746
  await proxy.modules.wait_until_ready(wait_uri, timeout=5.0)
723
747
  # Always retry once after waiting — even on timeout the module may be ready.
724
748
  result = await proxy.send_request(method, serialized)
725
749
  if result is not None and can_mark_ready and module_uri:
726
750
  proxy.modules.mark_ready(module_uri)
751
+ logger.info("jdtls: %s [cold] retry → %s", method, "result" if result is not None else "null")
727
752
  return result
728
753
 
729
754
 
@@ -803,8 +828,11 @@ async def _on_incoming_calls(
803
828
  if result is None:
804
829
  return None
805
830
  try:
806
- return [_converter.structure(c, lsp.CallHierarchyIncomingCall) for c in result]
807
- except Exception:
831
+ structured = [_converter.structure(c, lsp.CallHierarchyIncomingCall) for c in result]
832
+ logger.info("jdtls: incomingCalls → %d callers", len(structured))
833
+ return structured
834
+ except Exception as e:
835
+ logger.warning("jdtls: incomingCalls structuring failed: %s (raw=%r)", e, result)
808
836
  return None
809
837
 
810
838
 
@@ -290,6 +290,54 @@ class TestServerInternals:
290
290
  finally:
291
291
  server.workspace.remove_text_document(uri)
292
292
 
293
+ def test_on_jdtls_diagnostics_non_project_skips_ready(self) -> None:
294
+ """A publishDiagnostics batch with code-16 must NOT mark the module READY."""
295
+ from unittest.mock import patch
296
+
297
+ from java_functional_lsp.proxy import ModuleState
298
+ from java_functional_lsp.server import server
299
+
300
+ server._proxy.modules.clear()
301
+ uri = "file:///mod/Foo.java"
302
+ non_project_diag = {
303
+ "code": "16",
304
+ "message": "Foo.java is a non-project file, only syntax errors are reported",
305
+ "source": "Java",
306
+ "severity": 2,
307
+ "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 1}},
308
+ }
309
+ with (
310
+ patch("java_functional_lsp.server._resolve_module_uri", return_value="file:///mod"),
311
+ patch("java_functional_lsp.server._analyze_and_publish"),
312
+ ):
313
+ server._on_jdtls_diagnostics(uri, [non_project_diag])
314
+ assert server._proxy.modules.get_state("file:///mod") != ModuleState.READY
315
+
316
+ def test_on_jdtls_diagnostics_project_file_marks_ready(self) -> None:
317
+ """A publishDiagnostics batch without code-16 MUST mark the module READY."""
318
+ from unittest.mock import patch
319
+
320
+ from java_functional_lsp.proxy import ModuleState
321
+ from java_functional_lsp.server import server
322
+
323
+ server._proxy.modules.clear()
324
+ uri = "file:///mod/Pr.java"
325
+ real_diag = {
326
+ "code": "268435844",
327
+ "message": "Some unused import",
328
+ "source": "Java",
329
+ "severity": 2,
330
+ "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 1}},
331
+ }
332
+ with (
333
+ patch("java_functional_lsp.server._resolve_module_uri", return_value="file:///mod"),
334
+ patch("java_functional_lsp.server._analyze_and_publish"),
335
+ patch("java_functional_lsp.server._fire_and_forget"),
336
+ ):
337
+ server._on_jdtls_diagnostics(uri, [real_diag])
338
+ assert server._proxy.modules.get_state("file:///mod") == ModuleState.READY
339
+ server._proxy.modules.clear()
340
+
293
341
  def test_init_capabilities_exclude_jdtls_features(self) -> None:
294
342
  """Static capabilities must NOT include hover/definition/references/completion/documentSymbol.
295
343
 
@@ -541,7 +589,7 @@ class TestServerInternals:
541
589
  mock_add = AsyncMock(return_value="file:///mod")
542
590
  mock_send = AsyncMock(side_effect=[None, {"result": "ok"}])
543
591
 
544
- async def mock_wait(uri: str, timeout: float = 30.0) -> bool: # pyright: ignore[reportUnusedParameter]
592
+ async def mock_wait(uri: str, timeout: float = 30.0) -> bool: # type: ignore[reportUnusedParameter]
545
593
  srv._proxy.modules.mark_ready(uri)
546
594
  return True
547
595