blob: b3d9684e5f83f79e1720e109fd343208c00e16fa [file] [log] [blame]
/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.services.internal.refactoring;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.dart.engine.ast.AdjacentStrings;
import com.google.dart.engine.ast.AssignmentExpression;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.BinaryExpression;
import com.google.dart.engine.ast.Block;
import com.google.dart.engine.ast.BlockFunctionBody;
import com.google.dart.engine.ast.BooleanLiteral;
import com.google.dart.engine.ast.ClassDeclaration;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.DoubleLiteral;
import com.google.dart.engine.ast.Expression;
import com.google.dart.engine.ast.ExpressionFunctionBody;
import com.google.dart.engine.ast.FormalParameterList;
import com.google.dart.engine.ast.FunctionBody;
import com.google.dart.engine.ast.FunctionDeclaration;
import com.google.dart.engine.ast.IntegerLiteral;
import com.google.dart.engine.ast.MethodDeclaration;
import com.google.dart.engine.ast.MethodInvocation;
import com.google.dart.engine.ast.NamedExpression;
import com.google.dart.engine.ast.NullLiteral;
import com.google.dart.engine.ast.PrefixedIdentifier;
import com.google.dart.engine.ast.PropertyAccess;
import com.google.dart.engine.ast.ReturnStatement;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.SimpleStringLiteral;
import com.google.dart.engine.ast.Statement;
import com.google.dart.engine.ast.SymbolLiteral;
import com.google.dart.engine.ast.ThisExpression;
import com.google.dart.engine.ast.VariableDeclaration;
import com.google.dart.engine.ast.VariableDeclarationList;
import com.google.dart.engine.ast.VariableDeclarationStatement;
import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor;
import com.google.dart.engine.ast.visitor.RecursiveAstVisitor;
import com.google.dart.engine.element.ClassElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.ExecutableElement;
import com.google.dart.engine.element.FieldElement;
import com.google.dart.engine.element.FunctionElement;
import com.google.dart.engine.element.LocalElement;
import com.google.dart.engine.element.MethodElement;
import com.google.dart.engine.element.ParameterElement;
import com.google.dart.engine.element.PropertyAccessorElement;
import com.google.dart.engine.element.VariableElement;
import com.google.dart.engine.element.visitor.GeneralizingElementVisitor;
import com.google.dart.engine.search.SearchMatch;
import com.google.dart.engine.services.assist.AssistContext;
import com.google.dart.engine.services.change.Change;
import com.google.dart.engine.services.change.CompositeChange;
import com.google.dart.engine.services.change.Edit;
import com.google.dart.engine.services.change.MergeCompositeChange;
import com.google.dart.engine.services.change.SourceChange;
import com.google.dart.engine.services.change.SourceChangeManager;
import com.google.dart.engine.services.internal.correction.CorrectionUtils;
import com.google.dart.engine.services.refactoring.InlineMethodRefactoring;
import com.google.dart.engine.services.refactoring.ProgressMonitor;
import com.google.dart.engine.services.status.RefactoringStatus;
import com.google.dart.engine.services.status.RefactoringStatusContext;
import com.google.dart.engine.services.util.HierarchyUtils;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.utilities.source.SourceRange;
import static com.google.dart.engine.services.internal.correction.CorrectionUtils.getExpressionParentPrecedence;
import static com.google.dart.engine.services.internal.correction.CorrectionUtils.getExpressionPrecedence;
import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeFromBase;
import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeNode;
import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeStartEnd;
import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeStartLength;
import static com.google.dart.engine.utilities.source.SourceRangeFactory.rangeStartStart;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* Implementation of {@link InlineMethodRefactoring}.
*/
public class InlineMethodRefactoringImpl extends RefactoringImpl implements InlineMethodRefactoring {
private static class ParameterOccurrence {
final int parentPrecedence;
final SourceRange range;
public ParameterOccurrence(int parentPrecedence, SourceRange range) {
this.parentPrecedence = parentPrecedence;
this.range = range;
}
}
/**
* Processor for single {@link SearchMatch} reference to {@link #methodElement}.
*/
private class ReferenceProcessor {
private final Source refSource;
private final CorrectionUtils refUtils;
private final AstNode node;
private final SourceRange refLineRange;
private final String refPrefix;
private boolean argsHaveSideEffect;
ReferenceProcessor(SearchMatch reference) throws Exception {
// prepare SourceChange to update
Element refElement = reference.getElement();
refSource = refElement.getSource();
// prepare CorrectionUtils
CompilationUnit refUnit = refElement.getUnit();
refUtils = new CorrectionUtils(refUnit);
// prepare node and environment
node = refUtils.findNode(reference.getSourceRange().getOffset());
Statement refStatement = node.getAncestor(Statement.class);
if (refStatement != null) {
refLineRange = refUtils.getLinesRange(ImmutableList.of(refStatement));
refPrefix = refUtils.getNodePrefix(refStatement);
} else {
refLineRange = null;
refPrefix = refUtils.getLinePrefix(node.getOffset());
}
}
public void updateRequiresPreviewFlag() {
if (!shouldProcess()) {
return;
}
requiresPreview |= argsHaveSideEffect;
}
void checkForSideEffects() throws Exception {
AstNode nodeParent = node.getParent();
// may be invocation of inline method
if (nodeParent instanceof MethodInvocation) {
MethodInvocation invocation = (MethodInvocation) nodeParent;
List<Expression> arguments = invocation.getArgumentList().getArguments();
argsHaveSideEffect = hasSideEffect(arguments);
} else {
if (methodElement instanceof PropertyAccessorElement) {
// prepare arguments
List<Expression> arguments = Lists.newArrayList();
if (((SimpleIdentifier) node).inSetterContext()) {
arguments.add(((AssignmentExpression) nodeParent.getParent()).getRightHandSide());
}
// check arguments
argsHaveSideEffect = hasSideEffect(arguments);
}
}
}
void process(RefactoringStatus status) throws Exception {
AstNode nodeParent = node.getParent();
// may be only single place should be inlined
if (!shouldProcess()) {
return;
}
// may be invocation of inline method
if (nodeParent instanceof MethodInvocation) {
MethodInvocation invocation = (MethodInvocation) nodeParent;
Expression target = invocation.getTarget();
List<Expression> arguments = invocation.getArgumentList().getArguments();
inlineMethodInvocation(status, invocation, invocation.isCascaded(), target, arguments);
} else {
// cannot inline reference to method: var v = new A().method;
if (methodElement instanceof MethodElement) {
status.addFatalError(
"Cannot inline class method reference.",
new RefactoringStatusContext(node));
return;
}
// PropertyAccessorElement
if (methodElement instanceof PropertyAccessorElement) {
Expression target = null;
boolean cascade = false;
// TODO(scheglov) hopefully at some point in future we will have only PropertyAccess
if (nodeParent instanceof PrefixedIdentifier) {
PrefixedIdentifier propertyAccess = (PrefixedIdentifier) nodeParent;
target = propertyAccess.getPrefix();
cascade = false;
}
if (nodeParent instanceof PropertyAccess) {
PropertyAccess propertyAccess = (PropertyAccess) nodeParent;
target = propertyAccess.getRealTarget();
cascade = propertyAccess.isCascaded();
}
// prepare arguments
List<Expression> arguments = Lists.newArrayList();
if (((SimpleIdentifier) node).inSetterContext()) {
arguments.add(((AssignmentExpression) nodeParent.getParent()).getRightHandSide());
}
// inline body
inlineMethodInvocation(status, (Expression) nodeParent, cascade, target, arguments);
return;
}
// not invocation, just reference to function
String source;
{
source = methodUtils.getText(rangeStartEnd(
methodParameters.getLeftParenthesis(),
methodNode));
String methodPrefix = methodUtils.getLinePrefix(methodNode.getOffset());
source = refUtils.getIndentSource(source, methodPrefix, refPrefix);
source = source.trim();
}
// do insert
SourceChange refChange = safeManager.get(refSource);
SourceRange range = rangeNode(node);
Edit edit = new Edit(range, source);
refChange.addEdit(edit, "Replace all references to method with statements");
}
}
private boolean canInlineBody(AstNode usage) {
// no statements, usually just expression
if (methodStatementsPart == null) {
// empty method, inline as closure
if (methodExpressionPart == null) {
return false;
}
// OK, just expression
return true;
}
// analyze point of invocation
AstNode parent = usage.getParent();
AstNode parent2 = parent.getParent();
// OK, if statement in block
if (parent instanceof Statement) {
return parent2 instanceof Block;
}
// may be assignment, in block
if (parent instanceof AssignmentExpression) {
AssignmentExpression assignment = (AssignmentExpression) parent;
// inlining setter
if (assignment.getLeftHandSide() == usage) {
return parent2 instanceof Statement && parent2.getParent() instanceof Block;
}
// inlining initializer
return methodExpressionPart != null;
}
// may be value for variable initializer, in block
if (methodExpressionPart != null) {
if (parent instanceof VariableDeclaration) {
if (parent2 instanceof VariableDeclarationList) {
AstNode parent3 = parent2.getParent();
return parent3 instanceof VariableDeclarationStatement
&& parent3.getParent() instanceof Block;
}
}
}
// not in block, cannot inline body
return false;
}
private void inlineMethodInvocation(RefactoringStatus status, Expression methodUsage,
boolean cascaded, Expression target, List<Expression> arguments) {
// prepare change
SourceChange refChange;
if (argsHaveSideEffect) {
refChange = previewManager.get(refSource);
} else {
refChange = safeManager.get(refSource);
}
// we don't support cascade
if (cascaded) {
status.addError("Cannot inline cascade invocation.", new RefactoringStatusContext(
methodUsage));
}
// can we inline method body into "methodUsage" block?
if (canInlineBody(methodUsage)) {
// insert non-return statements
if (methodStatementsPart != null) {
// prepare statements source for invocation
String source = getMethodSourceForInvocation(
methodStatementsPart,
refUtils,
methodUsage,
target,
arguments);
source = refUtils.getIndentSource(source, methodStatementsPart.prefix, refPrefix);
// do insert
SourceRange range = rangeStartLength(refLineRange, 0);
Edit edit = new Edit(range, source);
refChange.addEdit(edit, "Replace all references to method with statements");
}
// replace invocation with return expression
if (methodExpressionPart != null) {
// prepare expression source for invocation
String source = getMethodSourceForInvocation(
methodExpressionPart,
refUtils,
methodUsage,
target,
arguments);
if (getExpressionPrecedence(methodExpression) < getExpressionParentPrecedence(methodUsage)) {
source = "(" + source + ")";
}
// do replace
SourceRange methodUsageRange = rangeNode(methodUsage);
Edit edit = new Edit(methodUsageRange, source);
refChange.addEdit(edit, "Replace all references to method with statements");
} else {
Edit edit = new Edit(refLineRange, "");
refChange.addEdit(edit, "Replace all references to method with statements");
}
return;
}
// inline as closure invocation
String source;
{
source = methodUtils.getText(rangeStartEnd(
methodParameters.getLeftParenthesis(),
methodNode));
String methodPrefix = methodUtils.getLinePrefix(methodNode.getOffset());
source = refUtils.getIndentSource(source, methodPrefix, refPrefix);
source = source.trim();
}
// do insert
SourceRange range = rangeNode(node);
Edit edit = new Edit(range, source);
refChange.addEdit(edit, "Replace all references to method with statements");
}
private boolean shouldProcess() {
if (currentMode == Mode.INLINE_SINGLE) {
SourceRange parentRange = rangeNode(node);
return parentRange.contains(context.getSelectionOffset());
}
return true;
}
}
/**
* Information about part of the source in {@link #methodUnit}.
*/
private static class SourcePart {
private final SourceRange baseRange;
final String source;
final String prefix;
final Map<ParameterElement, List<ParameterOccurrence>> parameters = Maps.newHashMap();
final Map<VariableElement, List<SourceRange>> variables = Maps.newHashMap();
final List<SourceRange> instanceFieldQualifiers = Lists.newArrayList();
final Map<String, List<SourceRange>> staticFieldQualifiers = Maps.newHashMap();
public SourcePart(SourceRange baseRange, String source, String prefix) {
this.baseRange = baseRange;
this.source = source;
this.prefix = prefix;
}
public void addInstanceFieldQualifier(SourceRange range) {
range = rangeFromBase(range, baseRange);
instanceFieldQualifiers.add(range);
}
public void addParameterOccurrence(ParameterElement parameter, SourceRange range, int precedence) {
if (parameter != null) {
List<ParameterOccurrence> occurrences = parameters.get(parameter);
if (occurrences == null) {
occurrences = Lists.newArrayList();
parameters.put(parameter, occurrences);
}
range = rangeFromBase(range, baseRange);
occurrences.add(new ParameterOccurrence(precedence, range));
}
}
public void addStaticFieldQualifier(String className, SourceRange range) {
List<SourceRange> ranges = staticFieldQualifiers.get(className);
if (ranges == null) {
ranges = Lists.newArrayList();
staticFieldQualifiers.put(className, ranges);
}
range = rangeFromBase(range, baseRange);
ranges.add(range);
}
public void addVariable(VariableElement element, SourceRange range) {
List<SourceRange> ranges = variables.get(element);
if (ranges == null) {
ranges = Lists.newArrayList();
variables.put(element, ranges);
}
range = rangeFromBase(range, baseRange);
ranges.add(range);
}
}
/**
* If the given {@link AstNode} is a some {@link ClassDeclaration}, returns the
* {@link ClassElement}. Otherwise returns {@code null}.
*/
private static ClassElement getEnclosingClassElement(AstNode node) {
ClassDeclaration enclosingClassNode = node.getAncestor(ClassDeclaration.class);
if (enclosingClassNode != null) {
return enclosingClassNode.getElement();
}
return null;
}
private final AssistContext context;
private SourceChangeManager safeManager;
private SourceChangeManager previewManager;
private Mode initialMode;
private List<ReferenceProcessor> referenceProcessors = Lists.newArrayList();
private boolean requiresPreview;
private Mode currentMode;
private boolean deleteSource;
private ExecutableElement methodElement;
private CompilationUnit methodUnit;
private CorrectionUtils methodUtils;
private AstNode methodNode;
private FormalParameterList methodParameters;
private FunctionBody methodBody;
private Expression methodExpression;
private SourcePart methodExpressionPart;
private SourcePart methodStatementsPart;
public InlineMethodRefactoringImpl(AssistContext context) throws Exception {
this.context = context;
}
@Override
public boolean canDeleteSource() {
// TODO(scheglov) check that declaration and all references can be updated
return true;
}
@Override
public RefactoringStatus checkFinalConditions(ProgressMonitor pm) throws Exception {
pm = checkProgressMonitor(pm);
pm.beginTask("Checking final conditions", 5);
RefactoringStatus result = new RefactoringStatus();
try {
safeManager = new SourceChangeManager();
previewManager = new SourceChangeManager();
// prepare changes
for (ReferenceProcessor processor : referenceProcessors) {
processor.process(result);
}
// delete method
if (deleteSource && currentMode == Mode.INLINE_ALL) {
SourceRange methodRange = rangeNode(methodNode);
SourceRange linesRange = methodUtils.getLinesRange(methodRange);
SourceChange change = safeManager.get(methodElement.getSource());
change.addEdit(new Edit(linesRange, ""), "Remove method declaration");
}
} finally {
pm.done();
}
return result;
}
@Override
public RefactoringStatus checkInitialConditions(ProgressMonitor pm) throws Exception {
pm = checkProgressMonitor(pm);
pm.beginTask("Checking initial conditions", 3);
try {
RefactoringStatus result = new RefactoringStatus();
// prepare method information
result.merge(prepareMethod());
if (result.hasFatalError()) {
return result;
}
pm.worked(1);
// may be operator
if (methodElement.isOperator()) {
return RefactoringStatus.createFatalErrorStatus("Cannot inline operator.");
}
// analyze method body
result.merge(prepareMethodParts());
pm.worked(1);
// process references
{
requiresPreview = false;
// find references
List<SearchMatch> references = context.getSearchEngine().searchReferences(
methodElement,
null,
null);
// prepare reference processors
referenceProcessors.clear();
for (SearchMatch reference : references) {
ReferenceProcessor processor = new ReferenceProcessor(reference);
referenceProcessors.add(processor);
processor.checkForSideEffects();
}
// update preview flag
updateRequiresPreviewFlag();
}
pm.worked(1);
// done
return result;
} finally {
pm.done();
}
}
@Override
public Change createChange(ProgressMonitor pm) throws Exception {
pm = checkProgressMonitor(pm);
try {
SourceChange[] safeChanges = safeManager.getChanges();
SourceChange[] previewChanges = previewManager.getChanges();
if (previewChanges.length == 0) {
CompositeChange compositeChange = new CompositeChange(getRefactoringName());
compositeChange.add(safeChanges);
return compositeChange;
} else {
CompositeChange safeChange = new CompositeChange("(Safe changes)");
CompositeChange previewChange = new CompositeChange("(Has arguments with side-effects)");
safeChange.add(safeChanges);
previewChange.add(previewChanges);
return new MergeCompositeChange(getRefactoringName(), previewChange, safeChange);
}
} finally {
pm.done();
}
}
@Override
public ExecutableElement getElement() {
return methodElement;
}
@Override
public Mode getInitialMode() {
return initialMode;
}
@Override
public String getRefactoringName() {
if (methodElement instanceof MethodElement) {
return "Inline Method";
} else {
return "Inline Function";
}
}
@Override
public boolean requiresPreview() {
return requiresPreview;
}
@Override
public void setCurrentMode(Mode currentMode) {
this.currentMode = currentMode;
updateRequiresPreviewFlag();
}
@Override
public void setDeleteSource(boolean delete) {
this.deleteSource = delete;
}
private SourcePart createSourcePart(final SourceRange sourceRange) {
final SourcePart result;
{
String source = methodUtils.getText(sourceRange);
String prefix = CorrectionUtils.getLinesPrefix(source);
result = new SourcePart(sourceRange, source, prefix);
}
// remember parameters and variables occurrences
methodUnit.accept(new GeneralizingAstVisitor<Void>() {
@Override
public Void visitNode(AstNode node) {
SourceRange nodeRange = rangeNode(node);
if (!sourceRange.intersects(nodeRange)) {
return null;
}
return super.visitNode(node);
}
@Override
public Void visitSimpleIdentifier(SimpleIdentifier node) {
SourceRange nodeRange = rangeNode(node);
if (sourceRange.covers(nodeRange)) {
addInstanceFieldQualifier(node);
addParameter(node);
addVariable(node);
}
return null;
}
private void addInstanceFieldQualifier(SimpleIdentifier node) {
PropertyAccessorElement accessor = CorrectionUtils.getPropertyAccessorElement(node);
if (isFieldAccessorElement(accessor)) {
AstNode qualifier = CorrectionUtils.getNodeQualifier(node);
if (qualifier == null || qualifier instanceof ThisExpression) {
if (accessor.isStatic()) {
String className = accessor.getEnclosingElement().getDisplayName();
if (qualifier == null) {
SourceRange qualifierRange = rangeStartLength(node, 0);
result.addStaticFieldQualifier(className, qualifierRange);
}
} else {
SourceRange qualifierRange;
if (qualifier != null) {
qualifierRange = rangeStartStart(qualifier, node);
} else {
qualifierRange = rangeStartLength(node, 0);
}
result.addInstanceFieldQualifier(qualifierRange);
}
}
}
}
private void addParameter(SimpleIdentifier node) {
ParameterElement parameterElement = CorrectionUtils.getParameterElement(node);
// not parameter
if (parameterElement == null) {
return;
}
// not parameter of a function being inlined
if (!ArrayUtils.contains(methodElement.getParameters(), parameterElement)) {
return;
}
// OK, add occurrence
SourceRange nodeRange = rangeNode(node);
int parentPrecedence = getExpressionParentPrecedence(node);
result.addParameterOccurrence(parameterElement, nodeRange, parentPrecedence);
}
private void addVariable(SimpleIdentifier node) {
VariableElement variableElement = CorrectionUtils.getLocalVariableElement(node);
if (variableElement != null) {
SourceRange nodeRange = rangeNode(node);
result.addVariable(variableElement, nodeRange);
}
}
});
// done
return result;
}
/**
* @return the source which should replace given invocation with given arguments.
*/
private String getMethodSourceForInvocation(SourcePart part, CorrectionUtils utils,
AstNode contextNode, Expression targetExpression, List<Expression> arguments) {
// prepare edits to replace parameters with arguments
List<Edit> edits = Lists.newArrayList();
for (Entry<ParameterElement, List<ParameterOccurrence>> entry : part.parameters.entrySet()) {
ParameterElement parameter = entry.getKey();
// prepare argument
Expression argument = null;
for (Expression arg : arguments) {
if (Objects.equal(arg.getBestParameterElement(), parameter)) {
argument = arg;
break;
}
}
if (argument instanceof NamedExpression) {
argument = ((NamedExpression) argument).getExpression();
}
int argumentPrecedence = getExpressionPrecedence(argument);
String argumentSource = utils.getText(argument);
// replace all occurrences of this parameter
for (ParameterOccurrence occurrence : entry.getValue()) {
SourceRange range = occurrence.range;
// prepare argument source to apply at this occurrence
String occurrenceArgumentSource;
if (argumentPrecedence < occurrence.parentPrecedence) {
occurrenceArgumentSource = "(" + argumentSource + ")";
} else {
occurrenceArgumentSource = argumentSource;
}
// do replace
edits.add(new Edit(range, occurrenceArgumentSource));
}
}
// replace static field "qualifier" with invocation target
for (Entry<String, List<SourceRange>> entry : part.staticFieldQualifiers.entrySet()) {
String className = entry.getKey();
for (SourceRange range : entry.getValue()) {
edits.add(new Edit(range, className + "."));
}
}
// replace instance field "qualifier" with invocation target
if (targetExpression != null) {
String targetSource = utils.getText(targetExpression) + ".";
for (SourceRange qualifierRange : part.instanceFieldQualifiers) {
edits.add(new Edit(qualifierRange, targetSource));
}
}
// prepare edits to replace conflicting variables
Set<String> conflictingNames = getNamesConflictingWithLocal(utils.getUnit(), contextNode);
for (Entry<VariableElement, List<SourceRange>> entry : part.variables.entrySet()) {
String originalName = entry.getKey().getDisplayName();
// prepare unique name
String uniqueName;
{
uniqueName = originalName;
int uniqueIndex = 2;
while (conflictingNames.contains(uniqueName)) {
uniqueName = originalName + uniqueIndex;
uniqueIndex++;
}
}
// update references, if name was change
if (!StringUtils.equals(uniqueName, originalName)) {
for (SourceRange range : entry.getValue()) {
edits.add(new Edit(range, uniqueName));
}
}
}
// prepare source with applied arguments
return CorrectionUtils.applyReplaceEdits(part.source, edits);
}
private SourceRange getNamesConflictingRange(AstNode node) {
// may be Block
Block block = node.getAncestor(Block.class);
if (block != null) {
int offset = node.getOffset();
int endOffset = block.getEnd();
return rangeStartEnd(offset, endOffset);
}
// may be whole executable
AstNode executableNode = CorrectionUtils.getEnclosingExecutableNode(node);
if (executableNode != null) {
return rangeNode(executableNode);
}
// not a part of declaration with locals
return SourceRange.EMPTY;
}
/**
* @return the names which will shadow or will be shadowed by any declaration at "node".
*/
private Set<String> getNamesConflictingWithLocal(CompilationUnit unit, AstNode node) {
final Set<String> result = Sets.newHashSet();
// local variables and functions
{
final SourceRange offsetRange = getNamesConflictingRange(node);
ExecutableElement enclosingExecutable = CorrectionUtils.getEnclosingExecutableElement(node);
if (enclosingExecutable != null) {
enclosingExecutable.accept(new GeneralizingElementVisitor<Void>() {
@Override
public Void visitLocalElement(LocalElement element) {
SourceRange elementRange = element.getVisibleRange();
if (elementRange != null && elementRange.intersects(offsetRange)) {
result.add(element.getDisplayName());
}
return super.visitLocalElement(element);
}
});
}
}
// fields
{
ClassElement enclosingClassElement = getEnclosingClassElement(node);
if (enclosingClassElement != null) {
Set<ClassElement> elements = Sets.newHashSet(enclosingClassElement);
elements.addAll(HierarchyUtils.getSuperClasses(enclosingClassElement));
for (ClassElement classElement : elements) {
List<Element> classMembers = CorrectionUtils.getChildren(classElement);
for (Element classMemberElement : classMembers) {
result.add(classMemberElement.getDisplayName());
}
}
}
}
// done
return result;
}
/**
* @return {@code true} if we can prove that the given {@link Expression} has no side effects.
*/
private boolean hasSideEffect(Expression e) {
if (e instanceof BooleanLiteral || e instanceof DoubleLiteral || e instanceof IntegerLiteral
|| e instanceof NullLiteral || e instanceof SimpleStringLiteral
|| e instanceof AdjacentStrings || e instanceof SymbolLiteral) {
return false;
}
if (e instanceof ThisExpression) {
return false;
}
if (e instanceof NamedExpression) {
NamedExpression namedExpression = (NamedExpression) e;
return hasSideEffect(namedExpression.getExpression());
}
if (e instanceof SimpleIdentifier) {
SimpleIdentifier identifier = (SimpleIdentifier) e;
Element element = identifier.getBestElement();
if (element instanceof VariableElement) {
return false;
}
if (element instanceof PropertyAccessorElement) {
return !element.isSynthetic();
}
}
if (e instanceof BinaryExpression) {
BinaryExpression binary = (BinaryExpression) e;
return hasSideEffect(binary.getLeftOperand()) || hasSideEffect(binary.getRightOperand());
}
return true;
}
/**
* @return {@code true} if we can prove that all of the given {@link Expression} have no side
* effects.
*/
private boolean hasSideEffect(List<Expression> arguments) {
for (Expression argument : arguments) {
if (hasSideEffect(argument)) {
return true;
}
}
return false;
}
/**
* @return <code>true</code> if given {@link PropertyAccessorElement} is accessor of some
* {@link FieldElement}.
*/
private boolean isFieldAccessorElement(PropertyAccessorElement accessor) {
return accessor != null && accessor.getVariable() instanceof FieldElement
&& accessor.getVariable().getEnclosingElement() instanceof ClassElement;
}
/**
* Initializes "method*" fields.
*/
private RefactoringStatus prepareMethod() throws Exception {
methodElement = null;
methodParameters = null;
methodBody = null;
// prepare selected SimpleIdentifier
AstNode selectedNode = context.getCoveringNode();
if (!(selectedNode instanceof SimpleIdentifier)) {
return RefactoringStatus.createFatalErrorStatus("Method declaration or reference must be selected to activate this refactoring.");
}
SimpleIdentifier selectedIdentifier = (SimpleIdentifier) selectedNode;
// prepare selected ExecutableElement
Element selectedElement = selectedIdentifier.getBestElement();
if (!(selectedElement instanceof ExecutableElement)) {
return RefactoringStatus.createFatalErrorStatus("Method declaration or reference must be selected to activate this refactoring.");
}
methodElement = (ExecutableElement) selectedElement;
methodUnit = selectedElement.getUnit();
methodUtils = new CorrectionUtils(methodUnit);
if (selectedElement instanceof MethodElement
|| selectedElement instanceof PropertyAccessorElement) {
MethodDeclaration methodDeclaration = (MethodDeclaration) methodElement.getNode();
methodNode = methodDeclaration;
methodParameters = methodDeclaration.getParameters();
methodBody = methodDeclaration.getBody();
// prepare mode
boolean isDeclaration = methodDeclaration.getName() == selectedNode;
initialMode = currentMode = isDeclaration ? Mode.INLINE_ALL : Mode.INLINE_SINGLE;
}
if (selectedElement instanceof FunctionElement) {
FunctionDeclaration functionDeclaration = (FunctionDeclaration) methodElement.getNode();
methodNode = functionDeclaration;
methodParameters = functionDeclaration.getFunctionExpression().getParameters();
methodBody = functionDeclaration.getFunctionExpression().getBody();
// prepare mode
boolean isDeclaration = functionDeclaration.getName() == selectedNode;
initialMode = currentMode = isDeclaration ? Mode.INLINE_ALL : Mode.INLINE_SINGLE;
}
// OK
return new RefactoringStatus();
}
/**
* Analyze {@link #methodBody} to fill {@link #methodExpressionPart} and
* {@link #methodStatementsPart}.
*/
private RefactoringStatus prepareMethodParts() {
final RefactoringStatus result = new RefactoringStatus();
if (methodBody instanceof ExpressionFunctionBody) {
ExpressionFunctionBody body = (ExpressionFunctionBody) methodBody;
methodExpression = body.getExpression();
SourceRange methodExpressionRange = rangeNode(methodExpression);
methodExpressionPart = createSourcePart(methodExpressionRange);
} else if (methodBody instanceof BlockFunctionBody) {
Block body = ((BlockFunctionBody) methodBody).getBlock();
List<Statement> statements = body.getStatements();
if (statements.size() >= 1) {
Statement lastStatement = statements.get(statements.size() - 1);
// "return" statement requires special handling
if (lastStatement instanceof ReturnStatement) {
methodExpression = ((ReturnStatement) lastStatement).getExpression();
SourceRange methodExpressionRange = rangeNode(methodExpression);
methodExpressionPart = createSourcePart(methodExpressionRange);
// exclude "return" statement from statements
statements = ImmutableList.copyOf(statements).subList(0, statements.size() - 1);
}
// if there are statements, process them
if (!statements.isEmpty()) {
SourceRange statementsRange = methodUtils.getLinesRange(statements);
methodStatementsPart = createSourcePart(statementsRange);
}
}
// check if more than one return
body.accept(new RecursiveAstVisitor<Void>() {
private int numReturns = 0;
@Override
public Void visitReturnStatement(ReturnStatement node) {
numReturns++;
if (numReturns == 2) {
result.addError("Ambiguous return value.", new RefactoringStatusContext(node));
}
return super.visitReturnStatement(node);
}
});
} else {
return RefactoringStatus.createFatalErrorStatus("Cannot inline method without body.");
}
return result;
}
private void updateRequiresPreviewFlag() {
requiresPreview = false;
for (ReferenceProcessor processor : referenceProcessors) {
processor.updateRequiresPreviewFlag();
}
}
}