java-functional-lsp 0.9.3__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.
Files changed (69) hide show
  1. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/PKG-INFO +1 -1
  2. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/pyproject.toml +1 -1
  3. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/__init__.py +1 -1
  4. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/proxy.py +22 -14
  5. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_proxy.py +39 -17
  6. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.claude-plugin/plugin.json +0 -0
  7. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.githooks/pre-commit +0 -0
  8. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.githooks/pre-push +0 -0
  9. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/CODEOWNERS +0 -0
  10. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  11. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  12. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/SECURITY.md +0 -0
  14. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/dependabot.yml +0 -0
  15. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/release-drafter.yml +0 -0
  16. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/workflows/publish.yml +0 -0
  17. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/workflows/release-drafter.yml +0 -0
  18. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/workflows/stale.yml +0 -0
  19. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/workflows/test.yml +0 -0
  20. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/workflows/update-homebrew.yml +0 -0
  21. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.github/workflows/vscode-ext.yml +0 -0
  22. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/.gitignore +0 -0
  23. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/CONTRIBUTING.md +0 -0
  24. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/LICENSE +0 -0
  25. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/README.md +0 -0
  26. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/SKILL.md +0 -0
  27. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/commands/lint-java.md +0 -0
  28. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/editors/intellij/README.md +0 -0
  29. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/editors/intellij/lsp4ij-template.json +0 -0
  30. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/editors/vscode/.vscodeignore +0 -0
  31. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/editors/vscode/README.md +0 -0
  32. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/editors/vscode/package-lock.json +0 -0
  33. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/editors/vscode/package.json +0 -0
  34. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/editors/vscode/src/extension.ts +0 -0
  35. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/editors/vscode/tsconfig.json +0 -0
  36. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/hooks/hooks.json +0 -0
  37. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/hooks/java_linter_reminder.py +0 -0
  38. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/scripts/ensure-lsp.sh +0 -0
  39. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/scripts/generate-formula.py +0 -0
  40. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/__main__.py +0 -0
  41. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/__init__.py +0 -0
  42. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/base.py +0 -0
  43. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
  44. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
  45. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
  46. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
  47. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
  48. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/cli.py +0 -0
  49. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/fixes.py +0 -0
  50. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/merkle.py +0 -0
  51. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/src/java_functional_lsp/server.py +0 -0
  52. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/__init__.py +0 -0
  53. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/conftest.py +0 -0
  54. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_base.py +0 -0
  55. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_cli.py +0 -0
  56. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_config.py +0 -0
  57. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_e2e.py +0 -0
  58. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_e2e_jdtls.py +0 -0
  59. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_exception_checker.py +0 -0
  60. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_fixes.py +0 -0
  61. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_functional_checker.py +0 -0
  62. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_merkle.py +0 -0
  63. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_merkle_proxy.py +0 -0
  64. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_mutation_checker.py +0 -0
  65. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_null_checker.py +0 -0
  66. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_server.py +0 -0
  67. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_spring_checker.py +0 -0
  68. {java_functional_lsp-0.9.3 → java_functional_lsp-0.9.4}/tests/test_suppress.py +0 -0
  69. {java_functional_lsp-0.9.3 → 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
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.3"
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" }
@@ -1,3 +1,3 @@
1
1
  """java-functional-lsp: A Java LSP server enforcing functional programming best practices."""
2
2
 
3
- __version__ = "0.9.3"
3
+ __version__ = "0.9.4"
@@ -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`` (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).
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
- current = initial_module.resolve().parent
467
- # Bail immediately if the initial module is outside the workspace.
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
- while True:
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
- return workspace_root
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)
@@ -924,8 +930,10 @@ class JdtlsProxy:
924
930
  Maven parent. This covers sibling modules (the common case for
925
931
  cross-module navigation) without ballooning the index.
926
932
 
927
- Falls back to the full IDE workspace root when no intermediate
928
- 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.
929
937
 
