diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/DiagnosticsUtils.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/DiagnosticsUtils.java index f9dbbd031..672758a6e 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/DiagnosticsUtils.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/DiagnosticsUtils.java @@ -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 @@ -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 @@ -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; } } diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationConstants.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationConstants.java index 77133068f..591fd38ba 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationConstants.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationConstants.java @@ -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"; diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationDiagnosticsCollector.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationDiagnosticsCollector.java index da2dedd30..eed0006eb 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationDiagnosticsCollector.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/AnnotationDiagnosticsCollector.java @@ -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; @@ -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; @@ -150,6 +152,8 @@ public void collectDiagnostics(PsiJavaFile unit, List 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)) { @@ -213,6 +217,18 @@ public void collectDiagnostics(PsiJavaFile unit, List 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 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. @@ -223,31 +239,64 @@ public void collectDiagnostics(PsiJavaFile unit, List diagnostics) { */ private void validateResourceMethods(PsiJavaFile unit, List 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 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> annotatables, diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/RemoveResourceAnnotationAttributeQuickFix.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/RemoveResourceAnnotationAttributeQuickFix.java new file mode 100644 index 000000000..3677165ae --- /dev/null +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/RemoveResourceAnnotationAttributeQuickFix.java @@ -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(); + } +} diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/RemoveResourceAnnotationQuickFix.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/RemoveResourceAnnotationQuickFix.java new file mode 100644 index 000000000..dd3c27e3e --- /dev/null +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/annotations/RemoveResourceAnnotationQuickFix.java @@ -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(); + } +} diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/proposal/quickfix/RemoveAnnotationAttributesQuickFix.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/proposal/quickfix/RemoveAnnotationAttributesQuickFix.java new file mode 100644 index 000000000..2e72f9c92 --- /dev/null +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/proposal/quickfix/RemoveAnnotationAttributesQuickFix.java @@ -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 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(); + +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 2bb591fe3..4b313c8a5 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -290,6 +290,14 @@ group="jakarta" targetDiagnostic="jakarta-annotations#PreDestroyException" implementationClass="io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.annotations.RemoveCheckedExceptionsInThrowsQuickFix"/> + +