protected void testExternChanges(String extern, String input, String expectedExtern) {
    Compiler compiler = createCompiler();
    CompilerOptions options = getOptions();
    compiler.init(
        ImmutableList.of(SourceFile.fromCode("extern", extern)),
        ImmutableList.of(SourceFile.fromCode("input", input)),
        options);
    compiler.parseInputs();
    assertFalse(compiler.hasErrors());

    Node externsAndJs = compiler.getRoot();
    Node root = externsAndJs.getLastChild();

    Node externs = externsAndJs.getFirstChild();

    Node expected = compiler.parseTestCode(expectedExtern);
    assertFalse(compiler.hasErrors());

    (getProcessor(compiler)).process(externs, root);

    String externsCode = compiler.toSource(externs);
    String expectedCode = compiler.toSource(expected);

    assertEquals(expectedCode, externsCode);
  }
 public void testPackagePrivateAccessForProperties3() {
   testSame(
       ImmutableList.of(
           SourceFile.fromCode(
               "foo/bar.js",
               "/** @constructor */ function Foo() {}"
                   + "/** @package */ Foo.prototype.bar = function() {}; (new Foo).bar();"),
           SourceFile.fromCode("foo/baz.js", "Foo.prototype.baz = function() { this.bar(); };")));
 }
  public void testSortInputs() throws Exception {
    SourceFile a = SourceFile.fromCode("a.js", "require('b');require('c')");
    SourceFile b = SourceFile.fromCode("b.js", "require('d')");
    SourceFile c = SourceFile.fromCode("c.js", "require('d')");
    SourceFile d = SourceFile.fromCode("d.js", "1;");

    assertSortedInputs(ImmutableList.of(d, b, c, a), ImmutableList.of(a, b, c, d));
    assertSortedInputs(ImmutableList.of(d, b, c, a), ImmutableList.of(d, b, c, a));
    assertSortedInputs(ImmutableList.of(d, c, b, a), ImmutableList.of(d, c, b, a));
    assertSortedInputs(ImmutableList.of(d, b, c, a), ImmutableList.of(d, a, b, c));
  }
 public void testNoPackagePrivateAccessForProperties3() {
   test(
       ImmutableList.of(
           SourceFile.fromCode(
               "foo/bar.js",
               "/** @constructor */ function Foo() {} "
                   + "/** @package */ Foo.prototype.bar = function() {};"),
           SourceFile.fromCode(
               "baz/quux.js", "/** @constructor */ function OtherFoo() { (new Foo).bar(); }")),
       null,
       BAD_PACKAGE_PROPERTY_ACCESS);
 }
 public void testNoPackagePrivateAccessForProperties5() {
   test(
       ImmutableList.of(
           SourceFile.fromCode(
               "foo/bar.js",
               "/** @constructor */ function Foo() {} "
                   + "/** @package */ Foo.prototype.bar = function() {};"),
           SourceFile.fromCode(
               "baz/quux.js",
               "/** @constructor \n * @extends {Foo} */ "
                   + "function SubFoo() {};"
                   + "SubFoo.prototype.baz = function() { this.bar(); }")),
       null,
       BAD_PACKAGE_PROPERTY_ACCESS);
 }
 /** Parses expected JS inputs and returns the root of the parse tree. */
 protected Node parseExpectedJs(String[] expected) {
   List<SourceFile> inputs = Lists.newArrayList();
   for (int i = 0; i < expected.length; i++) {
     inputs.add(SourceFile.fromCode("expected" + i, expected[i]));
   }
   return parseExpectedJs(inputs);
 }
  private void splitFiles(String[] input) {
    Compiler compiler = new Compiler();
    List<SourceFile> files = Lists.newArrayList();

    for (int i = 0; i < input.length; i++) {
      files.add(SourceFile.fromCode("file" + i, input[i]));
    }

    compiler.init(ImmutableList.<SourceFile>of(), files, new CompilerOptions());
    compiler.parse();
    Node original = compiler.getRoot();
    Node root = original.cloneTree();

    AstParallelizer parallelizer = AstParallelizer.createNewFileLevelAstParallelizer(root);
    List<Node> forest = parallelizer.split();
    assertEquals(input.length, forest.size());
    int i = 0;
    for (Node n : forest) {
      Node tree = compiler.parseTestCode(input[i++]);
      assertEquals(compiler.toSource(tree), compiler.toSource(n));
    }

    parallelizer.join();
    assertTrue(original.isEquivalentTo(root));
  }
 public void testNoPackagePrivateAccessForProperties6() {
   // Overriding a private property with a non-package-private property
   // in a different file causes problems.
   test(
       ImmutableList.of(
           SourceFile.fromCode(
               "foo/bar.js",
               "/** @constructor */ function Foo() {} "
                   + "/** @package */ Foo.prototype.bar = function() {};"),
           SourceFile.fromCode(
               "baz/quux.js",
               "/** @constructor \n * @extends {Foo} */ "
                   + "function SubFoo() {};"
                   + "SubFoo.prototype.bar = function() {};")),
       null,
       BAD_PACKAGE_PROPERTY_ACCESS);
 }
 public void testModuleName() {
   setFilename("foo/bar");
   testModules(
       "var name = require('other');",
       "goog.provide('module$foo$bar'); var module$foo$bar = {};"
           + "goog.require('module$other');"
           + "var name$$module$foo$bar = module$other;");
   ProcessEs6ModulesTest.testModules(
       this,
       ImmutableList.of(
           SourceFile.fromCode("foo/name.js", ""),
           SourceFile.fromCode("foo/bar.js", "var name = require('./name');")),
       "goog.provide('module$foo$bar');"
           + "var module$foo$bar = {};"
           + "goog.require('module$foo$name');"
           + "var name$$module$foo$bar = module$foo$name;");
 }
 /**
  * Generates a list of modules from a list of inputs. Does not generate any dependencies between
  * the modules.
  */
 static JSModule[] createModules(String... inputs) {
   JSModule[] modules = new JSModule[inputs.length];
   for (int i = 0; i < inputs.length; i++) {
     JSModule module = modules[i] = new JSModule("m" + i);
     module.add(SourceFile.fromCode("i" + i, inputs[i]));
   }
   return modules;
 }
 public void testNoPackagePrivateAccessForProperties7() {
   // It's OK to override a package-private property with a
   // non-package-private property in the same file, but you'll get
   // yelled at when you try to use it.
   test(
       ImmutableList.of(
           SourceFile.fromCode(
               "foo/bar.js",
               "/** @constructor */ function Foo() {} "
                   + "/** @package */ Foo.prototype.bar = function() {};"
                   + "/** @constructor \n * @extends {Foo} */ "
                   + "function SubFoo() {};"
                   + "SubFoo.prototype.bar = function() {};"),
           SourceFile.fromCode(
               "baz/quux.js", "SubFoo.prototype.baz = function() { this.bar(); }")),
       null,
       BAD_PACKAGE_PROPERTY_ACCESS);
 }
 /**
  * Verifies that the compiler pass's JS output is the same as its input and (optionally) that an
  * expected warning and description is issued.
  *
  * @param externs Externs input
  * @param js Input and output
  * @param warning Expected warning, or null if no warning is expected
  * @param description The description of the expected warning, or null if no warning is expected
  *     or if the warning's description should not be examined
  */
 public void testSame(
     String externs, String js, DiagnosticType type, String description, boolean error) {
   List<SourceFile> externsInputs = ImmutableList.of(SourceFile.fromCode("externs", externs));
   if (error) {
     test(externsInputs, js, js, type, null, description);
   } else {
     test(externsInputs, js, js, null, type, description);
   }
 }
 public void testFileNotOnOnlyApplyToRegexpIsNotChecked() {
   configuration =
       "requirement: {\n"
           + "  type: BANNED_NAME\n"
           + "  value: 'eval'\n"
           + "  error_message: 'eval is not allowed'\n"
           + "  only_apply_to_regexp: 'test.js$'\n "
           + "}";
   testSame(ImmutableList.of(SourceFile.fromCode("bar.js", "eval()")));
 }
  protected void testExternChanges(String extern, String input, String expectedExtern) {
    Compiler compiler = createCompiler();
    CompilerOptions options = getOptions();
    compiler.init(
        ImmutableList.of(SourceFile.fromCode("extern", extern)),
        ImmutableList.of(SourceFile.fromCode("input", input)),
        options);
    compiler.parseInputs();
    assertFalse(compiler.hasErrors());

    Node externsAndJs = compiler.getRoot();
    Node root = externsAndJs.getLastChild();

    Node externs = externsAndJs.getFirstChild();

    Node expected = compiler.parseTestCode(expectedExtern);
    assertFalse(compiler.hasErrors());

    (getProcessor(compiler)).process(externs, root);

    if (compareAsTree) {
      // Expected output parsed without implied block.
      Preconditions.checkState(externs.isBlock());
      Preconditions.checkState(compareJsDoc);
      Preconditions.checkState(
          externs.hasOneChild(), "Compare as tree only works when output has a single script.");
      externs = externs.getFirstChild();
      String explanation = expected.checkTreeEqualsIncludingJsDoc(externs);
      assertNull(
          "\nExpected: "
              + compiler.toSource(expected)
              + "\nResult:   "
              + compiler.toSource(externs)
              + "\n"
              + explanation,
          explanation);
    } else {
      String externsCode = compiler.toSource(externs);
      String expectedCode = compiler.toSource(expected);
      assertEquals(expectedCode, externsCode);
    }
  }
 /**
  * Verifies that the compiler pass's JS output matches the expected output and (optionally) that
  * an expected warning is issued. Or, if an error is expected, this method just verifies that the
  * error is encountered.
  *
  * @param js Inputs
  * @param expected Expected JS output
  * @param error Expected error, or null if no error is expected
  * @param warning Expected warning, or null if no warning is expected
  * @param description The description of the expected warning, or null if no warning is expected
  *     or if the warning's description should not be examined
  */
 public void test(
     String[] js,
     String[] expected,
     DiagnosticType error,
     DiagnosticType warning,
     String description) {
   List<SourceFile> inputs = Lists.newArrayList();
   for (int i = 0; i < js.length; i++) {
     inputs.add(SourceFile.fromCode("input" + i, js[i]));
   }
   test(inputs, expected, error, warning, description);
 }
 /**
  * Verifies that the compiler pass's JS output matches the expected output and (optionally) that
  * an expected warning is issued. Or, if an error is expected, this method just verifies that the
  * error is encountered.
  *
  * @param externs Externs input
  * @param js Input
  * @param expected Expected output, or null if an error is expected
  * @param error Expected error, or null if no error is expected
  * @param warning Expected warning, or null if no warning is expected
  * @param description The description of the expected warning, or null if no warning is expected
  *     or if the warning's description should not be examined
  */
 public void test(
     String externs,
     String js,
     String expected,
     DiagnosticType error,
     DiagnosticType warning,
     String description) {
   SourceFile externsFile = SourceFile.fromCode("externs", externs);
   externsFile.setIsExtern(true);
   List<SourceFile> externsInputs = ImmutableList.of(externsFile);
   test(externsInputs, js, expected, error, warning, description);
 }
 public void testWithoutExports() {
   setFilename("test");
   testModules(
       "var name = require('other');" + "name()",
       "goog.provide('module$test');"
           + "var module$test = {};"
           + "goog.require('module$other');"
           + "var name$$module$test = module$other;"
           + "name$$module$test();");
   setFilename("test/sub");
   ProcessEs6ModulesTest.testModules(
       this,
       ImmutableList.of(
           SourceFile.fromCode("mod/name.js", ""),
           SourceFile.fromCode(
               "test/sub.js", "var name = require('mod/name');" + "(function() { name(); })();")),
       "goog.provide('module$test$sub');"
           + "var module$test$sub = {};"
           + "goog.require('module$mod$name');"
           + "var name$$module$test$sub = module$mod$name;"
           + "(function() { name$$module$test$sub(); })();");
 }
 public void testPackagePrivateAccessForProperties5() {
   test(
       ImmutableList.of(
           SourceFile.fromCode(
               "foo/bar.js",
               "/** @constructor */\n"
                   + "function Parent () {\n"
                   + "  /** @package */\n"
                   + "  this.prop = 'foo';\n"
                   + "};"),
           SourceFile.fromCode(
               "baz/quux.js",
               "/**\n"
                   + " * @constructor\n"
                   + " * @extends {Parent}\n"
                   + " */\n"
                   + "function Child() {\n"
                   + "  this.prop = 'asdf';\n"
                   + "}\n"
                   + "Child.prototype = new Parent();")),
       null,
       BAD_PACKAGE_PROPERTY_ACCESS);
 }
 /**
  * Verifies that the compiler pass's JS output matches the expected output and (optionally) that
  * an expected warning is issued. Or, if an error is expected, this method just verifies that the
  * error is encountered.
  *
  * @param externs Externs inputs
  * @param js Input
  * @param expected Expected output, or null if an error is expected
  * @param error Expected error, or null if no error is expected
  * @param warning Expected warning, or null if no warning is expected
  * @param description The description of the expected warning, or null if no warning is expected
  *     or if the warning's description should not be examined
  */
 public void test(
     List<SourceFile> externs,
     String js,
     String expected,
     DiagnosticType error,
     DiagnosticType warning,
     String description) {
   test(
       externs,
       ImmutableList.of(SourceFile.fromCode(filename, js)),
       expected,
       error,
       warning,
       description);
 }
 public void testSpecifyingWhitelistAndOnlyApplyToIsRuntimeError() {
   configuration =
       "requirement: {\n"
           + "  type: BANNED_NAME\n"
           + "  value: 'eval'\n"
           + "  error_message: 'eval is not allowed'\n"
           + "  whitelist: 'blah'\n"
           + "  only_apply_to_regexp: 'test.js$'\n "
           + "}";
   try {
     testSame(ImmutableList.of(SourceFile.fromCode("bar.js", "eval()")));
     fail("expected IllegalArgumentException");
   } catch (Exception e) {
     assertThat(e).isInstanceOf(IllegalArgumentException.class);
   }
 }
 public void testFileOnOnlyApplyToIsChecked() {
   configuration =
       "requirement: {\n"
           + "  type: BANNED_NAME\n"
           + "  value: 'eval'\n"
           + "  error_message: 'eval is not allowed'\n"
           + "  only_apply_to: 'foo.js'\n "
           + "}";
   ImmutableList<SourceFile> input = ImmutableList.of(SourceFile.fromCode("foo.js", "eval()"));
   test(
       input,
       input,
       null,
       CheckConformance.CONFORMANCE_VIOLATION,
       "Violation: eval is not allowed");
 }
 /**
  * Verifies that the compiler pass's JS output matches the expected output and (optionally) that
  * an expected warning is issued. Or, if an error is expected, this method just verified that the
  * error is encountered.
  *
  * @param compiler A compiler that has been initialized via {@link Compiler#init}
  * @param expected Expected output, or null if an error is expected
  * @param error Expected error, or null if no error is expected
  * @param warning Expected warning, or null if no warning is expected
  */
 private void test(
     Compiler compiler,
     String[] expected,
     DiagnosticType error,
     DiagnosticType warning,
     String description) {
   if (expected == null) {
     test(compiler, (List<SourceFile>) null, error, warning, description);
   } else {
     List<SourceFile> inputs = Lists.newArrayList();
     for (int i = 0; i < expected.length; i++) {
       inputs.add(SourceFile.fromCode("expected" + i, expected[i]));
     }
     test(compiler, inputs, error, warning, description);
   }
 }
  public void testSyntheticExterns() {
    externs = ImmutableList.of(SourceFile.fromCode("externs", "myVar.property;"));
    test(
        "var theirVar = {}; var myVar = {}; var yourVar = {};",
        VarCheck.UNDEFINED_EXTERN_VAR_ERROR);

    args.add("--jscomp_off=externsValidation");
    args.add("--warning_level=VERBOSE");
    test(
        "var theirVar = {}; var myVar = {}; var yourVar = {};",
        "var theirVar={},myVar={},yourVar={};");

    args.add("--jscomp_off=externsValidation");
    args.add("--warning_level=VERBOSE");
    test(
        "var theirVar = {}; var myVar = {}; var myVar = {};",
        SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR);
  }
 private Node parse(String[] original) {
   String[] argStrings = args.toArray(new String[] {});
   CommandLineRunner runner = new CommandLineRunner(argStrings);
   Compiler compiler = runner.createCompiler();
   List<SourceFile> inputs = Lists.newArrayList();
   for (int i = 0; i < original.length; i++) {
     inputs.add(SourceFile.fromCode(getFilename(i), original[i]));
   }
   CompilerOptions options = new CompilerOptions();
   // ECMASCRIPT5 is the most forgiving.
   options.setLanguageIn(LanguageMode.ECMASCRIPT5);
   compiler.init(externs, inputs, options);
   Node all = compiler.parseInputs();
   Preconditions.checkState(compiler.getErrorCount() == 0);
   Preconditions.checkNotNull(all);
   Node n = all.getLastChild();
   return n;
 }
  private void assertSortedInputs(List<SourceFile> expected, List<SourceFile> shuffled)
      throws Exception {
    Compiler compiler = new Compiler(System.err);
    compiler.initCompilerOptionsIfTesting();
    compiler.getOptions().setProcessCommonJSModules(true);
    compiler
        .getOptions()
        .dependencyOptions
        .setEntryPoints(ImmutableList.of(ES6ModuleLoader.toModuleName(URI.create("a"))));
    compiler.compile(
        ImmutableList.of(SourceFile.fromCode("externs.js", "")), shuffled, compiler.getOptions());

    List<SourceFile> result = new ArrayList<>();
    for (JSModule m : compiler.getModuleGraph().getAllModules()) {
      for (CompilerInput i : m.getInputs()) {
        result.add(i.getSourceFile());
      }
    }

    assertEquals(expected, result);
  }
 private String printHelper(String js, boolean runProcessor) {
   Compiler compiler = createCompiler();
   CompilerOptions options = getOptions();
   compiler.init(
       ImmutableList.<SourceFile>of(),
       ImmutableList.of(SourceFile.fromCode("testcode", js)),
       options);
   Node root = compiler.parseInputs();
   assertNotNull(
       "Unexpected parse error(s): "
           + Joiner.on("\n").join(compiler.getErrors())
           + "\nEXPR: "
           + js,
       root);
   Node externsRoot = root.getFirstChild();
   Node mainRoot = externsRoot.getNext();
   if (runProcessor) {
     getProcessor(compiler).process(externsRoot, mainRoot);
   }
   return compiler.toSource(mainRoot);
 }
  private Compiler compile(String[] original) {
    CommandLineRunner runner = createCommandLineRunner(original);
    assertTrue(new String(errReader.toByteArray()), runner.shouldRunCompiler());
    Supplier<List<SourceFile>> inputsSupplier = null;
    Supplier<List<JSModule>> modulesSupplier = null;

    if (useModules == ModulePattern.NONE) {
      List<SourceFile> inputs = Lists.newArrayList();
      for (int i = 0; i < original.length; i++) {
        inputs.add(SourceFile.fromCode(getFilename(i), original[i]));
      }
      inputsSupplier = Suppliers.ofInstance(inputs);
    } else if (useModules == ModulePattern.STAR) {
      modulesSupplier =
          Suppliers.<List<JSModule>>ofInstance(
              Lists.<JSModule>newArrayList(CompilerTestCase.createModuleStar(original)));
    } else if (useModules == ModulePattern.CHAIN) {
      modulesSupplier =
          Suppliers.<List<JSModule>>ofInstance(
              Lists.<JSModule>newArrayList(CompilerTestCase.createModuleChain(original)));
    } else {
      throw new IllegalArgumentException("Unknown module type: " + useModules);
    }

    runner.enableTestMode(
        Suppliers.<List<SourceFile>>ofInstance(externs),
        inputsSupplier,
        modulesSupplier,
        new Function<Integer, Boolean>() {
          @Override
          public Boolean apply(Integer code) {
            return exitCodes.add(code);
          }
        });
    runner.run();
    lastCompiler = runner.getCompiler();
    lastCommandLineRunner = runner;
    return lastCompiler;
  }
