Java tutorial
/* * 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 static com.google.template.soy.passes.CheckTemplateCallsPass.ARGUMENT_TYPE_MISMATCH; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.protobuf.Descriptors.FieldDescriptor; import com.google.template.soy.base.internal.IdGenerator; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.error.SoyErrorKind; import com.google.template.soy.error.SoyErrors; import com.google.template.soy.exprtree.ExprNode; import com.google.template.soy.exprtree.ProtoInitNode; 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.SoyType.Kind; import com.google.template.soy.types.SoyTypes; import com.google.template.soy.types.aggregate.ListType; import com.google.template.soy.types.primitive.ErrorType; import com.google.template.soy.types.primitive.NullType; import com.google.template.soy.types.primitive.UnknownType; import com.google.template.soy.types.proto.SoyProtoType; import java.util.Set; /** * A {@link CompilerFilePass} that checks proto initialization calls for correctness. * * <p>Checks that proto init calls contain all required proto fields and that args are of the * correct types. */ final class CheckProtoInitCallsPass extends CompilerFilePass { private static final SoyErrorKind FIELD_DOES_NOT_EXIST = SoyErrorKind .of("Proto field ''{0}'' does not exist.{1}"); private static final SoyErrorKind MISSING_REQUIRED_FIELD = SoyErrorKind .of("Missing required proto field ''{0}''."); private static final SoyErrorKind NULL_ARG_TYPE = SoyErrorKind .of("Cannot assign static type ''null'' to proto field ''{0}''."); private final ErrorReporter errorReporter; CheckProtoInitCallsPass(ErrorReporter errorReporter) { this.errorReporter = errorReporter; } @Override public void run(SoyFileNode file, IdGenerator nodeIdGen) { for (ProtoInitNode node : SoyTreeUtils.getAllNodesOfType(file, ProtoInitNode.class)) { checkProtoInitNode(node); } } private void checkProtoInitNode(ProtoInitNode node) { SoyType soyType = node.getType(); Preconditions.checkNotNull(soyType); if (soyType.getKind() == Kind.PROTO) { checkProto(node, (SoyProtoType) soyType); } // else, do nothing. ResolveExpressionTypesVisitor should have already reported an error. } private void checkProto(ProtoInitNode node, SoyProtoType soyType) { // Check that all proto required fields are present. // TODO(user): Consider writing a soyProtoTypeImpl.getRequiredFields() Set<String> givenParams = Sets.newHashSet(node.getParamNames()); for (FieldDescriptor field : soyType.getDescriptor().getFields()) { if (field.isRequired() && !givenParams.contains(field.getName())) { errorReporter.report(node.getSourceLocation(), MISSING_REQUIRED_FIELD, field.getName()); } } ImmutableSet<String> fields = soyType.getFieldNames(); for (int i = 0; i < node.numChildren(); i++) { String fieldName = node.getParamNames().get(i); ExprNode expr = node.getChild(i); // Check that each arg exists in the proto. if (!fields.contains(fieldName)) { String extraErrorMessage = SoyErrors.getDidYouMeanMessageForProtoFields(fields, fieldName); errorReporter.report(expr.getSourceLocation(), FIELD_DOES_NOT_EXIST, fieldName, extraErrorMessage); continue; } // Check that the arg type is not null and that it matches the expected field type. SoyType argType = expr.getType(); if (argType.equals(NullType.getInstance())) { errorReporter.report(expr.getSourceLocation(), NULL_ARG_TYPE, fieldName); } SoyType fieldType = soyType.getFieldType(fieldName); // Let args with unknown or error types pass if (argType.equals(UnknownType.getInstance()) || argType.equals(ErrorType.getInstance())) { return; } // Same for List<?>, for repeated fields if (fieldType.getKind() == Kind.LIST && argType.getKind() == Kind.LIST) { SoyType argElementType = ((ListType) argType).getElementType(); if (argElementType == null || argElementType.equals(UnknownType.getInstance())) { return; } } SoyType expectedType = SoyTypes.makeNullable(fieldType); if (!expectedType.isAssignableFrom(argType)) { errorReporter.report(expr.getSourceLocation(), ARGUMENT_TYPE_MISMATCH, fieldName, expectedType, argType); } } } }