Package com.google.template.soy.parsepasses

Source Code of com.google.template.soy.parsepasses.CheckFunctionCallsVisitor$CheckFunctionCallsExprVisitor

/*
* 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.parsepasses;

import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.template.soy.base.SoySyntaxException;
import com.google.template.soy.exprtree.AbstractExprNodeVisitor;
import com.google.template.soy.exprtree.DataRefNode;
import com.google.template.soy.exprtree.ExprNode;
import com.google.template.soy.exprtree.ExprNode.ParentExprNode;
import com.google.template.soy.exprtree.FunctionNode;
import com.google.template.soy.exprtree.MapLiteralNode;
import com.google.template.soy.shared.internal.NonpluginFunction;
import com.google.template.soy.shared.restricted.SoyFunction;
import com.google.template.soy.soytree.AbstractSoyNodeVisitor;
import com.google.template.soy.soytree.ExprUnion;
import com.google.template.soy.soytree.ForeachNonemptyNode;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.ExprHolderNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;
import com.google.template.soy.soytree.SoySyntaxExceptionUtils;

import java.util.Map;
import java.util.Set;


/**
* Checks the signatures of functions.
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
* @author Mike Samuel
*/
public class CheckFunctionCallsVisitor extends AbstractSoyNodeVisitor<Void> {


  /** Injected Soy function definitions. */
  private final Map<String, SoyFunction> soyFunctionsByName;

  /** Determines whether we allow external function definitions. */
  private boolean allowExternallyDefinedFunctions;

  @Inject public CheckFunctionCallsVisitor(
      Map<String, SoyFunction> soyFunctionsByName) {
    this.soyFunctionsByName = ImmutableMap.copyOf(soyFunctionsByName);
  }


  /**
   * Specifies whether we allow external function definitions.
   * If true, then we ignore calls to functions that are not defined in the
   * map passed to the constructor.
   * Otherwise, we fail with an exception.
   *
   * @param allow true to allow external function definitions.
   */
  public void setAllowExternallyDefinedFunctions(boolean allow) {
    this.allowExternallyDefinedFunctions = allow;
  }


  /**
   * Recursively identifies all Soy nodes that contain expressions, and recurse to
   * each expression.
   */
  @Override protected void visitSoyNode(SoyNode node) {

    if (node instanceof ExprHolderNode) {

      for (ExprUnion exprUnion : ((ExprHolderNode) node).getAllExprUnions()) {
        if (exprUnion.getExpr() == null) {
          continue;
        }

        try {
          (new CheckFunctionCallsExprVisitor((ExprHolderNode) node)).exec(exprUnion.getExpr());
        } catch (SoySyntaxException ex) {
          // Add meta info based on node.
          throw SoySyntaxExceptionUtils.associateNode(ex, node);
        }
      }
    }

    if (node instanceof ParentSoyNode<?>) {
      visitChildren((ParentSoyNode<?>) node);
    }
  }


  /**
   * Used to visit expr nodes to find nonplugin functions and check their signatures.
   */
  private final class CheckFunctionCallsExprVisitor extends AbstractExprNodeVisitor<Void> {

    private final ExprHolderNode container;


    CheckFunctionCallsExprVisitor(ExprHolderNode container) {
      this.container = container;
    }


    /**
     * Recurse to children.
     */
    @Override protected void visitExprNode(ExprNode node) {
      if (node instanceof ParentExprNode) {
        visitChildren((ParentExprNode) node);
      }
    }


    /**
     * Check the function signature.
     *
     * @exception SoySyntaxException When a signature is violated.
     */
    @Override protected void visitFunctionNode(FunctionNode node) {
      String fnName = node.getFunctionName();
      int numArgs = node.numChildren();

      NonpluginFunction nonpluginFn = NonpluginFunction.forFunctionName(fnName);
      if (nonpluginFn != null) {
        // --- Check nonplugin function. ---
        // Check arity.
        if (numArgs != nonpluginFn.getNumArgs()) {
          throw SoySyntaxException.createWithoutMetaInfo(
              "Function '" + fnName + "' called with the wrong number of arguments" +
                  " (function call \"" + node.toSourceString() + "\").");
        }
        // Check argument types.
        switch (nonpluginFn) {
          case INDEX:
          case IS_FIRST:
          case IS_LAST:
            requireLoopVariableInScope(node, node.getChild(0));
            break;
          case QUOTE_KEYS_IF_JS:
            if (! (node.getChild(0) instanceof MapLiteralNode)) {
              throw SoySyntaxException.createWithoutMetaInfo(
                  "Function quoteKeysIfJs() must have a map literal as its arg (encountered \"" +
                      node.toSourceString() + "\").");
            }
            break;
          default:
            throw new AssertionError("Unrecognized nonplugin fn " + nonpluginFn);
        }

      } else {
        // --- Check plugin function. ---
        SoyFunction signature = soyFunctionsByName.get(fnName);
        if (signature != null) {
          Set<Integer> arities = signature.getValidArgsSizes();
          // Check arity.
          if (!arities.contains(numArgs)) {
            throw SoySyntaxException.createWithoutMetaInfo(
                "Function '" + fnName + "' called with the wrong number of arguments" +
                    " (function call \"" + node.toSourceString() + "\").");
          }
        } else if (!allowExternallyDefinedFunctions) {
          // In version 2 and later, all functions must be available as SoyFunctions
          // at compile time.
          throw SoySyntaxException.createWithoutMetaInfo(
              "Unrecognized function '" + fnName + "' (encountered function call \"" +
                  node.toSourceString() + "\").");
        }
      }

      // Recurse to operands.
      visitChildren(node);
    }


    /**
     * @param fn The function that must take a loop variable.
     */
    private void requireLoopVariableInScope(FunctionNode fn, ExprNode loopVariable) {
      if (!isLoopVariableInScope(loopVariable)) {
        throw SoySyntaxException.createWithoutMetaInfo(
            "Function '" + fn.getFunctionName() + "' must have a foreach loop variable as its" +
                " argument (encountered \"" + fn.toSourceString() + "\").");
      }
    }


    private boolean isLoopVariableInScope(ExprNode loopVariable) {
      if (!(loopVariable instanceof DataRefNode)) {
        return false;
      }
      DataRefNode loopVariableRef = (DataRefNode) loopVariable;
      if (loopVariableRef.isIjDataRef()) {
        return false;
      }
      if (loopVariableRef.numChildren() != 0) {
        return false;
      }
      String loopVariableName = loopVariableRef.getFirstKey();
      for (ParentSoyNode<?> ancestor = container.getParent(); ancestor != null;
           ancestor = ancestor.getParent()) {
        if (ancestor instanceof ForeachNonemptyNode) {
          String iteratorVariableName = ((ForeachNonemptyNode) ancestor).getVarName();
          if (loopVariableName.equals(iteratorVariableName)) {
            return true;
          }
        }
      }
      return false;
    }

  }

}
TOP

Related Classes of com.google.template.soy.parsepasses.CheckFunctionCallsVisitor$CheckFunctionCallsExprVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.