java-functional-lsp 0.11.2__tar.gz → 0.12.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.
Files changed (85) hide show
  1. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.claude-plugin/plugin.json +2 -5
  2. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/workflows/publish.yml +2 -2
  3. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/workflows/release-drafter.yml +1 -1
  4. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/workflows/stale.yml +1 -1
  5. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/workflows/test.yml +5 -5
  6. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/workflows/update-homebrew.yml +3 -3
  7. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/workflows/vscode-ext.yml +2 -2
  8. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/PKG-INFO +13 -18
  9. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/README.md +11 -16
  10. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/SKILL.md +5 -3
  11. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/commands/lint-java.md +1 -1
  12. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/editors/vscode/package-lock.json +190 -159
  13. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/editors/vscode/package.json +1 -1
  14. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/pyproject.toml +2 -2
  15. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/__init__.py +1 -1
  16. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/analyzers/base.py +29 -0
  17. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/analyzers/mutation_checker.py +46 -6
  18. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_config.py +27 -1
  19. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_functional_checker.py +16 -0
  20. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_mutation_checker.py +61 -0
  21. java_functional_lsp-0.12.0/tests/test_plugin_manifest.py +33 -0
  22. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/uv.lock +2 -2
  23. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.githooks/pre-commit +0 -0
  24. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.githooks/pre-push +0 -0
  25. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/CODEOWNERS +0 -0
  26. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  27. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  28. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  29. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/SECURITY.md +0 -0
  30. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/dependabot.yml +0 -0
  31. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.github/release-drafter.yml +0 -0
  32. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/.gitignore +0 -0
  33. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/CONTRIBUTING.md +0 -0
  34. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/LICENSE +0 -0
  35. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/editors/intellij/README.md +0 -0
  36. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/editors/intellij/lsp4ij-template.json +0 -0
  37. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/editors/vscode/.vscodeignore +0 -0
  38. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/editors/vscode/README.md +0 -0
  39. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/editors/vscode/src/extension.ts +0 -0
  40. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/editors/vscode/tsconfig.json +0 -0
  41. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/hooks/hooks.json +0 -0
  42. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/hooks/java_linter_reminder.py +0 -0
  43. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/hooks/post_tool_lint.py +0 -0
  44. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/scripts/ensure-lsp.sh +0 -0
  45. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/scripts/generate-formula.py +0 -0
  46. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/__main__.py +0 -0
  47. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/analyzers/__init__.py +0 -0
  48. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
  49. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/analyzers/functional_checker.py +0 -0
  50. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
  51. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
  52. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/capabilities/__init__.py +0 -0
  53. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/capabilities/handler_wiring.py +0 -0
  54. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/capabilities/negotiator.py +0 -0
  55. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/capabilities/probe.py +0 -0
  56. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/capabilities/registry.py +0 -0
  57. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/capabilities/static_builder.py +0 -0
  58. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/cli.py +0 -0
  59. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/fixes.py +0 -0
  60. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/merkle.py +0 -0
  61. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/proxy.py +0 -0
  62. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/src/java_functional_lsp/server.py +0 -0
  63. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/__init__.py +0 -0
  64. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/conftest.py +0 -0
  65. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_base.py +0 -0
  66. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_capabilities/__init__.py +0 -0
  67. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_capabilities/test_handler_wiring.py +0 -0
  68. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_capabilities/test_negotiator.py +0 -0
  69. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_capabilities/test_probe.py +0 -0
  70. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_capabilities/test_registry.py +0 -0
  71. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_capabilities/test_static_builder.py +0 -0
  72. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_cli.py +0 -0
  73. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_e2e.py +0 -0
  74. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_e2e_jdtls.py +0 -0
  75. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_exception_checker.py +0 -0
  76. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_fixes.py +0 -0
  77. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_hooks.py +0 -0
  78. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_merkle.py +0 -0
  79. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_merkle_proxy.py +0 -0
  80. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_null_checker.py +0 -0
  81. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_proxy.py +0 -0
  82. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_server.py +0 -0
  83. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_spring_checker.py +0 -0
  84. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_suppress.py +0 -0
  85. {java_functional_lsp-0.11.2 → java_functional_lsp-0.12.0}/tests/test_version.py +0 -0
