java-functional-lsp 0.9.9__tar.gz → 0.9.11__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.9 → java_functional_lsp-0.9.11}/PKG-INFO +1 -1
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/pyproject.toml +1 -1
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/__init__.py +1 -1
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/proxy.py +176 -65
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/server.py +1 -1
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_proxy.py +165 -4
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_server.py +34 -13
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.claude-plugin/plugin.json +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.githooks/pre-commit +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.githooks/pre-push +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/CODEOWNERS +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/SECURITY.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/dependabot.yml +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/workflows/publish.yml +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/workflows/release-drafter.yml +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/workflows/stale.yml +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/workflows/test.yml +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/workflows/update-homebrew.yml +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/workflows/vscode-ext.yml +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.gitignore +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/CONTRIBUTING.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/LICENSE +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/README.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/SKILL.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/commands/lint-java.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/editors/intellij/README.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/editors/intellij/lsp4ij-template.json +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/editors/vscode/.vscodeignore +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/editors/vscode/README.md +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/editors/vscode/package-lock.json +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/editors/vscode/package.json +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/editors/vscode/src/extension.ts +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/editors/vscode/tsconfig.json +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/hooks/hooks.json +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/hooks/java_linter_reminder.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/scripts/ensure-lsp.sh +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/scripts/generate-formula.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/__main__.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/analyzers/__init__.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/analyzers/base.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/cli.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/fixes.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/merkle.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/__init__.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/conftest.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_base.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_cli.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_config.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_e2e.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_e2e_jdtls.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_exception_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_fixes.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_functional_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_merkle.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_merkle_proxy.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_mutation_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_null_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_spring_checker.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/tests/test_suppress.py +0 -0
- {java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/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.11
|
|
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.11"
|
|
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" }
|
|
@@ -28,6 +28,28 @@ _INITIALIZE_TIMEOUT = 120.0 # seconds — module-scoped init can still be slow
|
|
|
28
28
|
_START_RETRY_COOLDOWN = 300.0 # seconds — retry jdtls startup after transient failure
|
|
29
29
|
DEFAULT_JVM_MAX_HEAP = "4g"
|
|
30
30
|
_STDERR_LINE_MAX = 1000
|
|
31
|
+
_MAX_EXPANDED_GROUPS: int = 5 # hard cap on concurrent Maven group workspace folders (prevents jdtls OOM)
|
|
32
|
+
|
|
33
|
+
# Default jdtls initialization settings. Sent via initializationOptions.settings
|
|
34
|
+
# so they apply BEFORE the Maven import scan (didChangeConfiguration is too late).
|
|
35
|
+
# Users can override via .java-functional-lsp.json: {"jdtls": {"settings": {...}}}
|
|
36
|
+
_DEFAULT_JDTLS_SETTINGS: dict[str, Any] = {
|
|
37
|
+
"java": {
|
|
38
|
+
"import": {
|
|
39
|
+
"maven": {"enabled": True},
|
|
40
|
+
"gradle": {"enabled": False},
|
|
41
|
+
"exclusions": [
|
|
42
|
+
"**/node_modules/**",
|
|
43
|
+
"**/.metadata/**",
|
|
44
|
+
"**/archetype-resources/**",
|
|
45
|
+
"**/META-INF/maven/**",
|
|
46
|
+
"**/target/**",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
"configuration": {"updateBuildConfiguration": "automatic"},
|
|
50
|
+
"maven": {"downloadSources": False},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
31
53
|
|
|
32
54
|
#: Minimum Java major version required by jdtls 1.57+. Anything below this
|
|
33
55
|
#: is rejected by the jdtls Python launcher with ``"jdtls requires at least
|
|
@@ -374,6 +396,10 @@ class ModuleRegistry:
|
|
|
374
396
|
if event is not None:
|
|
375
397
|
event.set()
|
|
376
398
|
|
|
399
|
+
def uris(self) -> list[str]:
|
|
400
|
+
"""Return all tracked module URIs (any state)."""
|
|
401
|
+
return list(self._states)
|
|
402
|
+
|
|
377
403
|
def clear(self) -> None:
|
|
378
404
|
"""Reset all state. Used by tests."""
|
|
379
405
|
self._states.clear()
|
|
@@ -445,6 +471,7 @@ def _version_key(name: str) -> tuple[int, ...]:
|
|
|
445
471
|
return tuple(parts)
|
|
446
472
|
|
|
447
473
|
|
|
474
|
+
@lru_cache(maxsize=64)
|
|
448
475
|
def _find_maven_group_root(initial_module: Path, workspace_root: Path) -> Path:
|
|
449
476
|
"""Return the tightest Maven multi-module parent of *initial_module*.
|
|
450
477
|
|
|
@@ -552,25 +579,43 @@ def _wipe_data_dir(data_dir: Path) -> None:
|
|
|
552
579
|
logger.info("jdtls: wiped data-dir %s after init timeout", _redact_path(str(data_dir)))
|
|
553
580
|
|
|
554
581
|
|
|
555
|
-
def
|
|
556
|
-
"""
|
|
582
|
+
def _compute_cache_marker() -> str:
|
|
583
|
+
"""Compute a stable cache marker from jdtls version path + Java version.
|
|
557
584
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
585
|
+
Our Python package version changes (pip upgrades) do NOT affect the jdtls
|
|
586
|
+
Eclipse workspace index — only jdtls binary upgrades or Java version
|
|
587
|
+
changes do. Resolves through symlinks so Homebrew Cellar version changes
|
|
588
|
+
(e.g. ``/opt/homebrew/Cellar/jdtls/1.58.0/...``) are detected even when
|
|
589
|
+
the wrapper script at ``/opt/homebrew/bin/jdtls`` doesn't change.
|
|
561
590
|
"""
|
|
562
|
-
|
|
591
|
+
parts: list[str] = []
|
|
592
|
+
jdtls_path = shutil.which("jdtls")
|
|
593
|
+
if jdtls_path:
|
|
594
|
+
resolved = str(Path(jdtls_path).resolve())
|
|
595
|
+
parts.append(hashlib.sha256(resolved.encode()).hexdigest()[:12])
|
|
596
|
+
parts.append(str(_MIN_JDTLS_JAVA_MAJOR))
|
|
597
|
+
return "|".join(parts)
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def _clear_cache_on_version_change(cache_root: Path) -> None:
|
|
601
|
+
"""Clear jdtls data cache when the jdtls installation changes.
|
|
563
602
|
|
|
603
|
+
Stores a ``.version`` marker in the cache root. The marker is derived
|
|
604
|
+
from the resolved jdtls binary path + Java version requirement — NOT
|
|
605
|
+
from our Python ``__version__``. This preserves warm caches across
|
|
606
|
+
java-functional-lsp upgrades (which don't affect the Eclipse index).
|
|
607
|
+
"""
|
|
608
|
+
current_marker = _compute_cache_marker()
|
|
564
609
|
marker = cache_root / ".version"
|
|
565
610
|
try:
|
|
566
|
-
if marker.exists() and marker.read_text().strip() ==
|
|
611
|
+
if marker.exists() and marker.read_text().strip() == current_marker:
|
|
567
612
|
return
|
|
568
|
-
#
|
|
613
|
+
# jdtls or Java version changed, or first run — clear stale caches.
|
|
569
614
|
if cache_root.exists():
|
|
570
615
|
shutil.rmtree(cache_root)
|
|
571
|
-
logger.info("jdtls: cleared stale cache (
|
|
616
|
+
logger.info("jdtls: cleared stale cache (marker changed to %s)", current_marker)
|
|
572
617
|
cache_root.mkdir(parents=True, exist_ok=True)
|
|
573
|
-
marker.write_text(
|
|
618
|
+
marker.write_text(current_marker)
|
|
574
619
|
except OSError as e:
|
|
575
620
|
logger.warning("jdtls: failed to manage cache version marker: %s", e)
|
|
576
621
|
|
|
@@ -616,6 +661,48 @@ def _find_lombok_jar(config: Mapping[str, Any] | None = None) -> str | None:
|
|
|
616
661
|
return None
|
|
617
662
|
|
|
618
663
|
|
|
664
|
+
def _build_effective_params(
|
|
665
|
+
init_params: dict[str, Any],
|
|
666
|
+
module_root_uri: str | None,
|
|
667
|
+
original_root: str,
|
|
668
|
+
config: Mapping[str, Any] | None,
|
|
669
|
+
) -> dict[str, Any]:
|
|
670
|
+
"""Build the ``initialize`` request params for jdtls.
|
|
671
|
+
|
|
672
|
+
Deep-copies *init_params*, scopes to *module_root_uri* if provided,
|
|
673
|
+
injects workspaceFolders capability, and injects Maven/Gradle settings
|
|
674
|
+
from *config* (or defaults) into ``initializationOptions.settings``.
|
|
675
|
+
"""
|
|
676
|
+
from pygls.uris import to_fs_path
|
|
677
|
+
|
|
678
|
+
effective_params = copy.deepcopy(init_params)
|
|
679
|
+
if module_root_uri:
|
|
680
|
+
effective_params["rootUri"] = module_root_uri
|
|
681
|
+
effective_params["rootPath"] = to_fs_path(module_root_uri)
|
|
682
|
+
logger.info(
|
|
683
|
+
"jdtls: scoping to module %s (full root: %s)",
|
|
684
|
+
_redact_path(module_root_uri),
|
|
685
|
+
_redact_path(original_root),
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
# Inject workspaceFolders capability for later expansion.
|
|
689
|
+
caps = effective_params.setdefault("capabilities", {})
|
|
690
|
+
ws = caps.setdefault("workspace", {})
|
|
691
|
+
ws["workspaceFolders"] = True
|
|
692
|
+
|
|
693
|
+
# Inject jdtls Maven/Gradle settings into initializationOptions.
|
|
694
|
+
# Must be in initializationOptions (not didChangeConfiguration) so they
|
|
695
|
+
# apply before the Maven import scan starts.
|
|
696
|
+
jdtls_settings = copy.deepcopy(_DEFAULT_JDTLS_SETTINGS)
|
|
697
|
+
if config and "jdtls" in config and "settings" in config["jdtls"]:
|
|
698
|
+
jdtls_settings = copy.deepcopy(config["jdtls"]["settings"])
|
|
699
|
+
init_opts = effective_params.setdefault("initializationOptions", {})
|
|
700
|
+
init_opts["settings"] = jdtls_settings
|
|
701
|
+
logger.debug("jdtls: initializationOptions.settings = %s", jdtls_settings)
|
|
702
|
+
|
|
703
|
+
return effective_params
|
|
704
|
+
|
|
705
|
+
|
|
619
706
|
class JdtlsProxy:
|
|
620
707
|
"""Manages a jdtls subprocess and provides async request/notification forwarding."""
|
|
621
708
|
|
|
@@ -641,7 +728,7 @@ class JdtlsProxy:
|
|
|
641
728
|
self._initial_module_uri: str | None = None
|
|
642
729
|
self.modules = ModuleRegistry()
|
|
643
730
|
self.has_lombok = False
|
|
644
|
-
self.
|
|
731
|
+
self._expanded_groups: set[str] = set()
|
|
645
732
|
# Merkle snapshot diff: module_uri → (diff | None, current_snapshot)
|
|
646
733
|
# Populated by _kick_module_diff (background task on add_module_if_new).
|
|
647
734
|
# Consumed by server._apply_module_diff after module reaches READY.
|
|
@@ -718,23 +805,7 @@ class JdtlsProxy:
|
|
|
718
805
|
data_dir = cache_root / data_hash
|
|
719
806
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
720
807
|
|
|
721
|
-
|
|
722
|
-
effective_params = copy.deepcopy(init_params)
|
|
723
|
-
if module_root_uri:
|
|
724
|
-
effective_params["rootUri"] = module_root_uri
|
|
725
|
-
from pygls.uris import to_fs_path
|
|
726
|
-
|
|
727
|
-
effective_params["rootPath"] = to_fs_path(module_root_uri)
|
|
728
|
-
logger.info(
|
|
729
|
-
"jdtls: scoping to module %s (full root: %s)",
|
|
730
|
-
_redact_path(module_root_uri),
|
|
731
|
-
_redact_path(original_root),
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
# Inject workspaceFolders capability for later expansion.
|
|
735
|
-
caps = effective_params.setdefault("capabilities", {})
|
|
736
|
-
ws = caps.setdefault("workspace", {})
|
|
737
|
-
ws["workspaceFolders"] = True
|
|
808
|
+
effective_params = _build_effective_params(init_params, module_root_uri, original_root, config)
|
|
738
809
|
|
|
739
810
|
jdtls_cmd: list[str] = [
|
|
740
811
|
jdtls_path,
|
|
@@ -887,33 +958,76 @@ class JdtlsProxy:
|
|
|
887
958
|
pass # result storage is _kick_module_diff's responsibility
|
|
888
959
|
|
|
889
960
|
async def add_module_if_new(self, file_uri: str) -> str | None:
|
|
890
|
-
"""Add the module
|
|
961
|
+
"""Add the module's Maven group to jdtls if not already expanded.
|
|
962
|
+
|
|
963
|
+
When the user navigates to a file in a new Maven group, we add the
|
|
964
|
+
group root (not the individual module) as a workspace folder. This
|
|
965
|
+
gives jdtls the full Maven classpath for cross-module navigation
|
|
966
|
+
within that group.
|
|
891
967
|
|
|
892
|
-
Returns the module URI if a new
|
|
893
|
-
or ``None`` if already
|
|
894
|
-
used with ``modules.wait_until_ready()`` for adaptive waiting.
|
|
968
|
+
Returns the module URI if a new group was expanded (for use with
|
|
969
|
+
``modules.wait_until_ready()``), or ``None`` if already covered.
|
|
895
970
|
|
|
896
|
-
|
|
897
|
-
duplicate add calls from concurrent coroutines.
|
|
971
|
+
Capped at ``_MAX_EXPANDED_GROUPS`` to prevent jdtls OOM.
|
|
898
972
|
"""
|
|
899
|
-
if not self._available
|
|
973
|
+
if not self._available:
|
|
900
974
|
return None
|
|
975
|
+
from pygls.uris import from_fs_path, to_fs_path
|
|
976
|
+
|
|
901
977
|
module_uri = _resolve_module_uri(file_uri)
|
|
902
978
|
if module_uri is None or self.modules.was_added(module_uri):
|
|
903
979
|
return None
|
|
980
|
+
|
|
981
|
+
# Resolve the Maven group root for this module.
|
|
982
|
+
module_path = to_fs_path(module_uri)
|
|
983
|
+
workspace_path = to_fs_path(self._original_root_uri) if self._original_root_uri else None
|
|
984
|
+
if module_path and workspace_path:
|
|
985
|
+
group_root = _find_maven_group_root(Path(module_path), Path(workspace_path))
|
|
986
|
+
group_uri = from_fs_path(str(group_root)) or module_uri
|
|
987
|
+
else:
|
|
988
|
+
group_uri = module_uri
|
|
989
|
+
|
|
990
|
+
# Already expanded this group — module is covered, nothing to do.
|
|
991
|
+
if group_uri in self._expanded_groups:
|
|
992
|
+
return None
|
|
993
|
+
|
|
994
|
+
# Hard cap: refuse expansion beyond the limit.
|
|
995
|
+
if len(self._expanded_groups) >= _MAX_EXPANDED_GROUPS:
|
|
996
|
+
logger.warning(
|
|
997
|
+
"jdtls: refusing to expand group for %s — %d groups already expanded (max %d). Restart IDE to reset.",
|
|
998
|
+
Path(module_path or module_uri).name,
|
|
999
|
+
len(self._expanded_groups),
|
|
1000
|
+
_MAX_EXPANDED_GROUPS,
|
|
1001
|
+
)
|
|
1002
|
+
return None
|
|
1003
|
+
|
|
1004
|
+
# Remove individually-added child modules within this group to avoid
|
|
1005
|
+
# double-indexing once the group root covers them. Must run BEFORE
|
|
1006
|
+
# mark_added so we don't remove the module we're about to add.
|
|
1007
|
+
group_path = Path(to_fs_path(group_uri) or group_uri)
|
|
1008
|
+
removed: list[dict[str, str]] = []
|
|
1009
|
+
for uri in self.modules.uris():
|
|
1010
|
+
if uri == group_uri:
|
|
1011
|
+
continue
|
|
1012
|
+
p = Path(to_fs_path(uri) or uri)
|
|
1013
|
+
if p == group_path or p.is_relative_to(group_path):
|
|
1014
|
+
removed.append({"uri": uri, "name": p.name})
|
|
1015
|
+
|
|
904
1016
|
# Mark ADDED before await — atomic in asyncio, prevents duplicate sends.
|
|
905
1017
|
self.modules.mark_added(module_uri)
|
|
906
|
-
|
|
1018
|
+
self.modules.mark_added(group_uri)
|
|
1019
|
+
self._expanded_groups.add(group_uri)
|
|
907
1020
|
|
|
908
|
-
|
|
909
|
-
|
|
1021
|
+
group_name = Path(to_fs_path(group_uri) or group_uri).name
|
|
1022
|
+
logger.info(
|
|
1023
|
+
"jdtls: expanding to group %s (group %d/%d)", group_name, len(self._expanded_groups), _MAX_EXPANDED_GROUPS
|
|
1024
|
+
)
|
|
910
1025
|
await self.send_notification(
|
|
911
1026
|
_WORKSPACE_DID_CHANGE_FOLDERS,
|
|
912
|
-
{"event": {"added": [{"uri":
|
|
1027
|
+
{"event": {"added": [{"uri": group_uri, "name": group_name}], "removed": removed}},
|
|
913
1028
|
)
|
|
914
|
-
|
|
915
|
-
#
|
|
916
|
-
# readiness timeout (which starts ticking at mark_added above).
|
|
1029
|
+
|
|
1030
|
+
# Fire-and-forget: compute snapshot diff in background.
|
|
917
1031
|
diff_task = asyncio.create_task(self._kick_module_diff(module_uri))
|
|
918
1032
|
self._proxy_bg_tasks.add(diff_task)
|
|
919
1033
|
diff_task.add_done_callback(self._proxy_bg_tasks.discard)
|
|
@@ -921,24 +1035,16 @@ class JdtlsProxy:
|
|
|
921
1035
|
return module_uri
|
|
922
1036
|
|
|
923
1037
|
async def expand_full_workspace(self) -> None:
|
|
924
|
-
"""Expand jdtls workspace to the
|
|
1038
|
+
"""Expand jdtls workspace to the initial module's Maven group root.
|
|
925
1039
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
``pom.xml`` declares ``<modules>`` — i.e. the tightest multi-module
|
|
930
|
-
Maven parent. This covers sibling modules (the common case for
|
|
931
|
-
cross-module navigation) without ballooning the index.
|
|
932
|
-
|
|
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.
|
|
1040
|
+
Called once during lazy start. Subsequent groups are expanded
|
|
1041
|
+
on-demand by ``add_module_if_new()`` as the user navigates to
|
|
1042
|
+
files in other Maven groups.
|
|
937
1043
|
|
|
938
1044
|
Removes all individually-added module folders to avoid double-indexing
|
|
939
1045
|
with the newly added group root.
|
|
940
1046
|
"""
|
|
941
|
-
if
|
|
1047
|
+
if not self._available or not self._original_root_uri:
|
|
942
1048
|
return
|
|
943
1049
|
from pygls.uris import from_fs_path, to_fs_path
|
|
944
1050
|
|
|
@@ -952,26 +1058,31 @@ class JdtlsProxy:
|
|
|
952
1058
|
|
|
953
1059
|
root_path = group_root_path
|
|
954
1060
|
root_uri = from_fs_path(root_path) or self._original_root_uri
|
|
955
|
-
|
|
956
|
-
|
|
1061
|
+
|
|
1062
|
+
# Already expanded (e.g. initial module IS the group root, or called twice).
|
|
1063
|
+
if root_uri in self._expanded_groups or self.modules.was_added(root_uri):
|
|
1064
|
+
self._expanded_groups.add(root_uri)
|
|
957
1065
|
return
|
|
958
1066
|
self.modules.mark_added(root_uri)
|
|
959
1067
|
|
|
960
|
-
# Remove
|
|
961
|
-
#
|
|
962
|
-
#
|
|
1068
|
+
# Remove individually-added modules that are children of the new root.
|
|
1069
|
+
# This covers the initial scoped module and any modules added by
|
|
1070
|
+
# add_module_if_new during the ensure_started window.
|
|
1071
|
+
root_path_obj = Path(root_path)
|
|
963
1072
|
removed: list[dict[str, str]] = []
|
|
964
|
-
for uri in
|
|
965
|
-
if uri
|
|
966
|
-
|
|
967
|
-
|
|
1073
|
+
for uri in self.modules.uris():
|
|
1074
|
+
if uri == root_uri:
|
|
1075
|
+
continue
|
|
1076
|
+
p = Path(to_fs_path(uri) or uri)
|
|
1077
|
+
if p == root_path_obj or p.is_relative_to(root_path_obj):
|
|
1078
|
+
removed.append({"uri": uri, "name": p.name})
|
|
968
1079
|
|
|
969
1080
|
logger.info("jdtls: expanding workspace to module group %s", _redact_path(root_path))
|
|
970
1081
|
await self.send_notification(
|
|
971
1082
|
_WORKSPACE_DID_CHANGE_FOLDERS,
|
|
972
1083
|
{"event": {"added": [{"uri": root_uri, "name": Path(root_path).name}], "removed": removed}},
|
|
973
1084
|
)
|
|
974
|
-
self.
|
|
1085
|
+
self._expanded_groups.add(root_uri)
|
|
975
1086
|
|
|
976
1087
|
async def stop(self) -> None:
|
|
977
1088
|
"""Shutdown jdtls subprocess gracefully."""
|
|
@@ -724,7 +724,7 @@ async def _ensure_module_and_forward(
|
|
|
724
724
|
return result
|
|
725
725
|
|
|
726
726
|
# Cold path: add module if unknown, then wait for ready.
|
|
727
|
-
logger.info("jdtls: %s [cold] module=%s
|
|
727
|
+
logger.info("jdtls: %s [cold] module=%s groups=%d", method, short_mod, len(proxy._expanded_groups))
|
|
728
728
|
new_module_uri = await proxy.add_module_if_new(file_uri)
|
|
729
729
|
|
|
730
730
|
serialized = _serialize_params(params)
|
|
@@ -983,6 +983,13 @@ class TestFindModuleRoot:
|
|
|
983
983
|
class TestLazyStart:
|
|
984
984
|
"""Tests for lazy-start proxy features."""
|
|
985
985
|
|
|
986
|
+
@pytest.fixture(autouse=True)
|
|
987
|
+
def _clear_group_root_cache(self) -> None: # type: ignore[override]
|
|
988
|
+
"""Clear _find_maven_group_root lru_cache between tests."""
|
|
989
|
+
from java_functional_lsp.proxy import _find_maven_group_root
|
|
990
|
+
|
|
991
|
+
_find_maven_group_root.cache_clear()
|
|
992
|
+
|
|
986
993
|
def test_check_available_true(self) -> None:
|
|
987
994
|
from java_functional_lsp.proxy import JdtlsProxy
|
|
988
995
|
|
|
@@ -1164,7 +1171,7 @@ class TestLazyStart:
|
|
|
1164
1171
|
|
|
1165
1172
|
# No notification — module was already added and is already the tightest scope
|
|
1166
1173
|
proxy.send_notification.assert_not_called() # type: ignore[attr-defined]
|
|
1167
|
-
assert proxy.
|
|
1174
|
+
assert len(proxy._expanded_groups) > 0
|
|
1168
1175
|
|
|
1169
1176
|
async def test_expand_full_workspace_sends_notification(self) -> None:
|
|
1170
1177
|
from unittest.mock import AsyncMock
|
|
@@ -1177,7 +1184,7 @@ class TestLazyStart:
|
|
|
1177
1184
|
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1178
1185
|
await proxy.expand_full_workspace()
|
|
1179
1186
|
proxy.send_notification.assert_called_once() # type: ignore[attr-defined]
|
|
1180
|
-
assert proxy.
|
|
1187
|
+
assert len(proxy._expanded_groups) > 0
|
|
1181
1188
|
|
|
1182
1189
|
async def test_expand_full_workspace_removes_initial_module(self, tmp_path: Path) -> None:
|
|
1183
1190
|
"""All modules in _states are removed when expanding to an intermediate group root."""
|
|
@@ -1227,7 +1234,7 @@ class TestLazyStart:
|
|
|
1227
1234
|
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1228
1235
|
await proxy.expand_full_workspace()
|
|
1229
1236
|
proxy.send_notification.assert_not_called() # type: ignore[attr-defined]
|
|
1230
|
-
assert proxy.
|
|
1237
|
+
assert len(proxy._expanded_groups) == 0
|
|
1231
1238
|
|
|
1232
1239
|
async def test_expand_full_workspace_noop_when_already_added(self) -> None:
|
|
1233
1240
|
from unittest.mock import AsyncMock
|
|
@@ -1241,7 +1248,109 @@ class TestLazyStart:
|
|
|
1241
1248
|
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1242
1249
|
await proxy.expand_full_workspace()
|
|
1243
1250
|
proxy.send_notification.assert_not_called() # type: ignore[attr-defined]
|
|
1244
|
-
assert proxy.
|
|
1251
|
+
assert len(proxy._expanded_groups) > 0
|
|
1252
|
+
|
|
1253
|
+
async def test_add_module_cross_group_expands_new_group(self, tmp_path: Path) -> None:
|
|
1254
|
+
"""Navigating to a file in a different Maven group expands that group."""
|
|
1255
|
+
from unittest.mock import AsyncMock
|
|
1256
|
+
|
|
1257
|
+
from java_functional_lsp.proxy import JdtlsProxy
|
|
1258
|
+
|
|
1259
|
+
# Layout: monorepo/group-a/mod-a, monorepo/group-b/mod-b
|
|
1260
|
+
monorepo = tmp_path / "monorepo"
|
|
1261
|
+
group_a = monorepo / "group-a"
|
|
1262
|
+
mod_a = group_a / "mod-a"
|
|
1263
|
+
group_b = monorepo / "group-b"
|
|
1264
|
+
mod_b = group_b / "mod-b"
|
|
1265
|
+
for d in (mod_a, mod_b):
|
|
1266
|
+
d.mkdir(parents=True)
|
|
1267
|
+
(d / "pom.xml").write_text("<project/>")
|
|
1268
|
+
(group_a / "pom.xml").write_text("<project><modules><module>mod-a</module></modules></project>")
|
|
1269
|
+
(group_b / "pom.xml").write_text("<project><modules><module>mod-b</module></modules></project>")
|
|
1270
|
+
|
|
1271
|
+
proxy = JdtlsProxy()
|
|
1272
|
+
proxy._available = True
|
|
1273
|
+
proxy._original_root_uri = monorepo.as_uri()
|
|
1274
|
+
proxy._initial_module_uri = mod_a.as_uri()
|
|
1275
|
+
proxy._expanded_groups.add(group_a.as_uri()) # simulate initial expansion done
|
|
1276
|
+
proxy.modules.mark_added(mod_a.as_uri())
|
|
1277
|
+
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1278
|
+
|
|
1279
|
+
file_uri = (mod_b / "src" / "Foo.java").as_uri()
|
|
1280
|
+
# Create the file so _resolve_module_uri can find mod-b's pom.xml
|
|
1281
|
+
(mod_b / "src").mkdir()
|
|
1282
|
+
(mod_b / "src" / "Foo.java").touch()
|
|
1283
|
+
|
|
1284
|
+
result = await proxy.add_module_if_new(file_uri)
|
|
1285
|
+
assert result is not None # module_uri returned for wait_until_ready
|
|
1286
|
+
proxy.send_notification.assert_called_once() # type: ignore[attr-defined]
|
|
1287
|
+
call_args = proxy.send_notification.call_args[0] # type: ignore[attr-defined]
|
|
1288
|
+
event = call_args[1]["event"]
|
|
1289
|
+
assert event["added"][0]["uri"] == group_b.as_uri()
|
|
1290
|
+
assert event["removed"] == [] # no child modules to remove from new group
|
|
1291
|
+
assert group_b.as_uri() in proxy._expanded_groups
|
|
1292
|
+
assert len(proxy._expanded_groups) == 2
|
|
1293
|
+
|
|
1294
|
+
async def test_add_module_within_expanded_group_is_noop(self, tmp_path: Path) -> None:
|
|
1295
|
+
"""A file within an already-expanded group returns None."""
|
|
1296
|
+
from unittest.mock import AsyncMock
|
|
1297
|
+
|
|
1298
|
+
from java_functional_lsp.proxy import JdtlsProxy
|
|
1299
|
+
|
|
1300
|
+
monorepo = tmp_path / "monorepo"
|
|
1301
|
+
group = monorepo / "group"
|
|
1302
|
+
mod_a = group / "mod-a"
|
|
1303
|
+
mod_b = group / "mod-b"
|
|
1304
|
+
for d in (mod_a, mod_b):
|
|
1305
|
+
d.mkdir(parents=True)
|
|
1306
|
+
(d / "pom.xml").write_text("<project/>")
|
|
1307
|
+
(group / "pom.xml").write_text(
|
|
1308
|
+
"<project><modules><module>mod-a</module><module>mod-b</module></modules></project>"
|
|
1309
|
+
)
|
|
1310
|
+
|
|
1311
|
+
proxy = JdtlsProxy()
|
|
1312
|
+
proxy._available = True
|
|
1313
|
+
proxy._original_root_uri = monorepo.as_uri()
|
|
1314
|
+
proxy._expanded_groups.add(group.as_uri())
|
|
1315
|
+
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1316
|
+
|
|
1317
|
+
file_uri = (mod_b / "src" / "Bar.java").as_uri()
|
|
1318
|
+
(mod_b / "src").mkdir()
|
|
1319
|
+
(mod_b / "src" / "Bar.java").touch()
|
|
1320
|
+
|
|
1321
|
+
result = await proxy.add_module_if_new(file_uri)
|
|
1322
|
+
assert result is None
|
|
1323
|
+
proxy.send_notification.assert_not_called() # type: ignore[attr-defined]
|
|
1324
|
+
|
|
1325
|
+
async def test_add_module_at_cap_refuses_expansion(self, tmp_path: Path) -> None:
|
|
1326
|
+
"""Expansion is refused when _MAX_EXPANDED_GROUPS is reached."""
|
|
1327
|
+
from unittest.mock import AsyncMock
|
|
1328
|
+
|
|
1329
|
+
from java_functional_lsp.proxy import _MAX_EXPANDED_GROUPS, JdtlsProxy
|
|
1330
|
+
|
|
1331
|
+
monorepo = tmp_path / "monorepo"
|
|
1332
|
+
new_group = monorepo / "new-group"
|
|
1333
|
+
mod = new_group / "mod"
|
|
1334
|
+
mod.mkdir(parents=True)
|
|
1335
|
+
(mod / "pom.xml").write_text("<project/>")
|
|
1336
|
+
(new_group / "pom.xml").write_text("<project><modules><module>mod</module></modules></project>")
|
|
1337
|
+
|
|
1338
|
+
proxy = JdtlsProxy()
|
|
1339
|
+
proxy._available = True
|
|
1340
|
+
proxy._original_root_uri = monorepo.as_uri()
|
|
1341
|
+
# Fill up to the cap with dummy groups
|
|
1342
|
+
for i in range(_MAX_EXPANDED_GROUPS):
|
|
1343
|
+
proxy._expanded_groups.add(f"file:///fake/group-{i}")
|
|
1344
|
+
proxy.send_notification = AsyncMock() # type: ignore[assignment]
|
|
1345
|
+
|
|
1346
|
+
file_uri = (mod / "src" / "Baz.java").as_uri()
|
|
1347
|
+
(mod / "src").mkdir()
|
|
1348
|
+
(mod / "src" / "Baz.java").touch()
|
|
1349
|
+
|
|
1350
|
+
result = await proxy.add_module_if_new(file_uri)
|
|
1351
|
+
assert result is None
|
|
1352
|
+
proxy.send_notification.assert_not_called() # type: ignore[attr-defined]
|
|
1353
|
+
assert len(proxy._expanded_groups) == _MAX_EXPANDED_GROUPS
|
|
1245
1354
|
|
|
1246
1355
|
async def test_ensure_started_no_build_file(self) -> None:
|
|
1247
1356
|
"""ensure_started with no build file should pass module_root_uri=None."""
|
|
@@ -1308,6 +1417,58 @@ class TestLazyStart:
|
|
|
1308
1417
|
# ---------------------------------------------------------------------------
|
|
1309
1418
|
|
|
1310
1419
|
|
|
1420
|
+
class TestBuildEffectiveParams:
|
|
1421
|
+
"""Tests for _build_effective_params."""
|
|
1422
|
+
|
|
1423
|
+
def test_defaults_injected_when_no_config(self) -> None:
|
|
1424
|
+
from java_functional_lsp.proxy import _build_effective_params
|
|
1425
|
+
|
|
1426
|
+
result = _build_effective_params({"rootUri": "file:///ws"}, None, "file:///ws", None)
|
|
1427
|
+
settings = result["initializationOptions"]["settings"]
|
|
1428
|
+
assert settings["java"]["import"]["maven"]["enabled"] is True
|
|
1429
|
+
assert settings["java"]["import"]["gradle"]["enabled"] is False
|
|
1430
|
+
assert "**/target/**" in settings["java"]["import"]["exclusions"]
|
|
1431
|
+
|
|
1432
|
+
def test_config_override_replaces_defaults(self) -> None:
|
|
1433
|
+
from java_functional_lsp.proxy import _build_effective_params
|
|
1434
|
+
|
|
1435
|
+
custom = {"java": {"custom": "value"}}
|
|
1436
|
+
config = {"jdtls": {"settings": custom}}
|
|
1437
|
+
result = _build_effective_params({"rootUri": "file:///ws"}, None, "file:///ws", config)
|
|
1438
|
+
settings = result["initializationOptions"]["settings"]
|
|
1439
|
+
assert settings == {"java": {"custom": "value"}}
|
|
1440
|
+
assert "import" not in settings.get("java", {})
|
|
1441
|
+
|
|
1442
|
+
def test_does_not_mutate_original_init_params(self) -> None:
|
|
1443
|
+
from java_functional_lsp.proxy import _build_effective_params
|
|
1444
|
+
|
|
1445
|
+
original = {"rootUri": "file:///ws", "capabilities": {}}
|
|
1446
|
+
_build_effective_params(original, "file:///mod", "file:///ws", None)
|
|
1447
|
+
assert "initializationOptions" not in original
|
|
1448
|
+
assert original["rootUri"] == "file:///ws"
|
|
1449
|
+
|
|
1450
|
+
def test_module_root_uri_scopes_root(self) -> None:
|
|
1451
|
+
from java_functional_lsp.proxy import _build_effective_params
|
|
1452
|
+
|
|
1453
|
+
result = _build_effective_params({"rootUri": "file:///ws"}, "file:///mod", "file:///ws", None)
|
|
1454
|
+
assert result["rootUri"] == "file:///mod"
|
|
1455
|
+
|
|
1456
|
+
def test_workspace_folders_capability_always_set(self) -> None:
|
|
1457
|
+
from java_functional_lsp.proxy import _build_effective_params
|
|
1458
|
+
|
|
1459
|
+
result = _build_effective_params({"rootUri": "file:///ws"}, None, "file:///ws", None)
|
|
1460
|
+
assert result["capabilities"]["workspace"]["workspaceFolders"] is True
|
|
1461
|
+
|
|
1462
|
+
def test_deepcopy_prevents_default_mutation(self) -> None:
|
|
1463
|
+
from java_functional_lsp.proxy import _DEFAULT_JDTLS_SETTINGS, _build_effective_params
|
|
1464
|
+
|
|
1465
|
+
result = _build_effective_params({"rootUri": "file:///ws"}, None, "file:///ws", None)
|
|
1466
|
+
# Mutate the returned settings
|
|
1467
|
+
result["initializationOptions"]["settings"]["java"]["import"]["exclusions"].append("MUTATED")
|
|
1468
|
+
# Module-level defaults must be unchanged
|
|
1469
|
+
assert "MUTATED" not in _DEFAULT_JDTLS_SETTINGS["java"]["import"]["exclusions"]
|
|
1470
|
+
|
|
1471
|
+
|
|
1311
1472
|
class TestWipeDataDir:
|
|
1312
1473
|
def test_wipe_removes_directory(self, tmp_path: Path) -> None:
|
|
1313
1474
|
"""Directory is removed and info is logged."""
|
|
@@ -1548,48 +1548,69 @@ class TestFindLombokJar:
|
|
|
1548
1548
|
|
|
1549
1549
|
|
|
1550
1550
|
class TestCacheClear:
|
|
1551
|
-
"""Tests for _clear_cache_on_version_change()."""
|
|
1551
|
+
"""Tests for _clear_cache_on_version_change() and _compute_cache_marker()."""
|
|
1552
1552
|
|
|
1553
1553
|
def test_clears_on_version_mismatch(self, tmp_path: Any) -> None:
|
|
1554
|
-
from java_functional_lsp.proxy import _clear_cache_on_version_change
|
|
1554
|
+
from java_functional_lsp.proxy import _clear_cache_on_version_change, _compute_cache_marker
|
|
1555
1555
|
|
|
1556
1556
|
cache = tmp_path / "jdtls-data"
|
|
1557
1557
|
cache.mkdir()
|
|
1558
1558
|
stale_dir = cache / "abc123"
|
|
1559
1559
|
stale_dir.mkdir()
|
|
1560
|
-
(cache / ".version").write_text("
|
|
1560
|
+
(cache / ".version").write_text("old-stale-marker")
|
|
1561
1561
|
|
|
1562
1562
|
_clear_cache_on_version_change(cache)
|
|
1563
1563
|
|
|
1564
1564
|
assert not stale_dir.exists(), "Stale data-dir should be cleared"
|
|
1565
1565
|
assert cache.exists(), "Cache root should be recreated"
|
|
1566
|
-
|
|
1566
|
+
assert (cache / ".version").read_text() == _compute_cache_marker()
|
|
1567
1567
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
def test_keeps_cache_on_same_version(self, tmp_path: Any) -> None:
|
|
1571
|
-
from java_functional_lsp import __version__
|
|
1572
|
-
from java_functional_lsp.proxy import _clear_cache_on_version_change
|
|
1568
|
+
def test_keeps_cache_on_same_marker(self, tmp_path: Any) -> None:
|
|
1569
|
+
from java_functional_lsp.proxy import _clear_cache_on_version_change, _compute_cache_marker
|
|
1573
1570
|
|
|
1574
1571
|
cache = tmp_path / "jdtls-data"
|
|
1575
1572
|
cache.mkdir()
|
|
1576
1573
|
existing_dir = cache / "abc123"
|
|
1577
1574
|
existing_dir.mkdir()
|
|
1578
|
-
(cache / ".version").write_text(
|
|
1575
|
+
(cache / ".version").write_text(_compute_cache_marker())
|
|
1579
1576
|
|
|
1580
1577
|
_clear_cache_on_version_change(cache)
|
|
1581
1578
|
|
|
1582
1579
|
assert existing_dir.exists(), "Existing data-dir should be preserved"
|
|
1583
1580
|
|
|
1584
1581
|
def test_creates_marker_on_first_run(self, tmp_path: Any) -> None:
|
|
1585
|
-
from java_functional_lsp.proxy import _clear_cache_on_version_change
|
|
1582
|
+
from java_functional_lsp.proxy import _clear_cache_on_version_change, _compute_cache_marker
|
|
1586
1583
|
|
|
1587
1584
|
cache = tmp_path / "jdtls-data"
|
|
1588
1585
|
_clear_cache_on_version_change(cache)
|
|
1589
1586
|
|
|
1590
|
-
|
|
1587
|
+
assert (cache / ".version").read_text() == _compute_cache_marker()
|
|
1588
|
+
|
|
1589
|
+
def test_python_version_bump_preserves_cache(self) -> None:
|
|
1590
|
+
"""Our __version__ is NOT in the cache marker — pip upgrades don't wipe."""
|
|
1591
|
+
from unittest.mock import patch
|
|
1592
|
+
|
|
1593
|
+
from java_functional_lsp.proxy import _compute_cache_marker
|
|
1594
|
+
|
|
1595
|
+
marker_before = _compute_cache_marker()
|
|
1596
|
+
with patch("java_functional_lsp.__version__", "99.99.99"):
|
|
1597
|
+
marker_after = _compute_cache_marker()
|
|
1598
|
+
assert marker_before == marker_after, "Changing __version__ must not change the cache marker"
|
|
1599
|
+
|
|
1600
|
+
def test_different_jdtls_paths_produce_different_markers(self) -> None:
|
|
1601
|
+
"""Different resolved jdtls paths produce different cache markers."""
|
|
1602
|
+
from pathlib import Path
|
|
1603
|
+
from unittest.mock import patch
|
|
1604
|
+
|
|
1605
|
+
from java_functional_lsp.proxy import _compute_cache_marker
|
|
1591
1606
|
|
|
1592
|
-
|
|
1607
|
+
with patch("java_functional_lsp.proxy.shutil.which", return_value="/opt/jdtls/1.57/bin/jdtls"):
|
|
1608
|
+
with patch("java_functional_lsp.proxy.Path.resolve", return_value=Path("/opt/jdtls/1.57/bin/jdtls")):
|
|
1609
|
+
marker_a = _compute_cache_marker()
|
|
1610
|
+
with patch("java_functional_lsp.proxy.shutil.which", return_value="/opt/jdtls/1.58/bin/jdtls"):
|
|
1611
|
+
with patch("java_functional_lsp.proxy.Path.resolve", return_value=Path("/opt/jdtls/1.58/bin/jdtls")):
|
|
1612
|
+
marker_b = _compute_cache_marker()
|
|
1613
|
+
assert marker_a != marker_b
|
|
1593
1614
|
|
|
1594
1615
|
|
|
1595
1616
|
# Subprocess-based tests — zero mocks, real LSP transport
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.github/ISSUE_TEMPLATE/bug-report.md
RENAMED
|
File without changes
|
{java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.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.9 → java_functional_lsp-0.9.11}/.github/workflows/release-drafter.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/.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.9 → java_functional_lsp-0.9.11}/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
|
{java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/src/java_functional_lsp/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.9.9 → java_functional_lsp-0.9.11}/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
|