com.google.template.soy.SoyFileSet.java Source code

Java tutorial

Introduction

Here is the source code for com.google.template.soy.SoyFileSet.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;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.CharSource;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.util.Providers;
import com.google.template.soy.base.SoySyntaxException;
import com.google.template.soy.base.internal.SoyFileKind;
import com.google.template.soy.base.internal.SoyFileSupplier;
import com.google.template.soy.base.internal.VolatileSoyFileSupplier;
import com.google.template.soy.basetree.SyntaxVersion;
import com.google.template.soy.conformance.CheckConformance;
import com.google.template.soy.conformance.ConformanceInput;
import com.google.template.soy.error.ErrorPrettyPrinter;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.ErrorReporter.Checkpoint;
import com.google.template.soy.jbcsrc.BytecodeCompiler;
import com.google.template.soy.jbcsrc.api.CompiledTemplate;
import com.google.template.soy.jbcsrc.api.CompiledTemplates;
import com.google.template.soy.jbcsrc.api.SoySauce;
import com.google.template.soy.jbcsrc.api.SoySauceImpl;
import com.google.template.soy.jssrc.SoyJsSrcOptions;
import com.google.template.soy.jssrc.internal.JsSrcMain;
import com.google.template.soy.msgs.SoyMsgBundle;
import com.google.template.soy.msgs.SoyMsgBundleHandler;
import com.google.template.soy.msgs.internal.ExtractMsgsVisitor;
import com.google.template.soy.msgs.restricted.SoyMsg;
import com.google.template.soy.msgs.restricted.SoyMsgBundleImpl;
import com.google.template.soy.parseinfo.passes.GenerateParseInfoVisitor;
import com.google.template.soy.parsepasses.ChangeCallsToPassAllDataVisitor;
import com.google.template.soy.parsepasses.CheckFunctionCallsVisitor.CheckFunctionCallsVisitorFactory;
import com.google.template.soy.parsepasses.contextautoesc.ContentSecurityPolicyPass;
import com.google.template.soy.parsepasses.contextautoesc.ContextualAutoescaper;
import com.google.template.soy.parsepasses.contextautoesc.DerivedTemplateUtils;
import com.google.template.soy.parsepasses.contextautoesc.SoyAutoescapeException;
import com.google.template.soy.pysrc.SoyPySrcOptions;
import com.google.template.soy.pysrc.internal.PySrcMain;
import com.google.template.soy.shared.SoyAstCache;
import com.google.template.soy.shared.SoyGeneralOptions;
import com.google.template.soy.shared.internal.MainEntryPointUtils;
import com.google.template.soy.sharedpasses.AssertStrictAutoescapingVisitor;
import com.google.template.soy.sharedpasses.ClearSoyDocStringsVisitor;
import com.google.template.soy.sharedpasses.FindIjParamsVisitor;
import com.google.template.soy.sharedpasses.FindIjParamsVisitor.IjParamsInfo;
import com.google.template.soy.sharedpasses.FindTransitiveDepTemplatesVisitor;
import com.google.template.soy.sharedpasses.FindTransitiveDepTemplatesVisitor.TransitiveDepTemplatesInfo;
import com.google.template.soy.sharedpasses.ResolvePackageRelativeCssNamesVisitor;
import com.google.template.soy.sharedpasses.StrictDepsVisitor;
import com.google.template.soy.sharedpasses.SubstituteGlobalsVisitor;
import com.google.template.soy.sharedpasses.opti.SimplifyVisitor;
import com.google.template.soy.soytree.SoyFileNode;
import com.google.template.soy.soytree.SoyFileSetNode;
import com.google.template.soy.soytree.SoytreeUtils;
import com.google.template.soy.soytree.TemplateDelegateNode;
import com.google.template.soy.soytree.TemplateNode;
import com.google.template.soy.soytree.TemplateRegistry;
import com.google.template.soy.soytree.Visibility;
import com.google.template.soy.tofu.SoyTofu;
import com.google.template.soy.tofu.SoyTofuOptions;
import com.google.template.soy.tofu.internal.BaseTofu.BaseTofuFactory;
import com.google.template.soy.types.SoyType;
import com.google.template.soy.types.SoyTypeProvider;
import com.google.template.soy.types.SoyTypeRegistry;
import com.google.template.soy.types.primitive.UnknownType;
import com.google.template.soy.xliffmsgplugin.XliffMsgPlugin;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Represents a complete set of Soy files for compilation as one bundle. The files may depend on
 * each other but should not have dependencies outside of the set.
 *
 * <p> Note: Soy file (or resource) contents must be encoded in UTF-8.
 *
 */
public final class SoyFileSet {

    /**
     * Creates a builder with the standard set of Soy directives, functions, and types.
     *
     * <p>If you need additional directives, functions, or types, create the Builder instance using
     * Guice.  If your project doesn't otherwise use Guice, you can just use Guice.createInjector
     * with only the modules you need, similar to the implementation of this method.
     */
    @SuppressWarnings("deprecation")
    public static Builder builder() {
        Builder builder = new Builder();
        // We inject based on a plain SoyModule, rather than using GuiceInitializer, to avoid relying
        // on whatever lingering static state is around.
        builder.setFactory(Guice.createInjector(new SoyModule()).getInstance(SoyFileSetFactory.class));
        return builder;
    }

    /**
     * Builder for a {@code SoyFileSet}.
     */
    public static final class Builder {

        /**
         * Assisted-injection factory. This is optionally injected since many clients inject
         * SoyFileSet.Builder without installing a SoyModule, in which case we need to fall back to
         * static injection.
         */
        private SoyFileSetFactory factory;

        /**
         * The SoyFileSuppliers collected so far in added order, as a set to prevent dupes.
         */
        private final ImmutableSet.Builder<SoyFileSupplier> setBuilder;

        /** Optional AST cache. */
        private SoyAstCache cache;

        /** The general compiler options. */
        private SoyGeneralOptions lazyGeneralOptions;

