java-functional-lsp 0.7.1__tar.gz → 0.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. java_functional_lsp-0.7.2/.github/workflows/test.yml +109 -0
  2. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/CONTRIBUTING.md +12 -1
  3. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/PKG-INFO +7 -5
  4. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/README.md +6 -4
  5. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/SKILL.md +7 -5
  6. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/pyproject.toml +5 -2
  7. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/__init__.py +1 -1
  8. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/server.py +9 -2
  9. java_functional_lsp-0.7.2/tests/test_e2e_jdtls.py +451 -0
  10. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_proxy.py +141 -0
  11. java_functional_lsp-0.7.2/tests/test_server.py +537 -0
  12. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/uv.lock +1 -1
  13. java_functional_lsp-0.7.1/.github/workflows/test.yml +0 -36
  14. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.claude-plugin/plugin.json +0 -0
  15. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.githooks/pre-commit +0 -0
  16. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.githooks/pre-push +0 -0
  17. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/CODEOWNERS +0 -0
  18. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  19. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  20. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/SECURITY.md +0 -0
  22. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/dependabot.yml +0 -0
  23. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/release-drafter.yml +0 -0
  24. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/workflows/publish.yml +0 -0
  25. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/workflows/release-drafter.yml +0 -0
  26. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/workflows/stale.yml +0 -0
  27. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/workflows/update-homebrew.yml +0 -0
  28. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.github/workflows/vscode-ext.yml +0 -0
  29. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/.gitignore +0 -0
  30. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/LICENSE +0 -0
  31. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/commands/lint-java.md +0 -0
  32. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/editors/intellij/README.md +0 -0
  33. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/editors/intellij/lsp4ij-template.json +0 -0
  34. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/editors/vscode/.vscodeignore +0 -0
  35. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/editors/vscode/README.md +0 -0
  36. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/editors/vscode/package-lock.json +0 -0
  37. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/editors/vscode/package.json +0 -0
  38. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/editors/vscode/src/extension.ts +0 -0
  39. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/editors/vscode/tsconfig.json +0 -0
  40. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/hooks/hooks.json +0 -0
  41. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/hooks/java_linter_reminder.py +0 -0
  42. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/scripts/ensure-lsp.sh +0 -0
  43. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/scripts/generate-formula.py +0 -0
  44. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/__main__.py +0 -0
  45. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/analyzers/__init__.py +0 -0
  46. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/analyzers/base.py +0 -0
  47. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
  48. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
  49. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
  50. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
  51. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
  52. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/cli.py +0 -0
  53. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/fixes.py +0 -0
  54. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/src/java_functional_lsp/proxy.py +0 -0
  55. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/__init__.py +0 -0
  56. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/conftest.py +0 -0
  57. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_base.py +0 -0
  58. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_cli.py +0 -0
  59. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_config.py +0 -0
  60. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_e2e.py +0 -0
  61. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_exception_checker.py +0 -0
  62. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_fixes.py +0 -0
  63. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_functional_checker.py +0 -0
  64. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_mutation_checker.py +0 -0
  65. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_null_checker.py +0 -0
  66. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_spring_checker.py +0 -0
  67. {java_functional_lsp-0.7.1 → java_functional_lsp-0.7.2}/tests/test_suppress.py +0 -0
