diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java index 715040ac8048..747d6a63c26f 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java @@ -286,6 +286,9 @@ public final class HighlightControlFlowUtil { if (!field.hasModifierProperty(PsiModifier.FINAL)) return null; if (isFieldInitializedAfterObjectConstruction(field)) return null; if (PsiUtilCore.hasErrorElementChild(field)) return null; + if (IncompleteModelUtil.isIncompleteModel(field) && IncompleteModelUtil.canBeAugmented(field.getContainingClass())) { + return IncompleteModelUtil.getPendingReferenceHighlightInfo(field); + } String description = JavaErrorBundle.message("variable.not.initialized", field.getName()); TextRange range = HighlightNamesUtil.getFieldDeclarationTextRange(field); HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description); diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java index 185161f58df7..35932557339c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightMethodUtil.java @@ -876,6 +876,12 @@ public final class HighlightMethodUtil { String description; PsiElement elementToHighlight = ObjectUtils.notNull(referenceToMethod.getReferenceNameElement(), referenceToMethod); if (element != null && !resolveResult.isAccessible()) { + PsiClass qualifierClass = RefactoringChangeUtil.getQualifierClass(referenceToMethod); + if (qualifierClass!=null && + IncompleteModelUtil.isIncompleteModel(file) && + IncompleteModelUtil.canBeAugmented(qualifierClass)) { + return IncompleteModelUtil.getPendingReferenceHighlightInfo(elementToHighlight); + } description = HighlightUtil.accessProblemDescription(referenceToMethod, element, resolveResult); } else if (element != null && !resolveResult.isStaticsScopeCorrect()) { @@ -902,6 +908,9 @@ public final class HighlightMethodUtil { IncompleteModelUtil.canBePendingReference(referenceToMethod)) { return IncompleteModelUtil.getPendingReferenceHighlightInfo(elementToHighlight); } + if (IncompleteModelUtil.isIncompleteModel(file) && IncompleteModelUtil.canBeAugmented(qualifierClass)) { + return IncompleteModelUtil.getPendingReferenceHighlightInfo(elementToHighlight); + } description = JavaErrorBundle.message("ambiguous.method.call.no.match", referenceToMethod.getReferenceName(), className); } else if (qualifierExpression != null && diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncompleteModelUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncompleteModelUtil.java index 07d9e085f512..b45b27d5a80b 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncompleteModelUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/IncompleteModelUtil.java @@ -6,6 +6,8 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.openapi.project.IncompleteDependenciesService; import com.intellij.psi.*; +import com.intellij.psi.augment.PsiAugmentProvider; +import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiUtil; import com.siyeh.ig.psiutils.ClassUtils; import org.jetbrains.annotations.Contract; @@ -224,6 +226,9 @@ final class IncompleteModelUtil { if (qualifierTarget == null && canBePendingReference(qualifierRef)) { return true; } + if (qualifierTarget instanceof PsiClass psiClass && canBeAugmented(psiClass)) { + return true; + } if (qualifierTarget instanceof PsiClass cls && isHierarchyResolved(cls)) { return false; } @@ -247,4 +252,10 @@ final class IncompleteModelUtil { return HighlightInfo.newHighlightInfo(HighlightInfoType.PENDING_REFERENCE).range(elementToHighlight) .descriptionAndTooltip(JavaErrorBundle.message("incomplete.project.state.pending.reference")); } + + static boolean canBeAugmented(@Nullable PsiClass targetClass) { + if (targetClass == null) return false; + return CachedValuesManager.getProjectPsiDependentCache(targetClass, + psiClass -> PsiAugmentProvider.canBeAugmentedForIncompleteMode(psiClass)); + } } diff --git a/java/java-psi-api/src/com/intellij/psi/augment/PsiAugmentProvider.java b/java/java-psi-api/src/com/intellij/psi/augment/PsiAugmentProvider.java index 1f2612deb8a9..564d423efaa0 100644 --- a/java/java-psi-api/src/com/intellij/psi/augment/PsiAugmentProvider.java +++ b/java/java-psi-api/src/com/intellij/psi/augment/PsiAugmentProvider.java @@ -267,5 +267,33 @@ public abstract class PsiAugmentProvider implements PossiblyDumbAware { } } - // + + /** + * Checks if the given PsiClass might be augmented. + * This method is usually called in incomplete mode. + * + * @param psiClass the PsiClass to be checked + * @return true if the PsiClass might be augmented for incomplete mode, false otherwise + */ + protected boolean mightBeAugmentedForIncompleteMode(@NotNull PsiClass psiClass) { + return false; + } + + /** + * Checks if the given PsiClass might be augmented. + * This method is usually called in incomplete mode. + * + * @param targetClass the PsiClass to be checked + * @return true if the PsiClass might be augmented for incomplete mode, false otherwise + */ + public static boolean canBeAugmentedForIncompleteMode(@NotNull PsiClass targetClass) { + Ref result = Ref.create(); + + forEach(targetClass.getProject(), provider -> { + boolean augmentedForIncompleteMode = provider.mightBeAugmentedForIncompleteMode(targetClass); + result.set(augmentedForIncompleteMode); + return !augmentedForIncompleteMode; + }); + return result.get(); + } } \ No newline at end of file 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 efcc5493a777..283d2d31848e 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 @@ -5,7 +5,7 @@ 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.search.GlobalSearchScope; +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; @@ -14,9 +14,6 @@ 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.psi.LombokLightAnnotationMethodBuilder; -import de.plushnikov.intellij.plugin.psi.LombokLightClassBuilder; -import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder; import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil; import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil; import org.jetbrains.annotations.NotNull; @@ -173,4 +170,43 @@ public final class LombokAugmentProvider extends PsiAugmentProvider { } return ExtensionMethodsHelper.getExtensionMethods(aClass, nameHint, context); } + + @Override + protected boolean mightBeAugmentedForIncompleteMode(@NotNull PsiClass psiClass) { + if(!(psiClass instanceof PsiExtensibleClass extensibleClass)) { + return false; + } + if(!(extensibleClass.getContainingFile() instanceof PsiJavaFile file)) { + return false; + } + return hasAnyLombokAnnotation(extensibleClass.getAnnotations()) || + ContainerUtil.exists(extensibleClass.getOwnFields(), field -> hasAnyLombokAnnotation(field.getAnnotations())) || + (file.getImportList() != null && + ContainerUtil.exists(file.getImportList().getAllImportStatements(), 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.getQualifiedName(); + if (qualifiedName == null) { + return false; + } + return qualifiedName.startsWith("lombok."); + }); + } } diff --git a/plugins/lombok/src/test/java/com/intellij/java/lomboktest/LombokIncompleteModeHighlightingTest.java b/plugins/lombok/src/test/java/com/intellij/java/lomboktest/LombokIncompleteModeHighlightingTest.java new file mode 100644 index 000000000000..a94b50a36dc2 --- /dev/null +++ b/plugins/lombok/src/test/java/com/intellij/java/lomboktest/LombokIncompleteModeHighlightingTest.java @@ -0,0 +1,34 @@ +package com.intellij.java.lomboktest; + +import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.project.IncompleteDependenciesService; +import com.intellij.testFramework.PlatformTestUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public class LombokIncompleteModeHighlightingTest extends LightDaemonAnalyzerTestCase { + + public void testLombokBasics() { doTest(); } + + public void testLombokBasicsWithExplicitImport() { doTest(); } + + private void doTest() { + doTest(getTestName(false) + ".java"); + } + + private void doTest(String fileName) { + var ignored = WriteAction.compute(() -> getProject().getService(IncompleteDependenciesService.class).enterIncompleteState()); + try { + doTest("/plugins/lombok/testData/highlightingIncompleteMode/" + fileName, true, true); + } + finally { + WriteAction.run(ignored::close); + } + } + + @Override + protected @NonNls @NotNull String getTestDataPath() { + return PlatformTestUtil.getCommunityPath(); + } +} diff --git a/plugins/lombok/testData/highlightingIncompleteMode/LombokBasics.java b/plugins/lombok/testData/highlightingIncompleteMode/LombokBasics.java new file mode 100644 index 000000000000..873d10bf0255 --- /dev/null +++ b/plugins/lombok/testData/highlightingIncompleteMode/LombokBasics.java @@ -0,0 +1,32 @@ +import lombok.* + +public final class LombokBasics { + public static void main(String[] args) { + UserDao userDao = UserDao.builder() + .info("1") + .build(); + String name = userDao.name(); + UserChain userChain = new UserChain(); + String name1 = userChain.getName(); + } +} + +@Getter +@SuperBuilder +class UserDao extends UserId { + private final String name; + private final String surname; + private final String email; +} + +@SuperBuilder +abstract class UserId { + private final long id; + private final String info; +} + +class UserChain { + @Getter + @Setter + private String name; +} \ No newline at end of file diff --git a/plugins/lombok/testData/highlightingIncompleteMode/LombokBasicsWithExplicitImport.java b/plugins/lombok/testData/highlightingIncompleteMode/LombokBasicsWithExplicitImport.java new file mode 100644 index 000000000000..051918dd70ac --- /dev/null +++ b/plugins/lombok/testData/highlightingIncompleteMode/LombokBasicsWithExplicitImport.java @@ -0,0 +1,34 @@ +import lombok.Getter; +import lombok.Setter; +import lombok.SuperBuilder; + +public final class LombokBasicsWithExplicitImport { + public static void main(String[] args) { + UserDao userDao = UserDao.builder() + .info("1") + .build(); + String name = userDao.name(); + UserChain userChain = new UserChain(); + String name1 = userChain.getName(); + } +} + +@Getter +@SuperBuilder +class UserDao extends UserId { + private final String name; + private final String surname; + private final String email; +} + +@SuperBuilder +abstract class UserId { + private final long id; + private final String info; +} + +class UserChain { + @Getter + @Setter + private String name; +} \ No newline at end of file