/**
 * Tests for {@link CommandLineRunner}.
 *
 * @author [email protected] (Nick Santos)
 */
public class CommandLineRunnerTest extends TestCase {

  private Compiler lastCompiler = null;
  private CommandLineRunner lastCommandLineRunner = null;
  private List<Integer> exitCodes = null;
  private ByteArrayOutputStream outReader = null;
  private ByteArrayOutputStream errReader = null;
  private Map<Integer, String> filenames;

  // If set, this will be appended to the end of the args list.
  // For testing args parsing.
  private String lastArg = null;

  // If set to true, uses comparison by string instead of by AST.
  private boolean useStringComparison = false;

  private ModulePattern useModules = ModulePattern.NONE;

  private enum ModulePattern {
    NONE,
    CHAIN,
    STAR
  }

  private List<String> args = Lists.newArrayList();

  /** Externs for the test */
  private final List<SourceFile> DEFAULT_EXTERNS =
      ImmutableList.of(
          SourceFile.fromCode(
              "externs",
              "var arguments;"
                  + "/**\n"
                  + " * @constructor\n"
                  + " * @param {...*} var_args\n"
                  + " * @nosideeffects\n"
                  + " * @throws {Error}\n"
                  + " */\n"
                  + "function Function(var_args) {}\n"
                  + "/**\n"
                  + " * @param {...*} var_args\n"
                  + " * @return {*}\n"
                  + " */\n"
                  + "Function.prototype.call = function(var_args) {};"
                  + "/**\n"
                  + " * @constructor\n"
                  + " * @param {...*} var_args\n"
                  + " * @return {!Array}\n"
                  + " */\n"
                  + "function Array(var_args) {}"
                  + "/**\n"
                  + " * @param {*=} opt_begin\n"
                  + " * @param {*=} opt_end\n"
                  + " * @return {!Array}\n"
                  + " * @this {Object}\n"
                  + " */\n"
                  + "Array.prototype.slice = function(opt_begin, opt_end) {};"
                  + "/** @constructor */ function Window() {}\n"
                  + "/** @type {string} */ Window.prototype.name;\n"
                  + "/** @type {Window} */ var window;"
                  + "/** @constructor */ function Element() {}"
                  + "Element.prototype.offsetWidth;"
                  + "/** @nosideeffects */ function noSideEffects() {}\n"
                  + "/** @param {...*} x */ function alert(x) {}\n"));

