/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.codeInspection.statement;
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.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import gw.internal.gosu.parser.Expression;
import gw.internal.gosu.parser.Statement;
import gw.internal.gosu.parser.expressions.Identifier;
import gw.internal.gosu.parser.expressions.NumericLiteral;
import gw.internal.gosu.parser.expressions.RelationalExpression;
import gw.internal.gosu.parser.statements.WhileStatement;
import gw.lang.parser.IParsedElement;
import gw.lang.parser.IStatement;
import gw.lang.parser.exceptions.IWarningSuppressor;
import gw.lang.parser.statements.IAssignmentStatement;
import gw.lang.parser.statements.IStatementList;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.java.JavaTypes;
import gw.plugin.ij.intentions.WhileToForFix;
import gw.plugin.ij.lang.psi.api.statements.IGosuVariable;
import gw.plugin.ij.lang.psi.impl.GosuElementVisitor;
import gw.plugin.ij.lang.psi.impl.expressions.GosuIdentifierImpl;
import gw.plugin.ij.lang.psi.impl.statements.GosuWhileStatementImpl;
import gw.plugin.ij.util.GosuBundle;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
public class GosuWhileToForInspection extends BaseLocalInspectionTool implements IWarningSuppressor {
public static final String SUPPRESS_WARNING_CODE = "WhileToFor";
@Nls
@NotNull
@Override
public String getGroupDisplayName() {
return GosuBundle.message("inspection.group.name.statement.issues");
}
@Nls
@NotNull
@Override
public String getDisplayName() {
return GosuBundle.message("inspection.while.to.for");
}
@NotNull
@Override
public String getShortName() {
return "GosuWhileToForInspection";
}
@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 visitWhileStatement(GosuWhileStatementImpl whileStatement) {
IParsedElement parsedElement = whileStatement.getParsedElement();
PsiElement problemTarget = whileStatement.getFirstChild();
if (parsedElement instanceof WhileStatement && problemTarget != null) {
{
WhileStatement whileStmt = (WhileStatement) parsedElement;
Expression expr = whileStmt.getExpression();
if(expr instanceof RelationalExpression && ((RelationalExpression) expr).getOperator().equals("<") ) {
TypeSystem.pushModule(parsedElement.getModule());
try {
if( whileStmt.isSuppressed( GosuWhileToForInspection.this ) ) {
return;
}
}
finally{
TypeSystem.popModule( parsedElement.getModule() );
}
RelationalExpression cond = (RelationalExpression) expr;
Expression lhs = cond.getLHS();
if(!lhs.getType().equals(JavaTypes.INTEGER()) &&
!lhs.getType().equals(JavaTypes.pINT()) &&
!(lhs instanceof Identifier))
{
return;
}
String ident = lhs.toString();
IGosuVariable declarationEqualToZero = findDeclarationEqualToZero(ident, whileStatement);
if( declarationEqualToZero == null) {
return;
}
if(!isDeclarationDeletable(ident, whileStatement, declarationEqualToZero.getTextOffset())) {
return;
}
Expression rhs = cond.getRHS();
if((rhs instanceof NumericLiteral && isPositive((NumericLiteral) rhs)) ||
rhs.toString().endsWith(".size()") ||
rhs.toString().endsWith(".length()") ||
rhs.toString().endsWith(".length"))
{
Statement s = whileStmt.getStatement();
if(s instanceof IStatementList) {
IStatement[] statements = ((IStatementList) s).getStatements();
if(statements == null || statements.length <= 1) {
return;
}
IAssignmentStatement increment = findOnlyUpdateInStatements(ident, statements, whileStatement);
if(increment != null && increment.getLocation().getTextFromTokens().equals(ident+"++")) {
holder.registerProblem(problemTarget, GosuBundle.message("inspection.while.to.for"),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
new WhileFix(whileStatement, ident, rhs, declarationEqualToZero, increment));
}
}
}
}
}
}
}
private boolean isDeclarationDeletable(final String ident, GosuWhileStatementImpl whileStmt, final int declLine) {
TextRange whileTextRange = whileStmt.getTextRange();
Collection<GosuIdentifierImpl> identifiers = PsiTreeUtil.collectElementsOfType(whileStmt.getParent(), GosuIdentifierImpl.class);
for (GosuIdentifierImpl i : identifiers) {
PsiElement parent = i.getParent();
if (i.getText().equals(ident)) {
int parOff = parent.getTextOffset();
if (parOff != declLine && !whileTextRange.contains(parOff)) {
return false;
}
}
}
return true;
}
private IAssignmentStatement findOnlyUpdateInStatements(String ident, IStatement[] statements, GosuWhileStatementImpl whileStatement) {
ArrayList<IAssignmentStatement> l = new ArrayList<>();
for (IStatement statement : statements) {
if (statement instanceof IAssignmentStatement &&
((IAssignmentStatement) statement).getIdentifier().toString().equals(ident)) {
l.add(((IAssignmentStatement) statement));
}
}
if(l.size() == 1) {
IAssignmentStatement incr = l.get(0);
int incrOff = incr.getLocation().getOffset();
Collection<GosuIdentifierImpl> identifiers = PsiTreeUtil.collectElementsOfType(whileStatement, GosuIdentifierImpl.class);
for (GosuIdentifierImpl i : identifiers) {
PsiElement parent = i.getParent();
if (i.getText().equals(ident)) {
int parOff = parent.getNode().getStartOffset();
if (parOff > incrOff) {
return null;
}
}
}
return incr;
}
return null;
}
private IGosuVariable findDeclarationEqualToZero(String ident, GosuWhileStatementImpl whileStatement) {
PsiElement prev = whileStatement.getPrevSibling();
while(prev != null){
if(prev instanceof IGosuVariable) {
IGosuVariable var = (IGosuVariable) prev;
PsiExpression initializer = var.getInitializer();
if(var.getName().equals(ident) && initializer != null && initializer.getText().equals("0")) {
return var;
}
}
prev = prev.getPrevSibling();
}
return null;
}
private boolean isPositive(NumericLiteral num) {
Object res = num.evaluate();
return res instanceof Integer && (Integer) res > 0;
}
};
}
@Override
public boolean isSuppressed( String warningCode ) {
return SUPPRESS_WARNING_CODE.equals( warningCode ) || "all".equals( warningCode );
}
private class WhileFix implements LocalQuickFix {
private final WhileToForFix myQuickFix;
public WhileFix(PsiElement whileStmt, String ident, Expression rhs, IGosuVariable declarationEqualToZero, IAssignmentStatement increment) {
myQuickFix = new WhileToForFix(whileStmt, ident, rhs, declarationEqualToZero, increment);
}
@NotNull
public String getName() {
return myQuickFix.getText();
}
@NotNull
public String getFamilyName() {
return GosuBundle.message("inspection.group.name.statement.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);
}
}
}
}