/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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.template.soy.jssrc.internal; import com.google.template.soy.html.AbstractReturningHtmlSoyNodeVisitor; import com.google.template.soy.html.IncrementalHtmlAttributeNode; import com.google.template.soy.html.IncrementalHtmlCloseTagNode; import com.google.template.soy.html.IncrementalHtmlOpenTagNode; import com.google.template.soy.shared.internal.ApiCallScope; import com.google.template.soy.soytree.CallNode; import com.google.template.soy.soytree.CallParamContentNode; import com.google.template.soy.soytree.CallParamValueNode; import com.google.template.soy.soytree.CssNode; import com.google.template.soy.soytree.DebuggerNode; import com.google.template.soy.soytree.ForNode; import com.google.template.soy.soytree.ForeachNode; import com.google.template.soy.soytree.IfCondNode; import com.google.template.soy.soytree.IfElseNode; import com.google.template.soy.soytree.IfNode; import com.google.template.soy.soytree.LetNode; import com.google.template.soy.soytree.LogNode; import com.google.template.soy.soytree.MsgFallbackGroupNode; import com.google.template.soy.soytree.MsgHtmlTagNode; import com.google.template.soy.soytree.MsgPlaceholderNode; import com.google.template.soy.soytree.PrintNode; import com.google.template.soy.soytree.RawTextNode; import com.google.template.soy.soytree.SoyNode; import com.google.template.soy.soytree.SoyNode.ParentSoyNode; import com.google.template.soy.soytree.SwitchNode; import com.google.template.soy.soytree.TemplateNode; import com.google.template.soy.soytree.XidNode; import java.util.HashMap; import java.util.Map; import javax.inject.Inject; /** * Visitor to determine whether the output string for the subtree rooted at a given node is * computable as the concatenation of one or more JS expressions. If this is false, it means the * generated code for computing the node's output must include one or more full JS statements. * * <p>Precondition: MsgNode should not exist in the tree. * * <p>Important: This class is in {@link ApiCallScope} because it memoizes results that are reusable * for the same parse tree. If we change the parse tree between uses of the scoped instance, then * the results may not be correct. (In that case, we would need to take this class out of {@code * ApiCallScope} and rewrite the code somehow to still take advantage of the memoized results to the * extent that they remain correct.) * * <p>TODO(user): This should no longer be necessary after CodeChunk migration. Rip it all out. * */ @ApiCallScope public class IsComputableAsJsExprsVisitor extends AbstractReturningHtmlSoyNodeVisitor<Boolean> { /** The memoized results of past visits to nodes. */ private final Map<SoyNode, Boolean> memoizedResults; @Inject protected IsComputableAsJsExprsVisitor() { memoizedResults = new HashMap<>(); } /** * Executes this visitor on the children of the given node, and returns true if all children are * computable as JsExprs. Ignores whether the given node itself is computable as JsExprs or not. */ public Boolean execOnChildren(ParentSoyNode<?> node) { return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visit(SoyNode node) { if (memoizedResults.containsKey(node)) { return memoizedResults.get(node); } else { Boolean result = super.visit(node); memoizedResults.put(node, result); return result; } } // ----------------------------------------------------------------------------------------------- // Implementations for specific nodes. @Override protected Boolean visitTemplateNode(TemplateNode node) { return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visitRawTextNode(RawTextNode node) { return true; } @Override protected Boolean visitMsgFallbackGroupNode(MsgFallbackGroupNode node) { return false; } @Override protected Boolean visitMsgPlaceholderNode(MsgPlaceholderNode node) { return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visitMsgHtmlTagNode(MsgHtmlTagNode node) { return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visitPrintNode(PrintNode node) { return true; } @Override protected Boolean visitXidNode(XidNode node) { return true; } @Override protected Boolean visitCssNode(CssNode node) { return true; } @Override protected Boolean visitLetNode(LetNode node) { return false; } @Override protected Boolean visitIfNode(IfNode node) { // If all children are computable as JS expressions, then this 'if' statement can be written // as an expression as well, using the ternary conditional operator ("? :"). return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visitIfCondNode(IfCondNode node) { return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visitIfElseNode(IfElseNode node) { return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visitSwitchNode(SwitchNode node) { return false; } @Override protected Boolean visitForeachNode(ForeachNode node) { return false; } @Override protected Boolean visitForNode(ForNode node) { return false; } @Override protected Boolean visitCallNode(CallNode node) { return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visitCallParamValueNode(CallParamValueNode node) { return true; } @Override protected Boolean visitCallParamContentNode(CallParamContentNode node) { return areChildrenComputableAsJsExprs(node); } @Override protected Boolean visitLogNode(LogNode node) { return false; } @Override protected Boolean visitDebuggerNode(DebuggerNode node) { return false; } @Override protected Boolean visitIncrementalHtmlAttributeNode(IncrementalHtmlAttributeNode node) { return false; } @Override protected Boolean visitIncrementalHtmlOpenTagNode(IncrementalHtmlOpenTagNode node) { return false; } @Override protected Boolean visitIncrementalHtmlCloseTagNode(IncrementalHtmlCloseTagNode node) { return false; } // ----------------------------------------------------------------------------------------------- // Private helpers. /** * Private helper to check whether all children of a given parent node satisfy * IsComputableAsJsExprsVisitor. * * @param node The parent node whose children to check. * @return True if all children satisfy IsComputableAsJsExprsVisitor. */ private boolean areChildrenComputableAsJsExprs(ParentSoyNode<?> node) { for (SoyNode child : node.getChildren()) { if (canSkipChild(child)) { continue; } if (!visit(child)) { return false; } } return true; } /** @return True if there is no point in visiting the child node, since it's always computable. */ protected boolean canSkipChild(SoyNode child) { // TODO(brndn): This check is probably not worth doing. Remove. return child instanceof RawTextNode || child instanceof PrintNode; } }