From f4d5075f6375656370b837900d6a5c9280c92cc1 Mon Sep 17 00:00:00 2001 From: azerr Date: Mon, 19 Aug 2024 13:52:11 +0200 Subject: [PATCH] build: Improve Compatibility verification Signed-off-by: azerr --- .../psi/core/utils/PsiTypeUtils.java | 47 +- .../java/MicroProfileGraphQLASTValidator.java | 73 +-- .../internal/template/JavaTypesSearch.java | 2 +- .../intellij/qute/psi/utils/PsiTypeUtils.java | 49 +- .../QuarkusConfigMappingProvider.java | 532 +++++++++--------- .../QuarkusConfigPropertiesProvider.java | 2 +- .../properties/QuarkusConfigRootProvider.java | 2 +- .../psi/quarkus/PsiQuarkusUtils.java | 4 + 8 files changed, 314 insertions(+), 397 deletions(-) diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/utils/PsiTypeUtils.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/utils/PsiTypeUtils.java index 6d8423e43..b37ddceb9 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/utils/PsiTypeUtils.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/utils/PsiTypeUtils.java @@ -15,35 +15,17 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiAnnotationMemberValue; -import com.intellij.psi.PsiAnnotationMethod; -import com.intellij.psi.PsiArrayType; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiCompiledElement; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiLiteral; -import com.intellij.psi.PsiManager; -import com.intellij.psi.PsiMember; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiModifierListOwner; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiReference; -import com.intellij.psi.PsiType; -import com.intellij.psi.PsiTypeParameter; -import com.intellij.psi.PsiVariable; +import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.ClassUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -288,7 +270,7 @@ public static VirtualFile getRootDirectory(PsiElement element) { return getRootDirectory(PsiTreeUtil.getParentOfType(element, PsiFile.class)); } - public static VirtualFile getRootDirectory(PsiFile file) { + public static @Nullable VirtualFile getRootDirectory(PsiFile file) { ProjectFileIndex index = ProjectFileIndex.getInstance(file.getProject()); VirtualFile directory = index.getSourceRootForFile(file.getVirtualFile()); if (directory == null) { @@ -297,25 +279,6 @@ public static VirtualFile getRootDirectory(PsiFile file) { return directory; } - public static String getLocation(Project project, VirtualFile directory) { - String location = null; - Module module = ProjectFileIndex.getInstance(project).getModuleForFile(directory); - if (module != null) { - VirtualFile moduleRoot = LocalFileSystem.getInstance().findFileByIoFile(new File(module.getModuleFilePath()).getParentFile()); - String path = VfsUtilCore.getRelativePath(directory, moduleRoot); - if (path != null) { - location = '/' + module.getName() + '/' + path; - } - } - if (location == null) { - location = directory.getPath(); - } - if (location.endsWith("!/")) { - location = location.substring(0, location.length() - 2); - } - return location; - } - public static boolean overlaps(TextRange typeRange, TextRange methodRange) { if (typeRange == null || methodRange == null) { return false; @@ -347,6 +310,6 @@ public static String getRawResolvedTypeName(PsiMethod method) { * @return */ public static boolean isVoidReturnType(PsiMethod method) { - return PsiType.VOID.equals(method.getReturnType()); + return PsiTypes.voidType().equals(method.getReturnType()); } } 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 e1e0fdc5b..2d5d0cb5e 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 @@ -1,35 +1,21 @@ /******************************************************************************* -* Copyright (c) 2023 Red Hat Inc. 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, or the Apache License, Version 2.0 -* which is available at https://www.apache.org/licenses/LICENSE-2.0. -* -* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -* -* Contributors: -* Red Hat Inc. - initial API and implementation -*******************************************************************************/ + * Copyright (c) 2023 Red Hat Inc. 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, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ package com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.graphql.java; -import java.text.MessageFormat; -import java.util.logging.Logger; -import java.util.regex.Matcher; - 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.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.PsiType; +import com.intellij.psi.*; 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; @@ -38,18 +24,22 @@ 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 java.text.MessageFormat; +import java.util.logging.Logger; import static com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.AnnotationUtils.getAnnotation; import static com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.AnnotationUtils.isMatchAnnotation; /** * Diagnostics for microprofile-graphql. - * + *

* TODO: We currently don't check directives on input/output objects and their properties, because * it's not trivial to determine whether a class is used as an input, or an output, or both. That * will possibly require building the whole GraphQL schema on-the-fly, which might be too expensive. * - * @see https://download.eclipse.org/microprofile/microprofile-graphql-1.0/microprofile-graphql.html + * @see MicroProfile GraphQL */ public class MicroProfileGraphQLASTValidator extends JavaASTValidator { @@ -67,7 +57,7 @@ public class MicroProfileGraphQLASTValidator extends JavaASTValidator { @Override public boolean isAdaptedForDiagnostics(JavaDiagnosticsContext context) { Module javaProject = context.getJavaProject(); - if(PsiTypeUtils.findType(javaProject, MicroProfileGraphQLConstants.QUERY_ANNOTATION) == null) { + if (PsiTypeUtils.findType(javaProject, MicroProfileGraphQLConstants.QUERY_ANNOTATION) == null) { return false; } // void GraphQL operations are allowed in Quarkus 3.1 and higher @@ -81,9 +71,9 @@ public boolean isAdaptedForDiagnostics(JavaDiagnosticsContext context) { } @Override - public void visitMethod(PsiMethod node) { + public void visitMethod(@NotNull PsiMethod node) { validateDirectivesOnMethod(node); - if(!allowsVoidReturnFromOperations) { + if (!allowsVoidReturnFromOperations) { validateNoVoidReturnedFromOperations(node); } validateMultiReturnTypeFromSubscriptions(node); @@ -92,7 +82,7 @@ public void visitMethod(PsiMethod node) { } @Override - public void visitClass(PsiClass node) { + public void visitClass(@NotNull PsiClass node) { validateDirectivesOnClass(node); super.visitClass(node); } @@ -115,13 +105,13 @@ private void validateDirectivesOnMethod(PsiMethod node) { private void validateDirectivesOnClass(PsiClass node) { // a class with @GraphQLApi may only have directives allowed on SCHEMA - if(getAnnotation(node, MicroProfileGraphQLConstants.GRAPHQL_API_ANNOTATION) != null) { + if (getAnnotation(node, MicroProfileGraphQLConstants.GRAPHQL_API_ANNOTATION) != null) { validateDirectives(node, TypeSystemDirectiveLocation.SCHEMA); } // if an interface has a `@Union` annotation, it may only have directives allowed on UNION // otherwise it may only have directives allowed on INTERFACE if (node.isInterface()) { - if(getAnnotation(node, MicroProfileGraphQLConstants.UNION_ANNOTATION) != null) { + if (getAnnotation(node, MicroProfileGraphQLConstants.UNION_ANNOTATION) != null) { validateDirectives(node, TypeSystemDirectiveLocation.UNION); } else { validateDirectives(node, TypeSystemDirectiveLocation.INTERFACE); @@ -132,7 +122,7 @@ else if (node.isEnum()) { validateDirectives(node, TypeSystemDirectiveLocation.ENUM); // enum values may only have directives allowed on ENUM_VALUE for (PsiField field : node.getFields()) { - if(field instanceof PsiEnumConstant) { + if (field instanceof PsiEnumConstant) { validateDirectives(field, TypeSystemDirectiveLocation.ENUM_VALUE); } } @@ -199,12 +189,11 @@ private PsiClass getDirectiveDeclaration(PsiAnnotation annotation) { 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())) { + if (node.getReturnTypeElement() == null || !PsiTypeUtils.isVoidReturnType(node)) { return; } for (PsiAnnotation annotation : node.getAnnotations()) { - if (isMatchAnnotation(annotation, MicroProfileGraphQLConstants.QUERY_ANNOTATION) ) { + if (isMatchAnnotation(annotation, MicroProfileGraphQLConstants.QUERY_ANNOTATION)) { super.addDiagnostic(NO_VOID_MESSAGE, // MicroProfileGraphQLConstants.DIAGNOSTIC_SOURCE, // node.getReturnTypeElement(), // @@ -224,12 +213,12 @@ private void validateNoVoidReturnedFromOperations(PsiMethod node) { * A method annotated with `@Subscription` must return a `Multi` or `Flow.Publisher`. */ private void validateMultiReturnTypeFromSubscriptions(PsiMethod node) { - if(node.getReturnType() == null) { + if (node.getReturnType() == null) { return; } for (PsiAnnotation annotation : node.getAnnotations()) { if (isMatchAnnotation(annotation, MicroProfileGraphQLConstants.SUBSCRIPTION_ANNOTATION)) { - if(node.getReturnType().equals(PsiType.VOID)) { + if (PsiTypeUtils.isVoidReturnType(node)) { super.addDiagnostic(SUBSCRIPTION_MUST_RETURN_MULTI, MicroProfileGraphQLConstants.DIAGNOSTIC_SOURCE, node.getReturnTypeElement(), @@ -258,7 +247,7 @@ private void validateMultiReturnTypeFromSubscriptions(PsiMethod node) { * a `Multi` or `Flow.Publisher` type. */ private void validateNoMultiReturnTypeFromQueriesAndMutations(PsiMethod node) { - if(node.getReturnType() == null) { + if (node.getReturnType() == null) { return; } for (PsiAnnotation annotation : node.getAnnotations()) { diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/JavaTypesSearch.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/JavaTypesSearch.java index 5d310bb7a..dc7bd96f1 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/JavaTypesSearch.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/internal/template/JavaTypesSearch.java @@ -121,7 +121,7 @@ private void collectClassesAndInterfaces(PsiPackage packageRoot, List existing = new ArrayList<>(); diff --git a/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiTypeUtils.java b/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiTypeUtils.java index 16de18923..473ea36f3 100644 --- a/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiTypeUtils.java +++ b/src/main/java/com/redhat/devtools/intellij/qute/psi/utils/PsiTypeUtils.java @@ -52,35 +52,6 @@ public static PsiClass findType(Module module, String name) { GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module)); } - /** - * Returns the resolved type name of the javaElement and null - * otherwise - * - * @param javaElement the Java element - * @return the resolved type name of the javaElement and null - * otherwise - */ - public static String getResolvedTypeName(PsiElement javaElement) { - if (javaElement instanceof PsiVariable) { - return getResolvedTypeName((PsiLocalVariable) javaElement); - } else if (javaElement instanceof PsiField) { - return getResolvedTypeName((PsiField) javaElement); - } - return null; - } - - /** - * Returns the resolved type name of the given localVar and null - * otherwise - * - * @param localVar the local variable - * @return the resolved type name of the given localVar and null - * otherwise - */ - public static String getResolvedTypeName(PsiLocalVariable localVar) { - return localVar.getType().getCanonicalText(); - } - /** * Returns the resolved type name of the given field and null * otherwise @@ -93,10 +64,6 @@ public static String getResolvedTypeName(PsiField field) { return field.getType().getCanonicalText(); } - public static String getPropertyType(PsiClass psiClass, String typeName) { - return psiClass != null ? psiClass.getQualifiedName() : typeName; - } - /** * Returns true if the given javaElement is from a Java binary, and * false otherwise @@ -216,40 +183,40 @@ public static PsiClass findType(String className, Module javaProject, ProgressIn * Return true if member is static, and false otherwise * * @param member the member to check for static - * @return + * @return true if member is static, and false otherwise */ public static boolean isStaticMember(PsiMember member) { - return member.getModifierList().hasExplicitModifier(PsiModifier.STATIC); + return member.getModifierList() != null && member.getModifierList().hasExplicitModifier(PsiModifier.STATIC); } /** * Return true if member is private, and false otherwise * * @param member the member to check for private access modifier - * @return + * @return true if member is private, and false otherwise */ public static boolean isPrivateMember(PsiMember member) { - return member.getModifierList().hasExplicitModifier(PsiModifier.PRIVATE); + return member.getModifierList() != null && member.getModifierList().hasExplicitModifier(PsiModifier.PRIVATE); } /** * Return true if member is public, and false otherwise * * @param member the member to check for public access modifier - * @return + * @return true if member is public, and false otherwise */ public static boolean isPublicMember(PsiMember member) { - return member.getModifierList().hasExplicitModifier(PsiModifier.PUBLIC); + return member.getModifierList() != null && member.getModifierList().hasExplicitModifier(PsiModifier.PUBLIC); } /** * Return true if method returns `void`, and false otherwise * * @param method the method to check return value of - * @return + * @return true if method returns `void`, and false otherwise */ public static boolean isVoidReturnType(PsiMethod method) { - return PsiType.VOID.equals(method.getReturnType()); + return PsiTypes.voidType().equals(method.getReturnType()); } /** diff --git a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigMappingProvider.java b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigMappingProvider.java index 61138e5b2..3bf27b532 100644 --- a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigMappingProvider.java +++ b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigMappingProvider.java @@ -1,12 +1,12 @@ /******************************************************************************* -* Copyright (c) 2021 Red Hat Inc. and others. -* All rights reserved. This program and the accompanying materials -* which accompanies this distribution, and is available at -* http://www.eclipse.org/legal/epl-v20.html -* -* Contributors: -* Red Hat Inc. - initial API and implementation -*******************************************************************************/ + * Copyright (c) 2021 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ package com.redhat.microprofile.psi.internal.quarkus.core.properties; import com.intellij.openapi.vfs.VirtualFile; @@ -68,270 +68,264 @@ /** * Properties provider to collect Quarkus properties from the Java classes or * interfaces annotated with "io.smallrye.config.ConfigMapping" annotation. - * + * * @author Angelo ZERR - * * @see https://quarkus.io/guides/config-mappings */ public class QuarkusConfigMappingProvider extends AbstractAnnotationTypeReferencePropertiesProvider { - private static final Logger LOGGER = Logger.getLogger(QuarkusConfigMappingProvider.class.getName()); - - private static final String[] ANNOTATION_NAMES = { CONFIG_MAPPING_ANNOTATION }; - - @Override - protected String[] getAnnotationNames() { - return ANNOTATION_NAMES; - } - - @Override - protected void processAnnotation(PsiModifierListOwner javaElement, PsiAnnotation annotation, String annotationName, - SearchContext context) { - processConfigMapping(javaElement, annotation, context.getCollector()); - } - - // ------------- Process Quarkus ConfigMapping ------------- - - private void processConfigMapping(PsiModifierListOwner javaElement, PsiAnnotation configMappingAnnotation, - IPropertiesCollector collector) { - if (!(javaElement instanceof PsiClass)) { - return; - } - PsiClass configMappingType = (PsiClass) javaElement; - if (!configMappingType.isInterface()) { - // @ConfigMapping can be used only with interfaces. - return; - } - // Location (JAR, src) - VirtualFile packageRoot = PsiTypeUtils.getRootDirectory(PsiTreeUtil.getParentOfType(javaElement, PsiFile.class)); - String location = PsiTypeUtils.getLocation(javaElement.getProject(), packageRoot); - // Quarkus Extension name - String extensionName = PsiQuarkusUtils.getExtensionName(location); - - String prefix = getPrefixFromAnnotation(configMappingAnnotation); - if (prefix == null || prefix.trim().isEmpty()) { - // @ConfigMapping has no prefix - return; - } - // @ConfigMapping(prefix="server") case - List allInterfaces = new ArrayList<>(Arrays.asList(findInterfaces(configMappingType))); - allInterfaces.add(0, configMappingType); - for (PsiClass configMappingInterface : allInterfaces) { - populateConfigObject(configMappingInterface, prefix, extensionName, new HashSet<>(), - configMappingAnnotation, collector); - } - } - - private static PsiClass[] findInterfaces(PsiClass type) { - return type.getInterfaces(); - } - - private void populateConfigObject(PsiClass configMappingType, String prefixStr, String extensionName, - Set typesAlreadyProcessed, PsiAnnotation configMappingAnnotation, IPropertiesCollector collector) { - if (typesAlreadyProcessed.contains(configMappingType)) { - return; - } - typesAlreadyProcessed.add(configMappingType); - PsiElement[] elements = configMappingType.getChildren(); - // Loop for each methods - for (PsiElement child : elements) { - if (child instanceof PsiMethod) { - PsiMethod method = (PsiMethod) child; - if (method.getReturnType() == null || method.getModifierList().hasExplicitModifier(PsiModifier.DEFAULT) || method.hasParameters() - || PsiType.VOID.equals(method.getReturnType())) { - continue; - } - - PsiType psiType = method.getReturnType(); - String returnTypeSignature = getResolvedResultTypeName(method); - String resolvedTypeSignature = getRawResolvedTypeName(method); - if (isOptional(psiType)) { - // it's an optional type - // Optional> databases(); - // extract the type List - psiType = getFirstTypeParameter(psiType); - if (psiType != null) { - resolvedTypeSignature = getRawResolvedTypeName(psiType); - } - } - - PsiClass returnType = findType(method.getManager(), resolvedTypeSignature); - boolean simpleType = isSimpleType(resolvedTypeSignature, returnType); - - if (!simpleType) { - if (returnType != null - && !returnType.isInterface()) { - // When type is not an interface, it requires Converters - // ex : - // interface Server {Log log; class Log {}} - // throw the error; - // java.lang.IllegalArgumentException: SRCFG00013: No Converter registered for - // class org.acme.Server2$Log - // at - // io.smallrye.config.SmallRyeConfig.requireConverter(SmallRyeConfig.java:466) - // at - // io.smallrye.config.ConfigMappingContext.getConverter(ConfigMappingContext.java:113) - continue; - } - } - - String defaultValue = getWithDefault(method); - String propertyName = getPropertyName(method, prefixStr, configMappingAnnotation); - // Method result type - String type = getPropertyType(returnType, resolvedTypeSignature); - - // TODO: extract Javadoc from Java sources - String description = null; - - // Method source - String sourceType = getSourceType(method); - String sourceMethod = getSourceMethod(method); - - // Enumerations - PsiClass enclosedType = getEnclosedType(returnType, resolvedTypeSignature, method.getManager()); - super.updateHint(collector, enclosedType); - - if (!simpleType) { - if (isMap(returnType, resolvedTypeSignature)) { - // Map - propertyName += ".{*}"; - simpleType = true; - } else if (isCollection(returnType, resolvedTypeSignature)) { - // List, List - propertyName += "[*]"; // Generate indexed property. - String genericTypeName = getResolvedTypeName(((PsiClassType) psiType).getParameters()[0]); - resolvedTypeSignature = getRawResolvedTypeName(((PsiClassType) psiType).getParameters()[0]); - returnType = findType(method.getManager(), resolvedTypeSignature); - simpleType = isSimpleType(resolvedTypeSignature, returnType); - } - } - - if (simpleType) { - // String, int, Optional, etc - ItemMetadata metadata = super.addItemMetadata(collector, propertyName, type, description, - sourceType, null, sourceMethod, defaultValue, extensionName, PsiTypeUtils.isBinary(method)); - PsiQuarkusUtils.updateConverterKinds(metadata, method, enclosedType); - } else { - // Other type (App, etc) - populateConfigObject(returnType, propertyName, extensionName, typesAlreadyProcessed, - configMappingAnnotation, collector); - } - } - } - } - - private boolean isSimpleType(String resolvedTypeSignature, PsiClass returnType) { - return returnType == null - || isPrimitiveType(resolvedTypeSignature) - || isSimpleOptionalType(resolvedTypeSignature) - || returnType.isEnum(); - } - - private boolean isSimpleOptionalType(String resolvedTypeSignature) { - return "java.util.OptionalInt".equals(resolvedTypeSignature) - || "java.util.OptionalDouble".equals(resolvedTypeSignature) - || "java.util.OptionalLong".equals(resolvedTypeSignature); - } - - private static boolean isMap(PsiClass type, String typeName) { - // Fast check - if (typeName.startsWith("java.util.Map") || typeName.startsWith("java.util.SortedMap")) { - return true; - } - // TODO : check if type extends Map - return false; - } - - private static boolean isCollection(PsiClass type, String typeName) { - // Fast check - if (typeName.startsWith("java.util.Collection") || typeName.startsWith("java.util.Set") - || typeName.startsWith("java.util.SortedSet") || typeName.startsWith("java.util.List")) { - return true; - } - // TODO : check if type extends Collection - return false; - } - - private String getPropertyName(PsiMember member, String prefix, PsiAnnotation configMappingAnnotation) { - if (hasAnnotation(member, WITH_PARENT_NAME_ANNOTATION)) { - return prefix; - } - return prefix + "." + convertName(member, configMappingAnnotation); - } - - private static String convertName(PsiMember member, PsiAnnotation configMappingAnnotation) { - // 1) Check if @WithName is used - // @WithName("name") - // String host(); - // --> See https://quarkus.io/guides/config-mappings#withname - PsiAnnotation withNameAnnotation = getAnnotation(member, WITH_NAME_ANNOTATION); - if (withNameAnnotation != null) { - String name = getAnnotationMemberValue(withNameAnnotation, WITH_NAME_ANNOTATION_VALUE); - if (StringUtils.isNotEmpty(name)) { - return name; - } - } - - String name = member.getName(); - - // 2) Check if ConfigMapping.NamingStrategy is used - // @ConfigMapping(prefix = "server", namingStrategy = - // ConfigMapping.NamingStrategy.VERBATIM) - // public interface ServerVerbatimNamingStrategy - // --> See https://quarkus.io/guides/config-mappings#namingstrategy - String namingStrategy = getAnnotationMemberValue(configMappingAnnotation, - CONFIG_MAPPING_ANNOTATION_NAMING_STRATEGY); - if (namingStrategy != null) { - int index = namingStrategy.lastIndexOf('.'); - if (index != -1) { - namingStrategy = namingStrategy.substring(index + 1, namingStrategy.length()); - } - switch (namingStrategy) { - case CONFIG_MAPPING_NAMING_STRATEGY_VERBATIM: - // The method name is used as is to map the configuration property. - return name; - case CONFIG_MAPPING_NAMING_STRATEGY_SNAKE_CASE: - // The method name is derived by replacing case changes with an underscore to - // map the configuration property. - return snake(name); - default: - // KEBAB_CASE - // The method name is derived by replacing case changes with a dash to map the - // configuration property. - return hyphenate(name); - } - } - - // None namingStrategy, use KEBAB_CASE as default - return hyphenate(name); - } - - /** - * Returns the value of @WithDefault("a value") and null otherwise. - * - * @param member the filed, method which is annotated with @WithDefault. s - * @return the value of @WithDefault("a value") and null otherwise. - */ - private static String getWithDefault(PsiMember member) { - PsiAnnotation withDefaultAnnotation = getAnnotation(member, WITH_DEFAULT_ANNOTATION); - if (withDefaultAnnotation != null) { - String defaultValue = getAnnotationMemberValue(withDefaultAnnotation, WITH_DEFAULT_ANNOTATION_VALUE); - if (StringUtils.isNotEmpty(defaultValue)) { - return defaultValue; - } - } - return null; - } - - private static String getPrefixFromAnnotation(PsiAnnotation configMappingAnnotation) { - String value = getAnnotationMemberValue(configMappingAnnotation, CONFIG_MAPPING_ANNOTATION_PREFIX); - if (value == null || value.isEmpty()) { - return null; - } - return value; - } - - private static String snake(String orig) { - return join("_", lowerCase(camelHumpsIterator(orig))); - } + private static final String[] ANNOTATION_NAMES = {CONFIG_MAPPING_ANNOTATION}; + + @Override + protected String[] getAnnotationNames() { + return ANNOTATION_NAMES; + } + + @Override + protected void processAnnotation(PsiModifierListOwner javaElement, PsiAnnotation annotation, String annotationName, + SearchContext context) { + processConfigMapping(javaElement, annotation, context.getCollector()); + } + + // ------------- Process Quarkus ConfigMapping ------------- + + private void processConfigMapping(PsiModifierListOwner javaElement, PsiAnnotation configMappingAnnotation, + IPropertiesCollector collector) { + if (!(javaElement instanceof PsiClass configMappingType)) { + return; + } + if (!configMappingType.isInterface()) { + // @ConfigMapping can be used only with interfaces. + return; + } + // Location (JAR, src) + VirtualFile packageRoot = PsiTypeUtils.getRootDirectory(PsiTreeUtil.getParentOfType(javaElement, PsiFile.class)); + String location = packageRoot != null ? packageRoot.getUrl() : null; + // Quarkus Extension name + String extensionName = PsiQuarkusUtils.getExtensionName(location); + + String prefix = getPrefixFromAnnotation(configMappingAnnotation); + if (prefix == null || prefix.trim().isEmpty()) { + // @ConfigMapping has no prefix + return; + } + // @ConfigMapping(prefix="server") case + List allInterfaces = new ArrayList<>(Arrays.asList(findInterfaces(configMappingType))); + allInterfaces.add(0, configMappingType); + for (PsiClass configMappingInterface : allInterfaces) { + populateConfigObject(configMappingInterface, prefix, extensionName, new HashSet<>(), + configMappingAnnotation, collector); + } + } + + private static PsiClass[] findInterfaces(PsiClass type) { + return type.getInterfaces(); + } + + private void populateConfigObject(PsiClass configMappingType, String prefixStr, String extensionName, + Set typesAlreadyProcessed, PsiAnnotation configMappingAnnotation, IPropertiesCollector collector) { + if (typesAlreadyProcessed.contains(configMappingType)) { + return; + } + typesAlreadyProcessed.add(configMappingType); + PsiElement[] elements = configMappingType.getChildren(); + // Loop for each methods + for (PsiElement child : elements) { + if (child instanceof PsiMethod method) { + if (method.getReturnType() == null || method.getModifierList().hasExplicitModifier(PsiModifier.DEFAULT) || method.hasParameters() + || PsiTypeUtils.isVoidReturnType(method)) { + continue; + } + + PsiType psiType = method.getReturnType(); + String resolvedTypeSignature = getRawResolvedTypeName(method); + if (isOptional(psiType)) { + // it's an optional type + // Optional> databases(); + // extract the type List + psiType = getFirstTypeParameter(psiType); + if (psiType != null) { + resolvedTypeSignature = getRawResolvedTypeName(psiType); + } + } + + PsiClass returnType = findType(method.getManager(), resolvedTypeSignature); + boolean simpleType = isSimpleType(resolvedTypeSignature, returnType); + + if (!simpleType) { + if (returnType != null + && !returnType.isInterface()) { + // When type is not an interface, it requires Converters + // ex : + // interface Server {Log log; class Log {}} + // throw the error; + // java.lang.IllegalArgumentException: SRCFG00013: No Converter registered for + // class org.acme.Server2$Log + // at + // io.smallrye.config.SmallRyeConfig.requireConverter(SmallRyeConfig.java:466) + // at + // io.smallrye.config.ConfigMappingContext.getConverter(ConfigMappingContext.java:113) + continue; + } + } + + String defaultValue = getWithDefault(method); + String propertyName = getPropertyName(method, prefixStr, configMappingAnnotation); + // Method result type + String type = getPropertyType(returnType, resolvedTypeSignature); + + // TODO: extract Javadoc from Java sources + String description = null; + + // Method source + String sourceType = getSourceType(method); + String sourceMethod = getSourceMethod(method); + + // Enumerations + PsiClass enclosedType = getEnclosedType(returnType, resolvedTypeSignature, method.getManager()); + super.updateHint(collector, enclosedType); + + if (!simpleType) { + if (isMap(returnType, resolvedTypeSignature)) { + // Map + propertyName += ".{*}"; + simpleType = true; + } else if (isCollection(returnType, resolvedTypeSignature)) { + // List, List + propertyName += "[*]"; // Generate indexed property. + String genericTypeName = getResolvedTypeName(((PsiClassType) psiType).getParameters()[0]); + resolvedTypeSignature = getRawResolvedTypeName(((PsiClassType) psiType).getParameters()[0]); + returnType = findType(method.getManager(), resolvedTypeSignature); + simpleType = isSimpleType(resolvedTypeSignature, returnType); + } + } + + if (simpleType) { + // String, int, Optional, etc + ItemMetadata metadata = super.addItemMetadata(collector, propertyName, type, description, + sourceType, null, sourceMethod, defaultValue, extensionName, PsiTypeUtils.isBinary(method)); + PsiQuarkusUtils.updateConverterKinds(metadata, method, enclosedType); + } else { + // Other type (App, etc) + populateConfigObject(returnType, propertyName, extensionName, typesAlreadyProcessed, + configMappingAnnotation, collector); + } + } + } + } + + private boolean isSimpleType(String resolvedTypeSignature, PsiClass returnType) { + return returnType == null + || isPrimitiveType(resolvedTypeSignature) + || isSimpleOptionalType(resolvedTypeSignature) + || returnType.isEnum(); + } + + private boolean isSimpleOptionalType(String resolvedTypeSignature) { + return "java.util.OptionalInt".equals(resolvedTypeSignature) + || "java.util.OptionalDouble".equals(resolvedTypeSignature) + || "java.util.OptionalLong".equals(resolvedTypeSignature); + } + + private static boolean isMap(PsiClass type, String typeName) { + // Fast check + if (typeName.startsWith("java.util.Map") || typeName.startsWith("java.util.SortedMap")) { + return true; + } + // TODO : check if type extends Map + return false; + } + + private static boolean isCollection(PsiClass type, String typeName) { + // Fast check + if (typeName.startsWith("java.util.Collection") || typeName.startsWith("java.util.Set") + || typeName.startsWith("java.util.SortedSet") || typeName.startsWith("java.util.List")) { + return true; + } + // TODO : check if type extends Collection + return false; + } + + private String getPropertyName(PsiMember member, String prefix, PsiAnnotation configMappingAnnotation) { + if (hasAnnotation(member, WITH_PARENT_NAME_ANNOTATION)) { + return prefix; + } + return prefix + "." + convertName(member, configMappingAnnotation); + } + + private static String convertName(PsiMember member, PsiAnnotation configMappingAnnotation) { + // 1) Check if @WithName is used + // @WithName("name") + // String host(); + // --> See https://quarkus.io/guides/config-mappings#withname + PsiAnnotation withNameAnnotation = getAnnotation(member, WITH_NAME_ANNOTATION); + if (withNameAnnotation != null) { + String name = getAnnotationMemberValue(withNameAnnotation, WITH_NAME_ANNOTATION_VALUE); + if (StringUtils.isNotEmpty(name)) { + return name; + } + } + + String name = member.getName(); + + // 2) Check if ConfigMapping.NamingStrategy is used + // @ConfigMapping(prefix = "server", namingStrategy = + // ConfigMapping.NamingStrategy.VERBATIM) + // public interface ServerVerbatimNamingStrategy + // --> See https://quarkus.io/guides/config-mappings#namingstrategy + String namingStrategy = getAnnotationMemberValue(configMappingAnnotation, + CONFIG_MAPPING_ANNOTATION_NAMING_STRATEGY); + if (namingStrategy != null) { + int index = namingStrategy.lastIndexOf('.'); + if (index != -1) { + namingStrategy = namingStrategy.substring(index + 1); + } + switch (namingStrategy) { + case CONFIG_MAPPING_NAMING_STRATEGY_VERBATIM: + // The method name is used as is to map the configuration property. + return name; + case CONFIG_MAPPING_NAMING_STRATEGY_SNAKE_CASE: + // The method name is derived by replacing case changes with an underscore to + // map the configuration property. + return snake(name); + default: + // KEBAB_CASE + // The method name is derived by replacing case changes with a dash to map the + // configuration property. + return hyphenate(name); + } + } + + // None namingStrategy, use KEBAB_CASE as default + return hyphenate(name); + } + + /** + * Returns the value of @WithDefault("a value") and null otherwise. + * + * @param member the filed, method which is annotated with @WithDefault. s + * @return the value of @WithDefault("a value") and null otherwise. + */ + private static String getWithDefault(PsiMember member) { + PsiAnnotation withDefaultAnnotation = getAnnotation(member, WITH_DEFAULT_ANNOTATION); + if (withDefaultAnnotation != null) { + String defaultValue = getAnnotationMemberValue(withDefaultAnnotation, WITH_DEFAULT_ANNOTATION_VALUE); + if (StringUtils.isNotEmpty(defaultValue)) { + return defaultValue; + } + } + return null; + } + + private static String getPrefixFromAnnotation(PsiAnnotation configMappingAnnotation) { + String value = getAnnotationMemberValue(configMappingAnnotation, CONFIG_MAPPING_ANNOTATION_PREFIX); + if (value == null || value.isEmpty()) { + return null; + } + return value; + } + + private static String snake(String orig) { + return join("_", lowerCase(camelHumpsIterator(orig))); + } } diff --git a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java index 9831e89cd..01c07d7bd 100644 --- a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java +++ b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigPropertiesProvider.java @@ -134,7 +134,7 @@ private void processConfigProperties(PsiModifierListOwner psiElement, PsiAnnotat PsiClass configPropertiesType = (PsiClass) psiElement; // Location (JAR, src) VirtualFile packageRoot = PsiTypeUtils.getRootDirectory(psiElement); - String location = PsiTypeUtils.getLocation(psiElement.getProject(), packageRoot); + String location = packageRoot != null ? packageRoot.getUrl() : null; // Quarkus Extension name String extensionName = PsiQuarkusUtils.getExtensionName(location); diff --git a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigRootProvider.java b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigRootProvider.java index f79968457..5256769e5 100644 --- a/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigRootProvider.java +++ b/src/main/java/com/redhat/microprofile/psi/internal/quarkus/core/properties/QuarkusConfigRootProvider.java @@ -108,7 +108,7 @@ private void processConfigRoot(PsiModifierListOwner psiElement, PsiAnnotation co } // Location (JAR, src) VirtualFile packageRoot = PsiTypeUtils.getRootDirectory(PsiTreeUtil.getParentOfType(psiElement, PsiFile.class)); - String location = PsiTypeUtils.getLocation(psiElement.getProject(), packageRoot); + String location = packageRoot != null ? packageRoot.getUrl() : null; // Quarkus Extension name String extensionName = PsiQuarkusUtils.getExtensionName(location); diff --git a/src/main/java/com/redhat/microprofile/psi/quarkus/PsiQuarkusUtils.java b/src/main/java/com/redhat/microprofile/psi/quarkus/PsiQuarkusUtils.java index 6155d3a18..18e81a97d 100644 --- a/src/main/java/com/redhat/microprofile/psi/quarkus/PsiQuarkusUtils.java +++ b/src/main/java/com/redhat/microprofile/psi/quarkus/PsiQuarkusUtils.java @@ -37,6 +37,10 @@ public static String getExtensionName(String location) { if (location == null) { return null; } + if (location.endsWith("!/")) { + // In IJ, location JAR ends with !/ + location = location.substring(0, location.length() - 2); + } if (!location.endsWith(".jar")) { return null; }