@@ -1,16 +1,13 @@
1
1
  {
2
2
  "name": "java-functional-lsp",
3
3
  "description": "Java LSP with functional programming rules enforcement — null safety, immutability, no exceptions, Spring best practices. Wraps jdtls for full Java language support.",
4
- "version": "0.4.2",
4
+ "version": "0.4.3",
5
5
  "lspServers": {
6
6
  "java-functional": {
7
7
  "command": "java-functional-lsp",
8
8
  "extensionToLanguage": {
9
9
  ".java": "java"
10
- },
11
- "startupTimeout": 120000,
12
- "restartOnCrash": true,
13
- "maxRestarts": 5
10
+ }
14
11
  }
15
12
  }
16
13
  }
@@ -13,9 +13,9 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  environment: pypi
15
15
  steps:
16
- - uses: actions/checkout@v6
16
+ - uses: actions/checkout@v7
17
17
  - name: Set up Python
18
- uses: actions/setup-python@v6
18
+ uses: actions/setup-python@v7
19
19
  with:
20
20
  python-version: "3.12"
21
21
  - name: Install build dependencies
@@ -12,6 +12,6 @@ jobs:
12
12
  update-release-draft:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: release-drafter/release-drafter@v7
15
+ - uses: release-drafter/release-drafter@v7.6.0
16
16
  env:
17
17
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -12,7 +12,7 @@ jobs:
12
12
  stale:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
- - uses: actions/stale@v10
15
+ - uses: actions/stale@v11
16
16
  with:
17
17
  stale-issue-message: >
18
18
  This issue has been automatically marked as stale because it has not
@@ -15,9 +15,9 @@ jobs:
15
15
  os: [ubuntu-latest, macos-latest]
16
16
  python-version: ["3.10", "3.11", "3.12", "3.13"]
17
17
  steps:
18
- - uses: actions/checkout@v6
18
+ - uses: actions/checkout@v7
19
19
  - name: Set up Python ${{ matrix.python-version }}
20
- uses: actions/setup-python@v6
20
+ uses: actions/setup-python@v7
21
21
  with:
22
22
  python-version: ${{ matrix.python-version }}
23
23
  - name: Install uv
@@ -48,13 +48,13 @@ jobs:
48
48
  # AND jdtls request forwarding end-to-end. The unit matrix above
49
49
  # provides fast Python-version-specific feedback without jdtls.
50
50
  steps:
51
- - uses: actions/checkout@v6
51
+ - uses: actions/checkout@v7
52
52
  - name: Set up Python 3.12
53
- uses: actions/setup-python@v6
53
+ uses: actions/setup-python@v7
54
54
  with:
55
55
  python-version: "3.12"
56
56
  - name: Install Java 21
57
- uses: actions/setup-java@v5
57
+ uses: actions/setup-java@v6
58
58
  with:
59
59
  distribution: temurin
60
60
  java-version: "21"
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
13
13
  steps:
14
- - uses: actions/checkout@v6
14
+ - uses: actions/checkout@v7
15
15
  - name: Get release version
16
16
  id: version
17
17
  env:
@@ -20,7 +20,7 @@ jobs:
20
20
  VERSION=$(gh api "repos/${{ github.repository }}/releases/latest" --jq .tag_name)
21
21
  echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT"
22
22
  - name: Set up Python
23
- uses: actions/setup-python@v6
23
+ uses: actions/setup-python@v7
24
24
  with:
25
25
  python-version: "3.12"
26
26
  - name: Wait for PyPI availability
@@ -43,7 +43,7 @@ jobs:
43
43
  echo "Generated formula:"
44
44
  cat java-functional-lsp.rb
45
45
  - name: Checkout tap repo
46
- uses: actions/checkout@v6
46
+ uses: actions/checkout@v7
47
47
  with:
