package com.redhat.ceylon.eclipse.code.refactor;
import static com.redhat.ceylon.eclipse.code.correct.ImportProposals.importProposals;
import static com.redhat.ceylon.eclipse.util.Nodes.getIdentifyingNode;
import static com.redhat.ceylon.eclipse.util.Nodes.getReferencedDeclaration;
import static org.eclipse.ltk.core.refactoring.RefactoringStatus.createFatalErrorStatus;
import static org.eclipse.ltk.core.refactoring.RefactoringStatus.createWarningStatus;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.Token;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.DocumentChange;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.ui.IEditorPart;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
import com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.redhat.ceylon.eclipse.util.FindReferencesVisitor;
import com.redhat.ceylon.eclipse.util.Nodes;
import com.redhat.ceylon.ide.common.model.CeylonUnit;
import com.redhat.ceylon.ide.common.typechecker.ProjectPhasedUnit;
import com.redhat.ceylon.ide.common.util.FindDeclarationNodeVisitor;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Function;
import com.redhat.ceylon.model.typechecker.model.FunctionOrValue;
import com.redhat.ceylon.model.typechecker.model.Generic;
import com.redhat.ceylon.model.typechecker.model.Package;
import com.redhat.ceylon.model.typechecker.model.Parameter;
import com.redhat.ceylon.model.typechecker.model.Referenceable;
import com.redhat.ceylon.model.typechecker.model.Setter;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.TypeAlias;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.model.typechecker.model.TypeParameter;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
import com.redhat.ceylon.model.typechecker.model.Unit;
public class InlineRefactoring extends AbstractRefactoring {
private final Declaration declaration;
private boolean delete = true;
private boolean justOne = false;
public InlineRefactoring(IEditorPart editor) {
super(editor);
Referenceable ref = getReferencedDeclaration(node);
if (ref instanceof Declaration) {
declaration = (Declaration) ref;
}
else {
declaration = null;
}
}
boolean isReference() {
return !(node instanceof Tree.Declaration) &&
getIdentifyingNode(node)
instanceof Tree.Identifier;
}
@Override
public boolean getEnabled() {
if (declaration!=null &&
project != null &&
inSameProject(declaration)) {
if (declaration instanceof FunctionOrValue) {
FunctionOrValue fov =
(FunctionOrValue) declaration;
return !fov.isParameter() &&
!(fov instanceof Setter) &&
!fov.isDefault() &&
!fov.isFormal() &&
!fov.isNative() &&
(fov.getTypeDeclaration()!=null) &&
(!fov.getTypeDeclaration().isAnonymous()) &&
(fov.isToplevel() || !fov.isShared() ||
(!fov.isFormal() &&
!fov.isDefault() &&
!fov.isActual())) &&
(!fov.getUnit().equals(rootNode.getUnit()) ||
!(getDeclararionNode(rootNode)
instanceof Tree.Variable)); //not a Destructure
//TODO: && !declaration is a control structure variable
//TODO: && !declaration is a value with lazy init
}
else if (declaration instanceof TypeAlias) {
return true;
}
else if (declaration instanceof ClassOrInterface) {
return ((ClassOrInterface) declaration).isAlias();
}
else {
return false;
}
}
else {
return false;
}
}
public int getCount() {
return declaration==null ? 0 :
countDeclarationOccurrences();
}
@Override
int countReferences(Tree.CompilationUnit cu) {
FindReferencesVisitor frv =
new FindReferencesVisitor(declaration);
cu.visit(frv);
return frv.getNodes().size();
}
public String getName() {
return "Inline";
}
public RefactoringStatus checkInitialConditions
(IProgressMonitor pm)
throws CoreException,
OperationCanceledException {
final RefactoringStatus result =
new RefactoringStatus();
Tree.CompilationUnit declarationUnit = null;
Unit unit = declaration.getUnit();
if (unit instanceof CeylonUnit) {
CeylonUnit cu = (CeylonUnit) unit;
declarationUnit =
cu.getPhasedUnit()
.getCompilationUnit();
}
Tree.Declaration declarationNode =
getDeclararionNode(declarationUnit);
if (declarationNode
instanceof Tree.AttributeDeclaration &&
((Tree.AttributeDeclaration) declarationNode)
.getSpecifierOrInitializerExpression()==null ||
declarationNode
instanceof Tree.MethodDeclaration &&
((Tree.MethodDeclaration) declarationNode)
.getSpecifierExpression()==null) {
return createFatalErrorStatus(
"Cannot inline forward declaration: " +
declaration.getName());
}
if (declarationNode
instanceof Tree.AttributeGetterDefinition) {
Tree.AttributeGetterDefinition getterDefinition =
(Tree.AttributeGetterDefinition)
declarationNode;
List<Tree.Statement> statements =
getterDefinition.getBlock()
.getStatements();
if (statements.size()!=1) {
return createFatalErrorStatus(
"Getter body is not a single statement: " +
declaration.getName());
}
if (!(statements.get(0) instanceof Tree.Return)) {
return createFatalErrorStatus(
"Getter body is not a return statement: " +
declaration.getName());
}
}
if (declarationNode instanceof Tree.MethodDefinition) {
Tree.MethodDefinition methodDefinition =
(Tree.MethodDefinition)
declarationNode;
List<Tree.Statement> statements =
methodDefinition.getBlock()
.getStatements();
if (statements.size()!=1) {
return createFatalErrorStatus(
"Function body is not a single statement: " +
declaration.getName());
}
Tree.Statement statement = statements.get(0);
if (methodDefinition.getType()
instanceof Tree.VoidModifier) {
if (!(statement
instanceof Tree.ExpressionStatement)) {
return createFatalErrorStatus(
"Function body is not an expression: " +
declaration.getName());
}
}
else {
if (!(statement instanceof Tree.Return)) {
return createFatalErrorStatus(
"Function body is not a return statement: " +
declaration.getName());
}
}
}
if (declarationNode instanceof Tree.AnyAttribute) {
Tree.AnyAttribute attribute =
(Tree.AnyAttribute)
declarationNode;
if (attribute.getDeclarationModel().isVariable()) {
result.merge(createWarningStatus(
"Inlined value is variable"));
}
}
declarationNode.visit(new Visitor() {
@Override
public void visit(Tree.BaseMemberOrTypeExpression that) {
super.visit(that);
Declaration dec = that.getDeclaration();
if (dec==null) {
result.merge(createWarningStatus(
"Definition contains unresolved reference"));
}
else if (declaration.isShared() &&
!dec.isShared() &&
!dec.isParameter()) {
result.merge(createWarningStatus(
"Definition contains reference to unshared declaration: " +
dec.getName()));
}
}
});
return result;
}
public RefactoringStatus checkFinalConditions
(IProgressMonitor pm)
throws CoreException,
OperationCanceledException {
return new RefactoringStatus();
}
public Change createChange(IProgressMonitor pm)
throws CoreException,
OperationCanceledException {
Tree.Declaration declarationNode = null;
Tree.CompilationUnit declarationUnit = null;
Node term = null;
List<CommonToken> declarationTokens = null;
Tree.CompilationUnit editorRootNode =
editor.getParseController()
.getLastCompilationUnit();
List<CommonToken> editorTokens =
editor.getParseController()
.getTokens();
if (declaration!=null) {
Unit unit = declaration.getUnit();
if (searchInEditor()) {
if (editorRootNode.getUnit()
.equals(unit)) {
declarationUnit = editorRootNode;
declarationTokens = editorTokens;
}
}
if (declarationUnit==null) {
for (PhasedUnit pu: getAllUnits()) {
if (pu.getUnit().equals(unit)) {
declarationUnit =
pu.getCompilationUnit();
declarationTokens = pu.getTokens();
break;
}
}
}
declarationNode =
getDeclararionNode(declarationUnit);
term = getInlinedTerm(declarationNode);
}
CompositeChange cc = new CompositeChange(getName());
if (declarationNode!=null) {
for (PhasedUnit pu: getAllUnits()) {
if (searchInFile(pu) &&
affectsUnit(pu.getUnit())) {
ProjectPhasedUnit ppu =
(ProjectPhasedUnit<IProject,IResource,IFolder,IFile>) pu;
TextFileChange tfc =
newTextFileChange(ppu);
Tree.CompilationUnit cu =
pu.getCompilationUnit();
inlineInFile(tfc, cc,
declarationNode,
declarationUnit, term,
declarationTokens, cu,
pu.getTokens());
}
}
}
if (searchInEditor() &&
affectsUnit(editorRootNode.getUnit())) {
DocumentChange dc = newDocumentChange();
inlineInFile(dc, cc,
declarationNode,
declarationUnit, term,
declarationTokens,
editorRootNode,
editorTokens);
}
return cc;
}
private Tree.Declaration getDeclararionNode(
Tree.CompilationUnit declarationUnit) {
FindDeclarationNodeVisitor fdv =
new FindDeclarationNodeVisitor(declaration);
declarationUnit.visit(fdv);
return (Tree.Declaration) fdv.getDeclarationNode();
}
private boolean affectsUnit(Unit unit) {
return delete && unit.equals(declaration.getUnit())
|| !justOne
|| unit.equals(node.getUnit());
}
private boolean addImports(
final TextChange change,
final Tree.Declaration declarationNode,
final Tree.CompilationUnit cu) {
final Package decPack =
declarationNode.getUnit().getPackage();
final Package filePack = cu.getUnit().getPackage();
final class AddImportsVisitor extends Visitor {
private final Set<Declaration> already;
boolean importedFromDeclarationPackage;
private AddImportsVisitor(Set<Declaration> already) {
this.already = already;
}
@Override
public void visit(Tree.BaseMemberOrTypeExpression that) {
super.visit(that);
Declaration dec = that.getDeclaration();
if (dec!=null) {
importProposals().importDeclaration(already, dec, cu);
Package refPack =
dec.getUnit().getPackage();
importedFromDeclarationPackage =
importedFromDeclarationPackage ||
refPack.equals(decPack) &&
!decPack.equals(filePack); //unnecessary
}
}
}
final Set<Declaration> already =
new HashSet<Declaration>();
AddImportsVisitor aiv =
new AddImportsVisitor(already);
declarationNode.visit(aiv);
Declaration dnd =
declarationNode.getDeclarationModel();
importProposals().applyImports(change, already, cu, document, dnd);
return aiv.importedFromDeclarationPackage;
}
private void inlineInFile(TextChange tfc,
CompositeChange cc,
Tree.Declaration declarationNode,
Tree.CompilationUnit declarationUnit,
Node term,
List<CommonToken> declarationTokens,
Tree.CompilationUnit cu,
List<CommonToken> tokens) {
tfc.setEdit(new MultiTextEdit());
inlineReferences(declarationNode, declarationUnit,
term, declarationTokens, cu, tokens, tfc);
boolean inlined = tfc.getEdit().hasChildren();
deleteDeclaration(declarationNode, declarationUnit,
cu, tokens, tfc);
boolean importsAddedToDeclarationPackage = false;
if (inlined) {
importsAddedToDeclarationPackage =
addImports(tfc, declarationNode, cu);
}
deleteImports(tfc, declarationNode, cu, tokens,
importsAddedToDeclarationPackage);
if (tfc.getEdit().hasChildren()) {
cc.add(tfc);
}
}
private void deleteImports(TextChange tfc,
Tree.Declaration declarationNode,
Tree.CompilationUnit cu,
List<CommonToken> tokens,
boolean importsAddedToDeclarationPackage) {
Tree.ImportList il = cu.getImportList();
if (il!=null) {
for (Tree.Import i: il.getImports()) {
List<Tree.ImportMemberOrType> list =
i.getImportMemberOrTypeList()
.getImportMemberOrTypes();
for (Tree.ImportMemberOrType imt: list) {
Declaration d = imt.getDeclarationModel();
Declaration dnd =
declarationNode.getDeclarationModel();
if (d!=null && d.equals(dnd)) {
if (list.size()==1 &&
!importsAddedToDeclarationPackage) {
//delete the whole import statement
tfc.addEdit(new DeleteEdit(
i.getStartIndex(),
i.getDistance()));
}
else {
//delete just the item in the import statement...
tfc.addEdit(new DeleteEdit(
imt.getStartIndex(),
imt.getDistance()));
//...along with a comma before or after
int ti =
Nodes.getTokenIndexAtCharacter(
tokens,
imt.getStartIndex());
CommonToken prev =
tokens.get(ti-1);
if (prev.getChannel()==CommonToken.HIDDEN_CHANNEL) {
prev = tokens.get(ti-2);
}
CommonToken next =
tokens.get(ti+1);
if (next.getChannel()==CommonToken.HIDDEN_CHANNEL) {
next = tokens.get(ti+2);
}
if (prev.getType()==CeylonLexer.COMMA) {
tfc.addEdit(new DeleteEdit(
prev.getStartIndex(),
imt.getStartIndex()
- prev.getStartIndex()));
}
else if (next.getType()==CeylonLexer.COMMA) {
tfc.addEdit(new DeleteEdit(
imt.getEndIndex(),
next.getStopIndex()
- imt.getEndIndex()
+ 1));
}
}
}
}
}
}
}
private void deleteDeclaration(
Tree.Declaration declarationNode,
Tree.CompilationUnit declarationUnit,
Tree.CompilationUnit cu,
List<CommonToken> tokens,
TextChange tfc) {
if (delete) {
Unit unit = declarationUnit.getUnit();
if (cu.getUnit().equals(unit)) {
CommonToken from =
(CommonToken)
declarationNode.getToken();
Tree.AnnotationList anns =
declarationNode.getAnnotationList();
if (!anns.getAnnotations().isEmpty()) {
from = (CommonToken)
anns.getAnnotations()
.get(0)
.getToken();
}
int prevIndex = from.getTokenIndex()-1;
if (prevIndex>=0) {
CommonToken tok = tokens.get(prevIndex);
if (tok.getChannel()==Token.HIDDEN_CHANNEL) {
from=tok;
}
}
tfc.addEdit(new DeleteEdit(
from.getStartIndex(),
declarationNode.getEndIndex()
- from.getStartIndex()));
}
}
}
private static Node getInlinedTerm(
Tree.Declaration declarationNode) {
if (declarationNode!=null) {
if (declarationNode
instanceof Tree.AttributeDeclaration) {
Tree.AttributeDeclaration att =
(Tree.AttributeDeclaration)
declarationNode;
return att.getSpecifierOrInitializerExpression()
.getExpression()
.getTerm();
}
else if (declarationNode
instanceof Tree.MethodDefinition) {
Tree.MethodDefinition meth =
(Tree.MethodDefinition)
declarationNode;
List<Tree.Statement> statements =
meth.getBlock()
.getStatements();
if (meth.getType()
instanceof Tree.VoidModifier) {
//TODO: in the case of a void method, tolerate
// multiple statements , including control
// structures, not just expression statements
if (!isSingleExpression(statements)) {
throw new RuntimeException(
"method body is not a single expression statement");
}
Tree.ExpressionStatement e =
(Tree.ExpressionStatement)
statements.get(0);
return e.getExpression().getTerm();
}
else {
if (!isSingleReturn(statements)) {
throw new RuntimeException(
"method body is not a single expression statement");
}
Tree.Return ret =
(Tree.Return)
statements.get(0);
return ret.getExpression().getTerm();
}
}
else if (declarationNode
instanceof Tree.MethodDeclaration) {
Tree.MethodDeclaration meth =
(Tree.MethodDeclaration)
declarationNode;
return meth.getSpecifierExpression()
.getExpression().getTerm();
}
else if (declarationNode
instanceof Tree.AttributeGetterDefinition) {
Tree.AttributeGetterDefinition att =
(Tree.AttributeGetterDefinition)
declarationNode;
List<Tree.Statement> statements =
att.getBlock()
.getStatements();
if (!isSingleReturn(statements)) {
throw new RuntimeException(
"getter body is not a single expression statement");
}
Tree.Return r =
(Tree.Return)
att.getBlock()
.getStatements()
.get(0);
return r.getExpression().getTerm();
}
else if (declarationNode
instanceof Tree.ClassDeclaration) {
Tree.ClassDeclaration alias =
(Tree.ClassDeclaration)
declarationNode;
return alias.getClassSpecifier();
}
else if (declarationNode
instanceof Tree.InterfaceDeclaration) {
Tree.InterfaceDeclaration alias =
(Tree.InterfaceDeclaration)
declarationNode;
return alias.getTypeSpecifier();
}
else if (declarationNode
instanceof Tree.TypeAliasDeclaration) {
Tree.TypeAliasDeclaration alias =
(Tree.TypeAliasDeclaration)
declarationNode;
return alias.getTypeSpecifier();
}
else {
throw new RuntimeException(
"not a value, function, or type alias");
}
}
else {
return null;
}
}
public static boolean isSingleExpression(
List<Tree.Statement> statements) {
return statements.size()==1 &&
statements.get(0)
instanceof Tree.ExpressionStatement;
}
public static boolean isSingleReturn(
List<Tree.Statement> statements) {
return statements.size()==1 &&
statements.get(0)
instanceof Tree.Return;
}
private void inlineReferences(
Tree.Declaration declarationNode,
Tree.CompilationUnit declarationUnit,
Node definition,
List<CommonToken> declarationTokens,
Tree.CompilationUnit pu,
List<CommonToken> tokens,
TextChange tfc) {
if (declarationNode
instanceof Tree.AnyAttribute) {
// Tree.AnyAttribute value =
// (Tree.AnyAttribute) declarationNode;
Tree.Term expression = (Tree.Term) definition;
inlineAttributeReferences(pu, tokens, expression,
declarationTokens, tfc);
}
else if (declarationNode
instanceof Tree.AnyMethod) {
Tree.Term expression = (Tree.Term) definition;
Tree.AnyMethod method =
(Tree.AnyMethod)
declarationNode;
inlineFunctionReferences(pu, tokens, expression,
method, declarationTokens, tfc);
}
else if (declarationNode
instanceof Tree.ClassDeclaration) {
Tree.ClassSpecifier spec =
(Tree.ClassSpecifier)
definition;
Tree.ClassDeclaration classAlias =
(Tree.ClassDeclaration)
declarationNode;
inlineClassAliasReferences(pu, tokens,
spec.getInvocationExpression(),
spec.getType(),
classAlias,
declarationTokens, tfc);
}
else if (declarationNode
instanceof Tree.TypeAliasDeclaration ||
declarationNode
instanceof Tree.InterfaceDeclaration) {
Tree.TypeSpecifier spec =
(Tree.TypeSpecifier)
definition;
inlineTypeAliasReferences(pu, tokens,
spec.getType(),
declarationTokens, tfc);
}
}
private void inlineFunctionReferences(
final Tree.CompilationUnit pu,
final List<CommonToken> tokens,
final Tree.Term term,
final Tree.AnyMethod decNode,
final List<CommonToken> declarationTokens,
final TextChange tfc) {
new Visitor() {
private boolean needsParens = false;
@Override
public void visit(
final Tree.InvocationExpression that) {
super.visit(that);
Tree.Primary primary = that.getPrimary();
if (primary instanceof Tree.MemberOrTypeExpression) {
Tree.MemberOrTypeExpression mte =
(Tree.MemberOrTypeExpression)
primary;
inlineDefinition(tokens, declarationTokens,
term, tfc, that, mte, needsParens);
}
}
@Override
public void visit(
final Tree.MemberOrTypeExpression that) {
super.visit(that);
Declaration dec = that.getDeclaration();
if (!that.getDirectlyInvoked() &&
inlineRef(that, dec)) {
StringBuilder text = new StringBuilder();
Function function =
decNode.getDeclarationModel();
if (function.isDeclaredVoid()) {
text.append("void ");
}
for (Tree.ParameterList pl:
decNode.getParameterLists()) {
text.append(Nodes.text(pl,
declarationTokens));
}
text.append(" => ");
text.append(Nodes.text(term,
declarationTokens));
tfc.addEdit(new ReplaceEdit(
that.getStartIndex(),
that.getDistance(),
text.toString()));
}
}
@Override
public void visit(Tree.OperatorExpression that) {
boolean onp = needsParens;
needsParens=true;
super.visit(that);
needsParens = onp;
}
@Override
public void visit(Tree.StatementOrArgument that) {
boolean onp = needsParens;
needsParens = false;
super.visit(that);
needsParens = onp;
}
@Override
public void visit(Tree.Expression that) {
boolean onp = needsParens;
needsParens = false;
super.visit(that);
needsParens = onp;
}
}.visit(pu);
}
private void inlineTypeAliasReferences(
final Tree.CompilationUnit pu,
final List<CommonToken> tokens,
final Tree.Type term,
final List<CommonToken> declarationTokens,
final TextChange tfc) {
new Visitor() {
@Override
public void visit(Tree.SimpleType that) {
super.visit(that);
inlineDefinition(tokens, declarationTokens,
term, tfc, null, that, false);
}
}.visit(pu);
}
private void inlineClassAliasReferences(
final Tree.CompilationUnit pu,
final List<CommonToken> tokens,
final Tree.InvocationExpression term,
final Tree.Type type,
final Tree.ClassDeclaration decNode,
final List<CommonToken> declarationTokens,
final TextChange tfc) {
new Visitor() {
@Override
public void visit(Tree.SimpleType that) {
super.visit(that);
inlineDefinition(tokens, declarationTokens,
type, tfc, null, that, false);
}
private boolean needsParens = false;
@Override
public void visit(
final Tree.InvocationExpression that) {
super.visit(that);
Tree.Primary primary = that.getPrimary();
if (primary instanceof Tree.MemberOrTypeExpression) {
Tree.MemberOrTypeExpression mte =
(Tree.MemberOrTypeExpression)
primary;
inlineDefinition(tokens, declarationTokens,
term, tfc, that, mte, needsParens);
}
}
@Override
public void visit(
final Tree.MemberOrTypeExpression that) {
super.visit(that);
Declaration d = that.getDeclaration();
if (!that.getDirectlyInvoked() &&
inlineRef(that, d)) {
StringBuilder text =
new StringBuilder();
Class dec =
decNode.getDeclarationModel();
if (dec.isDeclaredVoid()) {
text.append("void ");
}
Tree.ParameterList pl =
decNode.getParameterList();
text.append(Nodes.text(pl,
declarationTokens));
text.append(" => ");
text.append(Nodes.text(term,
declarationTokens));
tfc.addEdit(new ReplaceEdit(
that.getStartIndex(),
that.getDistance(),
text.toString()));
}
}
@Override
public void visit(Tree.OperatorExpression that) {
boolean onp = needsParens;
needsParens=true;
super.visit(that);
needsParens = onp;
}
@Override
public void visit(Tree.StatementOrArgument that) {
boolean onp = needsParens;
needsParens = false;
super.visit(that);
needsParens = onp;
}
@Override
public void visit(Tree.Expression that) {
boolean onp = needsParens;
needsParens = false;
super.visit(that);
needsParens = onp;
}
}.visit(pu);
}
private void inlineAttributeReferences(
final Tree.CompilationUnit pu,
final List<CommonToken> tokens,
final Tree.Term term,
final List<CommonToken> declarationTokens,
final TextChange tfc) {
new Visitor() {
private boolean needsParens = false;
@Override
public void visit(Tree.Variable that) {
if (that.getType()
instanceof Tree.SyntheticVariable) {
TypedDeclaration od =
that.getDeclarationModel()
.getOriginalDeclaration();
if (od!=null &&
od.equals(declaration) && delete) {
Integer startIndex =
that.getSpecifierExpression()
.getStartIndex();
String text =
that.getIdentifier().getText()
+ " = ";
tfc.addEdit(new InsertEdit(startIndex, text));
}
}
super.visit(that);
}
@Override
public void visit(Tree.MemberOrTypeExpression that) {
super.visit(that);
inlineDefinition(tokens, declarationTokens, term,
tfc, null, that, needsParens);
}
@Override
public void visit(Tree.OperatorExpression that) {
boolean onp = needsParens;
needsParens=true;
super.visit(that);
needsParens = onp;
}
@Override
public void visit(Tree.QualifiedMemberOrTypeExpression that) {
boolean onp = needsParens;
needsParens=true;
super.visit(that);
needsParens = onp;
}
@Override
public void visit(Tree.StatementOrArgument that) {
boolean onp = needsParens;
needsParens = false;
super.visit(that);
needsParens = onp;
}
@Override
public void visit(Tree.Expression that) {
boolean onp = needsParens;
needsParens = false;
super.visit(that);
needsParens = onp;
}
}.visit(pu);
}
private void inlineAliasDefinitionReference(
List<CommonToken> tokens,
List<CommonToken> declarationTokens,
Node reference,
StringBuilder result,
Tree.Type it) {
Type t = it.getTypeModel();
TypeDeclaration td = t.getDeclaration();
if (td instanceof TypeParameter) {
Generic ta = (Generic) declaration;
int index = ta.getTypeParameters().indexOf(td);
if (index>=0) {
if (reference
instanceof Tree.SimpleType) {
Tree.SimpleType st =
(Tree.SimpleType)
reference;
Tree.TypeArgumentList tal =
st.getTypeArgumentList();
List<Tree.Type> types = tal.getTypes();
if (types.size()>index) {
Tree.Type type = types.get(index);
result.append(
Nodes.text(type,
tokens));
return;
}
}
else if (reference
instanceof Tree.MemberOrTypeExpression) {
Tree.StaticMemberOrTypeExpression st =
(Tree.StaticMemberOrTypeExpression)
reference;
Tree.TypeArguments tas =
st.getTypeArguments();
if (tas instanceof Tree.TypeArgumentList) {
Tree.TypeArgumentList tal =
(Tree.TypeArgumentList) tas;
List<Tree.Type> types = tal.getTypes();
if (types.size()>index) {
Tree.Type type = types.get(index);
if (type!=null) {
result.append(
Nodes.text(type,
tokens));
}
return;
}
}
else {
List<Type> types = tas.getTypeModels();
if (types.size()>index) {
Type type = types.get(index);
if (type!=null) {
result.append(
type.asSourceCodeString(
it.getUnit()));
}
return;
}
}
}
}
}
result.append(Nodes.text(it, declarationTokens));
}
private void inlineDefinitionReference(
List<CommonToken> tokens,
List<CommonToken> declarationTokens,
Node reference,
Tree.InvocationExpression ie,
StringBuilder result,
Tree.StaticMemberOrTypeExpression it) {
Declaration dec = it.getDeclaration();
if (dec.isParameter() && ie!=null &&
it instanceof Tree.BaseMemberOrTypeExpression) {
FunctionOrValue fov = (FunctionOrValue) dec;
Parameter param = fov.getInitializerParameter();
if (param.getDeclaration().equals(declaration)) {
boolean sequenced = param.isSequenced();
if (ie.getPositionalArgumentList()!=null) {
interpolatePositionalArguments(result,
ie, it, sequenced, tokens);
}
if (ie.getNamedArgumentList()!=null) {
interpolateNamedArguments(result,
ie, it, sequenced, tokens);
}
return; //NOTE: early exit!
}
}
String expressionText =
Nodes.text(it, declarationTokens);
if (reference
instanceof Tree.QualifiedMemberOrTypeExpression) {
Tree.QualifiedMemberOrTypeExpression qmtre =
(Tree.QualifiedMemberOrTypeExpression)
reference;
String prim =
Nodes.text(qmtre.getPrimary(),
tokens);
if (it instanceof Tree.QualifiedMemberOrTypeExpression) {
//TODO: handle more depth, for example, foo.bar.baz
Tree.QualifiedMemberOrTypeExpression qmte =
(Tree.QualifiedMemberOrTypeExpression) it;
Tree.Primary p = qmte.getPrimary();
if (p instanceof Tree.This) {
String op =
qmte.getMemberOperator()
.getText();
String id =
qmte.getIdentifier()
.getText();
result.append(prim).append(op).append(id);
}
else {
String primaryText =
Nodes.text(p,
declarationTokens);
if (p instanceof Tree.MemberOrTypeExpression) {
Tree.MemberOrTypeExpression mte =
(Tree.MemberOrTypeExpression) p;
if (mte.getDeclaration()
.isClassOrInterfaceMember()) {
result.append(prim)
.append(".")
.append(primaryText);
}
}
else {
result.append(primaryText);
}
}
}
else {
if (it.getDeclaration()
.isClassOrInterfaceMember()) {
result.append(prim)
.append(".")
.append(expressionText);
}
else {
result.append(expressionText);
}
}
}
else {
result.append(expressionText);
}
}
private void inlineDefinition(
final List<CommonToken> tokens,
final List<CommonToken> declarationTokens,
final Node definition,
final TextChange tfc,
final Tree.InvocationExpression that,
final Node reference,
final boolean needsParens) {
Declaration dec;
if (reference
instanceof Tree.MemberOrTypeExpression) {
Tree.MemberOrTypeExpression mte =
(Tree.MemberOrTypeExpression)
reference;
dec = mte.getDeclaration();
}
else if (reference
instanceof Tree.SimpleType) {
Tree.SimpleType st =
(Tree.SimpleType)
reference;
dec = st.getDeclarationModel();
}
else {
//can't happen
return;
}
if (inlineRef(reference, dec)) {
//TODO: breaks for invocations like f(f(x, y),z)
final StringBuilder result = new StringBuilder();
class InterpolationVisitor extends Visitor {
int start = 0;
final String template =
Nodes.text(definition,
declarationTokens);
final int templateStart =
definition.getStartIndex();
void text(Node it) {
String text =
template.substring(start,
it.getStartIndex()
- templateStart);
result.append(text);
start = it.getEndIndex()-templateStart;
}
@Override
public void visit(Tree.BaseMemberExpression it) {
super.visit(it);
text(it);
inlineDefinitionReference(tokens,
declarationTokens, reference,
that, result, it);
}
@Override
public void visit(Tree.QualifiedMemberExpression it) {
super.visit(it);
text(it);
inlineDefinitionReference(tokens,
declarationTokens, reference,
that, result, it);
}
@Override
public void visit(Tree.Type it) {
super.visit(it);
text(it);
inlineAliasDefinitionReference(
tokens, declarationTokens,
reference, result, it);
}
void finish() {
String text =
template.substring(start,
template.length());
result.append(text);
}
}
InterpolationVisitor iv =
new InterpolationVisitor();
definition.visit(iv);
iv.finish();
if (needsParens &&
(definition instanceof Tree.OperatorExpression ||
definition instanceof Tree.IfExpression ||
definition instanceof Tree.SwitchExpression ||
definition instanceof Tree.ObjectExpression ||
definition instanceof Tree.LetExpression ||
definition instanceof Tree.FunctionArgument)) {
result.insert(0,'(').append(')');
}
Node node = that==null ? reference : that;
tfc.addEdit(new ReplaceEdit(
node.getStartIndex(),
node.getDistance(),
result.toString()));
}
}
private boolean inlineRef(Node that, Declaration dec) {
return (!justOne ||
that.getUnit().equals(node.getUnit()) &&
that.getStartIndex()!=null &&
that.getStartIndex()
.equals(node.getStartIndex())) &&
dec!=null && dec.equals(declaration);
}
private static void interpolatePositionalArguments(
StringBuilder result,
Tree.InvocationExpression that,
Tree.StaticMemberOrTypeExpression it,
boolean sequenced,
List<CommonToken> tokens) {
boolean first = true;
boolean found = false;
if (sequenced) {
result.append("{");
}
List<Tree.PositionalArgument> args =
that.getPositionalArgumentList()
.getPositionalArguments();
for (Tree.PositionalArgument arg: args) {
Parameter param = arg.getParameter();
FunctionOrValue model = param.getModel();
if (it.getDeclaration().equals(model)) {
if (param.isSequenced() &&
arg instanceof Tree.ListedArgument) {
if (first) result.append(" ");
if (!first) result.append(", ");
first = false;
}
result.append(Nodes.text(arg, tokens));
found = true;
}
}
if (sequenced) {
if (!first) result.append(" ");
result.append("}");
}
if (!found) {} //TODO: use default value!
}
private static void interpolateNamedArguments(
StringBuilder result,
Tree.InvocationExpression that,
Tree.StaticMemberOrTypeExpression it,
boolean sequenced,
List<CommonToken> tokens) {
boolean found = false;
List<Tree.NamedArgument> args =
that.getNamedArgumentList()
.getNamedArguments();
for (Tree.NamedArgument arg: args) {
FunctionOrValue pm =
arg.getParameter()
.getModel();
if (it.getDeclaration().equals(pm)) {
Tree.SpecifiedArgument sa =
(Tree.SpecifiedArgument) arg;
Tree.Term argTerm =
sa.getSpecifierExpression()
.getExpression()
.getTerm();
result//.append(template.substring(start,it.getStartIndex()-templateStart))
.append(Nodes.text(argTerm, tokens));
//start = it.getStopIndex()-templateStart+1;
found=true;
}
}
Tree.SequencedArgument seqArg =
that.getNamedArgumentList()
.getSequencedArgument();
if (seqArg!=null) {
FunctionOrValue spm =
seqArg.getParameter()
.getModel();
if (it.getDeclaration().equals(spm)) {
result//.append(template.substring(start,it.getStartIndex()-templateStart))
.append("{");
//start = it.getStopIndex()-templateStart+1;;
boolean first = true;
List<Tree.PositionalArgument> pargs =
seqArg.getPositionalArguments();
for (Tree.PositionalArgument pa: pargs) {
if (first) result.append(" ");
if (!first) result.append(", ");
first=false;
result.append(Nodes.text(pa, tokens));
}
if (!first) result.append(" ");
result.append("}");
found=true;
}
}
if (!found) {
if (sequenced) {
result.append("{}");
}
else {} //TODO: use default value!
}
}
public Declaration getDeclaration() {
return declaration;
}
public void setDelete() {
this.delete = !delete;
}
public void setJustOne() {
this.justOne = !justOne;
}
}