/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.codeInspection.declaration;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ex.BaseLocalInspectionTool;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import gw.internal.gosu.parser.Expression;
import gw.internal.gosu.parser.expressions.InferredNewExpression;
import gw.internal.gosu.parser.expressions.NullExpression;
import gw.internal.gosu.parser.expressions.NumericLiteral;
import gw.internal.gosu.parser.expressions.TypeLiteral;
import gw.internal.gosu.parser.statements.VarStatement;
import gw.lang.parser.IParsedElement;
import gw.lang.parser.exceptions.IWarningSuppressor;
import gw.lang.parser.expressions.IImplicitTypeAsExpression;
import gw.lang.parser.expressions.IMemberAccessExpression;
import gw.lang.reflect.IPlaceholder;
import gw.lang.reflect.IType;
import gw.lang.reflect.gs.IGosuProgram;
import gw.plugin.ij.intentions.varInferenceFix;
import gw.plugin.ij.lang.psi.api.expressions.IGosuExpression;
import gw.plugin.ij.lang.psi.api.statements.IGosuField;
import gw.plugin.ij.lang.psi.api.statements.IGosuVariable;
import gw.plugin.ij.lang.psi.impl.GosuElementVisitor;
import gw.plugin.ij.util.GosuBundle;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
public class GosuInferTypeInDeclarationInspection extends BaseLocalInspectionTool implements IWarningSuppressor {
public static final String SUPPRESS_WARNING_CODE = "InferTypeInDeclaration";
@Nls
@NotNull
@Override
public String getGroupDisplayName() {
return GosuBundle.message("inspection.group.name.declaration.issues");
}
@Nls
@NotNull
@Override
public String getDisplayName() {
return GosuBundle.message("inspection.variable.type.inferred");
}
@NotNull
@Override
public String getShortName() {
return "GosuInferTypeInDeclaration";
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
return new GosuElementVisitor() {
@Override
public void visitField(IGosuField field) {
IParsedElement parsedElement = field.getParsedElement();
if(parsedElement == null) {
return;
}
if( parsedElement.isSuppressed( GosuInferTypeInDeclarationInspection.this ) ) {
return;
}
boolean isProgramVariable = parsedElement.getGosuClass() instanceof IGosuProgram;
if (parsedElement instanceof VarStatement) {
VarStatement varStmt = (VarStatement) parsedElement;
if (isProgramVariable || varStmt.isPrivate()) {
IGosuExpression init = field.getInitializerGosu();
if (init == null) {
return;
}
Expression expr = (Expression) init.getParsedElement();
if (isFixable(varStmt, expr)) {
holder.registerProblem(field, GosuBundle.message("inspection.variable.type.inferred"), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new TypeInferenceFix(field));
}
}
}
}
@Override
public void visitVariable(IGosuVariable variable) {
IParsedElement parsedElement = variable.getParsedElement();
if (parsedElement instanceof VarStatement) {
if( parsedElement.isSuppressed( GosuInferTypeInDeclarationInspection.this ) ) {
return;
}
VarStatement varStmt = (VarStatement) parsedElement;
Expression expr = varStmt.getAsExpression();
if (isFixable(varStmt, expr)) {
holder.registerProblem(variable, GosuBundle.message("inspection.variable.type.inferred"), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new TypeInferenceFix(variable));
}
}
}
};
}
private boolean isFixable(VarStatement varStmt, Expression expr) {
TypeLiteral typeLiteral = varStmt.getTypeLiteral();
boolean fixable = false;
if (expr != null && typeLiteral != null && varStmt.getType().isAssignableFrom(expr.getType()))
{
fixable = true;
if( expr instanceof InferredNewExpression &&
expr.getLocation().getTextFromTokens().startsWith("{"))
{
// prevent fix in cases like this: var a : Integer[] = {1,2} or
// var a : Set<String> = {}
fixable = false;
} else if( expr instanceof NumericLiteral &&
!((NumericLiteral) expr).isExplicitlyTyped())
{
// prevent fix in cases like this: var a : float = 10
// var a : BigDecimal = 1
fixable = false;
} else if(expr instanceof NullExpression)
{
// prevent fix in cases like this: var a : String = null
fixable = false;
} else if(expr instanceof IImplicitTypeAsExpression)
{
// prevent fix in cases like this: var _subtype : typekey.State = "CA"
fixable = false;
} else if(expr instanceof IMemberAccessExpression && !expr.getLocation().getTextFromTokens().contains("."))
{
// prevent fix in cases like this: var c : Currency = TC_USD
fixable = false;
} else if(varStmt.hasProperty())
{
// prevent fix in cases like this: var x : String as Name = "Gosu"
fixable = false;
}
else {
IType type = typeLiteral.getType().getType();
if( type instanceof IPlaceholder && ((IPlaceholder)type).isPlaceholder() &&
!(expr.getType() instanceof IPlaceholder && ((IPlaceholder)expr.getType()).isPlaceholder()) ) {
// prevent fix in cases like this: var dyn : Dynamic = foo
fixable = false;
}
}
}
return fixable;
}
@Override
public boolean isSuppressed( String warningCode ) {
return SUPPRESS_WARNING_CODE.equals( warningCode ) || "all".equals( warningCode );
}
private class TypeInferenceFix implements LocalQuickFix {
private final varInferenceFix myQuickFix;
public TypeInferenceFix(IGosuVariable variable) {
myQuickFix = new varInferenceFix(variable);
}
@NotNull
public String getName() {
return myQuickFix.getText();
}
@NotNull
public String getFamilyName() {
return GosuBundle.message("inspection.group.name.declaration.issues");
}
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
if (element == null) return;
final PsiFile psiFile = element.getContainingFile();
if (myQuickFix.isAvailable(project, null, psiFile)) {
myQuickFix.invoke(project, null, psiFile);
}
}
}
}