/*
* Copyright 2013-2017 consulo.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package consulo.csharp.ide.highlight.check.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.annotations.RequiredReadAction;
import consulo.csharp.ide.codeInsight.actions.CastNArgumentToTypeRefFix;
import consulo.csharp.ide.codeInsight.actions.CreateUnresolvedConstructorFix;
import consulo.csharp.ide.codeInsight.actions.CreateUnresolvedMethodFix;
import consulo.csharp.ide.highlight.CSharpHighlightContext;
import consulo.csharp.ide.highlight.check.CompilerCheck;
import consulo.csharp.ide.parameterInfo.CSharpParametersInfo;
import consulo.csharp.lang.doc.CSharpDocUtil;
import consulo.csharp.lang.psi.CSharpAttribute;
import consulo.csharp.lang.psi.CSharpCallArgument;
import consulo.csharp.lang.psi.CSharpCallArgumentListOwner;
import consulo.csharp.lang.psi.CSharpConstructorDeclaration;
import consulo.csharp.lang.psi.CSharpMethodDeclaration;
import consulo.csharp.lang.psi.CSharpNewExpression;
import consulo.csharp.lang.psi.CSharpReferenceExpression;
import consulo.csharp.lang.psi.CSharpTypeRefPresentationUtil;
import consulo.csharp.lang.psi.impl.CSharpTypeUtil;
import consulo.csharp.lang.psi.impl.source.CSharpConstructorSuperCallImpl;
import consulo.csharp.lang.psi.impl.source.CSharpIndexAccessExpressionImpl;
import consulo.csharp.lang.psi.impl.source.CSharpMethodCallExpressionImpl;
import consulo.csharp.lang.psi.impl.source.CSharpOperatorReferenceImpl;
import consulo.csharp.lang.psi.impl.source.resolve.MethodResolveResult;
import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.MethodCalcResult;
import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.arguments.NCallArgument;
import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.arguments.NErrorCallArgument;
import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpLambdaResolveResult;
import consulo.csharp.lang.psi.impl.source.resolve.util.CSharpResolveUtil;
import consulo.csharp.module.extension.CSharpLanguageVersion;
import consulo.dotnet.psi.DotNetExpression;
import consulo.dotnet.psi.DotNetLikeMethodDeclaration;
import consulo.dotnet.psi.DotNetParameter;
import consulo.dotnet.psi.DotNetUserType;
import consulo.dotnet.psi.DotNetVariable;
import consulo.dotnet.resolve.DotNetTypeRef;
import consulo.dotnet.resolve.DotNetTypeResolveResult;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction;
import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixActionRegistrarImpl;
import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiPolyVariantReference;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveResult;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
import com.intellij.xml.util.XmlStringUtil;
/**
* @author VISTALL
* @since 19.11.14
*/
public class CC0001 extends CompilerCheck<CSharpReferenceExpression>
{
@RequiredReadAction
@NotNull
@Override
public List<HighlightInfoFactory> check(@NotNull CSharpLanguageVersion languageVersion, @NotNull CSharpHighlightContext highlightContext, @NotNull CSharpReferenceExpression expression)
{
PsiElement referenceElement = expression.getReferenceElement();
if(referenceElement == null || expression.isSoft() || CSharpDocUtil.isInsideDoc(expression))
{
return Collections.emptyList();
}
// if parent is call skip
PsiElement parent = expression.getParent();
if(parent instanceof CSharpMethodCallExpressionImpl || parent instanceof CSharpConstructorSuperCallImpl)
{
return Collections.emptyList();
}
return checkReference(expression, Arrays.asList(referenceElement));
}
@NotNull
@RequiredReadAction
public static List<HighlightInfoFactory> checkReference(@NotNull final PsiElement callElement, @NotNull List<? extends PsiElement> ranges)
{
if(ranges.isEmpty())
{
return Collections.emptyList();
}
ResolveResult[] resolveResults = ResolveResult.EMPTY_ARRAY;
if(callElement instanceof PsiPolyVariantReference)
{
resolveResults = ((PsiPolyVariantReference) callElement).multiResolve(false);
}
else if(callElement instanceof CSharpCallArgumentListOwner)
{
resolveResults = ((CSharpCallArgumentListOwner) callElement).multiResolve(false);
}
ResolveResult goodResult = CSharpResolveUtil.findFirstValidResult(resolveResults);
List<HighlightInfoFactory> list = new ArrayList<>(2);
if(goodResult == null)
{
if(resolveResults.length == 0)
{
for(PsiElement range : ranges)
{
CompilerCheckBuilder result = new CompilerCheckBuilder()
{
@Nullable
@Override
public HighlightInfo create()
{
HighlightInfo highlightInfo = super.create();
if(highlightInfo != null)
{
PsiReference reference = null;
if(callElement instanceof PsiReference)
{
reference = (PsiReference) callElement;
}
else if(callElement instanceof CSharpMethodCallExpressionImpl)
{
DotNetExpression callExpression = ((CSharpMethodCallExpressionImpl) callElement).getCallExpression();
if(callExpression instanceof PsiReference)
{
reference = (PsiReference) callExpression;
}
}
if(reference != null)
{
UnresolvedReferenceQuickFixProvider.registerReferenceFixes(reference, new QuickFixActionRegistrarImpl(highlightInfo));
}
}
return highlightInfo;
}
};
result.setHighlightInfoType(HighlightInfoType.WRONG_REF);
String unresolvedText = getUnresolvedText(callElement, range);
if(isCalleInsideCalle(callElement))
{
result.setText("Expression cant be invoked");
// remap to error, due we want make all exp red
result.setHighlightInfoType(HighlightInfoType.ERROR);
}
else
{
result.setText("'" + StringUtil.unescapeXml(unresolvedText) + "' is not resolved");
}
result.setTextRange(range.getTextRange());
list.add(result);
}
}
else
{
final HighlightInfo highlightInfo = createHighlightInfo(callElement, resolveResults[0]);
if(highlightInfo == null)
{
return list;
}
list.add(() -> highlightInfo);
}
}
return list;
}
@Contract("null -> false")
@RequiredReadAction
public static boolean isCalleInsideCalle(@Nullable PsiElement callElement)
{
if(callElement instanceof CSharpMethodCallExpressionImpl)
{
return !(((CSharpMethodCallExpressionImpl) callElement).getCallExpression() instanceof CSharpReferenceExpression);
}
else
{
return false;
}
}
@RequiredReadAction
private static String getUnresolvedText(@NotNull PsiElement element, @NotNull PsiElement range)
{
CSharpCallArgumentListOwner callOwner = findCallOwner(element);
if(callOwner != null)
{
String name;
if(element instanceof CSharpIndexAccessExpressionImpl)
{
name = "this";
}
else
{
name = range.getText();
}
StringBuilder builder = new StringBuilder();
builder.append(name);
char[] openAndCloseTokens = CSharpParametersInfo.getOpenAndCloseTokens(element);
builder.append(openAndCloseTokens[0]);
CSharpCallArgument[] arguments = callOwner.getCallArguments();
for(int i = 0; i < arguments.length; i++)
{
if(i != 0)
{
builder.append(", ");
}
CSharpCallArgument callArgument = arguments[i];
DotNetExpression argumentExpression = callArgument.getArgumentExpression();
appendType(builder, argumentExpression == null ? DotNetTypeRef.ERROR_TYPE : argumentExpression.toTypeRef(false), element);
}
builder.append(openAndCloseTokens[1]);
return builder.toString();
}
else
{
return range.getText();
}
}
@Nullable
@RequiredReadAction
public static HighlightInfo createHighlightInfo(@NotNull PsiElement element, @NotNull ResolveResult resolveResult)
{
if(!(resolveResult instanceof MethodResolveResult))
{
return null;
}
char[] openAndCloseTokens = CSharpParametersInfo.getOpenAndCloseTokens(element);
PsiElement resolveElement = resolveResult.getElement();
assert resolveElement != null;
MethodCalcResult calcResult = ((MethodResolveResult) resolveResult).getCalcResult();
List<NCallArgument> arguments = calcResult.getArguments();
for(NCallArgument argument : arguments)
{
// missed arguments returning error type too - but we dont need to hide call error
if(argument instanceof NErrorCallArgument)
{
continue;
}
if(CSharpTypeUtil.isErrorTypeRef(argument.getTypeRef()))
{
return null;
}
}
CSharpCallArgumentListOwner callOwner = findCallOwner(element);
if(callOwner != null)
{
StringBuilder tooltipBuilder = new StringBuilder();
tooltipBuilder.append("<b>");
// sometimes name can be null
if(element instanceof CSharpOperatorReferenceImpl)
{
String canonicalText = ((CSharpOperatorReferenceImpl) element).getCanonicalText();
tooltipBuilder.append(XmlStringUtil.escapeString(canonicalText));
}
else if(element instanceof CSharpIndexAccessExpressionImpl)
{
tooltipBuilder.append(""); //FIXME [VISTALL] some name?
}
else
{
String name = ((PsiNamedElement) resolveElement).getName();
tooltipBuilder.append(name);
}
tooltipBuilder.append(" ").append(openAndCloseTokens[0]);
if(resolveElement instanceof DotNetVariable)
{
DotNetTypeRef typeRef = ((DotNetVariable) resolveElement).toTypeRef(false);
DotNetTypeResolveResult typeResolveResult = typeRef.resolve();
if(!(typeResolveResult instanceof CSharpLambdaResolveResult))
{
return null;
}
DotNetTypeRef[] parameterTypes = ((CSharpLambdaResolveResult) typeResolveResult).getParameterTypeRefs();
for(int i = 0; i < parameterTypes.length; i++)
{
if(i != 0)
{
tooltipBuilder.append(", ");
}
appendType(tooltipBuilder, parameterTypes[i], element);
}
}
else if(resolveElement instanceof DotNetLikeMethodDeclaration)
{
DotNetParameter[] parameters = ((DotNetLikeMethodDeclaration) resolveElement).getParameters();
for(int i = 0; i < parameters.length; i++)
{
if(i != 0)
{
tooltipBuilder.append(", ");
}
tooltipBuilder.append(parameters[i].getName()).append(" : ");
appendType(tooltipBuilder, parameters[i].toTypeRef(false), element);
}
}
tooltipBuilder.append(openAndCloseTokens[1]).append("</b> cannot be applied<br>");
tooltipBuilder.append("to <b>").append(openAndCloseTokens[0]);
for(int i = 0; i < arguments.size(); i++)
{
if(i != 0)
{
tooltipBuilder.append(", ");
}
NCallArgument nCallArgument = arguments.get(i);
if(!nCallArgument.isValid())
{
tooltipBuilder.append("<font color=\"").append(ColorUtil.toHex(JBColor.RED)).append("\">");
}
String parameterName = nCallArgument.getParameterName();
if(parameterName != null)
{
tooltipBuilder.append(parameterName).append(" : ");
}
appendType(tooltipBuilder, nCallArgument.getTypeRef(), element);
if(!nCallArgument.isValid())
{
tooltipBuilder.append("</font>");
}
}
tooltipBuilder.append(openAndCloseTokens[1]).append("</b>");
PsiElement parameterList = callOwner.getParameterList();
if(parameterList == null)
{
parameterList = callOwner;
}
HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR);
builder = builder.description("");
builder = builder.escapedToolTip(tooltipBuilder.toString());
builder = builder.range(parameterList);
HighlightInfo highlightInfo = builder.create();
if(highlightInfo != null)
{
registerQuickFixes(callOwner, resolveElement, arguments, highlightInfo);
}
return highlightInfo;
}
return null;
}
private static void registerQuickFixes(@NotNull CSharpCallArgumentListOwner element, PsiElement resolveElement, List<NCallArgument> arguments, HighlightInfo highlightInfo)
{
DotNetExpression callExpression = element instanceof CSharpMethodCallExpressionImpl ? ((CSharpMethodCallExpressionImpl) element).getCallExpression() : null;
if(callExpression instanceof CSharpReferenceExpression)
{
if(resolveElement instanceof CSharpMethodDeclaration)
{
QuickFixAction.registerQuickFixAction(highlightInfo, new CreateUnresolvedMethodFix((CSharpReferenceExpression) callExpression));
}
if(resolveElement instanceof CSharpConstructorDeclaration)
{
QuickFixAction.registerQuickFixAction(highlightInfo, new CreateUnresolvedConstructorFix((CSharpReferenceExpression) callExpression));
}
}
for(NCallArgument argument : arguments)
{
if(!argument.isValid())
{
CSharpCallArgument callArgument = argument.getCallArgument();
if(callArgument == null)
{
continue;
}
DotNetExpression argumentExpression = callArgument.getArgumentExpression();
if(argumentExpression == null)
{
continue;
}
DotNetTypeRef parameterTypeRef = argument.getParameterTypeRef();
if(parameterTypeRef == null)
{
continue;
}
String parameterName = argument.getParameterName();
if(parameterName == null)
{
continue;
}
QuickFixAction.registerQuickFixAction(highlightInfo, new CastNArgumentToTypeRefFix(argumentExpression, parameterTypeRef, parameterName));
}
}
}
@RequiredReadAction
private static void appendType(@NotNull StringBuilder builder, @NotNull DotNetTypeRef typeRef, @NotNull PsiElement scope)
{
if(typeRef == DotNetTypeRef.ERROR_TYPE)
{
builder.append("?");
}
else
{
builder.append(XmlStringUtil.escapeString(CSharpTypeRefPresentationUtil.buildText(typeRef, scope)));
}
}
private static CSharpCallArgumentListOwner findCallOwner(PsiElement element)
{
PsiElement parent = element.getParent();
if(element instanceof CSharpOperatorReferenceImpl ||
element instanceof CSharpMethodCallExpressionImpl ||
element instanceof CSharpConstructorSuperCallImpl ||
element instanceof CSharpIndexAccessExpressionImpl)
{
return (CSharpCallArgumentListOwner) element;
}
else if(parent instanceof CSharpAttribute)
{
return (CSharpCallArgumentListOwner) parent;
}
else if(parent instanceof DotNetUserType && parent.getParent() instanceof CSharpNewExpression)
{
return (CSharpCallArgumentListOwner) parent.getParent();
}
return null;
}
}