Search in sources :

Example 11 with CodeChunk

use of com.google.template.soy.jssrc.dsl.CodeChunk in project closure-templates by google.

the class GenIncrementalDomCodeVisitor method tryGenerateFunctionCall.

/**
 * Emits a call to a value of type ATTRIBUTES or HTML, which is actually a JS function. Currently,
 * the only supported expressions for this operation are direct variable references and {X ?: ''}.
 *
 * @param expectedKind The kind of content that the expression must match.
 */
private GenerateFunctionCallResult tryGenerateFunctionCall(SoyType.Kind expectedKind, ExprNode expr) {
    IncrementalDomCodeBuilder jsCodeBuilder = getJsCodeBuilder();
    if (expr instanceof VarRefNode && expr.getType().getKind() == expectedKind) {
        VarRefNode varRefNode = (VarRefNode) expr;
        CodeChunk.WithValue call = templateTranslationContext.soyToJsVariableMappings().get(varRefNode.getName()).call();
        jsCodeBuilder.append(call);
        return GenerateFunctionCallResult.EMITTED;
    }
    if (!(expr instanceof NullCoalescingOpNode)) {
        return GenerateFunctionCallResult.INDIRECT_NODE;
    }
    // ResolveExpressionTypesVisitor will resolve {$attributes ?: ''} to String because '' is not of
    // type ATTRIBUTES.  Therefore, we must check the type of the first operand, not the whole node.
    NullCoalescingOpNode opNode = (NullCoalescingOpNode) expr;
    if (!(opNode.getLeftChild() instanceof VarRefNode) || !(opNode.getRightChild() instanceof StringNode) || opNode.getLeftChild().getType().getKind() != expectedKind) {
        return GenerateFunctionCallResult.INDIRECT_NODE;
    }
    if (!((StringNode) opNode.getRightChild()).getValue().isEmpty()) {
        errorReporter.report(expr.getSourceLocation(), NULL_COALESCING_NON_EMPTY);
        return GenerateFunctionCallResult.ILLEGAL_NODE;
    }
    VarRefNode varRefNode = (VarRefNode) opNode.getLeftChild();
    CodeChunk.WithValue varName = templateTranslationContext.soyToJsVariableMappings().get(varRefNode.getName());
    CodeChunk conditionalCall = CodeChunk.ifStatement(varName, varName.call()).build();
    jsCodeBuilder.append(conditionalCall);
    return GenerateFunctionCallResult.EMITTED;
}
Also used : NullCoalescingOpNode(com.google.template.soy.exprtree.OperatorNodes.NullCoalescingOpNode) VarRefNode(com.google.template.soy.exprtree.VarRefNode) CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) StringNode(com.google.template.soy.exprtree.StringNode)

Example 12 with CodeChunk

use of com.google.template.soy.jssrc.dsl.CodeChunk in project closure-templates by google.

the class TranslateExprNodeVisitor method visitLegacyObjectMapLiteralNode.

/**
 * Helper to visit a LegacyObjectMapLiteralNode, with the extra option of whether to quote keys.
 */
