Merge pull request #1185 from xiemaisi/js/improve-amd-imports

Approved by asger-semmle
This commit is contained in:
semmle-qlci
2019-04-01 16:30:47 +01:00
committed by GitHub
33 changed files with 197 additions and 50 deletions

View File

@@ -17,6 +17,6 @@ where
not f.inExternsFile() and
f.getNumParameter() > 7 and
// exclude AMD modules
not exists(AMDModuleDefinition m | f = m.getFactoryNode().(DataFlow::FunctionNode).getAstNode())
not exists(AmdModuleDefinition m | f = m.getFactoryNode().(DataFlow::FunctionNode).getAstNode())
select f.(FirstLineOf),
capitalize(f.describe()) + " has too many parameters (" + f.getNumParameter() + ")."

View File

@@ -23,8 +23,8 @@ import javascript
* where the first argument is the module name, the second argument an
* array of dependencies, and the third argument a factory method or object.
*/
class AMDModuleDefinition extends CallExpr {
AMDModuleDefinition() {
class AmdModuleDefinition extends CallExpr {
AmdModuleDefinition() {
getParent() instanceof ExprStmt and
getCallee().(GlobalVarAccess).getName() = "define" and
exists(int n | n = getNumArgument() |
@@ -153,7 +153,7 @@ class AMDModuleDefinition extends CallExpr {
result = getModuleExpr().analyze().getAValue()
or
// explicit exports: anything assigned to `module.exports`
exists(AbstractProperty moduleExports, AMDModule m |
exists(AbstractProperty moduleExports, AmdModule m |
this = m.getDefine() and
moduleExports.getBase().(AbstractModuleObject).getModule() = m and
moduleExports.getPropertyName() = "exports"
@@ -170,10 +170,15 @@ class AMDModuleDefinition extends CallExpr {
}
}
/**
* DEPRECATED: Use `AmdModuleDefinition` instead.
*/
deprecated class AMDModuleDefinition = AmdModuleDefinition;
/** An AMD dependency, considered as a path expression. */
private class AmdDependencyPath extends PathExprCandidate {
AmdDependencyPath() {
exists(AMDModuleDefinition amd |
exists(AmdModuleDefinition amd |
this = amd.getDependencies().getAnElement() or
this = amd.getARequireCall().getAnArgument()
)
@@ -191,21 +196,83 @@ private class ConstantAmdDependencyPathElement extends PathExprInModule, Constan
* Holds if `def` is an AMD module definition in `tl` which is not
* nested inside another module definition.
*/
private predicate amdModuleTopLevel(AMDModuleDefinition def, TopLevel tl) {
private predicate amdModuleTopLevel(AmdModuleDefinition def, TopLevel tl) {
def.getTopLevel() = tl and
not def.getParent+() instanceof AMDModuleDefinition
not def.getParent+() instanceof AmdModuleDefinition
}
/**
* An AMD dependency, viewed as an import.
*/
private class AmdDependencyImport extends Import {
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependency() }
override Module getEnclosingModule() { this = result.(AmdModule).getDefine().getADependency() }
override PathExpr getImportedPath() { result = this }
/**
* Gets a file that looks like it might be the target of this import.
*
* Specifically, we look for files whose absolute path ends with the imported path, possibly
* adding well-known JavaScript file extensions like `.js`.
*/
private File guessTarget() {
exists(PathString imported, string abspath, string dirname, string basename |
targetCandidate(result, abspath, imported, dirname, basename)
|
abspath.regexpMatch(".*/\\Q" + imported + "\\E")
or
exists(Folder dir |
// `dir` ends with the dirname of the imported path
dir.getAbsolutePath().regexpMatch(".*/\\Q" + dirname + "\\E") or
dirname = ""
|
result = dir.getJavaScriptFile(basename)
)
)
}
/**
* Holds if `f` is a file whose stem (that is, basename without extension) matches the imported path.
*
* Additionally, `abspath` is bound to the absolute path of `f`, `imported` to the imported path, and
* `dirname` and `basename` to the dirname and basename (respectively) of `imported`.
*/
private predicate targetCandidate(
File f, string abspath, PathString imported, string dirname, string basename
) {
imported = getImportedPath().getValue() and
f.getStem() = imported.getStem() and
f.getAbsolutePath() = abspath and
dirname = imported.getDirName() and
basename = imported.getBaseName()
}
/**
* Gets the module whose absolute path matches this import, if there is only a single such module.
*/
private Module resolveByAbsolutePath() {
count(guessTarget()) = 1 and
result.getFile() = guessTarget()
}
override Module getImportedModule() {
result = super.getImportedModule()
or
not exists(super.getImportedModule()) and
result = resolveByAbsolutePath()
}
}
/**
* An AMD-style module.
*/
class AMDModule extends Module {
AMDModule() { strictcount(AMDModuleDefinition def | amdModuleTopLevel(def, this)) = 1 }
class AmdModule extends Module {
AmdModule() { strictcount(AmdModuleDefinition def | amdModuleTopLevel(def, this)) = 1 }
/** Gets the definition of this module. */
AMDModuleDefinition getDefine() { amdModuleTopLevel(result, this) }
override Module getAnImportedModule() { result.getFile() = resolve(getDefine().getADependency()) }
AmdModuleDefinition getDefine() { amdModuleTopLevel(result, this) }
override predicate exports(string name, ASTNode export) {
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
@@ -214,3 +281,8 @@ class AMDModule extends Module {
)
}
}
/**
* DEPRECATED: Use `AmdModule` instead.
*/
deprecated class AMDModule = AmdModule;

View File

@@ -19,8 +19,6 @@ class ES2015Module extends Module {
/** Gets an export declaration in this module. */
ExportDeclaration getAnExport() { result.getTopLevel() = this }
override Module getAnImportedModule() { result = getAnImport().getImportedModule() }
override predicate exports(string name, ASTNode export) {
exists(ExportDeclaration ed | ed = getAnExport() and ed = export | ed.exportsAs(_, name))
}

View File

@@ -75,7 +75,7 @@ abstract class Container extends @container {
* </table>
*/
string getBaseName() {
result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 1)
}
/**
@@ -101,7 +101,7 @@ abstract class Container extends @container {
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
* </table>
*/
string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) }
string getExtension() { result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 4) }
/**
* Gets the stem of this container, that is, the prefix of its base name up to
@@ -120,7 +120,7 @@ abstract class Container extends @container {
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
* </table>
*/
string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) }
string getStem() { result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 2) }
/** Gets the parent container of this file or folder, if any. */
Container getParentContainer() { containerparent(result, this) }

View File

@@ -21,7 +21,7 @@ abstract class Module extends TopLevel {
Import getAnImport() { result.getTopLevel() = this }
/** Gets a module from which this module imports. */
abstract Module getAnImportedModule();
Module getAnImportedModule() { result = getAnImport().getImportedModule() }
/** Gets a symbol exported by this module. */
string getAnExportedSymbol() { exports(result, _) }
@@ -92,8 +92,8 @@ abstract class Module extends TopLevel {
}
/**
* An import in a module, which may either be an ECMAScript 2015-style
* `import` statement or a CommonJS-style `require` import.
* An import in a module, which may be an ECMAScript 2015-style
* `import` statement, a CommonJS-style `require` import, or an AMD dependency.
*/
abstract class Import extends ASTNode {
/** Gets the module in which this import appears. */

View File

@@ -21,9 +21,6 @@ class NodeModule extends Module {
/** Gets the scope induced by this module. */
override ModuleScope getScope() { result.getScopeElement() = this }
/** Gets a module imported by this module. */
override Module getAnImportedModule() { result = getAnImport().getImportedModule() }
/**
* Gets an abstract value representing one or more values that may flow
* into this module's `module.exports` property.

View File

@@ -76,6 +76,26 @@ private class ConsPath extends Path, TConsPath {
override string toString() { result = pp(this) }
}
/**
* Gets a regular expression that can be used to parse slash-separated paths.
*
* The first capture group captures the dirname of the path, that is, everything
* before the last slash, or the empty string if there isn't a slash.
*
* The second capture group captures the basename of the path, that is, everything
* after the last slash, or the entire path if there isn't a slash.
*
* The third capture group captures the stem of the basename, that is, everything
* before the last dot, or the entire basename if there isn't a dot.
*
* Finally, the fourth and fifth capture groups capture the extension of the basename,
* that is, everything after the last dot. The fourth group includes the dot, the
* fifth does not.
*/
private string pathRegex() {
result = "(.*)(?:/|^)(([^/]*?)(\\.([^.]*))?)"
}
/**
* A string value that represents a (relative or absolute) file system path.
*
@@ -98,7 +118,17 @@ abstract class PathString extends string {
int getNumComponent() { result = count(int i | exists(getComponent(i))) }
/** Gets the base name of the folder or file this path refers to. */
string getBaseName() { result = this.regexpCapture("(.*/|^)([^/]+)", 2) }
string getBaseName() { result = this.regexpCapture(pathRegex(), 2) }
/**
* Gets stem of the folder or file this path refers to, that is, the prefix of its base name
* up to (but not including) the last dot character if there is one, or the entire
* base name if there is not
*/
string getStem() { result = this.regexpCapture(pathRegex(), 3) }
/** Gets the path of the parent folder of the folder or file this path refers to. */
string getDirName() { result = this.regexpCapture(pathRegex(), 1) }
/**
* Gets the absolute path that the sub-path consisting of the first `n`

View File

@@ -472,12 +472,12 @@ module ModuleImportNode {
)
or
// declared AMD dependency
exists(AMDModuleDefinition amd |
exists(AmdModuleDefinition amd |
this = DataFlow::parameterNode(amd.getDependencyParameter(path))
)
or
// AMD require
exists(AMDModuleDefinition amd, CallExpr req |
exists(AmdModuleDefinition amd, CallExpr req |
req = amd.getARequireCall() and
this = DataFlow::valueNode(req) and
path = req.getArgument(0).(ConstantString).getStringValue()

View File

@@ -61,7 +61,7 @@ private predicate mayDynamicallyComputeExports(Module m) {
or
// AMD modules can export arbitrary objects, so an import is essentially a property read
// and hence must be considered indefinite
m instanceof AMDModule
m instanceof AmdModule
or
// `m` re-exports all exports of some other module that dynamically computes its exports
exists(BulkReExportDeclaration rexp | rexp = m.(ES2015Module).getAnExport() |
@@ -229,7 +229,7 @@ class AnalyzedExternalModuleReference extends AnalyzedPropertyRead, DataFlow::Va
* Flow analysis for AMD exports.
*/
private class AnalyzedAmdExport extends AnalyzedPropertyWrite, DataFlow::ValueNode {
AMDModule amd;
AmdModule amd;
AnalyzedAmdExport() { astNode = amd.getDefine().getModuleExpr() }
@@ -248,7 +248,7 @@ private class AnalyzedAmdImport extends AnalyzedPropertyRead, DataFlow::Node {
Module required;
AnalyzedAmdImport() {
exists(AMDModule amd, PathExpr dep, Parameter p |
exists(AmdModule amd, PathExpr dep, Parameter p |
amd.getDefine().dependencyParameter(dep, p) and
this = DataFlow::parameterNode(p) and
required.getFile() = amd.resolve(dep)

View File

@@ -180,7 +180,7 @@ private class AnalyzedAmdParameter extends AnalyzedVarDef {
AbstractValue implicitInitVal;
AnalyzedAmdParameter() {
exists(AMDModule m, AMDModuleDefinition mdef | mdef = m.getDefine() |
exists(AmdModule m, AmdModuleDefinition mdef | mdef = m.getDefine() |
this = mdef.getModuleParameter() and
implicitInitVal = TAbstractModuleObject(m)
or

View File

@@ -57,7 +57,7 @@ module TaintedPath {
ModulePathSink() {
astNode = any(Require rq).getArgument(0) or
astNode = any(ExternalModuleReference rq).getExpression() or
astNode = any(AMDModuleDefinition amd).getDependencies()
astNode = any(AmdModuleDefinition amd).getDependencies()
}
}

View File

@@ -1,4 +0,0 @@
| a.js:1:1:3:2 | define( ... 2 };\\n}) | a.js:2:12:2:22 | { foo: 42 } | a.js:2:12:2:22 | { foo: 42 } |
| dir/b.js:1:1:3:2 | define( ... : 42\\n}) | dir/b.js:1:8:3:1 | {\\n bar: 42\\n} | dir/b.js:1:8:3:1 | {\\n bar: 42\\n} |
| tst.js:1:1:6:2 | define( ... };\\n}) | tst.js:2:12:5:5 | {\\n ... r\\n } | tst.js:2:12:5:5 | {\\n ... r\\n } |
| umd.js:4:9:4:43 | define( ... actory) | umd.js:10:12:13:5 | {\\n ... r\\n } | umd.js:10:12:13:5 | {\\n ... r\\n } |

View File

@@ -1,5 +0,0 @@
| tst3.js:1:1:3:3 | <toplevel> | a.js:1:1:3:3 | <toplevel> |
| tst.js:1:1:6:3 | <toplevel> | a.js:1:1:3:3 | <toplevel> |
| tst.js:1:1:6:3 | <toplevel> | dir/b.js:1:1:3:3 | <toplevel> |
| umd.js:1:1:14:4 | <toplevel> | a.js:1:1:3:3 | <toplevel> |
| umd.js:1:1:14:4 | <toplevel> | dir/b.js:1:1:3:3 | <toplevel> |

View File

@@ -1,4 +0,0 @@
import javascript
from AMDModule m
select m, m.getAnImportedModule()

View File

@@ -1,6 +1,10 @@
| a.js:1:1:3:3 | <toplevel> | a.js:1:1:3:2 | define( ... 2 };\\n}) |
| dir/b.js:1:1:3:3 | <toplevel> | dir/b.js:1:1:3:2 | define( ... : 42\\n}) |
| lib/a.js:1:1:3:3 | <toplevel> | lib/a.js:1:1:3:2 | define( ... 2 };\\n}) |
| lib/foo.js:1:1:4:0 | <toplevel> | lib/foo.js:1:1:3:2 | define( ... : 23\\n}) |
| lib/nested/a.js:1:1:3:3 | <toplevel> | lib/nested/a.js:1:1:3:2 | define( ... 2 };\\n}) |
| tst2.js:1:1:3:3 | <toplevel> | tst2.js:1:1:3:2 | define( ... 42;\\n}) |
| tst3.js:1:1:3:3 | <toplevel> | tst3.js:1:1:3:2 | define( ... 42;\\n}) |
| tst4.js:1:1:11:3 | <toplevel> | tst4.js:1:1:11:2 | define( ... };\\n}) |
| tst.js:1:1:6:3 | <toplevel> | tst.js:1:1:6:2 | define( ... };\\n}) |
| umd.js:1:1:14:4 | <toplevel> | umd.js:4:9:4:43 | define( ... actory) |

