package de.plushnikov.intellij.plugin.processor; import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.RecursionManager; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiArrayInitializerExpression; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiConditionalExpression; import com.intellij.psi.PsiDeclarationStatement; import com.intellij.psi.PsiDiamondType; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiForStatement; import com.intellij.psi.PsiForeachStatement; import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.PsiLambdaExpression; import com.intellij.psi.PsiLocalVariable; import com.intellij.psi.PsiNewExpression; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiReferenceParameterList; import com.intellij.psi.PsiType; import com.intellij.psi.PsiTypeElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.TypeConversionUtil; import de.plushnikov.intellij.plugin.problem.LombokProblem; import de.plushnikov.intellij.plugin.settings.ProjectSettings; import lombok.experimental.var; import lombok.val; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; public class ValProcessor extends AbstractProcessor { private static final String LOMBOK_VAL_FQN = "lombok.val"; private static final String LOMBOK_VAL_SHORT_NAME = "val"; private static final String LOMBOK_VAR_FQN = "lombok.experimental.var"; private static final String LOMBOK_VAR_SHORT_NAME = "var"; public ValProcessor() { super(PsiElement.class, val.class, var.class); } public static boolean isVal(@NotNull PsiLocalVariable psiLocalVariable) { return psiLocalVariable.getInitializer() != null && isSameName(psiLocalVariable.getTypeElement().getText()); } public static boolean isValOrVar(@NotNull PsiLocalVariable psiLocalVariable) { return psiLocalVariable.getInitializer() != null && isValOrVar(psiLocalVariable.getTypeElement().getText()); } private static boolean isSameName(String className) { return LOMBOK_VAL_SHORT_NAME.equals(className) || LOMBOK_VAL_FQN.equals(className); } private static boolean isVar(String className) { return LOMBOK_VAR_SHORT_NAME.equals(className) || LOMBOK_VAR_FQN.equals(className); } private static boolean isValOrVar(String className) { return isSameName(className) || isVar(className); } public boolean isEnabled(@NotNull Project project) { return isEnabled(PropertiesComponent.getInstance(project)); } @Override public boolean isEnabled(@NotNull PropertiesComponent propertiesComponent) { return ProjectSettings.isEnabled(propertiesComponent, ProjectSettings.IS_VAL_ENABLED); } @NotNull @Override public Collection<PsiAnnotation> collectProcessedAnnotations(@NotNull PsiClass psiClass) { return Collections.emptyList(); } @NotNull @Override public Collection<LombokProblem> verifyAnnotation(@NotNull PsiAnnotation psiAnnotation) { return Collections.emptyList(); } public void verifyVariable(@NotNull final PsiLocalVariable psiLocalVariable, @NotNull final ProblemsHolder holder) { boolean isVal = isSameName(psiLocalVariable.getTypeElement().getText()); boolean isVar = isVar(psiLocalVariable.getTypeElement().getText()); final String ann = isVal ? "val" : "var"; if (isVal || isVar) { final PsiExpression initializer = psiLocalVariable.getInitializer(); if (initializer == null) { holder.registerProblem(psiLocalVariable, "'" + ann + "' on a local variable requires an initializer expression", ProblemHighlightType.ERROR); } else if (initializer instanceof PsiArrayInitializerExpression) { holder.registerProblem(psiLocalVariable, "'" + ann + "' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })", ProblemHighlightType.ERROR); } else if (initializer instanceof PsiLambdaExpression) { holder.registerProblem(psiLocalVariable, "'" + ann + "' is not allowed with lambda expressions.", ProblemHighlightType.ERROR); } else if (isVal) { final PsiElement typeParentParent = psiLocalVariable.getParent(); if (typeParentParent instanceof PsiDeclarationStatement && typeParentParent.getParent() instanceof PsiForStatement) { holder.registerProblem(psiLocalVariable, "'" + ann + "' is not allowed in old-style for loops", ProblemHighlightType.ERROR); } } } } public void verifyParameter(@NotNull final PsiParameter psiParameter, @NotNull final ProblemsHolder holder) { final PsiTypeElement typeElement = psiParameter.getTypeElement(); boolean isVal = null != typeElement && isSameName(typeElement.getText()); boolean isVar = null != typeElement && isVar(typeElement.getText()); if (isVar || isVal) { PsiElement scope = psiParameter.getDeclarationScope(); boolean isForeachStatement = scope instanceof PsiForeachStatement; boolean isForStatement = scope instanceof PsiForStatement; if (isVal && !isForeachStatement) { holder.registerProblem(psiParameter, "'val' works only on local variables and on foreach loops", ProblemHighlightType.ERROR); } else if (isVar && !(isForeachStatement || isForStatement)) { holder.registerProblem(psiParameter, "'var' works only on local variables and on for/foreach loops", ProblemHighlightType.ERROR); } } } private boolean isValOrVarForEach(@NotNull PsiParameter psiParameter) { return psiParameter.getParent() instanceof PsiForeachStatement && isValOrVar(psiParameter.getTypeElement().getText()); } @Nullable public PsiType inferType(PsiTypeElement typeElement) { PsiType psiType = null; final PsiElement parent = typeElement.getParent(); if ((parent instanceof PsiLocalVariable && isValOrVar((PsiLocalVariable) parent)) || (parent instanceof PsiParameter && isValOrVarForEach((PsiParameter) parent))) { if (parent instanceof PsiLocalVariable) { psiType = processLocalVariableInitializer(((PsiLocalVariable) parent).getInitializer()); } else { psiType = processParameterDeclaration(((PsiParameter) parent).getDeclarationScope()); } if (null == psiType) { psiType = PsiType.getJavaLangObject(typeElement.getManager(), GlobalSearchScope.allScope(typeElement.getProject())); } } return psiType; } private PsiType processLocalVariableInitializer(final PsiExpression psiExpression) { PsiType result = null; if (null != psiExpression && !(psiExpression instanceof PsiArrayInitializerExpression)) { if (psiExpression instanceof PsiConditionalExpression) { result = RecursionManager.doPreventingRecursion(psiExpression, true, new Computable<PsiType>() { @Override public PsiType compute() { final PsiExpression thenExpression = ((PsiConditionalExpression) psiExpression).getThenExpression(); final PsiExpression elseExpression = ((PsiConditionalExpression) psiExpression).getElseExpression(); final PsiType thenType = null != thenExpression ? thenExpression.getType() : null; final PsiType elseType = null != elseExpression ? elseExpression.getType() : null; if (thenType == null) { return elseType; } if (elseType == null) { return thenType; } if (TypeConversionUtil.isAssignable(thenType, elseType, false)) { return thenType; } if (TypeConversionUtil.isAssignable(elseType, thenType, false)) { return elseType; } return thenType; } }); } else { result = RecursionManager.doPreventingRecursion(psiExpression, true, new Computable<PsiType>() { @Override public PsiType compute() { return psiExpression.getType(); } }); } if (psiExpression instanceof PsiNewExpression) { final PsiJavaCodeReferenceElement reference = ((PsiNewExpression) psiExpression).getClassOrAnonymousClassReference(); if (reference != null) { final PsiReferenceParameterList parameterList = reference.getParameterList(); if (parameterList != null) { final PsiTypeElement[] elements = parameterList.getTypeParameterElements(); if (elements.length == 1 && elements[0].getType() instanceof PsiDiamondType) { result = TypeConversionUtil.erasure(result); } } } } } return result; } private PsiType processParameterDeclaration(PsiElement parentDeclarationScope) { PsiType result = null; if (parentDeclarationScope instanceof PsiForeachStatement) { final PsiForeachStatement foreachStatement = (PsiForeachStatement) parentDeclarationScope; final PsiExpression iteratedValue = foreachStatement.getIteratedValue(); if (iteratedValue != null) { result = JavaGenericsUtil.getCollectionItemType(iteratedValue); } } return result; } }