930
938
  Removes all individually-added module folders to avoid double-indexing
931
939
  with the newly added group root.
@@ -1131,17 +1131,24 @@ class TestLazyStart:
1131
1131
  assert event["added"][0]["uri"] == group.as_uri()
1132
1132
  assert event["added"][0]["uri"] != workspace_uri
1133
1133
 
1134
- async def test_expand_falls_back_to_workspace_root_when_no_group_pom(self, tmp_path: Path) -> None:
1135
- """Falls back to the IDE workspace root when no intermediate multi-module parent exists."""
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
+ """
1136
1143
  from unittest.mock import AsyncMock
1137
1144
 
1138
1145
  from java_functional_lsp.proxy import JdtlsProxy
1139
1146
 
1140
- # Flat layout: workspace/module — no intermediate pom.xml
1147
+ # Flat layout: workspace/module — no intermediate pom.xml between module and workspace
1141
1148
  workspace = tmp_path / "workspace"
1142
1149
  module = workspace / "module"
1143
1150
  module.mkdir(parents=True)
1144
- # workspace/pom.xml has <modules>, but there's nothing in between
1151
+ # workspace/pom.xml has <modules>, but the walk stops BEFORE reaching it
1145
1152
  (workspace / "pom.xml").write_text("<project><modules><module>module</module></modules></project>")
1146
1153
 
1147
1154
  module_uri = module.as_uri()
@@ -1155,9 +1162,9 @@ class TestLazyStart:
1155
1162
  proxy.send_notification = AsyncMock() # type: ignore[assignment]
1156
1163
  await proxy.expand_full_workspace()
1157
1164
 
1158
- call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
1159
- event = call_args[1]["event"]
1160
- assert event["added"][0]["uri"] == workspace_uri
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
1161
1168
 
1162
1169
  async def test_expand_full_workspace_sends_notification(self) -> None:
1163
1170
  from unittest.mock import AsyncMock
@@ -1172,28 +1179,43 @@ class TestLazyStart:
1172
1179
  proxy.send_notification.assert_called_once() # type: ignore[attr-defined]
1173
1180
  assert proxy._workspace_expanded is True
1174
1181
 
1175
- async def test_expand_full_workspace_removes_initial_module(self) -> None:
1176
- """All modules in _states (not just _initial_module_uri) are removed on expansion."""
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."""
1177
1184
  from unittest.mock import AsyncMock
1178
1185
 
1179
1186
  from java_functional_lsp.proxy import JdtlsProxy
1180
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
+
1181
1203
  proxy = JdtlsProxy()
1182
1204
  proxy._available = True
1183
- proxy._original_root_uri = "file:///workspace/monorepo"
1184
- proxy._initial_module_uri = "file:///workspace/monorepo/module-a"
1185
- # Simulate that module-a was mark_added during start() and module-b via add_module_if_new
1186
- proxy.modules.mark_added("file:///workspace/monorepo/module-a")
1187
- proxy.modules.mark_added("file:///workspace/monorepo/module-b")
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)
1188
1210
  proxy.send_notification = AsyncMock() # type: ignore[assignment]
1189
1211
  await proxy.expand_full_workspace()
1190
1212
  proxy.send_notification.assert_called_once() # type: ignore[attr-defined]
1191
1213
  call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
1192
1214
  event = call_args[1]["event"]
1193
1215
  removed_uris = {e["uri"] for e in event["removed"]}
1194
- assert "file:///workspace/monorepo/module-a" in removed_uris
1195
- assert "file:///workspace/monorepo/module-b" in removed_uris
1196
- assert event["added"][0]["uri"] == "file:///workspace/monorepo"
1216
+ assert module_a_uri in removed_uris
1217
+ assert module_b_uri in removed_uris
1218
+ assert event["added"][0]["uri"] == group_uri
1197
1219
 
1198
1220
  async def test_expand_full_workspace_noop_when_not_available(self) -> None:
1199
1221
  from unittest.mock import AsyncMock