Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2025 IBM Corporation
* Copyright (c) 2025, 2026 IBM Corporation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -22,9 +22,13 @@
*/
public class DiagnosticsUtils {

public static final String NAME_MUST_START_WITH_SET = "NameMustStartWithSet";
public static final String MUST_DECLARE_EXACTLY_ONE_PARAM = "MustDeclareExactlyOneParam";
public static final String RETURN_TYPE_MUST_BE_VOID = "ReturnTypeMustBeVoid";
public static final String VALID_SETTER_METHOD = "ValidSetterMethod";
/**
* inheritsFrom
* Check if specified superType is present or not in the type hierarchy
* find super class and Check if it is present or not in the type hierarchy
*
* @param clazz
* @param fqSuperType
Expand All @@ -34,7 +38,42 @@ public static boolean inheritsFrom(PsiClass clazz, String fqSuperType) {
Project project = clazz.getProject();
PsiClass superClass = JavaPsiFacade.getInstance(project)
.findClass(fqSuperType, GlobalSearchScope.allScope(project));
return superClass != null &&
(clazz.isEquivalentTo(superClass) || clazz.isInheritor(superClass, true));
return inheritsFrom(clazz, superClass);
}

/**
* inheritsFrom
* Check if specified superClass is present or not in the type hierarchy
*
* @param clazz
* @param superClass
* @return
*/
public static boolean inheritsFrom(PsiClass clazz, PsiClass superClass) {
if (clazz == null || superClass == null) {
return false;
}
return clazz.isEquivalentTo(superClass) || clazz.isInheritor(superClass, true);
}


/**
* validateSetterMethod
* This is to check whether a method is a valid setter.
*
* @param element
* @return
*/
public static String validateSetterMethod(PsiMethod element) {
String methodName = element.getName();
PsiType returnType = element.getReturnType();
if(!methodName.startsWith("set")){
return NAME_MUST_START_WITH_SET;
} else if(!(returnType == null || returnType.equals(PsiTypes.voidType()))){
return RETURN_TYPE_MUST_BE_VOID;
} else if(element.getParameterList().getParametersCount() != 1){
return MUST_DECLARE_EXACTLY_ONE_PARAM;
}
return VALID_SETTER_METHOD;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class AnnotationConstants {
public static final String DIAGNOSTIC_CODE_ANNOTATION_START_WITH_SET = "ResourceNameMustStartWithSet";
public static final String DIAGNOSTIC_CODE_MUST_DECLARE_EXACTLY_ONE_PARAM = "ResourceMustDeclareExactlyOneParam";
public static final String DIAGNOSTIC_CODE_RETURN_TYPE_MUST_BE_VOID = "ResourceReturnTypeMustBeVoid";
public static final String DIAGNOSTIC_CODE_RETURN_TYPE_MISMATCH = "ResourceTypeMismatch";
public static final String DIAGNOSTIC_CODE_POSTCONSTRUCT_PARAMS = "PostConstructParams";
public static final String DIAGNOSTIC_CODE_POSTCONSTRUCT_RETURN_TYPE = "PostConstructReturnType";
public static final String DIAGNOSTIC_CODE_POSTCONSTRUCT_EXCEPTION = "PostConstructException";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.AbstractDiagnosticsCollector;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.DiagnosticsUtils;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages;
Expand All @@ -27,6 +28,7 @@
import java.util.List;
import java.util.regex.Pattern;

import static io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.DiagnosticsUtils.*;
import static io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations.AnnotationConstants.EXCEPTION;
import static io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations.AnnotationConstants.RUNTIME_EXCEPTION;

Expand Down Expand Up @@ -150,6 +152,8 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
}
} else if (element instanceof PsiMethod) {
validateResourceMethods(unit, diagnostics, (PsiMethod) element, annotation);
} else if (element instanceof PsiField) {
validateResourceFields(unit, diagnostics, (PsiField) element, annotation);
}
}
if (isMatchedAnnotation(annotation, AnnotationConstants.POST_CONSTRUCT_FQ_NAME)) {
Expand Down Expand Up @@ -213,6 +217,18 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
}
}

/**
* validateResourceFields
* This method is responsible for finding diagnostics in fields annotated with @Resource.
* @param unit
* @param diagnostics
* @param element
* @param annotation
*/
private void validateResourceFields(PsiJavaFile unit, List<Diagnostic> diagnostics, PsiField element, PsiAnnotation annotation) {
checkTypeCompatibility(unit, diagnostics, annotation, element.getType(), "field");
}

