Skip to content

Commit ccf8790

Browse files
authored
Feature/diagnostic for checked exception condition (#1411)
* Added diagnostics error for checked exceptions in throws clause * added custom exception test resources * updated the existing test resources * added new test cases to support the changes * format corrected * format corrected * pr comments * PR comments * Update PostConstructAnnotationTest.java
1 parent fea63e1 commit ccf8790

File tree

8 files changed

+301
-160
lines changed

8 files changed

+301
-160
lines changed

src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationConstants.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2021, 2022 IBM Corporation and others.
2+
* Copyright (c) 2021, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -44,4 +44,8 @@ public class AnnotationConstants {
4444
public static final String DIAGNOSTIC_CODE_PREDESTROY_PARAMS = "PreDestroyParams";
4545
public static final String DIAGNOSTIC_CODE_PREDESTROY_EXCEPTION = "PreDestroyException";
4646
public static final String DIAGNOSTIC_CODE_PREDESTROY_STATIC = "PreDestroyStatic";
47+
48+
/* Exceptions */
49+
public static final String EXCEPTION = "java.lang.Exception";
50+
public static final String RUNTIME_EXCEPTION = "java.lang.RuntimeException";
4751
}

src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationDiagnosticsCollector.java

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2021, 2024 IBM Corporation and others.
2+
* Copyright (c) 2021, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -14,8 +14,8 @@
1414
package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations;
1515

1616
import com.intellij.psi.*;
17-
import com.intellij.psi.util.PsiUtil;
1817
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.AbstractDiagnosticsCollector;
18+
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.DiagnosticsUtils;
1919
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages;
2020
import org.eclipse.lsp4j.Diagnostic;
2121
import org.eclipse.lsp4j.DiagnosticSeverity;
@@ -25,6 +25,9 @@
2525
import java.util.List;
2626
import java.util.regex.Pattern;
2727

28+
import static io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations.AnnotationConstants.EXCEPTION;
29+
import static io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations.AnnotationConstants.RUNTIME_EXCEPTION;
30+
2831
/**
2932
*
3033
* jararta.annotation Diagnostics
@@ -148,6 +151,13 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
148151
if (isMatchedAnnotation(annotation, AnnotationConstants.POST_CONSTRUCT_FQ_NAME)) {
149152
if (element instanceof PsiMethod) {
150153
PsiMethod method = (PsiMethod) element;
154+
if (isCheckedExceptionPresent(method)) {
155+
String diagnosticMessage = Messages.getMessage("MethodMustNotThrow",
156+
"@PostConstruct");
157+
diagnostics.add(createDiagnostic(method, unit, diagnosticMessage,
158+
AnnotationConstants.DIAGNOSTIC_CODE_POSTCONSTRUCT_EXCEPTION, null,
159+
DiagnosticSeverity.Error));
160+
}
151161
if (method.getParameters().length != 0) {
152162
String diagnosticMessage = Messages.getMessage("MethodMustNotHaveParameters",
153163
"@PostConstruct");
@@ -163,18 +173,17 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
163173
AnnotationConstants.DIAGNOSTIC_CODE_POSTCONSTRUCT_RETURN_TYPE, null,
164174
DiagnosticSeverity.Error));
165175
}
166-
167-
if (method.getThrowsTypes().length != 0) {
168-
String diagnosticMessage = Messages.getMessage("MethodMustNotThrow",
169-
"@PostConstruct");
170-
diagnostics.add(createDiagnostic(method, unit, diagnosticMessage,
171-
AnnotationConstants.DIAGNOSTIC_CODE_POSTCONSTRUCT_EXCEPTION, null,
172-
DiagnosticSeverity.Warning));
173-
}
174176
}
175177
} else if (isMatchedAnnotation(annotation, AnnotationConstants.PRE_DESTROY_FQ_NAME)) {
176178
if (element instanceof PsiMethod) {
177179
PsiMethod method = (PsiMethod) element;
180+
if (isCheckedExceptionPresent(method)) {
181+
String diagnosticMessage = Messages.getMessage("MethodMustNotThrow",
182+
"@PreDestroy");
183+
diagnostics.add(createDiagnostic(method, unit, diagnosticMessage,
184+
AnnotationConstants.DIAGNOSTIC_CODE_PREDESTROY_EXCEPTION, null,
185+
DiagnosticSeverity.Error));
186+
}
178187
if (method.getParameters().length != 0) {
179188
String diagnosticMessage = Messages.getMessage("MethodMustNotHaveParameters",
180189
"@PreDestroy");
@@ -190,14 +199,6 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
190199
AnnotationConstants.DIAGNOSTIC_CODE_PREDESTROY_STATIC, method.getName(),
191200
DiagnosticSeverity.Error));
192201
}
193-
194-
if (method.getThrowsTypes().length != 0) {
195-
String diagnosticMessage = Messages.getMessage("MethodMustNotThrow",
196-
"@PreDestroy");
197-
diagnostics.add(createDiagnostic(method, unit, diagnosticMessage,
198-
AnnotationConstants.DIAGNOSTIC_CODE_PREDESTROY_EXCEPTION, null,
199-
DiagnosticSeverity.Warning));
200-
}
201202
}
202203
}
203204
}
@@ -224,4 +225,44 @@ private static boolean isValidAnnotation(String annotationName, String[] validAn
224225
}
225226
return false;
226227
}
228+
229+
/**
230+
* isCheckedExceptionPresent
231+
* This method scans the exception signatures to identify if any checked exceptions are declared.
232+
*
233+
* @param method
234+
* @return
235+
*/
236+
private boolean isCheckedExceptionPresent(PsiMethod method) {
237+
for (PsiClassType type : method.getThrowsList().getReferencedTypes()) {
238+
PsiClass exceptionClass = type.resolve();
239+
if (exceptionClass != null && extendsException(exceptionClass) && notExtendsRuntimeException(exceptionClass)) {
240+
return true;
241+
}
242+
}
243+
return false;
244+
}
245+
246+
/**
247+
* extendsException
248+
*
249+
* @param exceptionClass The root type of which the super-types are checked.
250+
* @return true if Exception is the superType of the given exception type.
251+
*/
252+
private static boolean extendsException(PsiClass exceptionClass) {
253+
return DiagnosticsUtils.inheritsFrom(exceptionClass, EXCEPTION);
254+
}
255+
256+
/**
257+
* notExtendsRuntimeException
258+
*
259+
* @param exceptionClass The root type of which the super-types are checked.
260+
* @return true if RuntimeException is not the superType of the given exception type.
261+
*/
262+
private static boolean notExtendsRuntimeException(PsiClass exceptionClass) {
263+
return !DiagnosticsUtils.inheritsFrom(exceptionClass, RUNTIME_EXCEPTION);
264+
}
265+
266+
267+
227268
}

