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