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

com.google.javascript.jscomp.AbstractCommandLineRunner Maven / Gradle / Ivy

There is a newer version: 9.0.8
Show newest version
/*
 * Copyright 2009 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.Math.min;
import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.google.javascript.jscomp.CompilerOptions.JsonStreamMode;
import com.google.javascript.jscomp.CompilerOptions.OutputJs;
import com.google.javascript.jscomp.CompilerOptions.TweakProcessing;
import com.google.javascript.jscomp.deps.ModuleLoader;
import com.google.javascript.jscomp.deps.SourceCodeEscapers;
import com.google.javascript.jscomp.ijs.IjsErrors;
import com.google.javascript.jscomp.parsing.Config;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticSourceFile.SourceKind;
import com.google.javascript.rhino.TokenStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;

/**
 * Implementations of AbstractCommandLineRunner translate flags into Java API calls on the Compiler.
 * AbstractCompiler contains common flags and logic to make that happen.
 *
 * 

This class may be extended and used to create other Java classes that behave the same as * running the Compiler from the command line. Example: * *

 * class MyCommandLineRunner extends
 *     AbstractCommandLineRunner<MyCompiler, MyOptions> {
 *   MyCommandLineRunner(String[] args) {
 *     super(args);
 *   }
 *
 *   @Override
 *   protected MyOptions createOptions() {
 *     MyOptions options = new MyOptions();
 *     CompilerFlagTranslator.setOptionsFromFlags(options);
 *     addMyCrazyCompilerPassThatOutputsAnExtraFile(options);
 *     return options;
 *   }
 *
 *   @Override
 *   protected MyCompiler createCompiler() {
 *     return new MyCompiler();
 *   }
 *
 *   public static void main(String[] args) {
 *     (new MyCommandLineRunner(args)).run();
 *   }
 * }
 * 
*/ public abstract class AbstractCommandLineRunner { static final DiagnosticType OUTPUT_SAME_AS_INPUT_ERROR = DiagnosticType.error( "JSC_OUTPUT_SAME_AS_INPUT_ERROR", "Bad output file (already listed as input file): {0}"); static final DiagnosticType COULD_NOT_SERIALIZE_AST = DiagnosticType.error("JSC_COULD_NOT_SERIALIZE_AST", "Could not serialize ast to: {0}"); static final DiagnosticType COULD_NOT_DESERIALIZE_AST = DiagnosticType.error("JSC_COULD_NOT_DESERIALIZE_AST", "Could not deserialize ast from: {0}"); static final DiagnosticType NO_TREE_GENERATED_ERROR = DiagnosticType.error( "JSC_NO_TREE_GENERATED_ERROR", "Code contains errors. No tree was generated."); static final DiagnosticType INVALID_MODULE_SOURCEMAP_PATTERN = DiagnosticType.error( "JSC_INVALID_MODULE_SOURCEMAP_PATTERN", "When using --module flags, the --create_source_map flag must contain " + "%outname% in the value."); static final String WAITING_FOR_INPUT_WARNING = "The compiler is waiting for input via stdin."; @GwtIncompatible("Unnecessary") private final CommandLineConfig config; @GwtIncompatible("Unnecessary") private final InputStream in; @GwtIncompatible("Unnecessary") private final PrintStream defaultJsOutput; @GwtIncompatible("Unnecessary") private final PrintStream err; @GwtIncompatible("Unnecessary") private A compiler; @GwtIncompatible("Unnecessary") private Charset inputCharset; // NOTE(nicksantos): JSCompiler has always used ASCII as the default // output charset. This was done to solve legacy problems with // bad proxies, etc. We are not sure if these issues are still problems, // and changing the encoding would require a big obnoxious migration plan. // // New outputs should use outputCharset2, which is how I would have // designed this if I had a time machine. @GwtIncompatible("Unnecessary") private Charset outputCharset2; @GwtIncompatible("Unnecessary") private Charset legacyOutputCharset; @GwtIncompatible("Unnecessary") private boolean testMode = false; @GwtIncompatible("Unnecessary") private Supplier> externsSupplierForTesting = null; @GwtIncompatible("Unnecessary") private Supplier> inputsSupplierForTesting = null; @GwtIncompatible("Unnecessary") private Supplier> modulesSupplierForTesting = null; @GwtIncompatible("Unnecessary") private Function exitCodeReceiver = SystemExitCodeReceiver.INSTANCE; @GwtIncompatible("Unnecessary") private Map rootRelativePathsMap = null; @GwtIncompatible("Unnecessary") private Map parsedModuleWrappers = null; @GwtIncompatible("Unnecessary") private Map parsedModuleOutputFiles = null; @GwtIncompatible("Unnecessary") private final Gson gson; static final String OUTPUT_MARKER = "%output%"; private static final String OUTPUT_MARKER_JS_STRING = "%output|jsstring%"; @GwtIncompatible("Unnecessary") private final List filesToStreamOut = new ArrayList<>(); @GwtIncompatible("Unnecessary") AbstractCommandLineRunner() { this(System.in, System.out, System.err); } @GwtIncompatible("Unnecessary") AbstractCommandLineRunner(PrintStream out, PrintStream err) { this(System.in, out, err); } @GwtIncompatible("Unnecessary") AbstractCommandLineRunner(InputStream in, PrintStream out, PrintStream err) { this.config = new CommandLineConfig(); this.in = checkNotNull(in); this.defaultJsOutput = checkNotNull(out); this.err = checkNotNull(err); this.gson = new Gson(); } /** * Put the command line runner into test mode. In test mode, all outputs will be blackholed. * * @param externsSupplier A provider for externs. * @param inputsSupplier A provider for source inputs. * @param modulesSupplier A provider for modules. Only one of inputsSupplier and modulesSupplier * may be non-null. * @param exitCodeReceiver A receiver for the status code that would have been passed to * System.exit in non-test mode. */ @VisibleForTesting @GwtIncompatible("Unnecessary") void enableTestMode( Supplier> externsSupplier, Supplier> inputsSupplier, Supplier> modulesSupplier, Function exitCodeReceiver) { checkArgument(inputsSupplier == null != (modulesSupplier == null)); testMode = true; this.externsSupplierForTesting = externsSupplier; this.inputsSupplierForTesting = inputsSupplier; this.modulesSupplierForTesting = modulesSupplier; this.exitCodeReceiver = exitCodeReceiver; } /** * @param newExitCodeReceiver receives a non-zero integer to indicate a problem during execution * or 0i to indicate success. */ @GwtIncompatible("Unnecessary") public void setExitCodeReceiver(Function newExitCodeReceiver) { this.exitCodeReceiver = checkNotNull(newExitCodeReceiver); } /** Returns whether we're in test mode. */ @GwtIncompatible("Unnecessary") protected boolean isInTestMode() { return testMode; } /** Returns whether output should be a JSON stream. */ @GwtIncompatible("Unnecessary") private boolean isOutputInJson() { return config.jsonStreamMode == JsonStreamMode.OUT || config.jsonStreamMode == JsonStreamMode.BOTH; } /** Get the command line config, so that it can be initialized. */ @GwtIncompatible("Unnecessary") protected CommandLineConfig getCommandLineConfig() { return config; } /** Returns the instance of the Compiler to use when {@link #run()} is called. */ @GwtIncompatible("Unnecessary") protected abstract A createCompiler(); /** * Performs any transformation needed on the given compiler input and appends it to the given * output bundle. */ @GwtIncompatible("Unnecessary") protected abstract void prepForBundleAndAppendTo( Appendable out, CompilerInput input, String content) throws IOException; /** Writes whatever runtime libraries are needed to bundle. */ @GwtIncompatible("Unnecessary") protected abstract void appendRuntimeTo(Appendable out) throws IOException; /** * Returns the instance of the Options to use when {@link #run()} is called. createCompiler() is * called before createOptions(), so getCompiler() will not return null when createOptions() is * called. */ @GwtIncompatible("Unnecessary") protected abstract B createOptions(); /** The warning classes that are available from the command-line. */ @GwtIncompatible("Unnecessary") protected DiagnosticGroups getDiagnosticGroups() { if (compiler == null) { return new DiagnosticGroups(); } return compiler.getDiagnosticGroups(); } @GwtIncompatible("Unnecessary") protected abstract void addAllowlistWarningsGuard(CompilerOptions options, File allowlistFile); @GwtIncompatible("Unnecessary") protected static void setWarningGuardOptions( CompilerOptions options, ArrayList> warningGuards, DiagnosticGroups diagnosticGroups) { if (warningGuards != null) { final Set groupNames = DiagnosticGroups.getRegisteredGroups().keySet(); for (FlagEntry entry : warningGuards) { if ("*".equals(entry.value)) { for (String groupName : groupNames) { if (!DiagnosticGroups.wildcardExcludedGroups.contains(groupName)) { diagnosticGroups.setWarningLevel(options, groupName, entry.flag); } } } else if (groupNames.contains(entry.value)) { diagnosticGroups.setWarningLevel(options, entry.value, entry.flag); } else { throw new FlagUsageException("Unknown diagnostic group: '" + entry.value + "'"); } } } } /** * Sets options based on the configurations set flags API. Called during the run() run() method. * If you want to ignore the flags API, or interpret flags your own way, then you should override * this method. */ @GwtIncompatible("Unnecessary") protected void setRunOptions(CompilerOptions options) throws IOException { DiagnosticGroups diagnosticGroups = getDiagnosticGroups(); if (config.shouldSaveAfterStage1() || config.shouldContinueCompilation()) { if (options.checksOnly) { throw new FlagUsageException( "checks_only mode is incompatible with multi-stage compilation"); } if (options.getExternExportsPath() != null) { throw new FlagUsageException( "generating externs from exports is incompatible with multi-stage compilation"); } } setWarningGuardOptions(options, config.warningGuards, diagnosticGroups); if (!config.warningsAllowFile.isEmpty()) { addAllowlistWarningsGuard(options, new File(config.warningsAllowFile)); } if (!config.hideWarningsFor.isEmpty()) { options.addWarningsGuard( new ShowByPathWarningsGuard( config.hideWarningsFor.toArray(new String[] {}), ShowByPathWarningsGuard.ShowType.EXCLUDE)); } List define = new ArrayList<>(config.define); if (config.browserFeaturesetYear != 0) { try { options.setBrowserFeaturesetYear(config.browserFeaturesetYear); } catch (IllegalStateException e) { throw new FlagUsageException(e.getMessage()); } } if (config.emitAsyncFunctionsWithZonejs) { options.setAllowZoneJsWithAsyncFunctionsInOutput(true); } createDefineReplacements(define, options); options.setTweakProcessing(config.tweakProcessing); // TODO(tjgq): Unconditionally set the options. if (config.dependencyOptions != null) { options.setDependencyOptions(config.dependencyOptions); } options.devMode = config.jscompDevMode; options.setCodingConvention(config.codingConvention); options.setSummaryDetailLevel(config.summaryDetailLevel); options.setTrustedStrings(true); legacyOutputCharset = options.outputCharset = getLegacyOutputCharset(); outputCharset2 = getOutputCharset2(); inputCharset = getInputCharset(); if (config.jsOutputFile.length() > 0) { if (config.skipNormalOutputs) { throw new FlagUsageException( "skip_normal_outputs and js_output_file" + " cannot be used together."); } } if (config.skipNormalOutputs && config.printAst) { throw new FlagUsageException( "skip_normal_outputs and print_ast cannot" + " be used together."); } if (config.skipNormalOutputs && config.printTree) { throw new FlagUsageException( "skip_normal_outputs and print_tree cannot" + " be used together."); } if (config.createSourceMap.length() > 0) { options.sourceMapOutputPath = config.createSourceMap; } else if (isOutputInJson()) { options.sourceMapOutputPath = "%outname%"; } options.sourceMapDetailLevel = config.sourceMapDetailLevel; options.sourceMapFormat = config.sourceMapFormat; options.sourceMapLocationMappings = config.sourceMapLocationMappings; options.parseInlineSourceMaps = config.parseInlineSourceMaps; options.applyInputSourceMaps = config.applyInputSourceMaps; ImmutableMap.Builder inputSourceMaps = new ImmutableMap.Builder<>(); for (Map.Entry files : config.sourceMapInputFiles.entrySet()) { SourceFile sourceMap = SourceFile.builder().withKind(SourceKind.NON_CODE).withPath(files.getValue()).build(); inputSourceMaps.put(files.getKey(), new SourceMapInput(sourceMap)); } options.inputSourceMaps = inputSourceMaps.buildOrThrow(); if (!config.variableMapInputFile.isEmpty()) { options.inputVariableMap = VariableMap.load(config.variableMapInputFile); } if (!config.propertyMapInputFile.isEmpty()) { options.inputPropertyMap = VariableMap.load(config.propertyMapInputFile); } if (!config.outputManifests.isEmpty()) { Set uniqueNames = new HashSet<>(); for (String filename : config.outputManifests) { if (!uniqueNames.add(filename)) { throw new FlagUsageException( "output_manifest flags specify duplicate file names: " + filename); } } } if (!config.outputBundles.isEmpty()) { Set uniqueNames = new HashSet<>(); for (String filename : config.outputBundles) { if (!uniqueNames.add(filename)) { throw new FlagUsageException( "output_bundle flags specify duplicate file names: " + filename); } } } options.transformAMDToCJSModules = config.transformAMDToCJSModules; options.setProcessCommonJSModules(config.processCommonJSModules); options.moduleRoots = config.moduleRoots; options.angularPass = config.angularPass; if (!config.jsonWarningsFile.isEmpty()) { options.addReportGenerator( new JsonErrorReportGenerator(new PrintStream(config.jsonWarningsFile), compiler)); } if (config.errorFormat == CommandLineConfig.ErrorFormatOption.JSON) { JsonErrorReportGenerator errorGenerator = new JsonErrorReportGenerator(getErrorPrintStream(), compiler); compiler.setErrorManager(new SortingErrorManager(ImmutableSet.of(errorGenerator))); } if (config.printTree) { options.setParseJsDocDocumentation(Config.JsDocParsing.INCLUDE_ALL_COMMENTS); } } @GwtIncompatible("Unnecessary") protected final A getCompiler() { return compiler; } /** * @return a mutable list * @throws IOException */ @GwtIncompatible("Unnecessary") public static List getBuiltinExterns(CompilerOptions.Environment env) throws IOException { try (InputStream input = getExternsInput()) { ZipInputStream zip = new ZipInputStream(input); String envPrefix = Ascii.toLowerCase(env.toString()) + "/"; Map mapFromExternsZip = new HashMap<>(); for (ZipEntry entry = null; (entry = zip.getNextEntry()) != null; ) { String filename = entry.getName(); // Always load externs in the root folder. // If the non-core-JS externs are organized in subfolders, only load // the ones in a subfolder matching the specified environment. Strip the subfolder. if (filename.contains("/")) { if (!filename.startsWith(envPrefix)) { continue; } filename = filename.substring(envPrefix.length()); // remove envPrefix, including '/' } BufferedInputStream entryStream = new BufferedInputStream(ByteStreams.limit(zip, entry.getSize())); mapFromExternsZip.put( filename, // Give the files an odd prefix, so that they do not conflict // with the user's files. SourceFile.builder() .withPath("externs.zip//" + filename) .withContent(entryStream) .build()); } return DefaultExterns.prepareExterns(env, mapFromExternsZip); } } /** * Some text identifying this binary and its version. * *

At minimum, this is what will be printed when `--version` is passed. */ protected abstract String getVersionText(); @GwtIncompatible("Unnecessary") private static InputStream getExternsInput() { InputStream input = AbstractCommandLineRunner.class.getResourceAsStream("/externs.zip"); if (input == null) { // In some environments, the externs.zip is relative to this class. input = AbstractCommandLineRunner.class.getResourceAsStream("externs.zip"); } checkNotNull(input); return input; } /** Runs the Compiler and calls System.exit() with the exit status of the compiler. */ @GwtIncompatible("Unnecessary") public final void run() { int result; try { result = doRun(); } catch (FlagUsageException e) { err.println(e.getMessage()); result = -1; } catch (Throwable t) { t.printStackTrace(err); result = -2; } exitCodeReceiver.apply(result); } /** Returns the PrintStream for writing errors associated with this AbstractCommandLineRunner. */ @GwtIncompatible("Unnecessary") protected final PrintStream getErrorPrintStream() { return err; } @GwtIncompatible("Unnecessary") public List parseJsonFilesFromInputStream() throws IOException { List jsonFiles = new ArrayList<>(); try (JsonReader reader = new JsonReader(new InputStreamReader(this.in, inputCharset))) { reader.beginArray(); while (reader.hasNext()) { JsonFileSpec jsonFile = gson.fromJson(reader, JsonFileSpec.class); jsonFiles.add(jsonFile); } reader.endArray(); } return jsonFiles; } /** * Creates inputs from a list of files. * * @param files A list of flag entries indicates js and zip file names. * @param allowStdIn Whether '-' is allowed appear as a filename to represent stdin. If true, '-' * is only allowed to appear once. * @param jsChunkSpecs A list chunk specs. * @return An array of inputs */ @GwtIncompatible("Unnecessary") private List createInputs( List> files, boolean allowStdIn, List jsChunkSpecs) throws IOException { return createInputs(files, /* jsonFiles= */ null, allowStdIn, jsChunkSpecs); } /** * Creates inputs from a list of source files and json files. * * @param files A list of flag entries indicates js and zip file names. * @param jsonFiles A list of json encoded files. * @param jsChunkSpecs A list chunk specs. * @return An array of inputs */ @GwtIncompatible("Unnecessary") private List createInputs( List> files, List jsonFiles, List jsChunkSpecs) throws IOException { return createInputs(files, jsonFiles, /* allowStdIn= */ false, jsChunkSpecs); } /** * Creates inputs from a list of source files, zips and json files. * *

Can be overridden by subclasses who want to pull files from different places. * * @param files A list of flag entries indicates js and zip file names * @param jsonFiles A list of json encoded files. * @param allowStdIn Whether '-' is allowed appear as a filename to represent stdin. If true, '-' * is only allowed to appear once. * @param jsChunkSpecs A list chunk specs. * @return An array of inputs */ @GwtIncompatible("Unnecessary") protected List createInputs( List> files, List jsonFiles, boolean allowStdIn, List jsChunkSpecs) throws IOException { List inputs = new ArrayList<>(files.size()); boolean usingStdin = false; int jsChunkIndex = 0; JsChunkSpec jsChunkSpec = Iterables.getFirst(jsChunkSpecs, null); int cumulatedInputFilesExpected = jsChunkSpec == null ? Integer.MAX_VALUE : jsChunkSpec.getNumInputs(); for (int i = 0; i < files.size(); i++) { FlagEntry file = files.get(i); String filename = file.value; if (file.flag == JsSourceType.JS_ZIP) { if (this.config.typedAstListInputFilename != null) { throw new FlagUsageException("Can't use TypedASTs with --zip."); } if (!"-".equals(filename)) { List newFiles = SourceFile.fromZipFile(filename, inputCharset); // Update the manifest maps for new zip entries. if (rootRelativePathsMap.containsKey(filename)) { String rootFilename = rootRelativePathsMap.get(filename); for (SourceFile zipEntry : newFiles) { String zipEntryName = zipEntry.getName(); checkState(zipEntryName.contains(filename)); String zipmap = zipEntryName.replace(filename, rootFilename); rootRelativePathsMap.put(zipEntryName, zipmap); } } inputs.addAll(newFiles); if (jsChunkSpec != null) { jsChunkSpec.numJsFiles += newFiles.size() - 1; } } } else if (!"-".equals(filename)) { SourceKind kind = file.flag == JsSourceType.WEAKDEP ? SourceKind.WEAK : SourceKind.STRONG; SourceFile newFile = SourceFile.builder() .withPath(filename) .withCharset(inputCharset) .withKind(kind) .build(); inputs.add(newFile); } else { if (this.config.typedAstListInputFilename != null) { throw new FlagUsageException("Can't use TypedASTs with stdin."); } if (!config.defaultToStdin) { throw new FlagUsageException("Can't specify stdin."); } if (usingStdin) { throw new FlagUsageException("Can't specify stdin twice."); } if (!config.outputManifests.isEmpty()) { throw new FlagUsageException( "Manifest files cannot be generated when the input is from stdin."); } if (!config.outputBundles.isEmpty()) { throw new FlagUsageException( "Bundle files cannot be generated when the input is from stdin."); } this.err.println(WAITING_FOR_INPUT_WARNING); inputs.add( SourceFile.builder() .withPath("stdin") .withContent(this.in) .withCharset(inputCharset) .build()); usingStdin = true; } if (i >= cumulatedInputFilesExpected - 1) { jsChunkIndex++; if (jsChunkIndex < jsChunkSpecs.size()) { jsChunkSpec = jsChunkSpecs.get(jsChunkIndex); cumulatedInputFilesExpected += jsChunkSpec.getNumInputs(); } } } if (jsonFiles != null) { for (JsonFileSpec jsonFile : jsonFiles) { inputs.add(SourceFile.fromCode(jsonFile.getPath(), jsonFile.getSrc())); } } return inputs; } /** * Removes input --ijs files whose basename matches an input --js or --weakdep file. * * @throws FlagUsageException If there are both input --ijs files and module specs. */ @GwtIncompatible("Unnecessary") private ImmutableList deduplicateIjsFiles( List> files, List moduleRoots, boolean hasModuleSpecs) { ImmutableList.Builder errors = ImmutableList.builder(); // First pass: collect the (module root relative) names of --js and --weakdep files. Map relativeToAbsoluteName = new HashMap<>(); for (FlagEntry file : files) { // TODO(tjgq): Handle zip files. if (file.flag == JsSourceType.JS || file.flag == JsSourceType.WEAKDEP) { String absoluteName = file.value; String relativeName = getModuleRootRelativeName(absoluteName, moduleRoots); relativeToAbsoluteName.put(relativeName, absoluteName); } } // Second pass: drop --ijs files whose (module root relative) name matches a --js or --weakdep // file. Iterator> iterator = files.iterator(); while (iterator.hasNext()) { FlagEntry file = iterator.next(); if (file.flag == JsSourceType.IJS) { if (hasModuleSpecs) { throw new FlagUsageException("--ijs is incompatible with --chunk or --module."); } String absoluteName = file.value; if (!absoluteName.endsWith(".i.js")) { errors.add(JSError.make(IjsErrors.BAD_IJS_FILE_NAME, absoluteName)); continue; } String relativeName = getModuleRootRelativeName(absoluteName, moduleRoots); String relativeNonIjsName = relativeName.substring(0, relativeName.length() - ".i.js".length()); if (relativeToAbsoluteName.containsKey(relativeNonIjsName)) { errors.add( JSError.make( IjsErrors.CONFLICTING_IJS_FILE, relativeToAbsoluteName.get(relativeNonIjsName), absoluteName)); iterator.remove(); } } } return errors.build(); } private String getModuleRootRelativeName(String filename, List moduleRoots) { for (String moduleRoot : moduleRoots) { if (filename.startsWith(moduleRoot + "/")) { return filename.substring(moduleRoot.length() + 1); } } return filename; } /** Creates JS source code inputs from a list of files. */ @GwtIncompatible("Unnecessary") private List createSourceInputs( List jsChunkSpecs, List> files, List jsonFiles, List moduleRoots) throws IOException { if (isInTestMode()) { return inputsSupplierForTesting != null ? inputsSupplierForTesting.get() : null; } if (files.isEmpty() && jsonFiles == null && config.defaultToStdin) { // Request to read from stdin. files = ImmutableList.of(new FlagEntry(JsSourceType.JS, "-")); } for (JSError error : deduplicateIjsFiles(files, moduleRoots, !jsChunkSpecs.isEmpty())) { compiler.report(error); } try { if (jsonFiles != null) { return createInputs(files, jsonFiles, jsChunkSpecs); } else { return createInputs(files, true, jsChunkSpecs); } } catch (FlagUsageException e) { throw new FlagUsageException("Bad --js flag. " + e.getMessage()); } } /** Creates JS extern inputs from a list of files. */ @GwtIncompatible("Unnecessary") private List createExternInputs(List files) throws IOException { List> externFiles = new ArrayList<>(); for (String file : files) { externFiles.add(new FlagEntry(JsSourceType.EXTERN, file)); } try { return createInputs(externFiles, false, new ArrayList()); } catch (FlagUsageException e) { throw new FlagUsageException("Bad --externs flag. " + e.getMessage()); } } /** * Creates module objects from a list of chunk specifications. * * @param specs A list of chunk specifications, not null or empty. * @param inputs A list of JS file paths, not null * @return An array of module objects */ public static List createJsModules(List specs, List inputs) throws IOException { checkState(specs != null); checkState(!specs.isEmpty()); checkState(inputs != null); List moduleNames = new ArrayList<>(specs.size()); Map modulesByName = new LinkedHashMap<>(); Map modulesFileCountMap = new LinkedHashMap<>(); int numJsFilesExpected = 0; int minJsFilesRequired = 0; for (JsChunkSpec spec : specs) { if (modulesByName.containsKey(spec.name)) { throw new FlagUsageException("Duplicate module name: " + spec.name); } JSChunk module = new JSChunk(spec.name); for (String dep : spec.deps) { JSChunk other = modulesByName.get(dep); if (other == null) { throw new FlagUsageException( "Module '" + spec.name + "' depends on unknown module '" + dep + "'. Be sure to list modules in dependency order."); } module.addDependency(other); } // We will allow modules of zero input. if (spec.numJsFiles < 0) { numJsFilesExpected = -1; } else { minJsFilesRequired += spec.numJsFiles; } if (numJsFilesExpected >= 0) { numJsFilesExpected += spec.numJsFiles; } // Add modules in reverse order so that source files are allocated to // modules in reverse order. This allows the first module // (presumably the base module) to have a size of 'auto' moduleNames.add(0, spec.name); modulesFileCountMap.put(spec.name, spec.numJsFiles); modulesByName.put(spec.name, module); } final int totalNumJsFiles = inputs.size(); if (numJsFilesExpected >= 0 || minJsFilesRequired > totalNumJsFiles) { if (minJsFilesRequired > totalNumJsFiles) { numJsFilesExpected = minJsFilesRequired; } if (numJsFilesExpected > totalNumJsFiles) { throw new FlagUsageException( "Not enough JS files specified. Expected " + numJsFilesExpected + " but found " + totalNumJsFiles); } else if (numJsFilesExpected < totalNumJsFiles) { throw new FlagUsageException( "Too many JS files specified. Expected " + numJsFilesExpected + " but found " + totalNumJsFiles); } } int numJsFilesLeft = totalNumJsFiles; int moduleIndex = 0; for (String moduleName : moduleNames) { // Parse module inputs. int numJsFiles = modulesFileCountMap.get(moduleName); JSChunk module = modulesByName.get(moduleName); // Check if the first chunk specified 'auto' for the number of files if (moduleIndex == moduleNames.size() - 1 && numJsFiles == -1) { numJsFiles = numJsFilesLeft; } List moduleFiles = inputs.subList(numJsFilesLeft - numJsFiles, numJsFilesLeft); for (CompilerInput input : moduleFiles) { module.add(input); } numJsFilesLeft -= numJsFiles; moduleIndex++; } return new ArrayList<>(modulesByName.values()); } /** * Validates the module name. Can be overridden by subclasses. * * @param name The module name */ @GwtIncompatible("Unnecessary") protected void checkModuleName(String name) { if (!TokenStream.isJSIdentifier(name)) { throw new FlagUsageException("Invalid module name: '" + name + "'"); } } /** * Parses module wrapper specifications. * * @param specs A list of module wrapper specifications, not null. The spec format is: * name:wrapper. Wrappers. * @param chunks The JS chunks whose wrappers are specified * @return A map from module name to module wrapper. Modules with no wrapper will have the empty * string as their value in this map. */ public static Map parseModuleWrappers( List specs, Iterable chunks) { checkState(specs != null); Map wrappers = new HashMap<>(); // Prepopulate the map with module names. for (JSChunk c : chunks) { wrappers.put(c.getName(), ""); } for (String spec : specs) { // Format is ":". int pos = spec.indexOf(':'); if (pos == -1) { throw new FlagUsageException( "Expected module wrapper to have " + ": format: " + spec); } // Parse module name. String name = spec.substring(0, pos); if (!wrappers.containsKey(name)) { throw new FlagUsageException("Unknown module: '" + name + "'"); } String wrapper = spec.substring(pos + 1); // Support for %n% and %output% wrapper = wrapper.replace("%output%", "%s").replace("%n%", "\n"); if (!wrapper.contains("%s")) { throw new FlagUsageException("No %s placeholder in module wrapper: '" + wrapper + "'"); } wrappers.put(name, wrapper); } return wrappers; } /** * Parses module output name specifications. * * @param specs A list of module output name specifications, not null. The spec format is: {@code * name:output_file}. * @return A map from module name to module output file name, if declared. Modules with no * predeclared output file name will have no entry in this map. */ private static ImmutableMap parseModuleOutputFiles(List specs) { checkArgument(specs != null); ImmutableMap.Builder outputFilesBuilder = ImmutableMap.builder(); for (String spec : specs) { // Format is ":". int pos = spec.indexOf(':'); if (pos == -1) { throw new FlagUsageException( "Expected chunk_output_file to have " + ": format: " + spec); } String name = spec.substring(0, pos); String filename = spec.substring(pos + 1); outputFilesBuilder.put(name, filename); } return outputFilesBuilder.buildOrThrow(); } /** * Returns the output file name for a chunk. * *

For chunks with predeclared output file names specified using {@code --chunk_output_file}, * the the output file name is {@code /} * *

Otherwise, the output file name is {@code /.js} */ @GwtIncompatible("Unnecessary") @VisibleForTesting String getModuleOutputFileName(JSChunk m) { if (parsedModuleOutputFiles == null) { parsedModuleOutputFiles = parseModuleOutputFiles(config.moduleOutputFiles); } String outputFile = parsedModuleOutputFiles.get(m.getName()); if (outputFile != null) { return config.moduleOutputPathPrefix + outputFile; } return config.moduleOutputPathPrefix + m.getName() + ".js"; } @VisibleForTesting @GwtIncompatible("Unnecessary") void writeModuleOutput(String fileName, Appendable out, JSChunk m) throws IOException { if (parsedModuleWrappers == null) { parsedModuleWrappers = parseModuleWrappers( config.moduleWrapper, ImmutableList.copyOf(compiler.getModuleGraph().getAllChunks())); } if (!isOutputInJson()) { maybeCreateDirsForPath(fileName); } String baseName = new File(fileName).getName(); writeOutput( out, compiler, m, parsedModuleWrappers.get(m.getName()).replace("%basename%", baseName), "%s", null, fileName); } /** * Writes code to an output stream, optionally wrapping it in an arbitrary wrapper that contains a * placeholder where the code should be inserted. * * @param module Which module to write. If this is null, write the entire AST. */ @GwtIncompatible("Unnecessary") void writeOutput( Appendable out, Compiler compiler, @Nullable JSChunk module, String wrapper, String codePlaceholder, @Nullable Function escaper, String filename) throws IOException { if (compiler.getOptions().outputJs == OutputJs.SENTINEL) { out.append("// No JS output because the compiler was run in checks-only mode.\n"); return; } checkState(compiler.getOptions().outputJs == OutputJs.NORMAL); String code = module == null ? compiler.toSource() : compiler.toSource(module); writeOutput(out, compiler, code, wrapper, codePlaceholder, escaper, filename); } /** * Writes code to an output stream, optionally wrapping it in an arbitrary wrapper that contains a * placeholder where the code should be inserted. */ @GwtIncompatible("Unnecessary") @VisibleForTesting void writeOutput( Appendable out, Compiler compiler, String code, String wrapper, String codePlaceholder, @Nullable Function escaper, String filename) throws IOException { int pos = wrapper.indexOf(codePlaceholder); if (pos != -1) { String prefix = ""; if (pos > 0) { prefix = wrapper.substring(0, pos); out.append(prefix); } out.append(escaper == null ? code : escaper.apply(code)); int suffixStart = pos + codePlaceholder.length(); if (suffixStart != wrapper.length()) { // Something after placeholder? out.append(wrapper.substring(suffixStart)); } // Make sure we always end output with a line feed. out.append('\n'); // If we have a source map, adjust its offsets to match // the code WITHIN the wrapper. if (compiler != null && compiler.getSourceMap() != null) { compiler.getSourceMap().setWrapperPrefix(prefix); } } else { out.append(code); out.append('\n'); } } /** Creates any directories necessary to write a file that will have a given path prefix. */ @GwtIncompatible("Unnecessary") private static void maybeCreateDirsForPath(String pathPrefix) { if (!Strings.isNullOrEmpty(pathPrefix)) { String dirName = pathPrefix.charAt(pathPrefix.length() - 1) == File.separatorChar ? pathPrefix.substring(0, pathPrefix.length() - 1) : new File(pathPrefix).getParent(); if (dirName != null) { new File(dirName).mkdirs(); } } } @GwtIncompatible("Unnecessary") private Appendable createDefaultOutput() throws IOException { boolean writeOutputToFile = !config.jsOutputFile.isEmpty(); if (writeOutputToFile) { return fileNameToLegacyOutputWriter(config.jsOutputFile); } else { return streamToLegacyOutputWriter(defaultJsOutput); } } @GwtIncompatible("Unnecessary") private static void closeAppendable(Appendable output) throws IOException { if (output instanceof Flushable) { ((Flushable) output).flush(); } if (output instanceof Closeable) { ((Closeable) output).close(); } } /** * Parses command-line arguments and runs the compiler. * * @return system exit status */ @GwtIncompatible("Unnecessary") protected int doRun() throws IOException { CompileMetricsRecorderInterface metricsRecorder = getCompileMetricsRecorder(); metricsRecorder.recordActionStart(); if (this.config.printVersion) { this.defaultJsOutput.println(this.getVersionText()); this.defaultJsOutput.flush(); return 0; } Compiler.setLoggingLevel(Level.parse(config.loggingLevel)); compiler = createCompiler(); B options = createOptions(); setRunOptions(options); @Nullable String typedAstListInputFilename = config.typedAstListInputFilename; List externs = createExterns(options); List modules = null; rootRelativePathsMap = constructRootRelativePathsMap(); boolean writeOutputToFile = !config.jsOutputFile.isEmpty(); List outputFileNames = new ArrayList<>(); if (writeOutputToFile) { outputFileNames.add(config.jsOutputFile); } boolean createCommonJsModules = false; if (options.getProcessCommonJSModules() && (config.module.size() == 1 && "auto".equals(config.module.get(0)))) { createCommonJsModules = true; config.module.remove(0); } List jsChunkSpecs = new ArrayList<>(); for (int i = 0; i < config.module.size(); i++) { jsChunkSpecs.add(JsChunkSpec.create(config.module.get(i), i == 0)); } List jsonFiles = null; if (config.jsonStreamMode == JsonStreamMode.IN || config.jsonStreamMode == JsonStreamMode.BOTH) { jsonFiles = parseJsonFilesFromInputStream(); ImmutableMap.Builder inputSourceMaps = new ImmutableMap.Builder<>(); ImmutableMap.Builder inputPathByWebpackId = new ImmutableMap.Builder<>(); boolean foundJsonInputSourceMap = false; for (JsonFileSpec jsonFile : jsonFiles) { if (jsonFile.getSourceMap() != null && jsonFile.getSourceMap().length() > 0) { String sourceMapPath = jsonFile.getPath() + ".map"; SourceFile sourceMap = SourceFile.fromCode(sourceMapPath, jsonFile.getSourceMap()); inputSourceMaps.put(jsonFile.getPath(), new SourceMapInput(sourceMap)); foundJsonInputSourceMap = true; } if (jsonFile.getWebpackId() != null) { inputPathByWebpackId.put(jsonFile.getWebpackId(), jsonFile.getPath()); } } if (foundJsonInputSourceMap) { inputSourceMaps.putAll(options.inputSourceMaps); options.inputSourceMaps = inputSourceMaps.buildOrThrow(); } compiler.initWebpackMap(inputPathByWebpackId.buildOrThrow()); } else { ImmutableMap emptyMap = ImmutableMap.of(); compiler.initWebpackMap(emptyMap); } options.setDoLateLocalization(config.shouldDoLateLocalization()); compiler.initOptions(options); List sources = createSourceInputs(jsChunkSpecs, config.mixedJsSources, jsonFiles, config.moduleRoots); if (!jsChunkSpecs.isEmpty()) { if (isInTestMode()) { modules = modulesSupplierForTesting.get(); } else { if (JsonStreamMode.IN.equals(config.jsonStreamMode) || JsonStreamMode.NONE.equals(config.jsonStreamMode)) { for (JsChunkSpec m : jsChunkSpecs) { checkModuleName(m.getName()); } } ImmutableList.Builder inputs = ImmutableList.builder(); for (SourceFile source : sources) { inputs.add(new CompilerInput(source, /* isExtern= */ false)); } modules = createJsModules(jsChunkSpecs, inputs.build()); } for (JSChunk m : modules) { outputFileNames.add(getModuleOutputFileName(m)); } if (typedAstListInputFilename != null) { this.initModulesWithTypedAstFilesystem( externs, modules, options, typedAstListInputFilename); } else { compiler.initModules(externs, modules, options); } } else { if (typedAstListInputFilename != null) { this.initWithTypedAstFilesystem(externs, sources, options, typedAstListInputFilename); } else { compiler.init(externs, sources, options); } } // Release temporary data structures now that the compiler has // been initialized jsChunkSpecs = null; jsonFiles = null; externs = null; sources = null; if (options.printConfig) { compiler.printConfig(); } Result result; // We won't want to process results for cases where compilation is only partially done. boolean shouldProcessResults = true; if (config.skipNormalOutputs) { metricsRecorder.recordActionName("skip normal outputs"); // TODO(bradfordcsmith): Should we be ignoring possible init/initModules() errors here? compiler.orderInputsWithLargeStack(); result = null; } else if (compiler.hasErrors()) { metricsRecorder.recordActionName("initialization errors occurred"); // init() or initModules() encountered an error. compiler.generateReport(); result = compiler.getResult(); } else if (options.getInstrumentForCoverageOnly()) { result = instrumentForCoverage(metricsRecorder); } else if (config.shouldSaveAfterStage1()) { result = performStage1andSave(config.getSaveCompilationStateToFilename(), metricsRecorder); // Don't output any results, since compilation isn't done yet. shouldProcessResults = false; } else if (typedAstListInputFilename != null) { result = parseAndPerformStages2and3(metricsRecorder); } else if (config.shouldRestoreAndPerformStage2AndSave()) { result = restoreAndPerformStage2AndSave( config.getContinueSavedCompilationFileName(), config.getSaveCompilationStateToFilename(), metricsRecorder); // Don't output any results, since compilation isn't done yet. shouldProcessResults = false; } else if (config.shouldRestoreAndPerformStages2And3()) { result = restoreAndPerformStages2and3( config.getContinueSavedCompilationFileName(), metricsRecorder); if (modules != null) { modules = ImmutableList.copyOf(compiler.getModules()); } } else if (config.shouldRestoreAndPerformStage3()) { result = restoreAndPerformStage3(config.getContinueSavedCompilationFileName(), metricsRecorder); if (modules != null) { modules = ImmutableList.copyOf(compiler.getModules()); } } else { result = performFullCompilation(metricsRecorder); } if (createCommonJsModules) { // For Commonchunks construct modules from actual inputs. modules = ImmutableList.copyOf(compiler.getModules()); for (JSChunk m : modules) { outputFileNames.add(getModuleOutputFileName(m)); } } for (String outputFileName : outputFileNames) { if (compiler.getSourceFileByName(outputFileName) != null) { compiler.report(JSError.make(OUTPUT_SAME_AS_INPUT_ERROR, outputFileName)); return 1; } } int exitStatus = shouldProcessResults ? processResults(result, modules, options) : 0; metricsRecorder.recordResultMetrics(compiler, result); return exitStatus; } /** * Child classes should override this if they want to actually record metrics about the * compilation. */ protected CompileMetricsRecorderInterface getCompileMetricsRecorder() { return new DummyCompileMetricsRecorder(); } @GwtIncompatible("Unnecessary") private Result performStage1andSave( String filename, CompileMetricsRecorderInterface metricsRecorder) { metricsRecorder.recordActionName("stage 1"); try (BufferedOutputStream serializedOutputStream = new BufferedOutputStream(new FileOutputStream(filename))) { compiler.parseForCompilation(); if (!compiler.hasErrors()) { metricsRecorder.recordStartState(compiler); compiler.stage1Passes(); } if (!compiler.hasErrors()) { compiler.saveState(serializedOutputStream); } compiler.performPostCompilationTasks(); } catch (IOException e) { compiler.report(JSError.make(COULD_NOT_SERIALIZE_AST, filename)); } finally { // Make sure we generate a report of errors and warnings even if the compiler throws an // exception somewhere. compiler.generateReport(); } return compiler.getResult(); } @GwtIncompatible("Unnecessary") private void initWithTypedAstFilesystem( List externs, List sources, CompilerOptions options, String filename) { try (BufferedInputStream typedAstListStream = new BufferedInputStream(new FileInputStream(filename))) { compiler.initWithTypedAstFilesystem(externs, sources, options, typedAstListStream); } catch (IOException e) { compiler.report(JSError.make(COULD_NOT_DESERIALIZE_AST, filename)); } } @GwtIncompatible("Unnecessary") private void initModulesWithTypedAstFilesystem( List externs, List chunks, CompilerOptions options, String filename) { try (BufferedInputStream typedAstListStream = new BufferedInputStream(new FileInputStream(filename))) { compiler.initModulesWithTypedAstFilesystem(externs, chunks, options, typedAstListStream); } catch (IOException e) { compiler.report(JSError.make(COULD_NOT_DESERIALIZE_AST, filename)); } } @GwtIncompatible("Unnecessary") private Result parseAndPerformStages2and3(CompileMetricsRecorderInterface metricsRecorder) { metricsRecorder.recordActionName("skip-checks compile"); try { compiler.parseForCompilation(); if (!compiler.hasErrors()) { metricsRecorder.recordStartState(compiler); compiler.stage2Passes(); if (!compiler.hasErrors()) { compiler.stage3Passes(); } compiler.performPostCompilationTasks(); } } finally { // Make sure we generate a report of errors and warnings even if the compiler throws an // exception somewhere. compiler.generateReport(); } return compiler.getResult(); } @GwtIncompatible("Unnecessary") private Result restoreAndPerformStage2AndSave( String inputFilename, String outputFilename, CompileMetricsRecorderInterface metricsRecorder) { metricsRecorder.recordActionName("stage 2/3"); try (BufferedInputStream serializedInputStream = new BufferedInputStream(new FileInputStream(inputFilename))) { compiler.restoreState(serializedInputStream); if (!compiler.hasErrors()) { metricsRecorder.recordStartState(compiler); compiler.stage2Passes(); if (!compiler.hasErrors()) { try (BufferedOutputStream serializedOutputStream = new BufferedOutputStream(new FileOutputStream(outputFilename))) { compiler.saveState(serializedOutputStream); } catch (IOException e) { compiler.report(JSError.make(COULD_NOT_SERIALIZE_AST, outputFilename)); } compiler.performPostCompilationTasks(); } } } catch (IOException | ClassNotFoundException e) { compiler.report(JSError.make(COULD_NOT_DESERIALIZE_AST, inputFilename)); } finally { // Make sure we generate a report of errors and warnings even if the compiler throws an // exception somewhere. compiler.generateReport(); } return compiler.getResult(); } @GwtIncompatible("Unnecessary") private Result restoreAndPerformStages2and3( String filename, CompileMetricsRecorderInterface metricsRecorder) { // From the outside this looks like the second stage of a 2-stage compile. metricsRecorder.recordActionName("stage 2/2"); try (BufferedInputStream serializedInputStream = new BufferedInputStream(new FileInputStream(filename))) { compiler.restoreState(serializedInputStream); if (!compiler.hasErrors()) { metricsRecorder.recordStartState(compiler); compiler.stage2Passes(); if (!compiler.hasErrors()) { compiler.stage3Passes(); } } compiler.performPostCompilationTasks(); } catch (IOException | ClassNotFoundException e) { compiler.report(JSError.make(COULD_NOT_DESERIALIZE_AST, filename)); } finally { // Make sure we generate a report of errors and warnings even if the compiler throws an // exception somewhere. compiler.generateReport(); } return compiler.getResult(); } @GwtIncompatible("Unnecessary") private Result restoreAndPerformStage3( String filename, CompileMetricsRecorderInterface metricsRecorder) { metricsRecorder.recordActionName("stage 3/3"); try (BufferedInputStream serializedInputStream = new BufferedInputStream(new FileInputStream(filename))) { compiler.restoreState(serializedInputStream); if (!compiler.hasErrors()) { metricsRecorder.recordStartState(compiler); compiler.stage3Passes(); } compiler.performPostCompilationTasks(); } catch (IOException | ClassNotFoundException e) { compiler.report(JSError.make(COULD_NOT_DESERIALIZE_AST, filename)); } finally { // Make sure we generate a report of errors and warnings even if the compiler throws an // exception somewhere. compiler.generateReport(); } return compiler.getResult(); } @GwtIncompatible("Unnecessary") private Result performFullCompilation(CompileMetricsRecorderInterface metricsRecorder) { // This is the code path taken when "building" a library by just checking it for errors // and generating an .ijs file and also when doing a full compilation. metricsRecorder.recordActionName( compiler.getOptions().checksOnly ? "checks-only" : "full compile"); try { compiler.parseForCompilation(); if (!compiler.hasErrors()) { metricsRecorder.recordStartState(compiler); compiler.stage1Passes(); if (!compiler.hasErrors()) { compiler.stage2Passes(); if (!compiler.hasErrors()) { compiler.stage3Passes(); } } compiler.performPostCompilationTasks(); } } finally { // Make sure we generate a report of errors and warnings even if the compiler throws an // exception somewhere. compiler.generateReport(); } return compiler.getResult(); } @GwtIncompatible("Unnecessary") private Result instrumentForCoverage(CompileMetricsRecorderInterface metricsRecorder) { metricsRecorder.recordActionName("instrument for coverage"); try { compiler.parseForCompilation(); if (!compiler.hasErrors()) { metricsRecorder.recordStartState(compiler); compiler.instrumentForCoverage(); } } finally { compiler.generateReport(); } return compiler.getResult(); } /** Processes the results of the compile job, and returns an error code. */ @GwtIncompatible("Unnecessary") int processResults(Result result, List modules, B options) throws IOException { if (config.printAst) { if (compiler.getRoot() == null) { return 1; } else { Appendable jsOutput = createDefaultOutput(); ControlFlowGraph cfg = compiler.computeCFG(); DotFormatter.appendDot(compiler.getRoot().getLastChild(), cfg, jsOutput); jsOutput.append('\n'); closeAppendable(jsOutput); return 0; } } if (config.printTree) { if (compiler.getRoot() == null) { compiler.report(JSError.make(NO_TREE_GENERATED_ERROR)); return 1; } else { Appendable jsOutput = createDefaultOutput(); compiler.getRoot().appendStringTree(jsOutput); jsOutput.append("\n"); closeAppendable(jsOutput); return 0; } } if (config.skipNormalOutputs) { // Output the manifest and bundle files if requested. outputManifest(); outputBundle(); outputModuleGraphJson(); return 0; } else if (options.outputJs != OutputJs.NONE && result.success) { outputModuleGraphJson(); if (modules == null) { outputSingleBinary(options); // Output the source map if requested. // If output files are being written to stdout as a JSON string, // outputSingleBinary will have added the sourcemap to the output file if (!isOutputInJson()) { outputSourceMap(options, config.jsOutputFile); } } else { DiagnosticType error = outputModuleBinaryAndSourceMaps(compiler.getModules(), options); if (error != null) { compiler.report(JSError.make(error)); return 1; } } // Output the externs if required. if (options.getExternExportsPath() != null) { try (Writer eeOut = openExternExportsStream(options, config.jsOutputFile)) { eeOut.append(result.externExport); } } // Output the variable and property name maps if requested. outputNameMaps(); // Output the ReplaceStrings map if requested outputStringMap(); // Output the manifest and bundle files if requested. outputManifest(); outputBundle(); // Output the production instrumentation param mapping if requested. outputInstrumentationMapping(); if (isOutputInJson()) { outputJsonStream(); } } // return 0 if no errors, the error count otherwise return min(result.errors.size(), 0x7f); } @GwtIncompatible("Unnecessary") Function getJavascriptEscaper() { return SourceCodeEscapers.javascriptEscaper().asFunction(); } @GwtIncompatible("Unnecessary") void outputSingleBinary(B options) throws IOException { Function escaper = null; String marker = OUTPUT_MARKER; if (config.outputWrapper.contains(OUTPUT_MARKER_JS_STRING)) { marker = OUTPUT_MARKER_JS_STRING; escaper = getJavascriptEscaper(); } if (isOutputInJson()) { this.filesToStreamOut.add(createJsonFile(options, marker, escaper)); } else { if (!config.jsOutputFile.isEmpty()) { maybeCreateDirsForPath(config.jsOutputFile); } Appendable jsOutput = createDefaultOutput(); writeOutput( jsOutput, compiler, (JSChunk) null, config.outputWrapper, marker, escaper, config.jsOutputFile); closeAppendable(jsOutput); } } /** Save the compiler output to a JsonFileSpec to be later written to stdout */ @GwtIncompatible("Unnecessary") JsonFileSpec createJsonFile(B options, String outputMarker, Function escaper) throws IOException { Appendable jsOutput = new StringBuilder(); writeOutput( jsOutput, compiler, (JSChunk) null, config.outputWrapper, outputMarker, escaper, config.jsOutputFile); JsonFileSpec jsonOutput = new JsonFileSpec( jsOutput.toString(), Strings.isNullOrEmpty(config.jsOutputFile) ? "compiled.js" : config.jsOutputFile); if (!Strings.isNullOrEmpty(options.sourceMapOutputPath)) { StringBuilder sourcemap = new StringBuilder(); compiler.getSourceMap().appendTo(sourcemap, jsonOutput.getPath()); jsonOutput.setSourceMap(sourcemap.toString()); } return jsonOutput; } @GwtIncompatible("Unnecessary") void outputJsonStream() throws IOException { try (JsonWriter jsonWriter = new JsonWriter(new BufferedWriter(new OutputStreamWriter(defaultJsOutput, UTF_8)))) { Gson gsonOut = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); Type filesCollectionType = new TypeToken>() {}.getType(); gsonOut.toJson(this.filesToStreamOut, filesCollectionType, jsonWriter); } } @GwtIncompatible("Unnecessary") private DiagnosticType outputModuleBinaryAndSourceMaps(Iterable modules, B options) throws IOException { parsedModuleWrappers = parseModuleWrappers(config.moduleWrapper, modules); if (!isOutputInJson()) { maybeCreateDirsForPath(config.moduleOutputPathPrefix); } // If the source map path is in fact a pattern for each // module, create a stream per-module. Otherwise, create // a single source map. Writer mapFileOut = null; // When the json_streams flag is specified, sourcemaps are always generated // per module if (!(shouldGenerateMapPerModule(options) || options.sourceMapOutputPath == null || config.jsonStreamMode == JsonStreamMode.OUT || config.jsonStreamMode == JsonStreamMode.BOTH)) { // warn that this is not supported return INVALID_MODULE_SOURCEMAP_PATTERN; } for (JSChunk m : modules) { if (m.getName().equals(JSChunk.WEAK_CHUNK_NAME)) { // Skip the weak module, which is always empty. continue; } if (isOutputInJson()) { this.filesToStreamOut.add(createJsonFileFromModule(m)); } else { if (shouldGenerateMapPerModule(options)) { mapFileOut = fileNameToOutputWriter2(expandSourceMapPath(options, m)); } String moduleFilename = getModuleOutputFileName(m); maybeCreateDirsForPath(moduleFilename); try (Writer writer = fileNameToLegacyOutputWriter(moduleFilename)) { if (options.sourceMapOutputPath != null) { compiler.resetAndIntitializeSourceMap(); } writeModuleOutput(moduleFilename, writer, m); if (options.sourceMapOutputPath != null) { compiler.getSourceMap().appendTo(mapFileOut, moduleFilename); } } if (shouldGenerateMapPerModule(options) && mapFileOut != null) { mapFileOut.close(); mapFileOut = null; } } } if (mapFileOut != null) { mapFileOut.close(); } return null; } /** Given an output module, convert it to a JSONFileSpec with associated sourcemap */ @GwtIncompatible("Unnecessary") private JsonFileSpec createJsonFileFromModule(JSChunk module) throws IOException { compiler.resetAndIntitializeSourceMap(); String filename = getModuleOutputFileName(module); StringBuilder output = new StringBuilder(); writeModuleOutput(filename, output, module); JsonFileSpec jsonFile = new JsonFileSpec(output.toString(), filename); StringBuilder moduleSourceMap = new StringBuilder(); compiler.getSourceMap().appendTo(moduleSourceMap, getModuleOutputFileName(module)); jsonFile.setSourceMap(moduleSourceMap.toString()); return jsonFile; } /** * Query the flag for the input charset, and return a Charset object representing the selection. * * @return Charset to use when reading inputs * @throws FlagUsageException if flag is not a valid Charset name. */ @GwtIncompatible("Unnecessary") private Charset getInputCharset() { if (!config.charset.isEmpty()) { if (!Charset.isSupported(config.charset)) { throw new FlagUsageException(config.charset + " is not a valid charset name."); } return Charset.forName(config.charset); } return UTF_8; } /** * Query the flag for the output charset. * *

Let the outputCharset be the same as the input charset... except if we're reading in UTF-8 * by default. By tradition, we've always output ASCII to avoid various hiccups with different * browsers, proxies and firewalls. * * @return Name of the charset to use when writing outputs. Guaranteed to be a supported charset. * @throws FlagUsageException if flag is not a valid Charset name. */ @GwtIncompatible("Unnecessary") private Charset getLegacyOutputCharset() { if (!config.charset.isEmpty()) { if (!Charset.isSupported(config.charset)) { throw new FlagUsageException(config.charset + " is not a valid charset name."); } return Charset.forName(config.charset); } return US_ASCII; } /** * Query the flag for the output charset. Defaults to UTF-8. * * @throws FlagUsageException if flag is not a valid Charset name. */ @GwtIncompatible("Unnecessary") private Charset getOutputCharset2() { if (!config.charset.isEmpty()) { if (!Charset.isSupported(config.charset)) { throw new FlagUsageException(config.charset + " is not a valid charset name."); } return Charset.forName(config.charset); } return UTF_8; } @GwtIncompatible("Unnecessary") protected List createExterns(CompilerOptions options) throws IOException { return isInTestMode() ? externsSupplierForTesting.get() : createExternInputs(config.externs); } /** * Returns true if and only if a source map file should be generated for each module, as opposed * to one unified map. This is specified by having the source map pattern include the %outname% * variable. */ @GwtIncompatible("Unnecessary") protected boolean shouldGenerateMapPerModule(B options) { return options.sourceMapOutputPath != null && options.sourceMapOutputPath.contains("%outname%"); } /** * Returns a stream for outputting the generated externs file. * * @param options The options to the Compiler. * @param path The path of the generated JS source file. * @return The stream or null if no extern-ed exports are being generated. */ @GwtIncompatible("Unnecessary") private Writer openExternExportsStream(B options, String path) throws IOException { final String externExportsPath = options.getExternExportsPath(); if (externExportsPath == null) { return null; } String exPath = externExportsPath; if (!exPath.contains(File.separator)) { File outputFile = new File(path); exPath = outputFile.getParent() + File.separatorChar + exPath; } return fileNameToOutputWriter2(exPath); } /** * Expand a file path specified on the command-line. * *

Most file paths on the command-line allow an %outname% placeholder. The placeholder will * expand to a different value depending on the current output mode. There are three scenarios: * *

1) Single JS output, single extra output: sub in jsOutputPath. 2) Multiple JS output, single * extra output: sub in the base module name. 3) Multiple JS output, multiple extra output: sub in * the module output file. * *

Passing a JSChunk to this function automatically triggers case #3. Otherwise, we'll use * strategy #1 or #2 based on the current output mode. */ @GwtIncompatible("Unnecessary") private String expandCommandLinePath(String path, JSChunk forModule) { String sub; if (forModule != null) { sub = getModuleOutputFileName(forModule); } else if (!config.module.isEmpty()) { sub = config.moduleOutputPathPrefix; } else { sub = config.jsOutputFile; } return path.replace("%outname%", sub); } /** Expansion function for source map. */ @VisibleForTesting @GwtIncompatible("Unnecessary") String expandSourceMapPath(B options, JSChunk forModule) { if (Strings.isNullOrEmpty(options.sourceMapOutputPath)) { return null; } return expandCommandLinePath(options.sourceMapOutputPath, forModule); } /** * Converts a file name into a Writer taking in account the output charset. Returns null if the * file name is null. */ @GwtIncompatible("Unnecessary") private Writer fileNameToLegacyOutputWriter(String fileName) throws IOException { if (fileName == null) { return null; } if (isInTestMode()) { return new StringWriter(); } return streamToLegacyOutputWriter(filenameToOutputStream(fileName)); } /** * Converts a file name into a Writer taking in account the output charset. Returns null if the * file name is null. */ @GwtIncompatible("Unnecessary") private Writer fileNameToOutputWriter2(String fileName) throws IOException { if (fileName == null) { return null; } if (isInTestMode()) { return new StringWriter(); } return streamToOutputWriter2(filenameToOutputStream(fileName)); } /** Converts a file name into a Outputstream. Returns null if the file name is null. */ @GwtIncompatible("Unnecessary") protected OutputStream filenameToOutputStream(String fileName) throws IOException { if (fileName == null) { return null; } return new FileOutputStream(fileName); } /** Create a writer with the legacy output charset. */ @GwtIncompatible("Unnecessary") private Writer streamToLegacyOutputWriter(OutputStream stream) throws IOException { if (legacyOutputCharset == null) { return new BufferedWriter(new OutputStreamWriter(stream, UTF_8)); } else { return new BufferedWriter(new OutputStreamWriter(stream, legacyOutputCharset)); } } /** Create a writer with the newer output charset. */ @GwtIncompatible("Unnecessary") private Writer streamToOutputWriter2(OutputStream stream) { if (outputCharset2 == null) { return new BufferedWriter(new OutputStreamWriter(stream, UTF_8)); } else { return new BufferedWriter(new OutputStreamWriter(stream, outputCharset2)); } } /** * Outputs the source map found in the compiler to the proper path if one exists. * * @param options The options to the Compiler. */ @GwtIncompatible("Unnecessary") private void outputSourceMap(B options, String associatedName) throws IOException { if (Strings.isNullOrEmpty(options.sourceMapOutputPath) || options.sourceMapOutputPath.equals("/dev/null")) { return; } String outName = expandSourceMapPath(options, null); maybeCreateDirsForPath(outName); try (Writer out = fileNameToOutputWriter2(outName)) { compiler.getSourceMap().appendTo(out, associatedName); } } /** * Returns the path at which to output map file(s) based on the path at which the JS binary will * be placed. * * @return The path in which to place the generated map file(s). */ @GwtIncompatible("Unnecessary") private String getMapPath(String outputFile) { String basePath = ""; if (outputFile.isEmpty()) { // If we have a js_module_binary rule, output the maps // at modulename_props_map.out, etc. if (!config.moduleOutputPathPrefix.isEmpty()) { basePath = config.moduleOutputPathPrefix; } else { basePath = "jscompiler"; } } else { // Get the path of the output file. File file = new File(outputFile); String outputFileName = file.getName(); // Strip the .js from the name. if (outputFileName.endsWith(".js")) { outputFileName = outputFileName.substring(0, outputFileName.length() - 3); } String fileParent = file.getParent(); if (fileParent == null) { basePath = outputFileName; } else { basePath = file.getParent() + File.separatorChar + outputFileName; } } return basePath; } /** * Outputs the variable and property name maps for the specified compiler if the proper FLAGS are * set. */ @GwtIncompatible("Unnecessary") private void outputNameMaps() throws IOException { String propertyMapOutputPath = null; String variableMapOutputPath = null; // Check the create_name_map_files FLAG. if (config.createNameMapFiles) { String basePath = getMapPath(config.jsOutputFile); propertyMapOutputPath = basePath + "_props_map.out"; variableMapOutputPath = basePath + "_vars_map.out"; } // Check the individual FLAGS. if (!config.variableMapOutputFile.isEmpty()) { if (variableMapOutputPath != null) { throw new FlagUsageException( "The flags variable_map_output_file and " + "create_name_map_files cannot both be used simultaneously."); } variableMapOutputPath = config.variableMapOutputFile; } if (!config.propertyMapOutputFile.isEmpty()) { if (propertyMapOutputPath != null) { throw new FlagUsageException( "The flags property_map_output_file and " + "create_name_map_files cannot both be used simultaneously."); } propertyMapOutputPath = config.propertyMapOutputFile; } // Output the maps. if (variableMapOutputPath != null && compiler.getVariableMap() != null) { compiler.getVariableMap().save(variableMapOutputPath); } if (propertyMapOutputPath != null && compiler.getPropertyMap() != null) { compiler.getPropertyMap().save(propertyMapOutputPath); } } /** * Outputs the string map generated by the {@link ReplaceStrings} pass if an output path exists. */ @GwtIncompatible("Unnecessary") private void outputStringMap() throws IOException { if (!config.stringMapOutputPath.isEmpty()) { if (compiler.getStringMap() == null) { // Ensure an empty file is created if there is no string map. // This avoids confusing some build tools that expect to see the file, even if it is empty. if (!(new File(config.stringMapOutputPath).createNewFile())) { throw new IOException("Could not create file: " + config.stringMapOutputPath); } } else { compiler.getStringMap().save(config.stringMapOutputPath); } } } /** * Create a map of constant names to constant values from a textual description of the map. * * @param definitions A list of overriding definitions for defines in the form {@code * [=]}, where {@code } is a number, boolean, or single-quoted string without * single quotes. */ @VisibleForTesting public static void createDefineReplacements(List definitions, CompilerOptions options) { // Parse the definitions for (String override : definitions) { String[] assignment = override.split("=", 2); String defName = assignment[0]; if (defName.length() > 0) { String defValue = assignment.length == 1 ? "true" : assignment[1]; boolean isTrue = defValue.equals("true"); boolean isFalse = defValue.equals("false"); if (isTrue || isFalse) { options.setDefineToBooleanLiteral(defName, isTrue); continue; } else if (defValue.length() > 1 && ((defValue.charAt(0) == '\'' && defValue.charAt(defValue.length() - 1) == '\'') || (defValue.charAt(0) == '\"' && defValue.charAt(defValue.length() - 1) == '\"'))) { // If the value starts and ends with a single quote, // we assume that it's a string. String maybeStringVal = defValue.substring(1, defValue.length() - 1); if (maybeStringVal.indexOf(defValue.charAt(0)) == -1) { options.setDefineToStringLiteral(defName, maybeStringVal); continue; } } else { try { double value = Double.parseDouble(defValue); options.setDefineToDoubleLiteral(defName, value); continue; } catch (NumberFormatException e) { // do nothing, it will be caught at the end } if (defValue.length() > 0) { options.setDefineToStringLiteral(defName, defValue); continue; } } } throw new RuntimeException("--define flag syntax invalid: " + override); } } /** * Returns true if and only if a manifest or bundle should be generated for each module, as * opposed to one unified manifest. */ @GwtIncompatible("Unnecessary") private boolean shouldGenerateOutputPerModule(String output) { return !config.module.isEmpty() && output != null && output.contains("%outname%"); } @GwtIncompatible("Unnecessary") private void outputManifest() throws IOException { outputManifestOrBundle(config.outputManifests, true); } @GwtIncompatible("Unnecessary") private void outputBundle() throws IOException { outputManifestOrBundle(config.outputBundles, false); } @GwtIncompatible("Unnecessary") private void outputInstrumentationMapping() throws IOException { if (!Strings.isNullOrEmpty(config.instrumentationMappingFile)) { String path = expandCommandLinePath(config.instrumentationMappingFile, /* forModule= */ null); compiler.getInstrumentationMapping().save(path); } } /** * Writes the manifest or bundle of all compiler input files that were included as controlled by * --dependency_mode, if requested. */ @GwtIncompatible("Unnecessary") private void outputManifestOrBundle(List outputFiles, boolean isManifest) throws IOException { if (outputFiles.isEmpty()) { return; } for (String output : outputFiles) { if (output.isEmpty()) { continue; } if (shouldGenerateOutputPerModule(output)) { // Generate per-module manifests or bundles. Iterable modules = compiler.getModuleGraph().getAllChunks(); for (JSChunk module : modules) { try (Writer out = fileNameToOutputWriter2(expandCommandLinePath(output, module))) { if (isManifest) { printManifestTo(module, out); } else { printBundleTo(module, out); } } } } else { // Generate a single file manifest or bundle. try (Writer out = fileNameToOutputWriter2(expandCommandLinePath(output, null))) { if (config.module.isEmpty()) { // For a single-module compilation, generate a single headerless manifest or bundle // containing only the strong files. JSChunk module = compiler.getModuleGraph().getChunkByName(JSChunk.STRONG_CHUNK_NAME); if (isManifest) { printManifestTo(module, out); } else { printBundleTo(module, out); } } else { // For a multi-module compilation, generate a single manifest file with module headers. printModuleGraphManifestOrBundleTo(compiler.getModuleGraph(), out, isManifest); } } } } } /** Creates a file containing the current module graph in JSON serialization. */ @GwtIncompatible("Unnecessary") private void outputModuleGraphJson() throws IOException { if (config.outputModuleDependencies != null && config.outputModuleDependencies.length() != 0) { try (Writer out = fileNameToOutputWriter2(config.outputModuleDependencies)) { printModuleGraphJsonTo(out); } } } /** Prints the current module graph as JSON. */ @VisibleForTesting @GwtIncompatible("Unnecessary") void printModuleGraphJsonTo(Appendable out) throws IOException { out.append(compiler.getModuleGraph().toJson().toString()); } /** Prints a set of modules to the manifest or bundle file. */ @VisibleForTesting @GwtIncompatible("Unnecessary") void printModuleGraphManifestOrBundleTo(JSChunkGraph graph, Appendable out, boolean isManifest) throws IOException { Joiner commas = Joiner.on(","); boolean requiresNewline = false; for (JSChunk module : graph.getAllChunks()) { if (!isManifest && module.isWeak()) { // Skip the weak module on a multi-module bundle, but not a multi-module manifest. continue; } if (requiresNewline) { out.append("\n"); } if (isManifest) { // See CommandLineRunnerTest to see what the format of this // manifest looks like. String dependencies = commas.join(module.getSortedDependencyNames()); out.append( String.format( "{%s%s}\n", module.getName(), dependencies.isEmpty() ? "" : ":" + dependencies)); printManifestTo(module, out); } else { printBundleTo(module, out); } requiresNewline = true; } } /** * Prints a list of input names (using root-relative paths), delimited by newlines, to the * manifest file. */ @VisibleForTesting @GwtIncompatible("Unnecessary") void printManifestTo(JSChunk module, Appendable out) throws IOException { for (CompilerInput input : module.getInputs()) { String name = input.getName(); // Ignore fill files created by the compiler to facilitate cross-module code motion. if (Compiler.isFillFileName(name)) { continue; } String rootRelativePath = rootRelativePathsMap.get(name); String displayName = rootRelativePath != null ? rootRelativePath : name; out.append(displayName); out.append("\n"); } } /** * Prints all the input contents, starting with a comment that specifies the input file name * (using root-relative paths) before each file. */ @VisibleForTesting @GwtIncompatible("Unnecessary") void printBundleTo(JSChunk module, Appendable out) throws IOException { Iterable inputs = module.getInputs(); // Prebuild ASTs before they're needed in getLoadFlags, for performance and because // StackOverflowErrors can be hit if not prebuilt. if (compiler.getOptions().numParallelThreads > 1) { new PrebuildAst(compiler, compiler.getOptions().numParallelThreads).prebuild(inputs); } if (!compiler.getOptions().preventLibraryInjection) { // ES6 modules will need a runtime in a bundle. Skip appending this runtime if there are no // ES6 modules to cut down on size. for (CompilerInput input : inputs) { if ("es6".equals(input.getLoadFlags().get("module"))) { appendRuntimeTo(out); break; } } } for (CompilerInput input : inputs) { String name = input.getName(); String code = input.getSourceFile().getCode(); // Ignore empty fill files created by the compiler to facilitate cross-module code motion. // Note that non-empty fill files (ones whose code has actually been moved into) are still // emitted. In particular, this ensures that if there are no (real) inputs the bundle will be // empty. if (Compiler.isFillFileName(name) && code.isEmpty()) { continue; } String rootRelativePath = rootRelativePathsMap.get(name); String displayName = rootRelativePath != null ? rootRelativePath : input.getName(); out.append("//"); out.append(displayName); out.append("\n"); prepForBundleAndAppendTo(out, input, code); out.append("\n"); } } /** * Construct and return the input root path map. The key is the exec path of each input file, and * the value is the corresponding root relative path. */ @GwtIncompatible("Unnecessary") private Map constructRootRelativePathsMap() { Map rootRelativePathsMap = new LinkedHashMap<>(); for (String mapString : config.manifestMaps) { int colonIndex = mapString.indexOf(':'); checkState(colonIndex > 0); String execPath = mapString.substring(0, colonIndex); String rootRelativePath = mapString.substring(colonIndex + 1); checkState(rootRelativePath.indexOf(':') == -1); rootRelativePathsMap.put(execPath, rootRelativePath); } return rootRelativePathsMap; } /** * Configurations for the command line configs. Designed for easy building, so that we can * decouple the flags-parsing library from the actual configuration options. * *

TODO(tjgq): Investigate whether this class is really needed to mediate between the * CompilerOptions and runner implementations. An alternative would be for the runners to fill in * the CompilerOptions directly, but that conflicts with the latter's mutability and the desire to * reuse the same options across multiple compilations. */ @GwtIncompatible("Unnecessary") protected static class CommandLineConfig { private boolean printVersion; CommandLineConfig setPrintVersion(boolean x) { this.printVersion = x; return this; } private boolean printTree = false; /** Prints out the parse tree and exits */ CommandLineConfig setPrintTree(boolean printTree) { this.printTree = printTree; return this; } private boolean printAst = false; /** Prints a dot file describing the internal abstract syntax tree and exits */ public CommandLineConfig setPrintAst(boolean printAst) { this.printAst = printAst; return this; } private CompilerOptions.DevMode jscompDevMode = CompilerOptions.DevMode.OFF; /** Turns on extra validity checks */ public CommandLineConfig setJscompDevMode(CompilerOptions.DevMode jscompDevMode) { this.jscompDevMode = jscompDevMode; return this; } private String loggingLevel = Level.WARNING.getName(); /** * The logging level (standard java.util.logging.Level values) for Compiler progress. Does not * control errors or warnings for the JavaScript code under compilation */ public CommandLineConfig setLoggingLevel(String loggingLevel) { this.loggingLevel = loggingLevel; return this; } private final List externs = new ArrayList<>(); /** The file containing JavaScript externs. You may specify multiple. */ public CommandLineConfig setExterns(List externs) { this.externs.clear(); this.externs.addAll(externs); return this; } private final List> mixedJsSources = new ArrayList<>(); /** The JavaScript source file names, including .js and .zip files. You may specify multiple. */ public CommandLineConfig setMixedJsSources(List> mixedJsSources) { this.mixedJsSources.clear(); this.mixedJsSources.addAll(mixedJsSources); return this; } private boolean defaultToStdin = false; /** * Whether to read a single source file from standard input if no input files are explicitly * specified. */ public CommandLineConfig setDefaultToStdin() { this.defaultToStdin = true; return this; } private String jsOutputFile = ""; /** Primary output filename. If not specified, output is written to stdout */ public CommandLineConfig setJsOutputFile(String jsOutputFile) { this.jsOutputFile = jsOutputFile; return this; } /** * When non-null specifies a file containing saved compiler state to restore and continue * compiling. */ private String continueSavedCompilationFileName = null; /** * When > 0 indicates the stage at which compilation stopped for the compilation state that is * being restored from a save file. */ private int restoredCompilationStage = -1; /** * When > 0 indicates the stage at which compilation should stop and the compilation state be * stored into a save file. */ private int saveAfterCompilationStage = -1; /** Set the compiler to resume a saved compilation state from a file. */ public CommandLineConfig setContinueSavedCompilationFileName( String fileName, int restoredCompilationStage) { if (fileName != null) { checkArgument( restoredCompilationStage > 0 && restoredCompilationStage < 3, "invalid compilation stage: %s", restoredCompilationStage); continueSavedCompilationFileName = fileName; this.restoredCompilationStage = restoredCompilationStage; } return this; } boolean shouldSaveAfterStage1() { if (saveCompilationStateToFilename != null && saveAfterCompilationStage == 1) { checkState( restoredCompilationStage < 0, "cannot perform stage 1 after restoring from stage %s", restoredCompilationStage); checkState( continueSavedCompilationFileName == null, "cannot restore a saved compilation and also save after stage 1"); return true; } else { return false; } } String getContinueSavedCompilationFileName() { return continueSavedCompilationFileName; } boolean shouldRestoreAndPerformStages2And3() { // We have a saved compilations state to restore return shouldContinueCompilation() // the restored compilation stopped at stage 1 && restoredCompilationStage == 1 // we want to complete the rest of the compilation stages && saveAfterCompilationStage < 0; } private boolean shouldContinueCompilation() { if (continueSavedCompilationFileName != null) { checkState( restoredCompilationStage > 0 && restoredCompilationStage < 3, "invalid restored compilation stage: %s", restoredCompilationStage); return true; } else { return false; } } boolean shouldRestoreAndPerformStage2AndSave() { // We have a saved compilations state to restore return shouldContinueCompilation() // the restored compilation stopped at stage 1 && restoredCompilationStage == 1 // we want to stop and save after stage 2 && saveAfterCompilationStage == 2; } boolean shouldRestoreAndPerformStage3() { if (shouldContinueCompilation() && restoredCompilationStage == 2) { // We have a saved compilations state from stage 2 to restore and continue checkState( saveAfterCompilationStage < 0, "request to save after stage %s is invalid when restoring from stage 2", saveAfterCompilationStage); return true; } else { return false; } } boolean shouldDoLateLocalization() { // The point of doing a 3-stage compilation is to save localization work for last, so // we avoid doing checks and optimizations separately for every locale. // If we aren't doing a 3-stage compilation, then late localization is just doing more work // for a possibly-bigger compiled output (because code gets added after optimizations have // already executed). return shouldRestoreAndPerformStage2AndSave() || shouldRestoreAndPerformStage3(); } @Nullable private String typedAstListInputFilename; public CommandLineConfig setTypedAstListInputFilename(@Nullable String fileName) { this.typedAstListInputFilename = fileName; return this; } private String saveCompilationStateToFilename = null; /** Set the compiler to perform the first phase and save the intermediate result to a file. */ public CommandLineConfig setSaveCompilationStateToFilename( String fileName, int saveAfterCompilationStage) { saveCompilationStateToFilename = fileName; this.saveAfterCompilationStage = saveAfterCompilationStage; return this; } public String getSaveCompilationStateToFilename() { return saveCompilationStateToFilename; } private final List module = new ArrayList<>(); /** * A JavaScript module specification. The format is {@code * :[:[,...][:]]]}. Module names must be unique. Each dep is the name * of a module that this module depends on. Modules must be listed in dependency order, and JS * source files must be listed in the corresponding order. Where --module flags occur in * relation to --js flags is unimportant */ public CommandLineConfig setModule(List module) { this.module.clear(); this.module.addAll(module); return this; } private Map sourceMapInputFiles = new HashMap<>(); public CommandLineConfig setSourceMapInputFiles(Map sourceMapInputFiles) { this.sourceMapInputFiles = sourceMapInputFiles; return this; } private boolean parseInlineSourceMaps = false; public CommandLineConfig setParseInlineSourceMaps(boolean parseInlineSourceMaps) { this.parseInlineSourceMaps = parseInlineSourceMaps; return this; } private String variableMapInputFile = ""; /** * File containing the serialized version of the variable renaming map produced by a previous * compilation */ public CommandLineConfig setVariableMapInputFile(String variableMapInputFile) { this.variableMapInputFile = variableMapInputFile; return this; } private String propertyMapInputFile = ""; /** * File containing the serialized version of the property renaming map produced by a previous * compilation */ public CommandLineConfig setPropertyMapInputFile(String propertyMapInputFile) { this.propertyMapInputFile = propertyMapInputFile; return this; } private String variableMapOutputFile = ""; /** File where the serialized version of the variable renaming map produced should be saved */ public CommandLineConfig setVariableMapOutputFile(String variableMapOutputFile) { this.variableMapOutputFile = variableMapOutputFile; return this; } private boolean createNameMapFiles = false; /** * If true, variable renaming and property renaming map files will be produced as {binary * name}_vars_map.out and {binary name}_props_map.out. Note that this flag cannot be used in * conjunction with either variable_map_output_file or property_map_output_file */ public CommandLineConfig setCreateNameMapFiles(boolean createNameMapFiles) { this.createNameMapFiles = createNameMapFiles; return this; } private String propertyMapOutputFile = ""; /** File where the serialized version of the property renaming map produced should be saved */ public CommandLineConfig setPropertyMapOutputFile(String propertyMapOutputFile) { this.propertyMapOutputFile = propertyMapOutputFile; return this; } private String stringMapOutputPath = ""; /** * File where the serialized version of the string map produced by the ReplaceStrings pass * should be saved. */ public CommandLineConfig setStringMapOutputFile(String stringMapOutputPath) { this.stringMapOutputPath = stringMapOutputPath; return this; } private String instrumentationMappingFile = ""; public CommandLineConfig setInstrumentationMappingFile(String instrumentationMappingFile) { this.instrumentationMappingFile = instrumentationMappingFile; return this; } private CodingConvention codingConvention = CodingConventions.getDefault(); /** Sets rules and conventions to enforce. */ public CommandLineConfig setCodingConvention(CodingConvention codingConvention) { this.codingConvention = codingConvention; return this; } private int summaryDetailLevel = 1; /** * Controls how detailed the compilation summary is. Values: 0 (never print summary), 1 (print * summary only if there are errors or warnings), 2 (print summary if type checking is on, see * --check_types), 3 (always print summary). The default level is 1 */ public CommandLineConfig setSummaryDetailLevel(int summaryDetailLevel) { this.summaryDetailLevel = summaryDetailLevel; return this; } private String outputWrapper = ""; /** * Interpolate output into this string at the place denoted by the marker token %output%, or * %output|jsstring% */ public CommandLineConfig setOutputWrapper(String outputWrapper) { this.outputWrapper = outputWrapper; return this; } private final List moduleWrapper = new ArrayList<>(); /** * An output wrapper for a JavaScript module (optional). See the flag description for formatting * requirements. */ public CommandLineConfig setModuleWrapper(List moduleWrapper) { this.moduleWrapper.clear(); this.moduleWrapper.addAll(moduleWrapper); return this; } private String moduleOutputPathPrefix = ""; /** * Prefix for filenames of compiled chunks. {@code .js} will be appended to this * prefix. Directories will be created as needed. Use with --module */ public CommandLineConfig setModuleOutputPathPrefix(String moduleOutputPathPrefix) { this.moduleOutputPathPrefix = moduleOutputPathPrefix; return this; } private final List moduleOutputFiles = new ArrayList<>(); /** * The output file name for a JavaScript chunk (optional). See the flag description for * formatting requirements. */ public CommandLineConfig setModuleOutputFiles(List moduleOutputFiles) { this.moduleOutputFiles.clear(); this.moduleOutputFiles.addAll(moduleOutputFiles); return this; } private String createSourceMap = ""; /** * If specified, a source map file mapping the generated source files back to the original * source file will be output to the specified path. The %outname% placeholder will expand to * the name of the output file that the source map corresponds to. */ public CommandLineConfig setCreateSourceMap(String createSourceMap) { this.createSourceMap = createSourceMap; return this; } private SourceMap.DetailLevel sourceMapDetailLevel = SourceMap.DetailLevel.ALL; /** The detail supplied in the source map file, if generated. */ public CommandLineConfig setSourceMapDetailLevel(SourceMap.DetailLevel level) { this.sourceMapDetailLevel = level; return this; } private SourceMap.Format sourceMapFormat = SourceMap.Format.DEFAULT; /** The source map format to use, if generated. */ public CommandLineConfig setSourceMapFormat(SourceMap.Format format) { this.sourceMapFormat = format; return this; } private ImmutableList sourceMapLocationMappings = ImmutableList.of(); /** The source map location mappings to use, if generated. */ public CommandLineConfig setSourceMapLocationMappings( List locationMappings) { this.sourceMapLocationMappings = ImmutableList.copyOf(locationMappings); return this; } private boolean applyInputSourceMaps = false; /** * Whether to apply input source maps to the output, i.e. map back to original inputs from input * files that have source maps applied to them. */ public CommandLineConfig setApplyInputSourceMaps(boolean applyInputSourceMaps) { this.applyInputSourceMaps = applyInputSourceMaps; return this; } private final ArrayList> warningGuards = new ArrayList<>(); /** Add warning guards. */ public CommandLineConfig setWarningGuards(List> warningGuards) { this.warningGuards.clear(); this.warningGuards.addAll(warningGuards); return this; } private final List define = new ArrayList<>(); /** * Override the value of a variable annotated @define. The format is {@code [=]}, * where {@code } is the name of a @define variable and {@code } is a boolean, * number, or a single-quoted string that contains no single quotes. If {@code [=]} is * omitted, the variable is marked true */ public CommandLineConfig setDefine(List define) { this.define.clear(); this.define.addAll(define); return this; } private int browserFeaturesetYear = 0; /** Indicates target browser's year */ public CommandLineConfig setBrowserFeaturesetYear(Integer browserFeaturesetYear) { this.browserFeaturesetYear = browserFeaturesetYear; return this; } private boolean emitAsyncFunctionsWithZonejs = false; /** Relax the restriction on disallowing --language_out=ES_2017 together with Zone.js */ public CommandLineConfig setEmitAsyncFunctionsWithZonejs(boolean emitAsyncFunctionsWithZonejs) { this.emitAsyncFunctionsWithZonejs = emitAsyncFunctionsWithZonejs; return this; } private TweakProcessing tweakProcessing = TweakProcessing.OFF; /** Sets the kind of processing to do for goog.tweak functions. */ public CommandLineConfig setTweakProcessing(TweakProcessing tweakProcessing) { this.tweakProcessing = tweakProcessing; return this; } private String charset = ""; /** Input charset for all files. */ public CommandLineConfig setCharset(String charset) { this.charset = charset; return this; } private DependencyOptions dependencyOptions = null; /** Sets the dependency management options. */ public CommandLineConfig setDependencyOptions(@Nullable DependencyOptions dependencyOptions) { this.dependencyOptions = dependencyOptions; return this; } private List outputManifests = ImmutableList.of(); /** Sets whether to print output manifest files. Filter out empty file names. */ public CommandLineConfig setOutputManifest(List outputManifests) { this.outputManifests = new ArrayList<>(); for (String manifestName : outputManifests) { if (!manifestName.isEmpty()) { this.outputManifests.add(manifestName); } } this.outputManifests = ImmutableList.copyOf(this.outputManifests); return this; } private String outputModuleDependencies = null; /** Sets whether a JSON file representing the dependencies between modules should be created. */ public CommandLineConfig setOutputModuleDependencies(String outputModuleDependencies) { this.outputModuleDependencies = outputModuleDependencies; return this; } private List outputBundles = ImmutableList.of(); /** Sets whether to print output bundle files. */ public CommandLineConfig setOutputBundle(List outputBundles) { this.outputBundles = outputBundles; return this; } private boolean skipNormalOutputs = false; /** Sets whether the normal outputs of compilation should be skipped. */ public CommandLineConfig setSkipNormalOutputs(boolean skipNormalOutputs) { this.skipNormalOutputs = skipNormalOutputs; return this; } private List manifestMaps = ImmutableList.of(); /** Sets the execPath:rootRelativePath mappings */ public CommandLineConfig setManifestMaps(List manifestMaps) { this.manifestMaps = manifestMaps; return this; } private boolean transformAMDToCJSModules = false; /** Set whether to transform AMD to Commonchunks. */ public CommandLineConfig setTransformAMDToCJSModules(boolean transformAMDToCJSModules) { this.transformAMDToCJSModules = transformAMDToCJSModules; return this; } private boolean processCommonJSModules = false; /** Sets whether to process Commonchunks. */ public CommandLineConfig setProcessCommonJSModules(boolean processCommonJSModules) { this.processCommonJSModules = processCommonJSModules; return this; } private List moduleRoots = ImmutableList.of(ModuleLoader.DEFAULT_FILENAME_PREFIX); /** Sets the module roots. */ public CommandLineConfig setModuleRoots(List jsChunkRoots) { this.moduleRoots = jsChunkRoots; return this; } private String warningsAllowFile = ""; /** Sets a allowlist file that suppresses warnings. */ public CommandLineConfig setWarningsAllowlistFile(String fileName) { this.warningsAllowFile = fileName; return this; } private List hideWarningsFor = ImmutableList.of(); /** Sets the paths for which warnings will be hidden. */ public CommandLineConfig setHideWarningsFor(List hideWarningsFor) { this.hideWarningsFor = hideWarningsFor; return this; } private boolean angularPass = false; /** Sets whether to process AngularJS-specific annotations. */ public CommandLineConfig setAngularPass(boolean angularPass) { this.angularPass = angularPass; return this; } private JsonStreamMode jsonStreamMode = JsonStreamMode.NONE; public CommandLineConfig setJsonStreamMode(JsonStreamMode mode) { this.jsonStreamMode = mode; return this; } /** Set of options that can be used with the --formatting flag. */ protected enum ErrorFormatOption { STANDARD, JSON } private ErrorFormatOption errorFormat = ErrorFormatOption.STANDARD; public CommandLineConfig setErrorFormat(ErrorFormatOption errorFormat) { this.errorFormat = errorFormat; return this; } private String jsonWarningsFile = ""; public CommandLineConfig setJsonWarningsFile(String jsonWarningsFile) { this.jsonWarningsFile = jsonWarningsFile; return this; } } /** Representation of a source file from an encoded json stream input */ @GwtIncompatible("Unnecessary") public static class JsonFileSpec { @Nullable private final String src; @Nullable private final String path; @Nullable @SerializedName( value = "source_map", alternate = {"sourceMap"}) private String sourceMap; @Nullable @SerializedName( value = "webpack_id", alternate = {"webpackId"}) private final String webpackId; // Graal requires a non-arg constructor for use with GSON // See https://github.com/oracle/graal/issues/680 private JsonFileSpec() { this(null, null, null, null); } public JsonFileSpec(String src, String path) { this(src, path, null, null); } public JsonFileSpec(String src, String path, String sourceMap) { this(src, path, sourceMap, null); } public JsonFileSpec(String src, String path, String sourceMap, @Nullable String webpackId) { this.src = src; this.path = path; this.sourceMap = sourceMap; this.webpackId = webpackId; } public String getSrc() { return this.src; } public String getPath() { return this.path; } public String getSourceMap() { return this.sourceMap; } public String getWebpackId() { return this.webpackId; } public void setSourceMap(String map) { this.sourceMap = map; } } /** Flag types for JavaScript source files. */ @GwtIncompatible("Unnecessary") protected enum JsSourceType { EXTERN("extern"), JS("js"), JS_ZIP("jszip"), WEAKDEP("weakdep"), IJS("ijs"); @VisibleForTesting final String flagName; private JsSourceType(String flagName) { this.flagName = flagName; } } /** A pair from flag to its value. */ @GwtIncompatible("Unnecessary") protected static class FlagEntry { private final T flag; private final String value; protected FlagEntry(T flag, String value) { this.flag = flag; this.value = value; } @Override public boolean equals(Object o) { if (o instanceof FlagEntry) { FlagEntry that = (FlagEntry) o; return that.flag.equals(this.flag) && that.value.equals(this.value); } return false; } @Override public int hashCode() { return flag.hashCode() + value.hashCode(); } @Override public String toString() { return this.flag + "=" + this.value; } public T getFlag() { return flag; } public String getValue() { return value; } } /** Represents a specification for a serving chunk. */ public static class JsChunkSpec { private final String name; // Number of input files, including js and zip files. private final int numInputs; private final ImmutableList deps; // Number of input js files. All zip files should be expanded. private int numJsFiles; private JsChunkSpec(String name, int numInputs, ImmutableList deps) { this.name = name; this.numInputs = numInputs; this.deps = deps; this.numJsFiles = numInputs; } /** * @param specString The spec format is: name:num-js-files[:[dep,...][:]]. Module * names must not contain the ':' character. * @param isFirstChunk Whether the spec is for the first module. * @return A parsed chunk spec. */ public static JsChunkSpec create(String specString, boolean isFirstChunk) { // Format is ":[:[,...][:]]". String[] parts = specString.split(":"); if (parts.length < 2 || parts.length > 4) { throw new FlagUsageException( "Expected 2-4 colon-delimited parts in " + "chunk spec: " + specString); } // Parse module name. String name = parts[0]; // Parse module dependencies. String[] deps = parts.length > 2 && parts[2].length() > 0 ? parts[2].split(",") : new String[0]; // Parse module inputs. int numInputs = -1; try { numInputs = Integer.parseInt(parts[1]); } catch (NumberFormatException ignored) { numInputs = -1; } // We will allow modules of zero input. if (numInputs < 0) { // A size of 'auto' is only allowed on the base module if // and it must also be the first module if (parts.length == 2 && "auto".equals(parts[1])) { if (!isFirstChunk) { throw new FlagUsageException( "Invalid JS file count '" + parts[1] + "' for module: " + name + ". Only the first module may specify " + "a size of 'auto' and it must have no dependencies."); } } else { throw new FlagUsageException( "Invalid JS file count '" + parts[1] + "' for module: " + name); } } return new JsChunkSpec(name, numInputs, ImmutableList.copyOf(deps)); } public String getName() { return name; } public int getNumInputs() { return numInputs; } public ImmutableList getDeps() { return deps; } public int getNumJsFiles() { return numJsFiles; } } @GwtIncompatible("Unnecessary") static final class SystemExitCodeReceiver implements Function { static final SystemExitCodeReceiver INSTANCE = new SystemExitCodeReceiver(); private SystemExitCodeReceiver() { // singleton } @Override public Void apply(Integer exitCode) { int exitCodeValue = checkNotNull(exitCode); // Don't spuriously report success. // Posix conventions only guarantee that 8b are significant. byte exitCodeByte = (byte) exitCodeValue; if (exitCodeByte == 0 && exitCodeValue != 0) { exitCodeByte = (byte) -1; } System.exit(exitCodeByte); return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy