/* * Copyright 2009 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.shared; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.inject.Injector; import com.google.inject.Key; import com.google.template.soy.base.SourceLocation; import com.google.template.soy.exprparse.ExpressionParser; import com.google.template.soy.exprparse.SoyParsingContext; import com.google.template.soy.exprtree.AbstractExprNodeVisitor; 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.VarRefNode; import com.google.template.soy.internal.i18n.BidiGlobalDir; import com.google.template.soy.msgs.SoyMsgBundle; import com.google.template.soy.shared.internal.ApiCallScopeUtils; import com.google.template.soy.shared.internal.GuiceSimpleScope; import com.google.template.soy.shared.restricted.ApiCallScopeBindingAnnotations.ApiCall; 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.types.SoyType; import com.google.template.soy.types.primitive.UnknownType; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Shared utilities for unit tests. * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * */ public final class SharedTestUtils { private SharedTestUtils() {} /** * Simulates the start of a new Soy API call by entering/re-entering the ApiCallScope and seeding * scoped values common to all backends. Does not seed backend-specific API call parameters. * * @param injector The Guice injector responsible for injections during the API call. * @param msgBundle The bundle of translated messages, or null to use the messages from the Soy * source. * @param bidiGlobalDir The bidi global directionality. If null, it is derived from the msgBundle * locale, if any, otherwise ltr. * @return The ApiCallScope object (for use by the caller of this method to seed additional API * call parameters, such as backend-specific parameters). */ public static GuiceSimpleScope.InScope simulateNewApiCall( Injector injector, @Nullable SoyMsgBundle msgBundle, @Nullable BidiGlobalDir bidiGlobalDir) { GuiceSimpleScope apiCallScope = injector.getInstance(Key.get(GuiceSimpleScope.class, ApiCall.class)); GuiceSimpleScope.InScope inscope = apiCallScope.enter(); ApiCallScopeUtils.seedSharedParams(inscope, msgBundle, bidiGlobalDir); return inscope; } /** * Builds a test Soy file's content from the given Soy code, which will be the body of the only * template in the test Soy file. * * @param soyDocParamNames Param names to declare in SoyDoc of the single template. * @param soyCode The code to parse as the full body of a template. * @return The test Soy file's content. */ public static String buildTestSoyFileContent( @Nullable List<String> soyDocParamNames, String soyCode) { return buildTestSoyFileContent( AutoEscapingType.DEPRECATED_NONCONTEXTUAL, soyDocParamNames, soyCode); } /** * Builds a test Soy file's content from the given Soy code, which will be the body of the only * template in the test Soy file. * * @param autoEscaping The form of autescaping to use for this namespace. * @param soyDocParamNames Param names to declare in SoyDoc of the single template. * @param soyCode The code to parse as the full body of a template. * @return The test Soy file's content. */ public static String buildTestSoyFileContent( AutoEscapingType autoEscaping, @Nullable List<String> soyDocParamNames, String soyCode) { String namespace = "brittle.test.ns"; String templateName = ".brittleTestTemplate"; StringBuilder soyFileContentBuilder = new StringBuilder(); soyFileContentBuilder .append("{namespace " + namespace) .append(" autoescape=\"" + autoEscaping.getKey() + "\"}\n") .append("\n") .append("/** Test template."); if (soyDocParamNames != null) { for (String paramName : soyDocParamNames) { soyFileContentBuilder.append(" @param " + paramName); } } soyFileContentBuilder .append(" */\n") .append("{template " + templateName + "}\n") .append(soyCode + "\n") .append("{/template}\n"); return soyFileContentBuilder.toString(); } /** * Returns a template body for the given soy expression. e.g. for the soy expression {@code $foo + * 2} this will return * * <pre><code> * {{@literal @}param foo : ?} * {$foo + 2} * </code></pre> * * <p>To supply types, call {@link #createTemplateBodyForExpression} directly. */ public static String untypedTemplateBodyForExpression(String soyExpr) { return createTemplateBodyForExpression(soyExpr, ImmutableMap.<String, SoyType>of()); } /** Returns a template body for the given soy expression. With type specializations. */ public static String createTemplateBodyForExpression( String soyExpr, final Map<String, SoyType> typeMap) { ExprNode expr = new ExpressionParser(soyExpr, SourceLocation.UNKNOWN, SoyParsingContext.exploding()) .parseExpression(); final Set<String> loopVarNames = new HashSet<>(); final Set<String> names = new HashSet<>(); new AbstractExprNodeVisitor<Void>() { @Override protected void visitVarRefNode(VarRefNode node) { if (!node.isDollarSignIjParameter()) { names.add(node.getName()); } } @Override protected void visitFunctionNode(FunctionNode node) { switch (node.getFunctionName()) { case "index": case "isFirst": case "isLast": loopVarNames.add(((VarRefNode) node.getChild(0)).getName()); break; default: // fall out } visitChildren(node); } @Override protected void visitExprNode(ExprNode node) { if (node instanceof ParentExprNode) { visitChildren((ParentExprNode) node); } } }.exec(expr); final StringBuilder templateBody = new StringBuilder(); for (String varName : Sets.difference(names, loopVarNames)) { SoyType type = typeMap.get(varName); if (type == null) { type = UnknownType.getInstance(); } templateBody.append("{@param " + varName + ": " + type + "}\n"); } String contents = "{" + soyExpr + "}\n"; for (String loopVar : loopVarNames) { contents = "{foreach $" + loopVar + " in [null]}\n" + contents + "\n{/foreach}"; } templateBody.append(contents); return templateBody.toString(); } /** * Retrieves the node within the given Soy tree indicated by the given indices to reach the * desired node. * * @param soyTree The Soy tree. * @param indicesToNode The indices to reach the desired node to retrieve. E.g. To retrieve the * first child of the template, simply pass a single 0. * @return The desired node in the Soy tree. */ public static SoyNode getNode(SoyFileSetNode soyTree, int... indicesToNode) { SoyNode node = soyTree.getChild(0).getChild(0); // initially set to TemplateNode for (int index : indicesToNode) { node = ((ParentSoyNode<?>) node).getChild(index); } return node; } }