/* * 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(); } } }