Super Dev Mode: support standalone apps without precompile

Standalone, static apps hosted entirely on the Super Dev Mode
code server didn't work with -noprecompile because we didn't
copy the public resources to the stub CompileDir and because
binding properties weren't computed.

It now copies the files and generates binding properties.

Change-Id: Ib01b3783aef18e0041370067bd056f5c50339d61
Review-Link: https://gwt-review.googlesource.com/#/c/9137/
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
index 08171ca..0a84805 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
@@ -285,5 +285,4 @@
     File prefix = new File(getOutputDir().getExtraDir(), getOutputModuleName());
     return new File(prefix, path);
   }
-
 }
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
index 14f547e..7bd1d72 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.impl.PropertiesUtil;
 import com.google.gwt.core.linker.CrossSiteIframeLinker;
 import com.google.gwt.core.linker.IFrameLinker;
 import com.google.gwt.dev.Compiler;
@@ -46,6 +47,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
@@ -183,38 +185,84 @@
     CompilerOptions loadOptions = new CompilerOptionsImpl(compileDir, inputModuleName, options);
     compilerContext = compilerContextBuilder.options(loadOptions).unitCache(
         Compiler.getOrCreateUnitCache(logger, loadOptions)).build();
+
+    // Loads and parses all the Java files in the GWT application using the JDT.
+    // (This is warmup to make compiling faster later; we stop at this point to avoid
+    // needing to know the binding properties.)
     module.getCompilationState(compileLogger, compilerContext);
 
-    String newModuleName = module.getName();  // includes any rename.
-    outputModuleName.set(newModuleName);
+    setUpCompileDir(compileDir, module, compileLogger);
 
+    outputModuleName.set(module.getName());
     lastBuild.set(compileDir);
 
-    try {
-      // Prepare directory.
-      File outputDir = new File(
-          compileDir.getWarDir().getCanonicalPath() + "/" + getOutputModuleName());
-      if (!outputDir.exists()) {
-        if (!outputDir.mkdir()) {
-          compileLogger.log(TreeLogger.Type.WARN, "cannot create directory: " + outputDir);
-        }
-      }
 
-      // Creates a "module_name.nocache.js" that just forces a recompile.
-      String moduleScript = PageUtil.loadResource(Recompiler.class, "nomodule.nocache.js");
-      moduleScript = moduleScript.replace("__MODULE_NAME__", getOutputModuleName());
-      PageUtil.writeFile(outputDir.getCanonicalPath() + "/" + getOutputModuleName() + ".nocache.js",
-          moduleScript);
-
-    } catch (IOException e) {
-      compileLogger.log(TreeLogger.Type.ERROR, "Error creating uncompiled module.", e);
-    }
     long elapsedTime = System.currentTimeMillis() - startTime;
     compileLogger.log(TreeLogger.Type.INFO, "Module setup completed in " + elapsedTime + " ms");
 
     return new Result(compileDir, null);
   }
 