  private List<SourceFile> externs;

  @Override
  public void setUp() throws Exception {
    super.setUp();
    externs = DEFAULT_EXTERNS;
    filenames = Maps.newHashMap();
    lastCompiler = null;
    lastArg = null;
    outReader = new ByteArrayOutputStream();
    errReader = new ByteArrayOutputStream();
    useStringComparison = false;
    useModules = ModulePattern.NONE;
    args.clear();
    exitCodes = Lists.newArrayList();
  }

  @Override
  public void tearDown() throws Exception {
    super.tearDown();
  }

  public void testUnknownAnnotation() {
    args.add("--warning_level=VERBOSE");
    test("/** @unknownTag */ function f() {}", RhinoErrorReporter.BAD_JSDOC_ANNOTATION);

    args.add("--extra_annotation_name=unknownTag");
    testSame("/** @unknownTag */ function f() {}");
  }

  public void testWarningGuardOrdering1() {
    args.add("--jscomp_error=globalThis");
    args.add("--jscomp_off=globalThis");
    testSame("function f() { this.a = 3; }");
  }

  public void testWarningGuardOrdering2() {
    args.add("--jscomp_off=globalThis");
    args.add("--jscomp_error=globalThis");
    test("function f() { this.a = 3; }", CheckGlobalThis.GLOBAL_THIS);
  }

  public void testWarningGuardOrdering3() {
    args.add("--jscomp_warning=globalThis");
    args.add("--jscomp_off=globalThis");
    testSame("function f() { this.a = 3; }");
  }

  public void testWarningGuardOrdering4() {
    args.add("--jscomp_off=globalThis");
    args.add("--jscomp_warning=globalThis");
    test("function f() { this.a = 3; }", CheckGlobalThis.GLOBAL_THIS);
  }

  public void testSimpleModeLeavesUnusedParams() {
    args.add("--compilation_level=SIMPLE_OPTIMIZATIONS");
    testSame("window.f = function(a) {};");
  }