View File

@@ -1,4 +1,4 @@
import javascript
from AMDModule m
from AmdModule m
select m, m.getDefine()

View File

@@ -1,6 +1,10 @@
| a.js:1:1:3:2 | define( ... 2 };\\n}) | a.js:1:8:3:1 | functio ... 42 };\\n} |
| dir/b.js:1:1:3:2 | define( ... : 42\\n}) | dir/b.js:1:8:3:1 | {\\n bar: 42\\n} |
| lib/a.js:1:1:3:2 | define( ... 2 };\\n}) | lib/a.js:1:8:3:1 | functio ... 42 };\\n} |
| lib/foo.js:1:1:3:2 | define( ... : 23\\n}) | lib/foo.js:1:8:3:1 | {\\n foo: 23\\n} |
| lib/nested/a.js:1:1:3:2 | define( ... 2 };\\n}) | lib/nested/a.js:1:8:3:1 | functio ... 42 };\\n} |
| tst2.js:1:1:3:2 | define( ... 42;\\n}) | tst2.js:1:21:3:1 | functio ... = 42;\\n} |
| tst3.js:1:1:3:2 | define( ... 42;\\n}) | tst3.js:1:8:3:1 | functio ... = 42;\\n} |
| tst4.js:1:1:11:2 | define( ... };\\n}) | tst4.js:6:11:11:1 | functio ... };\\n} |
| tst.js:1:1:6:2 | define( ... };\\n}) | tst.js:1:28:6:1 | functio ... };\\n} |
| umd.js:4:9:4:43 | define( ... actory) | umd.js:9:9:14:1 | functio ... };\\n} |

