blob: 5281a7afae28b01225029191a8e08176521cec73 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Red Hat Inc. - copied and modified for use in jdt.core.manipulation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.fix;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclarationStatement;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.manipulation.CleanUpOptionsCore;
import org.eclipse.jdt.core.manipulation.ICleanUpFixCore;
import org.eclipse.jdt.internal.core.manipulation.dom.NecessaryParenthesesChecker;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.LinkedNodeFinder;
import org.eclipse.jdt.internal.corext.dom.StatementRewrite;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.ui.fix.UnusedCodeCleanUpCore;
import org.eclipse.jdt.internal.ui.text.correction.IProblemLocationCore;
import org.eclipse.jdt.internal.ui.text.correction.JavadocTagsSubProcessorCore;
import org.eclipse.jdt.internal.ui.text.correction.ProblemLocationCore;
/**
* Fix which removes unused code.
*/
public class UnusedCodeFixCore extends CompilationUnitRewriteOperationsFixCore {
public static class SideEffectFinder extends ASTVisitor {
private final ArrayList<Expression> fSideEffectNodes;
public SideEffectFinder(ArrayList<Expression> res) {
fSideEffectNodes= res;
}
@Override
public boolean visit(Assignment node) {
fSideEffectNodes.add(node);
return false;
}
@Override
public boolean visit(PostfixExpression node) {
fSideEffectNodes.add(node);
return false;
}
@Override
public boolean visit(PrefixExpression node) {
Object operator= node.getOperator();
if (operator == PrefixExpression.Operator.INCREMENT || operator == PrefixExpression.Operator.DECREMENT) {
fSideEffectNodes.add(node);
return false;
}
return true;
}
@Override
public boolean visit(MethodInvocation node) {
fSideEffectNodes.add(node);
return false;
}
@Override
public boolean visit(ClassInstanceCreation node) {
fSideEffectNodes.add(node);
return false;
}
@Override
public boolean visit(SuperMethodInvocation node) {
fSideEffectNodes.add(node);
return false;
}
}
public static class RemoveImportOperation extends CompilationUnitRewriteOperation {
private final ImportDeclaration fImportDeclaration;
public RemoveImportOperation(ImportDeclaration importDeclaration) {
fImportDeclaration= importDeclaration;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
ImportDeclaration node= fImportDeclaration;
TextEditGroup group= createTextEditGroup(FixMessages.UnusedCodeFix_RemoveImport_description, cuRewrite);
cuRewrite.getASTRewrite().remove(node, group);
}
}
/**
* Removes the unused type parameter.
*
*/
public static class RemoveUnusedTypeParameterOperation extends CompilationUnitRewriteOperation {
private final SimpleName fUnusedName;
public RemoveUnusedTypeParameterOperation(SimpleName unusedName) {
fUnusedName= unusedName;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore linkedModel) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
IBinding binding= fUnusedName.resolveBinding();
CompilationUnit root= (CompilationUnit) fUnusedName.getRoot();
String displayString= FixMessages.UnusedCodeFix_RemoveUnusedTypeParameter_description;
TextEditGroup group= createTextEditGroup(displayString, cuRewrite);
if (binding.getKind() == IBinding.TYPE) {
ITypeBinding decl= ((ITypeBinding) binding).getTypeDeclaration();
ASTNode declaration= root.findDeclaringNode(decl);
if (declaration.getParent() instanceof TypeDeclarationStatement) {
declaration= declaration.getParent();
}
rewrite.remove(declaration, group);
}
}
}
public static class RemoveUnusedMemberOperation extends CompilationUnitRewriteOperation {
private final SimpleName[] fUnusedNames;
private boolean fForceRemove;
private int fRemovedAssignmentsCount;
private int fAlteredAssignmentsCount;
public RemoveUnusedMemberOperation(SimpleName[] unusedNames, boolean removeAllAsignements) {
fUnusedNames= unusedNames;
fForceRemove= removeAllAsignements;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
for (SimpleName unusedName : fUnusedNames) {
removeUnusedName(cuRewrite, unusedName);
}
}
private void removeUnusedName(CompilationUnitRewrite cuRewrite, SimpleName simpleName) {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
CompilationUnit completeRoot= cuRewrite.getRoot();
IBinding binding= simpleName.resolveBinding();
CompilationUnit root= (CompilationUnit) simpleName.getRoot();
String displayString= getDisplayString(binding);
TextEditGroup group= createTextEditGroup(displayString, cuRewrite);
if (binding.getKind() == IBinding.METHOD) {
IMethodBinding decl= ((IMethodBinding) binding).getMethodDeclaration();
ASTNode declaration= root.findDeclaringNode(decl);
rewrite.remove(declaration, group);
} else if (binding.getKind() == IBinding.TYPE) {
ITypeBinding decl= ((ITypeBinding) binding).getTypeDeclaration();
ASTNode declaration= root.findDeclaringNode(decl);
if (declaration.getParent() instanceof TypeDeclarationStatement) {
declaration= declaration.getParent();
}
rewrite.remove(declaration, group);
} else if (binding.getKind() == IBinding.VARIABLE) {
SimpleName nameNode= (SimpleName) NodeFinder.perform(completeRoot, simpleName.getStartPosition(), simpleName.getLength());
SimpleName[] references= LinkedNodeFinder.findByBinding(completeRoot, nameNode.resolveBinding());
for (int i= 0; i < references.length; i++) {
removeVariableReferences(rewrite, references[i], group);
}
IVariableBinding bindingDecl= ((IVariableBinding) nameNode.resolveBinding()).getVariableDeclaration();
ASTNode declaringNode= completeRoot.findDeclaringNode(bindingDecl);
if (declaringNode instanceof SingleVariableDeclaration) {
removeParamTag(rewrite, (SingleVariableDeclaration) declaringNode, group);
}
} else {
// unexpected
}
}
private String getDisplayString(IBinding binding) {
switch (binding.getKind()) {
case IBinding.TYPE:
return FixMessages.UnusedCodeFix_RemoveUnusedType_description;
case IBinding.METHOD:
if (((IMethodBinding) binding).isConstructor()) {
return FixMessages.UnusedCodeFix_RemoveUnusedConstructor_description;
} else {
return FixMessages.UnusedCodeFix_RemoveUnusedPrivateMethod_description;
}
case IBinding.VARIABLE:
if (((IVariableBinding) binding).isField()) {
return FixMessages.UnusedCodeFix_RemoveUnusedField_description;
} else {
return FixMessages.UnusedCodeFix_RemoveUnusedVariabl_description;
}
default:
return ""; //$NON-NLS-1$
}
}
private void removeParamTag(ASTRewrite rewrite, SingleVariableDeclaration varDecl, TextEditGroup group) {
if (varDecl.getParent() instanceof MethodDeclaration) {
Javadoc javadoc= ((MethodDeclaration) varDecl.getParent()).getJavadoc();
if (javadoc != null) {
TagElement tagElement= JavadocTagsSubProcessorCore.findParamTag(javadoc, varDecl.getName().getIdentifier());
if (tagElement != null) {
rewrite.remove(tagElement, group);
}
}
}
}
/**
* Remove the field or variable declaration including the initializer.
* @param rewrite the AST rewriter to use
* @param reference a reference to the variable to remove
* @param group the text edit group to use
*/
private void removeVariableReferences(ASTRewrite rewrite, SimpleName reference, TextEditGroup group) {
ASTNode parent= reference.getParent();
while (parent instanceof QualifiedName) {
parent= parent.getParent();
}
if (parent instanceof FieldAccess) {
parent= parent.getParent();
}
int nameParentType= parent.getNodeType();
if (nameParentType == ASTNode.ASSIGNMENT) {
Assignment assignment= (Assignment) parent;
Expression rightHand= assignment.getRightHandSide();
ASTNode assignParent= assignment.getParent();
if (assignParent.getNodeType() == ASTNode.EXPRESSION_STATEMENT && rightHand.getNodeType() != ASTNode.ASSIGNMENT) {
removeVariableWithInitializer(rewrite, rightHand, assignParent, group);
} else {
rewrite.replace(assignment, rewrite.createCopyTarget(rightHand), group);
}
} else if (nameParentType == ASTNode.SINGLE_VARIABLE_DECLARATION) {
rewrite.remove(parent, group);
} else if (nameParentType == ASTNode.VARIABLE_DECLARATION_FRAGMENT) {
VariableDeclarationFragment frag= (VariableDeclarationFragment) parent;
ASTNode varDecl= frag.getParent();
List<VariableDeclarationFragment> fragments;
if (varDecl instanceof VariableDeclarationExpression) {
fragments= ((VariableDeclarationExpression) varDecl).fragments();
} else if (varDecl instanceof FieldDeclaration) {
fragments= ((FieldDeclaration) varDecl).fragments();
} else {
fragments= ((VariableDeclarationStatement) varDecl).fragments();
}
Expression initializer= frag.getInitializer();
ArrayList<Expression> sideEffects= new ArrayList<>();
if (initializer != null) {
initializer.accept(new SideEffectFinder(sideEffects));
}
/*
* Special case for when the variable initializer is a conditional expression.
* Certain actions must be taken depending on where in the conditional the side effect expressions are located.
*/
if (initializer instanceof ConditionalExpression && varDecl instanceof VariableDeclarationStatement) {
AST ast= rewrite.getAST();
ConditionalExpression ce= (ConditionalExpression) initializer;
// check if side effects and both expressions are to be removed then we remove whole statement
if (fForceRemove || (!checkSideEffects(sideEffects) &&
!checkCondtionalExpression(ce.getThenExpression()) &&
!checkCondtionalExpression(ce.getElseExpression()))) {
rewrite.remove(varDecl, group);
return;
}
IfStatement ifStatement= ast.newIfStatement();
ifStatement.setExpression((Expression) rewrite.createCopyTarget(getExpressionWithoutParenthezis(ce.getExpression())));
Block thenBlock= ast.newBlock();
// check if 'then' block contains code to keep
if (checkCondtionalExpression(ce.getThenExpression())) {
ASTNode thenExpression= rewrite.createCopyTarget(getExpressionWithoutParenthezis(ce.getThenExpression()));
thenBlock.statements().add(ast.newExpressionStatement((Expression) thenExpression));
}
ifStatement.setThenStatement(thenBlock);
// check if 'else' block contains code to keep
if (checkCondtionalExpression(ce.getElseExpression())) {
Block elseBlock= ast.newBlock();
ASTNode elseExpression= rewrite.createCopyTarget(getExpressionWithoutParenthezis(ce.getElseExpression()));
elseBlock.statements().add(ast.newExpressionStatement((Expression) elseExpression));
ifStatement.setElseStatement(elseBlock);
}
rewrite.replace(varDecl, ifStatement, group);
return;
}
boolean sideEffectInitializer= sideEffects.size() > 0;
if (fragments.size() == fUnusedNames.length) {
if (fForceRemove) {
rewrite.remove(varDecl, group);
return;
}
if (parent.getParent() instanceof FieldDeclaration) {
rewrite.remove(varDecl, group);
return;
}
if (sideEffectInitializer) {
Statement[] wrapped= new Statement[sideEffects.size()];
for (int i= 0; i < wrapped.length; i++) {
Expression sideEffect= sideEffects.get(i);
Expression movedInit= (Expression) rewrite.createMoveTarget(sideEffect);
wrapped[i]= rewrite.getAST().newExpressionStatement(movedInit);
}
StatementRewrite statementRewrite= new StatementRewrite(rewrite, new ASTNode[] { varDecl });
statementRewrite.replace(wrapped, group);
} else {
rewrite.remove(varDecl, group);
}
} else {
if (fForceRemove) {
rewrite.remove(frag, group);
return;
}
//multiple declarations in one line
ASTNode declaration = parent.getParent();
if (declaration instanceof FieldDeclaration) {
rewrite.remove(frag, group);
return;
}
if (declaration instanceof VariableDeclarationStatement) {
splitUpDeclarations(rewrite, group, frag, (VariableDeclarationStatement) declaration, sideEffects);
rewrite.remove(frag, group);
return;
}
if (declaration instanceof VariableDeclarationExpression) {
//keep constructors and method invocations
if (!sideEffectInitializer){
rewrite.remove(frag, group);
}
}
}
} else if (nameParentType == ASTNode.POSTFIX_EXPRESSION || nameParentType == ASTNode.PREFIX_EXPRESSION) {
Expression expression= (Expression)parent;
ASTNode expressionParent= expression.getParent();
if (expressionParent.getNodeType() == ASTNode.EXPRESSION_STATEMENT) {
removeStatement(rewrite, expressionParent, group);
} else {
rewrite.remove(expression, group);
}
}
}
private static Expression getExpressionWithoutParenthezis(Expression expression) {
Expression currExpression= expression;
while (currExpression instanceof ParenthesizedExpression) {
currExpression= ((ParenthesizedExpression) currExpression).getExpression();
}
return currExpression;
}
/*
* Return TRUE if the expression node type is a method, pre/posfix or assignment
*/
private static boolean checkCondtionalExpression(Expression expression) {
int nodeType= getExpressionWithoutParenthezis(expression).getNodeType();
if (nodeType == ASTNode.METHOD_INVOCATION ||
nodeType == ASTNode.POSTFIX_EXPRESSION ||
nodeType == ASTNode.PREFIX_EXPRESSION ||
nodeType == ASTNode.ASSIGNMENT) {
return true;
}
return false;
}
/*
* Return TRUE if any of the sideEffects expression node type is a method, pre/posfix or assignment
*/
private static boolean checkSideEffects(List<Expression> sideEffects) {
if (sideEffects.isEmpty()) {
return false;
}
for (Expression expression : sideEffects) {
if (checkCondtionalExpression(expression)) {
return true;
}
}
return false;
}
private void splitUpDeclarations(ASTRewrite rewrite, TextEditGroup group, VariableDeclarationFragment frag, VariableDeclarationStatement originalStatement, List<Expression> sideEffects) {
if (sideEffects.size() > 0) {
ListRewrite statementRewrite= rewrite.getListRewrite(originalStatement.getParent(), (ChildListPropertyDescriptor) originalStatement.getLocationInParent());
Statement previousStatement= originalStatement;
for (Expression sideEffect : sideEffects) {
Expression movedInit= (Expression) rewrite.createMoveTarget(sideEffect);
ExpressionStatement wrapped= rewrite.getAST().newExpressionStatement(movedInit);
statementRewrite.insertAfter(wrapped, previousStatement, group);
previousStatement= wrapped;
}
VariableDeclarationStatement newDeclaration= null;
List<VariableDeclarationFragment> fragments= originalStatement.fragments();
int fragIndex= fragments.indexOf(frag);
ListIterator<VariableDeclarationFragment> fragmentIterator= fragments.listIterator(fragIndex+1);
while (fragmentIterator.hasNext()) {
VariableDeclarationFragment currentFragment= fragmentIterator.next();
VariableDeclarationFragment movedFragment= (VariableDeclarationFragment) rewrite.createMoveTarget(currentFragment);
if (newDeclaration == null) {
newDeclaration= rewrite.getAST().newVariableDeclarationStatement(movedFragment);
Type copiedType= (Type) rewrite.createCopyTarget(originalStatement.getType());
newDeclaration.setType(copiedType);
} else {
newDeclaration.fragments().add(movedFragment);
}
}
if (newDeclaration != null){
statementRewrite.insertAfter(newDeclaration, previousStatement, group);
if (originalStatement.fragments().size() == newDeclaration.fragments().size() + 1){
rewrite.remove(originalStatement, group);
}
}
}
}
private void removeVariableWithInitializer(ASTRewrite rewrite, ASTNode initializerNode, ASTNode statementNode, TextEditGroup group) {
boolean performRemove= fForceRemove;
if (!performRemove) {
ArrayList<Expression> sideEffectNodes= new ArrayList<>();
initializerNode.accept(new SideEffectFinder(sideEffectNodes));
performRemove= sideEffectNodes.isEmpty();
}
if (performRemove) {
removeStatement(rewrite, statementNode, group);
fRemovedAssignmentsCount++;
} else {
ASTNode initNode = rewrite.createMoveTarget(initializerNode);
ExpressionStatement statement = rewrite.getAST().newExpressionStatement((Expression) initNode);
rewrite.replace(statementNode, statement, null);
fAlteredAssignmentsCount++;
}
}
private void removeStatement(ASTRewrite rewrite, ASTNode statementNode, TextEditGroup group) {
if (ASTNodes.isControlStatementBody(statementNode.getLocationInParent())) {
rewrite.replace(statementNode, rewrite.getAST().newBlock(), group);
} else {
rewrite.remove(statementNode, group);
}
}
@Override
public String getAdditionalInfo() {
StringBuilder sb=new StringBuilder();
if (fRemovedAssignmentsCount == 1) {
sb.append(FixMessages.UnusedCodeFix_RemoveFieldOrLocal_RemovedAssignments_preview_singular);
} else if (fRemovedAssignmentsCount > 1) {
sb.append(Messages.format(FixMessages.UnusedCodeFix_RemoveFieldOrLocal_RemovedAssignments_preview_plural, String.valueOf(fRemovedAssignmentsCount)));
}
if (fAlteredAssignmentsCount == 1) {
sb.append(FixMessages.UnusedCodeFix_RemoveFieldOrLocal_AlteredAssignments_preview_singular);
} else if (fAlteredAssignmentsCount > 1) {
sb.append(Messages.format(FixMessages.UnusedCodeFix_RemoveFieldOrLocal_AlteredAssignments_preview_plural, String.valueOf(fAlteredAssignmentsCount)));
}
if (sb.length()>0) {
return sb.toString();
} else
return null;
}
}
public static class RemoveCastOperation extends CompilationUnitRewriteOperation {
private final CastExpression fCast;
public RemoveCastOperation(CastExpression cast) {
fCast= cast;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
TextEditGroup group= createTextEditGroup(FixMessages.UnusedCodeFix_RemoveCast_description, cuRewrite);
ASTRewrite rewrite= cuRewrite.getASTRewrite();
CastExpression cast= fCast;
Expression expression= cast.getExpression();
if (expression instanceof ParenthesizedExpression) {
Expression childExpression= ((ParenthesizedExpression) expression).getExpression();
if (NecessaryParenthesesChecker.needsParentheses(childExpression, cast, CastExpression.EXPRESSION_PROPERTY)) {
expression= childExpression;
}
}
replaceCast(cast, expression, rewrite, group);
}
}
public static class RemoveAllCastOperation extends CompilationUnitRewriteOperation {
private final LinkedHashSet<CastExpression> fUnnecessaryCasts;
public RemoveAllCastOperation(LinkedHashSet<CastExpression> unnecessaryCasts) {
fUnnecessaryCasts= unnecessaryCasts;
}
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore model) throws CoreException {
ASTRewrite rewrite= cuRewrite.getASTRewrite();
TextEditGroup group= createTextEditGroup(FixMessages.UnusedCodeFix_RemoveCast_description, cuRewrite);
while (fUnnecessaryCasts.size() > 0) {
CastExpression castExpression= fUnnecessaryCasts.iterator().next();
fUnnecessaryCasts.remove(castExpression);
/*
* ASTRewrite doesn't allow replacing (deleting) of moved nodes. To solve problems
* with nested casts, we need to replace all casts at once.
*
* The loop proceeds downwards to find the innermost expression that stays in the result (downChild)
* and it also skips necessary parentheses.
*/
CastExpression down= castExpression;
Expression downChild= down.getExpression();
while (true) {
if (fUnnecessaryCasts.contains(downChild)) {
down= (CastExpression) downChild;
fUnnecessaryCasts.remove(down);
downChild= down.getExpression();
} else if (downChild instanceof ParenthesizedExpression) {
Expression downChildExpression= ((ParenthesizedExpression) downChild).getExpression();
// is it justified that downChild is a ParenthesizedExpression?
if (NecessaryParenthesesChecker.needsParentheses(downChildExpression, down, CastExpression.EXPRESSION_PROPERTY)) {
// yes => continue walking down
downChild= downChildExpression;
} else {
// no => stop walking
break;
}
} else {
break;
}
}
// downChild is the innermost CastExpression's expression, stripped of a necessary surrounding ParenthesizedExpression
// Move either downChild (if it doesn't need parentheses), or a parenthesized version if necessary
replaceCast(castExpression, downChild, rewrite, group);
}
}
}
public static UnusedCodeFixCore createRemoveUnusedImportFix(CompilationUnit compilationUnit, IProblemLocationCore problem) {
if (isUnusedImport(problem)) {
ImportDeclaration node= getImportDeclaration(problem, compilationUnit);
if (node != null) {
String label= FixMessages.UnusedCodeFix_RemoveImport_description;
RemoveImportOperation operation= new RemoveImportOperation(node);
Map<String, String> options= new Hashtable<>();
options.put(CleanUpConstants.REMOVE_UNUSED_CODE_IMPORTS, CleanUpOptionsCore.TRUE);
return new UnusedCodeFixCore(label, compilationUnit, new CompilationUnitRewriteOperation[] {operation}, options);
}
}
return null;
}
public static boolean isUnusedImport(IProblemLocationCore problem) {
int id= problem.getProblemId();
return id == IProblem.UnusedImport || id == IProblem.DuplicateImport || id == IProblem.ConflictingImport || id == IProblem.CannotImportPackage || id == IProblem.ImportNotFound;
}
public static UnusedCodeFixCore createUnusedMemberFix(CompilationUnit compilationUnit, IProblemLocationCore problem, boolean removeAllAssignements) {
if (isUnusedMember(problem)) {
SimpleName name= getUnusedName(compilationUnit, problem);
if (name != null) {
IBinding binding= name.resolveBinding();
if (binding != null) {
if (isFormalParameterInEnhancedForStatement(name))
return null;
String label= getDisplayString(name, binding, removeAllAssignements);
RemoveUnusedMemberOperation operation= new RemoveUnusedMemberOperation(new SimpleName[] { name }, removeAllAssignements);
return new UnusedCodeFixCore(label, compilationUnit, new CompilationUnitRewriteOperation[] { operation }, getCleanUpOptions(binding, removeAllAssignements));
}
}
}
return null;
}
public static UnusedCodeFixCore createUnusedTypeParameterFix(CompilationUnit compilationUnit, IProblemLocationCore problemLoc) {
if (problemLoc.getProblemId() == IProblem.UnusedTypeParameter) {
SimpleName name= getUnusedName(compilationUnit, problemLoc);
if (name != null) {
IBinding binding= name.resolveBinding();
if (binding != null) {
String label= FixMessages.UnusedCodeFix_RemoveUnusedTypeParameter_description;
RemoveUnusedTypeParameterOperation operation= new RemoveUnusedTypeParameterOperation(name);
return new UnusedCodeFixCore(label, compilationUnit, new CompilationUnitRewriteOperation[] { operation }, getCleanUpOptions(binding, false));
}
}
}
return null;
}
public static boolean isUnusedMember(IProblemLocationCore problem) {
int id= problem.getProblemId();
return id == IProblem.UnusedPrivateMethod || id == IProblem.UnusedPrivateConstructor || id == IProblem.UnusedPrivateField || id == IProblem.UnusedPrivateType
|| id == IProblem.LocalVariableIsNeverUsed || id == IProblem.ArgumentIsNeverUsed;
}
public static UnusedCodeFixCore createRemoveUnusedCastFix(CompilationUnit compilationUnit, IProblemLocationCore problem) {
if (problem.getProblemId() != IProblem.UnnecessaryCast)
return null;
ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
ASTNode curr= selectedNode;
while (curr instanceof ParenthesizedExpression) {
curr= ((ParenthesizedExpression) curr).getExpression();
}
if (!(curr instanceof CastExpression))
return null;
return new UnusedCodeFixCore(FixMessages.UnusedCodeFix_RemoveCast_description, compilationUnit, new CompilationUnitRewriteOperation[] {new RemoveCastOperation((CastExpression)curr)});
}
public static ICleanUpFixCore createCleanUp(CompilationUnit compilationUnit,
boolean removeUnusedPrivateMethods,
boolean removeUnusedPrivateConstructors,
boolean removeUnusedPrivateFields,
boolean removeUnusedPrivateTypes,
boolean removeUnusedLocalVariables,
boolean removeUnusedImports,
boolean removeUnusedCast) {
IProblem[] problems= compilationUnit.getProblems();
IProblemLocationCore[] locations= new IProblemLocationCore[problems.length];
for (int i= 0; i < problems.length; i++) {
locations[i]= new ProblemLocationCore(problems[i]);
}
return createCleanUp(compilationUnit, locations,
removeUnusedPrivateMethods,
removeUnusedPrivateConstructors,
removeUnusedPrivateFields,
removeUnusedPrivateTypes,
removeUnusedLocalVariables,
removeUnusedImports,
removeUnusedCast);
}
public static ICleanUpFixCore createCleanUp(CompilationUnit compilationUnit, IProblemLocationCore[] problems,
boolean removeUnusedPrivateMethods,
boolean removeUnusedPrivateConstructors,
boolean removeUnusedPrivateFields,
boolean removeUnusedPrivateTypes,
boolean removeUnusedLocalVariables,
boolean removeUnusedImports,
boolean removeUnusedCast) {
List<CompilationUnitRewriteOperation> result= new ArrayList<>();
Hashtable<ASTNode, List<SimpleName>> variableDeclarations= new Hashtable<>();
LinkedHashSet<CastExpression> unnecessaryCasts= new LinkedHashSet<>();
for (IProblemLocationCore problem : problems) {
int id= problem.getProblemId();
if (removeUnusedImports && (id == IProblem.UnusedImport || id == IProblem.DuplicateImport || id == IProblem.ConflictingImport ||
id == IProblem.CannotImportPackage || id == IProblem.ImportNotFound))
{
ImportDeclaration node= UnusedCodeFixCore.getImportDeclaration(problem, compilationUnit);
if (node != null) {
result.add(new RemoveImportOperation(node));
}
}
if ((removeUnusedPrivateMethods && id == IProblem.UnusedPrivateMethod) || (removeUnusedPrivateConstructors && id == IProblem.UnusedPrivateConstructor) ||
(removeUnusedPrivateTypes && id == IProblem.UnusedPrivateType)) {
SimpleName name= getUnusedName(compilationUnit, problem);
if (name != null) {
IBinding binding= name.resolveBinding();
if (binding != null) {
result.add(new RemoveUnusedMemberOperation(new SimpleName[] {name}, false));
}
}
}
if ((removeUnusedLocalVariables && id == IProblem.LocalVariableIsNeverUsed) || (removeUnusedPrivateFields && id == IProblem.UnusedPrivateField)) {
SimpleName name= getUnusedName(compilationUnit, problem);
if (name != null) {
IBinding binding= name.resolveBinding();
if (binding instanceof IVariableBinding && !isFormalParameterInEnhancedForStatement(name) && (!((IVariableBinding) binding).isField() || isSideEffectFree(name, compilationUnit))) {
VariableDeclarationFragment parent= ASTNodes.getParent(name, VariableDeclarationFragment.class);
if (parent != null) {
ASTNode varDecl= parent.getParent();
if (!variableDeclarations.containsKey(varDecl)) {
variableDeclarations.put(varDecl, new ArrayList<SimpleName>());
}
variableDeclarations.get(varDecl).add(name);
} else {
result.add(new RemoveUnusedMemberOperation(new SimpleName[] {name}, false));
}
}
}
}
if (removeUnusedCast && id == IProblem.UnnecessaryCast) {
ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
ASTNode curr= selectedNode;
while (curr instanceof ParenthesizedExpression) {
curr= ((ParenthesizedExpression) curr).getExpression();
}
if (curr instanceof CastExpression) {
unnecessaryCasts.add((CastExpression) curr);
}
}
}
for (ASTNode node : variableDeclarations.keySet()) {
List<SimpleName> names= variableDeclarations.get(node);
result.add(new RemoveUnusedMemberOperation(names.toArray(new SimpleName[0]), false));
}
if (unnecessaryCasts.size() > 0)
result.add(new RemoveAllCastOperation(unnecessaryCasts));
if (result.isEmpty())
return null;
return new UnusedCodeFixCore(FixMessages.UnusedCodeFix_change_name, compilationUnit, result.toArray(new CompilationUnitRewriteOperation[result.size()]));
}
public static boolean isFormalParameterInEnhancedForStatement(SimpleName name) {
return name.getParent() instanceof SingleVariableDeclaration && name.getParent().getLocationInParent() == EnhancedForStatement.PARAMETER_PROPERTY;
}
public static boolean isSideEffectFree(SimpleName simpleName, CompilationUnit completeRoot) {
SimpleName nameNode= (SimpleName) NodeFinder.perform(completeRoot, simpleName.getStartPosition(), simpleName.getLength());
SimpleName[] references= LinkedNodeFinder.findByBinding(completeRoot, nameNode.resolveBinding());
for (SimpleName reference : references) {
if (hasSideEffect(reference))
return false;
}
return true;
}
private static boolean hasSideEffect(SimpleName reference) {
ASTNode parent= reference.getParent();
while (parent instanceof QualifiedName) {
parent= parent.getParent();
}
if (parent instanceof FieldAccess) {
parent= parent.getParent();
}
ASTNode node= null;
int nameParentType= parent.getNodeType();
if (nameParentType == ASTNode.ASSIGNMENT) {
Assignment assignment= (Assignment) parent;
node= assignment.getRightHandSide();
} else if (nameParentType == ASTNode.SINGLE_VARIABLE_DECLARATION) {
SingleVariableDeclaration decl= (SingleVariableDeclaration)parent;
node= decl.getInitializer();
if (node == null)
return false;
} else if (nameParentType == ASTNode.VARIABLE_DECLARATION_FRAGMENT) {
node= parent;
} else {
return false;
}
ArrayList<Expression> sideEffects= new ArrayList<>();
node.accept(new SideEffectFinder(sideEffects));
return sideEffects.size() > 0;
}
public static SimpleName getUnusedName(CompilationUnit compilationUnit, IProblemLocationCore problem) {
ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
if (selectedNode instanceof MethodDeclaration) {
return ((MethodDeclaration) selectedNode).getName();
} else if (selectedNode instanceof SimpleName) {
return (SimpleName) selectedNode;
}
return null;
}
public static String getDisplayString(SimpleName simpleName, IBinding binding, boolean removeAllAssignements) {
String name= BasicElementLabels.getJavaElementName(simpleName.getIdentifier());
switch (binding.getKind()) {
case IBinding.TYPE:
return Messages.format(FixMessages.UnusedCodeFix_RemoveType_description, name);
case IBinding.METHOD:
if (((IMethodBinding) binding).isConstructor()) {
return Messages.format(FixMessages.UnusedCodeFix_RemoveConstructor_description, name);
} else {
return Messages.format(FixMessages.UnusedCodeFix_RemoveMethod_description, name);
}
case IBinding.VARIABLE:
if (removeAllAssignements) {
return Messages.format(FixMessages.UnusedCodeFix_RemoveFieldOrLocalWithInitializer_description, name);
} else {
return Messages.format(FixMessages.UnusedCodeFix_RemoveFieldOrLocal_description, name);
}
default:
return ""; //$NON-NLS-1$
}
}
public static Map<String, String> getCleanUpOptions(IBinding binding, boolean removeAll) {
Map<String, String> result= new Hashtable<>();
result.put(CleanUpConstants.REMOVE_UNUSED_CODE_PRIVATE_MEMBERS, CleanUpOptionsCore.TRUE);
switch (binding.getKind()) {
case IBinding.TYPE:
result.put(CleanUpConstants.REMOVE_UNUSED_CODE_PRIVATE_TYPES, CleanUpOptionsCore.TRUE);
break;
case IBinding.METHOD:
if (((IMethodBinding) binding).isConstructor()) {
result.put(CleanUpConstants.REMOVE_UNUSED_CODE_PRIVATE_CONSTRUCTORS, CleanUpOptionsCore.TRUE);
} else {
result.put(CleanUpConstants.REMOVE_UNUSED_CODE_PRIVATE_METHODS, CleanUpOptionsCore.TRUE);
}
break;
case IBinding.VARIABLE:
if (removeAll)
return null;
result.put(CleanUpConstants.REMOVE_UNUSED_CODE_PRIVATE_FELDS, CleanUpOptionsCore.TRUE);
result.put(CleanUpConstants.REMOVE_UNUSED_CODE_LOCAL_VARIABLES, CleanUpOptionsCore.TRUE);
break;
default:
break;
}
return result;
}
public static ImportDeclaration getImportDeclaration(IProblemLocationCore problem, CompilationUnit compilationUnit) {
ASTNode selectedNode= problem.getCoveringNode(compilationUnit);
if (selectedNode != null) {
ASTNode node= ASTNodes.getParent(selectedNode, ASTNode.IMPORT_DECLARATION);
if (node instanceof ImportDeclaration) {
return (ImportDeclaration)node;
}
}
return null;
}
private static void replaceCast(CastExpression castExpression, Expression replacement, ASTRewrite rewrite, TextEditGroup group) {
boolean castEnclosedInNecessaryParentheses= castExpression.getParent() instanceof ParenthesizedExpression
&& NecessaryParenthesesChecker.needsParentheses(castExpression, castExpression.getParent().getParent(), castExpression.getParent().getLocationInParent());
ASTNode toReplace= castEnclosedInNecessaryParentheses ? castExpression.getParent() : castExpression;
ASTNode move;
if (NecessaryParenthesesChecker.needsParentheses(replacement, toReplace.getParent(), toReplace.getLocationInParent())) {
if (replacement.getParent() instanceof ParenthesizedExpression) {
move= rewrite.createMoveTarget(replacement.getParent());
} else if (castEnclosedInNecessaryParentheses) {
toReplace= castExpression;
move= rewrite.createMoveTarget(replacement);
} else {
ParenthesizedExpression parentheses= replacement.getAST().newParenthesizedExpression();
parentheses.setExpression((Expression) rewrite.createMoveTarget(replacement));
move= parentheses;
}
} else {
move= rewrite.createMoveTarget(replacement);
}
rewrite.replace(toReplace, move, group);
}
private final Map<String, String> fCleanUpOptions;
private UnusedCodeFixCore(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] fixRewriteOperations) {
this(name, compilationUnit, fixRewriteOperations, null);
}
private UnusedCodeFixCore(String name, CompilationUnit compilationUnit, CompilationUnitRewriteOperation[] fixRewriteOperations, Map<String, String> options) {
super(name, compilationUnit, fixRewriteOperations);
fCleanUpOptions= options;
}
public UnusedCodeCleanUpCore getCleanUp() {
if (fCleanUpOptions == null)
return null;
return new UnusedCodeCleanUpCore(fCleanUpOptions);
}
}