java-functional-lsp 0.4.0__tar.gz → 0.4.1__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 (55) hide show
  1. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.claude-plugin/plugin.json +1 -1
  2. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/PKG-INFO +30 -2
  3. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/README.md +29 -1
  4. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/SKILL.md +17 -2
  5. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/editors/vscode/package.json +1 -1
  6. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/hooks/java_linter_reminder.py +3 -5
  7. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/pyproject.toml +1 -1
  8. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/scripts/generate-formula.py +6 -6
  9. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/__init__.py +1 -1
  10. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/server.py +43 -13
  11. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/CODEOWNERS +0 -0
  12. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  13. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  14. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  15. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/SECURITY.md +0 -0
  16. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/dependabot.yml +0 -0
  17. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/release-drafter.yml +0 -0
  18. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/workflows/publish.yml +0 -0
  19. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/workflows/release-drafter.yml +0 -0
  20. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/workflows/stale.yml +0 -0
  21. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/workflows/test.yml +0 -0
  22. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/workflows/update-homebrew.yml +0 -0
  23. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.github/workflows/vscode-ext.yml +0 -0
  24. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/.gitignore +0 -0
  25. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/CONTRIBUTING.md +0 -0
  26. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/LICENSE +0 -0
  27. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/commands/lint-java.md +0 -0
  28. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/editors/intellij/README.md +0 -0
  29. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/editors/intellij/lsp4ij-template.json +0 -0
  30. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/editors/vscode/.vscodeignore +0 -0
  31. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/editors/vscode/README.md +0 -0
  32. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/editors/vscode/package-lock.json +0 -0
  33. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/editors/vscode/src/extension.ts +0 -0
  34. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/editors/vscode/tsconfig.json +0 -0
  35. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/hooks/hooks.json +0 -0
  36. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/scripts/ensure-lsp.sh +0 -0
  37. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/analyzers/__init__.py +0 -0
  38. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/analyzers/base.py +0 -0
  39. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
  40. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
  41. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
  42. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
  43. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/cli.py +0 -0
  44. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/src/java_functional_lsp/proxy.py +0 -0
  45. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/__init__.py +0 -0
  46. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/conftest.py +0 -0
  47. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/test_base.py +0 -0
  48. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/test_cli.py +0 -0
  49. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/test_config.py +0 -0
  50. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/test_exception_checker.py +0 -0
  51. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/test_mutation_checker.py +0 -0
  52. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/test_null_checker.py +0 -0
  53. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/test_proxy.py +0 -0
  54. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/tests/test_spring_checker.py +0 -0
  55. {java_functional_lsp-0.4.0 → java_functional_lsp-0.4.1}/uv.lock +0 -0
@@ -1,5 +1,5 @@
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.0"
4
+ "version": "0.4.1"
5
5
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: java-functional-lsp
3
- Version: 0.4.0
3
+ Version: 0.4.1
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
@@ -109,7 +109,18 @@ See [editors/intellij/README.md](editors/intellij/README.md) for detailed instru
109
109
 
110
110
  ### Claude Code
111
111
 
112
- Install as a plugin directly from GitHub:
112
+ **Step 1: Enable LSP support** (required, one-time):
113
+
114
+ Add to `~/.claude/settings.json`:
115
+ ```json
116
+ {
117
+ "env": {
118
+ "ENABLE_LSP_TOOL": "1"
119
+ }
120
+ }
121
+ ```
122
+
123
+ **Step 2: Install the plugin:**
113
124
 
114
125
  ```bash
115
126
  claude plugin add https://github.com/aviadshiber/java-functional-lsp.git
@@ -130,6 +141,23 @@ Or manually add to your Claude Code config:
130
141
  }
131
142
  ```
132
143
 
144
+ **Step 3: Nudge Claude to use diagnostics** (recommended):
145
+
146
+ Add to your project's `CLAUDE.md`:
147
+ ```markdown
148
+ After writing or editing Java code, check LSP diagnostics before moving on.
149
+ Fix any violations immediately — do not explain, just apply the fix.
150
+ ```
151
+
152
+ **Troubleshooting:**
153
+
154
+ | Issue | Fix |
155
+ |-------|-----|
156
+ | No diagnostics appear | Ensure `ENABLE_LSP_TOOL=1` is set, restart Claude Code |
157
+ | "java-functional-lsp not found" | Run `brew install aviadshiber/tap/java-functional-lsp` |
158
+ | Plugin not active | Run `claude plugin list` to verify, then `/reload-plugins` |
159
+ | Diagnostics slow on first open | Normal — tree-sitter parses on first load, then incremental |
160
+
133
161
  ### Other Editors
134
162
 
135
163
  Any LSP client that supports stdio transport can use this server. Point it to the `java-functional-lsp` command for `java` files.
@@ -81,7 +81,18 @@ See [editors/intellij/README.md](editors/intellij/README.md) for detailed instru
81
81
 
82
82
  ### Claude Code
83
83
 
84
- Install as a plugin directly from GitHub:
84
+ **Step 1: Enable LSP support** (required, one-time):
85
+
86
+ Add to `~/.claude/settings.json`:
87
+ ```json
88
+ {
89
+ "env": {
90
+ "ENABLE_LSP_TOOL": "1"
91
+ }
92
+ }
93
+ ```
94
+
95
+ **Step 2: Install the plugin:**
85
96
 
86
97
  ```bash
87
98
  claude plugin add https://github.com/aviadshiber/java-functional-lsp.git
@@ -102,6 +113,23 @@ Or manually add to your Claude Code config:
102
113
  }
103
114
  ```
104
115
 
116
+ **Step 3: Nudge Claude to use diagnostics** (recommended):
117
+
118
+ Add to your project's `CLAUDE.md`:
119
+ ```markdown
120
+ After writing or editing Java code, check LSP diagnostics before moving on.
121
+ Fix any violations immediately — do not explain, just apply the fix.
122
+ ```
123
+
124
+ **Troubleshooting:**
125
+
126
+ | Issue | Fix |
127
+ |-------|-----|
128
+ | No diagnostics appear | Ensure `ENABLE_LSP_TOOL=1` is set, restart Claude Code |
129
+ | "java-functional-lsp not found" | Run `brew install aviadshiber/tap/java-functional-lsp` |
130
+ | Plugin not active | Run `claude plugin list` to verify, then `/reload-plugins` |
131
+ | Diagnostics slow on first open | Normal — tree-sitter parses on first load, then incremental |
132
+
105
133
  ### Other Editors
106
134
 
107
135
  Any LSP client that supports stdio transport can use this server. Point it to the `java-functional-lsp` command for `java` files.
@@ -74,8 +74,23 @@ To release a new version:
74
74
  5. CI automatically publishes to PyPI and builds the VS Code extension `.vsix`
75
75
  6. Run `python3 scripts/generate-formula.py <version>` and update the Homebrew tap
76
76
 
77
+ ## Enabling LSP in Claude Code
78
+
79
+ LSP support requires `ENABLE_LSP_TOOL=1` in `~/.claude/settings.json`:
80
+ ```json
81
+ { "env": { "ENABLE_LSP_TOOL": "1" } }
82
+ ```
83
+
84
+ To nudge Claude to act on diagnostics, add to your project's `CLAUDE.md`:
85
+ ```
86
+ After writing or editing Java code, check LSP diagnostics before moving on.
87
+ Fix any violations immediately — do not explain, just apply the fix.
88
+ ```
89
+
77
90
  ## Troubleshooting
78
91
 
92
+ - **No diagnostics in Claude Code**: Ensure `ENABLE_LSP_TOOL=1` is set, restart Claude Code
79
93
  - **"java-functional-lsp not found"**: Run `brew install aviadshiber/tap/java-functional-lsp`
80
- - **No completions/hover**: Install jdtls: `brew install jdtls`
81
- - **Too many warnings**: Create `.java-functional-lsp.json` to tune severity or disable noisy rules
94
+ - **No completions/hover**: Install jdtls: `brew install jdtls` (requires JDK 21+)
95
+ - **Too many warnings**: Create `.java-functional-lsp.json` with `excludes` or per-rule severity
96
+ - **Plugin not active**: Run `claude plugin list` to verify, then `/reload-plugins`
@@ -2,7 +2,7 @@
2
2
  "name": "java-functional-lsp",
3
3
  "displayName": "Java Functional LSP",
4
4
  "description": "Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions",
5
- "version": "0.4.0",
5
+ "version": "0.4.1",
6
6
  "publisher": "aviadshiber",
7
7
  "license": "MIT",
