All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.template.soy.SoyFileSet Maven / Gradle / Ivy

There is a newer version: 2024-02-26
Show newest version
/*
 * 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.io.ByteSink;
import com.google.common.io.CharSource;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.protobuf.Descriptors.DescriptorValidationException;
import com.google.protobuf.Descriptors.GenericDescriptor;
import com.google.template.soy.SoyFileSetParser.ParseResult;
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.TriState;
import com.google.template.soy.base.internal.VolatileSoyFileSupplier;
import com.google.template.soy.basetree.CopyState;
import com.google.template.soy.basetree.SyntaxVersion;
import com.google.template.soy.conformance.ValidatedConformanceConfig;
import com.google.template.soy.data.SoyValueConverter;
import com.google.template.soy.error.ErrorReporter;
import com.google.template.soy.error.SoyCompilationException;
import com.google.template.soy.error.SoyError;
import com.google.template.soy.error.SoyErrors;
import com.google.template.soy.incrementaldomsrc.IncrementalDomSrcMain;
import com.google.template.soy.incrementaldomsrc.SoyIncrementalDomSrcOptions;
import com.google.template.soy.jbcsrc.BytecodeCompiler;
import com.google.template.soy.jbcsrc.api.SoySauce;
import com.google.template.soy.jbcsrc.api.SoySauceImpl;
import com.google.template.soy.jbcsrc.shared.CompiledTemplate;
import com.google.template.soy.jbcsrc.shared.CompiledTemplates;
import com.google.template.soy.jssrc.SoyJsSrcOptions;
import com.google.template.soy.jssrc.internal.JsSrcMain;
import com.google.template.soy.logging.LoggingConfig;
import com.google.template.soy.logging.ValidatedLoggingConfig;
import com.google.template.soy.msgs.SoyMsgBundle;
import com.google.template.soy.msgs.SoyMsgBundleHandler;
import com.google.template.soy.msgs.SoyMsgBundleHandler.OutputFileOptions;
import com.google.template.soy.msgs.SoyMsgPlugin;
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.passes.ClearSoyDocStringsVisitor;
import com.google.template.soy.passes.FindIjParamsVisitor;
import com.google.template.soy.passes.FindIjParamsVisitor.IjParamsInfo;
import com.google.template.soy.passes.FindTransitiveDepTemplatesVisitor;
import com.google.template.soy.passes.FindTransitiveDepTemplatesVisitor.TransitiveDepTemplatesInfo;
import com.google.template.soy.passes.PassManager;
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.GuiceSimpleScope;
import com.google.template.soy.shared.internal.MainEntryPointUtils;
import com.google.template.soy.shared.restricted.ApiCallScopeBindingAnnotations.ApiCall;
import com.google.template.soy.shared.restricted.SoyFunction;
import com.google.template.soy.shared.restricted.SoyPrintDirective;
import com.google.template.soy.soyparse.PluginResolver;
import com.google.template.soy.soytree.SoyFileNode;
import com.google.template.soy.soytree.SoyFileSetNode;
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.internal.BaseTofu;
import com.google.template.soy.types.SoyTypeProvider;
import com.google.template.soy.types.SoyTypeRegistry;
import com.google.template.soy.types.proto.SoyProtoTypeProvider;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
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.
 *
 * 

Note: Soy file (or resource) contents must be encoded in UTF-8. * */ public final class SoyFileSet { private static final Logger logger = Logger.getLogger(SoyFileSet.class.getName()); /** * Creates a builder with the standard set of Soy directives, functions, and types. * *

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. */ public static Builder builder() { return Guice.createInjector(new SoyModule()).getInstance(Builder.class); } // Implementation detail of SoyFileSet.Builder. // having it as its own 'parameter' class removes a small amount of boilerplate. static final class CoreDependencies { private final SoyValueConverter soyValueConverter; private final GuiceSimpleScope apiCallScope; private final SoyTypeRegistry typeRegistry; private final ImmutableMap soyFunctionMap; private final ImmutableMap printDirectives; @Inject CoreDependencies( SoyValueConverter soyValueConverter, @ApiCall GuiceSimpleScope apiCallScope, SoyTypeRegistry typeRegistry, ImmutableMap soyFunctionMap, ImmutableMap printDirectives) { this.soyValueConverter = soyValueConverter; this.apiCallScope = apiCallScope; this.typeRegistry = typeRegistry; this.soyFunctionMap = soyFunctionMap; this.printDirectives = printDirectives; } } /** * Builder for a {@code SoyFileSet}. * *

Instances of this can be obtained by calling {@link #builder()} or by installing {@link * SoyModule} and injecting it. */ public static final class Builder { /** The SoyFileSuppliers collected so far in added order, as a set to prevent dupes. */ private final ImmutableMap.Builder filesBuilder; /** Optional AST cache. */ private SoyAstCache cache; /** The general compiler options. */ private SoyGeneralOptions lazyGeneralOptions; /** Type registry for this fileset only. */ private SoyTypeRegistry localTypeRegistry; private final CoreDependencies coreDependencies; /** The SoyProtoTypeProvider builder that will be built for local type registry. */ private final SoyProtoTypeProvider.Builder protoTypeProviderBuilder; @Nullable private Appendable warningSink; private ValidatedConformanceConfig conformanceConfig = ValidatedConformanceConfig.EMPTY; private ValidatedLoggingConfig loggingConfig = ValidatedLoggingConfig.EMPTY; Builder(CoreDependencies coreDependencies) { this.coreDependencies = coreDependencies; this.filesBuilder = ImmutableMap.builder(); this.protoTypeProviderBuilder = new SoyProtoTypeProvider.Builder(); this.cache = null; this.lazyGeneralOptions = null; } /** * Sets all Soy general options. * *

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. * *

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() { try { if (!protoTypeProviderBuilder.isEmpty()) { Set typeProviders = ImmutableSet.of(protoTypeProviderBuilder.build()); localTypeRegistry = new SoyTypeRegistry(typeProviders); } } catch (DescriptorValidationException | IOException ex) { throw new RuntimeException("Malformed descriptor set", ex); } return new SoyFileSet( coreDependencies.apiCallScope, coreDependencies.soyValueConverter, localTypeRegistry == null ? coreDependencies.typeRegistry : localTypeRegistry, coreDependencies.soyFunctionMap, coreDependencies.printDirectives, filesBuilder.build(), getGeneralOptions(), cache, conformanceConfig, loggingConfig, warningSink); } /** * 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) { return addFile(SoyFileSupplier.Factory.create(contentSource, soyFileKind, filePath)); } /** * 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) { return addFile(SoyFileSupplier.Factory.create(inputFile, soyFileKind)); } /** * 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}. * *

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) { return addFile(new VolatileSoyFileSupplier(inputFile, soyFileKind)); } /** * Adds an input Soy file that supports checking for modifications, given a {@code File}. * *

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) { return addFile(SoyFileSupplier.Factory.create(inputFileUrl, soyFileKind, filePath)); } /** * 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}. * *

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) { return addFile(SoyFileSupplier.Factory.create(inputFileUrl, soyFileKind)); } /** * Adds an input Soy file, given a resource {@code URL}. * *

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) { return addFile(SoyFileSupplier.Factory.create(content, soyFileKind, filePath)); } /** * 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. * *

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 experimental features. These features are unreleased and are not generally available. * * @param experimentalFeatures * @return This builder. */ public Builder setExperimentalFeatures(List experimentalFeatures) { getGeneralOptions().setExperimentalFeatures(experimentalFeatures); return this; } /** * Disables optimizer. The optimizer tries to simplify the Soy AST by evaluating constant * expressions. It generally improves performance and should only be disabled in integration * tests. * *

This is public only because we need to set it in {@code SoyFileSetHelper}, that are * necessary for integration tests. Normal users should not use this. * * @return This builder. */ public Builder disableOptimizer() { getGeneralOptions().disableOptimizer(); 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. * *

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 compileTimeGlobalsMap) { getGeneralOptions().setCompileTimeGlobals(compileTimeGlobalsMap); return this; } /** * Sets the file containing compile-time globals. * *

Each line of the file should have the format * *

     *     <global_name> = <primitive_data>
     * 
* * 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. * *

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. * *

Each line of the file should have the format * *

     *     <global_name> = <primitive_data>
     * 
* * 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. * *

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; } /** * Add all proto descriptors found in the file to the type registry. * * @param descriptorFile A file containing FileDescriptorSet binary protos. These typically end * in {@code .proto.bin}. Note that this isn't the same as a {@code .proto} source file. */ public Builder addProtoDescriptorsFromFile(File descriptorFile) { protoTypeProviderBuilder.addFileDescriptorSetFromFile(descriptorFile); return this; } /** * Add all proto descriptors found in all files to the type registry. * * @param descriptorFiles files containing FileDescriptorSet binary protos. These typically end * in {@code .proto.bin}. Note that this isn't the same as a {@code .proto} source file. */ public Builder addProtoDescriptorsFromFiles(Iterable descriptorFiles) { for (File descriptorFile : descriptorFiles) { protoTypeProviderBuilder.addFileDescriptorSetFromFile(descriptorFile); } return this; } /** * Registers a collection of protocol buffer descriptors. This makes all the types defined in * the provided descriptors available to use in soy. */ public Builder addProtoDescriptors(GenericDescriptor... descriptors) { return addProtoDescriptors(Arrays.asList(descriptors)); } /** * Registers a collection of protocol buffer descriptors. This makes all the types defined in * the provided descriptors available to use in soy. */ public Builder addProtoDescriptors(Iterable descriptors) { protoTypeProviderBuilder.addDescriptors(descriptors); return this; } /** Registers a conformance config proto. */ Builder setConformanceConfig(ValidatedConformanceConfig config) { checkNotNull(config); this.conformanceConfig = config; return this; } /** Override the global type registry with one that is local to this file set. */ public Builder setLocalTypeRegistry(SoyTypeRegistry typeRegistry) { this.localTypeRegistry = typeRegistry; return this; } private Builder addFile(SoyFileSupplier supplier) { filesBuilder.put(supplier.getFilePath(), supplier); return this; } /** * Configures a place to write warnings for successful compilations. * *

For compilation failures warnings are reported along with the errors, by throwing an * exception. The default is to report warnings to the logger for SoyFileSet. */ Builder setWarningSink(Appendable warningSink) { this.warningSink = checkNotNull(warningSink); return this; } /** * Sets the logging config to use. * * @throws IllegalArgumentException if the config proto is invalid. For example, if there are * multiple elements with the same {@code name} or {@code id}, or if the name not a valid * identifier. */ public Builder setLoggingConfig(LoggingConfig config) { return setValidatedLoggingConfig(ValidatedLoggingConfig.create(config)); } /** Sets the validated logging config to use. */ Builder setValidatedLoggingConfig(ValidatedLoggingConfig parseLoggingConfigs) { this.loggingConfig = checkNotNull(parseLoggingConfigs); return this; } } private final GuiceSimpleScope apiCallScopeProvider; private final SoyValueConverter soyValueConverter; private final SoyTypeRegistry typeRegistry; private final ImmutableMap soyFileSuppliers; /** Optional soy tree cache for faster recompile times. */ @Nullable private final SoyAstCache cache; private final SoyGeneralOptions generalOptions; private final ValidatedConformanceConfig conformanceConfig; private final ValidatedLoggingConfig loggingConfig; /** For private use by pruneTranslatedMsgs(). */ private ImmutableSet memoizedExtractedMsgIdsForPruning; private final ImmutableMap soyFunctionMap; private final ImmutableMap printDirectives; /** For reporting errors during parsing. */ private ErrorReporter errorReporter; @Nullable private final Appendable warningSink; SoyFileSet( GuiceSimpleScope apiCallScopeProvider, SoyValueConverter soyValueConverter, SoyTypeRegistry typeRegistry, ImmutableMap soyFunctionMap, ImmutableMap printDirectives, ImmutableMap soyFileSuppliers, SoyGeneralOptions generalOptions, @Nullable SoyAstCache cache, ValidatedConformanceConfig conformanceConfig, ValidatedLoggingConfig loggingConfig, @Nullable Appendable warningSink) { this.apiCallScopeProvider = apiCallScopeProvider; this.soyValueConverter = soyValueConverter; Preconditions.checkArgument( !soyFileSuppliers.isEmpty(), "Must have non-zero number of input Soy files."); this.typeRegistry = typeRegistry; this.soyFileSuppliers = soyFileSuppliers; this.cache = cache; this.generalOptions = generalOptions.clone(); this.soyFunctionMap = soyFunctionMap; this.printDirectives = printDirectives; this.conformanceConfig = checkNotNull(conformanceConfig); this.loggingConfig = checkNotNull(loggingConfig); this.warningSink = warningSink; } /** Returns the list of suppliers for the input Soy files. For testing use only! */ @VisibleForTesting ImmutableMap getSoyFileSuppliersForTesting() { return soyFileSuppliers; } /** * 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. * @throws SoyCompilationException If compilation fails. */ public ImmutableMap generateParseInfo( String javaPackage, String javaClassNameSource) { resetErrorReporter(); // TODO(lukes): see if we can enforce that globals are provided at compile time here. given that // types have to be, this should be possible. Currently it is disabled for backwards // compatibility // N.B. we do not run the optimizer here for 2 reasons: // 1. it would just waste time, since we are not running code generation the optimization work // doesn't help anything // 2. it potentially removes metadata from the tree by precalculating expressions. For example, // trivial print nodes are evaluated, which can remove globals from the tree, but the // generator requires data about globals to generate accurate proto descriptors. Also, the // ChangeCallsToPassAllData pass will change the params of templates. ParseResult result = parse( passManagerBuilder(SyntaxVersion.V2_0).allowUnknownGlobals().optimize(false), typeRegistry, new PluginResolver( // we allow undefined plugins since they typically aren't provided :( PluginResolver.Mode.ALLOW_UNDEFINED, printDirectives, soyFunctionMap, errorReporter)); throwIfErrorsPresent(); SoyFileSetNode soyTree = result.fileSet(); TemplateRegistry registry = result.registry(); // Do renaming of package-relative class names. ImmutableMap parseInfo = new GenerateParseInfoVisitor(javaPackage, javaClassNameSource, registry).exec(soyTree); throwIfErrorsPresent(); reportWarnings(); return parseInfo; } /** * 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 SoyCompilationException If compilation fails. */ public SoyMsgBundle extractMsgs() { resetErrorReporter(); SoyMsgBundle bundle = doExtractMsgs(); reportWarnings(); return bundle; } /** * Extracts all messages from this Soy file set and writes the messages to an output sink. * * @param msgBundleHandler Handler to write the messages. * @param options Options to configure how to write the extracted messages. * @param output Where to write the extracted messages. * @throws IOException If there are errors writing to the output. */ public void extractAndWriteMsgs( SoyMsgBundleHandler msgBundleHandler, OutputFileOptions options, ByteSink output) throws IOException { resetErrorReporter(); SoyMsgBundle bundle = doExtractMsgs(); msgBundleHandler.writeExtractedMsgs(bundle, options, output, errorReporter); throwIfErrorsPresent(); reportWarnings(); } /** Performs the parsing and extraction logic. */ private SoyMsgBundle doExtractMsgs() { // extractMsgs disables a bunch of passes since it is typically not configured with things // like global definitions, type definitions, etc. SoyFileSetNode soyTree = parse( passManagerBuilder(SyntaxVersion.V1_0) .allowUnknownGlobals() .setTypeRegistry(SoyTypeRegistry.DEFAULT_UNKNOWN) .disableAllTypeChecking(), // override the type registry so that the parser doesn't report errors when it // can't resolve strict types SoyTypeRegistry.DEFAULT_UNKNOWN, new PluginResolver( PluginResolver.Mode.ALLOW_UNDEFINED, printDirectives, soyFunctionMap, errorReporter)) .fileSet(); throwIfErrorsPresent(); SoyMsgBundle bundle = new ExtractMsgsVisitor().exec(soyTree); throwIfErrorsPresent(); return bundle; } /** * Prunes messages from a given message bundle, keeping only messages used in this Soy file set. * *

Important: Do not use directly. This is subject to change and your code will break. * *

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. * @throws SoyCompilationException If compilation fails. */ public SoyMsgBundle pruneTranslatedMsgs(SoyMsgBundle origTransMsgBundle) { resetErrorReporter(); // ------ 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) { ParseResult result = parse( passManagerBuilder(SyntaxVersion.V1_0).allowUnknownGlobals().disableAllTypeChecking(), // override the type registry so that the parser doesn't report errors when it // can't resolve strict types SoyTypeRegistry.DEFAULT_UNKNOWN, new PluginResolver( PluginResolver.Mode.ALLOW_UNDEFINED, printDirectives, soyFunctionMap, errorReporter)); throwIfErrorsPresent(); SoyFileSetNode soyTree = result.fileSet(); TemplateRegistry registry = result.registry(); List allPublicTemplates = Lists.newArrayList(); for (SoyFileNode soyFile : soyTree.getChildren()) { for (TemplateNode template : soyFile.getChildren()) { if (template.getVisibility() == Visibility.PUBLIC) { allPublicTemplates.add(template); } } } Map depsInfoMap = new FindTransitiveDepTemplatesVisitor(registry) .execOnMultipleTemplates(allPublicTemplates); TransitiveDepTemplatesInfo mergedDepsInfo = TransitiveDepTemplatesInfo.merge(depsInfoMap.values()); SoyMsgBundle extractedMsgBundle = new ExtractMsgsVisitor().execOnMultipleNodes(mergedDepsInfo.depTemplateSet); ImmutableSet.Builder extractedMsgIdsBuilder = ImmutableSet.builder(); for (SoyMsg extractedMsg : extractedMsgBundle) { extractedMsgIdsBuilder.add(extractedMsg.getId()); } throwIfErrorsPresent(); memoizedExtractedMsgIdsForPruning = extractedMsgIdsBuilder.build(); } // ------ Prune. ------ ImmutableList.Builder prunedTransMsgsBuilder = ImmutableList.builder(); for (SoyMsg transMsg : origTransMsgBundle) { if (memoizedExtractedMsgIdsForPruning.contains(transMsg.getId())) { prunedTransMsgsBuilder.add(transMsg); } } throwIfErrorsPresent(); 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. * * @return The resulting {@code SoyTofu} object. * @throws SoyCompilationException If compilation fails. */ public SoyTofu compileToTofu() { resetErrorReporter(); ServerCompilationPrimitives primitives = compileForServerRendering(); throwIfErrorsPresent(); SoyTofu tofu = doCompileToTofu(primitives); reportWarnings(); return tofu; } /** Helper method to compile SoyTofu from {@link ServerCompilationPrimitives} */ private SoyTofu doCompileToTofu(ServerCompilationPrimitives primitives) { return new BaseTofu( soyValueConverter, apiCallScopeProvider, primitives.registry, getTransitiveIjs(primitives.soyTree, primitives.registry)); } /** * Compiles this Soy file set into a set of java classes implementing the {@link SoySauce} * interface. * *

This is useful for implementing 'edit refresh' workflows. Most production usecases should * use the command line interface to 'ahead of time' compile templates to jar files and then use * {@code PrecompiledSoyModule} to get access to a {@link SoySauce} object without invoking the * compiler. This will allow applications to avoid invoking the soy compiler at runtime which can * be relatively slow. * * @return A set of compiled templates * @throws SoyCompilationException If compilation fails. */ public SoySauce compileTemplates() { resetErrorReporter(); disallowExternalCalls(); ServerCompilationPrimitives primitives = compileForServerRendering(); throwIfErrorsPresent(); SoySauce sauce = doCompileSoySauce(primitives); reportWarnings(); return sauce; } /** * Compiles this Soy file set into a set of java classes implementing the {@link CompiledTemplate} * interface and writes them out to the given ByteSink as a JAR file. * * @throws SoyCompilationException If compilation fails. */ void compileToJar(ByteSink jarTarget, Optional srcJarTarget) throws IOException { resetErrorReporter(); disallowExternalCalls(); ServerCompilationPrimitives primitives = compileForServerRendering(); BytecodeCompiler.compileToJar(primitives.registry, errorReporter, jarTarget); if (srcJarTarget.isPresent()) { BytecodeCompiler.writeSrcJar(primitives.registry, soyFileSuppliers, srcJarTarget.get()); } throwIfErrorsPresent(); reportWarnings(); } /** Helper method to compile SoySauce from {@link ServerCompilationPrimitives} */ private SoySauce doCompileSoySauce(ServerCompilationPrimitives primitives) { Optional templates = BytecodeCompiler.compile( primitives.registry, // if there is an AST cache, assume we are in 'dev mode' and trigger lazy compilation. cache != null, errorReporter); throwIfErrorsPresent(); return new SoySauceImpl( templates.get(), apiCallScopeProvider, soyValueConverter, soyFunctionMap, printDirectives); } /** * A tuple of the outputs of shared compiler passes that are needed to produce SoyTofu or * SoySauce. */ private static final class ServerCompilationPrimitives { final SoyFileSetNode soyTree; final TemplateRegistry registry; ServerCompilationPrimitives(TemplateRegistry registry, SoyFileSetNode soyTree) { this.registry = registry; this.soyTree = soyTree; } } /** Runs common compiler logic shared by tofu and jbcsrc backends. */ private ServerCompilationPrimitives compileForServerRendering() { ParseResult result = parse(SyntaxVersion.V2_0); throwIfErrorsPresent(); SoyFileSetNode soyTree = result.fileSet(); TemplateRegistry registry = result.registry(); // Clear the SoyDoc strings because they use unnecessary memory, unless we have a cache, in // which case it is pointless. if (cache == null) { new ClearSoyDocStringsVisitor().exec(soyTree); } throwIfErrorsPresent(); return new ServerCompilationPrimitives(registry, soyTree); } private ImmutableMap> getTransitiveIjs( SoyFileSetNode soyTree, TemplateRegistry registry) { ImmutableMap templateToIjParamsInfoMap = new FindIjParamsVisitor(registry).execOnAllTemplates(soyTree); ImmutableMap.Builder> templateToTransitiveIjParams = ImmutableMap.builder(); for (Map.Entry entry : templateToIjParamsInfoMap.entrySet()) { templateToTransitiveIjParams.put( entry.getKey().getTemplateName(), entry.getValue().ijParamSet); } return templateToTransitiveIjParams.build(); } private void disallowExternalCalls() { TriState allowExternalCalls = generalOptions.allowExternalCalls(); if (allowExternalCalls == TriState.UNSET) { generalOptions.setAllowExternalCalls(false); } else if (allowExternalCalls == TriState.ENABLED) { throw new IllegalStateException( "SoyGeneralOptions.setAllowExternalCalls(true) is not supported with this method"); } // otherwise, it was already explicitly set to false which is what we want. } private void requireStrictAutoescaping() { TriState strictAutoescapingRequired = generalOptions.isStrictAutoescapingRequired(); if (strictAutoescapingRequired == TriState.UNSET) { generalOptions.setStrictAutoescapingRequired(true); } else if (strictAutoescapingRequired == TriState.DISABLED) { throw new IllegalStateException( "SoyGeneralOptions.isStrictAutoescapingRequired(false) is not supported with this" + " method"); } // otherwise, it was already explicitly set to true which is what we want. } /** * 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 SoyCompilationException If compilation fails. */ @SuppressWarnings("deprecation") public List compileToJsSrc( SoyJsSrcOptions jsSrcOptions, @Nullable SoyMsgBundle msgBundle) { ParseResult result = preprocessJsSrcResults(jsSrcOptions); TemplateRegistry registry = result.registry(); SoyFileSetNode fileSet = result.fileSet(); List generatedSrcs = new JsSrcMain(apiCallScopeProvider, typeRegistry) .genJsSrc(fileSet, registry, jsSrcOptions, msgBundle, errorReporter); throwIfErrorsPresent(); reportWarnings(); return generatedSrcs; } /** * 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 msgPlugin The {@link SoyMsgPlugin} to use, or null if not applicable * @param messageFilePathFormat The message file path format, or null if not applicable. * @throws SoyCompilationException If compilation fails. * @throws IOException If there is an error in opening/reading a message file or opening/writing * an output JS file. */ @SuppressWarnings("deprecation") void compileToJsSrcFiles( String outputPathFormat, String inputFilePathPrefix, SoyJsSrcOptions jsSrcOptions, List locales, @Nullable SoyMsgPlugin msgPlugin, @Nullable String messageFilePathFormat) throws IOException { ParseResult result = preprocessJsSrcResults(jsSrcOptions); SoyFileSetNode soyTree = result.fileSet(); TemplateRegistry registry = result.registry(); if (locales.isEmpty()) { // Not generating localized JS. new JsSrcMain(apiCallScopeProvider, typeRegistry) .genJsFiles( soyTree, registry, jsSrcOptions, null, null, outputPathFormat, inputFilePathPrefix, errorReporter); } else { checkArgument( msgPlugin != null, "a message plugin must be provided when generating localized sources"); checkArgument( messageFilePathFormat != null, "a messageFilePathFormat must be provided when generating localized sources"); // Generating localized JS. for (String locale : locales) { SoyFileSetNode soyTreeClone = soyTree.copy(new CopyState()); String msgFilePath = MainEntryPointUtils.buildFilePath( messageFilePathFormat, locale, null, inputFilePathPrefix); SoyMsgBundle msgBundle = new SoyMsgBundleHandler(msgPlugin).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); } } new JsSrcMain(apiCallScopeProvider, typeRegistry) .genJsFiles( soyTreeClone, registry, jsSrcOptions, locale, msgBundle, outputPathFormat, inputFilePathPrefix, errorReporter); } } throwIfErrorsPresent(); reportWarnings(); } @SuppressWarnings("deprecation") private ParseResult preprocessJsSrcResults(SoyJsSrcOptions jsSrcOptions) { resetErrorReporter(); // Synchronize old and new ways to declare syntax version V1. if (jsSrcOptions.shouldAllowDeprecatedSyntax()) { generalOptions.setDeclaredSyntaxVersionName("1.0"); } // JS has traditionally allowed unknown globals, as a way for soy to reference normal js enums // and constants. For consistency/reusability of templates it would be nice to not allow that // but the cat is out of the bag. PassManager.Builder builder = passManagerBuilder(SyntaxVersion.V2_0).allowUnknownGlobals().desugarHtmlNodes(false); ParseResult parseResult = parse(builder); throwIfErrorsPresent(); return parseResult; } /** * Compiles this Soy file set into iDOM 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. * @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 SoyCompilationException If compilation fails. */ public List compileToIncrementalDomSrc(SoyIncrementalDomSrcOptions jsSrcOptions) { resetErrorReporter(); ParseResult result = preprocessIncrementalDOMResults(); List generatedSrcs = new IncrementalDomSrcMain(apiCallScopeProvider, typeRegistry) .genJsSrc(result.fileSet(), result.registry(), jsSrcOptions, errorReporter); throwIfErrorsPresent(); reportWarnings(); return generatedSrcs; } /** * 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 jsSrcOptions The compilation options for the JS Src output target. * @throws SoyCompilationException If compilation fails. * @throws IOException If there is an error in opening/reading a message file or opening/writing * an output JS file. */ void compileToIncrementalDomSrcFiles( String outputPathFormat, SoyIncrementalDomSrcOptions jsSrcOptions) throws IOException { resetErrorReporter(); disallowExternalCalls(); ParseResult result = preprocessIncrementalDOMResults(); new IncrementalDomSrcMain(apiCallScopeProvider, typeRegistry) .genJsFiles( result.fileSet(), result.registry(), jsSrcOptions, outputPathFormat, errorReporter); throwIfErrorsPresent(); reportWarnings(); } /** Prepares the parsed result for use in generating Incremental DOM source code. */ @SuppressWarnings("deprecation") private ParseResult preprocessIncrementalDOMResults() { SyntaxVersion declaredSyntaxVersion = generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_0); Preconditions.checkState( declaredSyntaxVersion.num >= SyntaxVersion.V2_0.num, "Incremental DOM code generation only supports syntax version of V2 or higher."); requireStrictAutoescaping(); // For incremental dom backend, we don't deguar HTML nodes since it requires HTML context. // Also, we don't add HTML comments, since idom library does not support HTML comments. ParseResult result = parse( passManagerBuilder(SyntaxVersion.V2_0) .desugarHtmlNodes(false) .setAutoescaperEnabled(false) .addHtmlCommentsForDebug(false)); throwIfErrorsPresent(); 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 SoyCompilationException If compilation fails. * @throws IOException If there is an error in opening/reading a message file or opening/writing * an output JS file. */ public void compileToPySrcFiles( String outputPathFormat, String inputFilePathPrefix, SoyPySrcOptions pySrcOptions) throws IOException { resetErrorReporter(); requireStrictAutoescaping(); ParseResult result = parse(SyntaxVersion.V2_0); throwIfErrorsPresent(); new PySrcMain(apiCallScopeProvider) .genPyFiles( result.fileSet(), pySrcOptions, outputPathFormat, inputFilePathPrefix, errorReporter); throwIfErrorsPresent(); reportWarnings(); } // Parse the current file set with the given default syntax version. private ParseResult parse(SyntaxVersion defaultVersion) { return parse(passManagerBuilder(defaultVersion)); } private PassManager.Builder passManagerBuilder(SyntaxVersion defaultVersion) { return new PassManager.Builder() .setGeneralOptions(generalOptions) .setDeclaredSyntaxVersion(generalOptions.getDeclaredSyntaxVersion(defaultVersion)) .setSoyPrintDirectiveMap(printDirectives) .setErrorReporter(errorReporter) .setConformanceConfig(conformanceConfig) .setLoggingConfig(loggingConfig); } private ParseResult parse(PassManager.Builder builder) { return parse( builder, typeRegistry, new PluginResolver( generalOptions.getDeclaredSyntaxVersion(SyntaxVersion.V2_0) == SyntaxVersion.V1_0 ? PluginResolver.Mode.ALLOW_UNDEFINED_FUNCTIONS_FOR_V1_SUPPORT : PluginResolver.Mode.REQUIRE_DEFINITIONS, printDirectives, soyFunctionMap, errorReporter)); } private ParseResult parse( PassManager.Builder builder, SoyTypeRegistry typeRegistry, PluginResolver resolver) { return SoyFileSetParser.newBuilder() .setCache(cache) .setSoyFileSuppliers(soyFileSuppliers) .setTypeRegistry(typeRegistry) .setPluginResolver(resolver) .setPassManager(builder.setTypeRegistry(typeRegistry).build()) .setErrorReporter(errorReporter) .setGeneralOptions(generalOptions) .build() .parse(); } /** * This method resets the error reporter field in preparation to a new compiler invocation. * *

This method should be called at the beginning of every entry point into SoyFileSet. */ private void resetErrorReporter() { errorReporter = ErrorReporter.create(soyFileSuppliers); } private void throwIfErrorsPresent() { if (errorReporter.hasErrors()) { // if we are reporting errors we should report warnings at the same time. Iterable errors = Iterables.concat(errorReporter.getErrors(), errorReporter.getWarnings()); // clear the field to ensure that error reporters can't leak between compilations errorReporter = null; throw new SoyCompilationException(errors); } } /** * Reports warnings ot the user configured warning sink. Should be called at the end of successful * compiles. */ private void reportWarnings() { ImmutableList warnings = errorReporter.getWarnings(); if (warnings.isEmpty()) { return; } // this is a custom feature used by the integration test suite. if (generalOptions.getExperimentalFeatures().contains("testonly_throw_on_warnings")) { errorReporter = null; throw new SoyCompilationException(warnings); } String formatted = SoyErrors.formatErrors(warnings); if (warningSink != null) { try { warningSink.append(formatted); } catch (IOException ioe) { System.err.println("error while printing warnings"); ioe.printStackTrace(); } } else { logger.warning(formatted); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy