java-functional-lsp 0.4.2__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/PKG-INFO +43 -4
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/README.md +42 -3
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/SKILL.md +1 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/pyproject.toml +1 -1
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/__init__.py +1 -1
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/analyzers/base.py +55 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/server.py +6 -1
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_e2e.py +25 -0
- java_functional_lsp-0.5.0/tests/test_suppress.py +246 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/uv.lock +1 -1
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.claude-plugin/plugin.json +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.githooks/pre-commit +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.githooks/pre-push +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/CODEOWNERS +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/SECURITY.md +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/dependabot.yml +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/release-drafter.yml +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/workflows/publish.yml +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/workflows/release-drafter.yml +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/workflows/stale.yml +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/workflows/test.yml +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/workflows/update-homebrew.yml +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/workflows/vscode-ext.yml +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.gitignore +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/CONTRIBUTING.md +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/LICENSE +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/commands/lint-java.md +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/intellij/README.md +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/intellij/lsp4ij-template.json +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/vscode/.vscodeignore +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/vscode/README.md +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/vscode/package-lock.json +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/vscode/package.json +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/vscode/src/extension.ts +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/vscode/tsconfig.json +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/hooks/hooks.json +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/hooks/java_linter_reminder.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/scripts/ensure-lsp.sh +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/scripts/generate-formula.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/__main__.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/analyzers/__init__.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/analyzers/exception_checker.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/analyzers/mutation_checker.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/analyzers/null_checker.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/analyzers/spring_checker.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/cli.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/proxy.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/__init__.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/conftest.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_base.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_cli.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_config.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_exception_checker.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_mutation_checker.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_null_checker.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_proxy.py +0 -0
- {java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/tests/test_spring_checker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: java-functional-lsp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.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
|
|
@@ -33,10 +33,28 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
[](https://pypi.org/project/java-functional-lsp/)
|
|
34
34
|
[](https://opensource.org/licenses/MIT)
|
|
35
35
|
|
|
36
|
-
A Java Language Server that
|
|
36
|
+
A Java Language Server that provides two things in one:
|
|
37
|
+
|
|
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. **12 functional programming rules** — catches anti-patterns and suggests Vavr/Lombok/Spring alternatives, all before compilation
|
|
40
|
+
|
|
41
|
+
Designed for teams using **Vavr**, **Lombok**, and **Spring** with a functional-first approach.
|
|
37
42
|
|
|
38
43
|
## What it checks
|
|
39
44
|
|
|
45
|
+
### Java language (via jdtls)
|
|
46
|
+
|
|
47
|
+
When [jdtls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) is installed, the server proxies all standard Java language features:
|
|
48
|
+
|
|
49
|
+
- Compile errors and warnings
|
|
50
|
+
- Missing imports and unresolved symbols
|
|
51
|
+
- Type mismatches
|
|
52
|
+
- Completions, hover, go-to-definition, find references
|
|
53
|
+
|
|
54
|
+
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
|
+
|
|
56
|
+
### Functional programming rules
|
|
57
|
+
|
|
40
58
|
| Rule | Detects | Suggests |
|
|
41
59
|
|------|---------|----------|
|
|
42
60
|
| `null-literal-arg` | `null` passed as method argument | `Option.none()` or default value |
|
|
@@ -64,7 +82,7 @@ pip install java-functional-lsp
|
|
|
64
82
|
# From source
|
|
65
83
|
pip install git+https://github.com/aviadshiber/java-functional-lsp.git
|
|
66
84
|
|
|
67
|
-
# Optional: install jdtls for full Java language support (
|
|
85
|
+
# Optional: install jdtls for full Java language support (see above)
|
|
68
86
|
brew install jdtls
|
|
69
87
|
```
|
|
70
88
|
|
|
@@ -212,9 +230,30 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
|
|
|
212
230
|
- `throw-statement` and `catch-rethrow` are automatically suppressed inside `@Bean` methods
|
|
213
231
|
- `mutable-dto` suggests `@ConstructorBinding` instead of `@Value` when the class has `@ConfigurationProperties`
|
|
214
232
|
|
|
233
|
+
**Inline suppression** with `@SuppressWarnings`:
|
|
234
|
+
|
|
235
|
+
```java
|
|
236
|
+
// Suppress a specific rule on a method
|
|
237
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
238
|
+
public String findUser() { return null; } // no diagnostic
|
|
239
|
+
|
|
240
|
+
// Suppress multiple rules
|
|
241
|
+
@SuppressWarnings({"java-functional-lsp:null-return", "java-functional-lsp:throw-statement"})
|
|
242
|
+
public String findUser() { ... }
|
|
243
|
+
|
|
244
|
+
// Suppress all java-functional-lsp rules
|
|
245
|
+
@SuppressWarnings("java-functional-lsp")
|
|
246
|
+
public String legacyMethod() { ... }
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Works on classes, methods, constructors, fields, and local variables. Suppression applies to the annotated scope — a class-level annotation suppresses all methods within it.
|
|
250
|
+
|
|
215
251
|
## How it works
|
|
216
252
|
|
|
217
|
-
|
|
253
|
+
The server has two layers:
|
|
254
|
+
|
|
255
|
+
- **Custom rules** — uses [tree-sitter](https://tree-sitter.github.io/) with the Java grammar for sub-millisecond AST analysis (~0.4ms per file). No compiler or classpath needed — runs on raw source files.
|
|
256
|
+
- **Java language features** — proxies [Eclipse jdtls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) for compile errors, completions, hover, go-to-definition, and references. Diagnostics from both layers are merged and published together.
|
|
218
257
|
|
|
219
258
|
The server speaks the Language Server Protocol (LSP) via stdio, making it compatible with any LSP client.
|
|
220
259
|
|
|
@@ -5,10 +5,28 @@
|
|
|
5
5
|
[](https://pypi.org/project/java-functional-lsp/)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
A Java Language Server that
|
|
8
|
+
A Java Language Server that provides two things in one:
|
|
9
|
+
|
|
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. **12 functional programming rules** — catches anti-patterns and suggests Vavr/Lombok/Spring alternatives, all before compilation
|
|
12
|
+
|
|
13
|
+
Designed for teams using **Vavr**, **Lombok**, and **Spring** with a functional-first approach.
|
|
9
14
|
|
|
10
15
|
## What it checks
|
|
11
16
|
|
|
17
|
+
### Java language (via jdtls)
|
|
18
|
+
|
|
19
|
+
When [jdtls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) is installed, the server proxies all standard Java language features:
|
|
20
|
+
|
|
21
|
+
- Compile errors and warnings
|
|
22
|
+
- Missing imports and unresolved symbols
|
|
23
|
+
- Type mismatches
|
|
24
|
+
- Completions, hover, go-to-definition, find references
|
|
25
|
+
|
|
26
|
+
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
|
+
|
|
28
|
+
### Functional programming rules
|
|
29
|
+
|
|
12
30
|
| Rule | Detects | Suggests |
|
|
13
31
|
|------|---------|----------|
|
|
14
32
|
| `null-literal-arg` | `null` passed as method argument | `Option.none()` or default value |
|
|
@@ -36,7 +54,7 @@ pip install java-functional-lsp
|
|
|
36
54
|
# From source
|
|
37
55
|
pip install git+https://github.com/aviadshiber/java-functional-lsp.git
|
|
38
56
|
|
|
39
|
-
# Optional: install jdtls for full Java language support (
|
|
57
|
+
# Optional: install jdtls for full Java language support (see above)
|
|
40
58
|
brew install jdtls
|
|
41
59
|
```
|
|
42
60
|
|
|
@@ -184,9 +202,30 @@ Create `.java-functional-lsp.json` in your project root to customize rules:
|
|
|
184
202
|
- `throw-statement` and `catch-rethrow` are automatically suppressed inside `@Bean` methods
|
|
185
203
|
- `mutable-dto` suggests `@ConstructorBinding` instead of `@Value` when the class has `@ConfigurationProperties`
|
|
186
204
|
|
|
205
|
+
**Inline suppression** with `@SuppressWarnings`:
|
|
206
|
+
|
|
207
|
+
```java
|
|
208
|
+
// Suppress a specific rule on a method
|
|
209
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
210
|
+
public String findUser() { return null; } // no diagnostic
|
|
211
|
+
|
|
212
|
+
// Suppress multiple rules
|
|
213
|
+
@SuppressWarnings({"java-functional-lsp:null-return", "java-functional-lsp:throw-statement"})
|
|
214
|
+
public String findUser() { ... }
|
|
215
|
+
|
|
216
|
+
// Suppress all java-functional-lsp rules
|
|
217
|
+
@SuppressWarnings("java-functional-lsp")
|
|
218
|
+
public String legacyMethod() { ... }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Works on classes, methods, constructors, fields, and local variables. Suppression applies to the annotated scope — a class-level annotation suppresses all methods within it.
|
|
222
|
+
|
|
187
223
|
## How it works
|
|
188
224
|
|
|
189
|
-
|
|
225
|
+
The server has two layers:
|
|
226
|
+
|
|
227
|
+
- **Custom rules** — uses [tree-sitter](https://tree-sitter.github.io/) with the Java grammar for sub-millisecond AST analysis (~0.4ms per file). No compiler or classpath needed — runs on raw source files.
|
|
228
|
+
- **Java language features** — proxies [Eclipse jdtls](https://github.com/eclipse-jdtls/eclipse.jdt.ls) for compile errors, completions, hover, go-to-definition, and references. Diagnostics from both layers are merged and published together.
|
|
190
229
|
|
|
191
230
|
The server speaks the Language Server Protocol (LSP) via stdio, making it compatible with any LSP client.
|
|
192
231
|
|
|
@@ -58,6 +58,7 @@ Create `.java-functional-lsp.json` in your project root:
|
|
|
58
58
|
- `rules` — per-rule severity: `error`, `warning` (default), `info`, `hint`, `off`
|
|
59
59
|
- `throw-statement`/`catch-rethrow` auto-suppressed in `@Bean` methods
|
|
60
60
|
- `mutable-dto` suggests `@ConstructorBinding` for `@ConfigurationProperties` classes
|
|
61
|
+
- Inline suppression: `@SuppressWarnings("java-functional-lsp:rule-id")` on any declaration
|
|
61
62
|
|
|
62
63
|
## Automatic Enforcement
|
|
63
64
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "java-functional-lsp"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
8
8
|
description = "Java LSP server enforcing functional programming best practices — null safety, immutability, no exceptions"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
{java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/src/java_functional_lsp/analyzers/base.py
RENAMED
|
@@ -160,6 +160,61 @@ def is_excluded(path_str: str, patterns: list[str]) -> bool:
|
|
|
160
160
|
return any(fnmatch.fnmatch(normalized, pattern) for pattern in patterns)
|
|
161
161
|
|
|
162
162
|
|
|
163
|
+
_SUPPRESS_PREFIX = "java-functional-lsp"
|
|
164
|
+
_DECLARATION_TYPES = frozenset(
|
|
165
|
+
{
|
|
166
|
+
"method_declaration",
|
|
167
|
+
"class_declaration",
|
|
168
|
+
"interface_declaration",
|
|
169
|
+
"enum_declaration",
|
|
170
|
+
"record_declaration",
|
|
171
|
+
"field_declaration",
|
|
172
|
+
"local_variable_declaration",
|
|
173
|
+
"constructor_declaration",
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def is_suppressed(root: Node, line: int, col: int, rule_id: str) -> bool:
|
|
179
|
+
"""Check if a diagnostic at (line, col) is suppressed by @SuppressWarnings."""
|
|
180
|
+
node = root.descendant_for_point_range((line, col), (line, col))
|
|
181
|
+
if node is None:
|
|
182
|
+
return False
|
|
183
|
+
current: Node | None = node
|
|
184
|
+
while current is not None:
|
|
185
|
+
if current.type in _DECLARATION_TYPES:
|
|
186
|
+
modifiers = next((c for c in current.children if c.type == "modifiers"), None)
|
|
187
|
+
if modifiers and _modifiers_suppress(modifiers, rule_id):
|
|
188
|
+
return True
|
|
189
|
+
current = current.parent
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _modifiers_suppress(modifiers: Node, rule_id: str) -> bool:
|
|
194
|
+
"""Check if modifiers contain @SuppressWarnings suppressing the given rule."""
|
|
195
|
+
for child in modifiers.named_children:
|
|
196
|
+
if child.type == "annotation":
|
|
197
|
+
name_node = child.child_by_field_name("name")
|
|
198
|
+
if name_node and name_node.text == b"SuppressWarnings":
|
|
199
|
+
args = child.child_by_field_name("arguments")
|
|
200
|
+
if args and _annotation_args_suppress(args, rule_id):
|
|
201
|
+
return True
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _annotation_args_suppress(args: Node, rule_id: str) -> bool:
|
|
206
|
+
"""Parse @SuppressWarnings value(s) and check for rule match."""
|
|
207
|
+
for string_node in find_nodes(args, "string_literal"):
|
|
208
|
+
if string_node.text is None or len(string_node.text) < len('""'):
|
|
209
|
+
continue
|
|
210
|
+
value = string_node.text[1:-1].decode("utf-8")
|
|
211
|
+
if value == _SUPPRESS_PREFIX:
|
|
212
|
+
return True
|
|
213
|
+
if value == f"{_SUPPRESS_PREFIX}:{rule_id}":
|
|
214
|
+
return True
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
|
|
163
218
|
def has_sibling_annotation(modifiers_node: Node, annotation_name: bytes) -> bool:
|
|
164
219
|
"""Check if a modifiers node contains an annotation with the given name.
|
|
165
220
|
|
|
@@ -18,7 +18,7 @@ from lsprotocol import types as lsp
|
|
|
18
18
|
from pygls.lsp.server import LanguageServer
|
|
19
19
|
from pygls.uris import to_fs_path
|
|
20
20
|
|
|
21
|
-
from .analyzers.base import Analyzer, Severity, get_parser, is_excluded
|
|
21
|
+
from .analyzers.base import Analyzer, Severity, get_parser, is_excluded, is_suppressed
|
|
22
22
|
from .analyzers.base import Diagnostic as LintDiagnostic
|
|
23
23
|
from .analyzers.exception_checker import ExceptionChecker
|
|
24
24
|
from .analyzers.mutation_checker import MutationChecker
|
|
@@ -124,6 +124,11 @@ def _analyze_document(source_text: str, uri: str = "") -> list[lsp.Diagnostic]:
|
|
|
124
124
|
except Exception as e:
|
|
125
125
|
logger.error("Analyzer %s failed: %s", type(analyzer).__name__, e)
|
|
126
126
|
|
|
127
|
+
# Filter out diagnostics suppressed by @SuppressWarnings
|
|
128
|
+
if all_diagnostics:
|
|
129
|
+
root = tree.root_node
|
|
130
|
+
all_diagnostics = [d for d in all_diagnostics if not is_suppressed(root, d.line, d.col, d.code)]
|
|
131
|
+
|
|
127
132
|
return [_to_lsp_diagnostic(d) for d in all_diagnostics]
|
|
128
133
|
|
|
129
134
|
|
|
@@ -349,3 +349,28 @@ class TestE2EConfig:
|
|
|
349
349
|
msg = _wait_diagnostics(server)
|
|
350
350
|
assert msg is not None
|
|
351
351
|
assert len(msg["params"]["diagnostics"]) == 0
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class TestE2ESuppressWarnings:
|
|
355
|
+
def test_suppress_warnings_annotation(self, server: subprocess.Popen[bytes], tmp_path: Path) -> None:
|
|
356
|
+
"""@SuppressWarnings should suppress diagnostics for the annotated scope."""
|
|
357
|
+
java_file = tmp_path / "Suppress.java"
|
|
358
|
+
java_file.write_text(
|
|
359
|
+
"class T {\n"
|
|
360
|
+
' @SuppressWarnings("java-functional-lsp:null-return")\n'
|
|
361
|
+
" String f() { return null; }\n"
|
|
362
|
+
"\n"
|
|
363
|
+
" String g() { return null; }\n"
|
|
364
|
+
"}"
|
|
365
|
+
)
|
|
366
|
+
uri = java_file.as_uri()
|
|
367
|
+
|
|
368
|
+
_initialize(server, root_uri=tmp_path.as_uri())
|
|
369
|
+
_did_open(server, uri, java_file.read_text())
|
|
370
|
+
|
|
371
|
+
msg = _wait_diagnostics(server)
|
|
372
|
+
assert msg is not None
|
|
373
|
+
null_diags = [d for d in msg["params"]["diagnostics"] if d["code"] == "null-return"]
|
|
374
|
+
# f() suppressed, g() not — should have exactly 1 null-return diagnostic on line 4 (g)
|
|
375
|
+
assert len(null_diags) == 1
|
|
376
|
+
assert null_diags[0]["range"]["start"]["line"] == 4
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"""Tests for @SuppressWarnings inline suppression."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from java_functional_lsp.analyzers.base import get_parser, is_suppressed
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _codes(diags: list[Any]) -> set[str]:
|
|
11
|
+
return {d.code for d in diags}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _analyze_all(source: str) -> list[Any]:
|
|
15
|
+
"""Run analysis through server's _analyze_document to include suppression filtering."""
|
|
16
|
+
from java_functional_lsp.server import _analyze_document
|
|
17
|
+
|
|
18
|
+
return _analyze_document(source)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestSuppressSingleRule:
|
|
22
|
+
def test_suppress_null_return(self) -> None:
|
|
23
|
+
source = """
|
|
24
|
+
class T {
|
|
25
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
26
|
+
String f() { return null; }
|
|
27
|
+
}
|
|
28
|
+
"""
|
|
29
|
+
diags = _analyze_all(source)
|
|
30
|
+
assert "null-return" not in _codes(diags)
|
|
31
|
+
|
|
32
|
+
def test_suppress_throw_statement(self) -> None:
|
|
33
|
+
source = """
|
|
34
|
+
class T {
|
|
35
|
+
@SuppressWarnings("java-functional-lsp:throw-statement")
|
|
36
|
+
void f() { throw new RuntimeException(); }
|
|
37
|
+
}
|
|
38
|
+
"""
|
|
39
|
+
diags = _analyze_all(source)
|
|
40
|
+
assert "throw-statement" not in _codes(diags)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestSuppressAllRules:
|
|
44
|
+
def test_suppress_all(self) -> None:
|
|
45
|
+
source = """
|
|
46
|
+
class T {
|
|
47
|
+
@SuppressWarnings("java-functional-lsp")
|
|
48
|
+
String f() { return null; }
|
|
49
|
+
}
|
|
50
|
+
"""
|
|
51
|
+
diags = _analyze_all(source)
|
|
52
|
+
assert "null-return" not in _codes(diags)
|
|
53
|
+
|
|
54
|
+
def test_suppress_all_multiple_violations(self) -> None:
|
|
55
|
+
source = """
|
|
56
|
+
class T {
|
|
57
|
+
@SuppressWarnings("java-functional-lsp")
|
|
58
|
+
void f() {
|
|
59
|
+
String x = null;
|
|
60
|
+
throw new RuntimeException();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
"""
|
|
64
|
+
diags = _analyze_all(source)
|
|
65
|
+
codes = _codes(diags)
|
|
66
|
+
assert "null-assignment" not in codes
|
|
67
|
+
assert "throw-statement" not in codes
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TestSuppressMultipleRules:
|
|
71
|
+
def test_suppress_array_syntax(self) -> None:
|
|
72
|
+
source = """
|
|
73
|
+
class T {
|
|
74
|
+
@SuppressWarnings({"java-functional-lsp:null-return", "java-functional-lsp:throw-statement"})
|
|
75
|
+
String f() {
|
|
76
|
+
if (true) throw new RuntimeException();
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
"""
|
|
81
|
+
diags = _analyze_all(source)
|
|
82
|
+
codes = _codes(diags)
|
|
83
|
+
assert "null-return" not in codes
|
|
84
|
+
assert "throw-statement" not in codes
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class TestSuppressOnClass:
|
|
88
|
+
def test_class_level_suppresses_all_methods(self) -> None:
|
|
89
|
+
source = """
|
|
90
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
91
|
+
class T {
|
|
92
|
+
String f() { return null; }
|
|
93
|
+
String g() { return null; }
|
|
94
|
+
}
|
|
95
|
+
"""
|
|
96
|
+
diags = _analyze_all(source)
|
|
97
|
+
assert "null-return" not in _codes(diags)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class TestSuppressOnField:
|
|
101
|
+
def test_field_suppression(self) -> None:
|
|
102
|
+
source = """
|
|
103
|
+
class T {
|
|
104
|
+
@SuppressWarnings("java-functional-lsp:null-field-assignment")
|
|
105
|
+
String cache = null;
|
|
106
|
+
}
|
|
107
|
+
"""
|
|
108
|
+
diags = _analyze_all(source)
|
|
109
|
+
assert "null-field-assignment" not in _codes(diags)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestSuppressScope:
|
|
113
|
+
def test_does_not_affect_sibling_methods(self) -> None:
|
|
114
|
+
source = """
|
|
115
|
+
class T {
|
|
116
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
117
|
+
String f() { return null; }
|
|
118
|
+
|
|
119
|
+
String g() { return null; }
|
|
120
|
+
}
|
|
121
|
+
"""
|
|
122
|
+
diags = _analyze_all(source)
|
|
123
|
+
# f() is suppressed, g() is not
|
|
124
|
+
assert any(d.code == "null-return" for d in diags)
|
|
125
|
+
# Verify the remaining diagnostic is on g()'s line
|
|
126
|
+
null_diags = [d for d in diags if d.code == "null-return"]
|
|
127
|
+
assert len(null_diags) == 1
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestUnrelatedSuppressIgnored:
|
|
131
|
+
def test_unrelated_annotation(self) -> None:
|
|
132
|
+
source = """
|
|
133
|
+
class T {
|
|
134
|
+
@SuppressWarnings("unchecked")
|
|
135
|
+
String f() { return null; }
|
|
136
|
+
}
|
|
137
|
+
"""
|
|
138
|
+
diags = _analyze_all(source)
|
|
139
|
+
assert "null-return" in _codes(diags)
|
|
140
|
+
|
|
141
|
+
def test_other_prefix_ignored(self) -> None:
|
|
142
|
+
source = """
|
|
143
|
+
class T {
|
|
144
|
+
@SuppressWarnings("other-linter:null-return")
|
|
145
|
+
String f() { return null; }
|
|
146
|
+
}
|
|
147
|
+
"""
|
|
148
|
+
diags = _analyze_all(source)
|
|
149
|
+
assert "null-return" in _codes(diags)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class TestSuppressOnInterface:
|
|
153
|
+
def test_interface_level_suppression(self) -> None:
|
|
154
|
+
source = """
|
|
155
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
156
|
+
interface Foo {
|
|
157
|
+
default String bar() { return null; }
|
|
158
|
+
}
|
|
159
|
+
"""
|
|
160
|
+
diags = _analyze_all(source)
|
|
161
|
+
assert "null-return" not in _codes(diags)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class TestSuppressOnEnum:
|
|
165
|
+
def test_enum_level_suppression(self) -> None:
|
|
166
|
+
source = """
|
|
167
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
168
|
+
enum Status {
|
|
169
|
+
ACTIVE;
|
|
170
|
+
public String label() { return null; }
|
|
171
|
+
}
|
|
172
|
+
"""
|
|
173
|
+
diags = _analyze_all(source)
|
|
174
|
+
assert "null-return" not in _codes(diags)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class TestSuppressSiblingClasses:
|
|
178
|
+
def test_does_not_leak_across_top_level_classes(self) -> None:
|
|
179
|
+
source = """
|
|
180
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
181
|
+
class A { String f() { return null; } }
|
|
182
|
+
class B { String g() { return null; } }
|
|
183
|
+
"""
|
|
184
|
+
diags = _analyze_all(source)
|
|
185
|
+
null_diags = [d for d in diags if d.code == "null-return"]
|
|
186
|
+
assert len(null_diags) == 1
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class TestSuppressNamedElement:
|
|
190
|
+
def test_value_equals_form(self) -> None:
|
|
191
|
+
source = """
|
|
192
|
+
class T {
|
|
193
|
+
@SuppressWarnings(value = "java-functional-lsp:null-return")
|
|
194
|
+
String f() { return null; }
|
|
195
|
+
}
|
|
196
|
+
"""
|
|
197
|
+
diags = _analyze_all(source)
|
|
198
|
+
assert "null-return" not in _codes(diags)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class TestSuppressOtherRules:
|
|
202
|
+
def test_suppress_imperative_loop(self) -> None:
|
|
203
|
+
source = """
|
|
204
|
+
class T {
|
|
205
|
+
@SuppressWarnings("java-functional-lsp:imperative-loop")
|
|
206
|
+
void f() { for (int i = 0; i < 10; i++) {} }
|
|
207
|
+
}
|
|
208
|
+
"""
|
|
209
|
+
diags = _analyze_all(source)
|
|
210
|
+
assert "imperative-loop" not in _codes(diags)
|
|
211
|
+
|
|
212
|
+
def test_suppress_mutable_variable(self) -> None:
|
|
213
|
+
source = """
|
|
214
|
+
class T {
|
|
215
|
+
@SuppressWarnings("java-functional-lsp:mutable-variable")
|
|
216
|
+
void f() { int x = 0; x = 1; }
|
|
217
|
+
}
|
|
218
|
+
"""
|
|
219
|
+
diags = _analyze_all(source)
|
|
220
|
+
assert "mutable-variable" not in _codes(diags)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class TestNoSuppress:
|
|
224
|
+
def test_baseline_no_annotation(self) -> None:
|
|
225
|
+
source = "class T { String f() { return null; } }"
|
|
226
|
+
diags = _analyze_all(source)
|
|
227
|
+
assert "null-return" in _codes(diags)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class TestIsSuppressedHelper:
|
|
231
|
+
def test_returns_false_for_clean_code(self) -> None:
|
|
232
|
+
parser = get_parser()
|
|
233
|
+
tree = parser.parse(b'class T { String f() { return "ok"; } }')
|
|
234
|
+
assert not is_suppressed(tree.root_node, 0, 30, "null-return")
|
|
235
|
+
|
|
236
|
+
def test_returns_true_for_suppressed(self) -> None:
|
|
237
|
+
parser = get_parser()
|
|
238
|
+
source = b"""class T {
|
|
239
|
+
@SuppressWarnings("java-functional-lsp:null-return")
|
|
240
|
+
String f() { return null; }
|
|
241
|
+
}"""
|
|
242
|
+
tree = parser.parse(source)
|
|
243
|
+
# null is on line 2, find its column
|
|
244
|
+
lines = source.split(b"\n")
|
|
245
|
+
null_col = lines[2].index(b"null")
|
|
246
|
+
assert is_suppressed(tree.root_node, 2, null_col, "null-return")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/ISSUE_TEMPLATE/bug-report.md
RENAMED
|
File without changes
|
{java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/ISSUE_TEMPLATE/feature-request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/workflows/release-drafter.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/.github/workflows/update-homebrew.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{java_functional_lsp-0.4.2 → java_functional_lsp-0.5.0}/editors/intellij/lsp4ij-template.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|