  public void testAdvancedModeRemovesUnusedParams() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test("window.f = function(a) {};", "window.a = function() {};");
  }

  public void testCheckGlobalThisOffByDefault() {
    testSame("function f() { this.a = 3; }");
  }

  public void testCheckGlobalThisOnWithAdvancedMode() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test("function f() { this.a = 3; }", CheckGlobalThis.GLOBAL_THIS);
  }

  public void testCheckGlobalThisOnWithErrorFlag() {
    args.add("--jscomp_error=globalThis");
    test("function f() { this.a = 3; }", CheckGlobalThis.GLOBAL_THIS);
  }

  public void testCheckGlobalThisOff() {
    args.add("--warning_level=VERBOSE");
    args.add("--jscomp_off=globalThis");
    testSame("function f() { this.a = 3; }");
  }

  public void testTypeCheckingOffByDefault() {
    test("function f(x) { return x; } f();", "function f(a) { return a; } f();");
  }

  public void testReflectedMethods() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test(
        "/** @constructor */"
            + "function Foo() {}"
            + "Foo.prototype.handle = function(x, y) { alert(y); };"
            + "var x = goog.reflect.object(Foo, {handle: 1});"
            + "for (var i in x) { x[i].call(x); }"
            + "window['Foo'] = Foo;",
        "function a() {}"
            + "a.prototype.a = function(e, d) { alert(d); };"
            + "var b = goog.c.b(a, {a: 1}),c;"
            + "for (c in b) { b[c].call(b); }"
            + "window.Foo = a;");
  }

  public void testInlineVariables() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test(
        "/** @constructor */ function F() { this.a = 0; }"
            + "F.prototype.inc = function() { this.a++; return 10; };"
            + "F.prototype.bar = function() { "
            + "  var c = 3; var val = inc(); this.a += val + c;"
            + "};"
            + "window['f'] = new F();"
            + "window['f']['bar'] = window['f'].bar;",
        "function a(){ this.a = 0; }"
            + "a.prototype.b = function(){ var b=inc(); this.a += b + 3; };"
            + "window.f = new a;"
            + "window.f.bar = window.f.b");
  }

  public void testTypedAdvanced() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    args.add("--use_types_for_optimization");
    test(
        "/** @constructor */\n"
            + "function Foo() {}\n"
            + "Foo.prototype.handle1 = function(x, y) { alert(y); };\n"
            + "/** @constructor */\n"
            + "function Bar() {}\n"
            + "Bar.prototype.handle1 = function(x, y) {};\n"
            + "new Foo().handle1(1, 2);\n"
            + "new Bar().handle1(1, 2);\n",
        "alert(2)");
  }

  public void testTypeCheckingOnWithVerbose() {
    args.add("--warning_level=VERBOSE");
    test("function f(x) { return x; } f();", TypeCheck.WRONG_ARGUMENT_COUNT);
  }

  public void testTypeParsingOffByDefault() {
    testSame("/** @return {number */ function f(a) { return a; }");
  }

  public void testTypeParsingOnWithVerbose() {
    args.add("--warning_level=VERBOSE");
    test("/** @return {number */ function f(a) { return a; }", RhinoErrorReporter.TYPE_PARSE_ERROR);
    test("/** @return {n} */ function f(a) { return a; }", RhinoErrorReporter.TYPE_PARSE_ERROR);
  }

  public void testTypeCheckOverride1() {
    args.add("--warning_level=VERBOSE");
    args.add("--jscomp_off=checkTypes");
    testSame("var x = x || {}; x.f = function() {}; x.f(3);");
  }

  public void testTypeCheckOverride2() {
    args.add("--warning_level=DEFAULT");
    testSame("var x = x || {}; x.f = function() {}; x.f(3);");

    args.add("--jscomp_warning=checkTypes");
    test("var x = x || {}; x.f = function() {}; x.f(3);", TypeCheck.WRONG_ARGUMENT_COUNT);
  }

  public void testCheckSymbolsOffForDefault() {
    args.add("--warning_level=DEFAULT");
    test("x = 3; var y; var y;", "x=3; var y;");
  }

  public void testCheckSymbolsOnForVerbose() {
    args.add("--warning_level=VERBOSE");
    test("x = 3;", VarCheck.UNDEFINED_VAR_ERROR);
    test("var y; var y;", SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR);
  }

  public void testCheckSymbolsOverrideForVerbose() {
    args.add("--warning_level=VERBOSE");
    args.add("--jscomp_off=undefinedVars");
    testSame("x = 3;");
  }

  public void testCheckSymbolsOverrideForQuiet() {
    args.add("--warning_level=QUIET");
    args.add("--jscomp_error=undefinedVars");
    test("x = 3;", VarCheck.UNDEFINED_VAR_ERROR);
  }

  public void testCheckUndefinedProperties1() {
    args.add("--warning_level=VERBOSE");
    args.add("--jscomp_error=missingProperties");
    test("var x = {}; var y = x.bar;", TypeCheck.INEXISTENT_PROPERTY);
  }

  public void testCheckUndefinedProperties2() {
    args.add("--warning_level=VERBOSE");
    args.add("--jscomp_off=missingProperties");
    test("var x = {}; var y = x.bar;", CheckGlobalNames.UNDEFINED_NAME_WARNING);
  }

  public void testCheckUndefinedProperties3() {
    args.add("--warning_level=VERBOSE");
    test("function f() {var x = {}; var y = x.bar;}", TypeCheck.INEXISTENT_PROPERTY);
  }

  public void testDuplicateParams() {
    test("function f(a, a) {}", RhinoErrorReporter.DUPLICATE_PARAM);
    assertTrue(lastCompiler.hasHaltingErrors());
  }

  public void testDefineFlag() {
    args.add("--define=FOO");
    args.add("--define=\"BAR=5\"");
    args.add("--D");
    args.add("CCC");
    args.add("-D");
    args.add("DDD");
    test(
        "/** @define {boolean} */ var FOO = false;"
            + "/** @define {number} */ var BAR = 3;"
            + "/** @define {boolean} */ var CCC = false;"
            + "/** @define {boolean} */ var DDD = false;",
        "var FOO = !0, BAR = 5, CCC = !0, DDD = !0;");
  }

  public void testDefineFlag2() {
    args.add("--define=FOO='x\"'");
    test("/** @define {string} */ var FOO = \"a\";", "var FOO = \"x\\\"\";");
  }

  public void testDefineFlag3() {
    args.add("--define=FOO=\"x'\"");
    test("/** @define {string} */ var FOO = \"a\";", "var FOO = \"x'\";");
  }

  public void testScriptStrictModeNoWarning() {
    test("'use strict';", "");
    test("'no use strict';", CheckSideEffects.USELESS_CODE_ERROR);
  }

  public void testFunctionStrictModeNoWarning() {
    test("function f() {'use strict';}", "function f() {}");
    test("function f() {'no use strict';}", CheckSideEffects.USELESS_CODE_ERROR);
  }

  public void testQuietMode() {
    args.add("--warning_level=DEFAULT");
    test("/** @const \n * @const */ var x;", RhinoErrorReporter.PARSE_ERROR);
    args.add("--warning_level=QUIET");
    testSame("/** @const \n * @const */ var x;");
  }

  public void testProcessClosurePrimitives() {
    test("var goog = {}; goog.provide('goog.dom');", "var goog = {dom:{}};");
    args.add("--process_closure_primitives=false");
    testSame("var goog = {}; goog.provide('goog.dom');");
  }

  public void testGetMsgWiring() throws Exception {
    test(
        "var goog = {}; goog.getMsg = function(x) { return x; };"
            + "/** @desc A real foo. */ var MSG_FOO = goog.getMsg('foo');",
        "var goog={getMsg:function(a){return a}}, " + "MSG_FOO=goog.getMsg('foo');");
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test(
        "var goog = {}; goog.getMsg = function(x) { return x; };"
            + "/** @desc A real foo. */ var MSG_FOO = goog.getMsg('foo');"
            + "window['foo'] = MSG_FOO;",
        "window.foo = 'foo';");
  }

  public void testCssNameWiring() throws Exception {
    test(
        "var goog = {}; goog.getCssName = function() {};"
            + "goog.setCssNameMapping = function() {};"
            + "goog.setCssNameMapping({'goog': 'a', 'button': 'b'});"
            + "var a = goog.getCssName('goog-button');"
            + "var b = goog.getCssName('css-button');"
            + "var c = goog.getCssName('goog-menu');"
            + "var d = goog.getCssName('css-menu');",
        "var goog = { getCssName: function() {},"
            + "             setCssNameMapping: function() {} },"
            + "    a = 'a-b',"
            + "    b = 'css-b',"
            + "    c = 'a-menu',"
            + "    d = 'css-menu';");
  }

  //////////////////////////////////////////////////////////////////////////////
  // Integration tests

  public void testIssue70a() {
    test("function foo({}) {}", RhinoErrorReporter.PARSE_ERROR);
  }

  public void testIssue70b() {
    test("function foo([]) {}", RhinoErrorReporter.PARSE_ERROR);
  }

  public void testIssue81() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    useStringComparison = true;
    test("eval('1'); var x = eval; x('2');", "eval(\"1\");(0,eval)(\"2\");");
  }

  public void testIssue115() {
    args.add("--compilation_level=SIMPLE_OPTIMIZATIONS");
    args.add("--jscomp_off=es5Strict");
    args.add("--warning_level=VERBOSE");
    test(
        "function f() { "
            + "  var arguments = Array.prototype.slice.call(arguments, 0);"
            + "  return arguments[0]; "
            + "}",
        "function f() { "
            + "  arguments = Array.prototype.slice.call(arguments, 0);"
            + "  return arguments[0]; "
            + "}");
  }

  public void testIssue297() {
    args.add("--compilation_level=SIMPLE_OPTIMIZATIONS");
    test(
        "function f(p) {"
            + " var x;"
            + " return ((x=p.id) && (x=parseInt(x.substr(1))) && x>0);"
            + "}",
        "function f(b) {"
            + " var a;"
            + " return ((a=b.id) && (a=parseInt(a.substr(1))) && 0<a);"
            + "}");
  }

  public void testHiddenSideEffect() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test("element.offsetWidth;", "element.offsetWidth", CheckSideEffects.USELESS_CODE_ERROR);
  }

  public void testIssue504() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test(
        "void function() { alert('hi'); }();",
        "alert('hi');void 0",
        CheckSideEffects.USELESS_CODE_ERROR);
  }

  public void testIssue601() {
    args.add("--compilation_level=WHITESPACE_ONLY");
    test(
        "function f() { return '\\v' == 'v'; } window['f'] = f;",
        "function f(){return'\\v'=='v'}window['f']=f");
  }

  public void testIssue601b() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test(
        "function f() { return '\\v' == 'v'; } window['f'] = f;",
        "window.f=function(){return'\\v'=='v'}");
  }

  public void testIssue601c() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test(
        "function f() { return '\\u000B' == 'v'; } window['f'] = f;",
        "window.f=function(){return'\\u000B'=='v'}");
  }

  public void testIssue846() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    testSame("try { new Function('this is an error'); } catch(a) { alert('x'); }");
  }

  public void testSideEffectIntegration() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test(
        "/** @constructor */"
            + "var Foo = function() {};"
            + "Foo.prototype.blah = function() {"
            + "  Foo.bar_(this)"
            + "};"
            + "Foo.bar_ = function(f) {"
            + "  f.x = 5;"
            + "};"
            + "var y = new Foo();"
            + "Foo.bar_({});"
            +

            // We used to strip this too
            // due to bad side-effect propagation.
            "y.blah();"
            + "alert(y);",
        "var a = new function(){}; a.a = 5; alert(a);");
  }

  public void testDebugFlag1() {
    args.add("--compilation_level=SIMPLE_OPTIMIZATIONS");
    args.add("--debug=false");
    test("function foo(a) {}", "function foo(a) {}");
  }

  public void testDebugFlag2() {
    args.add("--compilation_level=SIMPLE_OPTIMIZATIONS");
    args.add("--debug=true");
    test("function foo(a) {alert(a)}", "function foo($a$$) {alert($a$$)}");
  }

  public void testDebugFlag3() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    args.add("--warning_level=QUIET");
    args.add("--debug=false");
    test(
        "function Foo() {}" + "Foo.x = 1;" + "function f() {throw new Foo().x;} f();",
        "throw (new function() {}).a;");
  }

  public void testDebugFlag4() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    args.add("--warning_level=QUIET");
    args.add("--debug=true");
    test(
        "function Foo() {}" + "Foo.x = 1;" + "function f() {throw new Foo().x;} f();",
        "throw (new function Foo() {}).$x$;");
  }

  public void testBooleanFlag1() {
    args.add("--compilation_level=SIMPLE_OPTIMIZATIONS");
    args.add("--debug");
    test("function foo(a) {alert(a)}", "function foo($a$$) {alert($a$$)}");
  }

  public void testBooleanFlag2() {
    args.add("--debug");
    args.add("--compilation_level=SIMPLE_OPTIMIZATIONS");
    test("function foo(a) {alert(a)}", "function foo($a$$) {alert($a$$)}");
  }

  public void testHelpFlag() {
    args.add("--help");
    assertFalse(createCommandLineRunner(new String[] {"function f() {}"}).shouldRunCompiler());
  }

  public void testExternsLifting1() throws Exception {
    String code = "/** @externs */ function f() {}";
    test(new String[] {code}, new String[] {});

    assertEquals(2, lastCompiler.getExternsForTesting().size());

    CompilerInput extern = lastCompiler.getExternsForTesting().get(1);
    assertNull(extern.getModule());
    assertTrue(extern.isExtern());
    assertEquals(code, extern.getCode());

    assertEquals(1, lastCompiler.getInputsForTesting().size());

    CompilerInput input = lastCompiler.getInputsForTesting().get(0);
    assertNotNull(input.getModule());
    assertFalse(input.isExtern());
    assertEquals("", input.getCode());
  }

  public void testExternsLifting2() {
    args.add("--warning_level=VERBOSE");
    test(
        new String[] {"/** @externs */ function f() {}", "f(3);"},
        new String[] {"f(3);"},
        TypeCheck.WRONG_ARGUMENT_COUNT);
  }

  public void testSourceSortingOff() {
    args.add("--compilation_level=WHITESPACE_ONLY");
    testSame(new String[] {"goog.require('beer');", "goog.provide('beer');"});
  }

  public void testSourceSortingOn() {
    test(
        new String[] {"goog.require('beer');", "goog.provide('beer');"},
        new String[] {"var beer = {};", ""});
  }

  public void testSourceSortingOn2() {
    test(
        new String[] {
          "goog.provide('a');", "goog.require('a');\n" + "var COMPILED = false;",
        },
        new String[] {"var a={};", "var COMPILED=!1"});
  }

  public void testSourceSortingOn3() {
    args.add("--manage_closure_dependencies=true");
    test(
        new String[] {
          "goog.addDependency('sym', [], []);\nvar x = 3;", "var COMPILED = false;",
        },
        new String[] {"var COMPILED = !1;", "var x = 3;"});
  }

  public void testSourceSortingCircularDeps1() {
    args.add("--manage_closure_dependencies=true");
    test(
        new String[] {
          "goog.provide('gin'); goog.require('tonic'); var gin = {};",
          "goog.provide('tonic'); goog.require('gin'); var tonic = {};",
          "goog.require('gin'); goog.require('tonic');"
        },
        JSModule.CIRCULAR_DEPENDENCY_ERROR);
  }

  public void testSourceSortingCircularDeps2() {
    args.add("--manage_closure_dependencies=true");
    test(
        new String[] {
          "goog.provide('roses.lime.juice');",
          "goog.provide('gin'); goog.require('tonic'); var gin = {};",
          "goog.provide('tonic'); goog.require('gin'); var tonic = {};",
          "goog.require('gin'); goog.require('tonic');",
          "goog.provide('gimlet');" + "     goog.require('gin'); goog.require('roses.lime.juice');"
        },
        JSModule.CIRCULAR_DEPENDENCY_ERROR);
  }

  public void testSourcePruningOn1() {
    args.add("--manage_closure_dependencies=true");
    test(
        new String[] {
          "goog.require('beer');", "goog.provide('beer');", "goog.provide('scotch'); var x = 3;"
        },
        new String[] {"var beer = {};", ""});
  }

  public void testSourcePruningOn2() {
    args.add("--closure_entry_point=guinness");
    test(
        new String[] {
          "goog.provide('guinness');\ngoog.require('beer');",
          "goog.provide('beer');",
          "goog.provide('scotch'); var x = 3;"
        },
        new String[] {"var beer = {};", "var guinness = {};"});
  }

  public void testSourcePruningOn3() {
    args.add("--closure_entry_point=scotch");
    test(
        new String[] {
          "goog.provide('guinness');\ngoog.require('beer');",
          "goog.provide('beer');",
          "goog.provide('scotch'); var x = 3;"
        },
        new String[] {
          "var scotch = {}, x = 3;",
        });
  }

  public void testSourcePruningOn4() {
    args.add("--closure_entry_point=scotch");
    args.add("--closure_entry_point=beer");
    test(
        new String[] {
          "goog.provide('guinness');\ngoog.require('beer');",
          "goog.provide('beer');",
          "goog.provide('scotch'); var x = 3;"
        },
        new String[] {
          "var beer = {};", "var scotch = {}, x = 3;",
        });
  }

  public void testSourcePruningOn5() {
    args.add("--closure_entry_point=shiraz");
    test(
        new String[] {
          "goog.provide('guinness');\ngoog.require('beer');",
          "goog.provide('beer');",
          "goog.provide('scotch'); var x = 3;"
        },
        Compiler.MISSING_ENTRY_ERROR);
  }

  public void testSourcePruningOn6() {
    args.add("--closure_entry_point=scotch");
    test(
        new String[] {
          "goog.require('beer');", "goog.provide('beer');", "goog.provide('scotch'); var x = 3;"
        },
        new String[] {
          "var beer = {};", "", "var scotch = {}, x = 3;",
        });
  }

  public void testSourcePruningOn7() {
    args.add("--manage_closure_dependencies=true");
    test(
        new String[] {
          "var COMPILED = false;",
        },
        new String[] {
          "var COMPILED = !1;",
        });
  }

  public void testSourcePruningOn8() {
    args.add("--only_closure_dependencies");
    args.add("--closure_entry_point=scotch");
    args.add("--warning_level=VERBOSE");
    test(
        new String[] {
          "/** @externs */\n" + "var externVar;", "goog.provide('scotch'); var x = externVar;"
        },
        new String[] {
          "var scotch = {}, x = externVar;",
        });
  }

  public void testModuleEntryPoint() throws Exception {
    useModules = ModulePattern.STAR;
    args.add("--only_closure_dependencies");
    args.add("--closure_entry_point=m1:a");
    test(
        new String[] {"goog.provide('a');", "goog.provide('b');"},
        // Check that 'b' was stripped out, and 'a' was moved to the second
        // module (m1).
        new String[] {"", "var a = {};"});
  }

  public void testNoCompile() {
    args.add("--warning_level=VERBOSE");
    test(
        new String[] {
          "/** @nocompile */\n" + "goog.provide('x');\n" + "var dupeVar;", "var dupeVar;"
        },
        new String[] {"var dupeVar;"});
  }

  public void testDependencySortingWhitespaceMode() {
    args.add("--manage_closure_dependencies");
    args.add("--compilation_level=WHITESPACE_ONLY");
    test(
        new String[] {
          "goog.require('beer');",
          "goog.provide('beer');\ngoog.require('hops');",
          "goog.provide('hops');",
        },
        new String[] {
          "goog.provide('hops');",
          "goog.provide('beer');\ngoog.require('hops');",
          "goog.require('beer');"
        });
  }

  public void testForwardDeclareDroppedTypes() {
    args.add("--manage_closure_dependencies=true");

    args.add("--warning_level=VERBOSE");
    test(
        new String[] {
          "goog.require('beer');",
          "goog.provide('beer'); /** @param {Scotch} x */ function f(x) {}",
          "goog.provide('Scotch'); var x = 3;"
        },
        new String[] {"var beer = {}; function f(a) {}", ""});

    test(
        new String[] {
          "goog.require('beer');", "goog.provide('beer'); /** @param {Scotch} x */ function f(x) {}"
        },
        new String[] {"var beer = {}; function f(a) {}", ""},
        RhinoErrorReporter.TYPE_PARSE_ERROR);
  }

  public void testOnlyClosureDependenciesEmptyEntryPoints() throws Exception {
    // Prevents this from trying to load externs.zip
    args.add("--use_only_custom_externs=true");

    args.add("--only_closure_dependencies=true");
    try {
      CommandLineRunner runner = createCommandLineRunner(new String[0]);
      runner.doRun();
      fail("Expected FlagUsageException");
    } catch (FlagUsageException e) {
      assertTrue(e.getMessage(), e.getMessage().contains("only_closure_dependencies"));
    }
  }

  public void testOnlyClosureDependenciesOneEntryPoint() throws Exception {
    args.add("--only_closure_dependencies=true");
    args.add("--closure_entry_point=beer");
    test(
        new String[] {
          "goog.require('beer'); var beerRequired = 1;",
          "goog.provide('beer');\ngoog.require('hops');\nvar beerProvided = 1;",
          "goog.provide('hops'); var hopsProvided = 1;",
          "goog.provide('scotch'); var scotchProvided = 1;",
          "goog.require('scotch');\nvar includeFileWithoutProvides = 1;",
          "/** This is base.js */\nvar COMPILED = false;",
        },
        new String[] {
          "var COMPILED = !1;",
          "var hops = {}, hopsProvided = 1;",
          "var beer = {}, beerProvided = 1;"
        });
  }

  public void testSourceMapExpansion1() {
    args.add("--js_output_file");
    args.add("/path/to/out.js");
    args.add("--create_source_map=%outname%.map");
    testSame("var x = 3;");
    assertEquals(
        "/path/to/out.js.map",
        lastCommandLineRunner.expandSourceMapPath(lastCompiler.getOptions(), null));
  }

  public void testSourceMapExpansion2() {
    useModules = ModulePattern.CHAIN;
    args.add("--create_source_map=%outname%.map");
    args.add("--module_output_path_prefix=foo");
    testSame(new String[] {"var x = 3;", "var y = 5;"});
    assertEquals(
        "foo.map", lastCommandLineRunner.expandSourceMapPath(lastCompiler.getOptions(), null));
  }

  public void testSourceMapExpansion3() {
    useModules = ModulePattern.CHAIN;
    args.add("--create_source_map=%outname%.map");
    args.add("--module_output_path_prefix=foo_");
    testSame(new String[] {"var x = 3;", "var y = 5;"});
    assertEquals(
        "foo_m0.js.map",
        lastCommandLineRunner.expandSourceMapPath(
            lastCompiler.getOptions(), lastCompiler.getModuleGraph().getRootModule()));
  }

  public void testSourceMapFormat1() {
    args.add("--js_output_file");
    args.add("/path/to/out.js");
    testSame("var x = 3;");
    assertEquals(SourceMap.Format.DEFAULT, lastCompiler.getOptions().sourceMapFormat);
  }

  public void testSourceMapFormat2() {
    args.add("--js_output_file");
    args.add("/path/to/out.js");
    args.add("--source_map_format=V3");
    testSame("var x = 3;");
    assertEquals(SourceMap.Format.V3, lastCompiler.getOptions().sourceMapFormat);
  }

  public void testModuleWrapperBaseNameExpansion() throws Exception {
    useModules = ModulePattern.CHAIN;
    args.add("--module_wrapper=m0:%s // %basename%");
    testSame(new String[] {"var x = 3;", "var y = 4;"});

    StringBuilder builder = new StringBuilder();
    lastCommandLineRunner.writeModuleOutput(builder, lastCompiler.getModuleGraph().getRootModule());
    assertEquals("var x=3; // m0.js\n", builder.toString());
  }

  public void testCharSetExpansion() {
    testSame("");
    assertEquals("US-ASCII", lastCompiler.getOptions().outputCharset);
    args.add("--charset=UTF-8");
    testSame("");
    assertEquals("UTF-8", lastCompiler.getOptions().outputCharset);
  }

  public void testChainModuleManifest() throws Exception {
    useModules = ModulePattern.CHAIN;
    testSame(new String[] {"var x = 3;", "var y = 5;", "var z = 7;", "var a = 9;"});

    StringBuilder builder = new StringBuilder();
    lastCommandLineRunner.printModuleGraphManifestOrBundleTo(
        lastCompiler.getModuleGraph(), builder, true);
    assertEquals(
        "{m0}\n"
            + "i0\n"
            + "\n"
            + "{m1:m0}\n"
            + "i1\n"
            + "\n"
            + "{m2:m1}\n"
            + "i2\n"
            + "\n"
            + "{m3:m2}\n"
            + "i3\n",
        builder.toString());
  }

  public void testStarModuleManifest() throws Exception {
    useModules = ModulePattern.STAR;
    testSame(new String[] {"var x = 3;", "var y = 5;", "var z = 7;", "var a = 9;"});

    StringBuilder builder = new StringBuilder();
    lastCommandLineRunner.printModuleGraphManifestOrBundleTo(
        lastCompiler.getModuleGraph(), builder, true);
    assertEquals(
        "{m0}\n"
            + "i0\n"
            + "\n"
            + "{m1:m0}\n"
            + "i1\n"
            + "\n"
            + "{m2:m0}\n"
            + "i2\n"
            + "\n"
            + "{m3:m0}\n"
            + "i3\n",
        builder.toString());
  }

  public void testOutputModuleGraphJson() throws Exception {
    useModules = ModulePattern.STAR;
    testSame(new String[] {"var x = 3;", "var y = 5;", "var z = 7;", "var a = 9;"});

    StringBuilder builder = new StringBuilder();
    lastCommandLineRunner.printModuleGraphJsonTo(builder);
    assertTrue(builder.toString().indexOf("transitive-dependencies") != -1);
  }

  public void testVersionFlag() {
    args.add("--version");
    testSame("");
    assertEquals(
        0,
        new String(errReader.toByteArray())
            .indexOf("Closure Compiler (http://code.google.com/closure/compiler)\n" + "Version: "));
  }

  public void testVersionFlag2() {
    lastArg = "--version";
    testSame("");
    assertEquals(
        0,
        new String(errReader.toByteArray())
            .indexOf("Closure Compiler (http://code.google.com/closure/compiler)\n" + "Version: "));
  }

  public void testPrintAstFlag() {
    args.add("--print_ast=true");
    testSame("");
    assertEquals(
        "digraph AST {\n"
            + "  node [color=lightblue2, style=filled];\n"
            + "  node0 [label=\"BLOCK\"];\n"
            + "  node1 [label=\"SCRIPT\"];\n"
            + "  node0 -> node1 [weight=1];\n"
            + "  node1 -> RETURN [label=\"UNCOND\", "
            + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n"
            + "  node0 -> RETURN [label=\"SYN_BLOCK\", "
            + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n"
            + "  node0 -> node1 [label=\"UNCOND\", "
            + "fontcolor=\"red\", weight=0.01, color=\"red\"];\n"
            + "}\n\n",
        new String(outReader.toByteArray()));
  }

  public void testSyntheticExterns() {
    externs = ImmutableList.of(SourceFile.fromCode("externs", "myVar.property;"));
    test(
        "var theirVar = {}; var myVar = {}; var yourVar = {};",
        VarCheck.UNDEFINED_EXTERN_VAR_ERROR);

    args.add("--jscomp_off=externsValidation");
    args.add("--warning_level=VERBOSE");
    test(
        "var theirVar = {}; var myVar = {}; var yourVar = {};",
        "var theirVar={},myVar={},yourVar={};");

    args.add("--jscomp_off=externsValidation");
    args.add("--warning_level=VERBOSE");
    test(
        "var theirVar = {}; var myVar = {}; var myVar = {};",
        SyntacticScopeCreator.VAR_MULTIPLY_DECLARED_ERROR);
  }

  public void testGoogAssertStripping() {
    args.add("--compilation_level=ADVANCED_OPTIMIZATIONS");
    test("goog.asserts.assert(false)", "");
    args.add("--debug");
    test("goog.asserts.assert(false)", "goog.$asserts$.$assert$(!1)");
  }

  public void testMissingReturnCheckOnWithVerbose() {
    args.add("--warning_level=VERBOSE");
    test(
        "/** @return {number} */ function f() {f()} f();",
        CheckMissingReturn.MISSING_RETURN_STATEMENT);
  }

  public void testGenerateExports() {
    args.add("--generate_exports=true");
    test(
        "/** @export */ foo.prototype.x = function() {};",
        "foo.prototype.x=function(){};"
            + "goog.exportSymbol(\"foo.prototype.x\",foo.prototype.x);");
  }

  public void testDepreciationWithVerbose() {
    args.add("--warning_level=VERBOSE");
    test("/** @deprecated */ function f() {}; f()", CheckAccessControls.DEPRECATED_NAME);
  }

  public void testTwoParseErrors() {
    // If parse errors are reported in different files, make
    // sure all of them are reported.
    Compiler compiler = compile(new String[] {"var a b;", "var b c;"});
    assertEquals(2, compiler.getErrors().length);
  }

  public void testES3ByDefault() {
    test("var x = f.function", "var x = f['function']", RhinoErrorReporter.INVALID_ES3_PROP_NAME);
  }

  public void testES5ChecksByDefault() {
    testSame("var x = 3; delete x;");
  }

  public void testES5ChecksInVerbose() {
    args.add("--warning_level=VERBOSE");
    test("function f(x) { delete x; }", StrictModeCheck.DELETE_VARIABLE);
  }

  public void testES5() {
    args.add("--language_in=ECMASCRIPT5");
    test("var x = f.function", "var x = f.function");
    test("var let", "var let");
  }

  public void testES5Strict() {
    args.add("--language_in=ECMASCRIPT5_STRICT");
    test("var x = f.function", "'use strict';var x = f.function");
    test("var let", RhinoErrorReporter.PARSE_ERROR);
    test("function f(x) { delete x; }", StrictModeCheck.DELETE_VARIABLE);
  }

  public void testES5StrictUseStrict() {
    args.add("--language_in=ECMASCRIPT5_STRICT");
    Compiler compiler = compile(new String[] {"var x = f.function"});
    String outputSource = compiler.toSource();
    assertEquals("'use strict'", outputSource.substring(0, 12));
  }

  public void testES5StrictUseStrictMultipleInputs() {
    args.add("--language_in=ECMASCRIPT5_STRICT");
    Compiler compiler =
        compile(new String[] {"var x = f.function", "var y = f.function", "var z = f.function"});
    String outputSource = compiler.toSource();
    assertEquals("'use strict'", outputSource.substring(0, 12));
    assertEquals(outputSource.substring(13).indexOf("'use strict'"), -1);
  }

  public void testWithKeywordDefault() {
    test("var x = {}; with (x) {}", ControlStructureCheck.USE_OF_WITH);
  }

  public void testWithKeywordWithEs5ChecksOff() {
    args.add("--jscomp_off=es5Strict");
    testSame("var x = {}; with (x) {}");
  }

  public void testNoSrCFilesWithManifest() throws IOException {
    args.add("--use_only_custom_externs=true");
    args.add("--output_manifest=test.MF");
    CommandLineRunner runner = createCommandLineRunner(new String[0]);
    String expectedMessage = "";
    try {
      runner.doRun();
    } catch (FlagUsageException e) {
      expectedMessage = e.getMessage();
    }
    assertEquals(
        expectedMessage,
        "Bad --js flag. " + "Manifest files cannot be generated when the input is from stdin.");
  }

  public void testTransformAMD() {
    args.add("--transform_amd_modules");
    test("define({test: 1})", "exports = {test: 1}");
  }

  public void testProcessCJS() {
    useStringComparison = true;
    args.add("--process_common_js_modules");
    args.add("--common_js_entry_module=foo/bar");
    setFilename(0, "foo/bar.js");
    String expected = "var module$foo$bar={test:1};";
    test("exports.test = 1", expected);
    assertEquals(expected + "\n", outReader.toString());
  }

  public void testProcessCJSWithModuleOutput() {
    useStringComparison = true;
    args.add("--process_common_js_modules");
    args.add("--common_js_entry_module=foo/bar");
    args.add("--module=auto");
    setFilename(0, "foo/bar.js");
    test("exports.test = 1", "var module$foo$bar={test:1};");
    // With modules=auto no direct output is created.
    assertEquals("", outReader.toString());
  }

  public void testFormattingSingleQuote() {
    testSame("var x = '';");
    assertEquals("var x=\"\";", lastCompiler.toSource());

    args.add("--formatting=SINGLE_QUOTES");
    testSame("var x = '';");
    assertEquals("var x='';", lastCompiler.toSource());
  }

  public void testTransformAMDAndProcessCJS() {
    useStringComparison = true;
    args.add("--transform_amd_modules");
    args.add("--process_common_js_modules");
    args.add("--common_js_entry_module=foo/bar");
    setFilename(0, "foo/bar.js");
    test("define({foo: 1})", "var module$foo$bar={},module$foo$bar={foo:1};");
  }

  public void testModuleJSON() {
    useStringComparison = true;
    args.add("--transform_amd_modules");
    args.add("--process_common_js_modules");
    args.add("--common_js_entry_module=foo/bar");
    args.add("--output_module_dependencies=test.json");
    setFilename(0, "foo/bar.js");
    test("define({foo: 1})", "var module$foo$bar={},module$foo$bar={foo:1};");
  }

  public void testOutputSameAsInput() {
    args.add("--js_output_file=" + getFilename(0));
    test("", AbstractCommandLineRunner.OUTPUT_SAME_AS_INPUT_ERROR);
  }

  /* Helper functions */

  private void testSame(String original) {
    testSame(new String[] {original});
  }

  private void testSame(String[] original) {
    test(original, original);
  }

  private void test(String original, String compiled) {
    test(new String[] {original}, new String[] {compiled});
  }

  /**
   * Asserts that when compiling with the given compiler options, {@code original} is transformed
   * into {@code compiled}.
   */
  private void test(String[] original, String[] compiled) {
    test(original, compiled, null);
  }

  /**
   * Asserts that when compiling with the given compiler options, {@code original} is transformed
   * into {@code compiled}. If {@code warning} is non-null, we will also check if the given warning
   * type was emitted.
   */
  private void test(String[] original, String[] compiled, DiagnosticType warning) {
    Compiler compiler = compile(original);

    if (warning == null) {
      assertEquals(
          "Expected no warnings or errors\n"
              + "Errors: \n"
              + Joiner.on("\n").join(compiler.getErrors())
              + "Warnings: \n"
              + Joiner.on("\n").join(compiler.getWarnings()),
          0,
          compiler.getErrors().length + compiler.getWarnings().length);
    } else {
      assertEquals(1, compiler.getWarnings().length);
      assertEquals(warning, compiler.getWarnings()[0].getType());
    }

    Node root = compiler.getRoot().getLastChild();
    if (useStringComparison) {
      assertEquals(Joiner.on("").join(compiled), compiler.toSource());
    } else {
      Node expectedRoot = parse(compiled);
      String explanation = expectedRoot.checkTreeEquals(root);
      assertNull(
          "\nExpected: "
              + compiler.toSource(expectedRoot)
              + "\nResult: "
              + compiler.toSource(root)
              + "\n"
              + explanation,
          explanation);
    }
  }

  /** Asserts that when compiling, there is an error or warning. */
  private void test(String original, DiagnosticType warning) {
    test(new String[] {original}, warning);
  }

  private void test(String original, String expected, DiagnosticType warning) {
    test(new String[] {original}, new String[] {expected}, warning);
  }

  /** Asserts that when compiling, there is an error or warning. */
  private void test(String[] original, DiagnosticType warning) {
    Compiler compiler = compile(original);
    assertEquals(
        "Expected exactly one warning or error "
            + "Errors: \n"
            + Joiner.on("\n").join(compiler.getErrors())
            + "Warnings: \n"
            + Joiner.on("\n").join(compiler.getWarnings()),
        1,
        compiler.getErrors().length + compiler.getWarnings().length);

    assertTrue(exitCodes.size() > 0);
    int lastExitCode = exitCodes.get(exitCodes.size() - 1);

    if (compiler.getErrors().length > 0) {
      assertEquals(1, compiler.getErrors().length);
      assertEquals(warning, compiler.getErrors()[0].getType());
      assertEquals(1, lastExitCode);
    } else {
      assertEquals(1, compiler.getWarnings().length);
      assertEquals(warning, compiler.getWarnings()[0].getType());
      assertEquals(0, lastExitCode);
    }
  }

  private CommandLineRunner createCommandLineRunner(String[] original) {
    for (int i = 0; i < original.length; i++) {
      args.add("--js");
      args.add("/path/to/input" + i + ".js");
      if (useModules == ModulePattern.CHAIN) {
        args.add("--module");
        args.add("m" + i + ":1" + (i > 0 ? (":m" + (i - 1)) : ""));
      } else if (useModules == ModulePattern.STAR) {
        args.add("--module");
        args.add("m" + i + ":1" + (i > 0 ? ":m0" : ""));
      }
    }

    if (lastArg != null) {
      args.add(lastArg);
    }

    String[] argStrings = args.toArray(new String[] {});
    return new CommandLineRunner(
        argStrings, new PrintStream(outReader), new PrintStream(errReader));
  }

  private Compiler compile(String[] original) {
    CommandLineRunner runner = createCommandLineRunner(original);
    assertTrue(new String(errReader.toByteArray()), runner.shouldRunCompiler());
    Supplier<List<SourceFile>> inputsSupplier = null;
    Supplier<List<JSModule>> modulesSupplier = null;

    if (useModules == ModulePattern.NONE) {
      List<SourceFile> inputs = Lists.newArrayList();
      for (int i = 0; i < original.length; i++) {
        inputs.add(SourceFile.fromCode(getFilename(i), original[i]));
      }
      inputsSupplier = Suppliers.ofInstance(inputs);
    } else if (useModules == ModulePattern.STAR) {
      modulesSupplier =
          Suppliers.<List<JSModule>>ofInstance(
              Lists.<JSModule>newArrayList(CompilerTestCase.createModuleStar(original)));
    } else if (useModules == ModulePattern.CHAIN) {
      modulesSupplier =
          Suppliers.<List<JSModule>>ofInstance(
              Lists.<JSModule>newArrayList(CompilerTestCase.createModuleChain(original)));
    } else {
      throw new IllegalArgumentException("Unknown module type: " + useModules);
    }

    runner.enableTestMode(
        Suppliers.<List<SourceFile>>ofInstance(externs),
        inputsSupplier,
        modulesSupplier,
        new Function<Integer, Boolean>() {
          @Override
          public Boolean apply(Integer code) {
            return exitCodes.add(code);
          }
        });
    runner.run();
    lastCompiler = runner.getCompiler();
    lastCommandLineRunner = runner;
    return lastCompiler;
  }

  private Node parse(String[] original) {
    String[] argStrings = args.toArray(new String[] {});
    CommandLineRunner runner = new CommandLineRunner(argStrings);
    Compiler compiler = runner.createCompiler();
    List<SourceFile> inputs = Lists.newArrayList();
    for (int i = 0; i < original.length; i++) {
      inputs.add(SourceFile.fromCode(getFilename(i), original[i]));
    }
    CompilerOptions options = new CompilerOptions();
    // ECMASCRIPT5 is the most forgiving.
    options.setLanguageIn(LanguageMode.ECMASCRIPT5);
    compiler.init(externs, inputs, options);
    Node all = compiler.parseInputs();
    Preconditions.checkState(compiler.getErrorCount() == 0);
    Preconditions.checkNotNull(all);
    Node n = all.getLastChild();
    return n;
  }

  private void setFilename(int i, String filename) {
    this.filenames.put(i, filename);
  }

  private String getFilename(int i) {
    if (filenames.isEmpty()) {
      return "input" + i;
    }
    return filenames.get(i);
  }
}
  private final void parseAndTypeCheck(String externs, String js) {
    setUp();
    final CompilerOptions options = compiler.getOptions();
    options.setClosurePass(true);
    options.setNewTypeInference(true);
    options.setWarningLevel(DiagnosticGroups.NEW_CHECK_TYPES_ALL_CHECKS, CheckLevel.WARNING);
    compiler.init(
        ImmutableList.of(SourceFile.fromCode("[externs]", externs)),
        ImmutableList.of(SourceFile.fromCode("[testcode]", js)),
        options);

    Node externsRoot = IR.block();
    externsRoot.setIsSyntheticBlock(true);
    externsRoot.addChildToFront(compiler.getInput(new InputId("[externs]")).getAstRoot(compiler));
    Node astRoot = IR.block();
    astRoot.setIsSyntheticBlock(true);
    astRoot.addChildToFront(compiler.getInput(new InputId("[testcode]")).getAstRoot(compiler));

    assertEquals(
        "parsing error: " + Joiner.on(", ").join(compiler.getErrors()),
        0,
        compiler.getErrorCount());
    assertEquals(
        "parsing warning: " + Joiner.on(", ").join(compiler.getWarnings()),
        0,
        compiler.getWarningCount());

    // Create common parent of externs and ast; needed by Es6RewriteBlockScopedDeclaration.
    Node block = IR.block(externsRoot, astRoot);
    block.setIsSyntheticBlock(true);

    // Run ASTValidator
    (new AstValidator(compiler)).validateRoot(block);

    DeclaredGlobalExternsOnWindow rewriteExterns = new DeclaredGlobalExternsOnWindow(compiler);
    passes.add(makePassFactory("globalExternsOnWindow", rewriteExterns));
    ProcessClosurePrimitives closurePass =
        new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR, false);
    passes.add(makePassFactory("ProcessClosurePrimitives", closurePass));
    if (options.getLanguageIn() == CompilerOptions.LanguageMode.ECMASCRIPT6_TYPED) {
      passes.add(makePassFactory("convertEs6TypedToEs6", new Es6TypedToEs6Converter(compiler)));
    }
    if (options.getLanguageIn().isEs6OrHigher()) {
      passes.add(
          makePassFactory(
              "Es6RenameVariablesInParamLists", new Es6RenameVariablesInParamLists(compiler)));
      passes.add(
          makePassFactory(
              "Es6SplitVariableDeclarations", new Es6SplitVariableDeclarations(compiler)));
      passes.add(makePassFactory("es6ConvertSuper", new Es6ConvertSuper(compiler)));
      passes.add(makePassFactory("convertEs6", new Es6ToEs3Converter(compiler)));
      passes.add(
          makePassFactory(
              "Es6RewriteBlockScopedDeclaration", new Es6RewriteBlockScopedDeclaration(compiler)));
      passes.add(makePassFactory("rewriteGenerators", new Es6RewriteGenerators(compiler)));
      passes.add(makePassFactory("Es6RuntimeLibrary", new InjectEs6RuntimeLibrary(compiler)));
      passes.add(
          makePassFactory("Es6StaticInheritance", new Es6ToEs3ClassSideInheritance(compiler)));
    }
    passes.add(makePassFactory("GlobalTypeInfo", compiler.getSymbolTable()));
    passes.add(makePassFactory("NewTypeInference", new NewTypeInference(compiler)));

    PhaseOptimizer phaseopt = new PhaseOptimizer(compiler, null, null);
    phaseopt.consume(passes);
    phaseopt.process(externsRoot, astRoot);
  }
 /**
  * Constructs a test.
  *
  * @param externs Externs JS as a string
  * @param compareAsTree True to compare output & expected as a node tree. 99% of the time you want
  *     to compare as a tree. There are a few special cases where you don't, like if you want to
  *     test the code printing of "unnatural" syntax trees. For example,
  *     <pre>
  * IF
  *   IF
  *     STATEMENT
  * ELSE
  *   STATEMENT
  * </pre>
  */
 protected CompilerTestCase(String externs, boolean compareAsTree) {
   this.externsInputs = ImmutableList.of(SourceFile.fromCode("externs", externs));
   this.compareAsTree = compareAsTree;
   this.parseTypeInfo = false;
   this.compareJsDoc = true;
 }