[lombok] IDEA-352727 Support incomplete mode

- support main processors (except ConstantField, because it depends on a lombok version)
- parser tests are executed in three modes (normal, incomplete, dumb) by default

The main idea is to provide users some functionality in incomplete mode (when libraries are not downloaded yet), but users can already do something.
After downloading psi caches will be dropped and everything will be recomputed in an accurate way.

GitOrigin-RevId: a7b2774a01be86a60aa5bdcd69a3826682b4135e
This commit is contained in:
Mikhail Pyltsin
2024-05-24 20:46:40 +02:00
committed by intellij-monorepo-bot
parent 56117ec688
commit 8a33c22212
29 changed files with 456 additions and 64 deletions

View File

@@ -5,6 +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.LombokLibraryUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -22,7 +23,8 @@ public final class LombokElementFinder extends PsiElementFinder {
@Nullable
@Override
public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
if (!LombokLibraryUtil.hasLombokLibrary(myProject)) {
if (!LombokLibraryUtil.hasLombokLibrary(myProject) &&
!IncompleteModeUtil.isIncompleteMode(myProject)) {
return null;
}

View File

@@ -7,6 +7,7 @@ import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import de.plushnikov.intellij.plugin.processor.clazz.*;
import de.plushnikov.intellij.plugin.processor.clazz.builder.*;
import de.plushnikov.intellij.plugin.processor.clazz.constructor.AllArgsConstructorProcessor;
@@ -21,12 +22,15 @@ 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 org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@Service
public final class LombokProcessorManager {
@@ -85,10 +89,18 @@ public final class LombokProcessorManager {
private final SynchronizedProcessor mySynchronizedProcessor = new SynchronizedProcessor();
private final JacksonizedProcessor myJacksonizedProcessor = new JacksonizedProcessor();
private final Set<String> ourSupportedShortNames = getAllProcessors()
.stream().flatMap(p -> Arrays.stream(p.getSupportedAnnotationClasses()))
.map(StringUtil::getShortName)
.collect(Collectors.toSet());
private final MultiMap<String, String> ourSupportedShortNames = createSupportedShortNames();
@NotNull
private MultiMap<String, String> createSupportedShortNames() {
MultiMap<String, String> map = new MultiMap<>();
for (Processor processor : getAllProcessors()) {
for (String annotationClass : processor.getSupportedAnnotationClasses()) {
map.putValue(StringUtil.getShortName(annotationClass), annotationClass);
}
}
return map;
}
public static LombokProcessorManager getInstance() {
return ApplicationManager.getApplication().getService(LombokProcessorManager.class);
@@ -266,6 +278,10 @@ public final class LombokProcessorManager {
return myJacksonizedProcessor;
}
public MultiMap<String, String> getOurSupportedShortNames() {
return ourSupportedShortNames;
}
@NotNull
private Collection<Processor> getAllProcessors() {
return Arrays.asList(
@@ -348,15 +364,19 @@ public final class LombokProcessorManager {
return Collections.emptyList();
}
String referenceName = nameReferenceElement.getReferenceName();
if (referenceName == null || !manager.ourSupportedShortNames.contains(referenceName)) {
if (referenceName == null || !manager.ourSupportedShortNames.containsKey(referenceName)) {
return Collections.emptyList();
}
final String qualifiedName = psiAnnotation.getQualifiedName();
String qualifiedName = psiAnnotation.getQualifiedName();
if (PsiAnnotationSearchUtil.isDumbOrIncompleteMode(psiAnnotation)) {
qualifiedName = PsiAnnotationSearchUtil.findLombokAnnotationQualifiedNameInIncompleteMode(psiAnnotation);
}
if (StringUtil.isEmpty(qualifiedName) || !qualifiedName.contains("lombok")) {
return Collections.emptyList();
}
String finalQualifiedName = qualifiedName;
return manager.getWithCache("byAnnotationFQN_" + qualifiedName,
() -> ContainerUtil.filter(manager.getAllProcessors(), p -> p.isSupportedAnnotationFQN(qualifiedName))
() -> ContainerUtil.filter(manager.getAllProcessors(), p -> p.isSupportedAnnotationFQN(finalQualifiedName))
);
}
}

View File

@@ -28,6 +28,8 @@ public abstract class AbstractFieldNameConstantsProcessor extends AbstractClassP
@Override
protected boolean supportAnnotationVariant(@NotNull PsiAnnotation psiAnnotation) {
//it can help for dumb mode or incomplete mode
if (null != psiAnnotation.findDeclaredAttributeValue("asEnum")) return true;
// new version of @FieldNameConstants has an attribute "asEnum", the old one not
return null != psiAnnotation.findAttributeValue("asEnum");
}

View File

@@ -39,8 +39,11 @@ public final class FieldNameConstantsOldProcessor extends AbstractClassProcessor
@Override
protected boolean supportAnnotationVariant(@NotNull PsiAnnotation psiAnnotation) {
String prefix = "prefix";
//it can help for dumb mode or incomplete mode
if (null != psiAnnotation.findDeclaredAttributeValue(prefix)) return true;
// old version of @FieldNameConstants has attributes "prefix" and "suffix", the new one not
return null != psiAnnotation.findAttributeValue("prefix");
return null != psiAnnotation.findAttributeValue(prefix);
}
@Override

View File

@@ -33,8 +33,11 @@ public final class FieldNameConstantsFieldProcessor extends AbstractFieldProcess
@Override
protected boolean supportAnnotationVariant(@NotNull PsiAnnotation psiAnnotation) {
String prefix = "prefix";
//it can help for dumb mode or incomplete mode
if (null != psiAnnotation.findDeclaredAttributeValue(prefix)) return true;
// old version of @FieldNameConstants has attributes "prefix" and "suffix", the new one not
return null != psiAnnotation.findAttributeValue("prefix");
return null != psiAnnotation.findAttributeValue(prefix);
}
@Override

View File

@@ -8,6 +8,9 @@ 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;
@@ -16,6 +19,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.PsiAnnotationSearchUtil;
import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil;
import org.jetbrains.annotations.NotNull;
@@ -47,7 +51,7 @@ public final class LombokAugmentProvider extends PsiAugmentProvider implements P
@Override
protected Set<String> transformModifiers(@NotNull PsiModifierList modifierList, @NotNull final Set<String> modifiers) {
// skip if no lombok library is present
if (!hasLombokLibrary(modifierList.getProject())) {
if (!hasLombokLibrary(modifierList.getProject()) && !isIncompleteModeWithLombokAnnotation(modifierList)) {
return modifiers;
}
@@ -68,7 +72,6 @@ public final class LombokAugmentProvider extends PsiAugmentProvider implements P
}
else {
runnable.run();
}
return result;
}
@@ -154,7 +157,7 @@ public final class LombokAugmentProvider extends PsiAugmentProvider implements P
}
// skip processing if disabled, or no lombok library is present
if (!hasLombokLibrary(element.getProject())) {
if (!hasLombokLibrary(element.getProject()) && !isIncompleteModeWithLombokAnnotation(psiClass)) {
return emptyResult;
}
if (psiClass.isAnnotationType() && type == PsiMethod.class) {
@@ -201,4 +204,79 @@ 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.");
});
}
}

View File

@@ -76,7 +76,7 @@ public enum LombokCopyableAnnotations {
List<PsiAnnotation> result = new ArrayList<>();
for (PsiAnnotation annotation : fieldAnnotations) {
if (ContainerUtil.exists(annotationNames, annotation::hasQualifiedName)) {
if (PsiAnnotationSearchUtil.checkAnnotationHasOneOfFQNs(annotation, annotationNames)) {
result.add(annotation);
}
}

View File

@@ -0,0 +1,23 @@
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();
}
}

View File

@@ -1,6 +1,5 @@
package de.plushnikov.intellij.plugin.util;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
@@ -86,7 +85,7 @@ public final class LombokProcessorUtil {
public static Collection<String> getOldOnX(@NotNull PsiAnnotation psiAnnotation, @NotNull String parameterName) {
PsiAnnotationMemberValue onXValue;
if (DumbService.isDumb(psiAnnotation.getProject())) {
if (PsiAnnotationSearchUtil.isDumbOrIncompleteMode(psiAnnotation)) {
onXValue = psiAnnotation.findDeclaredAttributeValue(parameterName);
}
else {
@@ -100,7 +99,7 @@ public final class LombokProcessorUtil {
}
public static Collection<String> getNewOnX(@NotNull PsiAnnotation psiAnnotation, @NotNull String parameterName) {
if (DumbService.isDumb(psiAnnotation.getProject()) || psiAnnotation.hasAttribute(parameterName)) {
if (PsiAnnotationSearchUtil.isDumbOrIncompleteMode(psiAnnotation) || psiAnnotation.hasAttribute(parameterName)) {
final Collection<PsiAnnotation> annotations =
PsiAnnotationUtil.getAnnotationValues(psiAnnotation, parameterName, PsiAnnotation.class, List.of());
return collectAnnotationStrings(annotations);

View File

@@ -5,16 +5,19 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
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;
import java.util.Set;
public final class PsiAnnotationSearchUtil {
@Nullable
public static PsiAnnotation findAnnotation(@NotNull PsiModifierListOwner psiModifierListOwner, @NotNull String annotationFQN) {
if (isDumbMode(psiModifierListOwner)) {
if (isDumbOrIncompleteMode(psiModifierListOwner)) {
return findAnnotationInDumbMode(psiModifierListOwner, annotationFQN);
}
return psiModifierListOwner.getAnnotation(annotationFQN);
@@ -52,14 +55,15 @@ public final class PsiAnnotationSearchUtil {
(mayBeOuterClass!=null && importList.findSingleClassImportStatement(mayBeOuterClass) != null);
}
private static boolean isDumbMode(@NotNull PsiElement context) {
public static boolean isDumbOrIncompleteMode(@NotNull PsiElement context) {
Project project = context.getProject();
return DumbService.isDumb(project);
return DumbService.isDumb(project) ||
IncompleteModeUtil.isIncompleteMode(context);
}
@Nullable
public static PsiAnnotation findAnnotation(@NotNull PsiModifierListOwner psiModifierListOwner, String @NotNull ... annotationFQNs) {
boolean isDumbMode = isDumbMode(psiModifierListOwner);
boolean isDumbMode = isDumbOrIncompleteMode(psiModifierListOwner);
for (String annotationFQN : annotationFQNs) {
PsiAnnotation annotation;
if (isDumbMode) {
@@ -76,7 +80,7 @@ public final class PsiAnnotationSearchUtil {
}
public static boolean isAnnotatedWith(@NotNull PsiModifierListOwner psiModifierListOwner, @NotNull String annotationFQN) {
if (isDumbMode(psiModifierListOwner)) {
if (isDumbOrIncompleteMode(psiModifierListOwner)) {
return findAnnotationInDumbMode(psiModifierListOwner, annotationFQN) != null;
}
return psiModifierListOwner.hasAnnotation(annotationFQN);
@@ -129,9 +133,49 @@ public final class PsiAnnotationSearchUtil {
public static boolean checkAnnotationHasOneOfFQNs(@NotNull PsiAnnotation psiAnnotation,
String @NotNull ... annotationFQNs) {
if (isDumbMode(psiAnnotation)) {
if (isDumbOrIncompleteMode(psiAnnotation)) {
return ContainerUtil.or(annotationFQNs, fqn-> hasQualifiedNameInDumbMode(psiAnnotation, fqn));
}
return ContainerUtil.or(annotationFQNs, psiAnnotation::hasQualifiedName);
}
public static boolean checkAnnotationHasOneOfFQNs(@NotNull PsiAnnotation psiAnnotation,
@NotNull Set<String> annotationFQNs) {
if (isDumbOrIncompleteMode(psiAnnotation)) {
return ContainerUtil.or(annotationFQNs, fqn -> hasQualifiedNameInDumbMode(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<String, String> names = instance.getOurSupportedShortNames();
Collection<String> 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;
}
}

View File

@@ -1,7 +1,6 @@
package de.plushnikov.intellij.plugin.util;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
@@ -43,7 +42,7 @@ public final class PsiAnnotationUtil {
@NotNull List<T> defaultDumbValue) {
Collection<T> result = Collections.emptyList();
PsiAnnotationMemberValue attributeValue;
if (DumbService.isDumb(psiAnnotation.getProject())) {
if (PsiAnnotationSearchUtil.isDumbOrIncompleteMode(psiAnnotation)) {
attributeValue = psiAnnotation.findDeclaredAttributeValue(parameter);
if (attributeValue == null) return defaultDumbValue;
}
@@ -85,6 +84,10 @@ 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) {
//more or less good approximation if it is a complete mode
return referenceExpression.getReferenceName();
}
String result = attrValue != null ? resolveElementValue(attrValue, String.class) : null;
return result != null ? result : defaultValue;
}

View File

@@ -253,6 +253,8 @@
<registryKey key="lombok.dumb.mode.enabled" defaultValue="true" restartRequired="true"
description="Lombok augment works in dumb mode"/>
<registryKey key="lombok.incomplete.mode.enabled" defaultValue="true" restartRequired="true"
description="Lombok supports incomplete mode"/>
</extensions>
<projectListeners>

View File

@@ -1,14 +1,20 @@
package de.plushnikov.intellij.plugin;
import com.google.common.base.Objects;
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.text.StringUtil;
import com.intellij.pom.PomNamedTarget;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
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.PsiAnnotationSearchUtil;
import de.plushnikov.intellij.plugin.util.PsiElementUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -19,6 +25,8 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.intellij.openapi.project.IncompleteDependenciesServiceKt.asAutoCloseable;
/**
* Base test case for testing that the Lombok plugin parses the Lombok annotations correctly.
*/
@@ -26,10 +34,53 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC
private static final Logger LOG = Logger.getInstance(AbstractLombokParsingTestCase.class);
@Nullable
protected ModeRunnerType myRunnerType;
@NotNull
protected List<ModeRunnerType> modes() {
return List.of(ModeRunnerType.INCOMPLETE, ModeRunnerType.DUMB, ModeRunnerType.NORMAL);
}
/**
* All tests are run in three modes: normal, incomplete and dumb mode.
* If it is necessary to skip some of them, use @SkipMode
*/
@Override
protected void runBare(@NotNull ThrowableRunnable<Throwable> testRunnable) throws Throwable {
String testName = getName();
for (ModeRunnerType value : modes()) {
myRunnerType = value;
try {
LOG.info("Method: " + testName + " starts with " + value);
super.runBare(testRunnable);
LOG.info("Method: " + testName + " finish with " + value);
}
catch (Throwable e) {
LOG.warn("Method: " + testName + "failed for " + value);
throw e;
}
}
}
public enum ModeRunnerType {
INCOMPLETE, DUMB, NORMAL
}
protected boolean shouldCompareAnnotations() {
return !".*".equals(annotationToComparePattern());
}
@Override
protected final @NotNull LightProjectDescriptor getProjectDescriptor() {
return (myRunnerType == ModeRunnerType.INCOMPLETE) ? LombokTestUtil.WITHOUT_LOMBOK_DESCRIPTOR : getParsingDescriptor();
}
protected @NotNull LightProjectDescriptor getParsingDescriptor() {
return super.getProjectDescriptor();
}
protected String annotationToComparePattern() {
return ".*";
}
@@ -46,13 +97,25 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC
doTest(false);
}
public void doTest(final boolean lowercaseFirstLetter) {
doTest(getTestName(lowercaseFirstLetter));
public final void doTest(final boolean lowercaseFirstLetter) {
PsiManager.getInstance(getProject()).dropPsiCaches();
if (myRunnerType == ModeRunnerType.DUMB) {
DumbModeTestUtils.runInDumbModeSynchronously(getProject(),
() -> compareFiles(lowercaseFirstLetter));
}
else if (myRunnerType == ModeRunnerType.INCOMPLETE) {
IncompleteDependenciesService service = getProject().getService(IncompleteDependenciesService.class);
try (var ignored = asAutoCloseable(WriteAction.compute(() -> service.enterIncompleteState()))) {
compareFiles(lowercaseFirstLetter);
}
}
else {
compareFiles(lowercaseFirstLetter);
}
}
public void doTest(String testName) {
DumbModeTestUtils.runInDumbModeSynchronously(getProject(),
() -> compareFiles(loadBeforeLombokFile(testName), loadAfterDeLombokFile(testName)));
protected void compareFiles(boolean lowercaseFirstLetter) {
String testName = getTestName(lowercaseFirstLetter);
compareFiles(loadBeforeLombokFile(testName), loadAfterDeLombokFile(testName));
}
@@ -193,13 +256,13 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC
private void compareAnnotations(PsiModifierList beforeModifierList, PsiModifierList afterModifierList) {
if (shouldCompareAnnotations()) {
Collection<String> beforeAnnotations = Arrays.stream(beforeModifierList.getAnnotations())
.map(an-> getAnnotationQualifiedName(an))
.map(an -> getAnnotationQualifiedName(an))
.filter(Pattern.compile("lombok.*").asPredicate().negate().or(LombokClassNames.NON_NULL::equals))
.filter(Pattern.compile(annotationToComparePattern()).asPredicate())
.filter(Predicate.not(annotationsToIgnoreList()::contains))
.toList();
Collection<String> afterAnnotations = Arrays.stream(afterModifierList.getAnnotations())
.map(an-> getAnnotationQualifiedName(an))
.map(an -> getAnnotationQualifiedName(an))
.filter(Pattern.compile(annotationToComparePattern()).asPredicate())
.filter(Predicate.not(annotationsToIgnoreList()::contains))
.toList();
@@ -229,9 +292,17 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC
.computeWithAlternativeResolveEnabled(() -> modifierList.findAnnotation(qualifiedName));
}
private static @Nullable String getAnnotationQualifiedName(PsiAnnotation annotation) {
return DumbService.getInstance(annotation.getProject())
private static @NotNull String getAnnotationQualifiedName(@NotNull PsiAnnotation annotation) {
String qualifiedName = 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) {
@@ -245,7 +316,10 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC
final Collection<PsiMethod> matchedMethods = filterMethods(beforeMethods, afterMethod);
if (matchedMethods.isEmpty()) {
fail("Method names are not equal, Method: " + afterMethod.getPresentation().getPresentableText() + " not found in class : " + beforeClass.getName());
fail("Method names are not equal, Method: " +
afterMethod.getPresentation().getPresentableText() +
" not found in class : " +
beforeClass.getName());
}
for (PsiMethod beforeMethod : matchedMethods) {
@@ -326,7 +400,7 @@ public abstract class AbstractLombokParsingTestCase extends AbstractLombokLightC
for (PsiClassType beforeType : beforeTypes) {
boolean found = false;
for (PsiClassType afterType : afterTypes) {
boolean equals = dumbService.computeWithAlternativeResolveEnabled(()->beforeType.equals(afterType));
boolean equals = dumbService.computeWithAlternativeResolveEnabled(() -> beforeType.equals(afterType));
if (equals) {
found = true;
break;

View File

@@ -0,0 +1,19 @@
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);
}
}

View File

@@ -34,6 +34,22 @@ public final class LombokTestUtil {
}
};
public static final DefaultLightProjectDescriptor WITHOUT_LOMBOK_DESCRIPTOR = new DefaultLightProjectDescriptor() {
@Override
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {
DefaultLightProjectDescriptor.addJetBrainsAnnotations(model);
MavenDependencyUtil.addFromMaven(model, JACKSON_MAVEN_COORDINATES);
MavenDependencyUtil.addFromMaven(model, "com.google.guava:guava:27.0.1-jre");
MavenDependencyUtil.addFromMaven(model, "org.slf4j:slf4j-api:1.7.30");
model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(LanguageLevel.HIGHEST);
}
@Override
public Sdk getSdk() {
return IdeaTestUtil.getMockJdk18();
}
};
public static final DefaultLightProjectDescriptor LOMBOK_NEW_DESCRIPTOR = new DefaultLightProjectDescriptor() {
@Override
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) {

View File

@@ -5,9 +5,15 @@ import com.intellij.psi.PsiJavaFile;
import de.plushnikov.intellij.plugin.AbstractLombokParsingTestCase;
public abstract class AbstractLombokConfigSystemTestCase extends AbstractLombokParsingTestCase {
@Override
public void doTest() {
final String fullFileName = getTestName(true).replace('$', '/') + ".java";
doTest(true);
}
@Override
protected void compareFiles(boolean lowercaseFirstLetter) {
final String fullFileName = getTestName(lowercaseFirstLetter).replace('$', '/') + ".java";
final int lastIndexOf = fullFileName.lastIndexOf('/');
final String subPath = fullFileName.substring(0, lastIndexOf);
final String fileName = fullFileName.substring(lastIndexOf + 1);
@@ -25,6 +31,6 @@ public abstract class AbstractLombokConfigSystemTestCase extends AbstractLombokP
fail("The test file type is not supported");
}
compareFiles((PsiJavaFile) psiLombokFile, (PsiJavaFile) psiDelombokFile);
compareFiles((PsiJavaFile)psiLombokFile, (PsiJavaFile)psiDelombokFile);
}
}

View File

@@ -12,10 +12,6 @@ public class FieldDefaultsTest extends AbstractLombokConfigSystemTestCase {
return super.getBasePath() + "/configsystem/fieldDefaults";
}
//region DefaultFinal
public void testDefaultFinal$DefaultFinalFieldTest() throws IOException {
doTest();
}
public void testDefaultFinal$DefaultFinalFieldWithFieldDefaultsTest() throws IOException {
doTest();
@@ -31,10 +27,6 @@ public class FieldDefaultsTest extends AbstractLombokConfigSystemTestCase {
//endregion
//region DefaultPrivate
public void testDefaultPrivate$DefaultPrivateFieldTest() throws IOException {
doTest();
}
public void testDefaultPrivate$DefaultPrivateFieldWithFieldDefaultsTest() throws IOException {
doTest();
}

View File

@@ -0,0 +1,33 @@
package de.plushnikov.intellij.plugin.configsystem;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.List;
/**
* Unit tests for IntelliJPlugin for Lombok with activated config system
*/
public class FieldDefaultsWithoutLombokImportTest extends AbstractLombokConfigSystemTestCase {
@Override
protected String getBasePath() {
return super.getBasePath() + "/configsystem/fieldDefaults";
}
public void testDefaultFinal$DefaultFinalFieldTest() throws IOException {
doTest();
}
public void testDefaultPrivate$DefaultPrivateFieldTest() throws IOException {
doTest();
}
@Override
protected @NotNull List<ModeRunnerType> modes() {
//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);
}
}

View File

@@ -1,5 +1,9 @@
package de.plushnikov.intellij.plugin.configsystem;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class FieldNameConstantsTest extends AbstractLombokConfigSystemTestCase {
@Override
@@ -14,4 +18,11 @@ public class FieldNameConstantsTest extends AbstractLombokConfigSystemTestCase {
public void testUppercase$FieldNameConstantsUppercased() {
doTest();
}
@Override
protected @NotNull List<ModeRunnerType> 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);
}
}

View File

@@ -1,6 +1,9 @@
package de.plushnikov.intellij.plugin.configsystem;
import com.intellij.testFramework.DumbModeTestUtils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class GetterDumbModeTest extends AbstractLombokConfigSystemTestCase {
@@ -38,4 +41,10 @@ public class GetterDumbModeTest extends AbstractLombokConfigSystemTestCase {
}
);
}
@Override
protected @NotNull List<ModeRunnerType> modes() {
//use normal mode, because it is configured manually in tests
return List.of(ModeRunnerType.NORMAL);
}
}

View File

@@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
public class Builder16Test extends AbstractLombokParsingTestCase {
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
protected @NotNull LightProjectDescriptor getParsingDescriptor() {
return LombokTestUtil.LOMBOK_NEW_DESCRIPTOR;
}

View File

@@ -5,6 +5,8 @@ import de.plushnikov.intellij.plugin.AbstractLombokParsingTestCase;
import de.plushnikov.intellij.plugin.LombokTestUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Unit tests for @FieldNameConstants annotation from old version of lombok (1.18.2)
*/
@@ -12,11 +14,18 @@ public class FieldNameConstantsOldTest extends AbstractLombokParsingTestCase {
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
protected LightProjectDescriptor getParsingDescriptor() {
return LombokTestUtil.LOMBOK_OLD_DESCRIPTOR;
}
public void testFieldnameconstants$FieldNameConstantsOldBasic() {
doTest(true);
}
@Override
protected @NotNull List<ModeRunnerType> 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);
}
}

View File

@@ -5,6 +5,8 @@ import de.plushnikov.intellij.plugin.AbstractLombokParsingTestCase;
import de.plushnikov.intellij.plugin.LombokTestUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Unit tests for @FieldNameConstants annotation from current version of lombok
*/
@@ -21,14 +23,21 @@ public class FieldNameConstantsTest extends AbstractLombokParsingTestCase {
public void testFieldnameconstants$FieldNameConstantsHandrolled() {
doTest(true);
}
public void testFieldnameconstants$FieldNameConstantsOnRecord() {
doTest(true);
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
protected LightProjectDescriptor getParsingDescriptor() {
return LombokTestUtil.LOMBOK_NEW_DESCRIPTOR;
}
@Override
protected @NotNull List<ModeRunnerType> 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);
}
}

View File

@@ -18,4 +18,7 @@ public class OnXAnnotationTest extends AbstractLombokParsingTestCase {
doTest(false);
}
public void testTestOnXSimple() {
doTest(false);
}
}

View File

@@ -7,4 +7,4 @@ public class UtilityClassTest extends AbstractLombokParsingTestCase {
public void testUtilityclass$UtilityClassPlain() {
doTest(true);
}
}
}

View File

@@ -6,14 +6,14 @@ import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
public class TestOnX {
@NonNull
@NotNull
private final Integer someIntField;
/** @deprecated */
@Deprecated
@NonNull
@NotNull
private String someStringField;
private float someFloatField;
@@ -30,7 +30,7 @@ public class TestOnX {
@Inject
@Named("myName1")
public TestOnX(@NonNull Integer someIntField, @NonNull String someStringField) {
public TestOnX(@NotNull Integer someIntField, @NotNull 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(@NonNull Integer someIntField, @NonNull String someStringField, float someFloatField) {
public TestOnX(@NotNull Integer someIntField, @NotNull 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)
@NonNull
@NotNull
public Integer getSomeIntField() {
return this.someIntField;
}
@@ -118,7 +118,7 @@ public class TestOnX {
@Size(
max = 20
)
@NonNull
@NotNull
public String getSomeStringField() {
return this.someStringField;
}
@@ -128,7 +128,7 @@ public class TestOnX {
@Size(
min = 10
)
public void setSomeStringField(@Size(min = 15) @NonNull String someStringField) {
public void setSomeStringField(@Size(min = 15) @NotNull String someStringField) {
if (someStringField == null) {
throw new NullPointerException("someStringField is marked non-null but is null");
} else {
@@ -136,7 +136,7 @@ public class TestOnX {
}
}
@NonNull
@NotNull
public TestOnX withSomeFloatField(@Min(1) float someFloatField) {
return this.someFloatField == someFloatField ? this : new TestOnX(this.someIntField, this.someStringField, someFloatField);
}

View File

@@ -0,0 +1,18 @@
package action.delombok.onx;
import lombok.RequiredArgsConstructor;
import lombok.NonNull;
import javax.inject.Inject;
import javax.inject.Named;
public class TestOnX {
@NonNull
private final Integer someIntField;
@Named("myName1")
@Inject
public TestOnX(@NonNull Integer someIntField) {
this.someIntField = someIntField;
}
}

View File

@@ -8,6 +8,7 @@ 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")})
@@ -15,16 +16,16 @@ import javax.validation.constraints.Size;
@EqualsAndHashCode(onParam_ = @Valid)
public class TestOnX {
@Getter(onMethod_ = @Max(100))
@NonNull
@NotNull
private final Integer someIntField;
@NonNull
@NotNull
@Deprecated
@Getter(onMethod_ = @Size(max = 20))
@Setter(onMethod_ = @Size(min = 10), onParam_ = @Size(min = 15))
private String someStringField;
@With(onMethod_ = @NonNull, onParam_ = @Min(1))
@With(onMethod_ = @NotNull, onParam_ = @Min(1))
private float someFloatField;
public static void main(String[] args) {

View File

@@ -0,0 +1,13 @@
package action.delombok.onx;
import lombok.RequiredArgsConstructor;
import lombok.NonNull;
import javax.inject.Inject;
import javax.inject.Named;
@RequiredArgsConstructor(onConstructor_ = {@Inject, @Named("myName1")})
public class TestOnX {
@NonNull
private final Integer someIntField;
}