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;
}
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());
}
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());
}
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());
}
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));
}
Aggregations