java-functional-lsp 0.9.1__tar.gz → 0.9.2__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.1 → java_functional_lsp-0.9.2}/PKG-INFO +1 -1
  2. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/pyproject.toml +1 -1
  3. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/__init__.py +1 -1
  4. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/proxy.py +59 -5
  5. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_proxy.py +60 -0
  6. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.claude-plugin/plugin.json +0 -0
  7. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.githooks/pre-commit +0 -0
  8. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.githooks/pre-push +0 -0
  9. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/CODEOWNERS +0 -0
  10. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  11. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  12. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/SECURITY.md +0 -0
  14. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/dependabot.yml +0 -0
  15. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/release-drafter.yml +0 -0
  16. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/workflows/publish.yml +0 -0
  17. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/workflows/release-drafter.yml +0 -0
  18. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/workflows/stale.yml +0 -0
  19. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/workflows/test.yml +0 -0
  20. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/workflows/update-homebrew.yml +0 -0
  21. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.github/workflows/vscode-ext.yml +0 -0
  22. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/.gitignore +0 -0
  23. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/CONTRIBUTING.md +0 -0
  24. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/LICENSE +0 -0
  25. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/README.md +0 -0
  26. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/SKILL.md +0 -0
  27. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/commands/lint-java.md +0 -0
  28. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/editors/intellij/README.md +0 -0
  29. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/editors/intellij/lsp4ij-template.json +0 -0
  30. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/editors/vscode/.vscodeignore +0 -0
  31. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/editors/vscode/README.md +0 -0
  32. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/editors/vscode/package-lock.json +0 -0
  33. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/editors/vscode/package.json +0 -0
  34. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/editors/vscode/src/extension.ts +0 -0
  35. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/editors/vscode/tsconfig.json +0 -0
  36. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/hooks/hooks.json +0 -0
  37. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/hooks/java_linter_reminder.py +0 -0
  38. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/scripts/ensure-lsp.sh +0 -0
  39. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/scripts/generate-formula.py +0 -0
  40. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/__main__.py +0 -0
  41. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/analyzers/__init__.py +0 -0
  42. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/analyzers/base.py +0 -0
  43. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
  44. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
  45. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
  46. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
  47. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
  48. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/cli.py +0 -0
  49. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/fixes.py +0 -0
  50. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/merkle.py +0 -0
  51. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/src/java_functional_lsp/server.py +0 -0
  52. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/__init__.py +0 -0
  53. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/conftest.py +0 -0
  54. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_base.py +0 -0
  55. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_cli.py +0 -0
  56. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_config.py +0 -0
  57. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_e2e.py +0 -0
  58. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_e2e_jdtls.py +0 -0
  59. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_exception_checker.py +0 -0
  60. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_fixes.py +0 -0
  61. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_functional_checker.py +0 -0
  62. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_merkle.py +0 -0
  63. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_merkle_proxy.py +0 -0
  64. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_mutation_checker.py +0 -0
  65. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_null_checker.py +0 -0
  66. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_server.py +0 -0
  67. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_spring_checker.py +0 -0
  68. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/tests/test_suppress.py +0 -0
  69. {java_functional_lsp-0.9.1 → java_functional_lsp-0.9.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-functional-lsp
3
- Version: 0.9.1
3
+ Version: 0.9.2
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.1"
7
+ version = "0.9.2"
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.1"
3
+ __version__ = "0.9.2"
@@ -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*.
@@ -862,16 +898,34 @@ class JdtlsProxy:
862
898
  return module_uri
863
899
 
864
900
  async def expand_full_workspace(self) -> None:
865
- """Expand jdtls workspace to the full monorepo root (background task).
901
+ """Expand jdtls workspace to the nearest Maven module-group root.
902
+
903
+ Instead of expanding all the way to the IDE workspace root (which may
904
+ contain hundreds of Maven modules and cause jdtls OOM), this walks up
905
+ from the initial module to find the *nearest* ancestor directory whose
906
+ ``pom.xml`` declares ``<modules>`` — i.e. the tightest multi-module
907
+ Maven parent. This covers sibling modules (the common case for
908
+ cross-module navigation) without ballooning the index.
909
+
910
+ Falls back to the full IDE workspace root when no intermediate
911
+ multi-module parent is found.
866
912
 
867
- Removes the initial module-scoped folder and adds the full root to
868
- avoid double-indexing.
913
+ Removes all individually-added module folders to avoid double-indexing
914
+ with the newly added group root.
869
915
  """
870
916
  if self._workspace_expanded or not self._available or not self._original_root_uri:
871
917
  return
872
918
  from pygls.uris import from_fs_path, to_fs_path
873
919
 
874
- root_path = to_fs_path(self._original_root_uri) or self._original_root_uri
920
+ workspace_path = to_fs_path(self._original_root_uri) or self._original_root_uri
921
+
922
+ # Find the tightest multi-module Maven parent of the initial module.
923
+ group_root_path: str = workspace_path
924
+ if self._initial_module_uri:
925
+ initial_path = to_fs_path(self._initial_module_uri) or self._initial_module_uri
926
+ group_root_path = str(_find_maven_group_root(Path(initial_path), Path(workspace_path)))
927
+
928
+ root_path = group_root_path
875
929
  root_uri = from_fs_path(root_path) or self._original_root_uri
876
930
  if self.modules.was_added(root_uri):
877
931
  self._workspace_expanded = True
@@ -887,7 +941,7 @@ class JdtlsProxy:
887
941
  p = to_fs_path(uri) or uri
888
942
  removed.append({"uri": uri, "name": Path(p).name})
889
943
 
890
- logger.info("jdtls: expanding to full workspace %s", _redact_path(root_path))
944
+ logger.info("jdtls: expanding workspace to module group %s", _redact_path(root_path))
891
945
  await self.send_notification(
892
946
  _WORKSPACE_DID_CHANGE_FOLDERS,
893
947
  {"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
 
@@ -1037,6 +1038,65 @@ class TestLazyStart:
1037
1038
  assert result2 is None # Already known
1038
1039
  assert proxy.send_notification.call_count == 1 # type: ignore[attr-defined]
1039
1040
 
1041
+ async def test_expand_uses_maven_group_root_not_workspace_root(self, tmp_path: Path) -> None:
1042
+ """expand_full_workspace expands to nearest multi-module Maven parent, not the full IDE root."""
1043
+ from unittest.mock import AsyncMock
1044
+
1045
+ from java_functional_lsp.proxy import JdtlsProxy
1046
+
1047
+ # Layout: workspace/group/module — group/pom.xml has <modules>
1048
+ workspace = tmp_path / "workspace"
1049
+ group = workspace / "group"
1050
+ module = group / "module"
1051
+ module.mkdir(parents=True)
1052
+ (group / "pom.xml").write_text("<project><modules><module>module</module></modules></project>")
1053
+ (workspace / "pom.xml").write_text("<project><modules><module>group</module></modules></project>")
1054
+
1055
+ module_uri = module.as_uri()
1056
+ workspace_uri = workspace.as_uri()
1057
+
1058
+ proxy = JdtlsProxy()
1059
+ proxy._available = True
1060
+ proxy._original_root_uri = workspace_uri
1061
+ proxy._initial_module_uri = module_uri
1062
+ proxy.modules.mark_added(module_uri)
1063
+ proxy.send_notification = AsyncMock() # type: ignore[assignment]
1064
+ await proxy.expand_full_workspace()
1065
+
1066
+ call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
1067
+ event = call_args[1]["event"]
1068
+ # Should expand to group/, NOT workspace/
1069
+ assert event["added"][0]["uri"] == group.as_uri()
1070
+ assert event["added"][0]["uri"] != workspace_uri
1071
+
1072
+ async def test_expand_falls_back_to_workspace_root_when_no_group_pom(self, tmp_path: Path) -> None:
1073
+ """Falls back to the IDE workspace root when no intermediate multi-module parent exists."""
1074
+ from unittest.mock import AsyncMock
1075
+
1076
+ from java_functional_lsp.proxy import JdtlsProxy
1077
+
1078
+ # Flat layout: workspace/module — no intermediate pom.xml
1079
+ workspace = tmp_path / "workspace"
1080
+ module = workspace / "module"
1081
+ module.mkdir(parents=True)
1082
+ # workspace/pom.xml has <modules>, but there's nothing in between
1083
+ (workspace / "pom.xml").write_text("<project><modules><module>module</module></modules></project>")
1084
+
1085
+ module_uri = module.as_uri()
1086
+ workspace_uri = workspace.as_uri()
1087
+
1088
+ proxy = JdtlsProxy()
1089
+ proxy._available = True
1090
+ proxy._original_root_uri = workspace_uri
1091
+ proxy._initial_module_uri = module_uri
1092
+ proxy.modules.mark_added(module_uri)
1093
+ proxy.send_notification = AsyncMock() # type: ignore[assignment]
1094
+ await proxy.expand_full_workspace()
1095
+
1096
+ call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
1097
+ event = call_args[1]["event"]
1098
+ assert event["added"][0]["uri"] == workspace_uri
1099
+
1040
1100
  async def test_expand_full_workspace_sends_notification(self) -> None:
1041
1101
  from unittest.mock import AsyncMock
1042
1102