View File

@@ -1,4 +1,4 @@
import javascript
from AMDModuleDefinition d
from AmdModuleDefinition d
select d, d.getFactoryNode()

View File

@@ -1,5 +1,9 @@
| tst2.js:1:1:3:2 | define( ... 42;\\n}) | tst2.js:1:9:1:17 | 'exports' |
| tst3.js:1:1:3:2 | define( ... 42;\\n}) | tst3.js:2:21:2:25 | './a' |
| tst4.js:1:1:11:2 | define( ... };\\n}) | tst4.js:2:9:2:14 | 'a.js' |
| tst4.js:1:1:11:2 | define( ... };\\n}) | tst4.js:3:9:3:13 | 'foo' |
| tst4.js:1:1:11:2 | define( ... };\\n}) | tst4.js:4:9:4:18 | 'nested/a' |
| tst4.js:1:1:11:2 | define( ... };\\n}) | tst4.js:5:9:5:20 | 'lib/foo.js' |
| tst.js:1:1:6:2 | define( ... };\\n}) | tst.js:1:9:1:13 | './a' |
| tst.js:1:1:6:2 | define( ... };\\n}) | tst.js:1:16:1:24 | './dir/b' |
| umd.js:4:9:4:43 | define( ... actory) | umd.js:4:17:4:21 | './a' |

