diff --git a/javascript/ql/src/NodeJS/InvalidExport.ql b/javascript/ql/src/NodeJS/InvalidExport.ql index df9a9b3387e..90a7e04c4d3 100644 --- a/javascript/ql/src/NodeJS/InvalidExport.ql +++ b/javascript/ql/src/NodeJS/InvalidExport.ql @@ -46,5 +46,7 @@ where moduleExportsAssign(_, exportsVal) and // however, if there are no further uses of `exports` the assignment is useless anyway strictcount(exportsVar.getAnAccess()) > 1 - ) + ) and + // export assignments do work in closure modules + not assgn.getTopLevel() instanceof ClosureModule select assgn, "Assigning to 'exports' does not export anything." diff --git a/javascript/ql/src/semmle/javascript/Closure.qll b/javascript/ql/src/semmle/javascript/Closure.qll index 616e7746d7c..503197c52c9 100644 --- a/javascript/ql/src/semmle/javascript/Closure.qll +++ b/javascript/ql/src/semmle/javascript/Closure.qll @@ -37,39 +37,162 @@ class GoogFunctionCallStmt extends ExprStmt { Expr getAnArgument() { result = getArgument(_) } } +private abstract class GoogNamespaceRef extends ExprOrStmt { + abstract string getNamespaceId(); +} + /** * A call to `goog.provide`. */ -class GoogProvide extends GoogFunctionCallStmt { +class GoogProvide extends GoogFunctionCallStmt, GoogNamespaceRef { GoogProvide() { getFunctionName() = "provide" } /** Gets the identifier of the namespace created by this call. */ - string getNamespaceId() { result = getArgument(0).(ConstantString).getStringValue() } + override string getNamespaceId() { result = getArgument(0).(ConstantString).getStringValue() } } /** * A call to `goog.require`. */ -class GoogRequire extends GoogFunctionCallStmt { +class GoogRequire extends GoogFunctionCall, GoogNamespaceRef { GoogRequire() { getFunctionName() = "require" } /** Gets the identifier of the namespace imported by this call. */ - string getNamespaceId() { result = getArgument(0).(ConstantString).getStringValue() } + override string getNamespaceId() { result = getArgument(0).(ConstantString).getStringValue() } +} + +private class GoogRequireImport extends GoogRequire, Import { + /** Gets the module in which this import appears. */ + override Module getEnclosingModule() { result = getTopLevel() } + + /** Gets the (unresolved) path that this import refers to. */ + override PathExpr getImportedPath() { + result = getArgument(0) + } } /** - * A Closure module, that is, a toplevel that contains a call to `goog.provide` or - * `goog.require`. + * A call to `goog.module` or `goog.declareModuleId`. */ -class ClosureModule extends TopLevel { +class GoogModuleDeclaration extends GoogFunctionCallStmt, GoogNamespaceRef { + GoogModuleDeclaration() { + getFunctionName() = "module" or + getFunctionName() = "declareModuleId" + } + + /** Gets the identifier of the namespace imported by this call. */ + override string getNamespaceId() { result = getArgument(0).(ConstantString).getStringValue() } +} + +/** + * A module using the Closure module system, declared using `goog.module()` or `goog.declareModuleId()`. + */ +class ClosureModule extends Module { ClosureModule() { + getAChildStmt() instanceof GoogModuleDeclaration + } + + /** + * Gets the call to `goog.module()` or `goog.declareModuleId` in this module. + */ + GoogModuleDeclaration getModuleDeclaration() { + result = getAChildStmt() + } + + /** + * Gets the namespace of this module. + */ + string getNamespaceId() { result = getModuleDeclaration().getNamespaceId() } + + override Module getAnImportedModule() { + exists (GoogRequireImport imprt | + imprt.getEnclosingModule() = this and + result.(ClosureModule).getNamespaceId() = imprt.getNamespaceId() + ) + } + + /** + * Gets the top-level `exports` variable in this module, if this module is defined by + * a `good.module` call. + * + * This variable denotes the object exported from this module. + * + * Has no result for ES6 modules using `goog.declareModuleId`. + */ + Variable getExportsVariable() { + getModuleDeclaration().getFunctionName() = "module" and + result.getScope() = this.getScope() and + result.getName() = "exports" + } + + override predicate exports(string name, ASTNode export) { + // exports.foo = bar + export.(AssignExpr).getLhs().(PropAccess).accesses(getExportsVariable().getAnAccess(), name) + or + // exports = { foo: bar } + exists (VarDef def | + def.getTarget() = getExportsVariable().getAReference() and + def.getSource().(ObjectExpr).getPropertyByName(name) = export + ) + } +} + +/** + * A global Closure script, that is, a toplevel that is executed in the global scope and + * contains a toplevel call to `goog.provide` or `goog.require`. + */ +class ClosureScript extends TopLevel { + ClosureScript() { + not this instanceof ClosureModule and getAChildStmt() instanceof GoogProvide or - getAChildStmt() instanceof GoogRequire + getAChildStmt().(ExprStmt).getExpr() instanceof GoogRequire } /** Gets the identifier of a namespace required by this module. */ - string getARequiredNamespace() { result = getAChildStmt().(GoogRequire).getNamespaceId() } + string getARequiredNamespace() { result = getAChildStmt().(ExprStmt).getExpr().(GoogRequire).getNamespaceId() } /** Gets the identifer of a namespace provided by this module. */ string getAProvidedNamespace() { result = getAChildStmt().(GoogProvide).getNamespaceId() } } + +/** + * Holds if `name` is a closure namespace, including proper namespace prefixes. + */ +pragma[noinline] +predicate isClosureLibraryNamespacePath(string name) { + exists (string namespace | namespace = any(GoogNamespaceRef provide).getNamespaceId() | + name = namespace.substring(0, namespace.indexOf(".")) + or + name = namespace + ) +} + +/** + * Gets the closure namespace path addressed by the given dataflow node, if any. + */ +string getClosureLibraryAccessPath(DataFlow::SourceNode node) { + isClosureLibraryNamespacePath(result) and + node = DataFlow::globalVarRef(result) + or + isClosureLibraryNamespacePath(result) and + exists (DataFlow::PropRead read | node = read | + result = getClosureLibraryAccessPath(read.getBase().getALocalSource()) + "." + read.getPropertyName() + ) + or + // Associate an access path with the immediate RHS of a store on a closure namespace. + // This is to support patterns like: + // foo.bar = { baz() {} } + exists (DataFlow::PropWrite write | + node = write.getRhs() and + result = getWrittenClosureLibraryAccessPath(write) + ) + or + result = node.asExpr().(GoogRequire).getNamespaceId() +} + +/** + * Gets the closure namespace path written to by the given property write, if any. + */ +string getWrittenClosureLibraryAccessPath(DataFlow::PropWrite node) { + result = getClosureLibraryAccessPath(node.getBase().getALocalSource()) + "." + node.getPropertyName() +} diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll index 7f8a2325ee2..f771228c418 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll @@ -5,6 +5,7 @@ */ import javascript +private import semmle.javascript.Closure private import AbstractValuesImpl private import semmle.javascript.dataflow.InferredTypes private import AbstractPropertiesImpl @@ -332,3 +333,50 @@ private class AnalyzedExportAssign extends AnalyzedPropertyWrite, DataFlow::Valu source = this } } + +/** + * Flow analysis for assignments to the `exports` variable in a Closure module. + */ +private class AnalyzedClosureExportAssign extends AnalyzedPropertyWrite, DataFlow::ValueNode { + ClosureModule mod; + + AnalyzedClosureExportAssign() { + astNode.(AssignExpr).getLhs() = mod.getExportsVariable().getAReference() + } + + override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) { + baseVal = TAbstractModuleObject(astNode.getTopLevel()) and + propName = "exports" and + source = astNode.(AssignExpr).getRhs().flow() + } +} + +/** + * Read of a global access path exported by a Closure library. + * + * This adds a direct flow edge to the assigned value. + */ +private class AnalyzedClosureGlobalAccessPath extends AnalyzedNode, AnalyzedPropertyRead { + string accessPath; + + AnalyzedClosureGlobalAccessPath() { + accessPath = getClosureLibraryAccessPath(this) + } + + override AnalyzedNode localFlowPred() { + exists (DataFlow::PropWrite write | + getWrittenClosureLibraryAccessPath(write) = accessPath and + result = write.getRhs() + ) + or + result = AnalyzedNode.super.localFlowPred() + } + + override predicate reads(AbstractValue base, string propName) { + exists (ClosureModule mod | + mod.getNamespaceId() = accessPath and + base = TAbstractModuleObject(mod) and + propName = "exports" + ) + } +} diff --git a/javascript/ql/test/library-tests/Closure/CallGraph.expected b/javascript/ql/test/library-tests/Closure/CallGraph.expected new file mode 100644 index 00000000000..8726f479895 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/CallGraph.expected @@ -0,0 +1,22 @@ +| tests/importFromEs6.js:9:1:9:15 | es6Module.fun() | tests/es6Module.js:3:8:3:24 | function fun() {} | +| tests/importFromEs6.js:10:1:10:18 | es6ModuleDefault() | tests/es6ModuleDefault.js:3:16:3:28 | function() {} | +| tests/importFromEs6.js:12:1:12:16 | googModule.fun() | tests/googModule.js:4:6:4:10 | () {} | +| tests/importFromEs6.js:13:1:13:19 | googModuleDefault() | tests/googModuleDefault.js:3:11:3:27 | function fun() {} | +| tests/requireFromEs6.js:12:1:12:18 | globalModule.fun() | tests/globalModule.js:4:6:4:10 | () {} | +| tests/requireFromEs6.js:13:1:13:21 | globalM ... fault() | tests/globalModuleDefault.js:3:23:3:39 | function fun() {} | +| tests/requireFromEs6.js:15:1:15:15 | es6Module.fun() | tests/es6Module.js:3:8:3:24 | function fun() {} | +| tests/requireFromEs6.js:16:1:16:18 | es6ModuleDefault() | tests/es6ModuleDefault.js:3:16:3:28 | function() {} | +| tests/requireFromEs6.js:18:1:18:16 | googModule.fun() | tests/googModule.js:4:6:4:10 | () {} | +| tests/requireFromEs6.js:19:1:19:19 | googModuleDefault() | tests/googModuleDefault.js:3:11:3:27 | function fun() {} | +| tests/requireFromGlobalModule.js:10:1:10:18 | x.y.z.global.fun() | tests/globalModule.js:4:6:4:10 | () {} | +| tests/requireFromGlobalModule.js:11:1:11:21 | x.y.z.g ... fault() | tests/globalModuleDefault.js:3:23:3:39 | function fun() {} | +| tests/requireFromGlobalModule.js:13:1:13:16 | x.y.z.goog.fun() | tests/googModule.js:4:6:4:10 | () {} | +| tests/requireFromGlobalModule.js:14:1:14:19 | x.y.z.googdefault() | tests/googModuleDefault.js:3:11:3:27 | function fun() {} | +| tests/requireFromGlobalModule.js:16:1:16:15 | x.y.z.es6.fun() | tests/es6Module.js:3:8:3:24 | function fun() {} | +| tests/requireFromGlobalModule.js:17:1:17:18 | x.y.z.es6default() | tests/es6ModuleDefault.js:3:16:3:28 | function() {} | +| tests/requireFromGoogModule.js:12:1:12:18 | globalModule.fun() | tests/globalModule.js:4:6:4:10 | () {} | +| tests/requireFromGoogModule.js:13:1:13:21 | globalM ... fault() | tests/globalModuleDefault.js:3:23:3:39 | function fun() {} | +| tests/requireFromGoogModule.js:15:1:15:15 | es6Module.fun() | tests/es6Module.js:3:8:3:24 | function fun() {} | +| tests/requireFromGoogModule.js:16:1:16:18 | es6ModuleDefault() | tests/es6ModuleDefault.js:3:16:3:28 | function() {} | +| tests/requireFromGoogModule.js:18:1:18:16 | googModule.fun() | tests/googModule.js:4:6:4:10 | () {} | +| tests/requireFromGoogModule.js:19:1:19:19 | googModuleDefault() | tests/googModuleDefault.js:3:11:3:27 | function fun() {} | diff --git a/javascript/ql/test/library-tests/Closure/CallGraph.ql b/javascript/ql/test/library-tests/Closure/CallGraph.ql new file mode 100644 index 00000000000..860789e8006 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/CallGraph.ql @@ -0,0 +1,4 @@ +import javascript + +from DataFlow::InvokeNode node +select node, node.getACallee() diff --git a/javascript/ql/test/library-tests/Closure/ClosureModule.expected b/javascript/ql/test/library-tests/Closure/ClosureModule.expected deleted file mode 100644 index bcef7c4b344..00000000000 --- a/javascript/ql/test/library-tests/Closure/ClosureModule.expected +++ /dev/null @@ -1,2 +0,0 @@ -| a.js:1:1:5:1 | | -| b.js:1:1:3:21 | | diff --git a/javascript/ql/test/library-tests/Closure/ClosureModule.ql b/javascript/ql/test/library-tests/Closure/ClosureModule.ql deleted file mode 100644 index c13884e72eb..00000000000 --- a/javascript/ql/test/library-tests/Closure/ClosureModule.ql +++ /dev/null @@ -1,4 +0,0 @@ -import semmle.javascript.Closure - -from ClosureModule cm -select cm diff --git a/javascript/ql/test/library-tests/Closure/ClosureModule_getAProvidedNamespace.expected b/javascript/ql/test/library-tests/Closure/ClosureModule_getAProvidedNamespace.expected deleted file mode 100644 index b00fa691b2a..00000000000 --- a/javascript/ql/test/library-tests/Closure/ClosureModule_getAProvidedNamespace.expected +++ /dev/null @@ -1 +0,0 @@ -| a.js:1:1:5:1 | | a | diff --git a/javascript/ql/test/library-tests/Closure/ClosureModule_getAProvidedNamespace.ql b/javascript/ql/test/library-tests/Closure/ClosureModule_getAProvidedNamespace.ql deleted file mode 100644 index 23e7b3831b3..00000000000 --- a/javascript/ql/test/library-tests/Closure/ClosureModule_getAProvidedNamespace.ql +++ /dev/null @@ -1,4 +0,0 @@ -import semmle.javascript.Closure - -from ClosureModule cm -select cm, cm.getAProvidedNamespace() diff --git a/javascript/ql/test/library-tests/Closure/ClosureModule_getARequiredNamespace.expected b/javascript/ql/test/library-tests/Closure/ClosureModule_getARequiredNamespace.expected deleted file mode 100644 index 3346345796b..00000000000 --- a/javascript/ql/test/library-tests/Closure/ClosureModule_getARequiredNamespace.expected +++ /dev/null @@ -1 +0,0 @@ -| b.js:1:1:3:21 | | a | diff --git a/javascript/ql/test/library-tests/Closure/ClosureModule_getARequiredNamespace.ql b/javascript/ql/test/library-tests/Closure/ClosureModule_getARequiredNamespace.ql deleted file mode 100644 index c187bf2ce65..00000000000 --- a/javascript/ql/test/library-tests/Closure/ClosureModule_getARequiredNamespace.ql +++ /dev/null @@ -1,4 +0,0 @@ -import semmle.javascript.Closure - -from ClosureModule cm -select cm, cm.getARequiredNamespace() diff --git a/javascript/ql/test/library-tests/Closure/GoogFunctionCall.expected b/javascript/ql/test/library-tests/Closure/GoogFunctionCall.expected deleted file mode 100644 index 315105fa6fc..00000000000 --- a/javascript/ql/test/library-tests/Closure/GoogFunctionCall.expected +++ /dev/null @@ -1,3 +0,0 @@ -| a.js:1:1:1:17 | goog.provide('a') | provide | -| b.js:1:1:1:17 | goog.require('a') | require | -| c.js:2:1:2:14 | goog.leyness() | leyness | diff --git a/javascript/ql/test/library-tests/Closure/GoogFunctionCall.ql b/javascript/ql/test/library-tests/Closure/GoogFunctionCall.ql deleted file mode 100644 index 93b44a4a144..00000000000 --- a/javascript/ql/test/library-tests/Closure/GoogFunctionCall.ql +++ /dev/null @@ -1,4 +0,0 @@ -import semmle.javascript.Closure - -from GoogFunctionCall gfc -select gfc, gfc.getFunctionName() diff --git a/javascript/ql/test/library-tests/Closure/GoogProvide.expected b/javascript/ql/test/library-tests/Closure/GoogProvide.expected deleted file mode 100644 index 818436b0e0a..00000000000 --- a/javascript/ql/test/library-tests/Closure/GoogProvide.expected +++ /dev/null @@ -1 +0,0 @@ -| a.js:1:1:1:18 | goog.provide('a'); | a | diff --git a/javascript/ql/test/library-tests/Closure/GoogProvide.ql b/javascript/ql/test/library-tests/Closure/GoogProvide.ql deleted file mode 100644 index c02842bf060..00000000000 --- a/javascript/ql/test/library-tests/Closure/GoogProvide.ql +++ /dev/null @@ -1,4 +0,0 @@ -import semmle.javascript.Closure - -from GoogProvide gp -select gp, gp.getNamespaceId() diff --git a/javascript/ql/test/library-tests/Closure/GoogRequire.expected b/javascript/ql/test/library-tests/Closure/GoogRequire.expected deleted file mode 100644 index 753c733b30d..00000000000 --- a/javascript/ql/test/library-tests/Closure/GoogRequire.expected +++ /dev/null @@ -1 +0,0 @@ -| b.js:1:1:1:18 | goog.require('a'); | a | diff --git a/javascript/ql/test/library-tests/Closure/GoogRequire.ql b/javascript/ql/test/library-tests/Closure/GoogRequire.ql deleted file mode 100644 index 17c19b361e5..00000000000 --- a/javascript/ql/test/library-tests/Closure/GoogRequire.ql +++ /dev/null @@ -1,4 +0,0 @@ -import semmle.javascript.Closure - -from GoogRequire gr -select gr, gr.getNamespaceId() diff --git a/javascript/ql/test/library-tests/Closure/a.js b/javascript/ql/test/library-tests/Closure/a.js deleted file mode 100644 index fe9a093506f..00000000000 --- a/javascript/ql/test/library-tests/Closure/a.js +++ /dev/null @@ -1,5 +0,0 @@ -goog.provide('a'); - -a.foo = function() { - return 42; -} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Closure/b.js b/javascript/ql/test/library-tests/Closure/b.js deleted file mode 100644 index ce45c3aa444..00000000000 --- a/javascript/ql/test/library-tests/Closure/b.js +++ /dev/null @@ -1,3 +0,0 @@ -goog.require('a'); - -console.log(a.foo()); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Closure/c.js b/javascript/ql/test/library-tests/Closure/c.js deleted file mode 100644 index f46c1963e52..00000000000 --- a/javascript/ql/test/library-tests/Closure/c.js +++ /dev/null @@ -1,2 +0,0 @@ -// not a Closure module -goog.leyness(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Closure/tests/es6Module.js b/javascript/ql/test/library-tests/Closure/tests/es6Module.js new file mode 100644 index 00000000000..c1a709d4037 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/es6Module.js @@ -0,0 +1,3 @@ +goog.declareModuleId('x.y.z.es6'); + +export function fun() {} diff --git a/javascript/ql/test/library-tests/Closure/tests/es6ModuleDefault.js b/javascript/ql/test/library-tests/Closure/tests/es6ModuleDefault.js new file mode 100644 index 00000000000..72f98436c2e --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/es6ModuleDefault.js @@ -0,0 +1,3 @@ +goog.declareModuleId('x.y.z.es6default'); + +export default function() {} diff --git a/javascript/ql/test/library-tests/Closure/tests/globalModule.js b/javascript/ql/test/library-tests/Closure/tests/globalModule.js new file mode 100644 index 00000000000..129a313d937 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/globalModule.js @@ -0,0 +1,5 @@ +goog.provide('x.y.z.global'); + +x.y.z.global = { + fun() {} +}; diff --git a/javascript/ql/test/library-tests/Closure/tests/globalModuleDefault.js b/javascript/ql/test/library-tests/Closure/tests/globalModuleDefault.js new file mode 100644 index 00000000000..d5044e56dbb --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/globalModuleDefault.js @@ -0,0 +1,3 @@ +goog.provide('x.y.z.globaldefault'); + +x.y.z.globaldefault = function fun() {} diff --git a/javascript/ql/test/library-tests/Closure/tests/googModule.js b/javascript/ql/test/library-tests/Closure/tests/googModule.js new file mode 100644 index 00000000000..c986078db3e --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/googModule.js @@ -0,0 +1,5 @@ +goog.module('x.y.z.goog'); + +exports = { + fun() {} +}; diff --git a/javascript/ql/test/library-tests/Closure/tests/googModuleDefault.js b/javascript/ql/test/library-tests/Closure/tests/googModuleDefault.js new file mode 100644 index 00000000000..a4bf3384848 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/googModuleDefault.js @@ -0,0 +1,3 @@ +goog.module('x.y.z.googdefault'); + +exports = function fun() {}; diff --git a/javascript/ql/test/library-tests/Closure/tests/importFromEs6.js b/javascript/ql/test/library-tests/Closure/tests/importFromEs6.js new file mode 100644 index 00000000000..5e380984817 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/importFromEs6.js @@ -0,0 +1,13 @@ +// ES6 imports can import files by name, as long as they are modules + +import * as googModule from './googModule'; +import * as googModuleDefault from './googModuleDefault'; + +import * as es6Module from './es6Module'; +import * as es6ModuleDefault from './es6ModuleDefault'; + +es6Module.fun(); +es6ModuleDefault(); + +googModule.fun(); +googModuleDefault(); diff --git a/javascript/ql/test/library-tests/Closure/tests/requireFromEs6.js b/javascript/ql/test/library-tests/Closure/tests/requireFromEs6.js new file mode 100644 index 00000000000..9078bb91c40 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/requireFromEs6.js @@ -0,0 +1,19 @@ +import * as dummy from 'dummy'; // treat as ES6 module + +let globalModule = goog.require('x.y.z.global'); +let globalModuleDefault = goog.require('x.y.z.globaldefault'); + +let es6Module = goog.require('x.y.z.es6'); +let es6ModuleDefault = goog.require('x.y.z.es6default'); + +let googModule = goog.require('x.y.z.goog'); +let googModuleDefault = goog.require('x.y.z.googdefault'); + +globalModule.fun(); +globalModuleDefault(); + +es6Module.fun(); +es6ModuleDefault(); + +googModule.fun(); +googModuleDefault(); diff --git a/javascript/ql/test/library-tests/Closure/tests/requireFromGlobalModule.js b/javascript/ql/test/library-tests/Closure/tests/requireFromGlobalModule.js new file mode 100644 index 00000000000..0542e6fd356 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/requireFromGlobalModule.js @@ -0,0 +1,17 @@ +goog.require('x.y.z.global'); +goog.require('x.y.z.globaldefault'); + +goog.require('x.y.z.goog'); +goog.require('x.y.z.googdefault'); + +goog.require('x.y.z.es6'); +goog.require('x.y.z.es6default'); + +x.y.z.global.fun(); +x.y.z.globaldefault(); + +x.y.z.goog.fun(); +x.y.z.googdefault(); + +x.y.z.es6.fun(); +x.y.z.es6default(); diff --git a/javascript/ql/test/library-tests/Closure/tests/requireFromGoogModule.js b/javascript/ql/test/library-tests/Closure/tests/requireFromGoogModule.js new file mode 100644 index 00000000000..00df1fb3ae1 --- /dev/null +++ b/javascript/ql/test/library-tests/Closure/tests/requireFromGoogModule.js @@ -0,0 +1,19 @@ +goog.module('test.importer'); + +let globalModule = goog.require('x.y.z.global'); +let globalModuleDefault = goog.require('x.y.z.globaldefault'); + +let es6Module = goog.require('x.y.z.es6'); +let es6ModuleDefault = goog.require('x.y.z.es6default'); + +let googModule = goog.require('x.y.z.goog'); +let googModuleDefault = goog.require('x.y.z.googdefault'); + +globalModule.fun(); +globalModuleDefault(); + +es6Module.fun(); +es6ModuleDefault(); + +googModule.fun(); +googModuleDefault();