package com.mobilesorcery.sdk.html5.debug.rewrite; import java.io.ObjectInputStream.GetField; import java.text.MessageFormat; import java.util.List; import java.util.Map; import org.eclipse.text.edits.TextEdit; import org.eclipse.wst.jsdt.core.dom.AST; import org.eclipse.wst.jsdt.core.dom.ASTNode; import org.eclipse.wst.jsdt.core.dom.ASTVisitor; import org.eclipse.wst.jsdt.core.dom.Block; import org.eclipse.wst.jsdt.core.dom.CatchClause; import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration; import org.eclipse.wst.jsdt.core.dom.ReturnStatement; import org.eclipse.wst.jsdt.core.dom.SimpleName; import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration; import org.eclipse.wst.jsdt.core.dom.TryStatement; import org.eclipse.wst.jsdt.core.dom.Type; import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite; import org.eclipse.wst.jsdt.internal.compiler.util.SimpleNameVector; import com.mobilesorcery.sdk.core.IFilter; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.html5.Html5Plugin; import com.mobilesorcery.sdk.html5.debug.JSODDSupport; import com.mobilesorcery.sdk.html5.debug.Position; public class FunctionRewrite extends NodeRewrite { private final class HasReturnASTVisitor extends ASTVisitor { private int inInInnerFunction = 0; private boolean hasReturn = false; public void preVisit(ASTNode node) { if (node instanceof FunctionDeclaration) { inInInnerFunction++; } if (node instanceof ReturnStatement) { if (inInInnerFunction == 0 && ((ReturnStatement) node).getExpression() != null) { hasReturn = true; } } } public void postVisit(ASTNode node) { if (node instanceof FunctionDeclaration) { inInInnerFunction--; } } } private static final String END_OF_BODY_BUG = "End of body bug"; private static final String EMPTY_BODY_BUG = "JSDT has problems understanding this kind of construct (eg jQuery has this issue)"; private static final String OUTSIDE_PARENT = "JSDT thinks body is overflowing parent"; private long fileId; private Map<ASTNode, String> nodeRedefinables; private Block body; private boolean useEscapedThis; public FunctionRewrite(ISourceSupport rewriter, ASTNode node, long fileId, Map<ASTNode, String> nodeRedefinables) { super(rewriter, node); if (!(node instanceof FunctionDeclaration)) { throw new IllegalArgumentException("Must use the body of the function"); } this.fileId = fileId; this.nodeRedefinables = nodeRedefinables; } @Override public void rewrite(IFilter<String> features, IRewrite rewrite) { FunctionDeclaration fd = (FunctionDeclaration) getNode(); Block body = fd.getBody(); // Strange parsing problems in some instances! if (isOutsideParent(body)) { setBlacklisted(OUTSIDE_PARENT); return; } if (hasEmptyBodyBug(body)) { setBlacklisted(EMPTY_BODY_BUG); return; } List statements = body.statements(); if (hasEndOfBodyBug(body, statements)) { setBlacklisted(END_OF_BODY_BUG); return; } Position startOfBody = getPosition(body, true); Position endOfBody = getPosition(body, false); boolean emptyBody = statements.isEmpty(); int startLineOfBody = startOfBody.getLine(); int endLineOfBody = endOfBody.getLine(); SimpleName functionName = fd.getName(); boolean isAnonymous = isAnonymous(fd); String functionIdentifier = isAnonymous ? Html5Plugin.ANONYMOUS_FUNCTION : functionName.getIdentifier(); /* AST ast = rewrite.getAST(); ASTNode fdCopy = rewrite.createCopyTarget(fd); Block newBody = ast.newBlock(); Block tryBody = ast.newBlock(); String pushStackStatementStr = MessageFormat.format("MoSyncDebugProtocol.pushStack(\"{0}\",{1},{2});", functionIdentifier, Long.toString(fileId), Integer.toString(startLineOfBody)); ASTNode pushStackStatement = rewrite.createStringPlaceholder(pushStackStatementStr, ASTNode.EXPRESSION_STATEMENT); tryBody.statements().add(pushStackStatement); TryStatement functionSurround = ast.newTryStatement(); functionSurround.setBody(tryBody); CatchClause catchClause = ast.newCatchClause(); SingleVariableDeclaration exceptionDecl = ast.newSingleVariableDeclaration(); exceptionDecl.setName(ast.newSimpleName("anException")); Block catchBody = ast.newBlock(); String exceptionHandlerStr = "if (!anException.alreadyThrown && !anException.dropToFrame) {\n" + " anException = MoSyncDebugProtocol.reportException(anException, " + endLineOfBody + "," + JSODDSupport.EVAL_FUNC_SNIPPET + ");\n" + " }\n" + " anException.alreadyThrown = true;\n" + " if (!anException.dropToFrame) {\n" + " throw anException;\n" + " } else {\n" + " if (anException.expression) {\n" + " eval(anException.expression);" + " }\n" + " }\n"; rewrite.createStringPlaceholder(exceptionHandlerStr, ASTNode.) BAHH!!! catchClause.setBody(catchBody); functionSurround.catchClauses().add(catchClause); newBody.statements().add(functionSurround); rewrite.replace(body, newBody, null);*/ boolean shouldBlockify = !supports(features, JSODDSupport.DROP_TO_FRAME) && !supports(features, JSODDSupport.EDIT_AND_CONTINUE); String functionStart = MessageFormat.format( "'{' try '{' MoSyncDebugProtocol.pushStack(\"{0}\",{1},{2});", functionIdentifier, Long.toString(fileId), Integer.toString(startLineOfBody)); String functionEnd = "} catch (anException) {\n" + " if (!anException.alreadyThrown && !anException.dropToFrame) {\n" + " anException = MoSyncDebugProtocol.reportException(anException, " + endLineOfBody + "," + JSODDSupport.EVAL_FUNC_SNIPPET + ");\n" + " }\n" + " anException.alreadyThrown = true;\n" + " if (!anException.dropToFrame) {\n" + " throw anException;\n" + " } else {\n" + " if (anException.expression) {\n" + " eval(anException.expression);" + " }\n" + " }\n" + "} finally {\n" + " MoSyncDebugProtocol.popStack();\n" + "}\n}\n"; String dropToFramePreamble = supports(features, JSODDSupport.DROP_TO_FRAME) ? "{ do" : ""; String dropToFramePostamble = supports(features, JSODDSupport.DROP_TO_FRAME) ? "while (MoSyncDebugProtocol.dropToFrame()); }" : ""; String editAndContinuePreamble = ""; String editAndContinuePostamble = ""; boolean addEditAndContinue = supports(features, JSODDSupport.EDIT_AND_CONTINUE) && !isAnonymous(fd); if (addEditAndContinue) { String functionRef = functionIdentifier + ".____yaloid"; String redefineKey = nodeRedefinables.get(fd); String signaturePrefix = isAnonymous ? "" : "____"; editAndContinuePreamble = "if(!" + functionRef + ") { " + "var ____unevaled=MoSyncDebugProtocol.yaloid(\"" + redefineKey + "\");\n" + "if (____unevaled){\n" + "eval(\"" + functionRef + "=\" + ____unevaled);}\n" + "if(typeof " + functionRef + " !== \'function\')\n" + "{" + functionRef + "= function " + signaturePrefix + getSignature(fd); List parameters = fd.parameters(); String[] parameterNames = new String[parameters.size()]; for (int i = 0; i < parameterNames.length; i++) { SingleVariableDeclaration parameter = (SingleVariableDeclaration) parameters .get(i); String parameterName = parameter.getName().getIdentifier(); parameterNames[i] = parameterName; } String returnStr = hasReturn(fd.getBody()) ? "return " : ""; editAndContinuePostamble = "\nMoSyncDebugProtocol.registerFunction(\"" + redefineKey + "\"," + functionIdentifier + ");}}\n" + returnStr + functionRef + "(" + Util.join(parameterNames, ",") + ");"; } rewrite.seek(startOfBody); if (shouldBlockify) { //rewrite.insert("\n{"); } rewrite.insert(dropToFramePreamble); if (supports(features, JSODDSupport.ARTIFICIAL_STACK)) { rewrite.insert(functionStart); } if (useEscapedThis && supports(features, JSODDSupport.EDIT_AND_CONTINUE)) { if (useEscapedThis) { rewrite.insert("var ____this = this;\n"); } } //if (addEditAndContinue) { rewrite.insert("var ____arguments = arguments;\n"); //} rewrite.insert(editAndContinuePreamble); boolean firstStatement = true; for (Object statementObj : statements) { ASTNode statement = (ASTNode) statementObj; NodeRewrite stmtRewrite = getRewrite(statement); if (firstStatement && addEditAndContinue) { Position firstStmtPos = stmtRewrite.getPosition(statement, true); rewrite.seek(firstStmtPos); rewrite.insert("arguments = ____arguments;\n"); } stmtRewrite.rewrite(features, rewrite); firstStatement = false; } rewrite.seek(endOfBody); rewrite.insert(editAndContinuePostamble); if (supports(features, JSODDSupport.ARTIFICIAL_STACK)) { rewrite.insert(functionEnd); } rewrite.insert(dropToFramePostamble); if (shouldBlockify) { //rewrite.insert("\n}"); } } private boolean hasEndOfBodyBug(Block body, List statements) { // Another strange JSDT bug -- and now // don't dare using ASTRewrite; what // if the same bug is there!? // Sometimes the node length of a function is too small! // This is a non-bullet proof way to do it. Position result = getPosition(body, false); for (Object statementObj : statements) { ASTNode statement = (ASTNode) statementObj; Position endOfStatement = getPosition(statement, false); if (endOfStatement.getPosition() > result.getPosition()) { return true; } } return false; } private boolean hasEmptyBodyBug(Block body) { // JQuery 1.8 has several constructs that // confuses JSDT; hence this one. String src = getSource(body); int trimmedLen = src.length() - src.trim().length(); int bodyLength = body.getLength(); return body.statements().isEmpty() && bodyLength > trimmedLen; } private boolean isOutsideParent(Block body) { if (body == null) { return true; } // Note: A body declaration CAN be outside its parent, // but blocks cannot. int startOfBody = body.getStartPosition(); ASTNode parent = body.getParent(); while (parent != null) { if (parent.getStartPosition() > startOfBody) { return true; } parent = parent.getParent(); } return false; } private String getSignature(FunctionDeclaration fd) { // Bah. Again: bah. ASTNode node = findFirstNode(fd.modifiers(), fd.getReturnType2(), fd.getName(), fd.parameters()); if (node == null) { // Anonymous, zero-arg function return "()"; } else { int start = node.getStartPosition(); int end = fd.getBody().getStartPosition(); return rewriter.getSource(start, end); } } private ASTNode findFirstNode(Object... nodes) { for (int i = 0; i < nodes.length; i++) { Object nodeObj = nodes[i]; if (nodeObj instanceof List) { ASTNode result = findFirstNode(((List) nodeObj).toArray()); if (result != null) { return result; } } else { ASTNode node = (ASTNode) nodeObj; if (node != null) { return node; } } } return null; } public ASTNode getReplacedNode() { return body; } private boolean isAnonymous(FunctionDeclaration fd) { return fd.getName() == null; } private boolean hasReturn(Block statements) { HasReturnASTVisitor visitor = new HasReturnASTVisitor(); statements.accept(visitor); return visitor.hasReturn; } public void useEscapedThis(boolean useEscapedThis) { this.useEscapedThis = useEscapedThis; } }