+  /**
+   * Prepares a stub compile directory.
+   * It will include all "public" resources and a nocache.js file that invokes the compiler.
+   */
+  private static void setUpCompileDir(CompileDir compileDir, ModuleDef module,
+      TreeLogger compileLogger) throws UnableToCompleteException {
+    try {
+      String outputModuleName = module.getName();
+
+      // Create the directory.
+      File outputDir = new File(
+          compileDir.getWarDir().getCanonicalPath() + "/" + outputModuleName);
+      if (!outputDir.exists()) {
+        if (!outputDir.mkdir()) {
+          compileLogger.log(Type.WARN, "cannot create directory: " + outputDir);
+        }
+      }
+
+      // Copy the public resources to the output.
+      ResourceOracleImpl publicResources = module.getPublicResourceOracle();
+      for (String pathName : publicResources.getPathNames()) {
+        File file = new File(outputDir, pathName);
+        File parent = file.getParentFile();
+        if (!parent.isDirectory() && !parent.mkdirs()) {
+          compileLogger.log(Type.ERROR, "cannot create directory: " + parent);
+          throw new UnableToCompleteException();
+        }
+        Files.copy(publicResources.getResourceAsStream(pathName), file.toPath());
+      }
+
+      // Create a "module_name.nocache.js" that calculates the permutation and forces a recompile.
+      String stubJs = generateStub(module, compileLogger);
+      PageUtil.writeFile(outputDir.getCanonicalPath() + "/" + outputModuleName + ".nocache.js",
+          stubJs);
+
+    } catch (IOException e) {
+      compileLogger.log(Type.ERROR, "Error creating stub compile directory.", e);
+      UnableToCompleteException wrapped = new UnableToCompleteException();
+      wrapped.initCause(e);
+      throw wrapped;
+    }
+  }
+
+  /**
+   * Generates the nocache.js file to use when precompile is not on.
+   */
+  private static String generateStub(ModuleDef module, TreeLogger compileLogger)
+      throws IOException, UnableToCompleteException {
+
+    String outputModuleName = module.getName();
+
+    String stub = PageUtil.loadResource(Recompiler.class, "nomodule.nocache.js");
+
+    return "(function() {\n"
+        + " var moduleName = '" + outputModuleName  + "';\n"
+        + PropertiesUtil.generatePropertiesSnippet(module, compileLogger)
+        + stub
+        + "})();\n";
+  }
+
   private boolean doCompile(TreeLogger compileLogger, CompileDir compileDir, Job job)
       throws UnableToCompleteException {
 
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/computeScriptBase.js b/dev/codeserver/java/com/google/gwt/dev/codeserver/computeScriptBase.js
index 7b30dea..04fd716 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/computeScriptBase.js
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/computeScriptBase.js
@@ -25,6 +25,8 @@
  * script tag with an absolute URL to head. (However, it's also okay for an html
  * file included in the GWT compiler's output to load the nocache.js file using
  * a relative URL.)
+ *
+ * As a side effect, sets superdevmode on in the __gwt_activeModules registry.
  */
 function computeScriptBase() {
   // TODO(skybrian) This approach won't work for workers.
@@ -48,5 +50,6 @@
     }
   }
 
