/***** BEGIN LICENSE BLOCK ***** * Version: CPL 1.0/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Common Public * License Version 1.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/cpl-v10.html * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * Copyright (C) 2006-2007 Mirko Stocker <me@misto.ch> * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the CPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the CPL, the GPL or the LGPL. ***** END LICENSE BLOCK *****/ package org.rubypeople.rdt.core.formatter; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.math.BigInteger; import java.util.ArrayList; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jruby.ast.*; import org.jruby.ast.types.INameNode; import org.jruby.ast.visitor.NodeVisitor; import org.jruby.lexer.yacc.ISourcePosition; import org.jruby.parser.StaticScope; import org.rubypeople.rdt.internal.formatter.rewriter.ClassBodyWriter; import org.rubypeople.rdt.internal.formatter.rewriter.Operators; /** * Visits each node and outputs the corresponding Ruby sourcecode for the nodes. * * @author Mirko Stocker */ public class ReWriteVisitor implements NodeVisitor { protected final ReWriterContext config; protected final ReWriterFactory factory; public ReWriteVisitor(Writer out, String source) { this(new ReWriterContext(new PrintWriter(out), source, new EditableFormatHelper())); } public ReWriteVisitor(OutputStream out, String source) { this(new ReWriterContext(new PrintWriter(out, true), source, new EditableFormatHelper())); } public ReWriteVisitor(ReWriterContext config) { this.config = config; factory = new ReWriterFactory(config); } public void flushStream() { config.getOutput().flush(); } protected void print(String s) { config.getOutput().print(s); } protected void print(char c) { config.getOutput().print(c); } protected void print(BigInteger i) { config.getOutput().print(i); } protected void print(int i) { config.getOutput().print(i); } protected void print(long l) { config.getOutput().print(l); } protected void print(double d) { config.getOutput().print(d); } private void enterCall() { config.getCallDepth().enterCall(); } private void leaveCall() { config.getCallDepth().leaveCall(); } private boolean inCall() { return config.getCallDepth().inCall(); } protected void printNewlineAndIndentation() { print(config.getFormatHelper().getLineDelimiter()); config.getIndentor().printIndentation(config.getOutput()); } private static boolean isReceiverACallNode(CallNode n) { return (n.getReceiverNode() instanceof CallNode || n.getReceiverNode() instanceof FCallNode); } private void printCommentsBefore(Node iVisited) { for (CommentNode n : iVisited.getComments()) { if (getStartLine(n) < getStartLine(iVisited)) { visitNode(n); printComment(n.getContent()); printNewlineAndIndentation(); } } } private void printComment(String content) { if (content == null) return; if (content.trim().endsWith("=end") && content.startsWith("begin")) { print('='); } print(content); } protected boolean printCommentsAfter(Node iVisited) { boolean hasComment = false; for (CommentNode n : iVisited.getComments()) { if (getStartLine(n) >= getEndLine(iVisited)) { print(' '); visitNode(n); print(n.getContent()); hasComment = true; } } return hasComment; } public void visitNode(Node iVisited) { if (iVisited == null || iVisited.isInvisible()) return; printCommentsBefore(iVisited); if (iVisited instanceof ArgumentNode) { print(((ArgumentNode) iVisited).getName()); } else { iVisited.accept(this); } printCommentsAfter(iVisited); config.setLastPosition(iVisited.getPosition()); } public void visitIter(Iterator<? extends Node> iterator) { while (iterator.hasNext()) { visitNode(iterator.next()); } } private void visitIterAndSkipFirst(Iterator<? extends Node> iterator) { iterator.next(); visitIter(iterator); } private static boolean isStartOnNewLine(Node first, Node second) { if (first == null || second == null) return false; return (getStartLine(first) < getStartLine(second)); } private boolean needsParentheses(Node n) { return (n != null && (n.childNodes().size() > 1 || inCall() || firstChild(n) instanceof HashNode) || firstChild(n) instanceof NewlineNode || firstChild(n) instanceof IfNode); } private void printCallArguments(Node argsNode, Node iterNode) { if (argsNode != null && argsNode.childNodes().size() < 1 && iterNode == null) return; if (argsNode != null && argsNode.childNodes().size() == 1 && firstChild(argsNode) instanceof HashNode && iterNode == null) { HashNode hashNode = (HashNode) firstChild(argsNode); if (hashNode.getListNode().childNodes().size() < 1) { print("({})"); } else { print(' '); printHashNodeContent(hashNode); } return; } boolean paranthesesPrinted = needsParentheses(argsNode) || (argsNode == null && iterNode != null && iterNode instanceof BlockPassNode) || (argsNode != null && argsNode.childNodes().size() > 0 && iterNode != null); if (paranthesesPrinted) { print('('); } else if (argsNode != null) { print(config.getFormatHelper().beforeCallArguments()); } if (firstChild(argsNode) instanceof NewlineNode) { config.setSkipNextNewline(true); } enterCall(); if (argsNode instanceof SplatNode) { visitNode(argsNode); } else if (argsNode != null) { visitAndPrintWithSeparator(argsNode.childNodes().iterator()); } if (iterNode instanceof BlockPassNode) { if (argsNode != null) print(config.getFormatHelper().getListSeparator()); print('&'); visitNode(((BlockPassNode) iterNode).getBodyNode()); } if (paranthesesPrinted) { print(')'); } else { print(config.getFormatHelper().afterCallArguments()); } leaveCall(); } public void visitAndPrintWithSeparator(Iterator<Node> it) { while (it.hasNext()) { Node n = it.next(); factory.createIgnoreCommentsReWriteVisitor().visitNode(n); if (it.hasNext()) print(config.getFormatHelper().getListSeparator()); if (n.hasComments()) { factory.createReWriteVisitor().visitIter(n.getComments().iterator()); printNewlineAndIndentation(); } } } public Object visitAliasNode(AliasNode iVisited) { print("alias "); print(iVisited.getNewName()); print(' '); print(iVisited.getOldName()); printCommentsAtEnd(iVisited); return null; } private boolean sourceRangeContains(ISourcePosition pos, String searched) { return pos.getStartOffset() < config.getSource().length() && pos.getEndOffset() < config.getSource().length() + 1 && config.getSource().substring(pos.getStartOffset(), pos.getEndOffset()).indexOf(searched) > -1; } public Object visitAndNode(AndNode iVisited) { enterCall(); visitNode(iVisited.getFirstNode()); if (sourceRangeContains(iVisited.getPosition(), "&&")) { print(" && "); } else { print(" and "); } visitNode(iVisited.getSecondNode()); leaveCall(); return null; } private ArrayList<Node> collectAllArguments(ArgsNode iVisited) { ArrayList<Node> arguments = new ArrayList<Node>(); if (iVisited.getPre() != null) arguments.addAll(iVisited.getPre().childNodes()); if (iVisited.getOptArgs() != null) arguments.addAll(iVisited.getOptArgs().childNodes()); if (iVisited.getRestArgNode() != null) { arguments.add(new ConstNode(iVisited.getRestArgNode().getPosition(), '*' + iVisited.getRestArgNode() .getName())); } if (iVisited.getPost() != null) arguments.addAll(iVisited.getPost().childNodes()); if (iVisited.getBlock() != null) arguments.add(iVisited.getBlock()); return arguments; } private boolean hasNodeCommentsAtEnd(Node n) { for (Node comment : n.getComments()) { if (getStartLine(comment) == getStartLine(n)) return true; } return false; } private void printCommentsInArgs(Node n, boolean hasNext) { if (hasNodeCommentsAtEnd(n) && hasNext) print(","); if (printCommentsAfter(n) && hasNext) { printNewlineAndIndentation(); } else if (hasNext) { print(config.getFormatHelper().getListSeparator()); } } public Object visitArgsNode(ArgsNode iVisited) { for (Iterator<Node> it = collectAllArguments(iVisited).iterator(); it.hasNext();) { Node n = it.next(); if (n instanceof ArgumentNode) { print(((ArgumentNode) n).getName()); printCommentsInArgs(n, it.hasNext()); } else { visitNode(n); if (it.hasNext()) print(config.getFormatHelper().getListSeparator()); } if (!it.hasNext()) print(config.getFormatHelper().afterMethodArguments()); } return null; } public Object visitArgsCatNode(ArgsCatNode iVisited) { print("["); visitAndPrintWithSeparator(iVisited.getFirstNode().childNodes().iterator()); print(config.getFormatHelper().getListSeparator()); print("*"); visitNode(iVisited.getSecondNode()); print("]"); return null; } public Object visitArrayNode(ArrayNode iVisited) { print('['); enterCall(); visitAndPrintWithSeparator(iVisited.childNodes().iterator()); leaveCall(); print(']'); return null; } public Object visitBackRefNode(BackRefNode iVisited) { print('$'); print(iVisited.getType()); return null; } public Object visitBeginNode(BeginNode iVisited) { print("begin"); if (getStartLine(iVisited) == getEndLine(iVisited.getBodyNode())) // one-liner { config.setSkipNextNewline(true); print(" "); visitNode(iVisited.getBodyNode()); print(" "); } else { visitNodeInIndentation(iVisited.getBodyNode()); printNewlineAndIndentation(); } print("end"); return null; } public Object visitBignumNode(BignumNode iVisited) { print(iVisited.getValue()); return null; } public Object visitBlockArgNode(BlockArgNode iVisited) { print('&'); print(iVisited.getName()); return null; } public Object visitBlockNode(BlockNode iVisited) { visitIter(iVisited.childNodes().iterator()); return null; } public static int getLocalVarIndex(Node n) { return n instanceof LocalVarNode ? ((LocalVarNode) n).getIndex() : -1; } public Object visitBlockPassNode(BlockPassNode iVisited) { visitNode(iVisited.getBodyNode()); return null; } public Object visitBreakNode(BreakNode iVisited) { print("break"); return null; } public Object visitConstDeclNode(ConstDeclNode iVisited) { printAsgnNode(iVisited); return null; } public Object visitClassVarAsgnNode(ClassVarAsgnNode iVisited) { printAsgnNode(iVisited); return null; } public Object visitClassVarDeclNode(ClassVarDeclNode iVisited) { printAsgnNode(iVisited); return null; } public Object visitClassVarNode(ClassVarNode iVisited) { print(iVisited.getName()); return null; } private boolean isNumericNode(Node n) { return (n != null && (n instanceof FixnumNode || n instanceof BignumNode)); } private boolean isNameAnOperator(String name) { return Operators.contain(name); } private boolean printSpaceInsteadOfDot(CallNode n) { return (isNameAnOperator(n.getName()) && !(n.getArgsNode().childNodes().size() > 1)); } protected void printAssignmentOperator() { print(config.getFormatHelper().beforeAssignment()); print("="); print(config.getFormatHelper().afterAssignment()); } private Object printIndexAssignment(AttrAssignNode iVisited) { enterCall(); visitNode(iVisited.getReceiverNode()); leaveCall(); print('['); visitNode(firstChild(iVisited.getArgsNode())); print("]"); printAssignmentOperator(); if (iVisited.getArgsNode().childNodes().size() > 1) visitNode((Node) iVisited.getArgsNode().childNodes().get(1)); return null; } private Object printIndexAccess(CallNode visited) { enterCall(); visitNode(visited.getReceiverNode()); leaveCall(); print('['); if (visited.getArgsNode() != null) { visitAndPrintWithSeparator(visited.getArgsNode().childNodes().iterator()); } print("]"); return null; } private Object printNegativNumericNode(CallNode visited) { print('-'); visitNode(visited.getReceiverNode()); return null; } private boolean isNegativeNumericNode(CallNode visited) { return isNumericNode(visited.getReceiverNode()) && visited.getName().equals("-@"); } private void printCallReceiverNode(CallNode iVisited) { if (iVisited.getReceiverNode() instanceof HashNode) print('('); if (isReceiverACallNode(iVisited) && !printSpaceInsteadOfDot(iVisited)) { enterCall(); visitNewlineInParentheses(iVisited.getReceiverNode()); leaveCall(); } else { visitNewlineInParentheses(iVisited.getReceiverNode()); } if (iVisited.getReceiverNode() instanceof HashNode) print(')'); } protected boolean inMultipleAssignment() { return false; } public Object visitCallNode(CallNode iVisited) { if (isNegativeNumericNode(iVisited)) return printNegativNumericNode(iVisited); if (iVisited.getName().equals("[]")) return printIndexAccess(iVisited); printCallReceiverNode(iVisited); print(printSpaceInsteadOfDot(iVisited) ? ' ' : '.'); if (inMultipleAssignment() && iVisited.getName().endsWith("=")) { print(iVisited.getName().substring(0, iVisited.getName().length() - 1)); } else { print(iVisited.getName()); } if (isNameAnOperator(iVisited.getName())) { if (firstChild(iVisited.getArgsNode()) instanceof NewlineNode) print(' '); config.getCallDepth().disableCallDepth(); } printCallArguments(iVisited.getArgsNode(), iVisited.getIterNode()); if (isNameAnOperator(iVisited.getName())) config.getCallDepth().enableCallDepth(); if (!(iVisited.getIterNode() instanceof BlockPassNode)) visitNode(iVisited.getIterNode()); return null; } public Object visitCaseNode(CaseNode iVisited) { print("case "); visitNode(iVisited.getCaseNode()); visitNode(iVisited.getCases().get(0)); printNewlineAndIndentation(); print("end"); return null; } private boolean printCommentsIn(Node iVisited) { boolean hadComment = false; for (CommentNode n : iVisited.getComments()) { if (getStartLine(n) > getStartLine(iVisited) && getEndLine(n) < getEndLine(iVisited)) { hadComment = true; visitNode(n); printComment(n.getContent()); printNewlineAndIndentation(); } } return hadComment; } public Object visitClassNode(ClassNode iVisited) { print("class "); visitNode(iVisited.getCPath()); if (iVisited.getSuperNode() != null) { print(" < "); visitNode(iVisited.getSuperNode()); } new ClassBodyWriter(this, iVisited.getBodyNode()).write(); printNewlineAndIndentation(); printCommentsIn(iVisited); print("end"); return null; } public Object visitColon2Node(Colon2Node iVisited) { if (iVisited.getLeftNode() != null) { visitNode(iVisited.getLeftNode()); print("::"); } print(iVisited.getName()); return null; } public Object visitColon3Node(Colon3Node iVisited) { if (!(iVisited instanceof Colon2ImplicitNode)) { print("::"); } print(iVisited.getName()); return null; } public Object visitConstNode(ConstNode iVisited) { print(iVisited.getName()); return null; } public Object visitDAsgnNode(DAsgnNode iVisited) { printAsgnNode(iVisited); return null; } public Object visitDRegxNode(DRegexpNode iVisited) { config.getPrintQuotesInString().set(false); print(getFirstRegexpEnclosure(iVisited)); factory.createDRegxReWriteVisitor().visitIter(iVisited.childNodes().iterator()); print(getSecondRegexpEnclosure(iVisited)); printRegexpOptions(iVisited.getOptions()); config.getPrintQuotesInString().revert(); return null; } public Object visitDStrNode(DStrNode iVisited) { if (firstChild(iVisited) instanceof StrNode) { StrNode str = (StrNode) firstChild(iVisited); // look for a here-document: String realSource = getStringSource(str); if (realSource != null && realSource.startsWith("<<")) { print(realSource.trim()); return null; } } if (config.getPrintQuotesInString().isTrue()) print(getSeparatorForStr(iVisited)); config.getPrintQuotesInString().set(false); leaveCall(); for (Node child : iVisited.childNodes()) { visitNode(child); } enterCall(); config.getPrintQuotesInString().revert(); if (config.getPrintQuotesInString().isTrue()) print(getSeparatorForStr(iVisited)); return null; } public Object visitDSymbolNode(DSymbolNode iVisited) { print(':'); if (config.getPrintQuotesInString().isTrue()) print(getSeparatorForSym(iVisited)); config.getPrintQuotesInString().set(false); leaveCall(); for (Node child : iVisited.childNodes()) { visitNode(child); } enterCall(); config.getPrintQuotesInString().revert(); if (config.getPrintQuotesInString().isTrue()) print(getSeparatorForSym(iVisited)); return null; } public Object visitDVarNode(DVarNode iVisited) { print(iVisited.getName()); return null; } public Object visitDXStrNode(DXStrNode iVisited) { config.getPrintQuotesInString().set(false); print("%x{"); visitIter(iVisited.childNodes().iterator()); print('}'); config.getPrintQuotesInString().revert(); return null; } public Object visitDefinedNode(DefinedNode iVisited) { print("defined? "); enterCall(); visitNode(iVisited.getExpressionNode()); leaveCall(); return null; } private boolean hasArguments(Node n) { if (n instanceof ArgsNode) { ArgsNode args = (ArgsNode) n; return (args.getPre() != null || args.getOptArgs() != null || args.getBlock() != null || args .getRestArgNode() != null); } else if (n instanceof ArrayNode && n.childNodes().isEmpty()) { return false; } return true; } protected void printCommentsAtEnd(Node n) { for (CommentNode comment : n.getComments()) { if (getStartLine(n) == getStartLine(comment)) { print(' '); visitNode(comment); print(comment.getContent()); } } } private void printDefNode(Node parent, String name, Node args, StaticScope scope, Node bodyNode) { print(name); config.getLocalVariables().addLocalVariable(scope); if (hasArguments(args)) { print(config.getFormatHelper().beforeMethodArguments()); visitNode(args); } printCommentsAtEnd(parent); visitNode(bodyNode); config.getIndentor().outdent(); printNewlineAndIndentation(); printCommentsIn(parent); print("end"); } public Object visitDefnNode(DefnNode iVisited) { config.getIndentor().indent(); print("def "); printDefNode(iVisited, iVisited.getName(), iVisited.getArgsNode(), iVisited.getScope(), iVisited.getBodyNode()); return null; } public Object visitDefsNode(DefsNode iVisited) { config.getIndentor().indent(); print("def "); visitNode(iVisited.getReceiverNode()); print('.'); printDefNode(iVisited, iVisited.getName(), iVisited.getArgsNode(), iVisited.getScope(), iVisited.getBodyNode()); return null; } public Object visitDotNode(DotNode iVisited) { enterCall(); visitNode(iVisited.getBeginNode()); print(".."); if (iVisited.isExclusive()) print('.'); visitNode(iVisited.getEndNode()); leaveCall(); return null; } public Object visitEnsureNode(EnsureNode iVisited) { visitNode(iVisited.getBodyNode()); config.getIndentor().outdent(); printNewlineAndIndentation(); print("ensure"); visitNodeInIndentation(iVisited.getEnsureNode()); config.getIndentor().indent(); return null; } public Object visitEvStrNode(EvStrNode iVisited) { print('#'); if (!(iVisited.getBody() instanceof NthRefNode)) print('{'); config.getPrintQuotesInString().set(true); visitNode(unwrapNewlineNode(iVisited.getBody())); config.getPrintQuotesInString().revert(); if (!(iVisited.getBody() instanceof NthRefNode)) print('}'); return null; } private Node unwrapNewlineNode(Node node) { return node instanceof NewlineNode ? ((NewlineNode) node).getNextNode() : node; } public Object visitFCallNode(FCallNode iVisited) { print(iVisited.getName()); if (iVisited.getIterNode() != null) config.getCallDepth().enterCall(); // If the function matches a local variable name and has no args, we should print parens anyways! if ((iVisited.getArgsNode() == null || iVisited.getArgsNode().childNodes().isEmpty()) && matchingLocalVar(iVisited.getName())) { print("()"); } printCallArguments(iVisited.getArgsNode(), iVisited.getIterNode()); if (iVisited.getIterNode() != null) config.getCallDepth().leaveCall(); if (!(iVisited.getIterNode() instanceof BlockPassNode)) visitNode(iVisited.getIterNode()); return null; } private boolean matchingLocalVar(String name) { for (String varName : config.getLocalVariables().getNames()) { if (varName.equals(name)) return true; } return false; } public Object visitFalseNode(FalseNode iVisited) { print("false"); return null; } public Object visitFixnumNode(FixnumNode iVisited) { print(iVisited.getValue()); return null; } public Object visitFlipNode(FlipNode iVisited) { enterCall(); visitNode(iVisited.getBeginNode()); print(" .."); if (iVisited.isExclusive()) print('.'); print(' '); visitNode(iVisited.getEndNode()); leaveCall(); return null; } public Object visitFloatNode(FloatNode iVisited) { print(iVisited.getValue()); return null; } public Object visitForNode(ForNode iVisited) { print("for "); visitNode(iVisited.getVarNode()); print(" in "); visitNode(iVisited.getIterNode()); visitNodeInIndentation(iVisited.getBodyNode()); printNewlineAndIndentation(); print("end"); return null; } public Object visitGlobalAsgnNode(GlobalAsgnNode iVisited) { printAsgnNode(iVisited); return null; } public Object visitGlobalVarNode(GlobalVarNode iVisited) { print(iVisited.getName()); return null; } private void printHashNodeContent(HashNode iVisited) { print(config.getFormatHelper().beforeHashContent()); if (iVisited.getListNode() != null) { for (Iterator<Node> it = iVisited.getListNode().childNodes().iterator(); it.hasNext();) { visitNode(it.next()); print(config.getFormatHelper().hashAssignment()); visitNode(it.next()); if (it.hasNext()) print(config.getFormatHelper().getListSeparator()); } } print(config.getFormatHelper().afterHashContent()); } public Object visitHashNode(HashNode iVisited) { print('{'); printHashNodeContent(iVisited); print('}'); return null; } private void printAsgnNode(AssignableNode n) { String name = ((INameNode) n).getName(); print(name); if (n.getValueNode() == null || n.getValueNode().isInvisible()) return; // If the value node's receiver is the same name and value node is a call to '+', handle specially! if (config.getFormatHelper().collapseOperatorSelfAssignments()) { if (n.getValueNode() instanceof CallNode) { CallNode call = (CallNode) n.getValueNode(); if (call.getName().equals("+") || call.getName().equals("-") || call.getName().equals("*")) { if (call.getReceiverNode() instanceof INameNode) { INameNode recvr = (INameNode) call.getReceiverNode(); if (recvr.getName().equals(name)) { print(config.getFormatHelper().beforeAssignment()); print(call.getName()); print("="); printCallArguments(call.getArgsNode(), call.getIterNode()); return; } } } } } printAssignmentOperator(); visitNewlineInParentheses(n.getValueNode()); } public Object visitInstAsgnNode(InstAsgnNode iVisited) { printAsgnNode(iVisited); return null; } public Object visitInstVarNode(InstVarNode iVisited) { print(iVisited.getName()); return null; } /** * Elsif-conditions in the AST are represented by multiple nested if / else combinations. This method takes a node * and checks if the node is an elsif-statement or a normal else node. * * @param iVisited * @return Returns the last ElseNode or null. */ private Node printElsIfNodes(Node iVisited) { if (iVisited != null && iVisited instanceof IfNode) { IfNode n = (IfNode) iVisited; printNewlineAndIndentation(); print("elsif "); visitNode(n.getCondition()); visitNodeInIndentation(n.getThenBody()); return printElsIfNodes(n.getElseBody()); } return iVisited != null ? iVisited : null; } private Object printShortIfStatement(IfNode n) { if (n.getThenBody() == null) { visitNode(n.getElseBody()); print(" unless "); visitNode(n.getCondition()); } else { enterCall(); factory.createShortIfNodeReWriteVisitor().visitNode(n.getCondition()); print(" ? "); factory.createShortIfNodeReWriteVisitor().visitNode(n.getThenBody()); print(" : "); factory.createShortIfNodeReWriteVisitor().visitNewlineInParentheses(n.getElseBody()); leaveCall(); } return null; } private boolean isAssignment(Node n) { return (n instanceof DAsgnNode || n instanceof GlobalAsgnNode || n instanceof InstAsgnNode || n instanceof LocalAsgnNode || n instanceof ClassVarAsgnNode); } private boolean sourceSubStringEquals(int offset, int length, String str) { return config.getSource().length() >= offset + length && config.getSource().substring(offset, offset + length).equals(str); } private boolean isShortIfStatement(IfNode iVisited) { return (isOnSingleLine(iVisited.getCondition(), iVisited.getElseBody()) && !(iVisited.getElseBody() instanceof IfNode) && !sourceSubStringEquals(getStartOffset(iVisited), 2, "if")); } public Object visitIfNode(IfNode iVisited) { if (isShortIfStatement(iVisited)) return printShortIfStatement(iVisited); if (isIfModifier(iVisited)) return printIfModifier(iVisited); print("if "); if (isAssignment(iVisited.getCondition())) enterCall(); // We have to skip a possible Newline here: visitNewlineInParentheses(iVisited.getCondition()); if (isAssignment(iVisited.getCondition())) leaveCall(); config.getIndentor().indent(); // we have to check this to generate valid code for this style: "return // if true", because there is no newline if (!isStartOnNewLine(iVisited.getCondition(), iVisited.getThenBody()) && iVisited.getThenBody() != null) { printNewlineAndIndentation(); config.setSkipNextNewline(true); } visitNode(iVisited.getThenBody()); config.getIndentor().outdent(); Node elseNode = printElsIfNodes(iVisited.getElseBody()); if (elseNode != null) { printNewlineAndIndentation(); print("else"); config.getIndentor().indent(); visitNode(elseNode); config.getIndentor().outdent(); } printNewlineAndIndentation(); print("end"); return null; } private Object printIfModifier(IfNode iVisited) { visitNode(iVisited.getThenBody()); print(" if "); if (isAssignment(iVisited.getCondition())) enterCall(); // We have to skip a possible Newline here: visitNewlineInParentheses(iVisited.getCondition()); if (isAssignment(iVisited.getCondition())) leaveCall(); return null; } private boolean isIfModifier(IfNode visited) { // condition is after then Node then = visited.getThenBody(); if (then == null) return false; Node condition = visited.getCondition(); if (condition.getPosition().getStartOffset() > then.getPosition().getEndOffset()) return true; return false; } private boolean isOnSingleLine(Node n) { return isOnSingleLine(n, n); } private boolean isOnSingleLine(Node n1, Node n2) { if (n1 == null || n2 == null) return false; return (getStartLine(n1) == getEndLine(n2)); } private boolean printIterVarNode(IterNode n) { if (n.getVarNode() == null) return false; print('|'); visitNode(n.getVarNode()); print('|'); return true; } public Object visitIterNode(IterNode iVisited) { if (isOnSingleLine(iVisited)) { print(config.getFormatHelper().beforeIterBrackets()); print("{"); print(config.getFormatHelper().beforeIterVars()); if (printIterVarNode(iVisited)) print(config.getFormatHelper().afterIterVars()); config.setSkipNextNewline(true); visitNode(iVisited.getBodyNode()); print(config.getFormatHelper().beforeClosingIterBrackets()); print('}'); } else { print(" do "); printIterVarNode(iVisited); visitNodeInIndentation(iVisited.getBodyNode()); printNewlineAndIndentation(); print("end"); } return null; } public Object visitLocalAsgnNode(LocalAsgnNode iVisited) { config.getLocalVariables().addLocalVariable(iVisited.getIndex(), iVisited.getName()); printAsgnNode(iVisited); return null; } public Object visitLocalVarNode(LocalVarNode iVisited) { print(iVisited.getName()); return null; } public Object visitMultipleAsgnNode(MultipleAsgnNode iVisited) { if (iVisited.getHeadNode() != null) { factory.createMultipleAssignmentReWriteVisitor().visitAndPrintWithSeparator( iVisited.getHeadNode().childNodes().iterator()); } if (iVisited.getValueNode() == null || iVisited.getValueNode().isInvisible()) { visitNode(iVisited.getArgsNode()); return null; } print(config.getFormatHelper().beforeAssignment()); print("="); print(config.getFormatHelper().afterAssignment()); enterCall(); if (iVisited.getValueNode() instanceof ArrayNode) { visitAndPrintWithSeparator(iVisited.getValueNode().childNodes().iterator()); } else { visitNode(iVisited.getValueNode()); } leaveCall(); return null; } public Object visitMultipleAsgnNode(MultipleAsgn19Node iVisited) { if (iVisited.getPre() != null) { factory.createMultipleAssignmentReWriteVisitor().visitAndPrintWithSeparator( iVisited.getPre().childNodes().iterator()); } if (iVisited.getValueNode() == null || iVisited.getValueNode().isInvisible()) { visitNode(iVisited.getRest()); return null; } print(config.getFormatHelper().beforeAssignment()); print("="); print(config.getFormatHelper().afterAssignment()); enterCall(); if (iVisited.getValueNode() instanceof ArrayNode) { visitAndPrintWithSeparator(iVisited.getValueNode().childNodes().iterator()); } else { visitNode(iVisited.getValueNode()); } leaveCall(); return null; } public Object visitMatch2Node(Match2Node iVisited) { visitNode(iVisited.getReceiverNode()); print(config.getFormatHelper().matchOperator()); enterCall(); visitNode(iVisited.getValueNode()); leaveCall(); return null; } public Object visitMatch3Node(Match3Node iVisited) { visitNode(iVisited.getValueNode()); print(config.getFormatHelper().matchOperator()); visitNode(iVisited.getReceiverNode()); return null; } public Object visitMatchNode(MatchNode iVisited) { visitNode(iVisited.getRegexpNode()); return null; } public Object visitModuleNode(ModuleNode iVisited) { print("module "); config.getIndentor().indent(); visitNode(iVisited.getCPath()); visitNode(iVisited.getBodyNode()); config.getIndentor().outdent(); printNewlineAndIndentation(); print("end"); return null; } public Object visitNewlineNode(NewlineNode iVisited) { if (config.isSkipNextNewline()) { config.setSkipNextNewline(false); } else { printNewlineAndIndentation(); } visitNode(iVisited.getNextNode()); return null; } public Object visitNextNode(NextNode iVisited) { print("next"); return null; } public Object visitNilNode(NilNode iVisited) { print("nil"); return null; } public Object visitNotNode(NotNode iVisited) { if (iVisited.getConditionNode() instanceof CallNode) { CallNode call = (CallNode) iVisited.getConditionNode(); String name = call.getName(); if (name.equals("==")) { printCallReceiverNode(call); print(" !="); if (firstChild(call.getArgsNode()) instanceof NewlineNode) print(' '); config.getCallDepth().disableCallDepth(); printCallArguments(call.getArgsNode(), call.getIterNode()); config.getCallDepth().enableCallDepth(); return null; } enterCall(); } print(sourceRangeContains(iVisited.getPosition(), "not") ? "not " : "!"); visitNewlineInParentheses(iVisited.getConditionNode()); if (iVisited.getConditionNode() instanceof CallNode) leaveCall(); return null; } public Object visitNthRefNode(NthRefNode iVisited) { print('$'); print(iVisited.getMatchNumber()); return null; } private boolean isSimpleNode(Node n) { return (n instanceof LocalVarNode || n instanceof AssignableNode || n instanceof InstVarNode || n instanceof ClassVarNode || n instanceof GlobalVarNode || n instanceof ConstDeclNode || n instanceof VCallNode || isNumericNode(n)); } public Object visitOpElementAsgnNode(OpElementAsgnNode iVisited) { if (!isSimpleNode(iVisited.getReceiverNode())) { visitNewlineInParentheses(iVisited.getReceiverNode()); } else { visitNode(iVisited.getReceiverNode()); } visitNode(iVisited.getArgsNode()); print(' '); print(iVisited.getOperatorName()); print("="); print(config.getFormatHelper().afterAssignment()); visitNode(iVisited.getValueNode()); return null; } public Object visitOpAsgnNode(OpAsgnNode iVisited) { visitNode(iVisited.getReceiverNode()); print('.'); print(iVisited.getVariableName()); print(' '); print(iVisited.getOperatorName()); print("="); print(config.getFormatHelper().afterAssignment()); visitNode(iVisited.getValueNode()); return null; } private void printOpAsgnNode(Node n, String operator) { enterCall(); print(((INameNode) n).getName()); print(config.getFormatHelper().beforeAssignment()); print(operator); print(config.getFormatHelper().afterAssignment()); visitNode(((AssignableNode) n).getValueNode()); leaveCall(); } public Object visitOpAsgnAndNode(OpAsgnAndNode iVisited) { printOpAsgnNode(iVisited.getSecondNode(), "&&="); return null; } public Object visitOpAsgnOrNode(OpAsgnOrNode iVisited) { printOpAsgnNode(iVisited.getSecondNode(), "||="); return null; } public Object visitOrNode(OrNode iVisited) { enterCall(); visitNode(iVisited.getFirstNode()); leaveCall(); print(sourceRangeContains(iVisited.getPosition(), "||") ? " || " : " or "); enterCall(); visitNewlineInParentheses(iVisited.getSecondNode()); leaveCall(); return null; } public Object visitPostExeNode(PostExeNode iVisited) { // this node contains nothing but an empty list, so we don't have to // process anything return null; } public Object visitPreExeNode(PreExeNode iVisited) { // this node contains nothing but an empty list, so we don't have to // process anything return null; } public Object visitRedoNode(RedoNode iVisited) { print("redo"); return null; } private String getFirstRegexpEnclosure(Node n) { return isSpecialRegexNotation(n) ? "%r(" : "/"; } private String getSecondRegexpEnclosure(Node n) { return isSpecialRegexNotation(n) ? ")" : "/"; } private boolean isSpecialRegexNotation(Node n) { return getStartOffset(n) >= 2 && !(config.getSource().length() < getStartOffset(n)) && config.getSource().charAt(getStartOffset(n) - 3) == '%'; } private void printRegexpOptions(int option) { if ((option & 1) == 1) print('i'); if ((option & 2) == 2) print('x'); if ((option & 4) == 4) print('m'); } public Object visitRegexpNode(RegexpNode iVisited) { print(getFirstRegexpEnclosure(iVisited)); print(iVisited.getValue().toString()); print(getSecondRegexpEnclosure(iVisited)); printRegexpOptions(iVisited.getOptions()); return null; } public static Node firstChild(Node n) { if (n == null || n.childNodes().size() <= 0) return null; return (Node) n.childNodes().get(0); } public Object visitRescueBodyNode(RescueBodyNode iVisited) { if (!iVisited.getBodyNode().isInvisible() && config.getLastPosition().getStartLine() == getEndLine(iVisited.getBodyNode())) { print(" rescue "); } else { print("rescue"); } if (iVisited.getExceptionNodes() != null) { printExceptionNode(iVisited); } else { visitNodeInIndentation(iVisited.getBodyNode()); } if (iVisited.getOptRescueNode() != null) printNewlineAndIndentation(); visitNode(iVisited.getOptRescueNode()); return null; } private void printExceptionNode(RescueBodyNode n) { if (n.getExceptionNodes() == null) return; print(' '); visitNode(firstChild(n.getExceptionNodes())); Node firstBodyNode = n.getBodyNode(); if (n.getBodyNode() instanceof BlockNode) firstBodyNode = firstChild(n.getBodyNode()); // if the exception is assigned to a variable, we have to skip the first // node in the body if (firstBodyNode instanceof AssignableNode) { print(config.getFormatHelper().beforeAssignment()); print("=>"); print(config.getFormatHelper().afterAssignment()); print(((INameNode) firstBodyNode).getName()); if (firstBodyNode instanceof LocalAsgnNode) config.getLocalVariables().addLocalVariable(((LocalAsgnNode) firstBodyNode).getIndex(), ((LocalAsgnNode) firstBodyNode).getName()); config.getIndentor().indent(); visitIterAndSkipFirst(n.getBodyNode().childNodes().iterator()); config.getIndentor().outdent(); } else { visitNodeInIndentation(n.getBodyNode()); } } public Object visitRescueNode(RescueNode iVisited) { visitNode(iVisited.getBodyNode()); config.getIndentor().outdent(); if (iVisited.getRescueNode().getBodyNode().isInvisible()) { printNewlineAndIndentation(); print("rescue"); printExceptionNode(iVisited.getRescueNode()); } else { if (getStartLine(iVisited) != getEndLine(iVisited.getRescueNode().getBodyNode())) printNewlineAndIndentation(); visitNode(iVisited.getRescueNode()); } if (iVisited.getElseNode() != null) { printNewlineAndIndentation(); print("else"); visitNodeInIndentation(iVisited.getElseNode()); } config.getIndentor().indent(); return null; } public Object visitRetryNode(RetryNode iVisited) { print("retry"); return null; } public static Node unwrapSingleArrayNode(Node n) { if (!(n instanceof ArrayNode)) return n; if (((ArrayNode) n).childNodes().size() > 1) return n; return firstChild((ArrayNode) n); } public Object visitReturnNode(ReturnNode iVisited) { print("return"); enterCall(); if (!iVisited.getValueNode().isInvisible()) { print(' '); visitNode(unwrapSingleArrayNode(iVisited.getValueNode())); } leaveCall(); return null; } public Object visitSClassNode(SClassNode iVisited) { print("class << "); config.getIndentor().indent(); visitNode(iVisited.getReceiverNode()); visitNode(iVisited.getBodyNode()); config.getIndentor().outdent(); printNewlineAndIndentation(); print("end"); return null; } public Object visitSelfNode(SelfNode iVisited) { print("self"); return null; } public Object visitSplatNode(SplatNode iVisited) { print("*"); visitNode(iVisited.getValue()); return null; } protected char getSeparatorForSym(Node n) { // ENEBO: I added one since a sym will start with ':'...This seems like an incomplete assumption if (config.getSource().length() >= (getStartOffset(n) + 1) && config.getSource().charAt(getStartOffset(n) + 1) == '\'') { return '\''; } return '"'; } protected char getSeparatorForStr(Node n) { if (config.getSource().length() >= getStartOffset(n) && config.getSource().charAt(getStartOffset(n)) == '\'') { return '\''; } return '"'; } protected boolean inDRegxNode() { return false; } public Object visitStrNode(StrNode iVisited) { String realSource = getStringSource(iVisited); // look for a here-document: if (realSource != null && realSource.startsWith("<<")) { print(realSource.trim()); return null; } if (realSource == null) realSource = iVisited.getValue().toString(); // opening and closing of strings String opening = "" + getSeparatorForStr(iVisited); String closing = opening; if (realSource != null && realSource.startsWith("%")) { // Special % string! Keep track of the opening and closing delimiters and print them before and after string contents int index = realSource.indexOf(iVisited.getValue().toString()); opening = realSource.substring(0, index); closing = realSource.substring(index + iVisited.getValue().toString().length()).trim(); } if (realSource.equals("")) { if (config.getPrintQuotesInString().isTrue()) print("\"\""); return null; } // don't print quotes if we are a subpart of an other here-document if (config.getPrintQuotesInString().isTrue()) { print(opening); } if (inDRegxNode()) { print(iVisited.getValue().toString()); } else { Matcher matcher = Pattern.compile("([\\\\\\n\\f\\r\\t\\\"\\\'])").matcher(iVisited.getValue().toString()); // FIXME Don't escape " inside '' string // FIXME Don't escape ' inside "" string // FIXME Don't replace newlines with \n? if (matcher.find()) { String unescChar = unescapeChar(matcher.group(1).charAt(0)); print(matcher.replaceAll("\\\\" + unescChar)); } else { print(iVisited.getValue().toString()); } } if (config.getPrintQuotesInString().isTrue()) { print(closing); } return null; } private String getStringSource(StrNode n) { String src = config.getSource(); if (src == null || src.length() == 0) return null; int start = Math.max(0, n.getPosition().getStartOffset()); int end = Math.min(src.length(), n.getPosition().getEndOffset() + 1); String realValue = src.substring(start, end); return realValue; } public static String unescapeChar(char escapedChar) { switch (escapedChar) { case '\n': return "n"; case '\f': return "f"; case '\r': return "r"; case '\t': return "t"; case '\"': return "\""; case '\'': return "'"; case '\\': return "\\\\"; default: return null; } } private boolean needsSuperNodeParentheses(SuperNode n) { return n.getArgsNode().childNodes().isEmpty() && config.getSource().charAt(getEndOffset(n)) == '('; } public Object visitSuperNode(SuperNode iVisited) { print("super"); if (needsSuperNodeParentheses(iVisited)) print('('); printCallArguments(iVisited.getArgsNode(), iVisited.getIterNode()); if (needsSuperNodeParentheses(iVisited)) print(')'); return null; } public Object visitSValueNode(SValueNode iVisited) { visitNode(iVisited.getValue()); return null; } public Object visitSymbolNode(SymbolNode iVisited) { print(':'); print(iVisited.getName()); return null; } public Object visitToAryNode(ToAryNode iVisited) { visitNode(iVisited.getValue()); return null; } public Object visitTrueNode(TrueNode iVisited) { print("true"); return null; } public Object visitUndefNode(UndefNode iVisited) { print("undef "); print(iVisited.getName()); return null; } public Object visitUntilNode(UntilNode iVisited) { if (isUntilModifier(iVisited)) return printUntilModifier(iVisited); print("until "); visitNode(iVisited.getConditionNode()); visitNodeInIndentation(iVisited.getBodyNode()); printNewlineAndIndentation(); print("end"); return null; } private Object printUntilModifier(UntilNode iVisited) { visitNode(iVisited.getBodyNode()); print(" until "); visitNode(iVisited.getConditionNode()); return null; } private boolean isUntilModifier(UntilNode visited) { Node condition = visited.getConditionNode(); Node body = visited.getBodyNode(); return (body.getPosition().getEndOffset() < condition.getPosition().getStartOffset()); } public Object visitVAliasNode(VAliasNode iVisited) { print("alias "); print(iVisited.getNewName()); print(' '); print(iVisited.getOldName()); return null; } public Object visitVCallNode(VCallNode iVisited) { print(iVisited.getName()); return null; } public void visitNodeInIndentation(Node n) { config.getIndentor().indent(); visitNode(n); config.getIndentor().outdent(); } public Object visitWhenNode(WhenNode iVisited) { printNewlineAndIndentation(); print("when "); enterCall(); Node expression = iVisited.getExpressionNodes(); if (expression instanceof ListNode) { visitAndPrintWithSeparator(expression.childNodes().iterator()); } else { visitNode(expression); } leaveCall(); visitNodeInIndentation(iVisited.getBodyNode()); if ((iVisited.getNextCase() instanceof WhenNode || iVisited.getNextCase() == null)) { visitNode(iVisited.getNextCase()); } else { printNewlineAndIndentation(); print("else"); visitNodeInIndentation(iVisited.getNextCase()); } return null; } protected void visitNewlineInParentheses(Node n) { if (n instanceof NewlineNode) { if (((NewlineNode) n).getNextNode() instanceof SplatNode) { print('['); visitNode(((NewlineNode) n).getNextNode()); print(']'); } else { print('('); visitNode(((NewlineNode) n).getNextNode()); print(')'); } } else { visitNode(n); } } private void printWhileStatement(WhileNode iVisited) { if (isWhileModifier(iVisited)) { printWhileModifier(iVisited); return; } print("while "); if (isAssignment(iVisited.getConditionNode())) enterCall(); visitNewlineInParentheses(iVisited.getConditionNode()); if (isAssignment(iVisited.getConditionNode())) leaveCall(); if (config.getFormatHelper().insertDoAfterWhileExpression()) print(" do"); visitNodeInIndentation(iVisited.getBodyNode()); printNewlineAndIndentation(); print("end"); } private void printWhileModifier(WhileNode visited) { visitNode(visited.getBodyNode()); print(" while "); if (isAssignment(visited.getConditionNode())) enterCall(); visitNewlineInParentheses(visited.getConditionNode()); if (isAssignment(visited.getConditionNode())) leaveCall(); } private boolean isWhileModifier(WhileNode visited) { Node condition = visited.getConditionNode(); Node body = visited.getBodyNode(); return (body.getPosition().getEndOffset() < condition.getPosition().getStartOffset()); } private void printDoWhileStatement(WhileNode iVisited) { print("begin"); visitNodeInIndentation(iVisited.getBodyNode()); printNewlineAndIndentation(); print("end while "); visitNode(iVisited.getConditionNode()); } public Object visitWhileNode(WhileNode iVisited) { if (iVisited.evaluateAtStart()) { printWhileStatement(iVisited); } else { printDoWhileStatement(iVisited); } return null; } public Object visitXStrNode(XStrNode iVisited) { print('`'); print(iVisited.getValue().toString()); print('`'); return null; } public Object visitYieldNode(YieldNode iVisited) { print("yield"); if (iVisited.getArgsNode() != null) { print(needsParentheses(iVisited.getArgsNode()) ? '(' : ' '); enterCall(); if (iVisited.getArgsNode() instanceof ArrayNode) { visitAndPrintWithSeparator(iVisited.getArgsNode().childNodes().iterator()); } else { visitNode(iVisited.getArgsNode()); } leaveCall(); if (needsParentheses(iVisited.getArgsNode())) print(')'); } return null; } public Object visitZArrayNode(ZArrayNode iVisited) { print("[]"); return null; } public Object visitZSuperNode(ZSuperNode iVisited) { print("super"); return null; } private static int getStartLine(Node n) { return n.getPosition().getStartLine(); } private static int getStartOffset(Node n) { return n.getPosition().getStartOffset(); } private static int getEndLine(Node n) { return n.getPosition().getEndLine(); } protected static int getEndOffset(Node n) { return n.getPosition().getEndOffset(); } public ReWriterContext getConfig() { return config; } public static String createCodeFromNode(Node node, String document) { return createCodeFromNode(node, document, new EditableFormatHelper()); } public static String createCodeFromNode(Node node, String document, FormatHelper helper) { StringWriter writer = new StringWriter(); ReWriterContext ctx = new ReWriterContext(writer, document, helper); ReWriteVisitor rewriter = new ReWriteVisitor(ctx); rewriter.visitNode(node); return writer.toString(); } public Object visitArgsPushNode(ArgsPushNode node) { assert false : "Unhandled node"; return null; } public Object visitAttrAssignNode(AttrAssignNode iVisited) { if (iVisited.getName().equals("[]=")) return printIndexAssignment(iVisited); if (iVisited.getName().endsWith("=")) { visitNode(iVisited.getReceiverNode()); print('.'); printNameWithoutEqualSign(iVisited); printAssignmentOperator(); if (iVisited.getArgsNode() != null) { visitAndPrintWithSeparator(iVisited.getArgsNode().childNodes().iterator()); } } else { assert false : "Unhandled AttrAssignNode"; } return null; } private void printNameWithoutEqualSign(INameNode iVisited) { print(iVisited.getName().substring(0, iVisited.getName().length() - 1)); } public Object visitRootNode(RootNode iVisited) { config.getLocalVariables().addLocalVariable(iVisited.getStaticScope()); visitNode(iVisited.getBodyNode()); return null; } public Object visitRestArgNode(RestArgNode iVisited) { print("*" + iVisited.getName()); return null; } }