@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); } }