/**
* validateResourceMethods
* This method is responsible for finding diagnostics in methods annotated with @Resource.
Expand All @@ -223,31 +239,64 @@ public void collectDiagnostics(PsiJavaFile unit, List<Diagnostic> diagnostics) {
*/
private void validateResourceMethods(PsiJavaFile unit, List<Diagnostic> diagnostics, PsiMethod element, PsiAnnotation annotation) {
String methodName = element.getName();
PsiType returnType = element.getReturnType();
String diagnosticMessage;
if(!methodName.startsWith("set")){
diagnosticMessage = Messages.getMessage("AnnotationNameMustStartWithSet",
"@Resource", methodName);
diagnostics.add(createDiagnostic(annotation, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_ANNOTATION_START_WITH_SET, null,
DiagnosticSeverity.Error));
}
if(!(returnType == null || returnType.equals(PsiTypes.voidType()))){
diagnosticMessage = Messages.getMessage("AnnotationReturnTypeMustBeVoid",
"@Resource", methodName);
diagnostics.add(createDiagnostic(annotation, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_RETURN_TYPE_MUST_BE_VOID, null,
DiagnosticSeverity.Error));
}
if(element.getParameterList().getParametersCount() != 1){
diagnosticMessage = Messages.getMessage("AnnotationMustDeclareExactlyOneParam",
"@Resource", methodName);
diagnostics.add(createDiagnostic(annotation, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_MUST_DECLARE_EXACTLY_ONE_PARAM, null,
DiagnosticSeverity.Error));
String errorCode = validateSetterMethod(element);
switch (errorCode) {
case NAME_MUST_START_WITH_SET -> {
diagnosticMessage = Messages.getMessage("AnnotationNameMustStartWithSet",
"@Resource", methodName);
diagnostics.add(createDiagnostic(annotation, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_ANNOTATION_START_WITH_SET, null,
DiagnosticSeverity.Error));
}
case RETURN_TYPE_MUST_BE_VOID -> {
diagnosticMessage = Messages.getMessage("AnnotationReturnTypeMustBeVoid",
"@Resource", methodName);
diagnostics.add(createDiagnostic(annotation, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_RETURN_TYPE_MUST_BE_VOID, null,
DiagnosticSeverity.Error));
}
case MUST_DECLARE_EXACTLY_ONE_PARAM -> {
diagnosticMessage = Messages.getMessage("AnnotationMustDeclareExactlyOneParam",
"@Resource", methodName);
diagnostics.add(createDiagnostic(annotation, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_MUST_DECLARE_EXACTLY_ONE_PARAM, null,
DiagnosticSeverity.Error));
}
case VALID_SETTER_METHOD -> {
PsiParameter param = element.getParameterList().getParameter(0);
checkTypeCompatibility(unit, diagnostics, annotation, param.getType(), "parameter");
}
default -> System.out.println("Unexpected value");
}
}

/**
* checkTypeCompatibility
* Create diagnostics if the type specified by a particular annotation is incompatible with
* the type of the corresponding field or method parameter.
*
* @param unit
* @param diagnostics
* @param annotation
* @param type
* @param typeString
*/
private void checkTypeCompatibility(PsiJavaFile unit, List<Diagnostic> diagnostics, PsiAnnotation annotation, PsiType type, String typeString) {
PsiAnnotationMemberValue typeValue = annotation.findDeclaredAttributeValue("type");
if (typeValue instanceof PsiClassObjectAccessExpression) {
PsiType psiResourceType = ((PsiClassObjectAccessExpression) typeValue).getOperand().getType();
PsiClass psiTypeClass = PsiUtil.resolveClassInType(type);
PsiClass psiResourceClass = PsiUtil.resolveClassInType(psiResourceType);
if (!inheritsFrom(psiResourceClass, psiTypeClass)){
String diagnosticMessage = Messages.getMessage("ResourceTypeMismatch",
typeString);
diagnostics.add(createDiagnostic(annotation, unit, diagnosticMessage,
AnnotationConstants.DIAGNOSTIC_CODE_RETURN_TYPE_MISMATCH, null,
DiagnosticSeverity.Error));
}
}
}

private void processAnnotations(PsiJvmModifiersOwner psiModifierOwner,
ArrayList<Tuple.Two<PsiAnnotation, PsiElement>> annotatables,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*******************************************************************************
* Copyright (c) 2026 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial implementation
*******************************************************************************/

package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations;


import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix.RemoveAnnotationAttributesQuickFix;

/**
* Removes the @Resource annotation attribute
*/
public class RemoveResourceAnnotationAttributeQuickFix extends RemoveAnnotationAttributesQuickFix {

public RemoveResourceAnnotationAttributeQuickFix() {
super("jakarta.annotation.Resource","type");
}

@Override
public String getLabel() {
return Messages.getMessage("RemoveAttribute", "type", "@Resource");
}

@Override
public String getParticipantId() {
return RemoveResourceAnnotationAttributeQuickFix.class.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*******************************************************************************
* Copyright (c) 2026 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial implementation
*******************************************************************************/

package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations;

import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix.RemoveAnnotationConflictQuickFix;

/**
* Removes the @Resource annotation from the declaring element.
*/
public class RemoveResourceAnnotationQuickFix extends RemoveAnnotationConflictQuickFix {

/**
* Constructor.
*/
public RemoveResourceAnnotationQuickFix() {
super(false, "jakarta.annotation.Resource");
}

/**
* {@inheritDoc}
*/
@Override
public String getParticipantId() {
return RemoveResourceAnnotationQuickFix.class.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (c) 2026 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.quickfix;

import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils;
import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.proposal.ModifyAnnotationProposal;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.IJavaCodeActionParticipant;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionContext;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.codeaction.JavaCodeActionResolveContext;
import io.openliberty.tools.intellij.lsp4mp4ij.psi.core.java.corrections.proposal.ChangeCorrectionProposal;
import io.openliberty.tools.intellij.util.ExceptionUtil;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;

/**
* Quickfix for removing annotations attributes
*/
public abstract class RemoveAnnotationAttributesQuickFix implements IJavaCodeActionParticipant {

private final String[] attributes;

private final String annotation;

private static final Logger LOGGER = Logger.getLogger(RemoveAnnotationAttributesQuickFix.class.getName());


public RemoveAnnotationAttributesQuickFix(String annotation,
String... attributes) {
this.annotation = annotation;
this.attributes = attributes;
}

@Override
public List<? extends CodeAction> getCodeActions(JavaCodeActionContext context, Diagnostic diagnostic) {
return List.of(JDTUtils.createCodeAction(context, diagnostic, getLabel(), getParticipantId()));
}

@Override
public CodeAction resolveCodeAction(JavaCodeActionResolveContext context) {
final CodeAction toResolve = context.getUnresolved();
final PsiElement node = context.getCoveredNode();
PsiModifierListOwner binding = getBinding(node);
PsiAnnotation annotationNode = PsiTreeUtil.getParentOfType(node, PsiAnnotation.class);
assert binding != null;
String label = getLabel();
ChangeCorrectionProposal proposal = new ModifyAnnotationProposal(label, context.getSource().getCompilationUnit(),
context.getASTRoot(), binding, annotationNode, 0, this.annotation, new ArrayList<>(), Arrays.asList(attributes));
ExceptionUtil.executeWithWorkspaceEditHandling(context, proposal, toResolve, LOGGER, "Unable to create workspace edit for code action " + label);
return toResolve;
}

protected static PsiModifierListOwner getBinding(PsiElement node) {
PsiModifierListOwner binding = PsiTreeUtil.getParentOfType(node, PsiVariable.class);
if (binding != null) {
return binding;
}
binding = PsiTreeUtil.getParentOfType(node, PsiMethod.class);
if (binding != null) {
return binding;
}
return PsiTreeUtil.getParentOfType(node, PsiClass.class);
}

protected abstract String getLabel();

}
8 changes: 8 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@
group="jakarta"
targetDiagnostic="jakarta-annotations#PreDestroyException"
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations.RemoveCheckedExceptionsInThrowsQuickFix"/>
<javaCodeActionParticipant kind="quickfix"
group="jakarta"
targetDiagnostic="jakarta-annotations#ResourceTypeMismatch"
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations.RemoveResourceAnnotationQuickFix"/>
<javaCodeActionParticipant kind="quickfix"
group="jakarta"
targetDiagnostic="jakarta-annotations#ResourceTypeMismatch"
implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations.RemoveResourceAnnotationAttributeQuickFix"/>

<!-- Bean Validation -->
<javaCodeActionParticipant kind="quickfix"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ AnnotationMustDefineAttributeFollowing8601 = The {0} annotation must define the
MethodMustNotHaveParameters = A method with the {0} annotation must not have any parameters.
MethodMustBeVoid = A method with the {0} annotation must be void.
MethodMustNotBeStatic = A method with the {0} annotation must not be static.
ResourceTypeMismatch = Type of the {0} MUST be compatible with the type element of the Resource annotation, if specified.
MethodMustNotThrow = A method with the {0} annotation must not throw checked exceptions.
RemoveCheckedExceptions = Remove all checked exceptions.
AnnotationNameMustStartWithSet = {0} method ''{1}'' is invalid: method name must start with 'set'.
Expand All @@ -39,7 +40,7 @@ InsertModifierToNestedClass = Add ''{0}'' modifier to the nested class

# RemoveAnnotationConflictQuickFix
RemoveItem = Remove {0}

RemoveAttribute = Remove {0} attribute from {1}
# RemoveParamAnnotationQuickFix
RemoveTheModifierFromParameter = Remove the {0} modifier from parameter {1}

Expand Down
Loading
Loading