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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2008 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.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.template.soy.base.SourceLocation;
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.SoyErrorKind.StyleAllowance;
import com.google.template.soy.error.SoyErrors;
import com.google.template.soy.exprtree.VarRefNode;
import com.google.template.soy.passes.FindIndirectParamsVisitor.IndirectParamsInfo;
import com.google.template.soy.soytree.SoyFileNode;
import com.google.template.soy.soytree.SoyTreeUtils;
import com.google.template.soy.soytree.TemplateBasicNode;
import com.google.template.soy.soytree.TemplateNode;
import com.google.template.soy.soytree.TemplateRegistry;
import com.google.template.soy.soytree.defn.TemplateHeaderVarDefn;
import com.google.template.soy.soytree.defn.TemplateParam;
import com.google.template.soy.soytree.defn.TemplateStateVar;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Pass for checking that in each template, the declared parameters match the data keys referenced
 * in the template.
 *
 * <p>Note this visitor only works for code in Soy V2 syntax.
 */
final class CheckTemplateHeaderVarsPass extends CompilerFileSetPass {

    private static final SoyErrorKind UNDECLARED_DATA_KEY = SoyErrorKind.of("Unknown data key ''{0}''.{1}",
            StyleAllowance.NO_PUNCTUATION);
    private static final SoyErrorKind UNUSED_PARAM = SoyErrorKind.of("Param ''{0}'' unused in template body.");
    private static final SoyErrorKind UNUSED_STATE = SoyErrorKind.of("State var ''{0}'' unused in template body.");

    private final ErrorReporter errorReporter;

    CheckTemplateHeaderVarsPass(ErrorReporter errorReporter) {
        this.errorReporter = errorReporter;
    }

    @Override
    public void run(ImmutableList<SoyFileNode> sourceFiles, IdGenerator idGenerator, TemplateRegistry registry) {
        for (SoyFileNode fileNode : sourceFiles) {
            for (TemplateNode templateNode : fileNode.getChildren()) {
                checkTemplate(templateNode, registry);
            }
        }
    }

    // -----------------------------------------------------------------------------------------------
    // Implementations for specific nodes.

    private void checkTemplate(TemplateNode node, TemplateRegistry templateRegistry) {
        if (node.isDeprecatedV1()) {
            return;
        }

        ListMultimap<String, SourceLocation> dataKeys = ArrayListMultimap.create();

        for (VarRefNode varRefNode : SoyTreeUtils.getAllNodesOfType(node, VarRefNode.class)) {
            if (varRefNode.isPossibleHeaderVar()) {
                dataKeys.put(varRefNode.getName(), varRefNode.getSourceLocation());
            }
        }

        IndirectParamsInfo ipi = new FindIndirectParamsVisitor(templateRegistry).exec(node);

        Set<String> allHeaderVarNames = new HashSet<>();
        List<TemplateHeaderVarDefn> unusedParams = new ArrayList<>();
        // Process @param header variables.
        for (TemplateParam param : node.getAllParams()) {
            allHeaderVarNames.add(param.name());
            if (dataKeys.containsKey(param.name())) {
                // Good: Declared and referenced in template. We remove these from dataKeys so
                // that at the end of the for-loop, dataKeys will only contain the keys that are referenced
                // but not declared in SoyDoc.
                dataKeys.removeAll(param.name());
            } else if (ipi.paramKeyToCalleesMultimap.containsKey(param.name())
                    || ipi.mayHaveIndirectParamsInExternalCalls || ipi.mayHaveIndirectParamsInExternalDelCalls) {
                // Good: Declared in SoyDoc and either (a) used in a call that passes all data or (b) used
                // in an external call or delcall that passes all data, which may need the param (we can't
                // verify).
            } else {
                // Bad: Declared in SoyDoc but not referenced in template.
                unusedParams.add(param);
            }
        }

        // Process @state header variables.
        List<TemplateHeaderVarDefn> unusedStateVars = new ArrayList<>();
        for (TemplateStateVar stateVar : node.getStateVars()) {
            allHeaderVarNames.add(stateVar.name());
            if (dataKeys.containsKey(stateVar.name())) {
                // Good: declared and referenced in the template.
                dataKeys.removeAll(stateVar.name());
            } else {
                // Bad: declared in the header, but not used.
                unusedStateVars.add(stateVar);
            }
        }
        // At this point, the only keys left in dataKeys are undeclared.
        for (Entry<String, SourceLocation> undeclared : dataKeys.entries()) {
            String extraErrorMessage = SoyErrors.getDidYouMeanMessage(allHeaderVarNames, undeclared.getKey());
            errorReporter.report(undeclared.getValue(), UNDECLARED_DATA_KEY, undeclared.getKey(),
                    extraErrorMessage);
        }

        // Delegate templates can declare unused params because other implementations
        // of the same delegate may need to use those params.
        if (node instanceof TemplateBasicNode) {
            reportUnusedHeaderVars(errorReporter, unusedParams, UNUSED_PARAM);
            reportUnusedHeaderVars(errorReporter, unusedStateVars, UNUSED_STATE);
        }
    }

    private static void reportUnusedHeaderVars(ErrorReporter errorReporter,
            List<TemplateHeaderVarDefn> unusedHeaderVars, SoyErrorKind soyError) {
        for (TemplateHeaderVarDefn unusedVar : unusedHeaderVars) {
            errorReporter.report(unusedVar.nameLocation(), soyError, unusedVar.name());
        }
    }
}