        /** Type registry for this fileset only. */
        private SoyTypeRegistry localTypeRegistry;

        /**
         * Constructs a builder using a statically-injected configuration.
         *
         * @deprecated Use the static SoyFileSet.builder() method, or  inject SoyFileSet.Builder
         *     using Guice with SoyModule installed. The behavior of this builder is unpredictable and
         *     will use the Soy configuration from the most recently configured Injector containing a
         *     SoyModule, because it relies on Guice's static injection.
         */
        @Inject
        @Deprecated
        public Builder() {
            this.setBuilder = ImmutableSet.builder();
            this.cache = null;
            this.lazyGeneralOptions = null;
        }

        @Inject(optional = true)
        /** Assigns the factory via Guice. */
        void setFactory(SoyFileSetFactory factory) {
            // Yay, we have Guice, and SoyModule is installed! :-) Inject the factory from the relevant
            // Injector!
            this.factory = factory;
        }

        /**
         * Sets all Soy general options.
         *
         * <p>This must be called before any other setters.
         */
        public void setGeneralOptions(SoyGeneralOptions generalOptions) {
            Preconditions.checkState(lazyGeneralOptions == null,
                    "Call SoyFileSet#setGeneralOptions before any other setters.");
            Preconditions.checkNotNull(generalOptions, "Non-null argument expected.");
            lazyGeneralOptions = generalOptions.clone();
        }

        /**
         * Returns and/or lazily-creates the SoyGeneralOptions for this builder.
         *
         * <p>Laziness is an important feature to ensure that setGeneralOptions can fail if options were
         * already set.  Otherwise, it'd be easy to set some options on this builder and overwrite them
         * by calling setGeneralOptions.
         */
        private SoyGeneralOptions getGeneralOptions() {
            if (lazyGeneralOptions == null) {
                lazyGeneralOptions = new SoyGeneralOptions();
            }
            return lazyGeneralOptions;
        }

        /**
         * Builds the new {@code SoyFileSet}.
         * @return The new {@code SoyFileSet}.
         */
        public SoyFileSet build() {
            if (factory == null) {
                factory = GuiceInitializer.getHackySoyFileSetFactory();
            }
            return factory.create(setBuilder.build().asList(), cache, getGeneralOptions(), localTypeRegistry);
        }

        /**
         * Adds an input Soy file, given a {@code CharSource} for the file content, as well as the
         * desired file path for messages.
         *
         * @param contentSource Source for the Soy file content.
         * @param soyFileKind The kind of this input Soy file.
         * @param filePath The path to the Soy file (used for messages only).
         * @return This builder.
         */
        public Builder addWithKind(CharSource contentSource, SoyFileKind soyFileKind, String filePath) {
            setBuilder.add(SoyFileSupplier.Factory.create(contentSource, soyFileKind, filePath));
            return this;
        }

        /**
         * Adds an input Soy file, given a {@code CharSource} for the file content, as well as the
         * desired file path for messages.
         *
         * @param contentSource Source for the Soy file content.
         * @param filePath The path to the Soy file (used for messages only).
         * @return This builder.
         */
        public Builder add(CharSource contentSource, String filePath) {
            return addWithKind(contentSource, SoyFileKind.SRC, filePath);
        }

        /**
         * Adds an input Soy file, given a {@code File}.
         *
         * @param inputFile The Soy file.
         * @param soyFileKind The kind of this input Soy file.
         * @return This builder.
         */
        public Builder addWithKind(File inputFile, SoyFileKind soyFileKind) {
            setBuilder.add(SoyFileSupplier.Factory.create(inputFile, soyFileKind));
            return this;
        }

        /**
         * Adds an input Soy file, given a {@code File}.
         *
         * @param inputFile The Soy file.
         * @return This builder.
         */
        public Builder add(File inputFile) {
            return addWithKind(inputFile, SoyFileKind.SRC);
        }

        /**
         * Adds an input Soy file that supports checking for modifications, given a {@code File}.
         *
         * <p>Note: This does nothing by itself. It should be used in conjunction with a feature that
         * actually checks for volatile files. Currently, that feature is
         * {@link #setSoyAstCache(SoyAstCache)}.
         *
         * @param inputFile The Soy file.
         * @param soyFileKind The kind of this input Soy file.
         * @return This builder.
         */
        public Builder addVolatileWithKind(File inputFile, SoyFileKind soyFileKind) {
            setBuilder.add(new VolatileSoyFileSupplier(inputFile, soyFileKind));
            return this;
        }

        /**
         * Adds an input Soy file that supports checking for modifications, given a {@code File}.
         *
         * <p>Note: This does nothing by itself. It should be used in conjunction with a feature that
         * actually checks for volatile files. Currently, that feature is
         * {@link #setSoyAstCache(SoyAstCache)}.
         *
         * @param inputFile The Soy file.
         * @return This builder.
         */
        public Builder addVolatile(File inputFile) {
            return addVolatileWithKind(inputFile, SoyFileKind.SRC);
        }

        /**
         * Adds an input Soy file, given a resource {@code URL}, as well as the desired file path for
         * messages.
         *
         * @param inputFileUrl The Soy file.
         * @param soyFileKind The kind of this input Soy file.
         * @param filePath The path to the Soy file (used for messages only).
         * @return This builder.
         */
        public Builder addWithKind(URL inputFileUrl, SoyFileKind soyFileKind, String filePath) {
            setBuilder.add(SoyFileSupplier.Factory.create(inputFileUrl, soyFileKind, filePath));
            return this;
        }

        /**
         * Adds an input Soy file, given a resource {@code URL}, as well as the desired file path for
         * messages.
         *
         * @param inputFileUrl The Soy file.
         * @param filePath The path to the Soy file (used for messages only).
         * @return This builder.
         */
        public Builder add(URL inputFileUrl, String filePath) {
            return addWithKind(inputFileUrl, SoyFileKind.SRC, filePath);
        }

