diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/graphql/java/MicroProfileGraphQLASTValidator.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/graphql/java/MicroProfileGraphQLASTValidator.java index 3b14933f8..77219d851 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/graphql/java/MicroProfileGraphQLASTValidator.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/graphql/java/MicroProfileGraphQLASTValidator.java @@ -15,29 +15,20 @@ import java.text.MessageFormat; -import java.util.Arrays; import java.util.logging.Logger; +import java.util.regex.Matcher; -import com.intellij.lang.jvm.JvmMethod; -import com.intellij.lang.jvm.types.JvmArrayType; -import com.intellij.lang.jvm.types.JvmPrimitiveType; -import com.intellij.lang.jvm.types.JvmReferenceType; -import com.intellij.lang.jvm.types.JvmType; -import com.intellij.lang.jvm.types.JvmTypeVisitor; -import com.intellij.lang.jvm.types.JvmWildcardType; import com.intellij.openapi.module.Module; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiAnnotationMemberValue; import com.intellij.psi.PsiArrayInitializerMemberValue; import com.intellij.psi.PsiArrayType; import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiEnumConstant; import com.intellij.psi.PsiField; import com.intellij.psi.PsiJvmModifiersOwner; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiType; import com.intellij.psi.impl.source.PsiClassReferenceType; import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.java.diagnostics.JavaDiagnosticsContext; @@ -45,8 +36,8 @@ import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.PsiTypeUtils; import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.graphql.MicroProfileGraphQLConstants; import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.graphql.TypeSystemDirectiveLocation; +import com.redhat.devtools.intellij.quarkus.QuarkusModuleUtil; import org.eclipse.lsp4j.DiagnosticSeverity; -import org.jetbrains.annotations.NotNull; import static com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.AnnotationUtils.getAnnotation; import static com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.AnnotationUtils.isMatchAnnotation; @@ -64,18 +55,41 @@ public class MicroProfileGraphQLASTValidator extends JavaASTValidator { private static final Logger LOGGER = Logger.getLogger(MicroProfileGraphQLASTValidator.class.getName()); + private static final String NO_VOID_MESSAGE = "Methods annotated with microprofile-graphql's `@Query` cannot have 'void' as a return type."; + private static final String NO_VOID_MUTATION_MESSAGE = "Methods annotated with microprofile-graphql's `@Mutation` cannot have 'void' as a return type."; private static final String WRONG_DIRECTIVE_PLACEMENT = "Directive ''{0}'' is not allowed on element type ''{1}''"; + boolean allowsVoidReturnFromOperations = true; + + @Override public boolean isAdaptedForDiagnostics(JavaDiagnosticsContext context) { Module javaProject = context.getJavaProject(); - // Check if microprofile-graphql is on the path - return PsiTypeUtils.findType(javaProject, MicroProfileGraphQLConstants.QUERY_ANNOTATION) != null; + if(PsiTypeUtils.findType(javaProject, MicroProfileGraphQLConstants.QUERY_ANNOTATION) == null) { + return false; + } + String quarkusVersion = QuarkusModuleUtil.getQuarkusVersion(context.getJavaProject()); + if(quarkusVersion != null) { + Matcher matcher = QuarkusModuleUtil.QUARKUS_STANDARD_VERSIONING.matcher(quarkusVersion); + if(matcher.matches()) { // ignore if this is not a standard version (for example, a custom snapshot) + //if Quarkus version >= 3.1, then returning void from operations is legal + if(Integer.valueOf(matcher.group(1)) < 3 || + (Integer.valueOf(matcher.group(1)) == 3) && Integer.valueOf(matcher.group(2)) == 0) { + allowsVoidReturnFromOperations = false; + } + } + } + LOGGER.fine("Detected Quarkus version = " + quarkusVersion + ". Allowing void return" + + " from GraphQL operations? " + allowsVoidReturnFromOperations); + return true; } @Override public void visitMethod(PsiMethod node) { validateDirectivesOnMethod(node); + if(!allowsVoidReturnFromOperations) { + validateNoVoidReturnedFromOperations(node); + } super.visitMethod(node); } @@ -116,7 +130,7 @@ private void validateDirectivesOnClass(PsiClass node) { } } // an enum may only have directives allowed on ENUM - if (node.isEnum()) { + else if (node.isEnum()) { validateDirectives(node, TypeSystemDirectiveLocation.ENUM); // enum values may only have directives allowed on ENUM_VALUE for (PsiField field : node.getFields()) { @@ -132,7 +146,7 @@ private void validateDirectives(PsiJvmModifiersOwner node, TypeSystemDirectiveLo for (PsiAnnotation annotation : node.getAnnotations()) { PsiClass directiveDeclaration = getDirectiveDeclaration(annotation); if (directiveDeclaration != null) { - LOGGER.severe("Checking directive: " + annotation.getQualifiedName() + " on node: " + node + " (location type = " + actualLocation.name() + ")"); + LOGGER.fine("Checking directive: " + annotation.getQualifiedName() + " on node: " + node + " (location type = " + actualLocation.name() + ")"); PsiArrayInitializerMemberValue allowedLocations = (PsiArrayInitializerMemberValue) directiveDeclaration .getAnnotation(MicroProfileGraphQLConstants.DIRECTIVE_ANNOTATION) .findAttributeValue("on"); @@ -185,5 +199,27 @@ private PsiClass getDirectiveDeclaration(PsiAnnotation annotation) { return null; } + private void validateNoVoidReturnedFromOperations(PsiMethod node) { + // ignore constructors, and non-void methods for now, it's faster than iterating through all annotations + if (node.getReturnTypeElement() == null || + !PsiType.VOID.equals(node.getReturnType())) { + return; + } + for (PsiAnnotation annotation : node.getAnnotations()) { + if (isMatchAnnotation(annotation, MicroProfileGraphQLConstants.QUERY_ANNOTATION) ) { + super.addDiagnostic(NO_VOID_MESSAGE, // + MicroProfileGraphQLConstants.DIAGNOSTIC_SOURCE, // + node.getReturnTypeElement(), // + MicroProfileGraphQLErrorCode.NO_VOID_QUERIES, // + DiagnosticSeverity.Error); + } else if (isMatchAnnotation(annotation, MicroProfileGraphQLConstants.MUTATION_ANNOTATION)) { + super.addDiagnostic(NO_VOID_MUTATION_MESSAGE, // + MicroProfileGraphQLConstants.DIAGNOSTIC_SOURCE, // + node.getReturnTypeElement(), // + MicroProfileGraphQLErrorCode.NO_VOID_MUTATIONS, // + DiagnosticSeverity.Error); + } + } + } } \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/graphql/java/MicroProfileGraphQLErrorCode.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/graphql/java/MicroProfileGraphQLErrorCode.java index 750d21a1a..18cd1b5c0 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/graphql/java/MicroProfileGraphQLErrorCode.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/graphql/java/MicroProfileGraphQLErrorCode.java @@ -20,6 +20,8 @@ */ public enum MicroProfileGraphQLErrorCode implements IJavaErrorCode { + NO_VOID_QUERIES, + NO_VOID_MUTATIONS, WRONG_DIRECTIVE_PLACEMENT ; diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusModuleUtil.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusModuleUtil.java index c2449e86a..f075feb8c 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusModuleUtil.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusModuleUtil.java @@ -42,9 +42,12 @@ import org.slf4j.LoggerFactory; import java.io.File; +import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; +import java.util.regex.Matcher; import java.util.regex.Pattern; public class QuarkusModuleUtil { @@ -179,6 +182,28 @@ public static boolean isQuarkusDeploymentLibrary(@NotNull LibraryOrderEntry libr libraryOrderEntry.getLibraryName().equalsIgnoreCase(QuarkusConstants.QUARKUS_DEPLOYMENT_LIBRARY_NAME); } + private static final Pattern QUARKUS_CORE_PATTERN = Pattern.compile("quarkus-core-(\\d[a-zA-Z\\d-.]+?).jar"); + public static final Pattern QUARKUS_STANDARD_VERSIONING = Pattern.compile("(\\d+).(\\d+).(\\d+)(.Final)?(-redhat-\\\\d+)?$"); + + /** + * Returns the version of Quarkus used in this module, or null if unable to detect. + */ + public static String getQuarkusVersion(Module module) { + Optional quarkusCoreJar = Arrays.stream(ModuleRootManager.getInstance(module).orderEntries() + .runtimeOnly() + .classes() + .getRoots()) + .filter(f -> Pattern.matches(QUARKUS_CORE_PATTERN.pattern(), f.getName())) + .findFirst(); + if (quarkusCoreJar.isPresent()) { + Matcher matcher = QUARKUS_CORE_PATTERN.matcher(quarkusCoreJar.get().getName()); + System.out.println("matches: " + matcher.matches()); + return matcher.group(1); + } else { + return null; + } + } + public static Set getModulesURIs(Project project) { Set uris = new HashSet<>(); for(Module module : ModuleManager.getInstance(project).getModules()) {