48
48
  repository: aviadshiber/homebrew-tap
49
49
  token: ${{ secrets.TAP_GITHUB_TOKEN }}
@@ -18,10 +18,10 @@ jobs:
18
18
  run:
19
19
  working-directory: editors/vscode
20
20
  steps:
21
- - uses: actions/checkout@v6
21
+ - uses: actions/checkout@v7
22
22
 
23
23
  - name: Set up Node.js
24
- uses: actions/setup-node@v6
24
+ uses: actions/setup-node@v7
25
25
  with:
26
26
  node-version: "20"
27
27
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: java-functional-lsp
3
- Version: 0.11.2
3
+ Version: 0.12.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
@@ -66,7 +66,7 @@ Install jdtls separately: `brew install jdtls` (requires JDK 21+). The server au
66
66
  | `catch-rethrow` | catch block that wraps + rethrows | `Try.of().toEither()` | — |
67
67
  | `mutable-variable` | Local variable reassignment | Final variables + functional transforms | — |
68
68
  | `imperative-loop` | `for`/`while` loops | `.map()`/`.filter()`/`.flatMap()`/`.foldLeft()` | — |
69
- | `mutable-dto` | `@Data` or `@Setter` on class | `@Value` (immutable) | ✅ |
69
+ | `mutable-dto` | `@Data` or `@Setter` on class | `@Value` (immutable); `record` instead at `sourceLevel` 16+ | ✅ |
70
70
  | `imperative-option-unwrap` | `if (opt.isDefined()) { opt.get() }` | `map()`/`flatMap()`/`fold()` | ✅ |
71
71
  | `field-injection` | `@Autowired` on field | Constructor injection | — |
72
72
  | `component-annotation` | `@Component`/`@Service`/`@Repository` | `@Configuration` + `@Bean` | — |
@@ -143,10 +143,7 @@ Add `lspServers` to `~/.claude/settings.json` (the plugin handles this automatic
143
143
  "lspServers": {
144
144
  "java-functional": {
145
145
  "command": "java-functional-lsp",
146
- "extensionToLanguage": { ".java": "java" },
147
- "startupTimeout": 120000,
148
- "restartOnCrash": true,
149
- "maxRestarts": 5
146
+ "extensionToLanguage": { ".java": "java" }
150
147
  }
151
148
  }
152
149
  }
@@ -190,27 +187,19 @@ Or manually add to your Claude Code config:
190
187
  "lspServers": {
191
188
  "java-functional": {
192
189
  "command": "java-functional-lsp",
193
- "extensionToLanguage": { ".java": "java" },
194
- "startupTimeout": 120000,
195
- "restartOnCrash": true,
196
- "maxRestarts": 5
190
+ "extensionToLanguage": { ".java": "java" }
197
191
  }
198
192
  }
199
193
  }
200
194
  ```
201
195
 
202
- (`startupTimeout: 120000` accommodates jdtls cold-start; `restartOnCrash` keeps the server alive across session.)
203
-
204
196
  **Alternative: project-level `.lsp.json`** — instead of installing the plugin or editing global config, add a `.lsp.json` file at your project root:
205
197
 
206
198
  ```json
207
199
  {
208
200
  "java-functional": {
209
201
  "command": "java-functional-lsp",
210
- "extensionToLanguage": { ".java": "java" },
211
- "startupTimeout": 120000,
212
- "restartOnCrash": true,
213
- "maxRestarts": 5
202
+ "extensionToLanguage": { ".java": "java" }
214
203
  }
215
204
  }
216
205
  ```
@@ -258,6 +247,7 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
258
247
  ```json
