java-functional-lsp 0.7.7__tar.gz → 0.7.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/PKG-INFO +17 -1
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/README.md +16 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/pyproject.toml +1 -1
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/__init__.py +1 -1
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/proxy.py +91 -14
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/server.py +26 -1
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_proxy.py +2 -2
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_server.py +119 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/uv.lock +1 -1
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.claude-plugin/plugin.json +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.githooks/pre-commit +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.githooks/pre-push +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/CODEOWNERS +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/SECURITY.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/dependabot.yml +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/release-drafter.yml +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/workflows/publish.yml +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/workflows/release-drafter.yml +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/workflows/stale.yml +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/workflows/test.yml +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/workflows/update-homebrew.yml +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/workflows/vscode-ext.yml +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.gitignore +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/CONTRIBUTING.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/LICENSE +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/SKILL.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/commands/lint-java.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/editors/intellij/README.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/editors/intellij/lsp4ij-template.json +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/editors/vscode/.vscodeignore +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/editors/vscode/README.md +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/editors/vscode/package-lock.json +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/editors/vscode/package.json +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/editors/vscode/src/extension.ts +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/editors/vscode/tsconfig.json +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/hooks/hooks.json +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/hooks/java_linter_reminder.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/scripts/ensure-lsp.sh +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/scripts/generate-formula.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/__main__.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/analyzers/__init__.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/analyzers/base.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/cli.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/src/java_functional_lsp/fixes.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/__init__.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/conftest.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_base.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_cli.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_config.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_e2e.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_e2e_jdtls.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_exception_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_fixes.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_functional_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_mutation_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_null_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/tests/test_spring_checker.py +0 -0
- {java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/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.7.
|
|
3
|
+
Version: 0.7.9
|
|
4
4
|
Summary: Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions
|
|
5
5
|
Project-URL: Homepage, https://github.com/aviadshiber/java-functional-lsp
|
|
6
6
|
Project-URL: Repository, https://github.com/aviadshiber/java-functional-lsp
|
|
@@ -253,6 +253,22 @@ public String legacyMethod() { ... }
|
|
|
253
253
|
|
|
254
254
|
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
255
|
|
|
256
|
+
### Lombok support
|
|
257
|
+
|
|
258
|
+
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.
|
|
259
|
+
|
|
260
|
+
The server auto-discovers `lombok.jar` from these locations (first match wins):
|
|
261
|
+
|
|
262
|
+
1. **Project config** — add to `.java-functional-lsp.json`:
|
|
263
|
+
```json
|
|
264
|
+
{ "lombok": "/path/to/lombok.jar" }
|
|
265
|
+
```
|
|
266
|
+
2. **Environment variable** — `LOMBOK_JAR=/path/to/lombok.jar`
|
|
267
|
+
3. **Maven cache** — auto-discovered from `~/.m2/repository/org/projectlombok/lombok/`
|
|
268
|
+
4. **Dedicated directory** — `~/.jdtls-libs/lombok.jar`
|
|
269
|
+
|
|
270
|
+
If Lombok is used in your project but the jar isn't found, the server automatically filters common Lombok false-positive errors (like "builder() is undefined") so they don't clutter your diagnostics.
|
|
271
|
+
|
|
256
272
|
## Code actions (quick fixes)
|
|
257
273
|
|
|
258
274
|
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:
|
|
@@ -225,6 +225,22 @@ public String legacyMethod() { ... }
|
|
|
225
225
|
|
|
226
226
|
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
227
|
|
|
228
|
+
### Lombok support
|
|
229
|
+
|
|
230
|
+
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.
|
|
231
|
+
|
|
232
|
+
The server auto-discovers `lombok.jar` from these locations (first match wins):
|
|
233
|
+
|
|
234
|
+
1. **Project config** — add to `.java-functional-lsp.json`:
|
|
235
|
+
```json
|
|
236
|
+
{ "lombok": "/path/to/lombok.jar" }
|
|
237
|
+
```
|
|
238
|
+
2. **Environment variable** — `LOMBOK_JAR=/path/to/lombok.jar`
|
|
239
|
+
3. **Maven cache** — auto-discovered from `~/.m2/repository/org/projectlombok/lombok/`
|
|
240
|
+
4. **Dedicated directory** — `~/.jdtls-libs/lombok.jar`
|
|
241
|
+
|
|
242
|
+
If Lombok is used in your project but the jar isn't found, the server automatically filters common Lombok false-positive errors (like "builder() is undefined") so they don't clutter your diagnostics.
|
|
243
|
+
|
|
228
244
|
## Code actions (quick fixes)
|
|
229
245
|
|
|
230
246
|
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.
|
|
7
|
+
version = "0.7.9"
|
|
8
8
|
description = "Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -433,6 +433,58 @@ 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 _find_lombok_jar(config: Mapping[str, Any] | None = None) -> str | None:
|
|
448
|
+
"""Find lombok.jar from configurable and auto-discovered locations.
|
|
449
|
+
|
|
450
|
+
Search order (first match wins):
|
|
451
|
+
1. Project config (.java-functional-lsp.json): ``{"lombok": "/path/to/lombok.jar"}``
|
|
452
|
+
2. Environment variable: ``LOMBOK_JAR``
|
|
453
|
+
3. Maven cache: ``~/.m2/repository/org/projectlombok/lombok/*/lombok-*.jar``
|
|
454
|
+
4. Dedicated directory: ``~/.jdtls-libs/lombok.jar``
|
|
455
|
+
"""
|
|
456
|
+
# 1. Project config
|
|
457
|
+
if config and config.get("lombok"):
|
|
458
|
+
p = Path(config["lombok"]).expanduser()
|
|
459
|
+
if p.is_file():
|
|
460
|
+
return str(p)
|
|
461
|
+
logger.warning("Lombok path from config does not exist: %s", _redact_path(str(p)))
|
|
462
|
+
|
|
463
|
+
# 2. Environment variable
|
|
464
|
+
env_path = os.environ.get("LOMBOK_JAR")
|
|
465
|
+
if env_path:
|
|
466
|
+
p = Path(env_path).expanduser()
|
|
467
|
+
if p.is_file():
|
|
468
|
+
return str(p)
|
|
469
|
+
logger.warning("LOMBOK_JAR does not exist: %s", _redact_path(env_path))
|
|
470
|
+
|
|
471
|
+
# 3. Maven cache (latest version, semantic sort)
|
|
472
|
+
m2_lombok = Path.home() / ".m2" / "repository" / "org" / "projectlombok" / "lombok"
|
|
473
|
+
if m2_lombok.is_dir():
|
|
474
|
+
version_dirs = [d for d in m2_lombok.iterdir() if d.is_dir()]
|
|
475
|
+
for version_dir in sorted(version_dirs, key=lambda d: _version_key(d.name), reverse=True):
|
|
476
|
+
jar = version_dir / f"lombok-{version_dir.name}.jar"
|
|
477
|
+
if jar.is_file():
|
|
478
|
+
return str(jar)
|
|
479
|
+
|
|
480
|
+
# 4. Dedicated directory
|
|
481
|
+
fallback = Path.home() / ".jdtls-libs" / "lombok.jar"
|
|
482
|
+
if fallback.is_file():
|
|
483
|
+
return str(fallback)
|
|
484
|
+
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
|
|
436
488
|
class JdtlsProxy:
|
|
437
489
|
"""Manages a jdtls subprocess and provides async request/notification forwarding."""
|
|
438
490
|
|
|
@@ -457,6 +509,7 @@ class JdtlsProxy:
|
|
|
457
509
|
self._original_root_uri: str | None = None
|
|
458
510
|
self._initial_module_uri: str | None = None
|
|
459
511
|
self.modules = ModuleRegistry()
|
|
512
|
+
self.has_lombok = False
|
|
460
513
|
self._workspace_expanded = False
|
|
461
514
|
|
|
462
515
|
@property
|
|
@@ -480,22 +533,32 @@ class JdtlsProxy:
|
|
|
480
533
|
logger.warning("jdtls not found on PATH — running in standalone mode (custom rules only)")
|
|
481
534
|
return self._jdtls_on_path
|
|
482
535
|
|
|
483
|
-
async def start(
|
|
536
|
+
async def start(
|
|
537
|
+
self,
|
|
538
|
+
init_params: dict[str, Any],
|
|
539
|
+
*,
|
|
540
|
+
module_root_uri: str | None = None,
|
|
541
|
+
config: Mapping[str, Any] | None = None,
|
|
542
|
+
) -> bool:
|
|
484
543
|
"""Start jdtls subprocess and initialize it.
|
|
485
544
|
|
|
486
545
|
If *module_root_uri* is provided, jdtls is scoped to that module for
|
|
487
|
-
fast startup
|
|
488
|
-
|
|
546
|
+
fast startup and the data-dir hash is based on the module URI (so each
|
|
547
|
+
module gets its own isolated index). Otherwise the workspace root is used.
|
|
489
548
|
"""
|
|
490
549
|
jdtls_path = shutil.which("jdtls")
|
|
491
550
|
if not jdtls_path:
|
|
492
551
|
return False
|
|
493
552
|
|
|
494
|
-
# Data-dir hash
|
|
553
|
+
# Data-dir hash: use module URI when scoped so each module gets its own
|
|
554
|
+
# clean jdtls state. This avoids loading a 2.5GB monorepo index when only
|
|
555
|
+
# one module is needed. Without expansion, each session is module-scoped,
|
|
556
|
+
# so the data-dir should match that scope.
|
|
495
557
|
original_root: str = init_params.get("rootUri") or init_params.get("rootPath") or str(Path.cwd())
|
|
496
558
|
self._original_root_uri = original_root
|
|
497
|
-
|
|
498
|
-
|
|
559
|
+
hash_source = module_root_uri or original_root
|
|
560
|
+
data_hash = hashlib.sha256(hash_source.encode()).hexdigest()[:12]
|
|
561
|
+
data_dir = Path.home() / ".cache" / "jdtls-data" / data_hash
|
|
499
562
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
500
563
|
|
|
501
564
|
# Deep copy to avoid mutating server._init_params.
|
|
@@ -521,16 +584,24 @@ class JdtlsProxy:
|
|
|
521
584
|
self._initial_module_uri = module_root_uri
|
|
522
585
|
self.modules.mark_added(effective_root_uri)
|
|
523
586
|
|
|
524
|
-
# Build a clean environment
|
|
587
|
+
# Build a clean environment and find Lombok jar (both do blocking I/O).
|
|
525
588
|
loop = asyncio.get_running_loop()
|
|
526
|
-
jdtls_env = await loop.run_in_executor(None, build_jdtls_env)
|
|
589
|
+
jdtls_env, lombok_jar = await loop.run_in_executor(None, lambda: (build_jdtls_env(), _find_lombok_jar(config)))
|
|
590
|
+
if lombok_jar:
|
|
591
|
+
logger.info("jdtls: using Lombok agent from %s", _redact_path(lombok_jar))
|
|
592
|
+
|
|
593
|
+
jdtls_cmd: list[str] = [
|
|
594
|
+
jdtls_path,
|
|
595
|
+
"-data",
|
|
596
|
+
str(data_dir),
|
|
597
|
+
f"--jvm-arg=-Xmx{DEFAULT_JVM_MAX_HEAP}",
|
|
598
|
+
]
|
|
599
|
+
if lombok_jar:
|
|
600
|
+
jdtls_cmd.append(f"--jvm-arg=-javaagent:{lombok_jar}")
|
|
527
601
|
|
|
528
602
|
try:
|
|
529
603
|
self._process = await asyncio.create_subprocess_exec(
|
|
530
|
-
|
|
531
|
-
"-data",
|
|
532
|
-
str(data_dir),
|
|
533
|
-
f"--jvm-arg=-Xmx{DEFAULT_JVM_MAX_HEAP}",
|
|
604
|
+
*jdtls_cmd,
|
|
534
605
|
stdin=asyncio.subprocess.PIPE,
|
|
535
606
|
stdout=asyncio.subprocess.PIPE,
|
|
536
607
|
stderr=asyncio.subprocess.PIPE,
|
|
@@ -559,13 +630,19 @@ class JdtlsProxy:
|
|
|
559
630
|
|
|
560
631
|
await self.send_notification("initialized", {})
|
|
561
632
|
self._available = True
|
|
633
|
+
self.has_lombok = lombok_jar is not None
|
|
562
634
|
return True
|
|
563
635
|
|
|
564
636
|
except (OSError, FileNotFoundError) as e:
|
|
565
637
|
logger.error("Failed to start jdtls: %s", e)
|
|
566
638
|
return False
|
|
567
639
|
|
|
568
|
-
async def ensure_started(
|
|
640
|
+
async def ensure_started(
|
|
641
|
+
self,
|
|
642
|
+
init_params: dict[str, Any],
|
|
643
|
+
file_uri: str,
|
|
644
|
+
config: Mapping[str, Any] | None = None,
|
|
645
|
+
) -> bool:
|
|
569
646
|
"""Start jdtls lazily, scoped to the module containing *file_uri*.
|
|
570
647
|
|
|
571
648
|
Thread-safe: uses asyncio.Lock to prevent double-start from rapid
|
|
@@ -592,7 +669,7 @@ class JdtlsProxy:
|
|
|
592
669
|
self._starting = True
|
|
593
670
|
try:
|
|
594
671
|
module_uri = _resolve_module_uri(file_uri)
|
|
595
|
-
started = await self.start(init_params, module_root_uri=module_uri)
|
|
672
|
+
started = await self.start(init_params, module_root_uri=module_uri, config=config)
|
|
596
673
|
if not started:
|
|
597
674
|
self._start_failed = True
|
|
598
675
|
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
|
|
@@ -197,6 +198,28 @@ def _jdtls_raw_to_lsp_diagnostics(raw_diagnostics: list[Any]) -> list[lsp.Diagno
|
|
|
197
198
|
return result
|
|
198
199
|
|
|
199
200
|
|
|
201
|
+
# Lombok-specific patterns: only match diagnostics that are clearly Lombok-generated.
|
|
202
|
+
# Intentionally narrow to avoid suppressing real compile errors.
|
|
203
|
+
_LOMBOK_PATTERNS = re.compile(
|
|
204
|
+
r"(?:"
|
|
205
|
+
r"The method builder\(\)|" # @Builder
|
|
206
|
+
r"The method toBuilder\(\)|" # @Builder(toBuilder=true)
|
|
207
|
+
r"log cannot be resolved|" # @Slf4j, @Log
|
|
208
|
+
r"\w+Builder cannot be resolved to a type|" # @Builder inner class
|
|
209
|
+
r"The blank final field \w+ may not have been initialized" # @Value final fields
|
|
210
|
+
r")"
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _is_lombok_false_positive(diag: dict[str, Any]) -> bool:
|
|
215
|
+
"""Check if a jdtls diagnostic is likely a Lombok false positive.
|
|
216
|
+
|
|
217
|
+
Only matches narrow Lombok-specific patterns to avoid suppressing real errors.
|
|
218
|
+
"""
|
|
219
|
+
msg = diag.get("message", "")
|
|
220
|
+
return bool(_LOMBOK_PATTERNS.search(msg))
|
|
221
|
+
|
|
222
|
+
|
|
200
223
|
def _run_analysis(source: str, uri: str) -> list[lsp.Diagnostic]:
|
|
201
224
|
"""Run custom analyzers on source text and merge with jdtls diagnostics."""
|
|
202
225
|
custom_diags = _analyze_document(source, uri)
|
|
@@ -204,6 +227,8 @@ def _run_analysis(source: str, uri: str) -> list[lsp.Diagnostic]:
|
|
|
204
227
|
jdtls_diags: list[lsp.Diagnostic] = []
|
|
205
228
|
if server._proxy.is_available:
|
|
206
229
|
raw = server._proxy.get_cached_diagnostics(uri)
|
|
230
|
+
if not server._proxy.has_lombok:
|
|
231
|
+
raw = [d for d in raw if not _is_lombok_false_positive(d)]
|
|
207
232
|
jdtls_diags = _jdtls_raw_to_lsp_diagnostics(raw)
|
|
208
233
|
|
|
209
234
|
return jdtls_diags + custom_diags
|
|
@@ -419,7 +444,7 @@ async def _lazy_start_jdtls(file_uri: str) -> None:
|
|
|
419
444
|
are loaded on-demand via ``add_module_if_new()`` as files are opened.
|
|
420
445
|
"""
|
|
421
446
|
try:
|
|
422
|
-
started = await server._proxy.ensure_started(server._init_params, file_uri)
|
|
447
|
+
started = await server._proxy.ensure_started(server._init_params, file_uri, config=server._config)
|
|
423
448
|
if started:
|
|
424
449
|
logger.info("jdtls proxy active — full Java language support enabled")
|
|
425
450
|
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,125 @@ class TestServerInternals:
|
|
|
642
642
|
|
|
643
643
|
|
|
644
644
|
# --------------------------------------------------------------------------
|
|
645
|
+
# --------------------------------------------------------------------------
|
|
646
|
+
# Lombok support tests
|
|
647
|
+
# --------------------------------------------------------------------------
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
class TestLombokFalsePositive:
|
|
651
|
+
"""Tests for _is_lombok_false_positive()."""
|
|
652
|
+
|
|
653
|
+
def test_matches_builder_undefined(self) -> None:
|
|
654
|
+
from java_functional_lsp.server import _is_lombok_false_positive
|
|
655
|
+
|
|
656
|
+
diag = {"code": "67108964", "message": "The method builder() is undefined for the type Foo"}
|
|
657
|
+
assert _is_lombok_false_positive(diag) is True
|
|
658
|
+
|
|
659
|
+
def test_matches_log_unresolved(self) -> None:
|
|
660
|
+
from java_functional_lsp.server import _is_lombok_false_positive
|
|
661
|
+
|
|
662
|
+
diag = {"code": "570425394", "message": "log cannot be resolved"}
|
|
663
|
+
assert _is_lombok_false_positive(diag) is True
|
|
664
|
+
|
|
665
|
+
def test_matches_builder_type_unresolved(self) -> None:
|
|
666
|
+
from java_functional_lsp.server import _is_lombok_false_positive
|
|
667
|
+
|
|
668
|
+
diag = {"code": "16777218", "message": "FooBuilder cannot be resolved to a type"}
|
|
669
|
+
assert _is_lombok_false_positive(diag) is True
|
|
670
|
+
|
|
671
|
+
def test_does_not_match_real_undefined_method(self) -> None:
|
|
672
|
+
from java_functional_lsp.server import _is_lombok_false_positive
|
|
673
|
+
|
|
674
|
+
diag = {"code": "67108964", "message": "The method foo() is undefined for the type String"}
|
|
675
|
+
assert _is_lombok_false_positive(diag) is False
|
|
676
|
+
|
|
677
|
+
def test_does_not_match_real_unresolved_type(self) -> None:
|
|
678
|
+
from java_functional_lsp.server import _is_lombok_false_positive
|
|
679
|
+
|
|
680
|
+
diag = {"code": "16777218", "message": "SomeClass cannot be resolved to a type"}
|
|
681
|
+
assert _is_lombok_false_positive(diag) is False
|
|
682
|
+
|
|
683
|
+
def test_empty_message(self) -> None:
|
|
684
|
+
from java_functional_lsp.server import _is_lombok_false_positive
|
|
685
|
+
|
|
686
|
+
diag = {"code": "67108964", "message": ""}
|
|
687
|
+
assert _is_lombok_false_positive(diag) is False
|
|
688
|
+
|
|
689
|
+
def test_missing_fields(self) -> None:
|
|
690
|
+
from java_functional_lsp.server import _is_lombok_false_positive
|
|
691
|
+
|
|
692
|
+
assert _is_lombok_false_positive({}) is False
|
|
693
|
+
|
|
694
|
+
def test_matches_blank_final_field(self) -> None:
|
|
695
|
+
from java_functional_lsp.server import _is_lombok_false_positive
|
|
696
|
+
|
|
697
|
+
diag = {"code": "33554513", "message": "The blank final field name may not have been initialized"}
|
|
698
|
+
assert _is_lombok_false_positive(diag) is True
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
class TestFindLombokJar:
|
|
702
|
+
"""Tests for _find_lombok_jar()."""
|
|
703
|
+
|
|
704
|
+
def test_config_path_wins(self, tmp_path: Any) -> None:
|
|
705
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
706
|
+
|
|
707
|
+
jar = tmp_path / "lombok.jar"
|
|
708
|
+
jar.touch()
|
|
709
|
+
result = _find_lombok_jar({"lombok": str(jar)})
|
|
710
|
+
assert result == str(jar)
|
|
711
|
+
|
|
712
|
+
def test_env_var_used(self, tmp_path: Any, monkeypatch: Any) -> None:
|
|
713
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
714
|
+
|
|
715
|
+
jar = tmp_path / "lombok.jar"
|
|
716
|
+
jar.touch()
|
|
717
|
+
monkeypatch.setenv("LOMBOK_JAR", str(jar))
|
|
718
|
+
result = _find_lombok_jar()
|
|
719
|
+
assert result == str(jar)
|
|
720
|
+
|
|
721
|
+
def test_config_wins_over_env(self, tmp_path: Any, monkeypatch: Any) -> None:
|
|
722
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
723
|
+
|
|
724
|
+
config_jar = tmp_path / "config-lombok.jar"
|
|
725
|
+
config_jar.touch()
|
|
726
|
+
env_jar = tmp_path / "env-lombok.jar"
|
|
727
|
+
env_jar.touch()
|
|
728
|
+
monkeypatch.setenv("LOMBOK_JAR", str(env_jar))
|
|
729
|
+
result = _find_lombok_jar({"lombok": str(config_jar)})
|
|
730
|
+
assert result == str(config_jar)
|
|
731
|
+
|
|
732
|
+
def test_maven_cache_semantic_sort(self, tmp_path: Any, monkeypatch: Any) -> None:
|
|
733
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
734
|
+
|
|
735
|
+
monkeypatch.delenv("LOMBOK_JAR", raising=False)
|
|
736
|
+
m2 = tmp_path / ".m2" / "repository" / "org" / "projectlombok" / "lombok"
|
|
737
|
+
for v in ["1.18.4", "1.18.30", "1.9.2"]:
|
|
738
|
+
d = m2 / v
|
|
739
|
+
d.mkdir(parents=True)
|
|
740
|
+
(d / f"lombok-{v}.jar").touch()
|
|
741
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
|
742
|
+
result = _find_lombok_jar()
|
|
743
|
+
assert result is not None
|
|
744
|
+
assert "1.18.30" in result # Semantic sort picks 1.18.30 over 1.9.2
|
|
745
|
+
|
|
746
|
+
def test_returns_none_when_nothing_found(self, tmp_path: Any, monkeypatch: Any) -> None:
|
|
747
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
748
|
+
|
|
749
|
+
monkeypatch.delenv("LOMBOK_JAR", raising=False)
|
|
750
|
+
monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
|
|
751
|
+
result = _find_lombok_jar()
|
|
752
|
+
assert result is None
|
|
753
|
+
|
|
754
|
+
def test_warns_on_missing_config_path(self, tmp_path: Any, caplog: Any) -> None:
|
|
755
|
+
import logging
|
|
756
|
+
|
|
757
|
+
from java_functional_lsp.proxy import _find_lombok_jar
|
|
758
|
+
|
|
759
|
+
with caplog.at_level(logging.WARNING, logger="java_functional_lsp.proxy"):
|
|
760
|
+
_find_lombok_jar({"lombok": "/nonexistent/lombok.jar"})
|
|
761
|
+
assert any("does not exist" in r.getMessage() for r in caplog.records)
|
|
762
|
+
|
|
763
|
+
|
|
645
764
|
# Subprocess-based tests — zero mocks, real LSP transport
|
|
646
765
|
# --------------------------------------------------------------------------
|
|
647
766
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.github/ISSUE_TEMPLATE/bug-report.md
RENAMED
|
File without changes
|
{java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.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.7 → java_functional_lsp-0.7.9}/.github/workflows/release-drafter.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.7.7 → java_functional_lsp-0.7.9}/.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.7 → java_functional_lsp-0.7.9}/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.7 → java_functional_lsp-0.7.9}/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
|