        /**
         * Adds an input Soy file, given a resource {@code URL}.
         *
         * <p> Important: This function assumes that the desired file path is returned by
         * {@code inputFileUrl.toString()}. If this is not the case, please use
         * {@link #addWithKind(URL, SoyFileKind, String)} instead.
         *
         * @see #addWithKind(URL, SoyFileKind, String)
         * @param inputFileUrl The Soy file.
         * @param soyFileKind The kind of this input Soy file.
         * @return This builder.
         */
        public Builder addWithKind(URL inputFileUrl, SoyFileKind soyFileKind) {
            setBuilder.add(SoyFileSupplier.Factory.create(inputFileUrl, soyFileKind));
            return this;
        }

        /**
         * Adds an input Soy file, given a resource {@code URL}.
         *
         * <p> Important: This function assumes that the desired file path is returned by
         * {@code inputFileUrl.toString()}. If this is not the case, please use
         * {@link #add(URL, String)} instead.
         *
         * @see #add(URL, String)
         * @param inputFileUrl The Soy file.
         * @return This builder.
         */
        public Builder add(URL inputFileUrl) {
            return addWithKind(inputFileUrl, SoyFileKind.SRC);
        }

        /**
         * Adds an input Soy file, given the file content provided as a string, as well as the desired
         * file path for messages.
         *
         * @param content The Soy file content.
         * @param soyFileKind The kind of this input Soy file.
         * @param filePath The path to the Soy file (used for messages only).
         * @return This builder.
         */
        public Builder addWithKind(CharSequence content, SoyFileKind soyFileKind, String filePath) {
            setBuilder.add(SoyFileSupplier.Factory.create(content, soyFileKind, filePath));
            return this;
        }

        /**
         * Adds an input Soy file, given the file content provided as a string, as well as the desired
         * file path for messages.
         *
         * @param content The Soy file content.
         * @param filePath The path to the Soy file (used for messages only).
         * @return This builder.
         */
        public Builder add(CharSequence content, String filePath) {
            return addWithKind(content, SoyFileKind.SRC, filePath);
        }

        /**
         * Configures to use an AST cache to speed up development time.
         *
         * <p>This is undesirable in production mode since it uses strictly more memory, and this only
         * helps if the same templates are going to be recompiled frequently.
         *
         * @param cache The cache to use, which can have a lifecycle independent of the SoyFileSet.
         *     Null indicates not to use a cache.
         * @return This builder.
         */
        public Builder setSoyAstCache(SoyAstCache cache) {
            this.cache = cache;
            return this;
        }

        /**
         * Sets the user-declared syntax version name for the Soy file bundle.
         * @param versionName The syntax version name, e.g. "1.0", "2.0", "2.3".
         */
        public Builder setDeclaredSyntaxVersionName(@Nonnull String versionName) {
            getGeneralOptions().setDeclaredSyntaxVersionName(versionName);
            return this;
        }

        /**
         * Sets whether to allow external calls (calls to undefined templates).
         *
         * @param allowExternalCalls Whether to allow external calls (calls to undefined templates).
         * @return This builder.
         */
        public Builder setAllowExternalCalls(boolean allowExternalCalls) {
            getGeneralOptions().setAllowExternalCalls(allowExternalCalls);
            return this;
        }

        /**
         * Sets whether to force strict autoescaping. Enabling will cause compile time exceptions if
         * non-strict autoescaping is used in namespaces or templates.
         *
         * @param strictAutoescapingRequired Whether strict autoescaping is required.
         * @return This builder.
         */
        public Builder setStrictAutoescapingRequired(boolean strictAutoescapingRequired) {
            getGeneralOptions().setStrictAutoescapingRequired(strictAutoescapingRequired);
            return this;
        }

        /**
         * Sets the map from compile-time global name to value.
         *
         * <p> The values can be any of the Soy primitive types: null, boolean, integer, float (Java
         * double), or string.
         *
         * @param compileTimeGlobalsMap Map from compile-time global name to value. The values can be
         *     any of the Soy primitive types: null, boolean, integer, float (Java double), or string.
         * @return This builder.
         * @throws SoySyntaxException If one of the values is not a valid Soy primitive type.
         */
        public Builder setCompileTimeGlobals(Map<String, ?> compileTimeGlobalsMap) {
            getGeneralOptions().setCompileTimeGlobals(compileTimeGlobalsMap);
            return this;
        }

        /**
         * Sets the file containing compile-time globals.
         *
         * <p> Each line of the file should have the format
         * <pre>
         *     &lt;global_name&gt; = &lt;primitive_data&gt;
         * </pre>
         * where primitive_data is a valid Soy expression literal for a primitive type (null, boolean,
         * integer, float, or string). Empty lines and lines beginning with "//" are ignored. The file
         * should be encoded in UTF-8.
         *
         * <p> If you need to generate a file in this format from Java, consider using the utility
         * {@code SoyUtils.generateCompileTimeGlobalsFile()}.
         *
         * @param compileTimeGlobalsFile The file containing compile-time globals.
         * @return This builder.
         * @throws IOException If there is an error reading the compile-time globals file.
         */
        public Builder setCompileTimeGlobals(File compileTimeGlobalsFile) throws IOException {
            getGeneralOptions().setCompileTimeGlobals(compileTimeGlobalsFile);
            return this;
        }

        /**
         * Sets the resource file containing compile-time globals.
         *
         * <p> Each line of the file should have the format
         * <pre>
         *     &lt;global_name&gt; = &lt;primitive_data&gt;
         * </pre>
         * where primitive_data is a valid Soy expression literal for a primitive type (null, boolean,
         * integer, float, or string). Empty lines and lines beginning with "//" are ignored. The file
         * should be encoded in UTF-8.
         *
         * <p> If you need to generate a file in this format from Java, consider using the utility
         * {@code SoyUtils.generateCompileTimeGlobalsFile()}.
         *
         * @param compileTimeGlobalsResource The resource containing compile-time globals.
         * @return This builder.
         * @throws IOException If there is an error reading the compile-time globals file.
         */
        public Builder setCompileTimeGlobals(URL compileTimeGlobalsResource) throws IOException {
            getGeneralOptions().setCompileTimeGlobals(compileTimeGlobalsResource);
            return this;
        }

        /**
         * Pass true to enable CSP (Content Security Policy) support which adds an extra pass that marks
         * inline scripts in templates specially so the browser can distinguish scripts written by
         * trusted template authors from scripts injected via XSS.
         * <p>
         * Scripts are marked using a per-page-render secret stored in the injected variable
         * {@code $ij.csp_nonce}.
         * Scripts in non-contextually auto-escaped templates may not be found.
         */
        public Builder setSupportContentSecurityPolicy(boolean supportContentSecurityPolicy) {
            getGeneralOptions().setSupportContentSecurityPolicy(supportContentSecurityPolicy);
            return this;
        }

        /**
         * Override the global type registry with one that is local to this file set.
         */
        public Builder setLocalTypeRegistry(SoyTypeRegistry typeRegistry) {
            localTypeRegistry = typeRegistry;
            return this;
        }
    }

    /**
     * Injectable factory for creating an instance of this class.
     */
    static interface SoyFileSetFactory {

        /**
         * @param soyFileSuppliers The suppliers for the input Soy files.
         * @param cache Optional (nullable) AST cache for faster recompile times.
         * @param options The general compiler options.
         */
        public SoyFileSet create(List<SoyFileSupplier> soyFileSuppliers, SoyAstCache cache,
                SoyGeneralOptions options, @Assisted("localTypeRegistry") SoyTypeRegistry localTypeRegistry);
    }

    /** Default SoyMsgBundleHandler uses the XLIFF message plugin. */
    private static final Provider<SoyMsgBundleHandler> DEFAULT_SOY_MSG_BUNDLE_HANDLER_PROVIDER = Providers
            .of(new SoyMsgBundleHandler(new XliffMsgPlugin()));

    /** Provider for getting an instance of SoyMsgBundleHandler. */
    private Provider<SoyMsgBundleHandler> msgBundleHandlerProvider;

    /** Factory for creating an instance of BaseTofu. */
    private final BaseTofuFactory baseTofuFactory;

    /** Factory for creating an instance of BaseTofu. */
    private final SoySauceImpl.Factory soyTemplatesFactory;

    /** Provider for getting an instance of JsSrcMain. */
    private final Provider<JsSrcMain> jsSrcMainProvider;

    /** Provider for getting an instance of PySrcMain. */
    private final Provider<PySrcMain> pySrcMainProvider;

    /** Factory for creating an instance of CheckFunctionCallsVisitor. */
    private final CheckFunctionCallsVisitorFactory checkFunctionCallsVisitorFactory;

    /** The instance of ContextualAutoescaper to use. */
    private final ContextualAutoescaper contextualAutoescaper;

    /** The instance of SimplifyVisitor to use. */
    private final SimplifyVisitor simplifyVisitor;

    /** The type registry for resolving type names. */
    private final SoyTypeRegistry typeRegistry;

    /** The suppliers for the input Soy files. */
    private final List<SoyFileSupplier> soyFileSuppliers;

    /** Optional soy tree cache for faster recompile times. */
    private final SoyAstCache cache;

    /** The general compiler options. */
    private final SoyGeneralOptions generalOptions;

    private CheckConformance checkConformance;

    /** For private use by pruneTranslatedMsgs(). */
    private ImmutableSet<Long> memoizedExtractedMsgIdsForPruning;

    /** For reporting errors during parsing. */
    private final ErrorReporter errorReporter;

    /**
     * @param baseTofuFactory Factory for creating an instance of BaseTofu.
     * @param jsSrcMainProvider Provider for getting an instance of JsSrcMain.
     * @param pySrcMainProvider Provider for getting an instance of PySrcMain.
     * @param checkFunctionCallsVisitorFactory Factory for creating an instance of
     *     CheckFunctionCallsVisitor.
     * @param contextualAutoescaper The instance of ContextualAutoescaper to use.
     * @param simplifyVisitor The instance of SimplifyVisitor to use.
     * @param typeRegistry The type registry to resolve parameter type names.
     * @param soyFileSuppliers The suppliers for the input Soy files.
     * @param generalOptions The general compiler options.
     * @param localTypeRegistry If non-null, use this local type registry instead
     *        of the typeRegistry param which is a global singleton.
     *        (Unfortunately because of the way assisted injection works, we need
     *        the global and local registries to be separate parameters).
     */
    @Inject
    SoyFileSet(BaseTofuFactory baseTofuFactory, SoySauceImpl.Factory soyTemplatesFactory,
            Provider<JsSrcMain> jsSrcMainProvider, Provider<PySrcMain> pySrcMainProvider,
            CheckFunctionCallsVisitorFactory checkFunctionCallsVisitorFactory,
            ContextualAutoescaper contextualAutoescaper, SimplifyVisitor simplifyVisitor,
            SoyTypeRegistry typeRegistry, ErrorReporter errorReporter,
            @Assisted List<SoyFileSupplier> soyFileSuppliers, @Assisted SoyGeneralOptions generalOptions,
            @Assisted @Nullable SoyAstCache cache,
            @Assisted("localTypeRegistry") @Nullable SoyTypeRegistry localTypeRegistry) {

        // Default value is optionally replaced using method injection.
        this.msgBundleHandlerProvider = DEFAULT_SOY_MSG_BUNDLE_HANDLER_PROVIDER;
        this.soyTemplatesFactory = soyTemplatesFactory;
        this.baseTofuFactory = baseTofuFactory;
        this.jsSrcMainProvider = jsSrcMainProvider;
        this.pySrcMainProvider = pySrcMainProvider;
        this.checkFunctionCallsVisitorFactory = checkFunctionCallsVisitorFactory;
        this.contextualAutoescaper = contextualAutoescaper;
        this.simplifyVisitor = simplifyVisitor;

        Preconditions.checkArgument(!soyFileSuppliers.isEmpty(), "Must have non-zero number of input Soy files.");
        this.typeRegistry = localTypeRegistry != null ? localTypeRegistry : typeRegistry;
        this.soyFileSuppliers = soyFileSuppliers;
        this.cache = cache;
        this.generalOptions = generalOptions.clone();
        this.errorReporter = errorReporter;
    }

