From dc877a87f4619e4b06c658460cc8391982acfeae Mon Sep 17 00:00:00 2001 From: thorogood Date: Mon, 15 Aug 2016 16:09:40 -0700 Subject: [PATCH] Enable JsInterop for GWT version with a workaround for polluting the external scope. Cleanup Flags usage to be more typesafe. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=130336797 --- .../jscomp/gwt/client/GwtRunner.java | 173 ++++++++++-------- .../jscomp/gwt/linker/MinimalLinker.java | 16 +- 2 files changed, 106 insertions(+), 83 deletions(-) diff --git a/src/com/google/javascript/jscomp/gwt/client/GwtRunner.java b/src/com/google/javascript/jscomp/gwt/client/GwtRunner.java index 06bd47424b3..f36c9cc209f 100644 --- a/src/com/google/javascript/jscomp/gwt/client/GwtRunner.java +++ b/src/com/google/javascript/jscomp/gwt/client/GwtRunner.java @@ -50,70 +50,99 @@ public final class GwtRunner implements EntryPoint { private GwtRunner() {} - @JsType(namespace = JsPackage.GLOBAL, name = "Object", isNative = true) - private static class Flags { - boolean angularPass; - boolean assumeFunctionWrapper; - String compilationLevel; - boolean dartPass; - boolean exportLocalPropertyDefinitions; - boolean generateExports; - String languageIn; - String languageOut; - boolean checksOnly; - boolean newTypeInf; - boolean polymerPass; - boolean preserveTypeAnnotations; - boolean processCommonJsModules; + /** + * Specifies flags and their defaults. + * + * You must specify defaults in the constructor (as of August 2016). Defaults specified + * alongside fields will cause falsey values to be optimized and inlined. Fix here- + * https://gwt-review.googlesource.com/#/c/16600/ + */ + @JsType(namespace = JsPackage.GLOBAL, name = "Flags") + public static class Flags { + public boolean angularPass; + public boolean assumeFunctionWrapper; + public String compilationLevel; + public boolean dartPass; + public boolean exportLocalPropertyDefinitions; + public boolean generateExports; + public String languageIn; + public String languageOut; + public boolean checksOnly; + public boolean newTypeInf; + public boolean polymerPass; + public boolean preserveTypeAnnotations; + public boolean processCommonJsModules; public String renamePrefixNamespace; - boolean rewritePolyfills; - String warningLevel; - boolean useTypesForOptimization; - - // These flags do not match the Java compiler JAR. - File[] jsCode; - File[] externs; - boolean createSourceMap; + public boolean rewritePolyfills; + public String warningLevel; + public boolean useTypesForOptimization; + + // These flags do not match the Java jar release. + public File[] jsCode; + public File[] externs; + public boolean createSourceMap; + + /** + * Flags constructor. Defaults must be specified here for every field. + */ + public Flags() { + this.angularPass = false; + this.assumeFunctionWrapper = false; + this.compilationLevel = "SIMPLE"; + this.dartPass = false; + this.exportLocalPropertyDefinitions = false; + this.generateExports = false; + this.languageIn = "ES6"; + this.languageOut = "ES5"; + this.checksOnly = false; + this.newTypeInf = false; + this.polymerPass = false; + this.preserveTypeAnnotations = false; + this.processCommonJsModules = false; + this.renamePrefixNamespace = null; + this.rewritePolyfills = true; + this.warningLevel = "DEFAULT"; + this.useTypesForOptimization = true; + this.jsCode = null; + this.externs = null; + this.createSourceMap = false; + } + + /** + * Updates this {@link Flags} with a raw {@link JavaScriptObject}. + * + * @param raw The raw flags passed to this program. + * @return A list of invalid/unhandled flags. + */ + public native String[] update(JavaScriptObject raw) /*-{ + var unhandled = []; + for (var k in raw) { + if (k in this) { + this[k] = raw[k]; + } else { + unhandled.push(k); + } + } + return unhandled; + }-*/; } /** - * 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. + * File object matching {@code AbstractCommandLineRunner.JsonFileSpec}. This is marked as the + * native {@code Object} type as it's not instantiated anywhere. */ - private static final Flags defaultFlags = new Flags(); - static { - defaultFlags.angularPass = false; - defaultFlags.assumeFunctionWrapper = false; - defaultFlags.compilationLevel = "SIMPLE"; - defaultFlags.dartPass = false; - defaultFlags.exportLocalPropertyDefinitions = false; - defaultFlags.generateExports = false; - defaultFlags.languageIn = "ECMASCRIPT6"; - defaultFlags.languageOut = "ECMASCRIPT5"; - defaultFlags.checksOnly = false; - defaultFlags.newTypeInf = false; - defaultFlags.polymerPass = false; - defaultFlags.preserveTypeAnnotations = false; - defaultFlags.processCommonJsModules = false; - defaultFlags.renamePrefixNamespace = null; - defaultFlags.rewritePolyfills = true; - defaultFlags.warningLevel = "DEFAULT"; - defaultFlags.useTypesForOptimization = true; - defaultFlags.jsCode = null; - defaultFlags.externs = null; - defaultFlags.createSourceMap = false; - } - @JsType(namespace = JsPackage.GLOBAL, name = "Object", isNative = true) - private static class File { + public static class File { @JsProperty String path; @JsProperty String src; @JsProperty String sourceMap; } - @JsType(namespace = JsPackage.GLOBAL, name = "Object", isNative = true) - private static class ModuleOutput { + /** + * Output type returned to caller. + */ + @JsType(namespace = JsPackage.GLOBAL, name = "ModuleOutput") + public static class ModuleOutput { @JsProperty String compiledCode; @JsProperty String sourceMap; @JsProperty JavaScriptObject[] errors; @@ -170,13 +199,16 @@ private static void applyOptionsFromFlags(CompilerOptions options, Flags flags) warningLevel.setOptionsForWarningLevel(options); LanguageMode languageIn = LanguageMode.fromString(flags.languageIn); - if (languageIn != null) { - options.setLanguageIn(languageIn); + if (languageIn == null) { + throw new RuntimeException("Bad value for languageIn: " + flags.languageIn); } + options.setLanguageIn(languageIn); + LanguageMode languageOut = LanguageMode.fromString(flags.languageOut); - if (languageOut != null) { - options.setLanguageOut(languageOut); + if (languageOut == null) { + throw new RuntimeException("Bad value for languageOut: " + flags.languageOut); } + options.setLanguageOut(languageOut); if (flags.createSourceMap) { options.setSourceMapOutputPath("%output%"); @@ -235,30 +267,15 @@ private static ImmutableMap buildSourceMaps( 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 native String[] updateFlags(Flags dst, Flags src) /*-{ - for (var k in src) { - if (!(k in dst)) { - dst[k] = src[k]; - } - } - var unhandled = []; - for (var k in dst) { - if (!(k in src)) { - unhandled.push(k); - } - } - return unhandled; - }-*/; - /** * Public compiler call. Exposed in {@link #exportCompile}. + * + * @param raw The passed raw flags from the user. + * @return The output from the compile. */ - public static ModuleOutput compile(Flags flags) { - String[] unhandled = updateFlags(flags, defaultFlags); + public static ModuleOutput compile(JavaScriptObject raw) { + Flags flags = new Flags(); + String[] unhandled = flags.update(raw); if (unhandled.length > 0) { throw new RuntimeException("Unhandled flag: " + unhandled[0]); } diff --git a/src/com/google/javascript/jscomp/gwt/linker/MinimalLinker.java b/src/com/google/javascript/jscomp/gwt/linker/MinimalLinker.java index 43030c0f1a1..e18fee4fb6e 100644 --- a/src/com/google/javascript/jscomp/gwt/linker/MinimalLinker.java +++ b/src/com/google/javascript/jscomp/gwt/linker/MinimalLinker.java @@ -29,9 +29,11 @@ import com.google.gwt.dev.util.Util; /** - * Simple single-script linker that doesn't add any dependence on the browser. - * This is intended to generate server-side runnable JS. It doesn't support - * permutations, nor does it allow late-loading code. + * Simple single-script linker that doesn't add any dependencies on the browser. + * + * This is intended to generate JS for servers, Node, or inside browsers as a module. It doesn't + * support permutations, nor does it allow late-loading code. It supports JSInterop, but it does not + * automatically add anything to the external scope (see {@code $wnd} below). */ @LinkerOrder(Order.PRIMARY) public class MinimalLinker extends AbstractLinker { @@ -41,10 +43,14 @@ public class MinimalLinker extends AbstractLinker { * to invoke the {@code onModuleLoad} methods of loaded modules. The gwtOnLoad method then sets * {@code $moduleName} and {@code $moduleBase}, shadow them here so they don't leak into the * global scope. + * + * We also fake {@code $wnd} with an empty object (containg Error, to work around GWT using it + * statically in {@code StackTraceCreator}). JSInterop will export its objects here, which are + * then discarded at runtime. This is needed to avoid polluting our environments. */ private static final String PREFIX = - "(function(){var $wnd=this; var $doc={}; var $moduleName, $moduleBase;"; - private static final String SUFFIX = "typeof gwtOnLoad==='function'&&gwtOnLoad()})();"; + "(function(){var $wnd={Error:{}}; var $doc={}; var $moduleName, $moduleBase;"; + private static final String SUFFIX = "$wnd=this;typeof gwtOnLoad==='function'&&gwtOnLoad()})();"; @Override public String getDescription() {