private CodeChunk.WithValue visitLegacyObjectMapLiteralNode(LegacyObjectMapLiteralNode node, boolean doQuoteKeys) {
    // If there are only string keys, then the expression will be
    // {aa: 11, bb: 22}    or    {'aa': 11, 'bb': 22}
    // where the former is with unquoted keys and the latter with quoted keys.
    // If there are both string and nonstring keys, then the expression will be
    // (function() { var $$tmp0 = {'aa': 11}; $$tmp0[opt_data.bb] = 22; return $$tmp0; })()
    // Since we are outputting JS code to be processed by Closure Compiler, it is important that
    // any unquoted map literal keys are string literals, since Closure Compiler can rename unquoted
    // map keys and we want everything to be renamed at the same time.
    // We will divide the map literal contents into two categories.
    // 
    // Key-value pairs with StringNode keys can be included in the JS object literal.
    // Key-value pairs that are not StringNodes (VarRefs, IJ values, etc.) must be passed through
    // the soy.$$checkLegacyObjectMapLiteralKey() function, cannot be included in the JS object
    // literal, and must generate code in the form of:
    // $$map[soy.$$checkLegacyObjectMapLiteralKey(key)] = value
    LinkedHashMap<CodeChunk.WithValue, CodeChunk.WithValue> objLiteral = new LinkedHashMap<>();
    LinkedHashMap<CodeChunk.WithValue, CodeChunk.WithValue> assignments = new LinkedHashMap<>();
    for (int i = 0; i < node.numChildren(); i += 2) {
        ExprNode keyNode = node.getChild(i);
        ExprNode valueNode = node.getChild(i + 1);
        // roll it into the next case.
        if (!(keyNode instanceof StringNode) && keyNode instanceof PrimitiveNode) {
            errorReporter.report(keyNode.getSourceLocation(), CONSTANT_USED_AS_KEY_IN_MAP_LITERAL, keyNode.toSourceString());
            continue;
        }
        // since the compiler may change the names of any unquoted map keys.
        if (!doQuoteKeys && !(keyNode instanceof StringNode)) {
            errorReporter.report(keyNode.getSourceLocation(), EXPR_IN_MAP_LITERAL_REQUIRES_QUOTE_KEYS_IF_JS, keyNode.toSourceString());
            continue;
        }
        if (keyNode instanceof StringNode) {
            if (doQuoteKeys) {
                objLiteral.put(visit(keyNode), visit(valueNode));
            } else {
                String strKey = ((StringNode) keyNode).getValue();
                if (BaseUtils.isIdentifier(strKey)) {
                    objLiteral.put(id(strKey), visit(valueNode));
                } else {
                    errorReporter.report(keyNode.getSourceLocation(), MAP_LITERAL_WITH_NON_ID_KEY_REQUIRES_QUOTE_KEYS_IF_JS, keyNode.toSourceString());
                    continue;
                }
            }
        } else {
            // key is not a StringNode; key must be passed through
            // soy.$$checkLegacyObjectMapLiteralKey() and the pair cannot be included in the JS object
            // literal.
            CodeChunk.WithValue rawKey = visit(keyNode);
            assignments.put(SOY_CHECK_LEGACY_OBJECT_MAP_LITERAL_KEY.call(rawKey), visit(valueNode));
        }
    }
    // Build the map literal
    ImmutableList<CodeChunk.WithValue> keys = ImmutableList.copyOf(objLiteral.keySet());
    ImmutableList<CodeChunk.WithValue> values = ImmutableList.copyOf(objLiteral.values());
    CodeChunk.WithValue map = mapLiteral(keys, values);
    if (assignments.isEmpty()) {
        // to a tmp var.
        return map;
    }
    // Otherwise, we need to bail to a tmp var and emit assignment statements.
    CodeChunk.WithValue mapVar = codeGenerator.declarationBuilder().setRhs(map).build().ref();
    ImmutableList.Builder<CodeChunk> initialStatements = ImmutableList.builder();
    for (Map.Entry<CodeChunk.WithValue, CodeChunk.WithValue> entry : assignments.entrySet()) {
        initialStatements.add(mapVar.bracketAccess(entry.getKey()).assign(entry.getValue()));
    }
    return mapVar.withInitialStatements(initialStatements.build());
}
Also used : WithValue(com.google.template.soy.jssrc.dsl.CodeChunk.WithValue) ImmutableList(com.google.common.collect.ImmutableList) LinkedHashMap(java.util.LinkedHashMap) ExprNode(com.google.template.soy.exprtree.ExprNode) PrimitiveNode(com.google.template.soy.exprtree.ExprNode.PrimitiveNode) CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) StringNode(com.google.template.soy.exprtree.StringNode) WithValue(com.google.template.soy.jssrc.dsl.CodeChunk.WithValue) Map(java.util.Map) LinkedHashMap(java.util.LinkedHashMap)

