diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RemoveUnusedVariableUtil.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RemoveUnusedVariableUtil.java index a024ba343962..cc3b96c6a3d5 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RemoveUnusedVariableUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RemoveUnusedVariableUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,121 +16,39 @@ package com.intellij.codeInsight.daemon.impl.quickfix; import com.intellij.psi.*; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.InheritanceUtil; -import com.intellij.psi.util.PropertyUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.util.IncorrectOperationException; -import gnu.trove.THashSet; +import com.siyeh.ig.psiutils.SideEffectChecker; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.ArrayList; +import java.util.List; public class RemoveUnusedVariableUtil { - public static final int MAKE_STATEMENT = 1; - public static final int DELETE_ALL = 2; - public static final int CANCEL = 0; - private static final Set ourSideEffectFreeClasses = new THashSet(); - static { - ourSideEffectFreeClasses.add(Object.class.getName()); - ourSideEffectFreeClasses.add(Short.class.getName()); - ourSideEffectFreeClasses.add(Character.class.getName()); - ourSideEffectFreeClasses.add(Byte.class.getName()); - ourSideEffectFreeClasses.add(Integer.class.getName()); - ourSideEffectFreeClasses.add(Long.class.getName()); - ourSideEffectFreeClasses.add(Float.class.getName()); - ourSideEffectFreeClasses.add(Double.class.getName()); - ourSideEffectFreeClasses.add(String.class.getName()); - ourSideEffectFreeClasses.add(StringBuffer.class.getName()); - ourSideEffectFreeClasses.add(Boolean.class.getName()); - - ourSideEffectFreeClasses.add(ArrayList.class.getName()); - ourSideEffectFreeClasses.add(Date.class.getName()); - ourSideEffectFreeClasses.add(HashMap.class.getName()); - ourSideEffectFreeClasses.add(HashSet.class.getName()); - ourSideEffectFreeClasses.add(Hashtable.class.getName()); - ourSideEffectFreeClasses.add(LinkedHashMap.class.getName()); - ourSideEffectFreeClasses.add(LinkedHashSet.class.getName()); - ourSideEffectFreeClasses.add(LinkedList.class.getName()); - ourSideEffectFreeClasses.add(Stack.class.getName()); - ourSideEffectFreeClasses.add(TreeMap.class.getName()); - ourSideEffectFreeClasses.add(TreeSet.class.getName()); - ourSideEffectFreeClasses.add(Vector.class.getName()); - ourSideEffectFreeClasses.add(WeakHashMap.class.getName()); + public enum RemoveMode { + MAKE_STATEMENT, + DELETE_ALL, + CANCEL } - static boolean isSideEffectFreeConstructor(PsiNewExpression newExpression) { - PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); - PsiClass aClass = classReference == null ? null : (PsiClass)classReference.resolve(); - String qualifiedName = aClass == null ? null : aClass.getQualifiedName(); - if (qualifiedName == null) return false; - if (ourSideEffectFreeClasses.contains(qualifiedName)) return true; - - PsiFile file = aClass.getContainingFile(); - PsiDirectory directory = file.getContainingDirectory(); - PsiPackage classPackage = JavaDirectoryService.getInstance().getPackage(directory); - String packageName = classPackage == null ? null : classPackage.getQualifiedName(); - - // all Throwable descendants from java.lang are side effects free - if ("java.lang".equals(packageName) || "java.io".equals(packageName)) { - PsiClass throwableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.lang.Throwable", aClass.getResolveScope()); - if (throwableClass != null && InheritanceUtil.isInheritorOrSelf(aClass, throwableClass, true)) { - return true; + public static boolean checkSideEffects(PsiExpression element, PsiVariable variableToIgnore, List sideEffects) { + if (sideEffects == null || element == null) return false; + List writes = new ArrayList(); + SideEffectChecker.checkSideEffects(element, writes); + if (variableToIgnore != null) { + for (int i = writes.size() - 1; i >= 0; i--) { + PsiElement write = writes.get(i); + if (!(write instanceof PsiAssignmentExpression)) continue; + PsiExpression lExpression = ((PsiAssignmentExpression)write).getLExpression(); + if (lExpression instanceof PsiReference && ((PsiReference)lExpression).resolve() == variableToIgnore) { + writes.remove(i); + } } } - return false; + sideEffects.addAll(writes); + return !writes.isEmpty(); } - public static boolean checkSideEffects(PsiElement element, PsiVariable variable, List sideEffects) { - if (sideEffects == null || element == null) return false; - if (element instanceof PsiMethodCallExpression) { - final PsiMethod psiMethod = ((PsiMethodCallExpression)element).resolveMethod(); - if (psiMethod == null || !PropertyUtil.isSimpleGetter(psiMethod) && !PropertyUtil.isSimpleSetter(psiMethod)) { - sideEffects.add(element); - return true; - } - } - if (element instanceof PsiNewExpression) { - PsiNewExpression newExpression = (PsiNewExpression)element; - if (newExpression.getArrayDimensions().length == 0 - && newExpression.getArrayInitializer() == null - && !isSideEffectFreeConstructor(newExpression)) { - sideEffects.add(element); - return true; - } - } - if (element instanceof PsiAssignmentExpression - && !(((PsiAssignmentExpression)element).getLExpression() instanceof PsiReferenceExpression - && ((PsiReferenceExpression)((PsiAssignmentExpression)element).getLExpression()).resolve() == variable)) { - sideEffects.add(element); - return true; - } - - IElementType tokenType = null; - PsiExpression operand = null; - if (element instanceof PsiPrefixExpression) { - operand = ((PsiPrefixExpression)element).getOperand(); - tokenType = ((PsiPrefixExpression)element).getOperationTokenType(); - } else if (element instanceof PsiPostfixExpression) { - operand = ((PsiPostfixExpression)element).getOperand(); - tokenType = ((PsiPostfixExpression)element).getOperationTokenType(); - } - - if (JavaTokenType.MINUSMINUS.equals(tokenType) || JavaTokenType.PLUSPLUS.equals(tokenType)) { - operand = PsiUtil.deparenthesizeExpression(operand); - if (!(operand instanceof PsiReferenceExpression && ((PsiReferenceExpression)operand).resolve() == variable)) { - sideEffects.add(element); - return true; - } - } - PsiElement[] children = element.getChildren(); - - for (PsiElement child : children) { - checkSideEffects(child, variable, sideEffects); - } - return !sideEffects.isEmpty(); -} - static PsiElement replaceElementWithExpression(PsiExpression expression, PsiElementFactory factory, PsiElement element) throws IncorrectOperationException { @@ -177,7 +95,7 @@ public class RemoveUnusedVariableUtil { } } - static void deleteReferences(PsiVariable variable, List references, int mode) throws IncorrectOperationException { + static void deleteReferences(PsiVariable variable, List references, @NotNull RemoveMode mode) throws IncorrectOperationException { for (PsiElement expression : references) { processUsage(expression, variable, null, mode); } @@ -198,7 +116,7 @@ public class RemoveUnusedVariableUtil { * null if read usage found (may happen if interval between fix creation in invoke() call was long enough) * @throws com.intellij.util.IncorrectOperationException */ - static Boolean processUsage(PsiElement element, PsiVariable variable, List sideEffects, int deleteMode) + static Boolean processUsage(PsiElement element, PsiVariable variable, List sideEffects, @NotNull RemoveMode deleteMode) throws IncorrectOperationException { if (!element.isValid()) return null; PsiElementFactory factory = JavaPsiFacade.getInstance(variable.getProject()).getElementFactory(); @@ -216,8 +134,8 @@ public class RemoveUnusedVariableUtil { // replace assignment with expression and resimplify boolean sideEffectFound = checkSideEffects(rExpression, variable, sideEffects); if (!(element.getParent() instanceof PsiExpressionStatement) || PsiUtil.isStatement(rExpression)) { - if (deleteMode == MAKE_STATEMENT || - deleteMode == DELETE_ALL && !(element.getParent() instanceof PsiExpressionStatement)) { + if (deleteMode == RemoveMode.MAKE_STATEMENT || + deleteMode == RemoveMode.DELETE_ALL && !(element.getParent() instanceof PsiExpressionStatement)) { element = replaceElementWithExpression(rExpression, factory, element); while (element.getParent() instanceof PsiParenthesizedExpression) { element = element.getParent().replace(element); @@ -226,19 +144,19 @@ public class RemoveUnusedVariableUtil { collectReferences(element, variable, references); deleteReferences(variable, references, deleteMode); } - else if (deleteMode == DELETE_ALL) { + else if (deleteMode == RemoveMode.DELETE_ALL) { deleteWholeStatement(element, factory); } return true; } else { - if (deleteMode != CANCEL) { + if (deleteMode != RemoveMode.CANCEL) { deleteWholeStatement(element, factory); } return !sideEffectFound; } } - else if (element instanceof PsiExpressionStatement && deleteMode != CANCEL) { + else if (element instanceof PsiExpressionStatement && deleteMode != RemoveMode.CANCEL) { final PsiElement parent = element.getParent(); if (parent instanceof PsiIfStatement || parent instanceof PsiLoopStatement && ((PsiLoopStatement)parent).getBody() == element) { element.replace(JavaPsiFacade.getElementFactory(element.getProject()).createStatementFromText(";", element)); @@ -257,19 +175,19 @@ public class RemoveUnusedVariableUtil { && !(variable.getParent() instanceof PsiDeclarationStatement && ((PsiDeclarationStatement)variable.getParent()).getDeclaredElements().length > 1)) { - if (deleteMode == MAKE_STATEMENT) { + if (deleteMode == RemoveMode.MAKE_STATEMENT) { element = element.replace(createStatementIfNeeded(expression, factory, element)); List references = new ArrayList(); collectReferences(element, variable, references); deleteReferences(variable, references, deleteMode); } - else if (deleteMode == DELETE_ALL) { + else if (deleteMode == RemoveMode.DELETE_ALL) { element.delete(); } return true; } else { - if (deleteMode != CANCEL) { + if (deleteMode != RemoveMode.CANCEL) { if (element instanceof PsiField) { ((PsiField)element).normalizeDeclaration(); } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AccessStaticViaInstanceFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AccessStaticViaInstanceFix.java index 9841643b1c93..614822ee0651 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AccessStaticViaInstanceFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AccessStaticViaInstanceFix.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -163,9 +163,9 @@ public class AccessStaticViaInstanceFix extends LocalQuickFixAndIntentionActionO }; dialog.show(); int res = dialog.getExitCode(); - if (res == RemoveUnusedVariableUtil.CANCEL) return false; + if (res == RemoveUnusedVariableUtil.RemoveMode.CANCEL.ordinal()) return false; try { - if (res == RemoveUnusedVariableUtil.MAKE_STATEMENT) { + if (res == RemoveUnusedVariableUtil.RemoveMode.MAKE_STATEMENT.ordinal()) { final PsiStatement statementFromText = factory.createStatementFromText(qualifierExpression.getText() + ";", null); final PsiStatement statement = PsiTreeUtil.getParentOfType(myExpression, PsiStatement.class); statement.getParent().addBefore(statementFromText, statement); diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RemoveUnusedVariableFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RemoveUnusedVariableFix.java index d779b1b6e535..8c080beb4a05 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RemoveUnusedVariableFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/RemoveUnusedVariableFix.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,12 +27,15 @@ import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.psi.*; -import com.intellij.psi.util.*; +import com.intellij.psi.util.PsiExpressionTrimRenderer; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.ArrayList; +import java.util.List; public class RemoveUnusedVariableFix implements IntentionAction { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.RemoveUnusedVariableFix"); @@ -83,7 +86,7 @@ public class RemoveUnusedVariableFix implements IntentionAction { references.add(myVariable); // check for side effects for (PsiElement element : references) { - Boolean result = RemoveUnusedVariableUtil.processUsage(element, myVariable, sideEffects, RemoveUnusedVariableUtil.CANCEL); + Boolean result = RemoveUnusedVariableUtil.processUsage(element, myVariable, sideEffects, RemoveUnusedVariableUtil.RemoveMode.CANCEL); if (result == null) return; canCopeWithSideEffects[0] &= result; } @@ -92,7 +95,8 @@ public class RemoveUnusedVariableFix implements IntentionAction { LOG.error(e); } - final int deleteMode = showSideEffectsWarning(sideEffects, myVariable, editor, canCopeWithSideEffects[0]); + final RemoveUnusedVariableUtil.RemoveMode + deleteMode = showSideEffectsWarning(sideEffects, myVariable, editor, canCopeWithSideEffects[0]); ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override @@ -107,17 +111,17 @@ public class RemoveUnusedVariableFix implements IntentionAction { }); } - public static int showSideEffectsWarning(List sideEffects, + public static RemoveUnusedVariableUtil.RemoveMode showSideEffectsWarning(List sideEffects, PsiVariable variable, Editor editor, boolean canCopeWithSideEffects, @NonNls String beforeText, @NonNls String afterText) { - if (sideEffects.isEmpty()) return RemoveUnusedVariableUtil.DELETE_ALL; + if (sideEffects.isEmpty()) return RemoveUnusedVariableUtil.RemoveMode.DELETE_ALL; if (ApplicationManager.getApplication().isUnitTestMode()) { return canCopeWithSideEffects - ? RemoveUnusedVariableUtil.MAKE_STATEMENT - : RemoveUnusedVariableUtil.DELETE_ALL; + ? RemoveUnusedVariableUtil.RemoveMode.MAKE_STATEMENT + : RemoveUnusedVariableUtil.RemoveMode.DELETE_ALL; } Project project = editor.getProject(); HighlightManager highlightManager = HighlightManager.getInstance(project); @@ -128,10 +132,11 @@ public class RemoveUnusedVariableFix implements IntentionAction { SideEffectWarningDialog dialog = new SideEffectWarningDialog(project, false, variable, beforeText, afterText, canCopeWithSideEffects); dialog.show(); - return dialog.getExitCode(); + int code = dialog.getExitCode(); + return RemoveUnusedVariableUtil.RemoveMode.values()[code]; } - private static int showSideEffectsWarning(List sideEffects, + private static RemoveUnusedVariableUtil.RemoveMode showSideEffectsWarning(List sideEffects, PsiVariable variable, Editor editor, boolean canCopeWithSideEffects) { diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/SideEffectWarningDialog.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/SideEffectWarningDialog.java index 5f30a43919af..df1d14851323 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/SideEffectWarningDialog.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/SideEffectWarningDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,7 +63,7 @@ public class SideEffectWarningDialog extends DialogWrapper { @Override public void actionPerformed(ActionEvent e) { - close(RemoveUnusedVariableUtil.DELETE_ALL); + close(RemoveUnusedVariableUtil.RemoveMode.DELETE_ALL.ordinal()); } }; @@ -76,7 +76,7 @@ public class SideEffectWarningDialog extends DialogWrapper { @Override public void actionPerformed(ActionEvent e) { - close(RemoveUnusedVariableUtil.MAKE_STATEMENT); + close(RemoveUnusedVariableUtil.RemoveMode.MAKE_STATEMENT.ordinal()); } }; actions.add(makeStmtAction); @@ -110,7 +110,7 @@ public class SideEffectWarningDialog extends DialogWrapper { @Override public void doCancelAction() { - close(RemoveUnusedVariableUtil.CANCEL); + close(RemoveUnusedVariableUtil.RemoveMode.CANCEL.ordinal()); } @Override diff --git a/java/java-impl/src/com/intellij/codeInspection/RemoveInitializerFix.java b/java/java-impl/src/com/intellij/codeInspection/RemoveInitializerFix.java index 58755b0ad740..af596325bad0 100644 --- a/java/java-impl/src/com/intellij/codeInspection/RemoveInitializerFix.java +++ b/java/java-impl/src/com/intellij/codeInspection/RemoveInitializerFix.java @@ -46,16 +46,19 @@ public class RemoveInitializerFix implements LocalQuickFix { if (!(psiInitializer.getParent() instanceof PsiVariable)) return; final PsiVariable variable = (PsiVariable)psiInitializer.getParent(); - sideEffectAwareRemove(project, psiInitializer, psiInitializer, variable); + sideEffectAwareRemove(project, (PsiExpression)psiInitializer, psiInitializer, variable); } - protected void sideEffectAwareRemove(Project project, PsiElement psiInitializer, PsiElement elementToDelete, PsiVariable variable) { + protected static void sideEffectAwareRemove(Project project, + PsiExpression psiInitializer, + PsiElement elementToDelete, + PsiVariable variable) { if (!FileModificationService.getInstance().prepareFileForWrite(elementToDelete.getContainingFile())) return; final PsiElement declaration = variable.getParent(); final List sideEffects = new ArrayList(); boolean hasSideEffects = RemoveUnusedVariableUtil.checkSideEffects(psiInitializer, variable, sideEffects); - int res = RemoveUnusedVariableUtil.DELETE_ALL; + RemoveUnusedVariableUtil.RemoveMode res = RemoveUnusedVariableUtil.RemoveMode.DELETE_ALL; if (hasSideEffects) { hasSideEffects = PsiUtil.isStatement(psiInitializer); res = RemoveUnusedVariableFix.showSideEffectsWarning(sideEffects, variable, @@ -65,15 +68,14 @@ public class RemoveInitializerFix implements LocalQuickFix { " " + variable.getName() + ";
" + - PsiExpressionTrimRenderer - .render((PsiExpression)psiInitializer) + PsiExpressionTrimRenderer.render(psiInitializer) ); } try { - if (res == RemoveUnusedVariableUtil.DELETE_ALL) { + if (res == RemoveUnusedVariableUtil.RemoveMode.DELETE_ALL) { elementToDelete.delete(); } - else if (res == RemoveUnusedVariableUtil.MAKE_STATEMENT) { + else if (res == RemoveUnusedVariableUtil.RemoveMode.MAKE_STATEMENT) { final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); final PsiStatement statementFromText = factory.createStatementFromText(psiInitializer.getText() + ";", null); final PsiElement parent = elementToDelete.getParent(); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/after7.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/after7.java index 8ad41cf0cbb9..5b47270119bb 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/after7.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/after7.java @@ -4,7 +4,8 @@ import java.io.*; class a { int k; private void run() { - while (1 > 0) ; + k = 9; + while (1 > 0) ; for (;; ) ; } } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/afterPure.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/afterPure.java new file mode 100644 index 000000000000..0b99f729a51e --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/afterPure.java @@ -0,0 +1,13 @@ +// "Remove variable 'i'" "true" +import org.jetbrains.annotations.Contract; + +class a { + private void run() { + } + + @Contract(pure = true) + private Object dodo() { + return null; + } +} + diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/beforePure.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/beforePure.java new file mode 100644 index 000000000000..57cc1b21fa54 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/removeUnusedVariable/beforePure.java @@ -0,0 +1,14 @@ +// "Remove variable 'i'" "true" +import org.jetbrains.annotations.Contract; + +class a { + private void run() { + Object i = dodo(); + } + + @Contract(pure = true) + private Object dodo() { + return null; + } +} + diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SideEffectChecker.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SideEffectChecker.java index 68952503378e..705781e1b665 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SideEffectChecker.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/SideEffectChecker.java @@ -19,12 +19,40 @@ import com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PropertyUtil; +import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; +import java.util.*; + public class SideEffectChecker { + private static final Set ourSideEffectFreeClasses = new THashSet(Arrays.asList( + Object.class.getName(), + Short.class.getName(), + Character.class.getName(), + Byte.class.getName(), + Integer.class.getName(), + Long.class.getName(), + Float.class.getName(), + Double.class.getName(), + String.class.getName(), + StringBuffer.class.getName(), + Boolean.class.getName(), + + ArrayList.class.getName(), + Date.class.getName(), + HashMap.class.getName(), + HashSet.class.getName(), + Hashtable.class.getName(), + LinkedHashMap.class.getName(), + LinkedHashSet.class.getName(), + LinkedList.class.getName(), + Stack.class.getName(), + TreeMap.class.getName(), + TreeSet.class.getName(), + Vector.class.getName(), + WeakHashMap.class.getName())); private SideEffectChecker() { - super(); } public static boolean mayHaveSideEffects(@NotNull PsiExpression exp) { @@ -33,13 +61,22 @@ public class SideEffectChecker { return visitor.mayHaveSideEffects(); } - private static class SideEffectsVisitor extends JavaRecursiveElementWalkingVisitor { + public static boolean checkSideEffects(@NotNull PsiExpression element, @NotNull List sideEffects) { + final SideEffectsVisitor visitor = new SideEffectsVisitor(); + element.accept(visitor); + if (visitor.sideEffect != null) { + sideEffects.add(visitor.sideEffect); + return true; + } + return false; + } - private boolean mayHaveSideEffects = false; + private static class SideEffectsVisitor extends JavaRecursiveElementWalkingVisitor { + PsiElement sideEffect; @Override public void visitElement(@NotNull PsiElement element) { - if (!mayHaveSideEffects) { + if (sideEffect == null) { super.visitElement(element); } } @@ -47,17 +84,17 @@ public class SideEffectChecker { @Override public void visitAssignmentExpression( @NotNull PsiAssignmentExpression expression) { - if (mayHaveSideEffects) { + if (sideEffect != null) { return; } super.visitAssignmentExpression(expression); - mayHaveSideEffects = true; + sideEffect = expression; } @Override public void visitMethodCallExpression( @NotNull PsiMethodCallExpression expression) { - if (mayHaveSideEffects) { + if (sideEffect != null) { return; } super.visitMethodCallExpression(expression); @@ -66,48 +103,70 @@ public class SideEffectChecker { return; } - mayHaveSideEffects = true; + sideEffect = expression; } @Override public void visitNewExpression(@NotNull PsiNewExpression expression) { - if (mayHaveSideEffects) { + if (sideEffect != null) { return; } super.visitNewExpression(expression); - mayHaveSideEffects = true; + sideEffect = isSideEffectFreeConstructor(expression) ? null : expression; } @Override public void visitPostfixExpression( @NotNull PsiPostfixExpression expression) { - if (mayHaveSideEffects) { + if (sideEffect != null) { return; } super.visitPostfixExpression(expression); final IElementType tokenType = expression.getOperationTokenType(); if (tokenType.equals(JavaTokenType.PLUSPLUS) || tokenType.equals(JavaTokenType.MINUSMINUS)) { - mayHaveSideEffects = true; + sideEffect = expression; } } @Override public void visitPrefixExpression( @NotNull PsiPrefixExpression expression) { - if (mayHaveSideEffects) { + if (sideEffect != null) { return; } super.visitPrefixExpression(expression); final IElementType tokenType = expression.getOperationTokenType(); if (tokenType.equals(JavaTokenType.PLUSPLUS) || tokenType.equals(JavaTokenType.MINUSMINUS)) { - mayHaveSideEffects = true; + sideEffect = expression; } } public boolean mayHaveSideEffects() { - return mayHaveSideEffects; + return sideEffect != null; } } + + private static boolean isSideEffectFreeConstructor(@NotNull PsiNewExpression newExpression) { + PsiJavaCodeReferenceElement classReference = newExpression.getClassReference(); + PsiClass aClass = classReference == null ? null : (PsiClass)classReference.resolve(); + String qualifiedName = aClass == null ? null : aClass.getQualifiedName(); + if (qualifiedName == null) return false; + if (ourSideEffectFreeClasses.contains(qualifiedName)) return true; + + PsiFile file = aClass.getContainingFile(); + PsiDirectory directory = file.getContainingDirectory(); + PsiPackage classPackage = JavaDirectoryService.getInstance().getPackage(directory); + String packageName = classPackage == null ? null : classPackage.getQualifiedName(); + + // all Throwable descendants from java.lang are side effects free + if ("java.lang".equals(packageName) || "java.io".equals(packageName)) { + PsiClass throwableClass = JavaPsiFacade.getInstance(aClass.getProject()).findClass("java.lang.Throwable", aClass.getResolveScope()); + if (throwableClass != null && com.intellij.psi.util.InheritanceUtil.isInheritorOrSelf(aClass, throwableClass, true)) { + return true; + } + } + return false; + } }