@@ -0,0 +1,109 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest]
16
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
17
+ steps:
18
+ - uses: actions/checkout@v6
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v6
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v7
25
+ - name: Install dependencies
26
+ run: uv sync
27
+ - name: Lint
28
+ run: uv run ruff check src/ tests/
29
+ - name: Format check
30
+ run: uv run ruff format --check src/ tests/
31
+ - name: Type check
32
+ run: uv run mypy src/
33
+ - name: Test
34
+ run: uv run pytest -q -m "not e2e"
35
+ env:
36
+ NO_COLOR: "1"
37
+
38
+ integration:
39
+ name: Integration (with jdtls) / ${{ matrix.os }}
40
+ runs-on: ${{ matrix.os }}
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ os: [ubuntu-latest, macos-latest]
45
+ # Runs the FULL test suite (unit + e2e) with jdtls installed, on one
46
+ # Python version per OS. This is a comprehensive sanity check that
47
+ # exercises: custom analyzer diagnostics, code action generation,
48
+ # AND jdtls request forwarding end-to-end. The unit matrix above
49
+ # provides fast Python-version-specific feedback without jdtls.
50
+ steps:
51
+ - uses: actions/checkout@v6
52
+ - name: Set up Python 3.12
53
+ uses: actions/setup-python@v6
54
+ with:
55
+ python-version: "3.12"
56
+ - name: Install Java 21
57
+ uses: actions/setup-java@v5
58
+ with:
59
+ distribution: temurin
60
+ java-version: "21"
61
+ - name: Install jdtls (macOS)
62
+ if: runner.os == 'macOS'
63
+ run: brew install jdtls
64
+ - name: Install jdtls (Linux)
65
+ if: runner.os == 'Linux'
66
+ run: |
67
+ set -euo pipefail
68
+ # Download the Eclipse JDT Language Server milestone build directly.
69
+ # Pinned to match the Homebrew formula so Linux + macOS exercise the
70
+ # same version. When bumping, update ALL THREE of JDTLS_VERSION,
71
+ # JDTLS_BUILD, and JDTLS_SHA256 together — the canonical source is
72
+ # the Homebrew formula:
73
+ # https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/j/jdtls.rb
74
+ # It has `url "...jdt-language-server-<version>-<build>.tar.gz"`
75
+ # and `sha256 "<hex>"` which you can copy verbatim.
76
+ JDTLS_VERSION="1.57.0"
77
+ JDTLS_BUILD="202602261110"
78
+ JDTLS_SHA256="f7ffa93fe1bbbea95dac13dd97cdcd25c582d6e56db67258da0dcceb2302601e"
79
+ JDTLS_URL="https://www.eclipse.org/downloads/download.php?file=/jdtls/milestones/${JDTLS_VERSION}/jdt-language-server-${JDTLS_VERSION}-${JDTLS_BUILD}.tar.gz&r=1"
80
+ JDTLS_DIR="$HOME/.local/share/jdtls"
81
+ BIN_DIR="$HOME/.local/bin"
82
+ mkdir -p "$JDTLS_DIR" "$BIN_DIR"
83
+ # -L follows Eclipse's mirror redirect; -f fails loudly on 404.
84
+ curl -sSLf -o /tmp/jdtls.tar.gz "$JDTLS_URL"
85
+ # Verify the tarball integrity against the hash pinned in the
86
+ # Homebrew formula. This protects against mirror tampering —
87
+ # without it, a compromised Eclipse mirror could ship arbitrary
88
+ # code that our e2e tests would then execute.
89
+ echo "${JDTLS_SHA256} /tmp/jdtls.tar.gz" | sha256sum -c -
90
+ tar -xzf /tmp/jdtls.tar.gz -C "$JDTLS_DIR"
91
+ # Wrapper script on PATH that invokes the bundled Python launcher
92
+ # with python3 (the tarball ships jdtls.py in bin/).
93
+ printf '#!/bin/bash\nexec python3 "%s/bin/jdtls" "$@"\n' "$JDTLS_DIR" > "$BIN_DIR/jdtls"
94
+ chmod +x "$BIN_DIR/jdtls"
95
+ echo "$BIN_DIR" >> "$GITHUB_PATH"
96
+ - name: Install uv
97
+ uses: astral-sh/setup-uv@v7
98
+ - name: Install dependencies
99
+ run: uv sync
100
+ - name: Verify jdtls is available
101
+ run: |
102
+ which jdtls
103
+ jdtls --help | head -5 || true
104
+ java -version
105
+ echo "JAVA_HOME=$JAVA_HOME"
106
+ - name: Run full test suite (unit + e2e)
107
+ run: uv run pytest -v
108
+ env:
109
+ NO_COLOR: "1"
@@ -48,9 +48,20 @@ uv run pytest
48
48
  4. Add a `DiagnosticData` entry to the module's `_DATA` dict with `fix_type`, `target_library`, and `rationale`
49
49
  5. Pass `data=_DATA["rule-id"]` when creating the `Diagnostic`
50
50
  6. Add tests in `tests/test_<analyzer>.py` (including a test verifying the `data` field)
51
- 7. Optionally add a quick fix generator in `src/java_functional_lsp/fixes.py` and register it in `_FIX_REGISTRY`
51
+ 7. Optionally add a quick fix generator in `src/java_functional_lsp/fixes.py` and register it in `_FIX_REGISTRY` + add its title to `_FIX_TITLES` in `server.py` (an import-time assertion catches mismatches)
52
52
  8. Update the rules table in `README.md`
53
53
 