Example 13 with CodeChunk

use of com.google.template.soy.jssrc.dsl.CodeChunk in project closure-templates by google.

the class TranslateExprNodeVisitor method visitMapLiteralNode.

@Override
protected WithValue visitMapLiteralNode(MapLiteralNode node) {
    // Map literal nodes are much simpler to translate than legacy object map literal nodes.
    // Because they are implemented by ES6 Maps, there is no possibility that JSCompiler
    // will mistakenly rename (or not rename) its keys, and no need to ever quote a key.
    CodeChunk.WithValue map = codeGenerator.declarationBuilder().setRhs(CodeChunk.new_(id("Map")).call()).build().ref();
    ImmutableList.Builder<CodeChunk> setCalls = ImmutableList.builder();
    for (int i = 0; i < node.numChildren(); i += 2) {
        ExprNode keyNode = node.getChild(i);
        // Constructing a map literal with a null key is a runtime error.
        CodeChunk.WithValue key = SOY_CHECK_NOT_NULL.call(genMapKeyCode(keyNode));
        CodeChunk.WithValue value = visit(node.getChild(i + 1));
        setCalls.add(map.dotAccess("set").call(key, value));
    }
    return map.withInitialStatements(setCalls.build());
}
Also used : ExprNode(com.google.template.soy.exprtree.ExprNode) WithValue(com.google.template.soy.jssrc.dsl.CodeChunk.WithValue) CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) ImmutableList(com.google.common.collect.ImmutableList)

Example 14 with CodeChunk

use of com.google.template.soy.jssrc.dsl.CodeChunk in project closure-templates by google.

the class TranslateExprNodeVisitor method visitProtoInitNode.

@Override
protected CodeChunk.WithValue visitProtoInitNode(ProtoInitNode node) {
    SoyProtoType type = (SoyProtoType) node.getType();
    CodeChunk.WithValue proto = new_(protoConstructor(type)).call();
    if (node.numChildren() == 0) {
        // If there's no further structure to the proto, no need to declare a variable.
        return proto;
    }
    CodeChunk.WithValue protoVar = codeGenerator.declarationBuilder().setRhs(proto).build().ref();
    ImmutableList.Builder<CodeChunk> initialStatements = ImmutableList.builder();
    for (int i = 0; i < node.numChildren(); i++) {
        String fieldName = node.getParamName(i);
        FieldDescriptor fieldDesc = type.getFieldDescriptor(fieldName);
        CodeChunk.WithValue fieldValue = visit(node.getChild(i));
        if (ProtoUtils.isSanitizedContentField(fieldDesc)) {
            CodeChunk.WithValue sanitizedContentPackFn = sanitizedContentToProtoConverterFunction(fieldDesc.getMessageType());
            fieldValue = fieldDesc.isRepeated() ? GOOG_ARRAY_MAP.call(fieldValue, sanitizedContentPackFn) : sanitizedContentPackFn.call(fieldValue);
        }
        if (fieldDesc.isExtension()) {
            CodeChunk.WithValue extInfo = extensionField(fieldDesc);
            initialStatements.add(protoVar.dotAccess("setExtension").call(extInfo, fieldValue));
        } else if (fieldDesc.isMapField()) {
            // Protocol buffer in JS does not generate setters for map fields. To construct a proto map
            // field, we first save a reference to the empty instance using the getter,  and then load
            // it with the contents of the SoyMap.
            String getFn = "get" + LOWER_CAMEL.to(UPPER_CAMEL, fieldName);
            CodeChunk.WithValue protoMap = protoVar.dotAccess(getFn).call();
            CodeChunk.WithValue protoMapVar = codeGenerator.declarationBuilder().setRhs(protoMap).build().ref();
            if (ProtoUtils.isSanitizedContentMap(fieldDesc)) {
                CodeChunk.WithValue sanitizedContentPackFn = sanitizedContentToProtoConverterFunction(ProtoUtils.getMapValueMessageType(fieldDesc));
                fieldValue = SOY_NEWMAPS_TRANSFORM_VALUES.call(fieldValue, sanitizedContentPackFn);
            }
            initialStatements.add(SOY_MAP_POPULATE.call(protoMapVar, fieldValue));
        } else {
            String setFn = "set" + LOWER_CAMEL.to(UPPER_CAMEL, fieldName);
            initialStatements.add(protoVar.dotAccess(setFn).call(fieldValue));
        }
    }
    return protoVar.withInitialStatements(initialStatements.build());
}
Also used : WithValue(com.google.template.soy.jssrc.dsl.CodeChunk.WithValue) CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) ImmutableList(com.google.common.collect.ImmutableList) WithValue(com.google.template.soy.jssrc.dsl.CodeChunk.WithValue) SoyProtoType(com.google.template.soy.types.SoyProtoType) FieldDescriptor(com.google.protobuf.Descriptors.FieldDescriptor)

Example 15 with CodeChunk

use of com.google.template.soy.jssrc.dsl.CodeChunk in project closure-templates by google.

the class GenJsCodeVisitor method visitForNode.

/**
 * Example:
 *
 * <pre>
 *   {for $foo in $boo.foos}
 *     ...
 *   {ifempty}
 *     ...
 *   {/for}
 * </pre>
 *
 * might generate
 *
 * <pre>
 *   var foo2List = opt_data.boo.foos;
 *   var foo2ListLen = foo2List.length;
 *   if (foo2ListLen > 0) {
 *     ...
 *   } else {
 *     ...
 *   }
 * </pre>
 */
@Override
protected void visitForNode(ForNode node) {
    boolean hasIfempty = (node.numChildren() == 2);
    // NOTE: below we call id(varName) on a number of variables instead of using
    // VariableDeclaration.ref(),  this is because the refs() might be referenced on the other side
    // of a call to visitChildrenReturningCodeChunk.
    // That will break the normal behavior of FormattingContext being able to tell whether or not an
    // initial statement has already been generated because it eagerly coerces the value to a
    // string.  This will lead to redundant variable declarations.
    // When visitChildrenReturningCodeChunk is gone, this can be cleaned up, but for now we have to
    // manually decide where to declare the variables.
    List<CodeChunk> statements = new ArrayList<>();
    // Build some local variable names.
    ForNonemptyNode nonEmptyNode = (ForNonemptyNode) node.getChild(0);
    String varPrefix = nonEmptyNode.getVarName() + node.getId();
    // TODO(user): A more consistent pattern for local variable management.
    String limitName = varPrefix + "ListLen";
    CodeChunk.WithValue limitInitializer;
    Optional<RangeArgs> args = RangeArgs.createFromNode(node);
    Function<CodeChunk.WithValue, CodeChunk.WithValue> getDataItemFunction;
    if (args.isPresent()) {
        RangeArgs range = args.get();
        // if any of the expressions are too expensive, allocate local variables for them
        final CodeChunk.WithValue start = maybeStashInLocal(range.start().isPresent() ? translateExpr(range.start().get()) : CodeChunk.number(0), varPrefix + "_RangeStart", statements);
        final CodeChunk.WithValue end = maybeStashInLocal(translateExpr(range.limit()), varPrefix + "_RangeEnd", statements);
        final CodeChunk.WithValue step = maybeStashInLocal(range.increment().isPresent() ? translateExpr(range.increment().get()) : CodeChunk.number(1), varPrefix + "_RangeStep", statements);
        // the logic we want is
        // step * (end-start) < 0 ? 0 : ( (end-start)/step + ((end-start) % step == 0 ? 0 : 1));
        // but given that all javascript numbers are doubles we can simplify this somewhat.
        // Math.max(0, Match.ceil((end - start)/step))
        // should yield identical results.
        limitInitializer = CodeChunk.dottedIdNoRequire("Math.max").call(number(0), dottedIdNoRequire("Math.ceil").call(end.minus(start).divideBy(step)));
        // optimize for foreach over a range
        getDataItemFunction = new Function<CodeChunk.WithValue, CodeChunk.WithValue>() {

            @Override
            public CodeChunk.WithValue apply(CodeChunk.WithValue index) {
                return start.plus(index.times(step));
            }
        };
    } else {
        // Define list var and list-len var.
        CodeChunk.WithValue dataRef = translateExpr(node.getExpr());
        final String listVarName = varPrefix + "List";
        CodeChunk.WithValue listVar = VariableDeclaration.builder(listVarName).setRhs(dataRef).build().ref();
        // does it make sense to store this in a variable?
        limitInitializer = listVar.dotAccess("length");
        getDataItemFunction = new Function<CodeChunk.WithValue, CodeChunk.WithValue>() {

            @Override
            public CodeChunk.WithValue apply(CodeChunk.WithValue index) {
                return id(listVarName).bracketAccess(index);
            }
        };
    }
    // Generate the foreach body as a CodeChunk.
    CodeChunk.WithValue limit = id(limitName);
    statements.add(VariableDeclaration.builder(limitName).setRhs(limitInitializer).build());
    CodeChunk foreachBody = handleForeachLoop(nonEmptyNode, limit, getDataItemFunction);
    if (hasIfempty) {
        // If there is an ifempty node, wrap the foreach body in an if statement and append the
        // ifempty body as the else clause.
        CodeChunk ifemptyBody = visitChildrenReturningCodeChunk(node.getChild(1));
        CodeChunk.WithValue limitCheck = limit.op(Operator.GREATER_THAN, number(0));
        foreachBody = ifStatement(limitCheck, foreachBody).else_(ifemptyBody).build();
    }
    statements.add(foreachBody);
    jsCodeBuilder.append(CodeChunk.statements(statements));
}
Also used : CodeChunk(com.google.template.soy.jssrc.dsl.CodeChunk) RangeArgs(com.google.template.soy.shared.RangeArgs) ArrayList(java.util.ArrayList) ForNonemptyNode(com.google.template.soy.soytree.ForNonemptyNode)

Aggregations

CodeChunk (com.google.template.soy.jssrc.dsl.CodeChunk)20 ImmutableList (com.google.common.collect.ImmutableList)6 VariableDeclaration (com.google.template.soy.jssrc.dsl.VariableDeclaration)4 SanitizedContentKind (com.google.template.soy.base.internal.SanitizedContentKind)3 ExprNode (com.google.template.soy.exprtree.ExprNode)3 WithValue (com.google.template.soy.jssrc.dsl.CodeChunk.WithValue)3 UniqueNameGenerator (com.google.template.soy.base.internal.UniqueNameGenerator)2 StringNode (com.google.template.soy.exprtree.StringNode)2 ConditionalBuilder (com.google.template.soy.jssrc.dsl.ConditionalBuilder)2 SwitchBuilder (com.google.template.soy.jssrc.dsl.SwitchBuilder)2 SoyPrintDirective (com.google.template.soy.shared.restricted.SoyPrintDirective)2 SoyNode (com.google.template.soy.soytree.SoyNode)2 ParentSoyNode (com.google.template.soy.soytree.SoyNode.ParentSoyNode)2 ArrayList (java.util.ArrayList)2 Map (java.util.Map)2 CheckReturnValue (javax.annotation.CheckReturnValue)2 ImmutableMap (com.google.common.collect.ImmutableMap)1 FieldDescriptor (com.google.protobuf.Descriptors.FieldDescriptor)1 PrimitiveNode (com.google.template.soy.exprtree.ExprNode.PrimitiveNode)1 ExprRootNode (com.google.template.soy.exprtree.ExprRootNode)1