mirror of
https://github.com/github/codeql.git
synced 2025-12-21 03:06:31 +01:00
JS: support imports/exports for closure library code
This commit is contained in:
@@ -46,5 +46,7 @@ where
|
|||||||
moduleExportsAssign(_, exportsVal) and
|
moduleExportsAssign(_, exportsVal) and
|
||||||
// however, if there are no further uses of `exports` the assignment is useless anyway
|
// however, if there are no further uses of `exports` the assignment is useless anyway
|
||||||
strictcount(exportsVar.getAnAccess()) > 1
|
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."
|
select assgn, "Assigning to 'exports' does not export anything."
|
||||||
|
|||||||
@@ -37,39 +37,162 @@ class GoogFunctionCallStmt extends ExprStmt {
|
|||||||
Expr getAnArgument() { result = getArgument(_) }
|
Expr getAnArgument() { result = getArgument(_) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private abstract class GoogNamespaceRef extends ExprOrStmt {
|
||||||
|
abstract string getNamespaceId();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A call to `goog.provide`.
|
* A call to `goog.provide`.
|
||||||
*/
|
*/
|
||||||
class GoogProvide extends GoogFunctionCallStmt {
|
class GoogProvide extends GoogFunctionCallStmt, GoogNamespaceRef {
|
||||||
GoogProvide() { getFunctionName() = "provide" }
|
GoogProvide() { getFunctionName() = "provide" }
|
||||||
|
|
||||||
/** Gets the identifier of the namespace created by this call. */
|
/** 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`.
|
* A call to `goog.require`.
|
||||||
*/
|
*/
|
||||||
class GoogRequire extends GoogFunctionCallStmt {
|
class GoogRequire extends GoogFunctionCall, GoogNamespaceRef {
|
||||||
GoogRequire() { getFunctionName() = "require" }
|
GoogRequire() { getFunctionName() = "require" }
|
||||||
|
|
||||||
/** Gets the identifier of the namespace imported by this call. */
|
/** 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
|
* A call to `goog.module` or `goog.declareModuleId`.
|
||||||
* `goog.require`.
|
|
||||||
*/
|
*/
|
||||||
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() {
|
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 GoogProvide or
|
||||||
getAChildStmt() instanceof GoogRequire
|
getAChildStmt().(ExprStmt).getExpr() instanceof GoogRequire
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gets the identifier of a namespace required by this module. */
|
/** 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. */
|
/** Gets the identifer of a namespace provided by this module. */
|
||||||
string getAProvidedNamespace() { result = getAChildStmt().(GoogProvide).getNamespaceId() }
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import javascript
|
import javascript
|
||||||
|
private import semmle.javascript.Closure
|
||||||
private import AbstractValuesImpl
|
private import AbstractValuesImpl
|
||||||
private import semmle.javascript.dataflow.InferredTypes
|
private import semmle.javascript.dataflow.InferredTypes
|
||||||
private import AbstractPropertiesImpl
|
private import AbstractPropertiesImpl
|
||||||
@@ -332,3 +333,50 @@ private class AnalyzedExportAssign extends AnalyzedPropertyWrite, DataFlow::Valu
|
|||||||
source = this
|
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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
22
javascript/ql/test/library-tests/Closure/CallGraph.expected
Normal file
22
javascript/ql/test/library-tests/Closure/CallGraph.expected
Normal file
@@ -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() {} |
|
||||||
4
javascript/ql/test/library-tests/Closure/CallGraph.ql
Normal file
4
javascript/ql/test/library-tests/Closure/CallGraph.ql
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import javascript
|
||||||
|
|
||||||
|
from DataFlow::InvokeNode node
|
||||||
|
select node, node.getACallee()
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
| a.js:1:1:5:1 | <toplevel> |
|
|
||||||
| b.js:1:1:3:21 | <toplevel> |
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import semmle.javascript.Closure
|
|
||||||
|
|
||||||
from ClosureModule cm
|
|
||||||
select cm
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
| a.js:1:1:5:1 | <toplevel> | a |
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import semmle.javascript.Closure
|
|
||||||
|
|
||||||
from ClosureModule cm
|
|
||||||
select cm, cm.getAProvidedNamespace()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
| b.js:1:1:3:21 | <toplevel> | a |
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import semmle.javascript.Closure
|
|
||||||
|
|
||||||
from ClosureModule cm
|
|
||||||
select cm, cm.getARequiredNamespace()
|
|
||||||
@@ -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 |
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import semmle.javascript.Closure
|
|
||||||
|
|
||||||
from GoogFunctionCall gfc
|
|
||||||
select gfc, gfc.getFunctionName()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
| a.js:1:1:1:18 | goog.provide('a'); | a |
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import semmle.javascript.Closure
|
|
||||||
|
|
||||||
from GoogProvide gp
|
|
||||||
select gp, gp.getNamespaceId()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
| b.js:1:1:1:18 | goog.require('a'); | a |
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import semmle.javascript.Closure
|
|
||||||
|
|
||||||
from GoogRequire gr
|
|
||||||
select gr, gr.getNamespaceId()
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
goog.provide('a');
|
|
||||||
|
|
||||||
a.foo = function() {
|
|
||||||
return 42;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
goog.require('a');
|
|
||||||
|
|
||||||
console.log(a.foo());
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
// not a Closure module
|
|
||||||
goog.leyness();
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
goog.declareModuleId('x.y.z.es6');
|
||||||
|
|
||||||
|
export function fun() {}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
goog.declareModuleId('x.y.z.es6default');
|
||||||
|
|
||||||
|
export default function() {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
goog.provide('x.y.z.global');
|
||||||
|
|
||||||
|
x.y.z.global = {
|
||||||
|
fun() {}
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
goog.provide('x.y.z.globaldefault');
|
||||||
|
|
||||||
|
x.y.z.globaldefault = function fun() {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
goog.module('x.y.z.goog');
|
||||||
|
|
||||||
|
exports = {
|
||||||
|
fun() {}
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
goog.module('x.y.z.googdefault');
|
||||||
|
|
||||||
|
exports = function fun() {};
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
Reference in New Issue
Block a user