8
8
  "engines": {
@@ -21,11 +21,9 @@ def main() -> None:
21
21
  "hookSpecificOutput": {
22
22
  "hookEventName": "PostToolUse",
23
23
  "additionalContext": (
24
- "CHECK for <new-diagnostics> from java-functional-lsp above. "
25
- "If any appear, you MUST acknowledge them and suggest fixes: "
26
- "null Option/Either, throw Either.left()/Try.of(), "
27
- "mutable → final + functional transforms, loops → .map()/.filter()/.flatMap(), "
28
- "@Data → @Value, @Autowired → constructor injection, @Component → @Configuration+@Bean."
24
+ "If <new-diagnostics> from java-functional-lsp appear above, "
25
+ "fix each violation immediately. Do not explain or list them "
26
+ "just apply the fix in your next Edit."
29
27
  ),
30
28
  }
31
29
  }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "java-functional-lsp"
7
- version = "0.4.0"
7
+ version = "0.4.1"
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" }
@@ -10,31 +10,31 @@ import sys
10
10
  import urllib.request
11
11
 
12
12
 
13
- def get_pypi_sha256(package: str, version: str) -> str:
14
- """Get the sdist sha256 for a package from PyPI."""
13
+ def get_pypi_sdist_info(package: str, version: str) -> tuple[str, str]:
14
+ """Get the sdist URL and sha256 for a package from PyPI."""
15
15
  url = f"https://pypi.org/pypi/{package}/{version}/json"
16
16
  with urllib.request.urlopen(url) as resp:
17
17
  data = json.loads(resp.read())
18
18
 
19
19
  for file_info in data.get("urls", []):
20
20
  if file_info["filename"].endswith(".tar.gz"):
21
- return file_info["digests"]["sha256"]
21
+ return file_info["url"], file_info["digests"]["sha256"]
22
22
 
23
23
  for file_info in data.get("urls", []):
24
24
  if file_info["packagetype"] == "sdist":
25
- return file_info["digests"]["sha256"]
25
+ return file_info["url"], file_info["digests"]["sha256"]
26
26
 
27
27
  raise ValueError(f"No sdist found for {package}=={version}")
28
28
 
29
29
 
30
30
  def generate_formula(version: str) -> str:
31
31
  """Generate the Homebrew formula."""
32
- sha256 = get_pypi_sha256("java-functional-lsp", version)
32
+ sdist_url, sha256 = get_pypi_sdist_info("java-functional-lsp", version)
33
33
 
