Package com.google.template.soy.parsepasses

Source Code of com.google.template.soy.parsepasses.CheckDelegatesVisitor

/*
* 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.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.template.soy.base.SoySyntaxException;
import com.google.template.soy.data.SanitizedContent.ContentKind;
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.SoyFileSetNode;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;
import com.google.template.soy.soytree.SoySyntaxExceptionUtils;
import com.google.template.soy.soytree.TemplateBasicNode;
import com.google.template.soy.soytree.TemplateDelegateNode;
import com.google.template.soy.soytree.TemplateDelegateNode.DelTemplateKey;
import com.google.template.soy.soytree.TemplateNode;
import com.google.template.soy.soytree.TemplateNode.SoyDocParam;
import com.google.template.soy.soytree.TemplateRegistry;
import com.google.template.soy.soytree.TemplateRegistry.DelegateTemplateDivision;

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


/**
* Checks various rules regarding the use of delegates (including delegate packages, delegate
* templates, and delegate calls).
*
* <p>Important: Do not use outside of Soy code (treat as superpackage-private).
*
* <p> {@link #exec} should be called on a full parse tree. There is no return value. A
* {@code SoySyntaxException} is thrown if an error is found.
*
* @author Kai Huang
*/
public class CheckDelegatesVisitor extends AbstractSoyNodeVisitor<Void> {


  /** A template registry built from the Soy tree. */
  private TemplateRegistry templateRegistry;

  /** The current enclosing template's name, as suitable for user messages (during pass). */
  private String currTemplateNameForUserMsgs;

  /** Current delegate package name, or null if none (during pass). */
  private String currDelPackageName;


  @Override public Void exec(SoyNode soyNode) {

    Preconditions.checkArgument(soyNode instanceof SoyFileSetNode);
    templateRegistry = new TemplateRegistry((SoyFileSetNode) soyNode);

    // Perform checks that only involve templates (uses templateRegistry only, no traversal).
    checkTemplates();

    // Perform checks that involve calls (uses traversal).
    super.exec(soyNode);

    return null;
  }


  /**
   * Performs checks that only involve templates (uses templateRegistry only).
   */
  private void checkTemplates() {

    Map<String, TemplateBasicNode> basicTemplatesMap = templateRegistry.getBasicTemplatesMap();
    Map<DelTemplateKey, List<DelegateTemplateDivision>> delTemplatesMap =
        templateRegistry.getDelTemplatesMap();

    // Check that no name is reused for both basic and delegate templates.
    Set<String> reusedTemplateNames = Sets.newLinkedHashSet();
    for (DelTemplateKey delTemplateKey : delTemplatesMap.keySet()) {
      if (basicTemplatesMap.containsKey(delTemplateKey.name)) {
        reusedTemplateNames.add(delTemplateKey.name);
      }
    }
    if (reusedTemplateNames.size() > 0) {
      throw SoySyntaxException.createWithoutMetaInfo(
          "Found template name " + reusedTemplateNames + " being reused for both basic and" +
              " delegate templates.");
    }

    // Check that all delegate templates with the same name have the same declared params and
    // content kind.
    for (List<DelegateTemplateDivision> divisions : delTemplatesMap.values()) {

      TemplateDelegateNode firstDelTemplate = null;
      String firstDelPackageName = null;
      Set<SoyDocParam> firstSoyDocParamsSet = null;
      ContentKind firstContentKind = null;

      for (DelegateTemplateDivision division : divisions) {
        for (TemplateDelegateNode delTemplate : division.delPackageNameToDelTemplateMap.values()) {
          String currDelPackageName =  (delTemplate.getDelPackageName() != null) ?
              delTemplate.getDelPackageName() : "<default>";

          if (firstDelTemplate == null) {
            // First template encountered.
            firstDelTemplate = delTemplate;
            firstDelPackageName = currDelPackageName;
            firstSoyDocParamsSet = Sets.newHashSet(delTemplate.getSoyDocParams());
            firstContentKind = delTemplate.getContentKind();

          } else {
            // Not first template encountered.
            Set<SoyDocParam> currSoyDocParamsSet = Sets.newHashSet(delTemplate.getSoyDocParams());
            if (! currSoyDocParamsSet.equals(firstSoyDocParamsSet)) {
              throw SoySyntaxExceptionUtils.createWithNode(
                  String.format(
                      "Found delegate templates with same name '%s' but different param" +
                          " declarations in delegate packages '%s' and '%s'.",
                      firstDelTemplate.getDelTemplateName(), firstDelPackageName,
                      currDelPackageName),
                  firstDelTemplate);
            }
            if (delTemplate.getContentKind() != firstContentKind) {
              // TODO: This is only *truly* a requirement if the strict mode deltemplates are being
              // called by contextual templates. For a strict-to-strict call, everything is
              // escaped at runtime at the call sites. You could imagine delegating between either
              // a plain-text or rich-html template. However, most developers will write their
              // deltemplates in a parallel manner, and will want to know when the templates
              // differ. Plus, requiring them all to be the same early-on will allow future
              // optimizations to avoid the run-time checks, so it's better to start out as strict
              // as possible and only open up if needed.
              throw SoySyntaxExceptionUtils.createWithNode(
                  String.format(
                      "If one deltemplate has strict autoescaping, all its peers must also be " +
                          "strictly autoescaped with the same content kind: %s != %s (delegate " +
                          "packages %s and %s)",
                      firstContentKind, delTemplate.getContentKind(), firstDelPackageName,
                      currDelPackageName),
                  firstDelTemplate);
            }
          }
        }
      }
    }

    // Check that all basic templates within delegate packages are private.
    for (TemplateBasicNode basicTemplate : basicTemplatesMap.values()) {
      if (basicTemplate.getDelPackageName() != null && ! basicTemplate.isPrivate()) {
        throw SoySyntaxExceptionUtils.createWithNode(
            String.format(
                "Found public template '%s' in delegate package '%s' (must mark as private).",
                basicTemplate.getTemplateName(), basicTemplate.getDelPackageName()),
            basicTemplate);
      }
    }
  }