src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/annotations/PostConstructAnnotationTest.java

Lines changed: 66 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2021, 2024 IBM Corporation and others.
2+
* Copyright (c) 2021, 2025 IBM Corporation and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -52,72 +52,85 @@ public void GeneratedAnnotation() throws Exception {
5252

5353
// expected Diagnostics
5454

55-
Diagnostic d1 = d(15, 19, 31, "A method with the @PostConstruct annotation must be void.",
55+
Diagnostic d1 = d(19, 19, 31, "A method with the @PostConstruct annotation must be void.",
5656
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructReturnType");
5757

58-
Diagnostic d2 = d(20, 16, 28, "A method with the @PostConstruct annotation must not have any parameters.",
58+
Diagnostic d2 = d(24, 16, 28, "A method with the @PostConstruct annotation must not have any parameters.",
5959
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructParams");
6060

61-
Diagnostic d3 = d(25, 16, 28, "A method with the @PostConstruct annotation must not throw checked exceptions.",
62-
DiagnosticSeverity.Warning, "jakarta-annotations", "PostConstructException");
61+
Diagnostic d3 = d(28, 16, 28, "A method with the @PostConstruct annotation must not throw checked exceptions.",
62+
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructException");
6363

64-
assertJavaDiagnostics(diagnosticsParams, utils, d1, d2, d3);
64+
Diagnostic d4 = d(43, 16, 32, "A method with the @PostConstruct annotation must not throw checked exceptions.",
65+
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructException");
66+
67+
Diagnostic d5 = d(48, 16, 31, "A method with the @PostConstruct annotation must not throw checked exceptions.",
68+
DiagnosticSeverity.Error, "jakarta-annotations", "PostConstructException");
69+
70+
assertJavaDiagnostics(diagnosticsParams, utils, d1, d2, d3, d4, d5);
6571

6672
// Starting codeAction tests.
67-
String newText = "package io.openliberty.sample.jakarta.annotations;\n\n" +
68-
"import jakarta.annotation.PostConstruct;\n" +
69-
"import jakarta.annotation.Resource;\n\n" +
70-
"@Resource(type = Object.class, name = \"aa\")\n" +
71-
"public class PostConstructAnnotation {\n\n" +
72-
" private Integer studentId;\n\n private boolean isHappy;\n\n" +
73-
" private boolean isSad;\n\n @PostConstruct()\n" +
74-
" public void getStudentId() {\n return this.studentId;\n" +
75-
" }\n\n @PostConstruct\n public void getHappiness(String type) {\n\n" +
76-
" }\n\n @PostConstruct\n" +
77-
" public void throwTantrum() throws Exception {\n" +
78-
" System.out.println(\"I'm sad\");\n }\n\n" +
79-
" private String emailAddress;\n\n}";
73+
String newText = "package io.openliberty.sample.jakarta.annotations;\n\nimport jakarta.annotation.PostConstruct;\n" +
74+
"import jakarta.annotation.Resource;\n\nimport java.io.IOException;\n\n@Resource(type = Object.class, name = \"aa\")\n" +
75+
"public class PostConstructAnnotation {\n\n private Integer studentId;\n\n private boolean isHappy;\n\n " +
76+
"private boolean isSad;\n\n private String emailAddress;\n\n @PostConstruct()\n " +
77+
"public void getStudentId() {\n return this.studentId;\n }\n\n @PostConstruct\n " +
78+
"public void getHappiness(String type) {\n }\n\n @PostConstruct\n " +
79+
"public void throwTantrum() throws Exception {\n System.out.println(\"I'm sad\");\n }\n\n " +
80+
"@PostConstruct\n public void throwRuntimeException() throws RuntimeException {\n " +
81+
"System.out.println(\"RuntimeException\");\n }\n\n @PostConstruct\n " +
82+
"public void throwNullPointerException() throws NullPointerException {\n " +
83+
"System.out.println(\"NullPointerException\");\n }\n\n @PostConstruct\n " +
84+
"public void throwIOException() throws IOException {\n System.out.println(\"IOException\");\n }\n\n " +
85+
"@PostConstruct\n public void throwExceptions() throws CustomCheckedException, CustomUncheckedException, IOException {\n " +
86+
"System.out.println(\"throwExceptions\");\n }\n\n @PostConstruct\n " +
87+
"public void throwCustomUnCheckedException() throws CustomUncheckedException {\n " +
88+
"System.out.println(\"CustomUncheckedException\");\n }\n\n " +
89+
"@PostConstruct\n public void throwError() throws Error {\n System.out.println(\"throwError\");\n }\n\n}";
8090

8191
JakartaJavaCodeActionParams codeActionParams2 = createCodeActionParams(uri, d1);
82-
TextEdit te3 = te(0, 0, 31, 1, newText);
92+
TextEdit te3 = te(0, 0, 62, 1, newText);
8393
CodeAction ca3 = ca(uri, "Change return type to void", d1, te3);
8494
assertJavaCodeAction(codeActionParams2, utils, ca3);
8595

86-
String newText1 = "package io.openliberty.sample.jakarta.annotations;\n\n" +
87-
"import jakarta.annotation.PostConstruct;\n" +
88-
"import jakarta.annotation.Resource;\n\n" +
89-
"@Resource(type = Object.class, name = \"aa\")\n" +
90-
"public class PostConstructAnnotation {\n\n" +
91-
" private Integer studentId;\n\n private boolean isHappy;\n\n" +
92-
" private boolean isSad;\n\n @PostConstruct()\n" +
93-
" public Integer getStudentId() {\n return this.studentId;\n" +
94-
" }\n\n public void getHappiness(String type) {\n\n" +
95-
" }\n\n @PostConstruct\n" +
96-
" public void throwTantrum() throws Exception {\n" +
97-
" System.out.println(\"I'm sad\");\n }\n\n" +
98-
" private String emailAddress;\n\n}";
99-
100-
String newText2 = "package io.openliberty.sample.jakarta.annotations;\n\n" +
101-
"import jakarta.annotation.PostConstruct;\n" +
102-
"import jakarta.annotation.Resource;\n\n" +
103-
"@Resource(type = Object.class, name = \"aa\")\n" +
104-
"public class PostConstructAnnotation {\n\n " +
105-
"private Integer studentId;\n\n " +
106-
"private boolean isHappy;\n\n " +
107-
"private boolean isSad;\n\n " +
108-
"@PostConstruct()\n " +
109-
"public Integer getStudentId() {\n " +
110-
"return this.studentId;\n }\n\n " +
111-
"@PostConstruct\n " +
112-
"public void getHappiness() {\n\n }\n\n " +
113-
"@PostConstruct\n " +
114-
"public void throwTantrum() throws Exception {\n " +
115-
"System.out.println(\"I'm sad\");\n }\n\n " +
116-
"private String emailAddress;\n\n}";
96+
String newText1 = "package io.openliberty.sample.jakarta.annotations;\n\nimport jakarta.annotation.PostConstruct;\n" +
97+
"import jakarta.annotation.Resource;\n\nimport java.io.IOException;\n\n@Resource(type = Object.class, name = \"aa\")\n" +
98+
"public class PostConstructAnnotation {\n\n private Integer studentId;\n\n private boolean isHappy;\n\n " +
99+
"private boolean isSad;\n\n private String emailAddress;\n\n @PostConstruct()\n " +
100+
"public Integer getStudentId() {\n return this.studentId;\n }\n\n public void getHappiness(String type) {\n }\n\n " +
101+
"@PostConstruct\n public void throwTantrum() throws Exception {\n System.out.println(\"I'm sad\");\n }\n\n " +
102+
"@PostConstruct\n public void throwRuntimeException() throws RuntimeException {\n " +
103+
"System.out.println(\"RuntimeException\");\n }\n\n " +
104+
"@PostConstruct\n public void throwNullPointerException() throws NullPointerException {\n " +
105+
"System.out.println(\"NullPointerException\");\n }\n\n @PostConstruct\n " +
106+
"public void throwIOException() throws IOException {\n System.out.println(\"IOException\");\n }\n\n " +
107+
"@PostConstruct\n public void throwExceptions() throws CustomCheckedException, CustomUncheckedException, IOException {\n " +
108+
"System.out.println(\"throwExceptions\");\n }\n\n @PostConstruct\n " +
109+
"public void throwCustomUnCheckedException() throws CustomUncheckedException {\n " +
110+
"System.out.println(\"CustomUncheckedException\");\n }\n\n @PostConstruct\n public void throwError() throws Error {\n " +
111+
"System.out.println(\"throwError\");\n }\n\n}";
112+
113+
String newText2 = "package io.openliberty.sample.jakarta.annotations;\n\nimport jakarta.annotation.PostConstruct;\n" +
114+
"import jakarta.annotation.Resource;\n\nimport java.io.IOException;\n\n@Resource(type = Object.class, name = \"aa\")\n" +
115+
"public class PostConstructAnnotation {\n\n private Integer studentId;\n\n private boolean isHappy;\n\n " +
116+
"private boolean isSad;\n\n private String emailAddress;\n\n @PostConstruct()\n " +
117+
"public Integer getStudentId() {\n return this.studentId;\n }\n\n @PostConstruct\n " +
118+
"public void getHappiness() {\n }\n\n @PostConstruct\n public void throwTantrum() throws Exception {\n " +
119+
"System.out.println(\"I'm sad\");\n }\n\n @PostConstruct\n public void throwRuntimeException() throws RuntimeException {\n " +
120+
"System.out.println(\"RuntimeException\");\n }\n\n @PostConstruct\n " +
121+
"public void throwNullPointerException() throws NullPointerException {\n " +
122+
"System.out.println(\"NullPointerException\");\n }\n\n " +
123+
"@PostConstruct\n public void throwIOException() throws IOException {\n " +
124+
"System.out.println(\"IOException\");\n }\n\n " +
125+
"@PostConstruct\n public void throwExceptions() throws CustomCheckedException, CustomUncheckedException, IOException {\n " +
126+
"System.out.println(\"throwExceptions\");\n }\n\n @PostConstruct\n " +
127+
"public void throwCustomUnCheckedException() throws CustomUncheckedException {\n " +
128+
"System.out.println(\"CustomUncheckedException\");\n }\n\n " +
129+
"@PostConstruct\n public void throwError() throws Error {\n System.out.println(\"throwError\");\n }\n\n}";
117130

118131
JakartaJavaCodeActionParams codeActionParams1 = createCodeActionParams(uri, d2);
119-
TextEdit te = te(0, 0, 31, 1, newText1);
120-
TextEdit te1 = te(0, 0, 31, 1, newText2);
132+
TextEdit te = te(0, 0, 62, 1, newText1);
133+
TextEdit te1 = te(0, 0, 62, 1, newText2);
121134
CodeAction ca = ca(uri, "Remove @PostConstruct", d2, te);
122135
CodeAction ca1 = ca(uri, "Remove all parameters", d2, te1);
123136
assertJavaCodeAction(codeActionParams1, utils, ca, ca1);

0 commit comments

Comments
 (0)