Skip to content

Commit

Permalink
Return checks for void operations, only for Quarkus less than 3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
jmartisk committed Sep 12, 2023
1 parent e41cf2e commit 3934362
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,29 @@


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;
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.java.validators.JavaASTValidator;
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;
Expand All @@ -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);
}

Expand Down Expand Up @@ -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()) {
Expand All @@ -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");
Expand Down Expand Up @@ -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);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
*/
public enum MicroProfileGraphQLErrorCode implements IJavaErrorCode {

NO_VOID_QUERIES,
NO_VOID_MUTATIONS,
WRONG_DIRECTIVE_PLACEMENT
;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<VirtualFile> 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<String> getModulesURIs(Project project) {
Set<String> uris = new HashSet<>();
for(Module module : ModuleManager.getInstance(project).getModules()) {
Expand Down

0 comments on commit 3934362

Please sign in to comment.