/* * Copyright 2011 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.passes; import com.google.common.base.Preconditions; import com.google.template.soy.exprparse.SoyParsingContext; import com.google.template.soy.exprtree.ExprRootNode; import com.google.template.soy.exprtree.VarRefNode; import com.google.template.soy.soytree.AbstractSoyNodeVisitor; import com.google.template.soy.soytree.CallBasicNode; import com.google.template.soy.soytree.CallDelegateNode; import com.google.template.soy.soytree.CallNode; import com.google.template.soy.soytree.CallNode.DataAttribute; import com.google.template.soy.soytree.CallParamNode; import com.google.template.soy.soytree.CallParamValueNode; import com.google.template.soy.soytree.SoyFileNode; import com.google.template.soy.soytree.SoyFileSetNode; import com.google.template.soy.soytree.SoyNode; import com.google.template.soy.soytree.SoyNode.ParentSoyNode; import com.google.template.soy.soytree.TemplateNode; /** * Visitor to change {@code call}s to use {@code data="all"} whenever possible. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * * <p>This visitor must be called on a SoyFileSetNode, SoyFileNode, or TemplateNode (i.e. template * or ancestor of a template). * */ public final class ChangeCallsToPassAllDataVisitor extends AbstractSoyNodeVisitor<Void> { @Override public Void exec(SoyNode node) { Preconditions.checkArgument( node instanceof SoyFileSetNode || node instanceof SoyFileNode || node instanceof TemplateNode); visit(node); return null; } // ----------------------------------------------------------------------------------------------- // Implementations for concrete nodes. @Override protected void visitCallNode(CallNode node) { // If there are no params (i.e. children), then this optimization doesn't apply. if (node.numChildren() == 0) { return; } // Recurse. visitChildrenAllowingConcurrentModification(node); // If this call already passes data (but not all data), then this optimization doesn't apply. if (node.dataAttribute().isPassingData() && !node.dataAttribute().isPassingAllData()) { return; } // Check whether the params (i.e. children) are all // (a) of the form {param <key>: $<key> /}, and // (b) referencing regular data (not local vars or injected data). // If not, then stop (i.e. return) because we cannot pass data="all" instead. for (CallParamNode param : node.getChildren()) { if (!(param instanceof CallParamValueNode)) { return; } CallParamValueNode valueParam = (CallParamValueNode) param; if (!("$" + valueParam.getKey()).equals(valueParam.getExprText())) { return; } ExprRootNode valueExprRoot = ((CallParamValueNode) param).getExpr(); if (valueExprRoot == null) { return; } VarRefNode valueDataRef = (VarRefNode) valueExprRoot.getRoot(); if (valueDataRef.isLocalVar() || valueDataRef.isInjected()) { return; } } // Change this call to pass data="all" and remove all params. (We reuse the node id.) CallNode newCallNode; if (node instanceof CallBasicNode) { CallBasicNode nodeCast = (CallBasicNode) node; newCallNode = new CallBasicNode.Builder(node.getId(), node.getSourceLocation()) .calleeName(nodeCast.getCalleeName()) .sourceCalleeName(nodeCast.getSrcCalleeName()) .dataAttribute(DataAttribute.all()) .userSuppliedPlaceholderName(node.getUserSuppliedPhName()) .escapingDirectiveNames(node.getEscapingDirectiveNames()) // Use the exploding reporter since we know it won't report any errors .build(SoyParsingContext.exploding()); } else { CallDelegateNode nodeCast = (CallDelegateNode) node; newCallNode = new CallDelegateNode.Builder(node.getId(), node.getSourceLocation()) .delCalleeName(nodeCast.getDelCalleeName()) .delCalleeVariantExpr(nodeCast.getDelCalleeVariantExpr()) .allowEmptyDefault(nodeCast.allowsEmptyDefault()) .dataAttribute(DataAttribute.all()) .userSuppliedPlaceholderName(node.getUserSuppliedPhName()) .escapingDirectiveNames(node.getEscapingDirectiveNames()) // Use the exploding reporter since we know it won't report any errors .build(SoyParsingContext.exploding()); } node.getParent().replaceChild(node, newCallNode); } // ----------------------------------------------------------------------------------------------- // Fallback implementation. @Override protected void visitSoyNode(SoyNode node) { if (node instanceof ParentSoyNode<?>) { visitChildrenAllowingConcurrentModification((ParentSoyNode<?>) node); } } }