@ParametersFactory
  public static Iterable<Object[]> parameters() {
    class Parameter {
      private final FileSystem fileSystem;
      private final Function<String, Path> temp;

      public Parameter(FileSystem fileSystem, String root) {
        this(
            fileSystem,
            s -> {
              try {
                return Files.createTempDirectory(fileSystem.getPath(root), s);
              } catch (IOException e) {
                throw new RuntimeException(e);
              }
            });
      }

      public Parameter(FileSystem fileSystem, Function<String, Path> temp) {
        this.fileSystem = fileSystem;
        this.temp = temp;
      }
    }
    List<Parameter> parameters = new ArrayList<>();
    parameters.add(new Parameter(Jimfs.newFileSystem(Configuration.windows()), "c:\\"));
    parameters.add(new Parameter(Jimfs.newFileSystem(toPosix(Configuration.osX())), "/"));
    parameters.add(new Parameter(Jimfs.newFileSystem(toPosix(Configuration.unix())), "/"));
    parameters.add(new Parameter(PathUtils.getDefaultFileSystem(), LuceneTestCase::createTempDir));
    return parameters
        .stream()
        .map(p -> new Object[] {p.fileSystem, p.temp})
        .collect(Collectors.toList());
  }
  public static ProjectFilesystem createJavaOnlyFilesystem(String rootPath) {
    FileSystem vfs = Jimfs.newFileSystem(Configuration.unix());

    Path root = vfs.getPath(rootPath);
    try {
      Files.createDirectories(root);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    return new ProjectFilesystem(root) {
      @Override
      public Path resolve(Path path) {
        // Avoid resolving paths from different Java FileSystems.
        return super.resolve(path.toString());
      }
    };
  }
/** Tests for {@link NodeModulePass}. */
@RunWith(JUnit4.class)
public class NodeModulePassTest {

  private final FileSystem fs = Jimfs.newFileSystem();

  @Inject TypeRegistry typeRegistry;

  @Test
  public void doesNotModifySourceIfFileIsNotACommonJsModule() {
    CompilerUtil compiler = createCompiler();

    compiler.compile(path("foo/bar.js"), "var x = 123;");
    assertEquals("var x = 123;", compiler.toSource().trim());
  }

  @Test
  public void setsUpCommonJsModulePrimitives_emptyModule() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(path("foo/bar.js"), "");
    assertThat(compiler.toSource()).contains("var module$foo$bar = {};");
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void setsUpCommonJsModulePrimitives_useStrictModule() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(path("foo/bar.js"), "'use strict';");
    assertThat(compiler.toSource()).contains("var module$foo$bar = {};");
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void setsUpCommonJsModulePrimitives_hasExportsReference() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(path("foo/bar.js"), "exports.x = 123;");
    assertThat(compiler.toSource())
        .contains(lines("var module$foo$bar = {};", "module$foo$bar.x = 123;"));
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void hasExportsReferenceAndAnotherScriptDefinesExportsInTheGlobalScope() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(
        createSourceFile(path("base.js"), "var exports = {};"),
        createSourceFile(path("foo/bar.js"), "exports.x = 123;"));
    assertThat(compiler.toSource())
        .contains(
            lines("var exports = {};", "var module$foo$bar = {};", "module$foo$bar.x = 123;"));
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void moduleRebindsExportsVariable() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(path("foo/bar.js"), "exports = 123;");
    assertThat(compiler.toSource()).contains("var module$foo$bar = 123;");
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void renamesModuleGlobalVars() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(path("foo/bar.js"), "var x = 123;");
    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar = {};",
                "$jscomp.scope.x = 123;"));
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void doesNotRenameNonGlobalVars() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(path("foo/bar.js"), "function x() { var x = 123; }");
    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar = {};",
                "$jscomp.scope.x = function() {",
                "  var x = 123;",
                "};"));
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void renamesModuleGlobalFunctionDeclarations() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(path("foo/bar.js"), "function foo(){}");
    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar = {};",
                "$jscomp.scope.foo = function() {",
                "};"));
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void renamesModuleGlobalFunctionExpressions() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(path("foo/bar.js"), "var foo = function(){}");
    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar = {};",
                "$jscomp.scope.foo = function() {",
                "};"));
    assertIsNodeModule("module$foo$bar", "foo/bar.js");
  }

  @Test
  public void sortsSingleModuleDep() {
    CompilerUtil compiler = createCompiler(path("foo/leaf.js"), path("foo/root.js"));

    SourceFile root = createSourceFile(path("foo/root.js"), "");
    SourceFile leaf = createSourceFile(path("foo/leaf.js"), "require('./root');");

    compiler.compile(leaf, root); // Should reorder since leaf depends on root.

    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$root = {};",
                "var module$foo$leaf = {};"));
    assertIsNodeModule("module$foo$leaf", "foo/leaf.js");
    assertIsNodeModule("module$foo$root", "foo/root.js");
  }

  @Test
  public void sortsWithTwoModuleDeps() {
    CompilerUtil compiler =
        createCompiler(path("foo/one.js"), path("foo/two.js"), path("foo/three.js"));

    SourceFile one = createSourceFile(path("foo/one.js"), "");
    SourceFile two =
        createSourceFile(path("foo/two.js"), "require('./one');", "require('./three');");
    SourceFile three = createSourceFile(path("foo/three.js"));

    compiler.compile(two, one, three); // Should properly reorder inputs.

    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$one = {};",
                "var module$foo$three = {};",
                "var module$foo$two = {};"));
    assertIsNodeModule("module$foo$one", "foo/one.js");
    assertIsNodeModule("module$foo$two", "foo/two.js");
    assertIsNodeModule("module$foo$three", "foo/three.js");
  }

  @Test
  public void rewritesRequireStatementToDirectlyReferenceExportsObject() {
    CompilerUtil compiler = createCompiler(path("foo/leaf.js"), path("foo/root.js"));

    compiler.compile(
        createSourceFile(path("foo/root.js"), ""),
        createSourceFile(
            path("foo/leaf.js"),
            "var foo = require('./root');",
            "var bar = require('./root').bar;",
            "foo.bar(foo);"));

    assertThat(compiler.toSource())
        .contains(
            lines(
                "var module$foo$root = {};",
                "var module$foo$leaf = {};",
                "module$foo$root.bar(module$foo$root);"));
    assertIsNodeModule("module$foo$leaf", "foo/leaf.js");
    assertIsNodeModule("module$foo$root", "foo/root.js");
  }

  @Test
  public void rewritesRequireStatementForDirectoryIndex1() {
    CompilerUtil compiler = createCompiler(path("foo/bar/index.js"), path("foo/main.js"));

    compiler.compile(
        createSourceFile(path("foo/bar/index.js"), "exports.a = 123;"),
        createSourceFile(
            path("foo/main.js"), "var bar = require('./bar');", "exports.b = bar.a * 2;"));

    assertThat(compiler.toSource())
        .contains(
            lines(
                "var module$foo$bar$index = {};",
                "module$foo$bar$index.a = 123;",
                "var module$foo$main = {};",
                "module$foo$main.b = module$foo$bar$index.a * 2;"));
  }

  @Test
  public void rewritesRequireStatementForDirectoryIndex2() throws IOException {
    createDirectories(path("foo"));
    createFile(path("foo/bar.js"));
    CompilerUtil compiler =
        createCompiler(path("foo/bar/index.js"), path("foo/bar.js"), path("foo/main.js"));

    compiler.compile(
        createSourceFile(path("foo/bar/index.js"), "exports.a = 123;"),
        createSourceFile(path("foo/bar.js"), "exports.b = 456;"),
        createSourceFile(
            path("foo/main.js"),
            "var bar1 = require('./bar');",
            "var bar2 = require('./bar/');",
            "exports.c = bar1.a * bar2.b;"));

    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar$index = {};",
                "module$foo$bar$index.a = 123;",
                "var module$foo$bar = {};",
                "module$foo$bar.b = 456;",
                "var module$foo$main = {};",
                "module$foo$main.c = module$foo$bar.a * module$foo$bar$index.b;"));
  }

  @Test
  public void handlesRequireNodeCoreModule() {
    CompilerUtil compiler = createCompiler(path("foo/module.js"));

    compiler.compile(
        createSourceFile(path("foo/module.js"), "var p = require('path');", "p.join('a', 'b');"));

    assertThat(compiler.toSource().trim())
        .contains(
            lines("var module$foo$module = {};", "$jscomp.scope.path_module.join(\"a\", \"b\");"));
  }

  @Test
  public void canReferToClassesExportedByNodeCoreModule() {
    CompilerUtil compiler = createCompiler(path("foo/module.js"));

    compiler.compile(
        createSourceFile(
            path("foo/module.js"),
            "var stream = require('stream');",
            "",
            "/** @type {!stream.Stream} */",
            "exports.s = new stream.Stream;"));

    assertThat(compiler.toSource().trim())
        .contains(
            lines(
                "var module$foo$module = {};",
                "module$foo$module.s = new $jscomp.scope.stream_module.Stream;"));
  }

  @Test
  public void leavesRequireStatementsForUnrecognizedModuleIds() {
    CompilerUtil compiler = createCompiler(path("foo/module.js"));

    compiler.compile(
        createSourceFile(path("foo/module.js"), "var foo = require('foo');", "foo.doSomething();"));

    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$module = {};",
                "$jscomp.scope.foo = require(\"foo\");",
                "$jscomp.scope.foo.doSomething();"));
    assertIsNodeModule("module$foo$module", "foo/module.js");
  }

  @Test
  public void rewritesRequireStatementToDirectlyReferenceExportsObject_compoundStatement() {
    CompilerUtil compiler = createCompiler(path("foo/leaf.js"), path("foo/root.js"));

    compiler.compile(
        createSourceFile(path("foo/root.js"), ""),
        createSourceFile(
            path("foo/leaf.js"),
            "var foo = require('./root'),",
            "    bar = require('./root').bar;",
            "foo.bar(foo);"));

    assertThat(compiler.toSource())
        .contains(
            lines(
                "var module$foo$root = {};",
                "var module$foo$leaf = {};",
                "module$foo$root.bar(module$foo$root);"));
  }

  @Test
  public void handlesRequiringModulesFromASubDirectory() {
    CompilerUtil compiler = createCompiler(path("foo/one.js"), path("foo/bar/two.js"));

    compiler.compile(
        createSourceFile(path("foo/one.js"), "require('./bar/two');"),
        createSourceFile(path("foo/bar/two.js"), ""));

    assertThat(compiler.toSource())
        .contains(lines("var module$foo$bar$two = {};", "var module$foo$one = {};"));
  }

  @Test
  public void handlesRequiringModulesFromAParentDirectory() {
    CompilerUtil compiler = createCompiler(path("foo/one.js"), path("foo/bar/two.js"));

    compiler.compile(
        createSourceFile(path("foo/one.js"), ""),
        createSourceFile(path("foo/bar/two.js"), "require('../one');"));

    assertThat(compiler.toSource())
        .contains(lines("var module$foo$one = {};", "var module$foo$bar$two = {};"));
  }

  @Test
  public void handlesRequiringModulesFromAParentsSibling() {
    CompilerUtil compiler = createCompiler(path("foo/baz/one.js"), path("foo/bar/two.js"));

    compiler.compile(
        createSourceFile(path("foo/baz/one.js"), ""),
        createSourceFile(path("foo/bar/two.js"), "require('../baz/one');"));

    assertThat(compiler.toSource())
        .contains(lines("var module$foo$baz$one = {};", "var module$foo$bar$two = {};"));
  }

  @Test
  public void handlesRequiringAbsoluteModule() {
    CompilerUtil compiler =
        createCompiler(path("/absolute/foo/baz/one.js"), path("foo/bar/two.js"));

    compiler.compile(
        createSourceFile(path("/absolute/foo/baz/one.js"), ""),
        createSourceFile(path("foo/bar/two.js"), "require('/absolute/foo/baz/one');"));

    assertThat(compiler.toSource())
        .contains(lines("var module$$absolute$foo$baz$one = {};", "var module$foo$bar$two = {};"));
  }

  @Test
  public void nonGlobalRequireCallsAreNotRegisteredAsInputRequirements() {
    CompilerUtil compiler =
        createCompiler(path("foo/one.js"), path("foo/two.js"), path("foo/three.js"));

    compiler.compile(
        createSourceFile(path("foo/one.js"), "var x = require('./two');", "x.go();"),
        createSourceFile(
            path("foo/two.js"),
            "var go = function() {",
            "  var x = require('./three');",
            "};",
            "exports.go = go;"),
        createSourceFile(path("foo/three.js"), "var x = require('./one');"));

    assertThat(compiler.toSource())
        .contains(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$two = {};",
                "$jscomp.scope.go = function() {",
                "  var x = module$foo$three;",
                "};",
                "module$foo$two.go = $jscomp.scope.go;",
                "var module$foo$one = {};",
                "$jscomp.scope.go();",
                "var module$foo$three = {};"));
  }

  @Test
  public void maintainsInternalTypeCheckingConsistency() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(
        path("foo/bar.js"),
        "/** @constructor */",
        "var Bar = function() {};",
        "",
        "/** @constructor */",
        "Bar.Baz = function() {};",
        "",
        "/** @type {!Bar} */",
        "var x = new Bar();",
        "",
        "/** @type {!Bar.Baz} */",
        "var y = new Bar.Baz();",
        "");

    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar = {};",
                "$jscomp.scope.Bar = function() {",
                "};",
                "$jscomp.scope.Bar.Baz = function() {",
                "};",
                "$jscomp.scope.x = new $jscomp.scope.Bar;",
                "$jscomp.scope.y = new $jscomp.scope.Bar.Baz;"));
  }

  @Test
  public void canReferenceInternalTypes() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(
        path("foo/bar.js"),
        "/** @constructor */",
        "var One = function() {};",
        "",
        "/**",
        " * @constructor",
        " * @extends {One}",
        " */",
        "exports.Two = function() {};",
        // Assignment tests.
        "/** @type {!One} */",
        "var testOne = new One();",
        "/** @type {!One} */",
        "var testTwo = new exports.Two();",
        "");
    // OK if compiles without error.
  }

  @Test
  public void canReferenceTypesDefinedOnOwnModuleExports() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(
        path("foo/bar.js"),
        "/** @constructor */",
        "var One = function() {};",
        "",
        "/**",
        " * @constructor",
        " * @extends {One}",
        " */",
        "exports.Two = function() {};",
        "",
        "/**",
        " * @param {!exports.Two} p .",
        " * @constructor",
        " */",
        "exports.Three = function(p) {};",
        "");
    // OK if compiles without error.
  }

  @Test
  public void canReferenceRequiredModuleTypesUsingImportAlias() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"), path("foo/baz.js"));

    compiler.compile(
        createSourceFile(path("foo/bar.js"), "/** @constructor */", "exports.Bar = function(){};"),
        createSourceFile(
            path("foo/baz.js"),
            "var bar = require('./bar');",
            "var bBar = bar.Bar;",
            "var Bar = require('./bar').Bar;",
            "",
            "/** @type {!bar.Bar} */",
            "var one = new bar.Bar();",
            "",
            "/** @type {!bar.Bar} */",
            "var two = new bBar;",
            "",
            "/** @type {!Bar} */",
            "var three = new Bar();",
            "",
            "/** @type {!Bar} */",
            "var four = new bar.Bar;",
            ""));
    // OK if compiles without error.
  }

  @Test
  public void canReferenceCastedTypeThroughModuleImportAlias() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"), path("foo/baz.js"));

    compiler.compile(
        createSourceFile(
            path("index.js"),
            "/**",
            " * @param {number} a .",
            " * @constructor",
            " */",
            "function NotACommonJsModuleCtor(a) {};"),
        createSourceFile(
            path("foo/bar.js"),
            "/** @constructor */",
            "exports.NotACommonJsModuleCtor = NotACommonJsModuleCtor;",
            "/** @constructor */",
            "exports.Bar = NotACommonJsModuleCtor;"),
        createSourceFile(
            path("foo/baz.js"),
            "var bar = require('./bar');",
            "",
            "/** @type {!bar.NotACommonJsModuleCtor} */",
            "var one = new bar.NotACommonJsModuleCtor(1);",
            "",
            "/** @type {!bar.Bar} */",
            "var two = new bar.Bar(2);",
            ""));
    // OK if compiles without error.
  }

  @Test
  public void canReferenceTypeExportedAsAlias() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"), path("foo/baz.js"));

    compiler.compile(
        createSourceFile(
            path("foo/bar.js"),
            "/**",
            " * @param {number} a .",
            " * @constructor",
            " */",
            "var Greeter = function(a) {};",
            "",
            "/** @constructor */",
            "exports.Bar = Greeter;"),
        createSourceFile(
            path("foo/baz.js"),
            "var bar = require('./bar');",
            "",
            "/** @type {!bar.Bar} */",
            "var b = new bar.Bar(1);",
            ""));
    // OK if compiles without error.
  }

  @Test
  public void exportedInternalVarInheritsJsDocInfo() {
    CompilerUtil compiler = createCompiler(path("foo.js"));

    compiler.compile(
        createSourceFile(
            path("foo.js"),
            "/**",
            " * @constructor",
            " */",
            "var Greeter = function(){};",
            "/**",
            " * @param {string} name .",
            " * @return {string} .",
            " */",
            "Greeter.prototype.sayHi = function(name) {",
            "  return 'Hello, ' + name;",
            "};",
            "",
            "exports.Greeter = Greeter"));

    JSType exportedGreeter =
        compiler
            .getCompiler()
            .getTopScope()
            .getVar("module$foo")
            .getType()
            .findPropertyType("Greeter");
    assertTrue(exportedGreeter.isConstructor());
  }

  @Test
  public void savesOriginalTypeNameInJsDoc() {
    CompilerUtil compiler = createCompiler(path("foo.js"));

    compiler.compile(
        createSourceFile(
            path("foo.js"),
            "/** @constructor */",
            "var Builder = function(){};",
            "/** @return {!Builder} . */",
            "Builder.prototype.returnThis = function() { return this; };",
            "exports.Builder = Builder"));

    Scope scope = compiler.getCompiler().getTopScope();
    Var var = scope.getVar("module$foo");
    JSType type = var.getInitialValue().getJSType().findPropertyType("Builder");
    assertTrue(type.isConstructor());

    type = type.toObjectType().getTypeOfThis();
    assertEquals("$jscomp.scope.Builder", type.toString());

    type = type.toObjectType().getPropertyType("returnThis");
    assertTrue(type.toString(), type.isFunctionType());

    JSDocInfo info = type.getJSDocInfo();
    assertNotNull(info);

    Node node = getOnlyElement(info.getTypeNodes());
    assertEquals(Token.BANG, node.getType());

    node = node.getFirstChild();
    assertTrue(node.isString());
    assertEquals("$jscomp.scope.Builder", node.getString());
    assertEquals("Builder", node.getProp(Node.ORIGINALNAME_PROP));
  }

  @Test
  public void canReferenceConstructorExportedByAnotherModule() {
    CompilerUtil compiler = createCompiler(path("x/foo.js"), path("x/bar.js"));

    compiler.compile(
        createSourceFile(path("x/foo.js"), "/** @constructor */", "exports.Foo = function(){};"),
        createSourceFile(
            path("x/bar.js"),
            "var foo = require('./foo');",
            "/** @type {function(new: foo.Foo)} */",
            "exports.Foo = foo.Foo;"));

    Scope scope = compiler.getCompiler().getTopScope();
    Var var = scope.getVar("module$x$bar");

    JSType type = var.getInitialValue().getJSType().findPropertyType("Foo");
    assertTrue(type.isConstructor());

    type = type.toObjectType().getTypeOfThis();
    assertEquals("module$x$foo.Foo", type.toString());
  }

  @Test
  public void canReferenceConstructorDefinedInTheGlobalScope() {
    CompilerUtil compiler = createCompiler(path("x/bar.js"));

    compiler.compile(
        createSourceFile(path("x/foo.js"), "/** @constructor */", "function Foo() {}"),
        createSourceFile(
            path("x/bar.js"), "/** @type {function(new: Foo)} */", "exports.Foo = Foo;"));

    Scope scope = compiler.getCompiler().getTopScope();
    Var var = scope.getVar("module$x$bar");

    JSType type = var.getInitialValue().getJSType().findPropertyType("Foo");
    assertTrue(type.isConstructor());

    type = type.toObjectType().getTypeOfThis();
    assertEquals("Foo", type.toString());
  }

  @Test
  public void canUseModuleInternalTypedefsInJsDoc() {
    CompilerUtil compiler = createCompiler(path("foo.js"));

    compiler.compile(
        createSourceFile(
            path("foo.js"),
            "/** @typedef {{x: number}} */",
            "var Variable;",
            "",
            "/**",
            " * @param {Variable} a .",
            " * @param {Variable} b .",
            " * @return {Variable} .",
            " */",
            "exports.add = function(a, b) {",
            "  return {x: a.x + b.x};",
            "};"));

    Scope scope = compiler.getCompiler().getTopScope();
    Var var = scope.getVar("module$foo");
    JSType type = var.getInitialValue().getJSType().toObjectType().getPropertyType("add");
    assertTrue(type.isFunctionType());

    JSDocInfo info = type.getJSDocInfo();
    Node node = info.getTypeNodes().iterator().next();
    assertTrue(node.isString());
    assertEquals("$jscomp.scope.Variable", node.getString());
    assertEquals("Variable", node.getProp(Node.ORIGINALNAME_PROP));
  }

  @Test
  public void renamesExportsWhenUsedAsParameter() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(createSourceFile(path("foo/bar.js"), "function go(e) {}", "go(exports);"));

    assertThat(compiler.toSource())
        .contains(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar = {};",
                "$jscomp.scope.go = function(e) {",
                "};",
                "(0,$jscomp.scope.go)(module$foo$bar);"));
  }

  @Test
  public void rewritesCompoundVarDeclarations() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"));

    compiler.compile(createSourceFile(path("foo/bar.js"), "var x = 1,", "    y = 2;"));

    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar = {};",
                "$jscomp.scope.x = 1;",
                "$jscomp.scope.y = 2;"));
  }

  @Test
  public void canReferenceExportedTypeReferences() {
    CompilerUtil compiler = createCompiler(path("foo/bar.js"), path("foo/baz.js"));

    compiler.compile(
        createSourceFile(path("foo/bar.js"), "/** @constructor */", "exports.foo = function(){};"),
        createSourceFile(
            path("foo/baz.js"),
            "var bar = require('./bar');",
            "var foo = require('./bar').foo;",
            "/** @type {!bar.foo} */",
            "var a = new bar.foo();",
            "/** @type {!foo} */",
            "var b = new foo();"));

    assertThat(compiler.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$foo$bar = {};",
                "module$foo$bar.foo = function() {",
                "};",
                "var module$foo$baz = {};",
                "$jscomp.scope.a = new module$foo$bar.foo;",
                "$jscomp.scope.b = new module$foo$bar.foo;"));

    ObjectType scope =
        compiler
            .getCompiler()
            .getTopScope()
            .getVar("$jscomp")
            .getType()
            .findPropertyType("scope")
            .toObjectType();

    assertEquals("module$foo$bar.foo", scope.getPropertyType("a").getDisplayName());
    assertEquals("module$foo$bar.foo", scope.getPropertyType("b").getDisplayName());
  }

  @Test
  public void doesNotNpeOnScriptThatAccessesPropertyOfReturnValue() {
    CompilerUtil util = createCompiler(path("foo/bar.js"));

    util.compile(
        path("foo/bar.js"),
        "function createCallback() {",
        " return function(y) { this.doIt().x = y; };",
        "}");
  }

  @Test
  public void multipleModuleExportAssignmentsAreNotPermitted() {
    CompilerUtil util = createCompiler(path("module.js"));

    try {
      util.compile(path("module.js"), "module.exports = 1;", "module.exports = 2;");
      fail();
    } catch (CompilerUtil.CompileFailureException expected) {
      assertThat(expected.getMessage())
          .contains("Multiple assignments to module.exports are not permitted");
    }
  }

  @Test
  public void requireAnEmptyModuleIdIsNotPermitted() {
    CompilerUtil util = createCompiler(path("module.js"));

    try {
      util.compile(path("module.js"), "require('');");
      fail();
    } catch (CompilerUtil.CompileFailureException expected) {
      assertThat(expected.getMessage()).contains("Invalid module ID passed to require()");
    }
  }

  @Test
  public void rewritesModuleExportAssignments() {
    CompilerUtil util = createCompiler(path("module.js"));

    util.compile(path("module.js"), "module.exports = 1234;");

    assertThat(util.toSource()).contains("var module$module = 1234;");
  }

  @Test
  public void ignoresLocalModuleExportReferences() {
    CompilerUtil util = createCompiler(path("module.js"));

    util.compile(
        path("module.js"),
        "function x() {",
        "  var module = {};",
        "  module.exports = 1234;",
        "}",
        "module.exports = {};");

    assertThat(util.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "$jscomp.scope.x = function() {",
                "  var module = {};",
                "  module.exports = 1234;",
                "};",
                "var module$module = {};"));
  }

  @Test
  public void ignoresModuleVarReferencesWhenDefinedAsTopLevelOfModule() {
    CompilerUtil util = createCompiler(path("module.js"));

    util.compile(
        path("module.js"),
        "var module = {};",
        "module.exports = {};",
        "module.exports.x = function() {};");

    assertThat(util.toSource())
        .startsWith(
            lines(
                "var $jscomp = {};",
                "$jscomp.scope = {};",
                "var module$module = {};",
                "$jscomp.scope.module = {};",
                "$jscomp.scope.module.exports = {};",
                "$jscomp.scope.module.exports.x = function() {",
                "};"));
  }

  @Test
  public void canAssignAdditionalPropertiesToModuleExports() {
    CompilerUtil util = createCompiler(path("module.js"));

    util.compile(path("module.js"), "module.exports = {};", "module.exports.x = function() {};");

    assertThat(util.toSource())
        .contains(lines("var module$module = {};", "module$module.x = function() {", "};"));
  }

  @Test
  public void rewritesModuleExportReferences() {
    CompilerUtil util = createCompiler(path("module.js"));

    util.compile(path("module.js"), "module.exports.x = function() {};");

    assertThat(util.toSource())
        .contains(lines("var module$module = {};", "module$module.x = function() {", "};"));
  }

  @Test
  public void resolveModuleTypeReference_nonRelativePath() {
    try {
      resolveModuleTypeReference(path("context.js"), "/abs/path");
      fail();
    } catch (IllegalArgumentException expected) {
      // Do nothing.
    }
  }

  @Test
  public void testResolveModuleTypeReference_pathResolvesToModuleDirectly() throws IOException {
    Path ref = path("a/b/c.js");
    Path file = ref.resolveSibling("d/e.js");
    createDirectories(file.getParent());
    createFile(file);

    assertThat(resolveModuleTypeReference(ref, "./d/e")).isEqualTo(getModuleId(file));
    assertThat(resolveModuleTypeReference(ref, "./d/../d/e")).isEqualTo(getModuleId(file));
    assertThat(resolveModuleTypeReference(ref, "../b/d/e")).isEqualTo(getModuleId(file));
  }

  @Test
  public void testResolveModuleTypeReference_pathResolvesToModuleWithIndex() throws IOException {
    Path ref = path("a/b/c.js");
    Path dir = ref.resolveSibling("d/e");
    Path file = dir.resolve("index.js");
    createDirectories(dir);
    createFile(file);

    assertThat(resolveModuleTypeReference(ref, "./d/e")).isEqualTo(getModuleId(file));
    assertThat(resolveModuleTypeReference(ref, "./d/../d/e")).isEqualTo(getModuleId(file));
    assertThat(resolveModuleTypeReference(ref, "../b/d/e")).isEqualTo(getModuleId(file));

    assertThat(resolveModuleTypeReference(ref, "./d/e/index")).isEqualTo(getModuleId(file));
    assertThat(resolveModuleTypeReference(ref, "./d/../d/e/index")).isEqualTo(getModuleId(file));
    assertThat(resolveModuleTypeReference(ref, "../b/d/e/index")).isEqualTo(getModuleId(file));
  }

  @Test
  public void testResolveModuleTypeReference_pathResolvesToExportedType() throws IOException {
    Path ref = path("a/b/c.js");
    Path dir = ref.resolveSibling("d/e");
    createDirectories(dir);

    Path indexFile = dir.resolve("index.js");
    createFile(indexFile);

    Path otherFile = dir.resolve("foo.bar.js");
    createFile(otherFile);

    assertThat(resolveModuleTypeReference(ref, "./d/e.Foo"))
        .isEqualTo(getModuleId(indexFile) + ".Foo");
    assertThat(resolveModuleTypeReference(ref, "./d/../d/e.Foo"))
        .isEqualTo(getModuleId(indexFile) + ".Foo");
    assertThat(resolveModuleTypeReference(ref, "../b/d/e.Foo"))
        .isEqualTo(getModuleId(indexFile) + ".Foo");
    assertThat(resolveModuleTypeReference(ref, "./d/e.Foo.Bar"))
        .isEqualTo(getModuleId(indexFile) + ".Foo.Bar");

    assertThat(resolveModuleTypeReference(ref, "./d/e/index.Foo"))
        .isEqualTo(getModuleId(indexFile) + ".Foo");
    assertThat(resolveModuleTypeReference(ref, "./d/../d/e/index.Foo"))
        .isEqualTo(getModuleId(indexFile) + ".Foo");
    assertThat(resolveModuleTypeReference(ref, "../b/d/e/index.Foo"))
        .isEqualTo(getModuleId(indexFile) + ".Foo");
    assertThat(resolveModuleTypeReference(ref, "./d/e/index.Foo.Bar"))
        .isEqualTo(getModuleId(indexFile) + ".Foo.Bar");

    assertThat(resolveModuleTypeReference(ref, "./d/e/foo.bar.Baz"))
        .isEqualTo(getModuleId(otherFile) + ".Baz");
    assertThat(resolveModuleTypeReference(ref, "./d/../d/e/foo.bar.Baz"))
        .isEqualTo(getModuleId(otherFile) + ".Baz");
    assertThat(resolveModuleTypeReference(ref, "../b/d/e/foo.bar.Baz"))
        .isEqualTo(getModuleId(otherFile) + ".Baz");
  }

  @Test
  public void testResolveModuleTypeReference_pathDoesNotREsolve() throws IOException {
    Path ref = path("a/b/c.js");
    assertThat(resolveModuleTypeReference(ref, "./d/e")).isEqualTo("./d/e");
  }

  @Test
  public void handlesReferencesToOtherModulesTypesEvenIfNotExplicitlyRequired() throws IOException {
    Path root = path("root.js");
    Path bar = path("foo/bar.js");
    Path baz = path("foo/baz.js");

    createFile(root);
    createDirectories(bar.getParent());
    createFile(bar);
    createFile(baz);

    CompilerUtil compiler = createCompiler(root, bar, baz);
    compiler.compile(
        createSourceFile(root, "/** @param {!./foo/bar.Person} p . */", "function inRoot(p) {}"),
        createSourceFile(bar, "/** @constructor */", "exports.Person = function(){};"),
        createSourceFile(baz, "/** @param {!./bar.Person} p . */", "function inBaz(p) {}"));
  }

  @Test
  public void processesExternModules() throws IOException {
    Path extern = path("root/externs/xml.js");
    Path module = path("root/source/foo.js");

    createDirectories(extern.getParent());
    write(
        extern,
        lines(
                "/** @const */",
                "var xml = {};",
                "/** @param {string} str",
                " *  @return {!Object}",
                " */",
                "xml.parse = function(str) {};",
                "module.exports = xml;")
            .getBytes(StandardCharsets.UTF_8));

    CompilerUtil compiler = createCompiler(ImmutableSet.of(extern), ImmutableSet.of(module));
    compiler.compile(module, "var xml = require('xml');", "xml.parse('abc');");

    assertThat(compiler.toSource())
        .contains(
            lines(
                "var xml = $jscomp.scope.xml_module;",
                "var module$root$source$foo = {};",
                "$jscomp.scope.xml_module.parse(\"abc\");"));
  }

  private void assertIsNodeModule(String id, String path) {
    Module module = typeRegistry.getModule(id);
    assertThat(module.getPath().toString()).isEqualTo(path);
    assertThat(module.getType()).isEqualTo(Module.Type.NODE);
  }

  private CompilerUtil createCompiler(final Path... modules) {
    return createCompiler(ImmutableSet.<Path>of(), ImmutableSet.copyOf(modules));
  }

  private CompilerUtil createCompiler(ImmutableSet<Path> externs, ImmutableSet<Path> modules) {
    for (Path path : concat(externs, modules)) {
      Path parent = path.getParent();
      if (parent != null) {
        try {
          Files.createDirectories(parent);
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    }
    Injector injector =
        GuiceRule.builder(new Object())
            .setInputFs(fs)
            .setModuleExterns(externs)
            .setModules(modules)
            .build()
            .createInjector();
    injector.injectMembers(this);
    CompilerUtil util = injector.getInstance(CompilerUtil.class);
    util.getOptions().setCheckTypes(true);
    return util;
  }

  private Path path(String first, String... remaining) {
    return fs.getPath(first, remaining);
  }

  private static String lines(String... lines) {
    return Joiner.on('\n').join(lines);
  }
}