java-functional-lsp 0.7.8__tar.gz → 0.8.0__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.7.8 → java_functional_lsp-0.8.0}/PKG-INFO +33 -1
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/README.md +32 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/pyproject.toml +1 -1
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/__init__.py +1 -1
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/proxy.py +121 -20
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/server.py +59 -7
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_proxy.py +2 -2
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_server.py +216 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/uv.lock +1 -1
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.claude-plugin/plugin.json +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.githooks/pre-commit +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.githooks/pre-push +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/CODEOWNERS +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/SECURITY.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/dependabot.yml +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/release-drafter.yml +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/workflows/publish.yml +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/workflows/release-drafter.yml +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/workflows/stale.yml +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/workflows/test.yml +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/workflows/update-homebrew.yml +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/workflows/vscode-ext.yml +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.gitignore +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/CONTRIBUTING.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/LICENSE +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/SKILL.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/commands/lint-java.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/editors/intellij/README.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/editors/intellij/lsp4ij-template.json +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/editors/vscode/.vscodeignore +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/editors/vscode/README.md +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/editors/vscode/package-lock.json +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/editors/vscode/package.json +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/editors/vscode/src/extension.ts +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/editors/vscode/tsconfig.json +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/hooks/hooks.json +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/hooks/java_linter_reminder.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/scripts/ensure-lsp.sh +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/scripts/generate-formula.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/__main__.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/analyzers/__init__.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/analyzers/base.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/cli.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/src/java_functional_lsp/fixes.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/__init__.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/conftest.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_base.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_cli.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_config.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_e2e.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_e2e_jdtls.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_exception_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_fixes.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_functional_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_mutation_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_null_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_spring_checker.py +0 -0
- {java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/tests/test_suppress.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: java-functional-lsp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
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
|
|
@@ -230,6 +230,7 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
|
|
|
230
230
|
**Options:**
|
|
231
231
|
- `excludes` — glob patterns for files/directories to skip entirely (supports `**` for multi-segment wildcards)
|
|
232
232
|
- `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
|
|
233
|
+
- `suppressJdtlsPatterns` — list of regex patterns to suppress jdtls diagnostics (see below)
|
|
233
234
|
|
|
234
235
|
**Spring-aware behavior:**
|
|
235
236
|
- `throw-statement`, `catch-rethrow`, and `try-catch-to-monadic` are automatically suppressed inside `@Bean` methods
|
|
@@ -253,6 +254,37 @@ public String legacyMethod() { ... }
|
|
|
253
254
|
|
|
254
255
|
Works on classes, methods, constructors, fields, and local variables. Suppression applies to the annotated scope — a class-level annotation suppresses all methods within it.
|
|
255
256
|
|
|
257
|
+
### Lombok support
|
|
258
|
+
|
|
259
|
+
Projects using [Lombok](https://projectlombok.org/) need the Lombok Java agent for jdtls to process `@Builder`, `@Value`, `@Data`, `@Slf4j`, and other annotations. Without it, jdtls reports false "method undefined" errors for generated methods.
|
|
260
|
+
|
|
261
|
+
The server auto-discovers `lombok.jar` from these locations (first match wins):
|
|
262
|
+
|
|
263
|
+
1. **Project config** — add to `.java-functional-lsp.json`:
|
|
264
|
+
```json
|
|
265
|
+
{ "lombok": "/path/to/lombok.jar" }
|
|
266
|
+
```
|
|
267
|
+
2. **Environment variable** — `LOMBOK_JAR=/path/to/lombok.jar`
|
|
268
|
+
3. **Maven cache** — auto-discovered from `~/.m2/repository/org/projectlombok/lombok/`
|
|
269
|
+
4. **Dedicated directory** — `~/.jdtls-libs/lombok.jar`
|
|
270
|
+
|
|
271
|
+
If Lombok is used in your project but the jar isn't found, the server logs a warning. The jdtls cache is automatically cleared on version upgrade, so adding Lombok support takes effect immediately after `brew upgrade`.
|
|
272
|
+
|
|
273
|
+
### Suppressing jdtls diagnostics
|
|
274
|
+
|
|
275
|
+
For project-specific jdtls false positives (e.g., annotation processor methods, MapStruct mappers), use `suppressJdtlsPatterns` to add custom regex filters:
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"suppressJdtlsPatterns": [
|
|
280
|
+
"The method \\w+Mapper\\(\\) is undefined",
|
|
281
|
+
"cannot be resolved to a type"
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Each entry is a regex matched against jdtls diagnostic messages. Invalid patterns are skipped with a warning.
|
|
287
|
+
|
|
256
288
|
## Code actions (quick fixes)
|
|
257
289
|
|
|
258
290
|
The server provides LSP code actions (`textDocument/codeAction`) that automatically refactor code. When your editor shows a diagnostic with a lightbulb icon, clicking it applies the fix:
|
|
@@ -202,6 +202,7 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
|
|
|
202
202
|
**Options:**
|
|
203
203
|
- `excludes` — glob patterns for files/directories to skip entirely (supports `**` for multi-segment wildcards)
|
|
204
204
|
- `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
|
|
205
|
+
- `suppressJdtlsPatterns` — list of regex patterns to suppress jdtls diagnostics (see below)
|
|
205
206
|
|
|
206
207
|
**Spring-aware behavior:**
|
|
207
208
|
- `throw-statement`, `catch-rethrow`, and `try-catch-to-monadic` are automatically suppressed inside `@Bean` methods
|
|
@@ -225,6 +226,37 @@ public String legacyMethod() { ... }
|
|
|
225
226
|
|
|
226
227
|
Works on classes, methods, constructors, fields, and local variables. Suppression applies to the annotated scope — a class-level annotation suppresses all methods within it.
|
|
227
228
|
|
|
229
|
+
### Lombok support
|
|
230
|
+
|
|
231
|
+
Projects using [Lombok](https://projectlombok.org/) need the Lombok Java agent for jdtls to process `@Builder`, `@Value`, `@Data`, `@Slf4j`, and other annotations. Without it, jdtls reports false "method undefined" errors for generated methods.
|
|
232
|
+
|
|
233
|
+
The server auto-discovers `lombok.jar` from these locations (first match wins):
|
|
234
|
+
|
|
235
|
+
1. **Project config** — add to `.java-functional-lsp.json`:
|
|
236
|
+
```json
|
|
237
|
+
{ "lombok": "/path/to/lombok.jar" }
|
|
238
|
+
```
|
|
239
|
+
2. **Environment variable** — `LOMBOK_JAR=/path/to/lombok.jar`
|
|
240
|
+
3. **Maven cache** — auto-discovered from `~/.m2/repository/org/projectlombok/lombok/`
|
|
241
|
+
4. **Dedicated directory** — `~/.jdtls-libs/lombok.jar`
|
|
242
|
+
|
|
243
|
+
If Lombok is used in your project but the jar isn't found, the server logs a warning. The jdtls cache is automatically cleared on version upgrade, so adding Lombok support takes effect immediately after `brew upgrade`.
|
|
244
|
+
|
|
245
|
+
### Suppressing jdtls diagnostics
|
|
246
|
+
|
|
247
|
+
For project-specific jdtls false positives (e.g., annotation processor methods, MapStruct mappers), use `suppressJdtlsPatterns` to add custom regex filters:
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"suppressJdtlsPatterns": [
|
|
252
|
+
"The method \\w+Mapper\\(\\) is undefined",
|
|
253
|
+
"cannot be resolved to a type"
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Each entry is a regex matched against jdtls diagnostic messages. Invalid patterns are skipped with a warning.
|
|
259
|
+
|
|
228
260
|
## Code actions (quick fixes)
|
|
229
261
|
|
|
230
262
|
The server provides LSP code actions (`textDocument/codeAction`) that automatically refactor code. When your editor shows a diagnostic with a lightbulb icon, clicking it applies the fix:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "java-functional-lsp"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.0"
|
|
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" }
|
|
@@ -433,6 +433,81 @@ def _resolve_module_uri(file_uri: str) -> str | None:
|
|
|
433
433
|
return module_uri or None
|
|
434
434
|
|
|
435
435
|
|
|
436
|
+
def _version_key(name: str) -> tuple[int, ...]:
|
|
437
|
+
"""Parse a version string into a tuple for semantic comparison."""
|
|
438
|
+
parts: list[int] = []
|
|
439
|
+
for segment in re.split(r"[.\-]", name):
|
|
440
|
+
try:
|
|
441
|
+
parts.append(int(segment))
|
|
442
|
+
except ValueError:
|
|
443
|
+
break
|
|
444
|
+
return tuple(parts)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _clear_cache_on_version_change(cache_root: Path) -> None:
|
|
448
|
+
"""Clear jdtls data cache when the server version changes.
|
|
449
|
+
|
|
450
|
+
Stores a ``.version`` marker in the cache root. On version mismatch
|
|
451
|
+
(e.g., Lombok agent added, jdtls config changed), wipes the entire
|
|
452
|
+
cache so jdtls re-indexes from scratch with the current configuration.
|
|
453
|
+
"""
|
|
454
|
+
from . import __version__
|
|
455
|
+
|
|
456
|
+
marker = cache_root / ".version"
|
|
457
|
+
try:
|
|
458
|
+
if marker.exists() and marker.read_text().strip() == __version__:
|
|
459
|
+
return
|
|
460
|
+
# Version changed or first run — clear stale caches.
|
|
461
|
+
if cache_root.exists():
|
|
462
|
+
shutil.rmtree(cache_root)
|
|
463
|
+
logger.info("jdtls: cleared stale cache (version changed to %s)", __version__)
|
|
464
|
+
cache_root.mkdir(parents=True, exist_ok=True)
|
|
465
|
+
marker.write_text(__version__)
|
|
466
|
+
except OSError as e:
|
|
467
|
+
logger.warning("jdtls: failed to manage cache version marker: %s", e)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _find_lombok_jar(config: Mapping[str, Any] | None = None) -> str | None:
|
|
471
|
+
"""Find lombok.jar from configurable and auto-discovered locations.
|
|
472
|
+
|
|
473
|
+
Search order (first match wins):
|
|
474
|
+
1. Project config (.java-functional-lsp.json): ``{"lombok": "/path/to/lombok.jar"}``
|
|
475
|
+
2. Environment variable: ``LOMBOK_JAR``
|
|
476
|
+
3. Maven cache: ``~/.m2/repository/org/projectlombok/lombok/*/lombok-*.jar``
|
|
477
|
+
4. Dedicated directory: ``~/.jdtls-libs/lombok.jar``
|
|
478
|
+
"""
|
|
479
|
+
# 1. Project config
|
|
480
|
+
if config and config.get("lombok"):
|
|
481
|
+
p = Path(config["lombok"]).expanduser()
|
|
482
|
+
if p.is_file():
|
|
483
|
+
return str(p)
|
|
484
|
+
logger.warning("Lombok path from config does not exist: %s", _redact_path(str(p)))
|
|
485
|
+
|
|
486
|
+
# 2. Environment variable
|
|
487
|
+
env_path = os.environ.get("LOMBOK_JAR")
|
|
488
|
+
if env_path:
|
|
489
|
+
p = Path(env_path).expanduser()
|
|
490
|
+
if p.is_file():
|
|
491
|
+
return str(p)
|
|
492
|
+
logger.warning("LOMBOK_JAR does not exist: %s", _redact_path(env_path))
|
|
493
|
+
|
|
494
|
+
# 3. Maven cache (latest version, semantic sort)
|
|
495
|
+
m2_lombok = Path.home() / ".m2" / "repository" / "org" / "projectlombok" / "lombok"
|
|
496
|
+
if m2_lombok.is_dir():
|
|
497
|
+
version_dirs = [d for d in m2_lombok.iterdir() if d.is_dir()]
|
|
498
|
+
for version_dir in sorted(version_dirs, key=lambda d: _version_key(d.name), reverse=True):
|
|
499
|
+
jar = version_dir / f"lombok-{version_dir.name}.jar"
|
|
500
|
+
if jar.is_file():
|
|
501
|
+
return str(jar)
|
|
502
|
+
|
|
503
|
+
# 4. Dedicated directory
|
|
504
|
+
fallback = Path.home() / ".jdtls-libs" / "lombok.jar"
|
|
505
|
+
if fallback.is_file():
|
|
506
|
+
return str(fallback)
|
|
507
|
+
|
|
508
|
+
return None
|
|
509
|
+
|
|
510
|
+
|
|
436
511
|
class JdtlsProxy:
|
|
437
512
|
"""Manages a jdtls subprocess and provides async request/notification forwarding."""
|
|
438
513
|
|
|
@@ -457,6 +532,7 @@ class JdtlsProxy:
|
|
|
457
532
|
self._original_root_uri: str | None = None
|
|
458
533
|
self._initial_module_uri: str | None = None
|
|
459
534
|
self.modules = ModuleRegistry()
|
|
535
|
+
self.has_lombok = False
|
|
460
536
|
self._workspace_expanded = False
|
|
461
537
|
|
|
462
538
|
@property
|
|
@@ -480,7 +556,13 @@ class JdtlsProxy:
|
|
|
480
556
|
logger.warning("jdtls not found on PATH — running in standalone mode (custom rules only)")
|
|
481
557
|
return self._jdtls_on_path
|
|
482
558
|
|
|
483
|
-
async def start(
|
|
559
|
+
async def start(
|
|
560
|
+
self,
|
|
561
|
+
init_params: dict[str, Any],
|
|
562
|
+
*,
|
|
563
|
+
module_root_uri: str | None = None,
|
|
564
|
+
config: Mapping[str, Any] | None = None,
|
|
565
|
+
) -> bool:
|
|
484
566
|
"""Start jdtls subprocess and initialize it.
|
|
485
567
|
|
|
486
568
|
If *module_root_uri* is provided, jdtls is scoped to that module for
|
|
@@ -491,20 +573,35 @@ class JdtlsProxy:
|
|
|
491
573
|
if not jdtls_path:
|
|
492
574
|
return False
|
|
493
575
|
|
|
494
|
-
# Data-dir hash: use module URI when scoped so each module gets its own
|
|
495
|
-
# clean jdtls state. This avoids loading a 2.5GB monorepo index when only
|
|
496
|
-
# one module is needed. Without expansion, each session is module-scoped,
|
|
497
|
-
# so the data-dir should match that scope.
|
|
498
576
|
original_root: str = init_params.get("rootUri") or init_params.get("rootPath") or str(Path.cwd())
|
|
499
577
|
self._original_root_uri = original_root
|
|
578
|
+
effective_root_uri = module_root_uri or original_root
|
|
579
|
+
|
|
580
|
+
# Mark ADDED before any await to prevent duplicate module additions
|
|
581
|
+
# from concurrent coroutines during the executor yield below.
|
|
582
|
+
self._initial_module_uri = module_root_uri
|
|
583
|
+
self.modules.mark_added(effective_root_uri)
|
|
584
|
+
|
|
585
|
+
# All blocking startup I/O in a single executor call: cache version check
|
|
586
|
+
# (may rmtree on upgrade), Lombok discovery, and jdtls env build.
|
|
587
|
+
cache_root = Path.home() / ".cache" / "jdtls-data"
|
|
588
|
+
|
|
589
|
+
def _blocking_startup() -> tuple[dict[str, str], str | None]:
|
|
590
|
+
_clear_cache_on_version_change(cache_root)
|
|
591
|
+
return build_jdtls_env(), _find_lombok_jar(config)
|
|
592
|
+
|
|
593
|
+
loop = asyncio.get_running_loop()
|
|
594
|
+
jdtls_env, lombok_jar = await loop.run_in_executor(None, _blocking_startup)
|
|
595
|
+
if lombok_jar:
|
|
596
|
+
logger.info("jdtls: using Lombok agent from %s", _redact_path(lombok_jar))
|
|
597
|
+
|
|
500
598
|
hash_source = module_root_uri or original_root
|
|
501
599
|
data_hash = hashlib.sha256(hash_source.encode()).hexdigest()[:12]
|
|
502
|
-
data_dir =
|
|
600
|
+
data_dir = cache_root / data_hash
|
|
503
601
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
504
602
|
|
|
505
603
|
# Deep copy to avoid mutating server._init_params.
|
|
506
604
|
effective_params = copy.deepcopy(init_params)
|
|
507
|
-
effective_root_uri = module_root_uri or original_root
|
|
508
605
|
if module_root_uri:
|
|
509
606
|
effective_params["rootUri"] = module_root_uri
|
|
510
607
|
from pygls.uris import to_fs_path
|
|
@@ -521,20 +618,18 @@ class JdtlsProxy:
|
|
|
521
618
|
ws = caps.setdefault("workspace", {})
|
|
522
619
|
ws["workspaceFolders"] = True
|
|
523
620
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
621
|
+
jdtls_cmd: list[str] = [
|
|
622
|
+
jdtls_path,
|
|
623
|
+
"-data",
|
|
624
|
+
str(data_dir),
|
|
625
|
+
f"--jvm-arg=-Xmx{DEFAULT_JVM_MAX_HEAP}",
|
|
626
|
+
]
|
|
627
|
+
if lombok_jar:
|
|
628
|
+
jdtls_cmd.append(f"--jvm-arg=-javaagent:{lombok_jar}")
|
|
531
629
|
|
|
532
630
|
try:
|
|
533
631
|
self._process = await asyncio.create_subprocess_exec(
|
|
534
|
-
|
|
535
|
-
"-data",
|
|
536
|
-
str(data_dir),
|
|
537
|
-
f"--jvm-arg=-Xmx{DEFAULT_JVM_MAX_HEAP}",
|
|
632
|
+
*jdtls_cmd,
|
|
538
633
|
stdin=asyncio.subprocess.PIPE,
|
|
539
634
|
stdout=asyncio.subprocess.PIPE,
|
|
540
635
|
stderr=asyncio.subprocess.PIPE,
|
|
@@ -563,13 +658,19 @@ class JdtlsProxy:
|
|
|
563
658
|
|
|
564
659
|
await self.send_notification("initialized", {})
|
|
565
660
|
self._available = True
|
|
661
|
+
self.has_lombok = lombok_jar is not None
|
|
566
662
|
return True
|
|
567
663
|
|
|
568
664
|
except (OSError, FileNotFoundError) as e:
|
|
569
665
|
logger.error("Failed to start jdtls: %s", e)
|
|
570
666
|
return False
|
|
571
667
|
|
|
572
|
-
async def ensure_started(
|
|
668
|
+
async def ensure_started(
|
|
669
|
+
self,
|
|
670
|
+
init_params: dict[str, Any],
|
|
671
|
+
file_uri: str,
|
|
672
|
+
config: Mapping[str, Any] | None = None,
|
|
673
|
+
) -> bool:
|
|
573
674
|
"""Start jdtls lazily, scoped to the module containing *file_uri*.
|
|
574
675
|
|
|
575
676
|
Thread-safe: uses asyncio.Lock to prevent double-start from rapid
|
|
@@ -596,7 +697,7 @@ class JdtlsProxy:
|
|
|
596
697
|
self._starting = True
|
|
597
698
|
try:
|
|
598
699
|
module_uri = _resolve_module_uri(file_uri)
|
|
599
|
-
started = await self.start(init_params, module_root_uri=module_uri)
|
|
700
|
+
started = await self.start(init_params, module_root_uri=module_uri, config=config)
|
|
600
701
|
if not started:
|
|
601
702
|
self._start_failed = True
|
|
602
703
|
self._start_failed_at = time.monotonic()
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import json
|
|
11
11
|
import logging
|
|
12
|
+
import re
|
|
12
13
|
import sys
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Any
|
|
@@ -64,6 +65,7 @@ class JavaFunctionalLspServer(LanguageServer):
|
|
|
64
65
|
self._config: dict[str, Any] = {}
|
|
65
66
|
self._init_params: dict[str, Any] = {}
|
|
66
67
|
self._proxy = JdtlsProxy(on_diagnostics=self._on_jdtls_diagnostics)
|
|
68
|
+
self._user_suppress_patterns: list[re.Pattern[str]] = []
|
|
67
69
|
|
|
68
70
|
def _on_jdtls_diagnostics(self, uri: str, diagnostics: list[Any]) -> None:
|
|
69
71
|
"""Called when jdtls publishes diagnostics — merge with custom and re-publish.
|
|
@@ -197,14 +199,54 @@ def _jdtls_raw_to_lsp_diagnostics(raw_diagnostics: list[Any]) -> list[lsp.Diagno
|
|
|
197
199
|
return result
|
|
198
200
|
|
|
199
201
|
|
|
202
|
+
def _compile_user_patterns(config: dict[str, Any]) -> list[re.Pattern[str]]:
|
|
203
|
+
"""Compile user-defined suppressJdtlsPatterns from config."""
|
|
204
|
+
raw = config.get("suppressJdtlsPatterns", [])
|
|
205
|
+
if not isinstance(raw, list):
|
|
206
|
+
logger.warning("suppressJdtlsPatterns must be a list of regex strings, got %s", type(raw).__name__)
|
|
207
|
+
return []
|
|
208
|
+
patterns: list[re.Pattern[str]] = []
|
|
209
|
+
for entry in raw:
|
|
210
|
+
if not isinstance(entry, str):
|
|
211
|
+
continue
|
|
212
|
+
try:
|
|
213
|
+
patterns.append(re.compile(entry))
|
|
214
|
+
except re.error as e:
|
|
215
|
+
logger.warning("Invalid suppressJdtlsPatterns regex %r: %s", entry, e)
|
|
216
|
+
return patterns
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _is_jdtls_suppressed(diag: dict[str, Any], user_patterns: list[re.Pattern[str]]) -> bool:
|
|
220
|
+
"""Check if a jdtls diagnostic matches a user-configured suppress pattern.
|
|
221
|
+
|
|
222
|
+
No built-in patterns — the root cause (stale jdtls caches) is fixed by
|
|
223
|
+
clearing the cache on version change. Users can still suppress specific
|
|
224
|
+
jdtls messages via suppressJdtlsPatterns in .java-functional-lsp.json.
|
|
225
|
+
"""
|
|
226
|
+
if not user_patterns:
|
|
227
|
+
return False
|
|
228
|
+
msg = diag.get("message", "")
|
|
229
|
+
for pat in user_patterns:
|
|
230
|
+
if pat.search(msg):
|
|
231
|
+
return True
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
|
|
200
235
|
def _run_analysis(source: str, uri: str) -> list[lsp.Diagnostic]:
|
|
201
|
-
"""Run custom analyzers on source text and merge with jdtls diagnostics.
|
|
236
|
+
"""Run custom analyzers on source text and merge with jdtls diagnostics.
|
|
237
|
+
|
|
238
|
+
jdtls processing is isolated: if it fails, custom diagnostics still publish.
|
|
239
|
+
"""
|
|
202
240
|
custom_diags = _analyze_document(source, uri)
|
|
203
241
|
|
|
204
242
|
jdtls_diags: list[lsp.Diagnostic] = []
|
|
205
243
|
if server._proxy.is_available:
|
|
206
|
-
|
|
207
|
-
|
|
244
|
+
try:
|
|
245
|
+
raw = server._proxy.get_cached_diagnostics(uri)
|
|
246
|
+
raw = [d for d in raw if not _is_jdtls_suppressed(d, server._user_suppress_patterns)]
|
|
247
|
+
jdtls_diags = _jdtls_raw_to_lsp_diagnostics(raw)
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logger.warning("jdtls diagnostic processing failed for %s: %s", uri, e)
|
|
208
250
|
|
|
209
251
|
return jdtls_diags + custom_diags
|
|
210
252
|
|
|
@@ -232,6 +274,7 @@ def on_initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
|
|
|
232
274
|
root = params.root_path
|
|
233
275
|
|
|
234
276
|
server._config = _load_config(root)
|
|
277
|
+
server._user_suppress_patterns = _compile_user_patterns(server._config)
|
|
235
278
|
|
|
236
279
|
return lsp.InitializeResult(
|
|
237
280
|
capabilities=lsp.ServerCapabilities(
|
|
@@ -338,7 +381,10 @@ def _analyze_and_publish(uri: str) -> None:
|
|
|
338
381
|
async def _deferred_validate(uri: str) -> None:
|
|
339
382
|
"""Debounced validation — waits before analyzing to batch rapid edits."""
|
|
340
383
|
await asyncio.sleep(_DEBOUNCE_SECONDS)
|
|
341
|
-
|
|
384
|
+
try:
|
|
385
|
+
_analyze_and_publish(uri)
|
|
386
|
+
except Exception as e:
|
|
387
|
+
logger.error("Validation failed for %s: %s", uri, e)
|
|
342
388
|
|
|
343
389
|
|
|
344
390
|
def _forward_or_queue(method: str, serialized: Any) -> None:
|
|
@@ -377,7 +423,10 @@ async def on_did_open(params: lsp.DidOpenTextDocumentParams) -> None:
|
|
|
377
423
|
task.add_done_callback(_bg_tasks.discard)
|
|
378
424
|
|
|
379
425
|
# Custom diagnostics always publish immediately — never blocked by jdtls.
|
|
380
|
-
|
|
426
|
+
try:
|
|
427
|
+
_analyze_and_publish(uri)
|
|
428
|
+
except Exception as e:
|
|
429
|
+
logger.error("Analysis failed on open for %s: %s", uri, e)
|
|
381
430
|
|
|
382
431
|
|
|
383
432
|
@server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE)
|
|
@@ -395,7 +444,10 @@ async def on_did_change(params: lsp.DidChangeTextDocumentParams) -> None:
|
|
|
395
444
|
async def on_did_save(params: lsp.DidSaveTextDocumentParams) -> None:
|
|
396
445
|
"""Forward to jdtls and re-analyze immediately (no debounce on save)."""
|
|
397
446
|
_forward_or_queue("textDocument/didSave", _serialize_params(params))
|
|
398
|
-
|
|
447
|
+
try:
|
|
448
|
+
_analyze_and_publish(params.text_document.uri)
|
|
449
|
+
except Exception as e:
|
|
450
|
+
logger.error("Analysis failed on save for %s: %s", params.text_document.uri, e)
|
|
399
451
|
|
|
400
452
|
|
|
401
453
|
@server.feature(lsp.TEXT_DOCUMENT_DID_CLOSE)
|
|
@@ -419,7 +471,7 @@ async def _lazy_start_jdtls(file_uri: str) -> None:
|
|
|
419
471
|
are loaded on-demand via ``add_module_if_new()`` as files are opened.
|
|
420
472
|
"""
|
|
421
473
|
try:
|
|
422
|
-
started = await server._proxy.ensure_started(server._init_params, file_uri)
|
|
474
|
+
started = await server._proxy.ensure_started(server._init_params, file_uri, config=server._config)
|
|
423
475
|
if started:
|
|
424
476
|
logger.info("jdtls proxy active — full Java language support enabled")
|
|
425
477
|
await _register_jdtls_capabilities()
|
|
@@ -1103,7 +1103,7 @@ class TestLazyStart:
|
|
|
1103
1103
|
proxy._jdtls_on_path = True
|
|
1104
1104
|
captured: dict[str, Any] = {}
|
|
1105
1105
|
|
|
1106
|
-
async def capturing_start(params: Any, *, module_root_uri: str | None = None) -> bool:
|
|
1106
|
+
async def capturing_start(params: Any, *, module_root_uri: str | None = None, config: Any = None) -> bool:
|
|
1107
1107
|
captured["module_root_uri"] = module_root_uri
|
|
1108
1108
|
return False
|
|
1109
1109
|
|
|
@@ -1127,7 +1127,7 @@ class TestLazyStart:
|
|
|
1127
1127
|
|
|
1128
1128
|
captured: dict[str, Any] = {}
|
|
1129
1129
|
|
|
1130
|
-
async def capturing_start(params: Any, *, module_root_uri: str | None = None) -> bool:
|
|
1130
|
+
async def capturing_start(params: Any, *, module_root_uri: str | None = None, config: Any = None) -> bool:
|
|
1131
1131
|
captured["module_root_uri"] = module_root_uri
|
|
1132
1132
|
return False
|
|
1133
1133
|
|
|
@@ -642,6 +642,222 @@ class TestServerInternals:
|
|
|
642
642
|
|
|
643
643
|
|
|
644
644
|
# --------------------------------------------------------------------------
|
|
645
|
+
# --------------------------------------------------------------------------
|
|
646
|
+
# Lombok support tests
|
|
647
|
+
# --------------------------------------------------------------------------
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
class TestUserSuppressPatterns:
|
|
651
|
+
"""Tests for configurable suppressJdtlsPatterns."""
|
|
652
|
+
|
|
653
|
+
def test_user_pattern_matches(self) -> None:
|
|
654
|
+
from java_functional_lsp.server import _compile_user_patterns, _is_jdtls_suppressed
|
|
655
|
+
|
|
656
|
+
patterns = _compile_user_patterns({"suppressJdtlsPatterns": [r"The method generateReport\(\) is undefined"]})
|
|
657
|
+
diag = {"message": "The method generateReport() is undefined for the type InternalService"}
|
|
658
|
+
assert _is_jdtls_suppressed(diag, patterns) is True
|
|
659
|
+
|
|
660
|
+
def test_user_pattern_does_not_match_unrelated(self) -> None:
|
|
661
|
+
from java_functional_lsp.server import _compile_user_patterns, _is_jdtls_suppressed
|
|
662
|
+
|
|
663
|
+
patterns = _compile_user_patterns({"suppressJdtlsPatterns": [r"The method generateReport\(\) is undefined"]})
|
|
664
|
+
diag = {"message": "The method foo() is undefined for the type String"}
|
|
665
|
+
assert _is_jdtls_suppressed(diag, patterns) is False
|
|
666
|
+
|
|
667
|
+
def test_no_patterns_passes_everything(self) -> None:
|
|
668
|
+
"""With no user patterns, nothing is suppressed."""
|
|
669
|
+
from java_functional_lsp.server import _is_jdtls_suppressed
|
|
670
|
+
|
|
671
|
+
diag = {"message": "The method builder() is undefined for the type Foo"}
|
|
672
|
+
assert _is_jdtls_suppressed(diag, []) is False
|
|
673
|
+
|
|
674
|
+
def test_invalid_regex_skipped(self) -> None:
|
|
675
|
+
from java_functional_lsp.server import _compile_user_patterns
|
|
676
|
+
|
|
677
|
+
patterns = _compile_user_patterns({"suppressJdtlsPatterns": [r"valid", r"[invalid"]})
|
|
678
|
+
assert len(patterns) == 1
|
|
679
|
+
assert patterns[0].search("valid") is not None
|
|
680
|
+
|
|
681
|
+
def test_non_list_ignored(self) -> None:
|
|
682
|
+
from java_functional_lsp.server import _compile_user_patterns
|
|
683
|
+
|
|
684
|
+
patterns = _compile_user_patterns({"suppressJdtlsPatterns": "not a list"})
|
|
685
|
+
assert patterns == []
|
|
686
|
+
|
|
687
|
+
def test_empty_config(self) -> None:
|
|
688
|
+
from java_functional_lsp.server import _compile_user_patterns
|
|
689
|
+
|
|
690
|
+
patterns = _compile_user_patterns({})
|
|
691
|
+
assert patterns == []
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
class TestJdtlsIsolation:
|
|
695
|
+
"""Tests that jdtls failures don't suppress custom diagnostics."""
|
|
696
|
+
|
|
697
|
+
def test_jdtls_exception_does_not_suppress_custom_diags(self) -> None:
|
|
698
|
+
"""If jdtls diagnostic processing throws, custom diagnostics still publish."""
|
|
699
|
+
from unittest.mock import patch
|
|
700
|
+
|
|
701
|
+
from java_functional_lsp.server import _run_analysis
|
|
702
|
+
|
|
703
|
+
java_source = "public class Foo { public String bar() { return null; } }"
|
|
704
|
+
|
|
705
|
+
with patch("java_functional_lsp.server.server") as mock_server:
|
|
706
|
+
mock_server._proxy.is_available = True
|
|
707
|
+
mock_server._proxy.get_cached_diagnostics.side_effect = RuntimeError("jdtls corrupt")
|
|
708
|
+
mock_server._user_suppress_patterns = []
|
|
709
|
+
mock_server._config = {}
|
|
710
|
+
mock_server._parser = __import__("java_functional_lsp.analyzers.base", fromlist=["get_parser"]).get_parser()
|
|
711
|
+
|
|
712
|
+
result = _run_analysis(java_source, "file:///test/Foo.java")
|
|
713
|
+
|
|
714
|
+
custom_diags = [d for d in result if d.source == "java-functional-lsp"]
|
|
715
|
+
assert len(custom_diags) > 0, "Custom diagnostics should publish even when jdtls fails"
|
|
716
|
+
|
|
717
|
+
def test_jdtls_unavailable_still_publishes_custom_diags(self) -> None:
|
|
718
|
+
"""When jdtls is not available, custom diagnostics still publish."""
|
|
719
|
+
from unittest.mock import patch
|
|
720
|
+
|
|
721
|
+
from java_functional_lsp.server import _run_analysis
|
|
722
|
+
|
|
723
|
+
java_source = "public class Foo { public String bar() { return null; } }"
|
|
724
|
+
|
|
725
|
+
with patch("java_functional_lsp.server.server") as mock_server:
|
|
726
|
+
mock_server._proxy.is_available = False
|
|
727
|
+
mock_server._config = {}
|
|
728
|
+
mock_server._parser = __import__("java_functional_lsp.analyzers.base", fromlist=["get_parser"]).get_parser()
|
|
729
|
+
|
|
730
|
+
result = _run_analysis(java_source, "file:///test/Foo.java")
|
|
731
|
+
|
|
732
|
+
custom_diags = [d for d in result if d.source == "java-functional-lsp"]
|
|
733
|
+
assert len(custom_diags) > 0, "Custom diagnostics should publish without jdtls"
|
|
734
|
+
|
|
735
|
+
def test_analyze_and_publish_exception_does_not_crash_handler(self) -> None:
|
|
736
|
+
"""Handler-level try/except prevents server crash when _analyze_and_publish raises."""
|
|
737
|
+
from unittest.mock import patch
|
|
738
|
+
|
|
739
|
+
from java_functional_lsp.server import _deferred_validate
|
|
740
|
+
|
|
741
|
+
with patch("java_functional_lsp.server._analyze_and_publish", side_effect=RuntimeError("boom")):
|
|
742
|
+
import asyncio
|
|
743
|
+
|
|
744
|
+
loop = asyncio.new_event_loop()
|
|
745
|
+
try:
|
|
746
|
+
# _deferred_validate should catch the exception and log, not raise
|
|
747
|
+
loop.run_until_complete(_deferred_validate("file:///test/Foo.java"))
|
|
748
|
+
finally:
|
|
749
|
+
loop.close()
|
|
750
|
+
# If we get here without exception, the handler caught it correctly
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
class TestFindLombokJar:
|
|
754
|
+
"""Tests for _find_lombok_jar()."""
|
|
755
|
+
|
|
756
|
+
def test_config_path_wins(self, tmp_path: Any) -> None:
|
|
757
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
758
|
+
|
|
759
|
+
jar = tmp_path / "lombok.jar"
|
|
760
|
+
jar.touch()
|
|
761
|
+
result = _find_lombok_jar({"lombok": str(jar)})
|
|
762
|
+
assert result == str(jar)
|
|
763
|
+
|
|
764
|
+
def test_env_var_used(self, tmp_path: Any, monkeypatch: Any) -> None:
|
|
765
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
766
|
+
|
|
767
|
+
jar = tmp_path / "lombok.jar"
|
|
768
|
+
jar.touch()
|
|
769
|
+
monkeypatch.setenv("LOMBOK_JAR", str(jar))
|
|
770
|
+
result = _find_lombok_jar()
|
|
771
|
+
assert result == str(jar)
|
|
772
|
+
|
|
773
|
+
def test_config_wins_over_env(self, tmp_path: Any, monkeypatch: Any) -> None:
|
|
774
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
775
|
+
|
|
776
|
+
config_jar = tmp_path / "config-lombok.jar"
|
|
777
|
+
config_jar.touch()
|
|
778
|
+
env_jar = tmp_path / "env-lombok.jar"
|
|
779
|
+
env_jar.touch()
|
|
780
|
+
monkeypatch.setenv("LOMBOK_JAR", str(env_jar))
|
|
781
|
+
result = _find_lombok_jar({"lombok": str(config_jar)})
|
|
782
|
+
assert result == str(config_jar)
|
|
783
|
+
|
|
784
|
+
def test_maven_cache_semantic_sort(self, tmp_path: Any, monkeypatch: Any) -> None:
|
|
785
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
786
|
+
|
|
787
|
+
monkeypatch.delenv("LOMBOK_JAR", raising=False)
|
|
788
|
+
m2 = tmp_path / ".m2" / "repository" / "org" / "projectlombok" / "lombok"
|
|
789
|
+
for v in ["1.18.4", "1.18.30", "1.9.2"]:
|
|
790
|
+
d = m2 / v
|
|
791
|
+
d.mkdir(parents=True)
|
|
792
|
+
(d / f"lombok-{v}.jar").touch()
|
|
793
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
|
794
|
+
result = _find_lombok_jar()
|
|
795
|
+
assert result is not None
|
|
796
|
+
assert "1.18.30" in result # Semantic sort picks 1.18.30 over 1.9.2
|
|
797
|
+
|
|
798
|
+
def test_returns_none_when_nothing_found(self, tmp_path: Any, monkeypatch: Any) -> None:
|
|
799
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
800
|
+
|
|
801
|
+
monkeypatch.delenv("LOMBOK_JAR", raising=False)
|
|
802
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
|
803
|
+
result = _find_lombok_jar()
|
|
804
|
+
assert result is None
|
|
805
|
+
|
|
806
|
+
def test_warns_on_missing_config_path(self, tmp_path: Any, caplog: Any) -> None:
|
|
807
|
+
import logging
|
|
808
|
+
|
|
809
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
810
|
+
|
|
811
|
+
with caplog.at_level(logging.WARNING, logger="java_functional_lsp.proxy"):
|
|
812
|
+
_find_lombok_jar({"lombok": "/nonexistent/lombok.jar"})
|
|
813
|
+
assert any("does not exist" in r.getMessage() for r in caplog.records)
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
class TestCacheClear:
|
|
817
|
+
"""Tests for _clear_cache_on_version_change()."""
|
|
818
|
+
|
|
819
|
+
def test_clears_on_version_mismatch(self, tmp_path: Any) -> None:
|
|
820
|
+
from java_functional_lsp.proxy import _clear_cache_on_version_change
|
|
821
|
+
|
|
822
|
+
cache = tmp_path / "jdtls-data"
|
|
823
|
+
cache.mkdir()
|
|
824
|
+
stale_dir = cache / "abc123"
|
|
825
|
+
stale_dir.mkdir()
|
|
826
|
+
(cache / ".version").write_text("0.7.9")
|
|
827
|
+
|
|
828
|
+
_clear_cache_on_version_change(cache)
|
|
829
|
+
|
|
830
|
+
assert not stale_dir.exists(), "Stale data-dir should be cleared"
|
|
831
|
+
assert cache.exists(), "Cache root should be recreated"
|
|
832
|
+
from java_functional_lsp import __version__
|
|
833
|
+
|
|
834
|
+
assert (cache / ".version").read_text() == __version__
|
|
835
|
+
|
|
836
|
+
def test_keeps_cache_on_same_version(self, tmp_path: Any) -> None:
|
|
837
|
+
from java_functional_lsp import __version__
|
|
838
|
+
from java_functional_lsp.proxy import _clear_cache_on_version_change
|
|
839
|
+
|
|
840
|
+
cache = tmp_path / "jdtls-data"
|
|
841
|
+
cache.mkdir()
|
|
842
|
+
existing_dir = cache / "abc123"
|
|
843
|
+
existing_dir.mkdir()
|
|
844
|
+
(cache / ".version").write_text(__version__)
|
|
845
|
+
|
|
846
|
+
_clear_cache_on_version_change(cache)
|
|
847
|
+
|
|
848
|
+
assert existing_dir.exists(), "Existing data-dir should be preserved"
|
|
849
|
+
|
|
850
|
+
def test_creates_marker_on_first_run(self, tmp_path: Any) -> None:
|
|
851
|
+
from java_functional_lsp.proxy import _clear_cache_on_version_change
|
|
852
|
+
|
|
853
|
+
cache = tmp_path / "jdtls-data"
|
|
854
|
+
_clear_cache_on_version_change(cache)
|
|
855
|
+
|
|
856
|
+
from java_functional_lsp import __version__
|
|
857
|
+
|
|
858
|
+
assert (cache / ".version").read_text() == __version__
|
|
859
|
+
|
|
860
|
+
|
|
645
861
|
# Subprocess-based tests — zero mocks, real LSP transport
|
|
646
862
|
# --------------------------------------------------------------------------
|
|
647
863
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.github/ISSUE_TEMPLATE/bug-report.md
RENAMED
|
File without changes
|
{java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.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.7.8 → java_functional_lsp-0.8.0}/.github/workflows/release-drafter.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/.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
|
{java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/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
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.7.8 → java_functional_lsp-0.8.0}/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
|