54
+ ## Test Architecture
55
+
56
+ The project has a layered test suite:
57
+
58
+ - **Unit tests** (`tests/test_*_checker.py`, `tests/test_fixes.py`, `tests/test_proxy.py`) — fast, focused, run in the main CI matrix across Python 3.10-3.13 on Ubuntu + macOS
59
+ - **Server integration tests** (`tests/test_server.py: TestServerInternals`) — exercise the server pipeline (config loading, diagnostic conversion, code actions) in-process
60
+ - **LSP lifecycle tests** (`tests/test_server.py: TestLspLifecycle`) — **zero mocks** — spawn the real server as a subprocess via pygls `LanguageClient`, connect over stdio, exercise the full LSP round-trip (initialize, didOpen, publishDiagnostics, codeAction, didChange)
61
+ - **jdtls e2e tests** (`tests/test_e2e_jdtls.py`) — **zero mocks** — spawn real jdtls, exercise definition/references/hover/completion/documentSymbol forwarding. Auto-skip when jdtls is not installed. Run in a dedicated CI integration job.
62
+
63
+ Coverage threshold is **80%**. Bump the version in both `pyproject.toml` and `src/java_functional_lsp/__init__.py` when making source changes (a pre-commit hook enforces this).
64
+
54
65
  ## Reporting Issues
55
66
 
