@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 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 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 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 doesNotModifySourceIfFileIsNotACommonJsModule() {
    CompilerUtil compiler = createCompiler();

    compiler.compile(path("foo/bar.js"), "var x = 123;");
    assertEquals("var x = 123;", compiler.toSource().trim());
  }
  @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\");"));
  }
  @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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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());
  }