    /** @param msgBundleHandlerProvider Provider for getting an instance of SoyMsgBundleHandler. */
    @Inject(optional = true)
    void setMsgBundleHandlerProvider(Provider<SoyMsgBundleHandler> msgBundleHandlerProvider) {
        this.msgBundleHandlerProvider = msgBundleHandlerProvider;
    }

    @Inject(optional = true)
    void setCheckConformance(CheckConformance checkConformance) {
        this.checkConformance = checkConformance;
    }

    /** Returns the list of suppliers for the input Soy files. For testing use only! */
    @VisibleForTesting
    List<SoyFileSupplier> getSoyFileSuppliersForTesting() {
        return soyFileSuppliers;
    }

    /** Returns the general compiler options. For testing use only! */
    @VisibleForTesting
    SoyGeneralOptions getOptionsForTesting() {
        return generalOptions;
    }

    /** TODO(user): workaround for {@link SoyJsSrcResource}. Remove. */
    ErrorReporter getErrorReporter() {
        return errorReporter;
    }

    /**
     * Generates Java classes containing parse info (param names, template names, meta info). There
     * will be one Java class per Soy file.
     *
     * @param javaPackage The Java package for the generated classes.
     * @param javaClassNameSource Source of the generated class names. Must be one of "filename",
     *     "namespace", or "generic".
     * @return A map from generated file name (of the form "<*>SoyInfo.java") to generated file
     *     content.
     */
    ParseInfo generateParseInfo(String javaPackage, String javaClassNameSource) {

        SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_0);
        SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, declaredSyntaxVersion, soyFileSuppliers,
                errorReporter).parse();

        // Do renaming of package-relative class names.
        new ResolvePackageRelativeCssNamesVisitor(errorReporter).exec(soyTree);
        ImmutableMap<String, String> parseInfo = new GenerateParseInfoVisitor(javaPackage, javaClassNameSource,
                errorReporter).exec(soyTree);
        return new ParseInfo(result(), parseInfo);
    }

    static final class ParseInfo {
        final CompilationResult result;
        final ImmutableMap<String, String> generatedFiles;

        ParseInfo(CompilationResult result, ImmutableMap<String, String> generatedFiles) {
            this.result = result;
            this.generatedFiles = generatedFiles;
        }
    }

    /**
     * Parses the templates in this file set and generates a template registry. Useful for tools
     * that want to index Soy files without performing further compilation.
     *
     * @throws SoySyntaxException if the templates in this file set cannot be parsed according to
     *     {@link SyntaxVersion#V2_0}.
     */
    public TemplateRegistry generateTemplateRegistry() {
        // If you want your Soy templates to be indexed, you have to use modern syntax.
        SyntaxVersion version = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_0);
        SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, version, soyFileSuppliers, errorReporter)
                .parse();
        return new TemplateRegistry(soyTree, errorReporter);
    }

    /**
     * Extracts all messages from this Soy file set into a SoyMsgBundle (which can then be turned
     * into an extracted messages file with the help of a SoyMsgBundleHandler).
     *
     * @return A SoyMsgBundle containing all the extracted messages (locale "en").
     * @throws SoySyntaxException If a syntax error is found.
     */
    public SoyMsgBundle extractMsgs() throws SoySyntaxException {

        SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V1_0);
        // Override the type registry with a version that simply returns unknown
        // for any named type.
        SoyTypeRegistry typeRegistry = createDummyTypeRegistry();
        SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, declaredSyntaxVersion, soyFileSuppliers,
                errorReporter).parse();
        return new ExtractMsgsVisitor(errorReporter).exec(soyTree);
    }

    /**
     * Prunes messages from a given message bundle, keeping only messages used in this Soy file set.
     *
     * <p> Important: Do not use directly. This is subject to change and your code will break.
     *
     * <p> Note: This method memoizes intermediate results to improve efficiency in the case that it
     * is called multiple times (which is a common case). Thus, this method will not work correctly if
     * the underlying Soy files are modified between calls to this method.
     *
     * @param origTransMsgBundle The message bundle to prune.
     * @return The pruned message bundle.
     * TODO(brndn): Instead of throwing, should return a structure with a list of errors that callers
     * can inspect.
     */
    public SoyMsgBundle pruneTranslatedMsgs(SoyMsgBundle origTransMsgBundle) throws SoySyntaxException {

        // ------ Extract msgs from all the templates reachable from public templates. ------
        // Note: In the future, instead of using all public templates as the root set, we can allow the
        // user to provide a root set.

        if (memoizedExtractedMsgIdsForPruning == null) {

            SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V1_0);
            SoyTypeRegistry typeRegistry = createDummyTypeRegistry();
            SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, declaredSyntaxVersion,
                    soyFileSuppliers, errorReporter).parse();

            List<TemplateNode> allPublicTemplates = Lists.newArrayList();
            for (SoyFileNode soyFile : soyTree.getChildren()) {
                for (TemplateNode template : soyFile.getChildren()) {
                    if (template.getVisibility() == Visibility.PUBLIC) {
                        allPublicTemplates.add(template);
                    }
                }
            }

            Map<TemplateNode, TransitiveDepTemplatesInfo> depsInfoMap = new FindTransitiveDepTemplatesVisitor(
                    null /* templateRegistry */, errorReporter).execOnMultipleTemplates(allPublicTemplates);
            TransitiveDepTemplatesInfo mergedDepsInfo = TransitiveDepTemplatesInfo.merge(depsInfoMap.values());

            SoyMsgBundle extractedMsgBundle = new ExtractMsgsVisitor(errorReporter)
                    .execOnMultipleNodes(mergedDepsInfo.depTemplateSet);

            ImmutableSet.Builder<Long> extractedMsgIdsBuilder = ImmutableSet.builder();
            for (SoyMsg extractedMsg : extractedMsgBundle) {
                extractedMsgIdsBuilder.add(extractedMsg.getId());
            }
            memoizedExtractedMsgIdsForPruning = extractedMsgIdsBuilder.build();
        }

        // ------ Prune. ------

        ImmutableList.Builder<SoyMsg> prunedTransMsgsBuilder = ImmutableList.builder();
        for (SoyMsg transMsg : origTransMsgBundle) {
            if (memoizedExtractedMsgIdsForPruning.contains(transMsg.getId())) {
                prunedTransMsgsBuilder.add(transMsg);
            }
        }
        return new SoyMsgBundleImpl(origTransMsgBundle.getLocaleString(), prunedTransMsgsBuilder.build());
    }

    /**
     * Compiles this Soy file set into a Java object (type {@code SoyTofu}) capable of rendering the
     * compiled templates. The resulting {@code SoyTofu} does not cache intermediate results after
     * substitutions from the SoyMsgBundle and the SoyCssRenamingMap.
     *
     * @see #compileToTofu(com.google.template.soy.tofu.SoyTofuOptions)
     *
     * @return The resulting {@code SoyTofu} object.
     * @throws SoySyntaxException If a syntax error is found.
     */
    public SoyTofu compileToTofu() throws SoySyntaxException {
        return compileToTofu(new SoyTofuOptions());
    }

    /**
     * Compiles this Soy file set into a Java object (type {@code SoyTofu}) capable of rendering the
     * compiled templates.
     *
     * @param tofuOptions The compilation options for the Tofu backend.
     * @return The resulting {@code SoyTofu} object.
     * @throws SoySyntaxException If a syntax error is found.
     */
    public SoyTofu compileToTofu(SoyTofuOptions tofuOptions) throws SoySyntaxException {

        // Defensive copy of options. (Doesn't matter now, but might forget later when it matters.)
        tofuOptions = tofuOptions.copy();

        SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_0);

        SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, declaredSyntaxVersion, soyFileSuppliers,
                errorReporter).parse();
        runMiddleendPasses(soyTree, declaredSyntaxVersion);

        // If allowExternalCalls is not explicitly set, then disallow by default for Tofu backend.
        if (generalOptions.allowExternalCalls() == null) {
            // TODO: Enable this check when all Google internal projects are compliant.
            //(new AssertNoExternalCallsVisitor()).exec(soyTree);
        }

        // Note: Globals should have been substituted already. The pass below is just a check.
        new SubstituteGlobalsVisitor(generalOptions.getCompileTimeGlobals(), typeRegistry,
                true /* shouldAssertNoUnboundGlobals */, errorReporter).exec(soyTree);

        // Clear the SoyDoc strings because they use unnecessary memory.
        new ClearSoyDocStringsVisitor(errorReporter).exec(soyTree);

        ((ErrorReporterImpl) errorReporter).throwIfErrorsPresent();

        return baseTofuFactory.create(soyTree, tofuOptions.useCaching(), errorReporter);
    }

    /**
     * This is an <em>extremely experimental API</em> and subject to change.  Not all features of soy
     * are implemented in this new backend and the features that are implemented are not necessarily
     * correct!
     *
     * <p>See com/google/template/soy/jbcsrc/README.md for background on this new backend.
     *
     * <p>Compiles this Soy file set into a set of java classes implementing the
     * {@link CompiledTemplate} interface.
     *
     * @return A set of compiled templates
     * @throws SoySyntaxException If a syntax error is found.
     */
    public SoySauce compileTemplates() throws SoySyntaxException {
        // allow null (the default) or false
        if (generalOptions.allowExternalCalls() == Boolean.TRUE) {
            throw new UnsupportedOperationException("jbcsrc doesn't support the allowExternalCalls option");
        }
        SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_0);
        Checkpoint checkpoint = errorReporter.checkpoint();
        SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, declaredSyntaxVersion, soyFileSuppliers,
                errorReporter).parse();
        if (errorReporter.errorsSince(checkpoint)) {
            ((ErrorReporterImpl) errorReporter).throwIfErrorsPresent();
        }

        checkpoint = errorReporter.checkpoint();
        runMiddleendPasses(soyTree, declaredSyntaxVersion);
        new StrictDepsVisitor(errorReporter).exec(soyTree);
        if (errorReporter.errorsSince(checkpoint)) {
            ((ErrorReporterImpl) errorReporter).throwIfErrorsPresent();
        }

        checkpoint = errorReporter.checkpoint();
        // Note: Globals should have been substituted already. The pass below is just a check.
        new SubstituteGlobalsVisitor(generalOptions.getCompileTimeGlobals(), typeRegistry,
                true /* shouldAssertNoUnboundGlobals */, errorReporter).exec(soyTree);
        if (errorReporter.errorsSince(checkpoint)) {
            ((ErrorReporterImpl) errorReporter).throwIfErrorsPresent();
        }

        checkpoint = errorReporter.checkpoint();
        TemplateRegistry registry = new TemplateRegistry(soyTree, errorReporter);
        Optional<CompiledTemplates> templates = BytecodeCompiler.compile(registry, errorReporter);
        if (errorReporter.errorsSince(checkpoint)) {
            ((ErrorReporterImpl) errorReporter).throwIfErrorsPresent();
        }
        CompiledTemplates compiledTemplates = templates.get();
        return soyTemplatesFactory.create(compiledTemplates, registry, extractMsgs(),
                getTransitiveIjs(soyTree, registry));
    }

    private ImmutableSetMultimap<String, String> getTransitiveIjs(SoyFileSetNode soyTree,
            TemplateRegistry registry) {
        ImmutableMap<TemplateNode, IjParamsInfo> templateToIjParamsInfoMap = new FindIjParamsVisitor(registry,
                errorReporter).execOnAllTemplates(soyTree);
        ImmutableSetMultimap.Builder<String, String> templateToTranstivieIjParams = ImmutableSetMultimap.builder();
        for (Map.Entry<TemplateNode, IjParamsInfo> entry : templateToIjParamsInfoMap.entrySet()) {
            templateToTranstivieIjParams.putAll(entry.getKey().getTemplateName(), entry.getValue().ijParamSet);
        }
        return templateToTranstivieIjParams.build();
    }

    /**
     * Compiles this Soy file set into a Java object (type {@code SoyTofu}) capable of rendering the
     * compiled templates. The resulting {@code SoyTofu} does not cache intermediate results after
     * substitutions from the SoyMsgBundle and the SoyCssRenamingMap.
     *
     * @see #compileToTofu()
     *
     * @return The result of compiling this Soy file set into a Java object.
     * @throws SoySyntaxException If a syntax error is found.
     * @deprecated Use {@link #compileToTofu()}.
     */
    @Deprecated
    public SoyTofu compileToJavaObj() throws SoySyntaxException {
        return compileToTofu(new SoyTofuOptions());
    }

    /**
     * Compiles this Soy file set into JS source code files and returns these JS files as a list of
     * strings, one per file.
     *
     * @param jsSrcOptions The compilation options for the JS Src output target.
     * @param msgBundle The bundle of translated messages, or null to use the messages from the Soy
     *     source.
     * @return A list of strings where each string represents the JS source code that belongs in one
     *     JS file. The generated JS files correspond one-to-one to the original Soy source files.
     * @throws SoySyntaxException If a syntax error is found.
     * TODO(brndn): Instead of throwing, should return a structure with a list of errors that callers
     * can inspect.
     */
    @SuppressWarnings("deprecation")
    public List<String> compileToJsSrc(SoyJsSrcOptions jsSrcOptions, @Nullable SoyMsgBundle msgBundle)
            throws SoySyntaxException {

        // Synchronize old and new ways to declare syntax version V1.
        if (jsSrcOptions.shouldAllowDeprecatedSyntax()) {
            generalOptions.setDeclaredSyntaxVersionName("1.0");
        }
        SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_0);

        SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, declaredSyntaxVersion, soyFileSuppliers,
                errorReporter).parse();
        runMiddleendPasses(soyTree, declaredSyntaxVersion);

        return jsSrcMainProvider.get().genJsSrc(soyTree, jsSrcOptions, msgBundle);
    }

    /**
     * Compiles this Soy file set into JS source code files and writes these JS files to disk.
     *
     * @param outputPathFormat The format string defining how to build the output file path
     *     corresponding to an input file path.
     * @param inputFilePathPrefix The prefix prepended to all input file paths (can be empty string).
     * @param jsSrcOptions The compilation options for the JS Src output target.
     * @param locales The list of locales. Can be an empty list if not applicable.
     * @param messageFilePathFormat The message file path format, or null if not applicable.
     * @throws SoySyntaxException If a syntax error is found.
     * @throws IOException If there is an error in opening/reading a message file or opening/writing
     *     an output JS file.
     */
    @SuppressWarnings("deprecation")
    CompilationResult compileToJsSrcFiles(String outputPathFormat, String inputFilePathPrefix,
            SoyJsSrcOptions jsSrcOptions, List<String> locales, @Nullable String messageFilePathFormat)
            throws SoySyntaxException, IOException {

        // Synchronize old and new ways to declare syntax version V1.
        if (jsSrcOptions.shouldAllowDeprecatedSyntax()) {
            generalOptions.setDeclaredSyntaxVersionName("1.0");
        }
        SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_0);

        Checkpoint checkpoint = errorReporter.checkpoint();
        SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, declaredSyntaxVersion, soyFileSuppliers,
                errorReporter).parse();
        if (errorReporter.errorsSince(checkpoint)) {
            return failure();
        }

        checkpoint = errorReporter.checkpoint();
        runMiddleendPasses(soyTree, declaredSyntaxVersion);
        if (errorReporter.errorsSince(checkpoint)) {
            return failure();
        }

        if (locales.isEmpty()) {
            // Not generating localized JS.
            jsSrcMainProvider.get().genJsFiles(soyTree, jsSrcOptions, null, null, outputPathFormat,
                    inputFilePathPrefix);

        } else {
            // Generating localized JS.
            for (String locale : locales) {

                SoyFileSetNode soyTreeClone = SoytreeUtils.cloneNode(soyTree);

                String msgFilePath = MainEntryPointUtils.buildFilePath(messageFilePathFormat, locale, null,
                        inputFilePathPrefix);

                SoyMsgBundle msgBundle = msgBundleHandlerProvider.get().createFromFile(new File(msgFilePath));
                if (msgBundle.getLocaleString() == null) {
                    // TODO: Remove this check (but make sure no projects depend on this behavior).
                    // There was an error reading the message file. We continue processing only if the locale
                    // begins with "en", because falling back to the Soy source will probably be fine.
                    if (!locale.startsWith("en")) {
                        throw new IOException("Error opening or reading message file " + msgFilePath);
                    }
                }

                jsSrcMainProvider.get().genJsFiles(soyTreeClone, jsSrcOptions, locale, msgBundle, outputPathFormat,
                        inputFilePathPrefix);
            }
        }
        return result();
    }

    /**
     * Compiles this Soy file set into Python source code files and writes these Python files to
     * disk.
     *
     * @param outputPathFormat The format string defining how to build the output file path
     *     corresponding to an input file path.
     * @param inputFilePathPrefix The prefix prepended to all input file paths (can be empty string).
     * @param pySrcOptions The compilation options for the Python Src output target.
     * @throws SoySyntaxException If a syntax error is found.
     * @throws IOException If there is an error in opening/reading a message file or opening/writing
     *     an output JS file.
     */
    CompilationResult compileToPySrcFiles(String outputPathFormat, String inputFilePathPrefix,
            SoyPySrcOptions pySrcOptions) throws SoySyntaxException, IOException {

        SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_2);

        Checkpoint checkpoint = errorReporter.checkpoint();
        SoyFileSetNode soyTree = new SoyFileSetParser(typeRegistry, cache, declaredSyntaxVersion, soyFileSuppliers,
                errorReporter).parse();
        if (errorReporter.errorsSince(checkpoint)) {
            return failure();
        }

        checkpoint = errorReporter.checkpoint();
        runMiddleendPasses(soyTree, declaredSyntaxVersion);
        if (errorReporter.errorsSince(checkpoint)) {
            return failure();
        }

        pySrcMainProvider.get().genPyFiles(soyTree, pySrcOptions, outputPathFormat, inputFilePathPrefix);

        return result();
    }

    private CompilationResult result() {
        ErrorReporterImpl impl = (ErrorReporterImpl) errorReporter;
        return new CompilationResult(impl.getErrors(), new ErrorPrettyPrinter(soyFileSuppliers));
    }

    private CompilationResult failure() {
        ImmutableCollection<? extends SoySyntaxException> errors = ((ErrorReporterImpl) errorReporter).getErrors();
        Preconditions.checkState(!errors.isEmpty());
        return new CompilationResult(errors, new ErrorPrettyPrinter(soyFileSuppliers));
    }

    // TODO(gboyer): There are several fields on this class that end up saving around some state, and
    // thus are not safe to be used by multiple threads at once. Here, we add synchronized as a
    // stop-gap. However, given that most users of SoyFileSet use it once and throw it away, that
    // might be a better precondition.
    /**
     * Runs middleend passes on the given Soy tree.
     *
     * @param soyTree The Soy tree to run middleend passes on.
     * @param declaredSyntaxVersion User-declared syntax version.
     * @throws SoySyntaxException If a syntax error is found.
     * @throws SoyAutoescapeException If there is a problem determining the context for an
     *     {@code autoescape="contextual"} template or one of its callers.
     */
    private synchronized void runMiddleendPasses(SoyFileSetNode soyTree, SyntaxVersion declaredSyntaxVersion)
            throws SoySyntaxException {

        Checkpoint checkpoint = errorReporter.checkpoint();

        // Check that all function calls have a SoyFunction definition and have the correct arity.
        // This really belongs in SoyFileSetParser, but moving it there would cause SoyFileSetParser to
        // need to be injected, and that feels like overkill at this time.
        checkFunctionCallsVisitorFactory.create(declaredSyntaxVersion, errorReporter).exec(soyTree);

        // Do renaming of package-relative class names.
        new ResolvePackageRelativeCssNamesVisitor(errorReporter).exec(soyTree);

        // If disallowing external calls, perform the check.
        if (generalOptions.allowExternalCalls() == Boolean.FALSE) {
            (new StrictDepsVisitor(errorReporter)).exec(soyTree);
        }

        // If requiring strict autoescaping, check and enforce it.
        if (generalOptions.isStrictAutoescapingRequired()) {
            (new AssertStrictAutoescapingVisitor(errorReporter)).exec(soyTree);
        }

        // Handle CSS commands (if not backend-specific) and substitute compile-time globals.
        if (generalOptions.getCompileTimeGlobals() != null || typeRegistry != null) {
            new SubstituteGlobalsVisitor(generalOptions.getCompileTimeGlobals(), typeRegistry,
                    false /* shouldAssertNoUnboundGlobals */, errorReporter).exec(soyTree);
        }

        // Run contextual escaping after CSS and substitutions have been done.
        doContextualEscaping(soyTree);
        if (errorReporter.errorsSince(checkpoint)) {
            // Further passes that rely on sliced raw text nodes, such as conformance and CSP, can't
            // proceed if contextual escaping failed.
            return;
        }

        if (checkConformance != null) {
            checkConformance.check(ConformanceInput.create(soyTree, contextualAutoescaper.getSlicedRawTextNodes()));
        }

        // Add print directives that mark inline-scripts as safe to run.
        if (generalOptions.supportContentSecurityPolicy()) {
            ContentSecurityPolicyPass.blessAuthorSpecifiedScripts(contextualAutoescaper.getSlicedRawTextNodes());
        }

        // Attempt to simplify the tree.
        new ChangeCallsToPassAllDataVisitor(errorReporter).exec(soyTree);
        simplifyVisitor.exec(soyTree);
    }

    private void doContextualEscaping(SoyFileSetNode soyTree) throws SoySyntaxException {
        List<TemplateNode> extraTemplates = contextualAutoescaper.rewrite(soyTree);
        // TODO: Run the redundant template remover here and rename after CL 16642341 is in.
        if (!extraTemplates.isEmpty()) {
            // TODO: pull out somewhere else.  Ideally do the merge as part of the redundant template
            // removal.
            Map<String, SoyFileNode> containingFile = Maps.newHashMap();
            for (SoyFileNode fileNode : soyTree.getChildren()) {
                for (TemplateNode templateNode : fileNode.getChildren()) {
                    String name = templateNode instanceof TemplateDelegateNode
                            ? ((TemplateDelegateNode) templateNode).getDelTemplateName()
                            : templateNode.getTemplateName();
                    containingFile.put(DerivedTemplateUtils.getBaseName(name), fileNode);
                }
            }
            for (TemplateNode extraTemplate : extraTemplates) {
                String name = extraTemplate instanceof TemplateDelegateNode
                        ? ((TemplateDelegateNode) extraTemplate).getDelTemplateName()
                        : extraTemplate.getTemplateName();
                containingFile.get(DerivedTemplateUtils.getBaseName(name)).addChild(extraTemplate);
            }
        }
    }

    private SoyTypeRegistry createDummyTypeRegistry() {
        return new SoyTypeRegistry(ImmutableSet.<SoyTypeProvider>of(new SoyTypeProvider() {
            @Override
            public SoyType getType(String typeName, SoyTypeRegistry typeRegistry) {
                return UnknownType.getInstance();
            }
        }));
    }
}