blob: 5a2da7bb723f20781b30cb59734eef3341dd2d06 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Google, Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sergey Prigogin (Google) - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.ui.tests.reducer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.INodeFactory;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTConstructorChainInitializer;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateDeclaration;
import org.eclipse.cdt.core.formatter.DefaultCodeFormatterOptions;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.ui.refactoring.CTextFileChange;
import org.eclipse.cdt.internal.core.dom.parser.ASTNode;
import org.eclipse.cdt.internal.core.dom.parser.cpp.ICPPInternalBinding;
import org.eclipse.cdt.internal.core.dom.rewrite.util.ASTNodes;
import org.eclipse.cdt.internal.ui.refactoring.CRefactoring;
import org.eclipse.cdt.internal.ui.refactoring.ModificationCollector;
import org.eclipse.cdt.internal.ui.refactoring.changes.CCompositeChange;
import org.eclipse.cdt.internal.ui.refactoring.utils.SelectionHelper;
public class RemoveFunctionBodiesRefactoring extends CRefactoring {
private INodeFactory nodeFactory;
private final DefaultCodeFormatterOptions formattingOptions;
private IIndex index;
private IASTTranslationUnit ast;
private IRegion region;
public RemoveFunctionBodiesRefactoring(ICElement element, ISelection selection, ICProject project) {
super(element, selection, project);
name = Messages.RemoveFunctionBodiesRefactoring_RemoveFunctionBodies;
formattingOptions = new DefaultCodeFormatterOptions(project.getOptions(true));
}
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
SubMonitor progress = SubMonitor.convert(pm, 10);
RefactoringStatus status = super.checkInitialConditions(progress.newChild(8));
if (status.hasError()) {
return status;
}
ast = getAST(tu, progress.newChild(1));
index = getIndex();
nodeFactory = ast.getASTNodeFactory();
region = selectedRegion.getLength() == 0 ?
new Region(0, ast.getFileLocation().getNodeLength()) : selectedRegion;
if (isProgressMonitorCanceled(progress, initStatus))
return initStatus;
return initStatus;
}
private ICPPASTFunctionDeclarator getDeclaration(IASTNode node) {
while (node != null && !(node instanceof IASTFunctionDefinition)) {
node = node.getParent();
}
if (node != null) {
IASTFunctionDeclarator declarator = ((IASTFunctionDefinition) node).getDeclarator();
if (declarator instanceof ICPPASTFunctionDeclarator) {
return (ICPPASTFunctionDeclarator) declarator;
}
}
return null;
}
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext checkContext) {
return new RefactoringStatus();
}
@Override
public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
// This method bypasses the standard refactoring framework involving ModificationCollector and ASTRewrite since
// it is too slow for the gigantic changes this refactoring has to deal with.
FunctionDefinitionCollector finder = new FunctionDefinitionCollector();
ast.accept(finder);
String code = ast.getRawSignature();
CTextFileChange fileChange = new CTextFileChange(tu.getElementName(), tu);
fileChange.setEdit(new MultiTextEdit());
for (IASTFunctionDefinition definition : finder.functionDefinitions) {
if (!SelectionHelper.isNodeInsideRegion(definition, region))
continue;
IASTStatement body = definition.getBody();
IASTName name = definition.getDeclarator().getName();
IBinding binding = name.resolveBinding();
if (binding instanceof ICPPInternalBinding) {
IASTNode[] declarations = ((ICPPInternalBinding) binding).getDeclarations();
if (declarations != null && declarations.length != 0
&& ((ASTNode) declarations[0]).getOffset() < ((ASTNode) definition).getOffset()) {
IASTNode node = definition;
IASTNode parent;
while ((parent = node.getParent()) instanceof ICPPASTTemplateDeclaration) {
node = parent;
}
int offset = ASTNodes.offset(node);
int endOffset = ASTNodes.endOffset(node);
offset = skipWhitespaceBefore(offset, code);
// Remove the whole definition since the function is declared already.
fileChange.addEdit(new DeleteEdit(offset, endOffset - offset));
continue;
}
}
int offset = ASTNodes.offset(body);
int endOffset = ASTNodes.endOffset(body);
if (definition instanceof ICPPASTFunctionDefinition) {
ICPPASTConstructorChainInitializer[] initializers =
((ICPPASTFunctionDefinition) definition).getMemberInitializers();
if (initializers.length != 0) {
offset = ASTNodes.offset(initializers[0]);
offset = skipWhitespaceBefore(offset, code);
if (offset > 0 && code.charAt(offset - 1) == ':')
offset--;
}
}
offset = skipWhitespaceBefore(offset, code);
fileChange.addEdit(new ReplaceEdit(offset, endOffset - offset, ";"));
}
CCompositeChange change = new CCompositeChange(""); //$NON-NLS-1$
change.markAsSynthetic();
change.add(fileChange);
change.setDescription(new RefactoringChangeDescriptor(getRefactoringDescriptor()));
return change;
}
private static int skipWhitespaceBefore(int offset, String text) {
while (--offset >= 0) {
char c = text.charAt(offset);
if (!Character.isWhitespace(c))
break;
}
return offset + 1;
}
@Override
protected void collectModifications(IProgressMonitor pm, ModificationCollector collector)
throws CoreException, OperationCanceledException {
// This method is no-op for this refactoring. The change is created in the createChange method.
}
/**
* Finds function definitions that have bodies, are not constexpr, and don't contain problem bindings.
*/
private class FunctionDefinitionCollector extends ASTVisitor {
final List<IASTFunctionDefinition> functionDefinitions = new ArrayList<>();
final ProblemFinder problemFinder = new ProblemFinder();
FunctionDefinitionCollector() {
shouldVisitDeclarations = true;
}
@Override
public int visit(IASTDeclaration declaration) {
if (!declaration.isPartOfTranslationUnitFile())
return PROCESS_SKIP;
if (!(declaration instanceof IASTFunctionDefinition))
return PROCESS_CONTINUE;
IASTFunctionDefinition definition = (IASTFunctionDefinition) declaration;
if (definition.getBody() == null)
return PROCESS_SKIP;
IASTDeclSpecifier declSpec = definition.getDeclSpecifier();
if (declSpec instanceof ICPPASTDeclSpecifier && ((ICPPASTDeclSpecifier) declSpec).isConstexpr())
return PROCESS_SKIP;
if (problemFinder.containsProblemBinding(declaration))
return PROCESS_SKIP;
functionDefinitions.add(definition);
return PROCESS_SKIP;
}
}
@Override
protected RefactoringDescriptor getRefactoringDescriptor() {
return null;
}
}