From 1ac9d8ac4ebf7941acc0bf74ceb63c74b666560c Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Fri, 31 May 2024 14:12:56 +0200 Subject: [PATCH] [lombok] IDEA-352727 Support incomplete mode - change tests - extract everything connected to dumb and incomplete mode into utils GitOrigin-RevId: 979f4333917f68d947c9d28c04d7e6dfef8400ae --- .../plugin/extension/LombokElementFinder.java | 4 +- .../processor/LombokProcessorManager.java | 6 +- .../provider/LombokAugmentProvider.java | 84 +------- .../plugin/util/DumbIncompleteModeUtil.java | 181 ++++++++++++++++++ .../plugin/util/IncompleteModeUtil.java | 23 --- .../plugin/util/LombokProcessorUtil.java | 4 +- .../plugin/util/PsiAnnotationSearchUtil.java | 98 ++-------- .../plugin/util/PsiAnnotationUtil.java | 4 +- .../plugin/AbstractLombokParsingTestCase.java | 42 ++-- .../plugin/IncompleteAndDumbModeRunner.java | 19 -- .../FieldDefaultsWithoutLombokImportTest.java | 4 +- .../configsystem/FieldNameConstantsTest.java | 4 +- .../processor/FieldNameConstantsOldTest.java | 4 +- .../processor/FieldNameConstantsTest.java | 4 +- plugins/lombok/testData/after/TestOnX.java | 18 +- plugins/lombok/testData/before/TestOnX.java | 7 +- 16 files changed, 257 insertions(+), 249 deletions(-) create mode 100644 plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/DumbIncompleteModeUtil.java delete mode 100644 plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/IncompleteModeUtil.java delete mode 100644 plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/IncompleteAndDumbModeRunner.java diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokElementFinder.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokElementFinder.java index 8699b181892c..954a88ab2715 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokElementFinder.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/extension/LombokElementFinder.java @@ -5,7 +5,7 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElementFinder; import com.intellij.psi.impl.file.impl.JavaFileManager; import com.intellij.psi.search.GlobalSearchScope; -import de.plushnikov.intellij.plugin.util.IncompleteModeUtil; +import de.plushnikov.intellij.plugin.util.DumbIncompleteModeUtil; import de.plushnikov.intellij.plugin.util.LombokLibraryUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,7 +24,7 @@ public final class LombokElementFinder extends PsiElementFinder { @Override public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { if (!LombokLibraryUtil.hasLombokLibrary(myProject) && - !IncompleteModeUtil.isIncompleteMode(myProject)) { + !DumbIncompleteModeUtil.isIncompleteMode(myProject)) { return null; } diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/processor/LombokProcessorManager.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/processor/LombokProcessorManager.java index 2e99fe1fda70..a838276663ef 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/processor/LombokProcessorManager.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/processor/LombokProcessorManager.java @@ -22,7 +22,7 @@ import de.plushnikov.intellij.plugin.processor.method.BuilderClassMethodProcesso import de.plushnikov.intellij.plugin.processor.method.BuilderMethodProcessor; import de.plushnikov.intellij.plugin.processor.method.DelegateMethodProcessor; import de.plushnikov.intellij.plugin.processor.modifier.*; -import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil; +import de.plushnikov.intellij.plugin.util.DumbIncompleteModeUtil; import org.jetbrains.annotations.NotNull; import java.util.Arrays; @@ -368,8 +368,8 @@ public final class LombokProcessorManager { return Collections.emptyList(); } String qualifiedName = psiAnnotation.getQualifiedName(); - if (PsiAnnotationSearchUtil.isDumbOrIncompleteMode(psiAnnotation)) { - qualifiedName = PsiAnnotationSearchUtil.findLombokAnnotationQualifiedNameInIncompleteMode(psiAnnotation); + if (DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiAnnotation)) { + qualifiedName = DumbIncompleteModeUtil.findLombokAnnotationQualifiedNameInDumbIncompleteMode(psiAnnotation); } if (StringUtil.isEmpty(qualifiedName) || !qualifiedName.contains("lombok")) { return Collections.emptyList(); diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/provider/LombokAugmentProvider.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/provider/LombokAugmentProvider.java index 63a7813ee4a9..3c6eebc4c039 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/provider/LombokAugmentProvider.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/provider/LombokAugmentProvider.java @@ -8,9 +8,6 @@ import com.intellij.psi.*; import com.intellij.psi.augment.PsiAugmentProvider; import com.intellij.psi.augment.PsiExtensionMethod; import com.intellij.psi.impl.source.PsiExtensibleClass; -import com.intellij.psi.util.CachedValuesManager; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.containers.ContainerUtil; import com.siyeh.ig.psiutils.InitializationUtils; import de.plushnikov.intellij.plugin.LombokClassNames; import de.plushnikov.intellij.plugin.processor.LombokProcessorManager; @@ -19,7 +16,7 @@ import de.plushnikov.intellij.plugin.processor.ValProcessor; import de.plushnikov.intellij.plugin.processor.lombok.LombokAnnotationProcessor; import de.plushnikov.intellij.plugin.processor.method.ExtensionMethodsHelper; import de.plushnikov.intellij.plugin.processor.modifier.ModifierProcessor; -import de.plushnikov.intellij.plugin.util.IncompleteModeUtil; +import de.plushnikov.intellij.plugin.util.DumbIncompleteModeUtil; import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil; import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil; import org.jetbrains.annotations.NotNull; @@ -51,7 +48,7 @@ public final class LombokAugmentProvider extends PsiAugmentProvider implements P @Override protected Set transformModifiers(@NotNull PsiModifierList modifierList, @NotNull final Set modifiers) { // skip if no lombok library is present - if (!hasLombokLibrary(modifierList.getProject()) && !isIncompleteModeWithLombokAnnotation(modifierList)) { + if (!hasLombokLibrary(modifierList.getProject()) && !DumbIncompleteModeUtil.isIncompleteModeWithLombokAnnotation(modifierList)) { return modifiers; } @@ -157,7 +154,7 @@ public final class LombokAugmentProvider extends PsiAugmentProvider implements P } // skip processing if disabled, or no lombok library is present - if (!hasLombokLibrary(element.getProject()) && !isIncompleteModeWithLombokAnnotation(psiClass)) { + if (!hasLombokLibrary(element.getProject()) && !DumbIncompleteModeUtil.isIncompleteModeWithLombokAnnotation(psiClass)) { return emptyResult; } if (psiClass.isAnnotationType() && type == PsiMethod.class) { @@ -204,79 +201,4 @@ public final class LombokAugmentProvider extends PsiAugmentProvider implements P } return ExtensionMethodsHelper.getExtensionMethods(aClass, nameHint, context); } - - - /** - * Checks if the project is in incomplete mode and the class contains Lombok annotation. - * Incomplete mode means that the project contains incomplete dependencies. - * There is no purpose to be absolutely accurate, but it can help to reduce false red-code highlighting and help with some completion - * This method can be quite slow, but it calls only in incomplete mode and cache value for file - * @param context The PsiElement to check for Lombok annotations. - * @return true if the project is in incomplete mode and the class has any Lombok annotation or if any of the class fields have any Lombok annotation; - * otherwise, false. - */ - private static boolean isIncompleteModeWithLombokAnnotation(@NotNull PsiElement context) { - if (!IncompleteModeUtil.isIncompleteMode(context)) { - return false; - } - - if (context.getLanguage() != JavaLanguage.INSTANCE) { - return false; - } - - if (context instanceof PsiModifierList modifierList && hasAnyLombokAnnotation(modifierList.getAnnotations())) { - return true; - } - - PsiClass psiClass = context instanceof PsiClass castedClass ? castedClass : - PsiTreeUtil.getParentOfType(context, PsiClass.class); - - if (psiClass == null) return false; - - return CachedValuesManager.getProjectPsiDependentCache(psiClass, psiElement -> { - if (!(psiElement.getContainingFile() instanceof PsiJavaFile file)) { - return false; - } - if (file.getImportList() != null && ContainerUtil.exists(file.getImportList().getAllImportStatements(), statement -> { - PsiJavaCodeReferenceElement reference = statement.getImportReference(); - return reference != null && reference.getText().startsWith("lombok"); - })) { - return true; - } - while (psiElement != null) { - if (psiElement instanceof PsiExtensibleClass extensibleClass && - (hasAnyLombokAnnotation(extensibleClass.getAnnotations()) || - ContainerUtil.exists(extensibleClass.getOwnFields(), field -> hasAnyLombokAnnotation(field.getAnnotations())) || - ContainerUtil.exists(extensibleClass.getOwnMethods(), method -> hasAnyLombokAnnotation(method.getAnnotations())) || - (file.getImportList() != null && ContainerUtil.exists(file.getImportList().getAllImportStatements(), statement -> { - PsiJavaCodeReferenceElement reference = statement.getImportReference(); - return reference != null && reference.getText().startsWith("lombok"); - })))) { - return true; - } - psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class); - } - return false; - }); - } - - /** - * Checks if the given PsiModifierListOwner has any Lombok annotation. - * It is used only for incomplete mode. - * - * @param annotations The annotations to check for Lombok annotations. - * @return true if the modifierListOwner has any Lombok annotation, otherwise false. - */ - private static boolean hasAnyLombokAnnotation(PsiAnnotation @NotNull [] annotations) { - return ContainerUtil.exists(annotations, annotation -> { - if (annotation == null) { - return false; - } - String qualifiedName = annotation.getText(); - if (qualifiedName == null) { - return false; - } - return qualifiedName.startsWith("@lombok."); - }); - } } diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/DumbIncompleteModeUtil.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/DumbIncompleteModeUtil.java new file mode 100644 index 000000000000..d499bcdb737c --- /dev/null +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/DumbIncompleteModeUtil.java @@ -0,0 +1,181 @@ +package de.plushnikov.intellij.plugin.util; + +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.IncompleteDependenciesService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.PsiExtensibleClass; +import com.intellij.psi.util.CachedValuesManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import de.plushnikov.intellij.plugin.processor.LombokProcessorManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +public final class DumbIncompleteModeUtil { + private DumbIncompleteModeUtil() { + } + + public static boolean isIncompleteMode(@NotNull Project project) { + return Registry.is("lombok.incomplete.mode.enabled", false) && + !project.getService(IncompleteDependenciesService.class).getState().isComplete(); + } + + public static boolean isDumbOrIncompleteMode(@NotNull PsiElement context) { + Project project = context.getProject(); + return (DumbService.isDumb(project) && Registry.is("lombok.dumb.mode.enabled", false)) || + isIncompleteMode(context.getProject()); + } + + /** + * Searches for a specific annotation in the list of annotations of a PsiModifierListOwner in dumb mode. + * + * @param owner the PsiModifierListOwner whose annotations to search. + * @param annotationFQN the fully qualified name of the annotation to search for. + * @return the found PsiAnnotation object if the annotation is found, or null if not found. + */ + static @Nullable PsiAnnotation findAnnotationInDumbOrIncompleteMode(@NotNull PsiModifierListOwner owner, @NotNull String annotationFQN) { + for (PsiAnnotation annotation : owner.getAnnotations()) { + if (hasQualifiedNameInDumbOrIncompleteMode(annotation, annotationFQN)) { + return annotation; + } + } + return null; + } + + /** + * Finds the fully qualified name of a Lombok annotation based only on psi structure, without resolving. + * Only annotations which have processors are supported + * + * @param psiAnnotation the PsiAnnotation object representing the Lombok annotation + * @return the fully qualified name of the Lombok annotation, or null if the annotation is unresolved or not a Lombok annotation + */ + public static @Nullable String findLombokAnnotationQualifiedNameInDumbIncompleteMode(@NotNull PsiAnnotation psiAnnotation) { + String qualifiedName = psiAnnotation.getQualifiedName(); + if (StringUtil.isEmpty(qualifiedName)) return null; + if (qualifiedName.startsWith("lombok")) return qualifiedName; + LombokProcessorManager instance = LombokProcessorManager.getInstance(); + MultiMap names = instance.getOurSupportedShortNames(); + Collection fullQualifiedNames = names.get(qualifiedName); + for (String fullQualifiedName : fullQualifiedNames) { + if (hasQualifiedNameInDumbOrIncompleteMode(psiAnnotation, fullQualifiedName)) { + return fullQualifiedName; + } + } + return qualifiedName; + } + + /** + * Checks if the given annotation has a qualified name in dumb mode. + * It is not fully accurate because it can't do resolving + * + * @param annotation the PsiAnnotation object to check. + * @param fqn the fully qualified name to check against. + * @return true if the annotation has the specified qualified name in dumb mode, otherwise false. + */ + static boolean hasQualifiedNameInDumbOrIncompleteMode(PsiAnnotation annotation, @NotNull String fqn) { + PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement(); + if (referenceElement == null) return false; + String qualifiedName = referenceElement.getReferenceName(); + if (qualifiedName == null) return false; + if (qualifiedName.equals(fqn) || ("java.lang." + qualifiedName).equals(fqn)) return true; + String referenceElementText = referenceElement.getText(); + if (referenceElementText != null && referenceElementText.equals(fqn)) return true; + if (!fqn.endsWith(qualifiedName)) return false; + PsiFile containingFile = annotation.getContainingFile(); + if (!(containingFile instanceof PsiJavaFile javaFile)) { + return false; + } + String packageName = StringUtil.getPackageName(fqn); + PsiImportList importList = javaFile.getImportList(); + if (importList == null) return false; + int indexMayByOuterClass = fqn.length() - qualifiedName.length() - 1; + String mayBeOuterClass = indexMayByOuterClass > 0 ? fqn.substring(0, indexMayByOuterClass) : null; + return importList.findOnDemandImportStatement(packageName) != null || + importList.findSingleClassImportStatement(fqn) != null || + (mayBeOuterClass!=null && importList.findSingleClassImportStatement(mayBeOuterClass) != null); + } + + /** + * Checks if the project is in incomplete mode and the class contains Lombok annotation. + * Incomplete mode means that the project contains incomplete dependencies. + * There is no purpose to be absolutely accurate, but it can help to reduce false red-code highlighting and help with some completion + * This method can be quite slow, but it calls only in incomplete mode and cache value for file + * @param context The PsiElement to check for Lombok annotations. + * @return true if the project is in incomplete mode and the class has any Lombok annotation or if any of the class fields have any Lombok annotation; + * otherwise, false. + */ + public static boolean isIncompleteModeWithLombokAnnotation(@NotNull PsiElement context) { + if (!isIncompleteMode(context.getProject())) { + return false; + } + + if (context.getLanguage() != JavaLanguage.INSTANCE) { + return false; + } + + if (context instanceof PsiModifierList modifierList && hasAnyLombokAnnotation(modifierList.getAnnotations())) { + return true; + } + + PsiClass psiClass = context instanceof PsiClass castedClass ? castedClass : + PsiTreeUtil.getParentOfType(context, PsiClass.class); + + if (psiClass == null) return false; + + return CachedValuesManager.getProjectPsiDependentCache(psiClass, psiElement -> { + if (!(psiElement.getContainingFile() instanceof PsiJavaFile file)) { + return false; + } + if (file.getImportList() != null && ContainerUtil.exists(file.getImportList().getAllImportStatements(), statement -> { + return canBeLombokImport(statement); + })) { + return true; + } + while (psiElement != null) { + if (psiElement instanceof PsiExtensibleClass extensibleClass && + (hasAnyLombokAnnotation(extensibleClass.getAnnotations()) || + ContainerUtil.exists(extensibleClass.getOwnFields(), field -> hasAnyLombokAnnotation(field.getAnnotations())) || + ContainerUtil.exists(extensibleClass.getOwnMethods(), method -> hasAnyLombokAnnotation(method.getAnnotations())) || + (file.getImportList() != null && ContainerUtil.exists(file.getImportList().getAllImportStatements(), statement -> { + return canBeLombokImport(statement); + })))) { + return true; + } + psiElement = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class); + } + return false; + }); + } + + private static boolean canBeLombokImport(@NotNull PsiImportStatementBase statement) { + PsiJavaCodeReferenceElement reference = statement.getImportReference(); + return reference != null && reference.getText().startsWith("lombok"); + } + + /** + * Checks if the given PsiModifierListOwner has any Lombok annotation. + * It is used only for incomplete mode. + * + * @param annotations The annotations to check for Lombok annotations. + * @return true if the modifierListOwner has any Lombok annotation, otherwise false. + */ + private static boolean hasAnyLombokAnnotation(PsiAnnotation @NotNull [] annotations) { + return ContainerUtil.exists(annotations, annotation -> { + if (annotation == null) { + return false; + } + String qualifiedName = annotation.getText(); + if (qualifiedName == null) { + return false; + } + return qualifiedName.startsWith("@lombok."); + }); + } +} diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/IncompleteModeUtil.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/IncompleteModeUtil.java deleted file mode 100644 index e2e8853d12e8..000000000000 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/IncompleteModeUtil.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.plushnikov.intellij.plugin.util; - -import com.intellij.openapi.project.IncompleteDependenciesService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiJavaFile; -import org.jetbrains.annotations.NotNull; - -public final class IncompleteModeUtil { - private IncompleteModeUtil() { - } - - public static boolean isIncompleteMode(@NotNull PsiElement context) { - if (!(context.getContainingFile() instanceof PsiJavaFile)) return false; - return isIncompleteMode(context.getProject()); - } - - public static boolean isIncompleteMode(@NotNull Project project) { - return Registry.is("lombok.incomplete.mode.enabled", false) && - !project.getService(IncompleteDependenciesService.class).getState().isComplete(); - } -} diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/LombokProcessorUtil.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/LombokProcessorUtil.java index fbec9436d585..d8f70ee1dc7a 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/LombokProcessorUtil.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/LombokProcessorUtil.java @@ -85,7 +85,7 @@ public final class LombokProcessorUtil { public static Collection getOldOnX(@NotNull PsiAnnotation psiAnnotation, @NotNull String parameterName) { PsiAnnotationMemberValue onXValue; - if (PsiAnnotationSearchUtil.isDumbOrIncompleteMode(psiAnnotation)) { + if (DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiAnnotation)) { onXValue = psiAnnotation.findDeclaredAttributeValue(parameterName); } else { @@ -99,7 +99,7 @@ public final class LombokProcessorUtil { } public static Collection getNewOnX(@NotNull PsiAnnotation psiAnnotation, @NotNull String parameterName) { - if (PsiAnnotationSearchUtil.isDumbOrIncompleteMode(psiAnnotation) || psiAnnotation.hasAttribute(parameterName)) { + if (DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiAnnotation) || psiAnnotation.hasAttribute(parameterName)) { final Collection annotations = PsiAnnotationUtil.getAnnotationValues(psiAnnotation, parameterName, PsiAnnotation.class, List.of()); return collectAnnotationStrings(annotations); diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/PsiAnnotationSearchUtil.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/PsiAnnotationSearchUtil.java index 808e1a95240f..1b758b27755e 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/PsiAnnotationSearchUtil.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/PsiAnnotationSearchUtil.java @@ -1,12 +1,10 @@ package de.plushnikov.intellij.plugin.util; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.*; +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiJavaCodeReferenceElement; +import com.intellij.psi.PsiModifierListOwner; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.MultiMap; -import de.plushnikov.intellij.plugin.processor.LombokProcessorManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -17,57 +15,19 @@ public final class PsiAnnotationSearchUtil { @Nullable public static PsiAnnotation findAnnotation(@NotNull PsiModifierListOwner psiModifierListOwner, @NotNull String annotationFQN) { - if (isDumbOrIncompleteMode(psiModifierListOwner)) { - return findAnnotationInDumbMode(psiModifierListOwner, annotationFQN); + if (DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiModifierListOwner)) { + return DumbIncompleteModeUtil.findAnnotationInDumbOrIncompleteMode(psiModifierListOwner, annotationFQN); } return psiModifierListOwner.getAnnotation(annotationFQN); } - private static @Nullable PsiAnnotation findAnnotationInDumbMode(@NotNull PsiModifierListOwner owner, @NotNull String annotationFQN) { - for (PsiAnnotation annotation : owner.getAnnotations()) { - if (hasQualifiedNameInDumbMode(annotation, annotationFQN)) { - return annotation; - } - } - return null; - } - - private static boolean hasQualifiedNameInDumbMode(PsiAnnotation annotation, @NotNull String fqn) { - PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement(); - if (referenceElement == null) return false; - String qualifiedName = referenceElement.getReferenceName(); - if (qualifiedName == null) return false; - if (qualifiedName.equals(fqn)) return true; - String referenceElementText = referenceElement.getText(); - if (referenceElementText != null && referenceElementText.equals(fqn)) return true; - if (!fqn.endsWith(qualifiedName)) return false; - PsiFile containingFile = annotation.getContainingFile(); - if (!(containingFile instanceof PsiJavaFile javaFile)) { - return false; - } - String packageName = StringUtil.getPackageName(fqn); - PsiImportList importList = javaFile.getImportList(); - if (importList == null) return false; - int indexMayByOuterClass = fqn.length() - qualifiedName.length() - 1; - String mayBeOuterClass = indexMayByOuterClass > 0 ? fqn.substring(0, indexMayByOuterClass) : null; - return importList.findOnDemandImportStatement(packageName) != null || - importList.findSingleClassImportStatement(fqn) != null || - (mayBeOuterClass!=null && importList.findSingleClassImportStatement(mayBeOuterClass) != null); - } - - public static boolean isDumbOrIncompleteMode(@NotNull PsiElement context) { - Project project = context.getProject(); - return DumbService.isDumb(project) || - IncompleteModeUtil.isIncompleteMode(context); - } - @Nullable public static PsiAnnotation findAnnotation(@NotNull PsiModifierListOwner psiModifierListOwner, String @NotNull ... annotationFQNs) { - boolean isDumbMode = isDumbOrIncompleteMode(psiModifierListOwner); + boolean isDumbMode = DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiModifierListOwner); for (String annotationFQN : annotationFQNs) { PsiAnnotation annotation; if (isDumbMode) { - annotation = findAnnotationInDumbMode(psiModifierListOwner, annotationFQN); + annotation = DumbIncompleteModeUtil.findAnnotationInDumbOrIncompleteMode(psiModifierListOwner, annotationFQN); } else { annotation = psiModifierListOwner.getAnnotation(annotationFQN); @@ -80,8 +40,8 @@ public final class PsiAnnotationSearchUtil { } public static boolean isAnnotatedWith(@NotNull PsiModifierListOwner psiModifierListOwner, @NotNull String annotationFQN) { - if (isDumbOrIncompleteMode(psiModifierListOwner)) { - return findAnnotationInDumbMode(psiModifierListOwner, annotationFQN) != null; + if (DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiModifierListOwner)) { + return DumbIncompleteModeUtil.findAnnotationInDumbOrIncompleteMode(psiModifierListOwner, annotationFQN) != null; } return psiModifierListOwner.hasAnnotation(annotationFQN); } @@ -133,49 +93,17 @@ public final class PsiAnnotationSearchUtil { public static boolean checkAnnotationHasOneOfFQNs(@NotNull PsiAnnotation psiAnnotation, String @NotNull ... annotationFQNs) { - if (isDumbOrIncompleteMode(psiAnnotation)) { - return ContainerUtil.or(annotationFQNs, fqn-> hasQualifiedNameInDumbMode(psiAnnotation, fqn)); + if (DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiAnnotation)) { + return ContainerUtil.or(annotationFQNs, fqn-> DumbIncompleteModeUtil.hasQualifiedNameInDumbOrIncompleteMode(psiAnnotation, fqn)); } return ContainerUtil.or(annotationFQNs, psiAnnotation::hasQualifiedName); } public static boolean checkAnnotationHasOneOfFQNs(@NotNull PsiAnnotation psiAnnotation, @NotNull Set annotationFQNs) { - if (isDumbOrIncompleteMode(psiAnnotation)) { - return ContainerUtil.or(annotationFQNs, fqn -> hasQualifiedNameInDumbMode(psiAnnotation, fqn)); + if (DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiAnnotation)) { + return ContainerUtil.or(annotationFQNs, fqn -> DumbIncompleteModeUtil.hasQualifiedNameInDumbOrIncompleteMode(psiAnnotation, fqn)); } return ContainerUtil.or(annotationFQNs, psiAnnotation::hasQualifiedName); } - - /** - * Finds the fully qualified name of a Lombok annotation based only on psi structure, without resolving. - * Only annotations which have processors are supported - * - * @param psiAnnotation the PsiAnnotation object representing the Lombok annotation - * @return the fully qualified name of the Lombok annotation, or null if the annotation is unresolved or not a Lombok annotation - */ - public static @Nullable String findLombokAnnotationQualifiedNameInIncompleteMode(@NotNull PsiAnnotation psiAnnotation) { - String qualifiedName = psiAnnotation.getQualifiedName(); - if (StringUtil.isEmpty(qualifiedName)) return null; - if (qualifiedName.startsWith("lombok")) return qualifiedName; - LombokProcessorManager instance = LombokProcessorManager.getInstance(); - MultiMap names = instance.getOurSupportedShortNames(); - Collection fullQualifiedNames = names.get(qualifiedName); - for (String fullQualifiedName : fullQualifiedNames) { - if (hasQualifiedNameInDumbMode(psiAnnotation, fullQualifiedName)) { - return fullQualifiedName; - } - } - String proposedQualifiedName = "lombok." + qualifiedName; - if (hasQualifiedNameInDumbMode(psiAnnotation, proposedQualifiedName)) { - qualifiedName = proposedQualifiedName; - } - else { - proposedQualifiedName = "lombok.experimental." + qualifiedName; - if (hasQualifiedNameInDumbMode(psiAnnotation, proposedQualifiedName)) { - qualifiedName = proposedQualifiedName; - } - } - return qualifiedName; - } } diff --git a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/PsiAnnotationUtil.java b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/PsiAnnotationUtil.java index 7ede34b6b05b..b01b1de785ae 100644 --- a/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/PsiAnnotationUtil.java +++ b/plugins/lombok/src/main/java/de/plushnikov/intellij/plugin/util/PsiAnnotationUtil.java @@ -42,7 +42,7 @@ public final class PsiAnnotationUtil { @NotNull List defaultDumbValue) { Collection result = Collections.emptyList(); PsiAnnotationMemberValue attributeValue; - if (PsiAnnotationSearchUtil.isDumbOrIncompleteMode(psiAnnotation)) { + if (DumbIncompleteModeUtil.isDumbOrIncompleteMode(psiAnnotation)) { attributeValue = psiAnnotation.findDeclaredAttributeValue(parameter); if (attributeValue == null) return defaultDumbValue; } @@ -84,7 +84,7 @@ public final class PsiAnnotationUtil { public static String getEnumAnnotationValue(@NotNull PsiAnnotation psiAnnotation, @NotNull String attributeName, @NotNull String defaultValue) { PsiAnnotationMemberValue attrValue = psiAnnotation.findDeclaredAttributeValue(attributeName); - if (IncompleteModeUtil.isIncompleteMode(psiAnnotation) && attrValue instanceof PsiReferenceExpression referenceExpression) { + if (DumbIncompleteModeUtil.isIncompleteMode(psiAnnotation.getProject()) && attrValue instanceof PsiReferenceExpression referenceExpression) { //more or less good approximation if it is a complete mode return referenceExpression.getReferenceName(); } diff --git a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/AbstractLombokParsingTestCase.java b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/AbstractLombokParsingTestCase.java index dae1c54a95e2..fee306d1c39a 100644 --- a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/AbstractLombokParsingTestCase.java +++ b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/AbstractLombokParsingTestCase.java @@ -5,6 +5,7 @@ import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.IncompleteDependenciesService; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.PomNamedTarget; import com.intellij.psi.*; @@ -13,7 +14,7 @@ import com.intellij.testFramework.DumbModeTestUtils; import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.util.ThrowableRunnable; import com.intellij.util.containers.ContainerUtil; -import de.plushnikov.intellij.plugin.util.IncompleteModeUtil; +import de.plushnikov.intellij.plugin.util.DumbIncompleteModeUtil; import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil; import de.plushnikov.intellij.plugin.util.PsiElementUtil; import org.jetbrains.annotations.NotNull; @@ -39,7 +40,15 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC @NotNull protected List modes() { - return List.of(ModeRunnerType.INCOMPLETE, ModeRunnerType.DUMB, ModeRunnerType.NORMAL); + ArrayList types = new ArrayList<>(); + if (Registry.is("lombok.incomplete.mode.enabled", false)) { + types.add(ModeRunnerType.INCOMPLETE); + } + if (Registry.is("lombok.dumb.mode.enabled", false)) { + types.add(ModeRunnerType.DUMB); + } + types.add(ModeRunnerType.NORMAL); + return types; } /** @@ -267,10 +276,21 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC .filter(Predicate.not(annotationsToIgnoreList()::contains)) .toList(); - assertTrue("Annotations are different for " + afterModifierList.getParent() + ": " + beforeAnnotations + "/" + afterAnnotations, - beforeAnnotations.size() == afterAnnotations.size() - && beforeAnnotations.containsAll(afterAnnotations) - && afterAnnotations.containsAll(beforeAnnotations)); + if (DumbIncompleteModeUtil.isIncompleteMode(beforeModifierList.getProject())) { + //In this case, it is impossible to resolve, and even if there are annotations we can't guess their fqn correctly. + //Let's check one by one considering import statements + for (String annotation : afterAnnotations) { + assertTrue("For " + afterModifierList.getParent() + " " + beforeAnnotations + " doesn't contain the annotation: " + annotation, + ContainerUtil.or(beforeModifierList.getAnnotations(), + an -> PsiAnnotationSearchUtil.checkAnnotationHasOneOfFQNs(an, Set.of(annotation)))); + } + } + else { + assertTrue("Annotations are different for " + afterModifierList.getParent() + ": " + beforeAnnotations + "/" + afterAnnotations, + beforeAnnotations.size() == afterAnnotations.size() + && beforeAnnotations.containsAll(afterAnnotations) + && afterAnnotations.containsAll(beforeAnnotations)); + } // compare annotations parameter list for (PsiAnnotation beforeAnnotation : beforeModifierList.getAnnotations()) { @@ -293,16 +313,8 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC } private static @NotNull String getAnnotationQualifiedName(@NotNull PsiAnnotation annotation) { - String qualifiedName = DumbService.getInstance(annotation.getProject()) + return DumbService.getInstance(annotation.getProject()) .computeWithAlternativeResolveEnabled(() -> annotation.getQualifiedName()); - - if (!qualifiedName.contains(".") && IncompleteModeUtil.isIncompleteMode(annotation)) { - String lombokAnnotation = PsiAnnotationSearchUtil.findLombokAnnotationQualifiedNameInIncompleteMode(annotation); - if (lombokAnnotation.startsWith("lombok")) { - qualifiedName = lombokAnnotation; - } - } - return qualifiedName; } private void compareMethods(PsiClass beforeClass, PsiClass afterClass) { diff --git a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/IncompleteAndDumbModeRunner.java b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/IncompleteAndDumbModeRunner.java deleted file mode 100644 index 7e3c8ce9125c..000000000000 --- a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/IncompleteAndDumbModeRunner.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.plushnikov.intellij.plugin; - -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.InitializationError; - -public class IncompleteAndDumbModeRunner extends BlockJUnit4ClassRunner { - - public @interface InsertModeType { - } - - - - - public IncompleteAndDumbModeRunner(Class testClass) throws InitializationError { - super(testClass); - } - - -} diff --git a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/configsystem/FieldDefaultsWithoutLombokImportTest.java b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/configsystem/FieldDefaultsWithoutLombokImportTest.java index 8be43300abd5..736231fa570c 100644 --- a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/configsystem/FieldDefaultsWithoutLombokImportTest.java +++ b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/configsystem/FieldDefaultsWithoutLombokImportTest.java @@ -28,6 +28,8 @@ public class FieldDefaultsWithoutLombokImportTest extends AbstractLombokConfigSy //incomplete mode is not supported, because files don't contain any lombok annotations, //checking lombok.config is expensive, so skip such cases //after returning to normal mode, caches will be dropped - return List.of(ModeRunnerType.NORMAL, ModeRunnerType.DUMB); + return super.modes() + .stream().filter(t -> t != ModeRunnerType.INCOMPLETE) + .toList(); } } diff --git a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/configsystem/FieldNameConstantsTest.java b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/configsystem/FieldNameConstantsTest.java index f3617662b324..a70ac19a6089 100644 --- a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/configsystem/FieldNameConstantsTest.java +++ b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/configsystem/FieldNameConstantsTest.java @@ -23,6 +23,8 @@ public class FieldNameConstantsTest extends AbstractLombokConfigSystemTestCase { protected @NotNull List modes() { //now incomplete mode is not supported for this processor, because it depends on the lombok version //after returning to normal mode, caches will be dropped - return List.of(ModeRunnerType.NORMAL, ModeRunnerType.DUMB); + return super.modes() + .stream().filter(t -> t != ModeRunnerType.INCOMPLETE) + .toList(); } } diff --git a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/processor/FieldNameConstantsOldTest.java b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/processor/FieldNameConstantsOldTest.java index dc079766f177..f7ec050ab601 100644 --- a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/processor/FieldNameConstantsOldTest.java +++ b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/processor/FieldNameConstantsOldTest.java @@ -26,6 +26,8 @@ public class FieldNameConstantsOldTest extends AbstractLombokParsingTestCase { protected @NotNull List modes() { //now incomplete mode is not supported for this processor, because it depends on the lombok version //after returning to normal mode, caches will be dropped - return List.of(ModeRunnerType.NORMAL, ModeRunnerType.DUMB); + return super.modes() + .stream().filter(t -> t != ModeRunnerType.INCOMPLETE) + .toList(); } } diff --git a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/processor/FieldNameConstantsTest.java b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/processor/FieldNameConstantsTest.java index 791e080d2aa7..84efbcb6c830 100644 --- a/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/processor/FieldNameConstantsTest.java +++ b/plugins/lombok/src/test/java/de/plushnikov/intellij/plugin/processor/FieldNameConstantsTest.java @@ -38,6 +38,8 @@ public class FieldNameConstantsTest extends AbstractLombokParsingTestCase { protected @NotNull List modes() { //now incomplete mode is not supported for this processor, because it depends on the lombok version //after returning to normal mode, caches will be dropped - return List.of(ModeRunnerType.NORMAL, ModeRunnerType.DUMB); + return super.modes() + .stream().filter(t -> t != ModeRunnerType.INCOMPLETE) + .toList(); } } diff --git a/plugins/lombok/testData/after/TestOnX.java b/plugins/lombok/testData/after/TestOnX.java index 5fbbe5d2e753..ec2bfde300b7 100644 --- a/plugins/lombok/testData/after/TestOnX.java +++ b/plugins/lombok/testData/after/TestOnX.java @@ -6,14 +6,14 @@ import javax.validation.Valid; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.Size; -import org.jetbrains.annotations.NotNull; +import lombok.NonNull; public class TestOnX { - @NotNull + @NonNull private final Integer someIntField; /** @deprecated */ @Deprecated - @NotNull + @NonNull private String someStringField; private float someFloatField; @@ -30,7 +30,7 @@ public class TestOnX { @Inject @Named("myName1") - public TestOnX(@NotNull Integer someIntField, @NotNull String someStringField) { + public TestOnX(@NonNull Integer someIntField, @NonNull String someStringField) { if (someIntField == null) { throw new NullPointerException("someIntField is marked non-null but is null"); } else if (someStringField == null) { @@ -43,7 +43,7 @@ public class TestOnX { @Inject @Named("myName2") - public TestOnX(@NotNull Integer someIntField, @NotNull String someStringField, float someFloatField) { + public TestOnX(@NonNull Integer someIntField, @NonNull String someStringField, float someFloatField) { if (someIntField == null) { throw new NullPointerException("someIntField is marked non-null but is null"); } else if (someStringField == null) { @@ -108,7 +108,7 @@ public class TestOnX { } @Max(100) - @NotNull + @NonNull public Integer getSomeIntField() { return this.someIntField; } @@ -118,7 +118,7 @@ public class TestOnX { @Size( max = 20 ) - @NotNull + @NonNull public String getSomeStringField() { return this.someStringField; } @@ -128,7 +128,7 @@ public class TestOnX { @Size( min = 10 ) - public void setSomeStringField(@Size(min = 15) @NotNull String someStringField) { + public void setSomeStringField(@Size(min = 15) @NonNull String someStringField) { if (someStringField == null) { throw new NullPointerException("someStringField is marked non-null but is null"); } else { @@ -136,7 +136,7 @@ public class TestOnX { } } - @NotNull + @NonNull public TestOnX withSomeFloatField(@Min(1) float someFloatField) { return this.someFloatField == someFloatField ? this : new TestOnX(this.someIntField, this.someStringField, someFloatField); } diff --git a/plugins/lombok/testData/before/TestOnX.java b/plugins/lombok/testData/before/TestOnX.java index a14e70970503..1d7f50d87fc3 100644 --- a/plugins/lombok/testData/before/TestOnX.java +++ b/plugins/lombok/testData/before/TestOnX.java @@ -8,7 +8,6 @@ import javax.validation.Valid; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.Size; -import org.jetbrains.annotations.NotNull; @ToString @RequiredArgsConstructor(onConstructor_ = {@Inject, @Named("myName1")}) @@ -16,16 +15,16 @@ import org.jetbrains.annotations.NotNull; @EqualsAndHashCode(onParam_ = @Valid) public class TestOnX { @Getter(onMethod_ = @Max(100)) - @NotNull + @NonNull private final Integer someIntField; - @NotNull + @NonNull @Deprecated @Getter(onMethod_ = @Size(max = 20)) @Setter(onMethod_ = @Size(min = 10), onParam_ = @Size(min = 15)) private String someStringField; - @With(onMethod_ = @NotNull, onParam_ = @Min(1)) + @With(onMethod_ = @NonNull, onParam_ = @Min(1)) private float someFloatField; public static void main(String[] args) {