View File

@@ -1,4 +1,4 @@
import javascript
from AMDModuleDefinition d
from AmdModuleDefinition d
select d, d.getADependency()

View File

@@ -1,7 +1,12 @@
| a.js:1:1:3:3 | <toplevel> | foo |
| dir/b.js:1:1:3:3 | <toplevel> | bar |
| lib/a.js:1:1:3:3 | <toplevel> | foo |
| lib/foo.js:1:1:4:0 | <toplevel> | foo |
| lib/nested/a.js:1:1:3:3 | <toplevel> | foo |
| tst2.js:1:1:3:3 | <toplevel> | foo |
| tst3.js:1:1:3:3 | <toplevel> | foo |
| tst4.js:1:1:11:3 | <toplevel> | bar |
| tst4.js:1:1:11:3 | <toplevel> | foo |
| tst.js:1:1:6:3 | <toplevel> | bar |
| tst.js:1:1:6:3 | <toplevel> | foo |
| umd.js:1:1:14:4 | <toplevel> | bar |

View File

@@ -1,4 +1,4 @@
import javascript
from AMDModule m
from AmdModule m
select m, m.getAnExportedSymbol()

View File

@@ -0,0 +1,8 @@
| a.js:1:1:3:2 | define( ... 2 };\\n}) | a.js:2:12:2:22 | { foo: 42 } | a.js:2:12:2:22 | { foo: 42 } |
| dir/b.js:1:1:3:2 | define( ... : 42\\n}) | dir/b.js:1:8:3:1 | {\\n bar: 42\\n} | dir/b.js:1:8:3:1 | {\\n bar: 42\\n} |
| lib/a.js:1:1:3:2 | define( ... 2 };\\n}) | lib/a.js:2:12:2:22 | { foo: 42 } | lib/a.js:2:12:2:22 | { foo: 42 } |
| lib/foo.js:1:1:3:2 | define( ... : 23\\n}) | lib/foo.js:1:8:3:1 | {\\n foo: 23\\n} | lib/foo.js:1:8:3:1 | {\\n foo: 23\\n} |
| lib/nested/a.js:1:1:3:2 | define( ... 2 };\\n}) | lib/nested/a.js:2:12:2:22 | { foo: 42 } | lib/nested/a.js:2:12:2:22 | { foo: 42 } |
| tst4.js:1:1:11:2 | define( ... };\\n}) | tst4.js:7:12:10:5 | {\\n ... r\\n } | tst4.js:7:12:10:5 | {\\n ... r\\n } |
| tst.js:1:1:6:2 | define( ... };\\n}) | tst.js:2:12:5:5 | {\\n ... r\\n } | tst.js:2:12:5:5 | {\\n ... r\\n } |
| umd.js:4:9:4:43 | define( ... actory) | umd.js:10:12:13:5 | {\\n ... r\\n } | umd.js:10:12:13:5 | {\\n ... r\\n } |

