/*
* Copyright 2011 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.template.soy.base.internal.SoyFileKind;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.SoyErrorKind;
import com.google.template.soy.soytree.AbstractSoyNodeVisitor;
import com.google.template.soy.soytree.CallBasicNode;
import com.google.template.soy.soytree.SoyFileNode;
import com.google.template.soy.soytree.SoyNode;
import com.google.template.soy.soytree.SoyNode.ParentSoyNode;
import com.google.template.soy.soytree.TemplateNode;
import com.google.template.soy.soytree.TemplateRegistry;
/**
* Visitor to check that there are no external calls. Used by backends that disallow external calls,
* such as the Tofu (JavaObj) backend.
*
* <p>{@link #exec} should be called on a {@code SoyFileSetNode} or a {@code SoyFileNode}. There is
* no return value. A {@code SoySyntaxException} is thrown if an error is found.
*
*/
public final class StrictDepsVisitor extends AbstractSoyNodeVisitor<Void> {
private static final SoyErrorKind CALL_TO_UNDEFINED_TEMPLATE =
SoyErrorKind.of("Undefined template ''{0}''.");
private static final SoyErrorKind CALL_TO_INDIRECT_DEPENDENCY =
SoyErrorKind.of(
"Call is satisfied only by indirect dependency {0}. Add it as a direct dependency.");
private static final SoyErrorKind CALL_FROM_DEP_TO_SRC =
SoyErrorKind.of(
"Illegal call to ''{0}'', because according to the dependency graph, {1} depends on {2}, "
+ "not the other way around.");
/** Registry of all templates in the Soy tree. */
private final TemplateRegistry templateRegistry;
private final ErrorReporter errorReporter;
public StrictDepsVisitor(TemplateRegistry templateRegistry, ErrorReporter errorReporter) {
this.templateRegistry = templateRegistry;
this.errorReporter = errorReporter;
}
// -----------------------------------------------------------------------------------------------
// Implementations for specific nodes.
// TODO(gboyer): Consider some deltemplate checking, but it's hard to make a coherent case for
// deltemplates since it's legitimate to have zero implementations, or to have the implementation
// in a different part of the dependency graph (if it's late-bound).
@Override
protected void visitCallBasicNode(CallBasicNode node) {
TemplateNode callee = templateRegistry.getBasicTemplate(node.getCalleeName());
if (callee == null) {
errorReporter.report(
node.getSourceLocation(), CALL_TO_UNDEFINED_TEMPLATE, node.getCalleeName());
} else {
SoyFileKind callerKind = node.getNearestAncestor(SoyFileNode.class).getSoyFileKind();
SoyFileKind calleeKind = callee.getParent().getSoyFileKind();
if (calleeKind == SoyFileKind.INDIRECT_DEP && callerKind == SoyFileKind.SRC) {
errorReporter.report(
node.getSourceLocation(),
CALL_TO_INDIRECT_DEPENDENCY,
callee.getSourceLocation().getFilePath());
}
// Double check if a dep calls a source. We shouldn't usually see this since the dependency
// should fail due to unknown template, but it doesn't hurt to add this.
if (calleeKind == SoyFileKind.SRC && callerKind != SoyFileKind.SRC) {
errorReporter.report(
node.getSourceLocation(),
CALL_FROM_DEP_TO_SRC,
callee.getTemplateNameForUserMsgs(),
callee.getSourceLocation().getFilePath(),
node.getSourceLocation().getFilePath());
}
}
// Don't forget to visit content within CallParamContentNodes.
visitChildren(node);
}
// -----------------------------------------------------------------------------------------------
// Fallback implementation.
@Override
protected void visitSoyNode(SoyNode node) {
if (node instanceof ParentSoyNode<?>) {
visitChildren((ParentSoyNode<?>) node);
}
}
}