  // -----------------------------------------------------------------------------------------------
  // Implementations for specific nodes.


  @Override protected void visitTemplateNode(TemplateNode node) {
    this.currTemplateNameForUserMsgs = node.getTemplateNameForUserMsgs();
    this.currDelPackageName = node.getDelPackageName();
    visitChildren(node);
  }


  @Override protected void visitCallBasicNode(CallBasicNode node) {

    String calleeName = node.getCalleeName();

    // Check that the callee name is not a delegate template name.
    if (templateRegistry.getDelTemplateKeysForAllVariants(calleeName) != null) {
      throw SoySyntaxExceptionUtils.createWithNode(
          String.format(
              "In template '%s', found a 'call' referencing a delegate template '%s'" +
                  " (expected 'delcall').",
              currTemplateNameForUserMsgs, calleeName),
          node);
    }

    // Check that the callee is either not in a delegate package or in the same delegate package.
    TemplateBasicNode callee = templateRegistry.getBasicTemplate(calleeName);
    if (callee != null) {
      String calleeDelPackageName = callee.getDelPackageName();
      if (calleeDelPackageName != null && ! calleeDelPackageName.equals(currDelPackageName)) {
        throw SoySyntaxExceptionUtils.createWithNode(
            String.format(
                "Found illegal call from '%s' to '%s', which is in a different delegate package.",
                currTemplateNameForUserMsgs, callee.getTemplateName()),
            node);
      }
    }
  }


  @Override protected void visitCallDelegateNode(CallDelegateNode node) {

    String delCalleeName = node.getDelCalleeName();

    // Check that the callee name is not a basic template name.
    if (templateRegistry.getBasicTemplate(delCalleeName) != null) {
      throw SoySyntaxExceptionUtils.createWithNode(
          String.format(
              "In template '%s', found a 'delcall' referencing a basic template '%s'" +
                  " (expected 'call').",
              currTemplateNameForUserMsgs, delCalleeName),
          node);
    }
  }


  // -----------------------------------------------------------------------------------------------
  // Fallback implementation.


  @Override protected void visitSoyNode(SoyNode node) {
    if (node instanceof ParentSoyNode<?>) {
      visitChildren((ParentSoyNode<?>) node);
    }
  }

}
TOP

Related Classes of com.google.template.soy.parsepasses.CheckDelegatesVisitor

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.