259
248
  {
260
249
  "excludes": ["**/generated/**", "**/vendor/**"],
250
+ "sourceLevel": 17,
261
251
  "rules": {
262
252
  "null-literal-arg": "warning",
263
253
  "throw-statement": "info",
@@ -270,12 +260,16 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
270
260
  **Options:**
271
261
  - `excludes` — glob patterns for files/directories to skip entirely (supports `**` for multi-segment wildcards)
272
262
  - `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
263
+ - `sourceLevel` — the project's Java language/source level, as an int (e.g. `17`) or version string (`"17"`, legacy `"1.8"`). Also accepted as `javaVersion`. Defaults to `8` if unset, so existing configs are unaffected. Currently only changes the `mutable-dto` recommendation (see below); higher source levels unlock more rewrite targets over time (records, sealed types, pattern matching, text blocks).
273
264
  - `suppressJdtlsPatterns` — list of regex patterns to suppress jdtls diagnostics (see below)
274
265
 
275
266
  **Spring-aware behavior:**
276
267
  - `throw-statement`, `catch-rethrow`, and `try-catch-to-monadic` are automatically suppressed inside `@Bean` methods
277
268
  - `mutable-dto` suggests `@ConstructorBinding` instead of `@Value` when the class has `@ConfigurationProperties`
278
269
 
270
+ **Source-level-aware behavior:**
271
+ - `mutable-dto` recommends a `record` instead of `@Value` once `sourceLevel` is 16 or higher (records became final/non-preview in JDK 16) — a plain immutable DTO is simpler as a built-in `record` with no Lombok dependency. It still calls out `@Value` as the fallback when the class needs `@With`/`@Builder`/`@Jacksonized`, is used as a facade, or relies on AOP proxying (records are `final` and can't be proxied). Below `sourceLevel` 16 (including the default), the message and quick fix are unchanged — `@Value` only.
272
+
279
273
  **Inline suppression** with `@SuppressWarnings`:
280
274
 
281
275
  ```java
@@ -366,7 +360,7 @@ The server provides LSP code actions (`textDocument/codeAction`) that automatica
366
360
  | `null-return` | Replace with Option.none() | Rewrites `return null` → `return Option.none()`, adds import |
367
361
  | `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. |
368
362
  | `imperative-option-unwrap` | Convert to Option.map().getOrElse() | Rewrites `if (opt.isDefined()) return opt.get(); else return X;` → `return opt.map(it -> ...).getOrElse(X);` (lazy `getOrElse(() -> ...)` for non-eager defaults). Bails on missing else or complex bodies. |
369
- | `mutable-dto` | Replace @Data with @Value | Replaces the `@Data` annotation with `@Value` and adds `import lombok.Value`. Skips `@Setter`, `@ConfigurationProperties`, and conflicting Lombok constructor annotations. |
363
+ | `mutable-dto` | Replace @Data with @Value | Replaces the `@Data` annotation with `@Value` and adds `import lombok.Value`. Skips `@Setter`, `@ConfigurationProperties`, and conflicting Lombok constructor annotations. This quick fix is currently `@Value`-only regardless of `sourceLevel` — the diagnostic message/snippet recommend a `record` at `sourceLevel` 16+, but applying that rewrite safely (enumerating fields into record components) isn't automated yet; use the suggested snippet as a manual starting point. |
370
364
 
371
365
  Quick fixes automatically add the required Vavr import if it's not already present. Disable auto-import with `"autoImportVavr": false` in config (`"autoImportLombok": false` for the Lombok import added by the `mutable-dto` fix).
372
366
 
@@ -404,6 +398,7 @@ This lets agents confidently apply fixes without guessing libraries or patterns
404
398
  | `autoImportVavr` | `true` | Quick fixes auto-add Vavr/Option imports |
405
399
  | `autoImportLombok` | `true` | The `mutable-dto` quick fix auto-adds `import lombok.Value` |
406
400
  | `strictPurity` | `false` | When `true`, `impure-method` uses WARNING severity instead of HINT |
401
+ | `sourceLevel` (alias `javaVersion`) | `8` | Java source level; `mutable-dto` recommends `record` over `@Value` at 16+ |
407
402
 
408
403
  > **Note:** The machine-readable `data` payload is always included in diagnostics when available — no configuration needed.
409
404
 
@@ -38,7 +38,7 @@ Install jdtls separately: `brew install jdtls` (requires JDK 21+). The server au
38
38
  | `catch-rethrow` | catch block that wraps + rethrows | `Try.of().toEither()` | — |
39
39
  | `mutable-variable` | Local variable reassignment | Final variables + functional transforms | — |
40
40
  | `imperative-loop` | `for`/`while` loops | `.map()`/`.filter()`/`.flatMap()`/`.foldLeft()` | — |
41
- | `mutable-dto` | `@Data` or `@Setter` on class | `@Value` (immutable) | ✅ |
41
+ | `mutable-dto` | `@Data` or `@Setter` on class | `@Value` (immutable); `record` instead at `sourceLevel` 16+ | ✅ |
42
42
  | `imperative-option-unwrap` | `if (opt.isDefined()) { opt.get() }` | `map()`/`flatMap()`/`fold()` | ✅ |
43
43
  | `field-injection` | `@Autowired` on field | Constructor injection | — |
44
44
  | `component-annotation` | `@Component`/`@Service`/`@Repository` | `@Configuration` + `@Bean` | — |
@@ -115,10 +115,7 @@ Add `lspServers` to `~/.claude/settings.json` (the plugin handles this automatic
115
115
  "lspServers": {
116
116
  "java-functional": {
117
117
  "command": "java-functional-lsp",
118
- "extensionToLanguage": { ".java": "java" },
119
- "startupTimeout": 120000,
120
- "restartOnCrash": true,
121
- "maxRestarts": 5
118
+ "extensionToLanguage": { ".java": "java" }
122
119
  }
123
120
  }
124
121
  }
@@ -162,27 +159,19 @@ Or manually add to your Claude Code config:
162
159
  "lspServers": {
163
160
  "java-functional": {
164
161
  "command": "java-functional-lsp",
165
- "extensionToLanguage": { ".java": "java" },
166
- "startupTimeout": 120000,
167
- "restartOnCrash": true,
168
- "maxRestarts": 5
162
+ "extensionToLanguage": { ".java": "java" }
169
163
  }
170
164
  }
171
165
  }
172
166
  ```
173
167
 
174
- (`startupTimeout: 120000` accommodates jdtls cold-start; `restartOnCrash` keeps the server alive across session.)
175
-
176
168
  **Alternative: project-level `.lsp.json`** — instead of installing the plugin or editing global config, add a `.lsp.json` file at your project root:
177
169
 
178
170
  ```json
179
171
  {
180
172
  "java-functional": {
181
173
  "command": "java-functional-lsp",
182
- "extensionToLanguage": { ".java": "java" },
183
- "startupTimeout": 120000,
184
- "restartOnCrash": true,
185
- "maxRestarts": 5
174
+ "extensionToLanguage": { ".java": "java" }
186
175
  }
187
176
  }
188
177
  ```
@@ -230,6 +219,7 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
230
219
  ```json
231
220
  {
232
221
  "excludes": ["**/generated/**", "**/vendor/**"],
222
+ "sourceLevel": 17,
233
223
  "rules": {
234
224
  "null-literal-arg": "warning",
235
225
  "throw-statement": "info",
@@ -242,12 +232,16 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
242
232
  **Options:**
243
233
  - `excludes` — glob patterns for files/directories to skip entirely (supports `**` for multi-segment wildcards)
244
234
  - `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
235
+ - `sourceLevel` — the project's Java language/source level, as an int (e.g. `17`) or version string (`"17"`, legacy `"1.8"`). Also accepted as `javaVersion`. Defaults to `8` if unset, so existing configs are unaffected. Currently only changes the `mutable-dto` recommendation (see below); higher source levels unlock more rewrite targets over time (records, sealed types, pattern matching, text blocks).
245
236
  - `suppressJdtlsPatterns` — list of regex patterns to suppress jdtls diagnostics (see below)
246
237
 
247
238
  **Spring-aware behavior:**
248
239
  - `throw-statement`, `catch-rethrow`, and `try-catch-to-monadic` are automatically suppressed inside `@Bean` methods
249
240
  - `mutable-dto` suggests `@ConstructorBinding` instead of `@Value` when the class has `@ConfigurationProperties`
250
241
 
242
+ **Source-level-aware behavior:**
243
+ - `mutable-dto` recommends a `record` instead of `@Value` once `sourceLevel` is 16 or higher (records became final/non-preview in JDK 16) — a plain immutable DTO is simpler as a built-in `record` with no Lombok dependency. It still calls out `@Value` as the fallback when the class needs `@With`/`@Builder`/`@Jacksonized`, is used as a facade, or relies on AOP proxying (records are `final` and can't be proxied). Below `sourceLevel` 16 (including the default), the message and quick fix are unchanged — `@Value` only.
244
+
251
245
  **Inline suppression** with `@SuppressWarnings`:
252
246
 
253
247
  ```java
@@ -338,7 +332,7 @@ The server provides LSP code actions (`textDocument/codeAction`) that automatica
338
332
  | `null-return` | Replace with Option.none() | Rewrites `return null` → `return Option.none()`, adds import |
339
333
  | `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. |
340
334
  | `imperative-option-unwrap` | Convert to Option.map().getOrElse() | Rewrites `if (opt.isDefined()) return opt.get(); else return X;` → `return opt.map(it -> ...).getOrElse(X);` (lazy `getOrElse(() -> ...)` for non-eager defaults). Bails on missing else or complex bodies. |
341
- | `mutable-dto` | Replace @Data with @Value | Replaces the `@Data` annotation with `@Value` and adds `import lombok.Value`. Skips `@Setter`, `@ConfigurationProperties`, and conflicting Lombok constructor annotations. |
335
+ | `mutable-dto` | Replace @Data with @Value | Replaces the `@Data` annotation with `@Value` and adds `import lombok.Value`. Skips `@Setter`, `@ConfigurationProperties`, and conflicting Lombok constructor annotations. This quick fix is currently `@Value`-only regardless of `sourceLevel` — the diagnostic message/snippet recommend a `record` at `sourceLevel` 16+, but applying that rewrite safely (enumerating fields into record components) isn't automated yet; use the suggested snippet as a manual starting point. |
342
336
 
343
337
  Quick fixes automatically add the required Vavr import if it's not already present. Disable auto-import with `"autoImportVavr": false` in config (`"autoImportLombok": false` for the Lombok import added by the `mutable-dto` fix).
344
338
 
@@ -376,6 +370,7 @@ This lets agents confidently apply fixes without guessing libraries or patterns
376
370
  | `autoImportVavr` | `true` | Quick fixes auto-add Vavr/Option imports |
377
371
  | `autoImportLombok` | `true` | The `mutable-dto` quick fix auto-adds `import lombok.Value` |
378
372
  | `strictPurity` | `false` | When `true`, `impure-method` uses WARNING severity instead of HINT |
373
+ | `sourceLevel` (alias `javaVersion`) | `8` | Java source level; `mutable-dto` recommends `record` over `@Value` at 16+ |
379
374
 
380
375
  > **Note:** The machine-readable `data` payload is always included in diagnostics when available — no configuration needed.
381
376
 
@@ -34,7 +34,7 @@ Without jdtls, the server runs in standalone mode — custom rules still work, b
34
34
  | `catch-rethrow` | catch wraps + rethrows | `Try.of().toEither()` | — |
35
35
  | `mutable-variable` | Variable reassignment | Final + functional transforms | — |
36
36
  | `imperative-loop` | `for`/`while` loops | `.map()`/`.filter()`/`.flatMap()` | — |
37
- | `mutable-dto` | `@Data` or `@Setter` | `@Value` (immutable) | ✅ |
37
+ | `mutable-dto` | `@Data` or `@Setter` | `record` (source level 16+) or `@Value` (immutable) | ✅ |
38
38
  | `imperative-option-unwrap` | `if (opt.isDefined()) { opt.get() }` | `map()`/`flatMap()`/`fold()` | ✅ |
39
39
  | `field-injection` | `@Autowired` on field | Constructor injection | — |
40
40
  | `component-annotation` | `@Component`/`@Service`/`@Repository` | `@Configuration` + `@Bean` | — |
@@ -53,7 +53,7 @@ Rules marked ✅ provide automated `textDocument/codeAction` fixes:
53
53
  - **null-return** → "Replace with Option.none()" — replaces `null` with `Option.none()`, adds import
54
54
  - **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.
55
55
  - **imperative-option-unwrap** → "Convert to Option.map().getOrElse()" — rewrites `if (opt.isDefined()) return opt.get(); else return X;` to `return opt.map(it -> ...).getOrElse(X);` (lazy supplier for non-eager defaults). Bails on missing else or complex bodies.
56
- - **mutable-dto** → "Replace @Data with @Value" — swaps the annotation and adds `import lombok.Value` (disable with `"autoImportLombok": false`). Skips `@Setter`, `@ConfigurationProperties`, and conflicting Lombok constructor annotations.
56
+ - **mutable-dto** → "Replace @Data with @Value" — swaps the annotation and adds `import lombok.Value` (disable with `"autoImportLombok": false`). Skips `@Setter`, `@ConfigurationProperties`, and conflicting Lombok constructor annotations. At source level 16+ the *diagnostic* recommends a `record` for a plain immutable DTO (with `@Value` as the fallback for `@With`/`@Builder`/`@Jacksonized`/facade/AOP cases); the automated fix still applies `@Value`, so convert to a `record` manually where appropriate.
57
57
 
58
58
  ## Agent-Ready Diagnostics
59
59
 
@@ -77,6 +77,7 @@ Create `.java-functional-lsp.json` in your project root:
77
77
 
78
78
  ```json
79
79
  {
80
+ "sourceLevel": 17,
80
81
  "excludes": ["**/generated/**", "**/vendor/**"],
81
82
  "rules": {
82
83
  "imperative-loop": "hint",
@@ -88,6 +89,7 @@ Create `.java-functional-lsp.json` in your project root:
88
89
  }
89
90
  ```
90
91
 
92
+ - `sourceLevel` (alias `javaVersion`) — the project's Java source level, as an int (`17`) or string (`"17"`, legacy `"1.8"` → 8). Defaults to `8` if unset (existing configs unaffected). At `16+`, `mutable-dto` recommends a `record` over `@Value` for plain DTOs
91
93
  - `excludes` — glob patterns to skip files/directories entirely
92
94
  - `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
93
95
  - `autoImportVavr` — quick fixes auto-add Vavr imports (default: `true`)
@@ -124,7 +126,7 @@ Declare `lspServers` in `~/.claude/settings.json` or in `plugin.json` — Claude
124
126
 
125
127
  For containers or CI, add a `.lsp.json` at the project root instead of installing the plugin:
126
128
  ```json
127
- { "java-functional": { "command": "java-functional-lsp", "extensionToLanguage": { ".java": "java" }, "startupTimeout": 120000, "restartOnCrash": true, "maxRestarts": 5 } }
129
+ { "java-functional": { "command": "java-functional-lsp", "extensionToLanguage": { ".java": "java" } } }
128
130
  ```
129
131
 
130
132
  To nudge Claude to act on diagnostics, add to your project's `CLAUDE.md`:
@@ -35,6 +35,6 @@ $ARGUMENTS - Java files, directories, or glob patterns to lint. If empty, lint a
35
35
  - `throw-statement` → convert to `Either.left()` or `Try.of()`
36
36
  - `mutable-variable` → make final, use functional transforms
37
37
  - `imperative-loop` → replace with `.map()`, `.filter()`, `.flatMap()`
38
- - `mutable-dto` → change `@Data` to `@Value`
38
+ - `mutable-dto` → convert to a `record` (source level 16+) or change `@Data` to `@Value` (follow the diagnostic's recommendation, which is source-level aware)
39
39
  - `field-injection` → convert to constructor injection
40
40
  - `component-annotation` → move to `@Configuration` class with `@Bean`