34
34
  return f'''class JavaFunctionalLsp < Formula
35
35
  desc "Java LSP server enforcing functional programming best practices"
36
36
  homepage "https://github.com/aviadshiber/java-functional-lsp"
37
- url "https://files.pythonhosted.org/packages/source/j/java-functional-lsp/java_functional_lsp-{version}.tar.gz"
37
+ url "{sdist_url}"
38
38
  sha256 "{sha256}"
39
39
  license "MIT"
40
40
 
@@ -1,3 +1,3 @@
1
1
  """java-functional-lsp: A Java LSP server enforcing functional programming best practices."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.4.1"
@@ -6,15 +6,17 @@ Proxies to jdtls for full Java language features (completions, hover, go-to-def)
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import asyncio
9
10
  import json
10
11
  import logging
12
+ import sys
11
13
  from pathlib import Path
12
14
  from typing import Any
13
- from urllib.parse import unquote, urlparse
14
15
 
15
16
  import cattrs
16
17
  from lsprotocol import types as lsp
17
18
  from pygls.lsp.server import LanguageServer
19
+ from pygls.uris import to_fs_path
18
20
 
19
21
  from .analyzers.base import Analyzer, Severity, get_parser, is_excluded
20
22
  from .analyzers.base import Diagnostic as LintDiagnostic
@@ -59,11 +61,17 @@ class JavaFunctionalLspServer(LanguageServer):
59
61
 
60
62
  server = JavaFunctionalLspServer()
61
63
 
64
+ # Debounce state for didChange events (only affects human typing in IDEs, not agents)
65
+ _pending: dict[str, asyncio.Task[None]] = {}
66
+ _DEBOUNCE_SECONDS = 0.15
62
67
 
63
- def _uri_to_path(uri: str) -> str:
64
- """Convert a file:// URI to a filesystem path."""
65
- parsed = urlparse(uri)
66
- return unquote(parsed.path)
68
+
69
+ def _handle_exception(exc_type: type[BaseException], exc_value: BaseException, exc_tb: Any) -> None:
70
+ """Log uncaught exceptions for crash debugging."""
71
+ logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_tb))
72
+
73
+
74
+ sys.excepthook = _handle_exception
67
75
 
68
76
 
69
77
  def _load_config(workspace_root: str | None) -> dict[str, Any]:
@@ -100,7 +108,7 @@ def _analyze_document(source_text: str, uri: str = "") -> list[lsp.Diagnostic]:
100
108
  if uri:
101
109
  excludes: list[str] = server._config.get("excludes", [])
102
110
  if excludes:
103
- path_str = _uri_to_path(uri)
111
+ path_str = to_fs_path(uri) or uri
104
112
  if is_excluded(path_str, excludes):
105
113
  return []
106
114
  source_bytes = source_text.encode("utf-8")
@@ -184,7 +192,7 @@ def on_initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
184
192
 
185
193
  root = None
186
194
  if params.root_uri:
187
- root = _uri_to_path(params.root_uri)
195
+ root = to_fs_path(params.root_uri)
188
196
  elif params.root_path:
189
197
  root = params.root_path
190
198
 
@@ -223,28 +231,50 @@ async def on_initialized(params: lsp.InitializedParams) -> None:
223
231
  # --- Document sync (forward to jdtls + run custom analyzers) ---
224
232
 
225
233
 
234
+ async def _deferred_validate(uri: str) -> None:
235
+ """Debounced validation — waits before analyzing to batch rapid edits."""
236
+ await asyncio.sleep(_DEBOUNCE_SECONDS)
237
+ await asyncio.to_thread(_publish_diagnostics, uri)
238
+
239
+
226
240
  @server.feature(lsp.TEXT_DOCUMENT_DID_OPEN)
227
241
  async def on_did_open(params: lsp.DidOpenTextDocumentParams) -> None:
228
- """Forward to jdtls and analyze."""
242
+ """Forward to jdtls and analyze immediately."""
229
243
  if server._proxy.is_available:
230
244
  await server._proxy.send_notification("textDocument/didOpen", _serialize_params(params))
231
- _publish_diagnostics(params.text_document.uri)
245
+ await asyncio.to_thread(_publish_diagnostics, params.text_document.uri)
232
246
 
233
247
 
234
248
  @server.feature(lsp.TEXT_DOCUMENT_DID_CHANGE)
235
249
  async def on_did_change(params: lsp.DidChangeTextDocumentParams) -> None:
236
- """Forward to jdtls and re-analyze."""
250
+ """Forward to jdtls and schedule debounced re-analysis."""
251
+ uri = params.text_document.uri
237
252
  if server._proxy.is_available:
238
253
  await server._proxy.send_notification("textDocument/didChange", _serialize_params(params))
239
- _publish_diagnostics(params.text_document.uri)
254
+ # Cancel pending validation, schedule new one (150ms debounce for IDE typing)
255
+ if uri in _pending:
256
+ _pending[uri].cancel()
257
+ _pending[uri] = asyncio.create_task(_deferred_validate(uri))
240
258
 
241
259
 
242
260
  @server.feature(lsp.TEXT_DOCUMENT_DID_SAVE)
243
261
  async def on_did_save(params: lsp.DidSaveTextDocumentParams) -> None:
244
- """Forward to jdtls and re-analyze."""
262
+ """Forward to jdtls and re-analyze immediately (no debounce on save)."""
245
263
  if server._proxy.is_available:
246
264
  await server._proxy.send_notification("textDocument/didSave", _serialize_params(params))
247
- _publish_diagnostics(params.text_document.uri)
265
+ await asyncio.to_thread(_publish_diagnostics, params.text_document.uri)
266
+
267
+
268
+ @server.feature(lsp.TEXT_DOCUMENT_DID_CLOSE)
269
+ async def on_did_close(params: lsp.DidCloseTextDocumentParams) -> None:
270
+ """Clean up cached state and forward to jdtls."""
271
+ uri = params.text_document.uri
272
+ server._trees.pop(uri, None)
273
+ if uri in _pending:
274
+ _pending[uri].cancel()
275
+ del _pending[uri]
276
+ if server._proxy.is_available:
277
+ await server._proxy.send_notification("textDocument/didClose", _serialize_params(params))
248
278
 
249
279
 
250
280
  # --- Forwarded features (jdtls passthrough) ---