com.google.template.soy.passes.CheckProtoInitCallsPass.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.passes.CheckProtoInitCallsPass.java

Source

/*
 * 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);
            }
        }
    }
}