56
67
  - Use the [bug report template](https://github.com/aviadshiber/java-functional-lsp/issues/new?template=bug-report.md)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-functional-lsp
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions
5
5
  Project-URL: Homepage, https://github.com/aviadshiber/java-functional-lsp
6
6
  Project-URL: Repository, https://github.com/aviadshiber/java-functional-lsp
@@ -36,7 +36,7 @@ Description-Content-Type: text/markdown
36
36
  A Java Language Server that provides two things in one:
37
37
 
38
38
  1. **Full Java language support** — completions, hover, go-to-definition, compile errors, missing imports — by proxying [Eclipse jdtls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) under the hood
39
- 2. **15 functional programming rules** — catches anti-patterns and suggests Vavr/Lombok/Spring alternatives, all before compilation
39
+ 2. **16 functional programming rules** — catches anti-patterns and suggests Vavr/Lombok/Spring alternatives, all before compilation
40
40
  3. **Code actions (quick fixes)** — automated refactoring via LSP `textDocument/codeAction`, with machine-readable diagnostic metadata for AI agents
41
41
 
42
42
  Designed for teams using **Vavr**, **Lombok**, and **Spring** with a functional-first approach.
@@ -52,7 +52,7 @@ When [jdtls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) is installed, the
52
52
  - Type mismatches
53
53
  - Completions, hover, go-to-definition, find references
54
54
 
55
- Install jdtls separately: `brew install jdtls` (requires JDK 21+). Without jdtls, the server runs in standalone mode — the 12 custom rules still work, but you won't get compile errors or completions.
55
+ Install jdtls separately: `brew install jdtls` (requires JDK 21+). The server auto-detects a Java 21+ installation even when the IDE's project SDK is older (e.g., Java 8) by probing `JDTLS_JAVA_HOME`, `JAVA_HOME`, `/usr/libexec/java_home -v 21+` (macOS), and `java` on PATH. Without jdtls, the server runs in standalone mode — the 16 custom rules still work, but you won't get compile errors or completions.
56
56
 
57
57
  ### Functional programming rules
58
58
 
@@ -72,6 +72,7 @@ Install jdtls separately: `brew install jdtls` (requires JDK 21+). Without jdtls
72
72
  | `component-annotation` | `@Component`/`@Service`/`@Repository` | `@Configuration` + `@Bean` | — |
73
73
  | `frozen-mutation` | Mutation on `List.of()`/`Collections.unmodifiable*` | `io.vavr.collection.List` | ✅ |
74
74
  | `null-check-to-monadic` | `if (x != null) { return x.foo(); }` | `Option.of(x).map(...)` | ✅ |
75
+ | `try-catch-to-monadic` | `try { return x(); } catch (E e) { return d; }` | `Try.of(() -> x()).getOrElse(d)` | ✅ |
75
76
  | `impure-method` | Method mixing pure logic with side-effects | Extract pure logic; wrap IO in `Try` | — |
76
77
 
77
78
  ## Install
@@ -231,7 +232,7 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
231
232
  - `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
232
233
 
233
234
  **Spring-aware behavior:**
234
- - `throw-statement` and `catch-rethrow` are automatically suppressed inside `@Bean` methods
235
+ - `throw-statement`, `catch-rethrow`, and `try-catch-to-monadic` are automatically suppressed inside `@Bean` methods
235
236
  - `mutable-dto` suggests `@ConstructorBinding` instead of `@Value` when the class has `@ConfigurationProperties`
236
237
 
237
238
  **Inline suppression** with `@SuppressWarnings`:
@@ -259,8 +260,9 @@ The server provides LSP code actions (`textDocument/codeAction`) that automatica
259
260
  | Rule | Code Action | What it does |
260
261
  |------|-------------|--------------|
261
262
  | `frozen-mutation` | Switch to Vavr Immutable Collection | Rewrites `List.of()` → `io.vavr.collection.List.of()`, `.add(x)` → `= list.append(x)`, adds import |
262
- | `null-check-to-monadic` | Convert to Option monadic flow | Rewrites `if (x != null) { return x.foo(); }` → `Option.of(x).map(...).getOrNull()`, adds import |
263
+ | `null-check-to-monadic` | Convert to Option monadic flow | Rewrites `if (x != null) { return x.foo(); }` → `Option.of(x).map(...)`, supports chained fallbacks via `.orElse()`, adds import |
263
264
  | `null-return` | Replace with Option.none() | Rewrites `return null` → `return Option.none()`, adds import |
265
+ | `try-catch-to-monadic` | Convert try/catch to Try monadic flow | Rewrites `try { return expr; } catch (E e) { return default; }` → `Try.of(() -> expr).getOrElse(default)`. Supports 3 patterns: simple default (eager/lazy `.getOrElse`), logging + default (`.onFailure().getOrElse`), and exception-dependent recovery (`.recover(E.class, ...).get()`). Skips try-with-resources, finally, multi-catch, and union types. Adds import. |
264
266
 
265
267
  Quick fixes automatically add the required Vavr import if it's not already present. Disable auto-import with `"autoImportVavr": false` in config.
266
268
 
@@ -8,7 +8,7 @@
8
8
  A Java Language Server that provides two things in one:
9
9
 
10
10
  1. **Full Java language support** — completions, hover, go-to-definition, compile errors, missing imports — by proxying [Eclipse jdtls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) under the hood
11
- 2. **15 functional programming rules** — catches anti-patterns and suggests Vavr/Lombok/Spring alternatives, all before compilation
11
+ 2. **16 functional programming rules** — catches anti-patterns and suggests Vavr/Lombok/Spring alternatives, all before compilation
12
12
  3. **Code actions (quick fixes)** — automated refactoring via LSP `textDocument/codeAction`, with machine-readable diagnostic metadata for AI agents
13
13
 
14
14
  Designed for teams using **Vavr**, **Lombok**, and **Spring** with a functional-first approach.
@@ -24,7 +24,7 @@ When [jdtls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) is installed, the
24
24
  - Type mismatches
25
25
  - Completions, hover, go-to-definition, find references
26
26
 
27
- Install jdtls separately: `brew install jdtls` (requires JDK 21+). Without jdtls, the server runs in standalone mode — the 12 custom rules still work, but you won't get compile errors or completions.
27
+ Install jdtls separately: `brew install jdtls` (requires JDK 21+). The server auto-detects a Java 21+ installation even when the IDE's project SDK is older (e.g., Java 8) by probing `JDTLS_JAVA_HOME`, `JAVA_HOME`, `/usr/libexec/java_home -v 21+` (macOS), and `java` on PATH. Without jdtls, the server runs in standalone mode — the 16 custom rules still work, but you won't get compile errors or completions.
28
28
 
29
29
  ### Functional programming rules
30
30
 
@@ -44,6 +44,7 @@ Install jdtls separately: `brew install jdtls` (requires JDK 21+). Without jdtls
44
44
  | `component-annotation` | `@Component`/`@Service`/`@Repository` | `@Configuration` + `@Bean` | — |
45
45
  | `frozen-mutation` | Mutation on `List.of()`/`Collections.unmodifiable*` | `io.vavr.collection.List` | ✅ |
46
46
  | `null-check-to-monadic` | `if (x != null) { return x.foo(); }` | `Option.of(x).map(...)` | ✅ |
47
+ | `try-catch-to-monadic` | `try { return x(); } catch (E e) { return d; }` | `Try.of(() -> x()).getOrElse(d)` | ✅ |
47
48
  | `impure-method` | Method mixing pure logic with side-effects | Extract pure logic; wrap IO in `Try` | — |
48
49
 
49
50
  ## Install
@@ -203,7 +204,7 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
203
204
  - `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
204
205
 
205
206
  **Spring-aware behavior:**
206
- - `throw-statement` and `catch-rethrow` are automatically suppressed inside `@Bean` methods
207
+ - `throw-statement`, `catch-rethrow`, and `try-catch-to-monadic` are automatically suppressed inside `@Bean` methods
207
208
  - `mutable-dto` suggests `@ConstructorBinding` instead of `@Value` when the class has `@ConfigurationProperties`
208
209
 
209
210
  **Inline suppression** with `@SuppressWarnings`:
@@ -231,8 +232,9 @@ The server provides LSP code actions (`textDocument/codeAction`) that automatica
231
232
  | Rule | Code Action | What it does |
232
233
  |------|-------------|--------------|
233
234
  | `frozen-mutation` | Switch to Vavr Immutable Collection | Rewrites `List.of()` → `io.vavr.collection.List.of()`, `.add(x)` → `= list.append(x)`, adds import |
234
- | `null-check-to-monadic` | Convert to Option monadic flow | Rewrites `if (x != null) { return x.foo(); }` → `Option.of(x).map(...).getOrNull()`, adds import |
235
+ | `null-check-to-monadic` | Convert to Option monadic flow | Rewrites `if (x != null) { return x.foo(); }` → `Option.of(x).map(...)`, supports chained fallbacks via `.orElse()`, adds import |
235
236
  | `null-return` | Replace with Option.none() | Rewrites `return null` → `return Option.none()`, adds import |
237
+ | `try-catch-to-monadic` | Convert try/catch to Try monadic flow | Rewrites `try { return expr; } catch (E e) { return default; }` → `Try.of(() -> expr).getOrElse(default)`. Supports 3 patterns: simple default (eager/lazy `.getOrElse`), logging + default (`.onFailure().getOrElse`), and exception-dependent recovery (`.recover(E.class, ...).get()`). Skips try-with-resources, finally, multi-catch, and union types. Adds import. |
236
238
 
237
239
  Quick fixes automatically add the required Vavr import if it's not already present. Disable auto-import with `"autoImportVavr": false` in config.
238
240
 
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: java-functional-lsp
3
- description: Java LSP with full language support (completions, hover, go-to-def, compile errors) plus 15 functional programming rules with automated quick fixes. Auto-invoke when setting up Java language support or discussing Java linting configuration.
3
+ description: Java LSP with full language support (completions, hover, go-to-def, compile errors) plus 16 functional programming rules with automated quick fixes. Auto-invoke when setting up Java language support or discussing Java linting configuration.
4
4
  allowed-tools: Bash
5
5
  disable-model-invocation: true
6
6
  ---
7
7
 
8
8
  # Java Functional LSP
9
9
 
10
- A Java LSP server that wraps jdtls and adds 15 functional programming rules with code actions (quick fixes). Gives you **full Java language support** (completions, hover, go-to-def, compile errors) **plus** custom diagnostics with machine-readable metadata for AI agents — all before compilation.
10
+ A Java LSP server that wraps jdtls and adds 16 functional programming rules with code actions (quick fixes). Gives you **full Java language support** (completions, hover, go-to-def, compile errors) **plus** custom diagnostics with machine-readable metadata for AI agents — all before compilation.
11
11
 
12
12
  ## Prerequisites
13
13
 
@@ -22,7 +22,7 @@ brew install jdtls
22
22
 
23
23
  Without jdtls, the server runs in standalone mode — custom rules still work, but no completions/hover/compile errors.
24
24
 
25
- ## Rules (15 checks)
25
+ ## Rules (16 checks)
26
26
 
27
27
  | Rule | Detects | Suggests | Quick Fix |
28
28
  |------|---------|----------|-----------|
@@ -40,6 +40,7 @@ Without jdtls, the server runs in standalone mode — custom rules still work, b
40
40
  | `component-annotation` | `@Component`/`@Service`/`@Repository` | `@Configuration` + `@Bean` | — |
41
41
  | `frozen-mutation` | Mutation on `List.of()`/`Collections.unmodifiable*` | `io.vavr.collection.List` | ✅ |
42
42
  | `null-check-to-monadic` | `if (x != null) { return x.foo(); }` | `Option.of(x).map(...)` | ✅ |
43
+ | `try-catch-to-monadic` | `try { return x(); } catch (E e) { return d; }` | `Try.of(() -> x()).getOrElse(d)` | ✅ |
43
44
  | `impure-method` | Method mixing pure logic with side-effects | Extract pure logic; wrap IO in `Try` | — |
44
45
 
45
46
  ## Code Actions (Quick Fixes)
@@ -47,8 +48,9 @@ Without jdtls, the server runs in standalone mode — custom rules still work, b
47
48
  Rules marked ✅ provide automated `textDocument/codeAction` fixes:
48
49
 
49
50
  - **frozen-mutation** → "Switch to Vavr Immutable Collection" — rewrites type, init, and mutation call to Vavr persistent API, adds import
50
- - **null-check-to-monadic** → "Convert to Option monadic flow" — rewrites `if (x != null)` to `Option.of(x).map(...)`, adds import
51
+ - **null-check-to-monadic** → "Convert to Option monadic flow" — rewrites `if (x != null)` to `Option.of(x).map(...)`, supports chained fallbacks via `.orElse()`, adds import
51
52
  - **null-return** → "Replace with Option.none()" — replaces `null` with `Option.none()`, adds import
53
+ - **try-catch-to-monadic** → "Convert try/catch to Try monadic flow" — rewrites `try { return expr; } catch (E e) { return default; }` to `Try.of(() -> expr).getOrElse(default)`. Supports 3 patterns: simple default, logging + default (`.onFailure().getOrElse`), and exception-dependent recovery (`.recover(E.class, ...).get()`). Skips try-with-resources, finally, multi-catch, union types. Adds import.
52
54
 
53
55
  ## Agent-Ready Diagnostics
54
56
 
@@ -85,7 +87,7 @@ Create `.java-functional-lsp.json` in your project root:
85
87
  - `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
86
88
  - `autoImportVavr` — quick fixes auto-add Vavr imports (default: `true`)
87
89
  - `strictPurity` — `impure-method` uses WARNING instead of HINT (default: `false`)
88
- - `throw-statement`/`catch-rethrow` auto-suppressed in `@Bean` methods
90
+ - `throw-statement`/`catch-rethrow`/`try-catch-to-monadic` auto-suppressed in `@Bean` methods
89
91
  - `mutable-dto` suggests `@ConstructorBinding` for `@ConfigurationProperties` classes
90
92
  - Inline suppression: `@SuppressWarnings("java-functional-lsp:rule-id")` on any declaration
91
93
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "java-functional-lsp"
7
- version = "0.7.1"
7
+ version = "0.7.2"
8
8
  description = "Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -63,8 +63,11 @@ testpaths = ["tests"]
63
63
  python_files = ["test_*.py"]
64
64
  python_classes = ["Test*"]
65
65
  python_functions = ["test_*"]
66
- addopts = "--cov=java_functional_lsp --cov-report=term-missing --cov-fail-under=60"
66
+ addopts = "--cov=java_functional_lsp --cov-report=term-missing --cov-fail-under=80"
67
67
  asyncio_mode = "auto"
68
+ markers = [
69
+ "e2e: end-to-end tests that spawn a real jdtls subprocess (require jdtls + Java 21+; skipped when unavailable)",
70
+ ]
68
71
 
69
72
  [tool.ruff]
70
73
  line-length = 120
@@ -1,3 +1,3 @@
1
1
  """java-functional-lsp: A Java LSP server enforcing functional programming best practices."""
2
2
 
3
- __version__ = "0.7.1"
3
+ __version__ = "0.7.2"
@@ -13,8 +13,8 @@ import sys
13
13
  from pathlib import Path
14
14
  from typing import Any
15
15
 
16
- import cattrs
17
16
  from lsprotocol import types as lsp
17
+ from lsprotocol.converters import get_converter
18
18
  from pygls.lsp.server import LanguageServer
19
19
  from pygls.uris import to_fs_path
20
20
 
@@ -45,7 +45,14 @@ _ANALYZERS: list[Analyzer] = [
45
45
  FunctionalChecker(),
46
46
  ]
47
47
 
48
- _converter = cattrs.Converter()
48
+ #: LSP-aware cattrs converter. Unstructures to the LSP JSON shape
49
+ #: (camelCase field names, discriminated unions, None-field pruning) and
50
+ #: correspondingly structures from the same shape. Using a vanilla
51
+ #: ``cattrs.Converter()`` here emits snake_case field names (``text_document``
52
+ #: instead of ``textDocument``), which breaks request forwarding to jdtls —
53
+ #: jdtls then sees a null ``TextDocumentIdentifier`` and throws NPEs during
54
+ #: go-to-definition, references, etc.
55
+ _converter = get_converter()
49
56
 
50
57
 
51
58
  class JavaFunctionalLspServer(LanguageServer):