/*
* 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.soytree;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.template.soy.base.SourceLocation;
import com.google.template.soy.base.internal.BaseUtils;
import com.google.template.soy.basetree.CopyState;
import com.google.template.soy.basetree.SyntaxVersionUpperBound;
import com.google.template.soy.exprparse.ExpressionParser;
import com.google.template.soy.exprparse.SoyParsingContext;
import com.google.template.soy.exprtree.ExprRootNode;
import com.google.template.soy.soytree.SoyNode.ExprHolderNode;
import com.google.template.soy.soytree.SoyNode.MsgPlaceholderInitialNode;
import com.google.template.soy.soytree.SoyNode.SplitLevelTopNode;
import com.google.template.soy.soytree.SoyNode.StandaloneNode;
import com.google.template.soy.soytree.SoyNode.StatementNode;
import com.google.template.soy.soytree.defn.TemplateParam;
import java.util.Collection;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* Node representing a call.
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
*/
public abstract class CallNode extends AbstractParentCommandNode<CallParamNode>
implements StandaloneNode,
SplitLevelTopNode<CallParamNode>,
StatementNode,
ExprHolderNode,
MsgPlaceholderInitialNode {
/**
* Private helper class used by constructors. Encapsulates all the info derived from the command
* text.
*/
@Immutable
protected static class CommandTextInfo {
private final String commandText;
private final DataAttribute dataAttribute;
@Nullable private final String userSuppliedPlaceholderName;
@Nullable protected final SyntaxVersionUpperBound syntaxVersionBound;
public CommandTextInfo(
String commandText,
DataAttribute dataAttribute,
@Nullable String userSuppliedPlaceholderName,
@Nullable SyntaxVersionUpperBound syntaxVersionBound) {
this.commandText = commandText;
this.dataAttribute = dataAttribute;
this.userSuppliedPlaceholderName = userSuppliedPlaceholderName;
this.syntaxVersionBound = syntaxVersionBound;
}
}
/** Fallback base placeholder name. */
public static final String FALLBACK_BASE_PLACEHOLDER_NAME = "XXX";
/** Parsed metadata from the 'data' attribute. */
private final DataAttribute dataAttr;
/** The user-supplied placeholder name, or null if not supplied or not applicable. */
@Nullable private final String userSuppliedPlaceholderName;
/**
* Escaping directives names (including the vertical bar) to apply to the return value. With
* strict autoescape, the result of each call site is escaped, which is potentially a no-op if the
* template's return value is the correct SanitizedContent object.
*/
private ImmutableList<String> escapingDirectiveNames = ImmutableList.of();
/** True if this node is within a HTML context. */
private boolean isPcData = false;
/**
* Protected constructor for use by subclasses.
*
* @param id The id for this node.
* @param sourceLocation The node's source location.
* @param commandTextInfo All the info derived from the command text.
* @param escapingDirectiveNames Call-site escaping directives used by strict autoescaping. This
* is inferred by the autoescaper and not part of the syntax, and thus is not in the
* CommandTextInfo.
*/
protected CallNode(
int id,
SourceLocation sourceLocation,
String commandName,
CommandTextInfo commandTextInfo,
ImmutableList<String> escapingDirectiveNames) {
super(id, sourceLocation, commandName, commandTextInfo.commandText);
this.dataAttr = commandTextInfo.dataAttribute;
this.userSuppliedPlaceholderName = commandTextInfo.userSuppliedPlaceholderName;
this.escapingDirectiveNames = escapingDirectiveNames;
maybeSetSyntaxVersionUpperBound(commandTextInfo.syntaxVersionBound);
}
/** A Parsed {@code data} attribute. */
@AutoValue
public abstract static class DataAttribute {
public static DataAttribute none() {
return new AutoValue_CallNode_DataAttribute(false, null);
}
public static DataAttribute all() {
return new AutoValue_CallNode_DataAttribute(true, null);
}
public static DataAttribute expr(ExprRootNode expr) {
return new AutoValue_CallNode_DataAttribute(true, expr);
}
DataAttribute() {}
public abstract boolean isPassingData();
public final boolean isPassingAllData() {
return isPassingData() && dataExpr() == null;
}
@Nullable
public abstract ExprRootNode dataExpr();
DataAttribute copy(CopyState copyState) {
if (dataExpr() == null) {
return this;
}
return new AutoValue_CallNode_DataAttribute(true, dataExpr().copy(copyState));
}
}
/**
* Private helper function for subclass constructors to parse the 'data' attribute.
*
* @param dataAttr The 'data' attribute in a call.
* @param sourceLocation The 'data' attribute's source location.
* @param errorReporter For reporting syntax errors.
* @return A pair (isPassingData, dataExpr) where dataExpr may be null.
*/
protected static DataAttribute parseDataAttributeHelper(
String dataAttr, SourceLocation sourceLocation, SoyParsingContext context) {
if (dataAttr == null) {
return DataAttribute.none();
} else if (dataAttr.equals("all")) {
return DataAttribute.all();
} else {
return DataAttribute.expr(
new ExprRootNode(
new ExpressionParser(dataAttr, sourceLocation, context).parseExpression()));
}
}
/**
* Copy constructor.
*
* @param orig The node to copy.
*/
protected CallNode(CallNode orig, CopyState copyState) {
super(orig, copyState);
this.dataAttr = orig.dataAttr.copy(copyState);
this.userSuppliedPlaceholderName = orig.userSuppliedPlaceholderName;
this.escapingDirectiveNames = orig.escapingDirectiveNames;
this.isPcData = orig.getIsPcData();
}
/** The parsed 'data' attribute. */
public DataAttribute dataAttribute() {
return dataAttr;
}
public boolean getIsPcData() {
return isPcData;
}
public void setIsPcData(boolean isPcData) {
this.isPcData = isPcData;
}
@Override
public String getUserSuppliedPhName() {
return userSuppliedPlaceholderName;
}
@Override
public String getTagString() {
return buildTagStringHelper(numChildren() == 0);
}
@Override
public String toSourceString() {
return (numChildren() == 0) ? getTagString() : super.toSourceString();
}
@Override
public ImmutableList<ExprRootNode> getExprList() {
return (dataAttr.dataExpr() != null)
? ImmutableList.of(dataAttr.dataExpr())
: ImmutableList.<ExprRootNode>of();
}
@Override
public String genBasePhName() {
if (userSuppliedPlaceholderName != null) {
return BaseUtils.convertToUpperUnderscore(userSuppliedPlaceholderName);
}
return FALLBACK_BASE_PLACEHOLDER_NAME;
}
@SuppressWarnings("UnnecessaryBoxing") // for IntelliJ
@Override
public Object genSamenessKey() {
// CallNodes are never considered the same placeholder. We return the node instance as the info
// for determining sameness. Sinces nodes have identity semantics this will only compare equal
// to itself.
return this;
}
@SuppressWarnings("unchecked")
@Override
public ParentSoyNode<StandaloneNode> getParent() {
return (ParentSoyNode<StandaloneNode>) super.getParent();
}
/**
* Returns the subset of {@link TemplateParam params} of the {@code callee} that require runtime
* type checking when this node is being rendered.
*/
public Collection<TemplateParam> getParamsToRuntimeCheck(TemplateNode callee) {
return callee.getParams();
}
/** Sets the inferred escaping directives. */
public void setEscapingDirectiveNames(ImmutableList<String> escapingDirectiveNames) {
this.escapingDirectiveNames = escapingDirectiveNames;
}
/**
* Returns the escaping directives, applied from left to right.
*
* <p>It is an error to call this before the contextual rewriter has been run.
*/
public ImmutableList<String> getEscapingDirectiveNames() {
return escapingDirectiveNames;
}
/** Base Builder for CallNode and CallDelegateNode. */
public abstract static class Builder {
public abstract SourceLocation getSourceLocation();
public abstract Builder commandText(String commandText);
public abstract Builder userSuppliedPlaceholderName(String commandText);
public abstract CallNode build(SoyParsingContext context);
}
}