Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.javascript.jscomp.gwt.client.JsRunnerMain Maven / Gradle / Ivy
/*
* Copyright 2016 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.gwt.client;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.javascript.jscomp.AbstractCommandLineRunner.createDefineOrTweakReplacements;
import static com.google.javascript.jscomp.AbstractCommandLineRunner.createJsModules;
import static com.google.javascript.jscomp.AbstractCommandLineRunner.parseModuleWrappers;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.javascript.jscomp.AbstractCommandLineRunner.JsModuleSpec;
import com.google.javascript.jscomp.BasicErrorManager;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.ClosureCodingConvention;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.CompilerOptions.IsolationMode;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.CompilerOptions.TracerMode;
import com.google.javascript.jscomp.DefaultExterns;
import com.google.javascript.jscomp.DependencyOptions;
import com.google.javascript.jscomp.DependencyOptions.DependencyMode;
import com.google.javascript.jscomp.DiagnosticGroups;
import com.google.javascript.jscomp.DiagnosticType;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.JSModule;
import com.google.javascript.jscomp.ModuleIdentifier;
import com.google.javascript.jscomp.PropertyRenamingPolicy;
import com.google.javascript.jscomp.ShowByPathWarningsGuard;
import com.google.javascript.jscomp.SourceFile;
import com.google.javascript.jscomp.SourceMap;
import com.google.javascript.jscomp.SourceMapInput;
import com.google.javascript.jscomp.VariableRenamingPolicy;
import com.google.javascript.jscomp.WarningLevel;
import com.google.javascript.jscomp.deps.ModuleLoader.ResolutionMode;
import com.google.javascript.jscomp.deps.SourceCodeEscapers;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.resources.ResourceLoader;
import elemental2.core.JsArray;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import jsinterop.annotations.JsMethod;
import jsinterop.annotations.JsOverlay;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsType;
import jsinterop.base.Any;
import jsinterop.base.Js;
import jsinterop.base.JsPropertyMap;
/** Runner for the GWT-compiled JSCompiler. */
public final class JsRunnerMain {
private static final Logger phaseLogger =
Logger.getLogger("com.google.javascript.jscomp.PhaseOptimizer");
private static final CompilationLevel DEFAULT_COMPILATION_LEVEL =
CompilationLevel.SIMPLE_OPTIMIZATIONS;
private static final String OUTPUT_MARKER = "%output%";
private static final String OUTPUT_MARKER_JS_STRING = "%output|jsstring%";
private static final String EXTERNS_PREFIX = "externs/";
@JsType(
isNative = true,
name = "com_google_javascript_jscomp_gwt_client_JsRunnerMain$Flags",
namespace = JsPackage.GLOBAL)
abstract static class Flags {
boolean angularPass;
boolean applyInputSourceMaps;
boolean assumeFunctionWrapper;
boolean checksOnly;
String[] chunk;
String[] chunkWrapper;
String chunkOutputPathPrefix;
String compilationLevel;
Object createSourceMap;
boolean dartPass;
boolean debug;
String[] define;
String dependencyMode;
String[] entryPoint;
String env;
boolean exportLocalPropertyDefinitions;
Object[] externs;
String[] extraAnnotationName;
String[] forceInjectLibraries;
String[] formatting;
boolean generateExports;
String[] hideWarningsFor;
boolean injectLibraries;
String isolationMode;
String[] js;
String[] jscompError;
String[] jscompOff;
String[] jscompWarning;
String[] jsModuleRoot;
String jsOutputFile;
String languageIn;
String languageOut;
String moduleResolution;
@Deprecated boolean newTypeInf;
String outputWrapper;
String packageJsonEntryNames;
boolean parseInlineSourceMaps;
@Deprecated boolean polymerPass;
Double polymerVersion; // nb. nullable JS number represented by java.lang.Double in GWT.
boolean preserveTypeAnnotations;
boolean processClosurePrimitives;
boolean processCommonJsModules;
boolean renaming;
String renamePrefixNamespace;
String renameVariablePrefix;
boolean rewritePolyfills;
boolean sourceMapIncludeContent;
boolean strictModeInput;
String tracerMode;
boolean useTypesForOptimization;
String warningLevel;
// These flags do not match the Java compiler JAR.
@Deprecated File[] jsCode;
JsPropertyMap defines;
@JsOverlay
static Flags create() {
return Js.uncheckedCast(JsPropertyMap.of());
}
}
/**
* defaultFlags must have a value set for each field. Otherwise, GWT has no way to create the
* fields inside Flags (as it's native). If Flags is not-native, GWT eats its field names anyway.
*/
private static Flags defaultFlags;
/**
* Lazy initialize due to GWT. If things are exported then Object is not available when the static
* initialization runs.
*/
private static Flags getDefaultFlags() {
if (defaultFlags != null) {
return defaultFlags;
}
defaultFlags = Flags.create();
defaultFlags.angularPass = false;
defaultFlags.applyInputSourceMaps = true;
defaultFlags.assumeFunctionWrapper = false;
defaultFlags.checksOnly = false;
defaultFlags.chunk = null;
defaultFlags.chunkWrapper = null;
defaultFlags.chunkOutputPathPrefix = "./";
defaultFlags.compilationLevel = "SIMPLE";
defaultFlags.createSourceMap = true;
defaultFlags.dartPass = false;
defaultFlags.debug = false;
defaultFlags.define = null;
defaultFlags.defines = null;
defaultFlags.dependencyMode = null;
defaultFlags.entryPoint = null;
defaultFlags.env = "BROWSER";
defaultFlags.exportLocalPropertyDefinitions = false;
defaultFlags.extraAnnotationName = null;
defaultFlags.externs = null;
defaultFlags.forceInjectLibraries = null;
defaultFlags.formatting = null;
defaultFlags.generateExports = false;
defaultFlags.hideWarningsFor = null;
defaultFlags.injectLibraries = true;
defaultFlags.js = null;
defaultFlags.jsCode = null;
defaultFlags.jscompError = null;
defaultFlags.jscompOff = null;
defaultFlags.jscompWarning = null;
defaultFlags.jsModuleRoot = null;
defaultFlags.jsOutputFile = "compiled.js";
defaultFlags.languageIn = "ECMASCRIPT_2017";
defaultFlags.languageOut = "ECMASCRIPT5";
defaultFlags.moduleResolution = "BROWSER";
defaultFlags.newTypeInf = false;
defaultFlags.isolationMode = "NONE";
defaultFlags.outputWrapper = null;
defaultFlags.packageJsonEntryNames = null;
defaultFlags.parseInlineSourceMaps = true;
defaultFlags.polymerPass = false;
defaultFlags.polymerVersion = null;
defaultFlags.preserveTypeAnnotations = false;
defaultFlags.processClosurePrimitives = true;
defaultFlags.processCommonJsModules = false;
defaultFlags.renamePrefixNamespace = null;
defaultFlags.renameVariablePrefix = null;
defaultFlags.renaming = true;
defaultFlags.rewritePolyfills = true;
defaultFlags.sourceMapIncludeContent = false;
defaultFlags.strictModeInput = true;
defaultFlags.tracerMode = "OFF";
defaultFlags.warningLevel = "DEFAULT";
defaultFlags.useTypesForOptimization = true;
return defaultFlags;
}
/** Properties here should match the AbstractCommandLineRunner.JsonFileSpec */
@JsType(
isNative = true,
name = "com_google_javascript_jscomp_gwt_client_JsRunnerMain$File",
namespace = JsPackage.GLOBAL)
abstract static class File {
String path;
String src;
String sourceMap;
String webpackId;
@JsOverlay
static File create() {
return Js.uncheckedCast(JsPropertyMap.of());
}
}
@JsType(
isNative = true,
name = "com_google_javascript_jscomp_gwt_client_JsRunnerMain$ChunkOutput",
namespace = JsPackage.GLOBAL)
abstract static class ChunkOutput {
@Deprecated String compiledCode;
@Deprecated String sourceMap;
JsArray compiledFiles;
JsArray> errors;
JsArray> warnings;
@JsOverlay
static ChunkOutput create() {
return Js.uncheckedCast(JsPropertyMap.of());
}
}
/** Reliably returns a string array from the flags/key combo. */
@JsMethod
private static String[] toStringArray(Object value) {
if (value == null) {
return new String[0];
} else if (value instanceof Any[]) {
return Js.uncheckedCast(value);
}
return new String[] {Js.uncheckedCast(value)};
}
/**
* Validates that the values of this {@code JsMap} are primitives: either number, string or
* boolean. Note that {@code typeof null} is object.
*/
private static void validatePrimitiveTypes(JsPropertyMap jsmap) {
jsmap.forEach(
(key) -> {
String type = Js.typeof(jsmap.get(key));
switch (type) {
case "number":
case "boolean":
case "string":
return;
default:
throw new IllegalArgumentException(
"Type of define `" + key + "` unsupported: " + type);
}
});
}
/**
* @param jsFilePaths Array of file paths. If running under NodeJS, they will be loaded via the
* native node fs module.
* @return Array of File objects. If called without running under node, return null to indicate
* failure.
*/
@JsMethod
private static native File[] filesFromPaths(String[] jsFilePaths) /*-{
if (!(typeof process === 'object' && process.version)) {
return null;
}
var jsFiles = [];
for (var i = 0; i < jsFilePaths.length; i++) {
if (typeof process === 'object' && process.version) {
jsFiles.push({
path: jsFilePaths[i],
src: require('fs').readFileSync(jsFilePaths[i], 'utf8')
});
}
}
return jsFiles;
}-*/;
private static JsPropertyMap createError(
String file, String description, String type, int lineNo, int charNo) {
JsPropertyMap result = JsPropertyMap.of();
result.set("file", file);
result.set("description", description);
result.set("type", type);
result.set("lineNo", lineNo);
result.set("charNo", charNo);
return result;
}
/** Convert a list of {@link JSError} instances to a JS array containing plain objects. */
private static JsArray> toNativeErrorArray(List errors) {
JsArray> out = new JsArray<>();
for (JSError error : errors) {
DiagnosticType type = error.getType();
out.push(
createError(
error.getSourceName(),
error.getDescription(),
type != null ? type.key : null,
error.getLineNumber(),
error.getCharno()));
}
return out;
}
/** Generates the output code, taking into account the passed {@code flags}. */
private static ChunkOutput writeChunkOutput(
Compiler compiler, Flags flags, List chunks) {
JsArray outputFiles = new JsArray<>();
ChunkOutput output = ChunkOutput.create();
Map parsedModuleWrappers =
parseModuleWrappers(Arrays.asList(toStringArray(flags.chunkWrapper)), chunks);
for (JSModule c : chunks) {
if (flags.createSourceMap != null && !flags.createSourceMap.equals(false)) {
compiler.resetAndIntitializeSourceMap();
}
File file = File.create();
file.path = flags.chunkOutputPathPrefix + c.getName() + ".js";
String code = compiler.toSource(c);
int lastSeparatorIndex = file.path.lastIndexOf('/');
if (lastSeparatorIndex < 0) {
lastSeparatorIndex = file.path.lastIndexOf('\\');
}
String baseName = file.path.substring(Math.max(0, lastSeparatorIndex));
String wrapper = parsedModuleWrappers.get(c.getName()).replace("%basename%", baseName);
StringBuilder out = new StringBuilder();
int pos = wrapper.indexOf("%s");
if (pos != -1) {
String prefix = "";
if (pos > 0) {
prefix = wrapper.substring(0, pos);
out.append(prefix);
}
out.append(code);
int suffixStart = pos + "%s".length();
if (suffixStart != wrapper.length()) {
// Something after placeholder?
out.append(wrapper, suffixStart, wrapper.length());
}
// 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');
}
file.src = out.toString();
if (flags.createSourceMap != null && !flags.createSourceMap.equals(false)) {
StringBuilder b = new StringBuilder();
try {
compiler.getSourceMap().appendTo(b, file.path);
} catch (IOException e) {
// ignore
}
file.sourceMap = b.toString();
}
outputFiles.push(file);
}
output.compiledFiles = outputFiles;
return output;
}
/** Generates the output code, taking into account the passed {@code flags}. */
private static ChunkOutput writeOutput(Compiler compiler, Flags flags) {
JsArray outputFiles = new JsArray<>();
ChunkOutput output = ChunkOutput.create();
File file = File.create();
file.path = flags.jsOutputFile;
String code = compiler.toSource();
String prefix = "";
String postfix = "";
if (flags.outputWrapper != null) {
String marker = null;
int pos = flags.outputWrapper.indexOf(OUTPUT_MARKER_JS_STRING);
if (pos != -1) {
// With jsstring, run SourceCodeEscapers (as per AbstractCommandLineRunner).
code = SourceCodeEscapers.javascriptEscaper().escape(code);
marker = OUTPUT_MARKER_JS_STRING;
} else {
pos = flags.outputWrapper.indexOf(OUTPUT_MARKER);
if (pos != -1) {
marker = OUTPUT_MARKER;
}
}
if (marker != null) {
prefix = flags.outputWrapper.substring(0, pos);
SourceMap sourceMap = compiler.getSourceMap();
if (sourceMap != null) {
sourceMap.setWrapperPrefix(prefix);
}
}
postfix = flags.outputWrapper.substring(pos + marker.length());
}
if (flags.createSourceMap != null && !flags.createSourceMap.equals(false)) {
StringBuilder b = new StringBuilder();
try {
compiler.getSourceMap().appendTo(b, flags.jsOutputFile);
} catch (IOException e) {
// ignore
}
file.sourceMap = b.toString();
}
file.src = prefix + code + postfix;
outputFiles.push(file);
output.compiledFiles = outputFiles;
output.compiledCode = file.src;
output.sourceMap = file.sourceMap;
return output;
}
private static List createExterns(CompilerOptions.Environment environment) {
String[] resources = ResourceLoader.resourceList(JsRunnerMain.class);
Map all = new HashMap<>();
for (String res : resources) {
if (res.startsWith(EXTERNS_PREFIX)) {
String filename = res.substring(EXTERNS_PREFIX.length());
all.put(
filename,
SourceFile.fromCode(
"externs.zip//" + res, ResourceLoader.loadTextResource(JsRunnerMain.class, res)));
}
}
return DefaultExterns.prepareExterns(environment, all);
}
private static ImmutableList createEntryPoints(String[] entryPoints) {
ImmutableList.Builder builder = new ImmutableList.Builder<>();
for (String entryPoint : entryPoints) {
if (entryPoint.startsWith("goog:")) {
builder.add(ModuleIdentifier.forClosure(entryPoint));
} else {
builder.add(ModuleIdentifier.forFile(entryPoint));
}
}
return builder.build();
}
private static void applyWarnings(
String[] warningGuards,
CompilerOptions options,
DiagnosticGroups diagnosticGroups,
CheckLevel checkLevel) {
for (String warningGuardName : warningGuards) {
if ("*".equals(warningGuardName)) {
for (String groupName : diagnosticGroups.getRegisteredGroups().keySet()) {
if (!DiagnosticGroups.wildcardExcludedGroups.contains(groupName)) {
diagnosticGroups.setWarningLevel(options, groupName, checkLevel);
}
}
} else {
diagnosticGroups.setWarningLevel(options, warningGuardName, checkLevel);
}
}
}
private static void applyOptionsFromFlags(
CompilerOptions options, Flags flags, DiagnosticGroups diagnosticGroups) {
// order matches createOptions in CommandLineRunner.java
LanguageMode languageIn = LanguageMode.fromString(flags.languageIn);
if (languageIn != null) {
options.setLanguageIn(languageIn);
} else {
throw new RuntimeException("Bad value for languageIn: " + flags.languageIn);
}
LanguageMode languageOut = LanguageMode.fromString(flags.languageOut);
if (languageOut != null) {
options.setLanguageOut(languageOut);
} else {
throw new RuntimeException("Bad value for languageOut: " + flags.languageOut);
}
options.setCodingConvention(new ClosureCodingConvention());
if (flags.extraAnnotationName != null) {
options.setExtraAnnotationNames(Arrays.asList(flags.extraAnnotationName));
}
CompilationLevel level = DEFAULT_COMPILATION_LEVEL;
if (flags.compilationLevel != null) {
level = CompilationLevel.fromString(Ascii.toUpperCase(flags.compilationLevel));
if (level == null) {
throw new RuntimeException("Bad value for compilationLevel: " + flags.compilationLevel);
}
}
if (level == CompilationLevel.ADVANCED_OPTIMIZATIONS && !flags.renaming) {
throw new RuntimeException("renaming cannot be disabled when ADVANCED_OPTIMIZATIONS is used");
}
level.setOptionsForCompilationLevel(options);
if (flags.debug) {
level.setDebugOptionsForCompilationLevel(options);
}
CompilerOptions.Environment environment = CompilerOptions.Environment.BROWSER;
if (flags.env != null) {
environment = CompilerOptions.Environment.valueOf(Ascii.toUpperCase(flags.env));
}
options.setEnvironment(environment);
options.setChecksOnly(flags.checksOnly);
if (flags.checksOnly) {
options.setOutputJs(CompilerOptions.OutputJs.NONE);
}
if (flags.useTypesForOptimization) {
level.setTypeBasedOptimizationOptions(options);
}
if (flags.isolationMode != null
&& IsolationMode.valueOf(flags.isolationMode) == IsolationMode.IIFE) {
flags.outputWrapper = "(function(){%output%}).call(this);";
flags.assumeFunctionWrapper = true;
}
if (flags.assumeFunctionWrapper) {
level.setWrappedOutputOptimizations(options);
}
options.setGenerateExports(flags.generateExports);
options.setExportLocalPropertyDefinitions(flags.exportLocalPropertyDefinitions);
WarningLevel warningLevel = WarningLevel.DEFAULT;
if (flags.warningLevel != null) {
warningLevel = WarningLevel.valueOf(flags.warningLevel);
}
warningLevel.setOptionsForWarningLevel(options);
if (flags.formatting != null) {
List formattingOptions = Arrays.asList(toStringArray(flags.formatting));
for (String formattingOption : formattingOptions) {
switch (formattingOption) {
case "PRETTY_PRINT":
options.setPrettyPrint(true);
break;
case "PRINT_INPUT_DELIMITER":
options.printInputDelimiter = true;
break;
case "SINGLE_QUOTES":
options.setPreferSingleQuotes(true);
break;
default:
throw new RuntimeException("Unknown formatting option: " + formattingOption);
}
}
}
options.setClosurePass(flags.processClosurePrimitives);
options.setAngularPass(flags.angularPass);
if (flags.polymerPass) {
options.setPolymerVersion(1);
} else if (flags.polymerVersion != null) {
options.setPolymerVersion(flags.polymerVersion.intValue());
}
options.setDartPass(flags.dartPass);
options.setRenamePrefix(flags.renameVariablePrefix);
options.setRenamePrefixNamespace(flags.renamePrefixNamespace);
options.setPreventLibraryInjection(!flags.injectLibraries);
if (flags.forceInjectLibraries != null) {
options.setForceLibraryInjection(Arrays.asList(flags.forceInjectLibraries));
}
options.setPreserveTypeAnnotations(flags.preserveTypeAnnotations);
options.setRewritePolyfills(
flags.rewritePolyfills && options.getLanguageIn().toFeatureSet().contains(FeatureSet.ES6));
// We don't support conformance configs
options.clearConformanceConfigs();
if (flags.tracerMode != null) {
options.setTracerMode(TracerMode.valueOf(flags.tracerMode));
}
options.setStrictModeInput(flags.strictModeInput);
options.setSourceMapIncludeSourcesContent(flags.sourceMapIncludeContent);
if (flags.moduleResolution != null) {
options.setModuleResolutionMode(ResolutionMode.valueOf(flags.moduleResolution));
}
if (flags.packageJsonEntryNames != null) {
options.setPackageJsonEntryNames(Arrays.asList(flags.packageJsonEntryNames.split(",\\s*")));
}
if (!flags.renaming) {
options.setVariableRenaming(VariableRenamingPolicy.OFF);
options.setPropertyRenaming(PropertyRenamingPolicy.OFF);
}
// order matches setRunOptions in AbstractCommandLineRunner.java
applyWarnings(toStringArray(flags.jscompOff), options, diagnosticGroups, CheckLevel.OFF);
applyWarnings(
toStringArray(flags.jscompWarning), options, diagnosticGroups, CheckLevel.WARNING);
applyWarnings(toStringArray(flags.jscompError), options, diagnosticGroups, CheckLevel.ERROR);
if (flags.hideWarningsFor != null) {
options.addWarningsGuard(
new ShowByPathWarningsGuard(
toStringArray(flags.hideWarningsFor), ShowByPathWarningsGuard.ShowType.EXCLUDE));
}
if (flags.define != null) {
createDefineOrTweakReplacements(Arrays.asList(toStringArray(flags.define)), options, false);
}
if (flags.defines != null) {
// CompilerOptions also validates types, but uses Preconditions and therefore won't generate
// a useful exception.
validatePrimitiveTypes(flags.defines);
options.setDefineReplacements(toMap(flags.defines));
}
DependencyMode dependencyMode = null;
if (flags.dependencyMode != null) {
dependencyMode = DependencyMode.valueOf(Ascii.toUpperCase(flags.dependencyMode));
}
List entryPoints = Arrays.asList(toStringArray(flags.entryPoint));
DependencyOptions dependencyOptions =
DependencyOptions.fromFlags(
dependencyMode,
entryPoints,
/* closureEntryPointFlag= */ ImmutableList.of(),
/* commonJsEntryModuleFlag= */ null,
/* manageClosureDependenciesFlag= */ false,
/* onlyClosureDependenciesFlag= */ false);
if (dependencyOptions != null) {
options.setDependencyOptions(dependencyOptions);
}
options.setTrustedStrings(true);
if (flags.createSourceMap != null) {
if (flags.createSourceMap instanceof String) {
options.setSourceMapOutputPath((String) flags.createSourceMap);
} else if (!flags.createSourceMap.equals(false)) {
options.setSourceMapOutputPath("%output%.map");
}
}
options.setSourceMapIncludeSourcesContent(flags.sourceMapIncludeContent);
options.setParseInlineSourceMaps(flags.parseInlineSourceMaps);
options.setApplyInputSourceMaps(flags.applyInputSourceMaps);
options.setProcessCommonJSModules(flags.processCommonJsModules);
options.setModuleRoots(Arrays.asList(toStringArray(flags.jsModuleRoot)));
}
/**
* @param externs Array of strings or File[]. If running under NodeJS, an array of strings will be
* treated as file paths and loaded via the native node fs module.
* @return Array of extern File objects. If an array of strings is passed without running under
* node, return null to indicate failure.
*/
private static File[] filesFromFilesOrPaths(Object[] externs) {
if (externs.length == 0) {
return new File[0];
}
File first = Js.uncheckedCast(externs[0]);
if (first.path != null && first.src != null) {
return Js.uncheckedCast(Arrays.copyOf(externs, externs.length));
}
return filesFromPaths(Js.uncheckedCast(externs));
}
private static List fromFileArray(File[] src, String unknownPrefix) {
List out = new ArrayList<>();
if (src != null) {
for (int i = 0; i < src.length; ++i) {
File file = src[i];
String path = file.path;
if (path == null) {
path = unknownPrefix + i;
}
out.add(SourceFile.fromCode(path, nullToEmpty(file.src)));
}
}
return out;
}
private static ImmutableMap toMap(JsPropertyMap jsmap) {
ImmutableMap.Builder builder = ImmutableMap.builder();
jsmap.forEach(
(k) -> {
builder.put(k, jsmap.get(k));
});
return builder.build();
}
private static ImmutableMap buildSourceMaps(
File[] src, String unknownPrefix) {
ImmutableMap.Builder inputSourceMaps = new ImmutableMap.Builder<>();
if (src != null) {
for (int i = 0; i < src.length; ++i) {
File file = src[i];
if (isNullOrEmpty(file.sourceMap)) {
continue;
}
String path = file.path;
if (path == null) {
path = unknownPrefix + i;
}
SourceFile sf = SourceFile.fromCode(path + ".map", file.sourceMap);
inputSourceMaps.put(path, new SourceMapInput(sf));
}
}
return inputSourceMaps.build();
}
/**
* Updates the destination flags (user input) with source flags (the defaults). Returns a list of
* flags that are on the destination, but not on the source.
*/
private static JsArray updateFlags(Flags dst, Flags src) {
JsPropertyMap jssrc = Js.asPropertyMap(src);
JsPropertyMap jsdst = Js.asPropertyMap(dst);
jssrc.forEach(
(k) -> {
if (!jsdst.has(k)) {
jsdst.set(k, jssrc.get(k));
}
});
JsArray unhandled = new JsArray<>();
jsdst.forEach(
(k) -> {
if (!jssrc.has(k)) {
unhandled.push(k);
}
});
return unhandled;
}
/** Public compiler call. Exposed in {@link #exportCompile}. */
@JsMethod
public static ChunkOutput compile(Flags flags, File[] inputs) throws IOException {
// The PhaseOptimizer logs skipped pass warnings that interfere with capturing
// output and errors in the open source runners.
phaseLogger.setLevel(Level.OFF);
JsArray unhandled = updateFlags(flags, getDefaultFlags());
if (unhandled.getLength() > 0) {
throw new RuntimeException("Unhandled flag: " + unhandled.getAt(0));
}
List jsCode = new ArrayList<>();
ImmutableMap sourceMaps = null;
if (flags.jsCode != null) {
jsCode = fromFileArray(flags.jsCode, "Input_");
sourceMaps = buildSourceMaps(flags.jsCode, "Input_");
}
ImmutableMap.Builder inputPathByWebpackId = new ImmutableMap.Builder<>();
if (inputs != null) {
List sourceFiles = fromFileArray(inputs, "Input_");
ImmutableMap inputSourceMaps = buildSourceMaps(inputs, "Input_");
jsCode.addAll(sourceFiles);
if (sourceMaps == null) {
sourceMaps = inputSourceMaps;
} else {
HashMap tempMaps = new HashMap<>(sourceMaps);
tempMaps.putAll(inputSourceMaps);
sourceMaps = ImmutableMap.copyOf(tempMaps);
}
for (JsRunnerMain.File element : inputs) {
if (element.webpackId != null && element.path != null) {
inputPathByWebpackId.put(element.webpackId, element.path);
}
}
}
if (flags.js != null) {
File[] jsFiles = filesFromPaths(toStringArray(flags.js));
if (jsFiles == null) {
throw new RuntimeException(
"Can only load files from the filesystem when running in NodeJS.");
} else {
jsCode.addAll(fromFileArray(jsFiles, "Input_"));
}
}
Compiler compiler = new Compiler(new NodePrintStream());
CompilerOptions options = new CompilerOptions();
applyOptionsFromFlags(options, flags, compiler.getDiagnosticGroups());
options.setInputSourceMaps(sourceMaps);
List externs = new ArrayList<>();
if (flags.externs != null) {
File[] externFiles = filesFromFilesOrPaths(toStringArray(flags.externs));
if (externFiles == null) {
throw new RuntimeException(
"Can only load files from the filesystem when running in NodeJS.");
}
externs = fromFileArray(externFiles, "Extern_");
}
externs.addAll(createExterns(options.getEnvironment()));
NodeErrorManager errorManager = new NodeErrorManager();
compiler.initWebpackMap(inputPathByWebpackId.build());
compiler.setErrorManager(errorManager);
List chunkSpecs = new ArrayList<>();
if (flags.chunk != null) {
Collections.addAll(chunkSpecs, flags.chunk);
}
List jsChunkSpecs = new ArrayList<>();
for (int i = 0; i < chunkSpecs.size(); i++) {
jsChunkSpecs.add(JsModuleSpec.create(chunkSpecs.get(i), i == 0));
}
ChunkOutput output;
if (!jsChunkSpecs.isEmpty()) {
List chunks = createJsModules(jsChunkSpecs, jsCode);
compiler.compileModules(externs, chunks, options);
output = writeChunkOutput(compiler, flags, chunks);
} else {
compiler.compile(externs, jsCode, options);
output = writeOutput(compiler, flags);
}
output.errors = toNativeErrorArray(errorManager.errors);
output.warnings = toNativeErrorArray(errorManager.warnings);
return output;
}
/**
* Exports the {@link #compile} method via JSNI.
*
* This will be placed on {@code module.exports}, {@code self.compile} or {@code
* window.compile}.
*/
@SuppressWarnings({"unusable-by-js"})
public static native void exportCompile() /*-{
var fn = $entry(@com.google.javascript.jscomp.gwt.client.JsRunnerMain::compile(*));
if (typeof module !== 'undefined' && module.exports) {
module.exports = fn;
} else if (typeof self === 'object') {
self.compile = fn;
} else {
window.compile = fn;
}
}-*/;
/** Custom {@link BasicErrorManager} to record {@link JSError} instances. */
private static class NodeErrorManager extends BasicErrorManager {
final List errors = new ArrayList<>();
final List warnings = new ArrayList<>();
@Override
public void println(CheckLevel level, JSError error) {
if (level == CheckLevel.ERROR) {
errors.add(error);
} else if (level == CheckLevel.WARNING) {
warnings.add(error);
}
}
@Override
public void printSummary() {}
}
private JsRunnerMain() {}
}