View File

@@ -1,4 +1,4 @@
import javascript
from AMDModuleDefinition d
from AmdModuleDefinition d
select d, d.getModuleExpr(), d.getAModuleSource()

View File

@@ -0,0 +1,8 @@
| tst3.js:1:1:3:3 | <toplevel> | tst3.js:2:21:2:25 | './a' | a.js:1:1:3:3 | <toplevel> |
| tst4.js:1:1:11:3 | <toplevel> | tst4.js:3:9:3:13 | 'foo' | lib/foo.js:1:1:4:0 | <toplevel> |
| tst4.js:1:1:11:3 | <toplevel> | tst4.js:4:9:4:18 | 'nested/a' | lib/nested/a.js:1:1:3:3 | <toplevel> |
| tst4.js:1:1:11:3 | <toplevel> | tst4.js:5:9:5:20 | 'lib/foo.js' | lib/foo.js:1:1:4:0 | <toplevel> |
| tst.js:1:1:6:3 | <toplevel> | tst.js:1:9:1:13 | './a' | a.js:1:1:3:3 | <toplevel> |
| tst.js:1:1:6:3 | <toplevel> | tst.js:1:16:1:24 | './dir/b' | dir/b.js:1:1:3:3 | <toplevel> |
| umd.js:1:1:14:4 | <toplevel> | umd.js:4:17:4:21 | './a' | a.js:1:1:3:3 | <toplevel> |
| umd.js:1:1:14:4 | <toplevel> | umd.js:4:24:4:32 | './dir/b' | dir/b.js:1:1:3:3 | <toplevel> |

View File

@@ -0,0 +1,5 @@
import javascript
from AmdModule m, Import i
where i = m.getAnImport()
select m, i, i.getImportedModule()

View File

@@ -1,7 +1,12 @@
| a.js:1:1:3:3 | <toplevel> | foo | a.js:2:14:2:20 | foo: 42 |
| dir/b.js:1:1:3:3 | <toplevel> | bar | dir/b.js:2:5:2:11 | bar: 42 |
| lib/a.js:1:1:3:3 | <toplevel> | foo | lib/a.js:2:14:2:20 | foo: 42 |
| lib/foo.js:1:1:4:0 | <toplevel> | foo | lib/foo.js:2:5:2:11 | foo: 23 |
| lib/nested/a.js:1:1:3:3 | <toplevel> | foo | lib/nested/a.js:2:14:2:20 | foo: 42 |
| tst2.js:1:1:3:3 | <toplevel> | foo | tst2.js:2:5:2:15 | exports.foo |
| tst3.js:1:1:3:3 | <toplevel> | foo | tst3.js:2:29:2:39 | exports.foo |
| tst4.js:1:1:11:3 | <toplevel> | bar | tst4.js:9:9:9:18 | bar: b.bar |
| tst4.js:1:1:11:3 | <toplevel> | foo | tst4.js:8:9:8:18 | foo: a.foo |
| tst.js:1:1:6:3 | <toplevel> | bar | tst.js:4:9:4:18 | bar: b.bar |
| tst.js:1:1:6:3 | <toplevel> | foo | tst.js:3:9:3:18 | foo: a.foo |
| umd.js:1:1:14:4 | <toplevel> | bar | umd.js:11:9:11:18 | bar: a.foo |

View File

@@ -0,0 +1,3 @@
define(function() {
return { foo: 42 };
});

View File

@@ -0,0 +1,3 @@
define({
foo: 23
});

View File

@@ -0,0 +1,3 @@
define(function() {
return { foo: 42 };
});

View File

@@ -0,0 +1,11 @@
define([
'a.js', // not resolved: ambiguous
'foo', // resolved to `lib/foo.js`
'nested/a', // resolved to `lib/nested/a.js`
'lib/foo.js' // resolved to `lib/foo.js`
], function(a, b, exports) {
return {
foo: a.foo,
bar: b.bar
};
});

View File

@@ -10,7 +10,7 @@ string getModuleType(TopLevel top) {
top instanceof ES2015Module and
result = "es2015"
or
top instanceof AMDModule and
top instanceof AmdModule and
result = "amd"
}