-  $wnd.alert('Unable to load Super Dev Mode version of ' + __MODULE_NAME__ + ".");
+  $wnd.alert('Unable to load Super Dev Mode version of __MODULE_NAME__.');
+  return null;
 }
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/dev_mode_on.js b/dev/codeserver/java/com/google/gwt/dev/codeserver/dev_mode_on.js
index c8f7124..cf434a3 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/dev_mode_on.js
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/dev_mode_on.js
@@ -546,9 +546,16 @@
     if (module_name && !error) {
       // The user clicked a "Compile" bookmarklet and we believe it will
       // succeed.
-      var active_modules = window.__gwt_activeModules;
-      compile(module_name, params.server_url,
-          active_modules[module_name].bindings);
+
+      // Use the binding properties provided in params if available.
+      // (This happens if we're being called from a stub rather than a bookmarklet.)
+      var getPropMap = params.getPropMap;
+      if (!getPropMap) {
+        // Probably a regular compile, so check in the page.
+        var active_modules = window.__gwt_activeModules;
+        getPropMap = active_modules[module_name].bindings;
+      }
+      compile(module_name, params.server_url, getPropMap);
     } else {
       // The user clicked the "Dev Mode On" bookmarklet or something is wrong.
       showModuleDialog(params.server_url);
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/nomodule.nocache.js b/dev/codeserver/java/com/google/gwt/dev/codeserver/nomodule.nocache.js
index 0b5e8e0..419105c 100644
--- a/dev/codeserver/java/com/google/gwt/dev/codeserver/nomodule.nocache.js
+++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/nomodule.nocache.js
@@ -15,33 +15,142 @@
  */
 
 /**
- * @fileoverview Stub for non-compiled modules.
+ * @fileoverview A stub used in place of nocache.js files for modules that haven't been
+ * compiled yet.
  *
- * This script forces the proper reload + compilation in Super Dev Mode.
+ * When the stub is loaded, it finds the appropriate code server and requests that it should
+ * recompile one module and reload the page.
+ *
+ * <p>It assumes the following variables are predefined:
+ * <ul>
+ *   <li>moduleName is the name of the module to compile. (Output module name.)
+ *   <li>providers is a map from binding properties (strings) to no-argument functions that
+ *   compute the value of the binding variable for that property (string).
+ *   <li>values is a map from a binding property name (string) to the set of possible
+ *   values for that binding property (represented as a map with string keys).
+ * </ul>
+ *
+ * <p>This script should be wrapped in a function so that the variables defined here aren't global.
  */
 
-(function() {
-  var moduleName = '__MODULE_NAME__';  // Replaced by actual module name.
+// These variables are used by property providers.
+// (We also use them below.)
+var $wnd = window;
+var $doc = document;
 
-  // Active Super Dev Mode is assumed.
-  var key = '__gwtDevModeHook:' + moduleName;
-  if (!window.sessionStorage[key]) {
-    alert('Unable to load Super Dev Mode version of ' + moduleName + '.');
-    return;
+/**
+ * The URL used previously to load this module or null if Super Dev Mode is not on for this module.
+ */
+var previousUrl = $wnd.sessionStorage.getItem('__gwtDevModeHook:' + moduleName);
+
+/**
+ * The URL of the front page of the code server to use to compile the module.
+ */
+var codeServerUrl = (function () {
+
+  /**
+   * Converts a relative URL to absolute and returns the front page (ending with a slash).
+   */
+  function getFrontPage(url) {
+    var a = $doc.createElement('a');
+    a.href = url;
+    return a.protocol + '//' + a.host + '/';
   }
-  var scriptLocation = window.sessionStorage[key];
 
-  // Get the Super Dev Mode Server URL: use the HTML a.href parsing.
-  var a = document.createElement('a');
-  a.href = scriptLocation;
-  var devServerUrl = a.protocol + '//' + a.host;
+  /**
+   * Returns true if string 's' has the given suffix.
+   */
+  function hasSuffix(s, suffix) {
+    var startPos =  s.length - suffix.length;
+    return s.indexOf(suffix, startPos) == startPos;
+  }
 
-  // Load the bookmarklet.
-  window.__gwt_bookmarklet_params = {
-    'server_url' : devServerUrl + '/',
-    'module_name': moduleName
+  /**
+   * Returns true if the given URL ends with {moduleName}.nocache.js,
+   * ignoring any query string or hash.
+   */
+  function isModuleNoCacheJs(url) {
+    // Remove trailing query string and/or fragment
+    url = url.split("?")[0].split("#")[0];
+    return hasSuffix(url, '/' + moduleName + '.nocache.js');
+  }
+
+  // If Super Dev Mode is already running for this module, use the same code server again.
+  if (previousUrl) {
+    return getFrontPage(previousUrl);
+  }
+
+  // Since Super Dev Mode is not on, an HTML page somewhere is directly including
+  // this nocache.js file from the code server.
+  //
+  // Some people run codeserver behind a proxy, for example to enable HTTPS support.
+  // So, we should figure out this script's URL from the client's point of view,
+  // similar to how computeScriptBase() does it but simpler. We do this by searching
+  // all script tags for the URL.
+  var scriptTagsToSearch = $doc.getElementsByTagName('script');
+  for (var i = 0; ; i++) {
+    var tag = scriptTagsToSearch[i];
+    if (!tag) {
+      break; // end of list; not found
+    }
+    if (tag && isModuleNoCacheJs(tag.src)) {
+      return getFrontPage(tag.src);
+    }
+  }
+
+  throw "unable to find the script tag that includes " + moduleName + ".nocache.js";
+}());
+
+var activeModules = $wnd.__gwt_activeModules = $wnd.__gwt_activeModules || {};
+if (!activeModules[moduleName]) {
+  // dev_mode_on.js checks for this.
+  activeModules[moduleName] = {canRedirect: true};
+}
+
+/**
+ * A function returning a map from binding property names to values.
+ */
+var getPropMap = (function() {
+
+  var module = activeModules[moduleName];
+  if (module.bindings) {
+    // The module is already running, so use the same bindings.
+    // (In this case, a dev mode hook probably redirected to this script.)
+    return module.bindings;
+  }
+
+  /**
+   * Returns the value of the given binding property name.
+   */
+  function computePropValue(propName) {
+    var val = providers[propName]();
+    // sanity check
+    var allowedValuesMap = values[propName];
+    if (val in allowedValuesMap) {
+      return val;
+    } else {
+      console.log("provider for " + propName + " returned unexpected value: '" + val + "'");
+      throw "can't compute binding property value for " + propName;
+    }
+  }
+
+  return function () {
+    var result = {};
+    for (var key in values) {
+      if (values.hasOwnProperty(key)) {
+        result[key] = computePropValue(key);
+      }
+    }
+    return result;
   };
-  var script = document.createElement('script');
-  script.src = devServerUrl + '/dev_mode_on.js';
-  document.getElementsByTagName('head')[0].appendChild(script);
-})();
+}());
+
+// Load the bookmarklet.
+window.__gwt_bookmarklet_params = {
+  'server_url' : codeServerUrl,
+  'module_name': moduleName,
+  'getPropMap': getPropMap
+};
+var script = document.createElement('script');
+script.src = codeServerUrl + 'dev_mode_on.js';
+document.getElementsByTagName('head')[0].appendChild(script);
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
index 4134f74..1f3937e 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
@@ -22,6 +22,8 @@
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.ConfigurationProperty;
 import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.jjs.JsOutputOption;
 
 import java.util.Map.Entry;
 import java.util.SortedSet;
@@ -59,17 +61,57 @@
 
     // Add property providers
     startPos = selectionScript.indexOf("// __PROPERTIES_END__");
-    if (startPos != -1) {
-      for (SelectionProperty p : context.getProperties()) {
-        String text = generatePropertyProvider(logger, p,
-            context.getConfigurationProperties());
-        selectionScript.insert(startPos, text);
-        startPos += text.length();
-      }
+    if (startPos == -1) {
+      return selectionScript;
     }
+    selectionScript.insert(startPos, generatePropertyProviders(logger, context));
     return selectionScript;
   }
 
+  /**
+   * Returns JavaScript that declares and initializes the "providers" and "values" variables.
+   *
+   * <p>Requires $doc and $wnd variables to be defined. (And possibly others; this is unclear.)
+   *
+   * <p>Provides "providers" and "values" variables.</p>.
+   *
+   * <ul>
+   *   <li>"providers" is a mapping from each binding property (string) to a no-argument function
+   *   that determines its value.</li>
+   *   <li>"values" is a mapping from each binding property (string) to the set of allowed values
+   *   (represented as a mapping with the value as key).
+   *   </li>
+   * </ul>
+   */
+  public static String generatePropertiesSnippet(ModuleDef module, TreeLogger compileLogger)
+      throws UnableToCompleteException {
+
+    // TODO: PropertyProviderGenerator should specify the JavaScript environment that a
+    // property provider can assume so the caller of this function knows what it should provide.
+
+    LinkerContext linkerContext = new StandardLinkerContext(compileLogger, module,
+        module.getPublicResourceOracle(), JsOutputOption.PRETTY);
+
+    String initProvidersJs =
+        generatePropertyProviders(compileLogger, linkerContext);
+
+    return "var providers = {};\nvar values = {};\n" + initProvidersJs + "\n";
+  }
+
+  /**
+   * Generates JavaScript to create a property provider for each non-derived binding property
+   * in the given linker. The JavaScript mutates the "providers" and "values" variables which
+   * must already exist.
+   */
+  private static String generatePropertyProviders(TreeLogger logger, LinkerContext context)
+      throws UnableToCompleteException {
+    StringBuilder out = new StringBuilder();
+    for (SelectionProperty p : context.getProperties()) {
+      out.append(generatePropertyProvider(logger, p, context.getConfigurationProperties()));
+    }
+    return out.toString();
+  }
+
   private static String generatePropertyProvider(TreeLogger logger,
       SelectionProperty prop, SortedSet<ConfigurationProperty> configProps)
       throws UnableToCompleteException {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 7e5f9bd..b33af9a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -31,7 +31,7 @@
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.Script;
 import com.google.gwt.dev.jjs.InternalCompilerException;
-import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.jjs.SourceInfo;
 import com.google.gwt.dev.js.JsLiteralInterner;
 import com.google.gwt.dev.js.JsNamer.IllegalNameException;
@@ -117,7 +117,7 @@
 
   private final SortedSet<ConfigurationProperty> configurationProperties;
 
-  private final JJSOptions jjsOptions;
+  private final JsOutputOption outputOption;
 
   private final List<Class<? extends Linker>> linkerClasses;
   private Linker[] linkers;
@@ -133,7 +133,8 @@
   private final SortedSet<SelectionProperty> selectionProperties;
 
   public StandardLinkerContext(TreeLogger logger, ModuleDef module,
-      ResourceOracle publicResourceOracle, JJSOptions jjsOptions) throws UnableToCompleteException {
+      ResourceOracle publicResourceOracle, JsOutputOption outputOption)
+      throws UnableToCompleteException {
     logger = logger.branch(TreeLogger.DEBUG,
         "Constructing StandardLinkerContext", null);
 
@@ -141,7 +142,7 @@
     this.moduleName = module.getName();
     this.moduleLastModified = module.lastModified();
     this.publicResourceOracle = publicResourceOracle;
-    this.jjsOptions = jjsOptions;
+    this.outputOption = outputOption;
 
     // Sort the linkers into the order they should actually run.
     linkerClasses = new ArrayList<Class<? extends Linker>>();
@@ -415,7 +416,7 @@
 
   @Override
   public boolean isOutputCompact() {
-    return jjsOptions.getOutput().shouldMinimize();
+    return outputOption.shouldMinimize();
   }
 
   @Override
@@ -449,7 +450,7 @@
     JsUnusedFunctionRemover.exec(jsProgram);
 
     try {
-      switch (jjsOptions.getOutput()) {
+      switch (outputOption) {
         case OBFUSCATED:
           /*
            * We can't apply the regular JsLiteralInterner to the JsProgram that
@@ -478,8 +479,7 @@
       throw new UnableToCompleteException();
     }
 
-    DefaultTextOutput out = new DefaultTextOutput(
-        jjsOptions.getOutput().shouldMinimize());
+    DefaultTextOutput out = new DefaultTextOutput(outputOption.shouldMinimize());
     JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(out);
     v.accept(jsProgram);
     return out.toString();
diff --git a/dev/core/src/com/google/gwt/dev/DevModeBase.java b/dev/core/src/com/google/gwt/dev/DevModeBase.java
index d78c778..72e7d28 100644
--- a/dev/core/src/com/google/gwt/dev/DevModeBase.java
+++ b/dev/core/src/com/google/gwt/dev/DevModeBase.java
@@ -982,7 +982,7 @@
 
     // Create a new active linker stack for the fresh link.
     StandardLinkerContext linkerStack = new StandardLinkerContext(
-        linkLogger, module, compilerContext.getPublicResourceOracle(), options);
+        linkLogger, module, compilerContext.getPublicResourceOracle(), options.getOutput());
     ArtifactSet artifacts = linkerStack.getArtifactsForPublicResources(logger, module);
     artifacts = linkerStack.invokeLegacyLinkers(linkLogger, artifacts);
     artifacts = linkerStack.invokeFinalLink(linkLogger, artifacts);
diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java
index 77ecb88..c2ebbb7 100644
--- a/dev/core/src/com/google/gwt/dev/Link.java
+++ b/dev/core/src/com/google/gwt/dev/Link.java
@@ -177,7 +177,8 @@
       LinkOptions linkOptions)
       throws UnableToCompleteException, IOException {
     StandardLinkerContext linkerContext =
-        new StandardLinkerContext(logger, module, publicResourceOracle, precompileOptions);
+        new StandardLinkerContext(logger, module, publicResourceOracle,
+            precompileOptions.getOutput());
     ArtifactSet artifacts = doSimulatedShardingLink(
         logger, module, linkerContext, generatedArtifacts, permutations, resultFiles, libraries);
 
@@ -205,7 +206,7 @@
       JarOutputStream jar = new JarOutputStream(new FileOutputStream(jarFile));
 
       StandardLinkerContext linkerContext = new StandardLinkerContext(logger,
-          module, publicResourceOracle, precompileOptions);
+          module, publicResourceOracle, precompileOptions.getOutput());
 
       StandardCompilationResult compilation = new StandardCompilationResult(
           permResult);
@@ -714,7 +715,7 @@
     TreeLogger branch = logger.branch(TreeLogger.INFO, "Linking module "
         + module.getName());
     StandardLinkerContext linkerContext = new StandardLinkerContext(branch,
-        module, publicResourceOracle, precompileOptions);
+        module, publicResourceOracle, precompileOptions.getOutput());
 
     try {
       ArtifactSet artifacts = scanCompilePermResults(logger, resultFiles);
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index 5d3d3ab..88fa711 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -438,7 +438,7 @@
       compilerContext = compilerContextBuilder.module(module).build();
 
       StandardLinkerContext linkerContext = new StandardLinkerContext(
-          TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(), options);
+          TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(), options.getOutput());
 
       boolean generateOnShards = true;
 
diff --git a/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java b/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java
index e71165c..2d16854 100644
--- a/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java
+++ b/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java
@@ -200,7 +200,7 @@
     ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, compilerContext, moduleName);
     compilerContext = compilerContextBuilder.module(module).build();
     StandardLinkerContext linkerContext = new StandardLinkerContext(
-        TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(), options);
+        TreeLogger.NULL, module, compilerContext.getPublicResourceOracle(), options.getOutput());
 
     if (!linkerContext.allLinkersAreShardable()) {
       logger.log(TreeLogger.ERROR,
diff --git a/user/src/com/google/gwt/junit/JUnitShell.java b/user/src/com/google/gwt/junit/JUnitShell.java
index 51c22da..2eada41 100644
--- a/user/src/com/google/gwt/junit/JUnitShell.java
+++ b/user/src/com/google/gwt/junit/JUnitShell.java
@@ -35,6 +35,7 @@
 import com.google.gwt.dev.javac.CompilationProblemReporter;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.shell.CheckForUpdates;
 import com.google.gwt.dev.shell.jetty.JettyLauncher;
 import com.google.gwt.dev.util.arg.ArgHandlerCompilePerFile;
@@ -1126,7 +1127,8 @@
       try {
         Linker l = module.getActivePrimaryLinker().newInstance();
         StandardLinkerContext context = new StandardLinkerContext(
-            getTopLogger(), module, compilerContext.getPublicResourceOracle(), null);
+            getTopLogger(), module, compilerContext.getPublicResourceOracle(),
+            JsOutputOption.PRETTY);
         if (!l.supportsDevModeInJunit(context)) {
           if (module.getLinker("std") != null) {
             // TODO: unfortunately, this could be race condition between dev/prod