/* * Copyright 2016 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.passes; import com.google.common.collect.ImmutableMap; import com.google.template.soy.base.internal.IdGenerator; import com.google.template.soy.data.internalutils.InternalValueUtils; import com.google.template.soy.data.restricted.PrimitiveData; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.error.SoyErrorKind; import com.google.template.soy.exprtree.ExprNode.PrimitiveNode; import com.google.template.soy.exprtree.GlobalNode; import com.google.template.soy.exprtree.IntegerNode; import com.google.template.soy.soytree.SoyFileNode; import com.google.template.soy.soytree.SoyTreeUtils; import com.google.template.soy.types.SoyType; import com.google.template.soy.types.SoyTypeRegistry; import com.google.template.soy.types.proto.SoyProtoEnumType; /** * A {@link CompilerFilePass} that searches for globals and substitutes values. * * <p>TODO(lukes): consider introducing a SoyEnumNode and replacing globals that reference enums * with that node type here. */ final class RewriteGlobalsPass extends CompilerFilePass { private static final SoyErrorKind ENUM_MEMBERSHIP_ERROR = SoyErrorKind.of("''{0}'' is not a member of enum ''{1}''."); private final SoyTypeRegistry typeRegistry; private final ImmutableMap<String, PrimitiveData> compileTimeGlobals; private final ErrorReporter errorReporter; private ImmutableMap<String, String> aliasToNamespaceMap; RewriteGlobalsPass( SoyTypeRegistry typeRegistry, ImmutableMap<String, PrimitiveData> compileTimeGlobals, ErrorReporter errorReporter) { this.typeRegistry = typeRegistry; this.compileTimeGlobals = compileTimeGlobals; this.errorReporter = errorReporter; } @Override public void run(SoyFileNode file, IdGenerator nodeIdGen) { this.aliasToNamespaceMap = file.getAliasToNamespaceMap(); for (GlobalNode global : SoyTreeUtils.getAllNodesOfType(file, GlobalNode.class)) { resolveAlias(global); // Must come before resolveGlobal() resolveGlobal(global); } } private void resolveAlias(GlobalNode global) { String name = global.getName(); String firstIdent; String remainder; int i = name.indexOf('.'); if (i > 0) { firstIdent = name.substring(0, i); remainder = name.substring(i); } else { firstIdent = name; remainder = ""; } String alias = aliasToNamespaceMap.get(firstIdent); if (alias != null) { global.setName(alias + remainder); } } private void resolveGlobal(GlobalNode global) { // First check to see if this global matches a proto enum. We do this because the enums from // the type registry have better type information and for applications with legacy globals // configs there is often overlap, so the order in which we check is actually important. // proto enums are dotted identifiers String name = global.getName(); int lastDot = name.lastIndexOf('.'); if (lastDot > 0) { String enumTypeName = name.substring(0, lastDot); SoyType type = typeRegistry.getType(enumTypeName); if (type != null && type.getKind() == SoyType.Kind.PROTO_ENUM) { SoyProtoEnumType enumType = (SoyProtoEnumType) type; String enumMemberName = name.substring(lastDot + 1); Integer enumValue = enumType.getValue(enumMemberName); if (enumValue != null) { // TODO(lukes): consider introducing a new PrimitiveNode for enums global.resolve(enumType, new IntegerNode(enumValue, global.getSourceLocation())); } else { // If we found the type definition but not the value, then that's an error // regardless of whether we're allowing unbound globals or not. errorReporter.report( global.getSourceLocation(), ENUM_MEMBERSHIP_ERROR, enumMemberName, enumTypeName); } // TODO(lukes): issue a warning if a registered global also matches return; } } // if that doesn't work, see if it was registered in the globals file. PrimitiveData value = compileTimeGlobals.get(global.getName()); if (value != null) { PrimitiveNode expr = InternalValueUtils.convertPrimitiveDataToExpr(value); global.resolve(expr.getType(), expr); } } }