diff --git a/change-notes/1.20/analysis-javascript.md b/change-notes/1.20/analysis-javascript.md
index 6167aaa6276..68f08324e35 100644
--- a/change-notes/1.20/analysis-javascript.md
+++ b/change-notes/1.20/analysis-javascript.md
@@ -12,6 +12,8 @@
* Type inference for function calls has been improved. This may give additional results for queries that rely on type inference.
+* The [Closure-Library](https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide) module system is now supported.
+
## New queries
| **Query** | **Tags** | **Purpose** |
diff --git a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java
index b135f0ce3f3..886cacd87a6 100644
--- a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java
+++ b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java
@@ -306,7 +306,7 @@ public class ASTExtractor {
public V(Platform platform, SourceType sourceType) {
this.platform = platform;
this.sourceType = sourceType;
- this.isStrict = sourceType == SourceType.MODULE;
+ this.isStrict = sourceType.isStrictMode();
}
private Label visit(INode child, Label parent, int childIndex) {
@@ -546,31 +546,37 @@ public class ASTExtractor {
isStrict = hasUseStrict(nd.getBody());
- // if we're extracting a Node.js/ES2015 module, introduce module scope
- if (platform == Platform.NODE) {
- // add node.js-specific globals
- scopeManager.addVariables("global", "process", "console", "Buffer");
+ // Add platform-specific globals.
+ scopeManager.addVariables(platform.getPredefinedGlobals());
+ // Introduce local scope if there is one.
+ if (sourceType.hasLocalScope()) {
Label moduleScopeKey = trapwriter.globalID("module;{" + locationManager.getFileLabel() + "}," + locationManager.getStartLine() + "," + locationManager.getStartColumn());
scopeManager.enterScope(3, moduleScopeKey, toplevelLabel);
- // special variables aren't available in `.mjs` modules
- if (!".mjs".equals(locationManager.getSourceFileExtension()))
- scopeManager.addVariables("require", "module", "exports", "__filename", "__dirname", "arguments");
- trapwriter.addTuple("isModule", toplevelLabel);
- } else if (sourceType == SourceType.MODULE) {
- Label moduleScopeKey = trapwriter.globalID("module;{" + locationManager.getFileLabel() + "}," + locationManager.getStartLine() + "," + locationManager.getStartColumn());
- scopeManager.enterScope(3, moduleScopeKey, toplevelLabel);
+ scopeManager.addVariables(sourceType.getPredefinedLocals(platform, locationManager.getSourceFileExtension()));
trapwriter.addTuple("isModule", toplevelLabel);
}
+ // Emit the specific source type.
+ switch (sourceType) {
+ case CLOSURE_MODULE:
+ trapwriter.addTuple("isClosureModule", toplevelLabel);
+ break;
+ case MODULE:
+ trapwriter.addTuple("isES2015Module", toplevelLabel);
+ break;
+ default:
+ break;
+ }
+
// add all declared global (or module-scoped) names, both non-lexical and lexical
scopeManager.addNames(scopeManager.collectDeclaredNames(nd, isStrict, false, DeclKind.none));
scopeManager.addNames(scopeManager.collectDeclaredNames(nd, isStrict, true, DeclKind.none));
visitAll(nd.getBody(), toplevelLabel);
- // if we're extracting a Node.js/ES2015 module, leave its scope
- if (platform == Platform.NODE || sourceType == SourceType.MODULE)
+ // Leave the local scope again.
+ if (sourceType.hasLocalScope())
scopeManager.leaveScope();
contextManager.leaveContainer();
diff --git a/javascript/extractor/src/com/semmle/js/extractor/ExtractorConfig.java b/javascript/extractor/src/com/semmle/js/extractor/ExtractorConfig.java
index dfb031021f3..bd42953e240 100644
--- a/javascript/extractor/src/com/semmle/js/extractor/ExtractorConfig.java
+++ b/javascript/extractor/src/com/semmle/js/extractor/ExtractorConfig.java
@@ -4,7 +4,12 @@ import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import com.semmle.js.parser.JcornWrapper;
import com.semmle.util.data.StringUtil;
import com.semmle.util.exception.UserError;
@@ -50,13 +55,75 @@ public class ExtractorConfig {
}
};
+ /**
+ * The type of a source file, which together with the {@link Platform}
+ * determines how the top-level scope of the file behaves, and whether ES2015
+ * module syntax should be allowed.
+ *
+ * Note that the names of these enum members are depended on by {@link Main},
+ * {@link AutoBuild}, and {@link JcornWrapper}.
+ */
public static enum SourceType {
- SCRIPT, MODULE, AUTO;
+ /** A script executed in the global scope. */
+ SCRIPT,
+
+ /** An ES2015 module. */
+ MODULE,
+
+ /** A Closure-Library module, defined using `goog.module()`. */
+ CLOSURE_MODULE,
+
+ /** A CommonJS module that is not also an ES2015 module. */
+ COMMONJS_MODULE,
+
+ /** Automatically determined source type. */
+ AUTO;
@Override
public String toString() {
return StringUtil.lc(name());
}
+
+ /**
+ * Returns true if this source is executed directly in the global scope,
+ * or false if it has its own local scope.
+ */
+ public boolean hasLocalScope() {
+ return this != SCRIPT;
+ }
+
+ /**
+ * Returns true if this source is implicitly in strict mode.
+ */
+ public boolean isStrictMode() {
+ return this == MODULE;
+ }
+
+ private static final Set closureLocals = Collections.singleton("exports");
+ private static final Set commonJsLocals = new LinkedHashSet<>(Arrays.asList("require", "module", "exports", "__filename", "__dirname", "arguments"));
+
+ /**
+ * Returns the set of local variables in scope at the top-level of this module.
+ *
+ * If this source type has no local scope, the empty set is returned.
+ */
+ public Set getPredefinedLocals(Platform platform, String extension) {
+ switch (this) {
+ case CLOSURE_MODULE:
+ return closureLocals;
+ case COMMONJS_MODULE:
+ return commonJsLocals;
+ case MODULE:
+ if (platform == Platform.NODE && !extension.equals(".mjs")) {
+ // An ES2015 module that is compiled to a Node.js module effectively has the locals
+ // from Node.js even if they are not part of the ES2015 standard.
+ return commonJsLocals;
+ }
+ return Collections.emptySet();
+ default:
+ return Collections.emptySet();
+ }
+ }
};
public static enum Platform {
@@ -66,6 +133,15 @@ public class ExtractorConfig {
public String toString() {
return StringUtil.lc(name());
}
+
+ private static final Set nodejsGlobals = new LinkedHashSet<>(Arrays.asList("global", "process", "console", "Buffer"));
+
+ /**
+ * Gets the set of predefined globals for this platform.
+ */
+ public Set getPredefinedGlobals() {
+ return this == NODE ? nodejsGlobals : Collections.emptySet();
+ }
}
/**
diff --git a/javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java
index 9b252011264..2ef02a769fb 100644
--- a/javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java
+++ b/javascript/extractor/src/com/semmle/js/extractor/JSExtractor.java
@@ -29,8 +29,8 @@ public class JSExtractor {
this.config = config;
}
- // heuristic: if `import` or `export` appears at the beginning of a line, it's probably a module
- private static final Pattern containsImportOrExport = Pattern.compile("(?m)^([ \t]*)(import|export)\\b");
+ // heuristic: if `import`, `export`, or `goog.module` appears at the beginning of a line, it's probably a module
+ private static final Pattern containsModuleIndicator = Pattern.compile("(?m)^([ \t]*)(import|export|goog\\.module)\\b");
public Pair extract(TextualExtractor textualExtractor, String source, int toplevelKind, ScopeManager scopeManager) throws ParseError {
// if the file starts with `{ "":` it won't parse as JavaScript; try parsing as JSON instead
@@ -69,9 +69,10 @@ public class JSExtractor {
if (sourceType != SourceType.AUTO)
return sourceType;
if (config.getEcmaVersion().compareTo(ECMAVersion.ECMA2015) >= 0) {
- Matcher m = containsImportOrExport.matcher(source);
- if (m.find() && (allowLeadingWS || m.group(1).isEmpty()))
- return SourceType.MODULE;
+ Matcher m = containsModuleIndicator.matcher(source);
+ if (m.find() && (allowLeadingWS || m.group(1).isEmpty())) {
+ return m.group(2).startsWith("goog") ? SourceType.CLOSURE_MODULE : SourceType.MODULE;
+ }
}
return SourceType.SCRIPT;
}
@@ -89,6 +90,9 @@ public class JSExtractor {
LoCInfo loc;
if (ast != null) {
platform = getPlatform(platform, ast);
+ if (sourceType == SourceType.SCRIPT && platform == Platform.NODE) {
+ sourceType = SourceType.COMMONJS_MODULE;
+ }
lexicalExtractor = new LexicalExtractor(textualExtractor, parserRes.getTokens(), parserRes.getComments());
ASTExtractor scriptExtractor = new ASTExtractor(lexicalExtractor, scopeManager);
@@ -125,7 +129,7 @@ public class JSExtractor {
if (config.isExterns())
textualExtractor.getTrapwriter().addTuple("isExterns", toplevelLabel);
- if (platform == Platform.NODE && sourceType != SourceType.MODULE)
+ if (platform == Platform.NODE && sourceType == SourceType.COMMONJS_MODULE)
textualExtractor.getTrapwriter().addTuple("isNodejs", toplevelLabel);
return Pair.make(toplevelLabel, loc);
diff --git a/javascript/extractor/src/com/semmle/js/extractor/Main.java b/javascript/extractor/src/com/semmle/js/extractor/Main.java
index 792823af247..98950f212f8 100644
--- a/javascript/extractor/src/com/semmle/js/extractor/Main.java
+++ b/javascript/extractor/src/com/semmle/js/extractor/Main.java
@@ -41,7 +41,7 @@ public class Main {
* such a way that it may produce different tuples for the same file under the same
* {@link ExtractorConfig}.
*/
- public static final String EXTRACTOR_VERSION = "2019-01-29";
+ public static final String EXTRACTOR_VERSION = "2019-02-04";
public static final Pattern NEWLINE = Pattern.compile("\n");
diff --git a/javascript/extractor/tests/closure/input/googDotDeclareModuleId.js b/javascript/extractor/tests/closure/input/googDotDeclareModuleId.js
new file mode 100644
index 00000000000..621884a8865
--- /dev/null
+++ b/javascript/extractor/tests/closure/input/googDotDeclareModuleId.js
@@ -0,0 +1,3 @@
+goog.declareModuleId('test');
+
+export let x = 5;
diff --git a/javascript/extractor/tests/closure/input/googDotModule.js b/javascript/extractor/tests/closure/input/googDotModule.js
new file mode 100644
index 00000000000..dcb994f981d
--- /dev/null
+++ b/javascript/extractor/tests/closure/input/googDotModule.js
@@ -0,0 +1,3 @@
+goog.module('test');
+var x = 5;
+exports = { x: x };
diff --git a/javascript/extractor/tests/closure/input/googDotProvide.js b/javascript/extractor/tests/closure/input/googDotProvide.js
new file mode 100644
index 00000000000..a194c35957f
--- /dev/null
+++ b/javascript/extractor/tests/closure/input/googDotProvide.js
@@ -0,0 +1,3 @@
+goog.provide('test.x');
+
+test.x = 5;
diff --git a/javascript/extractor/tests/closure/output/trap/googDotDeclareModuleId.js.trap b/javascript/extractor/tests/closure/output/trap/googDotDeclareModuleId.js.trap
new file mode 100644
index 00000000000..729aae779a4
--- /dev/null
+++ b/javascript/extractor/tests/closure/output/trap/googDotDeclareModuleId.js.trap
@@ -0,0 +1,203 @@
+#10000=@"/googDotDeclareModuleId.js;sourcefile"
+files(#10000,"/googDotDeclareModuleId.js","googDotDeclareModuleId","js",0)
+#10001=@"/;folder"
+folders(#10001,"/","")
+containerparent(#10001,#10000)
+#10002=@"loc,{#10000},0,0,0,0"
+locations_default(#10002,#10000,0,0,0,0)
+hasLocation(#10000,#10002)
+#20000=@"global_scope"
+scopes(#20000,0)
+#20001=@"script;{#10000},1,1"
+#20002=*
+lines(#20002,#20001,"goog.declareModuleId('test');","
+")
+#20003=@"loc,{#10000},1,1,1,29"
+locations_default(#20003,#10000,1,1,1,29)
+hasLocation(#20002,#20003)
+#20004=*
+lines(#20004,#20001,"","
+")
+#20005=@"loc,{#10000},2,1,2,0"
+locations_default(#20005,#10000,2,1,2,0)
+hasLocation(#20004,#20005)
+#20006=*
+lines(#20006,#20001,"export let x = 5;","
+")
+#20007=@"loc,{#10000},3,1,3,17"
+locations_default(#20007,#10000,3,1,3,17)
+hasLocation(#20006,#20007)
+numlines(#20001,3,2,0)
+#20008=*
+tokeninfo(#20008,6,#20001,0,"goog")
+#20009=@"loc,{#10000},1,1,1,4"
+locations_default(#20009,#10000,1,1,1,4)
+hasLocation(#20008,#20009)
+#20010=*
+tokeninfo(#20010,8,#20001,1,".")
+#20011=@"loc,{#10000},1,5,1,5"
+locations_default(#20011,#10000,1,5,1,5)
+hasLocation(#20010,#20011)
+#20012=*
+tokeninfo(#20012,6,#20001,2,"declareModuleId")
+#20013=@"loc,{#10000},1,6,1,20"
+locations_default(#20013,#10000,1,6,1,20)
+hasLocation(#20012,#20013)
+#20014=*
+tokeninfo(#20014,8,#20001,3,"(")
+#20015=@"loc,{#10000},1,21,1,21"
+locations_default(#20015,#10000,1,21,1,21)
+hasLocation(#20014,#20015)
+#20016=*
+tokeninfo(#20016,4,#20001,4,"'test'")
+#20017=@"loc,{#10000},1,22,1,27"
+locations_default(#20017,#10000,1,22,1,27)
+hasLocation(#20016,#20017)
+#20018=*
+tokeninfo(#20018,8,#20001,5,")")
+#20019=@"loc,{#10000},1,28,1,28"
+locations_default(#20019,#10000,1,28,1,28)
+hasLocation(#20018,#20019)
+#20020=*
+tokeninfo(#20020,8,#20001,6,";")
+#20021=@"loc,{#10000},1,29,1,29"
+locations_default(#20021,#10000,1,29,1,29)
+hasLocation(#20020,#20021)
+#20022=*
+tokeninfo(#20022,7,#20001,7,"export")
+#20023=@"loc,{#10000},3,1,3,6"
+locations_default(#20023,#10000,3,1,3,6)
+hasLocation(#20022,#20023)
+#20024=*
+tokeninfo(#20024,7,#20001,8,"let")
+#20025=@"loc,{#10000},3,8,3,10"
+locations_default(#20025,#10000,3,8,3,10)
+hasLocation(#20024,#20025)
+#20026=*
+tokeninfo(#20026,6,#20001,9,"x")
+#20027=@"loc,{#10000},3,12,3,12"
+locations_default(#20027,#10000,3,12,3,12)
+hasLocation(#20026,#20027)
+#20028=*
+tokeninfo(#20028,8,#20001,10,"=")
+#20029=@"loc,{#10000},3,14,3,14"
+locations_default(#20029,#10000,3,14,3,14)
+hasLocation(#20028,#20029)
+#20030=*
+tokeninfo(#20030,3,#20001,11,"5")
+#20031=@"loc,{#10000},3,16,3,16"
+locations_default(#20031,#10000,3,16,3,16)
+hasLocation(#20030,#20031)
+#20032=*
+tokeninfo(#20032,8,#20001,12,";")
+#20033=@"loc,{#10000},3,17,3,17"
+locations_default(#20033,#10000,3,17,3,17)
+hasLocation(#20032,#20033)
+#20034=*
+tokeninfo(#20034,0,#20001,13,"")
+#20035=@"loc,{#10000},4,1,4,0"
+locations_default(#20035,#10000,4,1,4,0)
+hasLocation(#20034,#20035)
+toplevels(#20001,0)
+#20036=@"loc,{#10000},1,1,4,0"
+locations_default(#20036,#10000,1,1,4,0)
+hasLocation(#20001,#20036)
+#20037=@"module;{#10000},1,1"
+scopes(#20037,3)
+scopenodes(#20001,#20037)
+scopenesting(#20037,#20000)
+isModule(#20001)
+isES2015Module(#20001)
+#20038=@"var;{x};{#20037}"
+variables(#20038,"x",#20037)
+#20039=*
+stmts(#20039,2,#20001,0,"goog.de ... test');")
+hasLocation(#20039,#20003)
+stmtContainers(#20039,#20001)
+#20040=*
+exprs(#20040,13,#20039,0,"goog.de ... 'test')")
+#20041=@"loc,{#10000},1,1,1,28"
+locations_default(#20041,#10000,1,1,1,28)
+hasLocation(#20040,#20041)
+enclosingStmt(#20040,#20039)
+exprContainers(#20040,#20001)
+#20042=*
+exprs(#20042,14,#20040,-1,"goog.declareModuleId")
+#20043=@"loc,{#10000},1,1,1,20"
+locations_default(#20043,#10000,1,1,1,20)
+hasLocation(#20042,#20043)
+enclosingStmt(#20042,#20039)
+exprContainers(#20042,#20001)
+#20044=*
+exprs(#20044,79,#20042,0,"goog")
+hasLocation(#20044,#20009)
+enclosingStmt(#20044,#20039)
+exprContainers(#20044,#20001)
+literals("goog","goog",#20044)
+#20045=@"var;{goog};{#20000}"
+variables(#20045,"goog",#20000)
+bind(#20044,#20045)
+#20046=*
+exprs(#20046,0,#20042,1,"declareModuleId")
+hasLocation(#20046,#20013)
+enclosingStmt(#20046,#20039)
+exprContainers(#20046,#20001)
+literals("declareModuleId","declareModuleId",#20046)
+#20047=*
+exprs(#20047,4,#20040,0,"'test'")
+hasLocation(#20047,#20017)
+enclosingStmt(#20047,#20039)
+exprContainers(#20047,#20001)
+literals("test","'test'",#20047)
+#20048=*
+stmts(#20048,30,#20001,1,"export let x = 5;")
+hasLocation(#20048,#20007)
+stmtContainers(#20048,#20001)
+#20049=*
+stmts(#20049,23,#20048,-1,"let x = 5;")
+#20050=@"loc,{#10000},3,8,3,17"
+locations_default(#20050,#10000,3,8,3,17)
+hasLocation(#20049,#20050)
+stmtContainers(#20049,#20001)
+#20051=*
+exprs(#20051,64,#20049,0,"x = 5")
+#20052=@"loc,{#10000},3,12,3,16"
+locations_default(#20052,#10000,3,12,3,16)
+hasLocation(#20051,#20052)
+enclosingStmt(#20051,#20049)
+exprContainers(#20051,#20001)
+#20053=*
+exprs(#20053,78,#20051,0,"x")
+hasLocation(#20053,#20027)
+enclosingStmt(#20053,#20049)
+exprContainers(#20053,#20001)
+literals("x","x",#20053)
+decl(#20053,#20038)
+#20054=*
+exprs(#20054,3,#20051,1,"5")
+hasLocation(#20054,#20031)
+enclosingStmt(#20054,#20049)
+exprContainers(#20054,#20001)
+literals("5","5",#20054)
+#20055=*
+entry_cfg_node(#20055,#20001)
+#20056=@"loc,{#10000},1,1,1,0"
+locations_default(#20056,#10000,1,1,1,0)
+hasLocation(#20055,#20056)
+#20057=*
+exit_cfg_node(#20057,#20001)
+hasLocation(#20057,#20035)
+successor(#20048,#20049)
+successor(#20049,#20053)
+successor(#20054,#20051)
+successor(#20053,#20054)
+successor(#20051,#20057)
+successor(#20039,#20044)
+successor(#20047,#20040)
+successor(#20046,#20042)
+successor(#20044,#20046)
+successor(#20042,#20047)
+successor(#20040,#20048)
+successor(#20055,#20039)
+numlines(#10000,3,2,0)
+filetype(#10000,"javascript")
diff --git a/javascript/extractor/tests/closure/output/trap/googDotModule.js.trap b/javascript/extractor/tests/closure/output/trap/googDotModule.js.trap
new file mode 100644
index 00000000000..7da62924256
--- /dev/null
+++ b/javascript/extractor/tests/closure/output/trap/googDotModule.js.trap
@@ -0,0 +1,283 @@
+#10000=@"/googDotModule.js;sourcefile"
+files(#10000,"/googDotModule.js","googDotModule","js",0)
+#10001=@"/;folder"
+folders(#10001,"/","")
+containerparent(#10001,#10000)
+#10002=@"loc,{#10000},0,0,0,0"
+locations_default(#10002,#10000,0,0,0,0)
+hasLocation(#10000,#10002)
+#20000=@"global_scope"
+scopes(#20000,0)
+#20001=@"script;{#10000},1,1"
+#20002=*
+lines(#20002,#20001,"goog.module('test');","
+")
+#20003=@"loc,{#10000},1,1,1,20"
+locations_default(#20003,#10000,1,1,1,20)
+hasLocation(#20002,#20003)
+#20004=*
+lines(#20004,#20001,"var x = 5;","
+")
+#20005=@"loc,{#10000},2,1,2,10"
+locations_default(#20005,#10000,2,1,2,10)
+hasLocation(#20004,#20005)
+#20006=*
+lines(#20006,#20001,"exports = { x: x };","
+")
+#20007=@"loc,{#10000},3,1,3,19"
+locations_default(#20007,#10000,3,1,3,19)
+hasLocation(#20006,#20007)
+numlines(#20001,3,3,0)
+#20008=*
+tokeninfo(#20008,6,#20001,0,"goog")
+#20009=@"loc,{#10000},1,1,1,4"
+locations_default(#20009,#10000,1,1,1,4)
+hasLocation(#20008,#20009)
+#20010=*
+tokeninfo(#20010,8,#20001,1,".")
+#20011=@"loc,{#10000},1,5,1,5"
+locations_default(#20011,#10000,1,5,1,5)
+hasLocation(#20010,#20011)
+#20012=*
+tokeninfo(#20012,6,#20001,2,"module")
+#20013=@"loc,{#10000},1,6,1,11"
+locations_default(#20013,#10000,1,6,1,11)
+hasLocation(#20012,#20013)
+#20014=*
+tokeninfo(#20014,8,#20001,3,"(")
+#20015=@"loc,{#10000},1,12,1,12"
+locations_default(#20015,#10000,1,12,1,12)
+hasLocation(#20014,#20015)
+#20016=*
+tokeninfo(#20016,4,#20001,4,"'test'")
+#20017=@"loc,{#10000},1,13,1,18"
+locations_default(#20017,#10000,1,13,1,18)
+hasLocation(#20016,#20017)
+#20018=*
+tokeninfo(#20018,8,#20001,5,")")
+#20019=@"loc,{#10000},1,19,1,19"
+locations_default(#20019,#10000,1,19,1,19)
+hasLocation(#20018,#20019)
+#20020=*
+tokeninfo(#20020,8,#20001,6,";")
+#20021=@"loc,{#10000},1,20,1,20"
+locations_default(#20021,#10000,1,20,1,20)
+hasLocation(#20020,#20021)
+#20022=*
+tokeninfo(#20022,7,#20001,7,"var")
+#20023=@"loc,{#10000},2,1,2,3"
+locations_default(#20023,#10000,2,1,2,3)
+hasLocation(#20022,#20023)
+#20024=*
+tokeninfo(#20024,6,#20001,8,"x")
+#20025=@"loc,{#10000},2,5,2,5"
+locations_default(#20025,#10000,2,5,2,5)
+hasLocation(#20024,#20025)
+#20026=*
+tokeninfo(#20026,8,#20001,9,"=")
+#20027=@"loc,{#10000},2,7,2,7"
+locations_default(#20027,#10000,2,7,2,7)
+hasLocation(#20026,#20027)
+#20028=*
+tokeninfo(#20028,3,#20001,10,"5")
+#20029=@"loc,{#10000},2,9,2,9"
+locations_default(#20029,#10000,2,9,2,9)
+hasLocation(#20028,#20029)
+#20030=*
+tokeninfo(#20030,8,#20001,11,";")
+#20031=@"loc,{#10000},2,10,2,10"
+locations_default(#20031,#10000,2,10,2,10)
+hasLocation(#20030,#20031)
+#20032=*
+tokeninfo(#20032,6,#20001,12,"exports")
+#20033=@"loc,{#10000},3,1,3,7"
+locations_default(#20033,#10000,3,1,3,7)
+hasLocation(#20032,#20033)
+#20034=*
+tokeninfo(#20034,8,#20001,13,"=")
+#20035=@"loc,{#10000},3,9,3,9"
+locations_default(#20035,#10000,3,9,3,9)
+hasLocation(#20034,#20035)
+#20036=*
+tokeninfo(#20036,8,#20001,14,"{")
+#20037=@"loc,{#10000},3,11,3,11"
+locations_default(#20037,#10000,3,11,3,11)
+hasLocation(#20036,#20037)
+#20038=*
+tokeninfo(#20038,6,#20001,15,"x")
+#20039=@"loc,{#10000},3,13,3,13"
+locations_default(#20039,#10000,3,13,3,13)
+hasLocation(#20038,#20039)
+#20040=*
+tokeninfo(#20040,8,#20001,16,":")
+#20041=@"loc,{#10000},3,14,3,14"
+locations_default(#20041,#10000,3,14,3,14)
+hasLocation(#20040,#20041)
+#20042=*
+tokeninfo(#20042,6,#20001,17,"x")
+#20043=@"loc,{#10000},3,16,3,16"
+locations_default(#20043,#10000,3,16,3,16)
+hasLocation(#20042,#20043)
+#20044=*
+tokeninfo(#20044,8,#20001,18,"}")
+#20045=@"loc,{#10000},3,18,3,18"
+locations_default(#20045,#10000,3,18,3,18)
+hasLocation(#20044,#20045)
+#20046=*
+tokeninfo(#20046,8,#20001,19,";")
+#20047=@"loc,{#10000},3,19,3,19"
+locations_default(#20047,#10000,3,19,3,19)
+hasLocation(#20046,#20047)
+#20048=*
+tokeninfo(#20048,0,#20001,20,"")
+#20049=@"loc,{#10000},4,1,4,0"
+locations_default(#20049,#10000,4,1,4,0)
+hasLocation(#20048,#20049)
+toplevels(#20001,0)
+#20050=@"loc,{#10000},1,1,4,0"
+locations_default(#20050,#10000,1,1,4,0)
+hasLocation(#20001,#20050)
+#20051=@"module;{#10000},1,1"
+scopes(#20051,3)
+scopenodes(#20001,#20051)
+scopenesting(#20051,#20000)
+#20052=@"var;{exports};{#20051}"
+variables(#20052,"exports",#20051)
+isModule(#20001)
+isClosureModule(#20001)
+#20053=@"var;{x};{#20051}"
+variables(#20053,"x",#20051)
+#20054=*
+stmts(#20054,2,#20001,0,"goog.module('test');")
+hasLocation(#20054,#20003)
+stmtContainers(#20054,#20001)
+#20055=*
+exprs(#20055,13,#20054,0,"goog.module('test')")
+#20056=@"loc,{#10000},1,1,1,19"
+locations_default(#20056,#10000,1,1,1,19)
+hasLocation(#20055,#20056)
+enclosingStmt(#20055,#20054)
+exprContainers(#20055,#20001)
+#20057=*
+exprs(#20057,14,#20055,-1,"goog.module")
+#20058=@"loc,{#10000},1,1,1,11"
+locations_default(#20058,#10000,1,1,1,11)
+hasLocation(#20057,#20058)
+enclosingStmt(#20057,#20054)
+exprContainers(#20057,#20001)
+#20059=*
+exprs(#20059,79,#20057,0,"goog")
+hasLocation(#20059,#20009)
+enclosingStmt(#20059,#20054)
+exprContainers(#20059,#20001)
+literals("goog","goog",#20059)
+#20060=@"var;{goog};{#20000}"
+variables(#20060,"goog",#20000)
+bind(#20059,#20060)
+#20061=*
+exprs(#20061,0,#20057,1,"module")
+hasLocation(#20061,#20013)
+enclosingStmt(#20061,#20054)
+exprContainers(#20061,#20001)
+literals("module","module",#20061)
+#20062=*
+exprs(#20062,4,#20055,0,"'test'")
+hasLocation(#20062,#20017)
+enclosingStmt(#20062,#20054)
+exprContainers(#20062,#20001)
+literals("test","'test'",#20062)
+#20063=*
+stmts(#20063,18,#20001,1,"var x = 5;")
+hasLocation(#20063,#20005)
+stmtContainers(#20063,#20001)
+#20064=*
+exprs(#20064,64,#20063,0,"x = 5")
+#20065=@"loc,{#10000},2,5,2,9"
+locations_default(#20065,#10000,2,5,2,9)
+hasLocation(#20064,#20065)
+enclosingStmt(#20064,#20063)
+exprContainers(#20064,#20001)
+#20066=*
+exprs(#20066,78,#20064,0,"x")
+hasLocation(#20066,#20025)
+enclosingStmt(#20066,#20063)
+exprContainers(#20066,#20001)
+literals("x","x",#20066)
+decl(#20066,#20053)
+#20067=*
+exprs(#20067,3,#20064,1,"5")
+hasLocation(#20067,#20029)
+enclosingStmt(#20067,#20063)
+exprContainers(#20067,#20001)
+literals("5","5",#20067)
+#20068=*
+stmts(#20068,2,#20001,2,"exports = { x: x };")
+hasLocation(#20068,#20007)
+stmtContainers(#20068,#20001)
+#20069=*
+exprs(#20069,47,#20068,0,"exports = { x: x }")
+#20070=@"loc,{#10000},3,1,3,18"
+locations_default(#20070,#10000,3,1,3,18)
+hasLocation(#20069,#20070)
+enclosingStmt(#20069,#20068)
+exprContainers(#20069,#20001)
+#20071=*
+exprs(#20071,79,#20069,0,"exports")
+hasLocation(#20071,#20033)
+enclosingStmt(#20071,#20068)
+exprContainers(#20071,#20001)
+literals("exports","exports",#20071)
+bind(#20071,#20052)
+#20072=*
+exprs(#20072,8,#20069,1,"{ x: x }")
+#20073=@"loc,{#10000},3,11,3,18"
+locations_default(#20073,#10000,3,11,3,18)
+hasLocation(#20072,#20073)
+enclosingStmt(#20072,#20068)
+exprContainers(#20072,#20001)
+#20074=*
+properties(#20074,#20072,0,0,"x: x")
+#20075=@"loc,{#10000},3,13,3,16"
+locations_default(#20075,#10000,3,13,3,16)
+hasLocation(#20074,#20075)
+#20076=*
+exprs(#20076,0,#20074,0,"x")
+hasLocation(#20076,#20039)
+enclosingStmt(#20076,#20068)
+exprContainers(#20076,#20001)
+literals("x","x",#20076)
+#20077=*
+exprs(#20077,79,#20074,1,"x")
+hasLocation(#20077,#20043)
+enclosingStmt(#20077,#20068)
+exprContainers(#20077,#20001)
+literals("x","x",#20077)
+bind(#20077,#20053)
+#20078=*
+entry_cfg_node(#20078,#20001)
+#20079=@"loc,{#10000},1,1,1,0"
+locations_default(#20079,#10000,1,1,1,0)
+hasLocation(#20078,#20079)
+#20080=*
+exit_cfg_node(#20080,#20001)
+hasLocation(#20080,#20049)
+successor(#20068,#20071)
+successor(#20072,#20076)
+successor(#20077,#20074)
+successor(#20076,#20077)
+successor(#20074,#20069)
+successor(#20071,#20072)
+successor(#20069,#20080)
+successor(#20063,#20066)
+successor(#20067,#20064)
+successor(#20066,#20067)
+successor(#20064,#20068)
+successor(#20054,#20059)
+successor(#20062,#20055)
+successor(#20061,#20057)
+successor(#20059,#20061)
+successor(#20057,#20062)
+successor(#20055,#20063)
+successor(#20078,#20054)
+numlines(#10000,3,3,0)
+filetype(#10000,"javascript")
diff --git a/javascript/extractor/tests/closure/output/trap/googDotProvide.js.trap b/javascript/extractor/tests/closure/output/trap/googDotProvide.js.trap
new file mode 100644
index 00000000000..be568d46f5c
--- /dev/null
+++ b/javascript/extractor/tests/closure/output/trap/googDotProvide.js.trap
@@ -0,0 +1,205 @@
+#10000=@"/googDotProvide.js;sourcefile"
+files(#10000,"/googDotProvide.js","googDotProvide","js",0)
+#10001=@"/;folder"
+folders(#10001,"/","")
+containerparent(#10001,#10000)
+#10002=@"loc,{#10000},0,0,0,0"
+locations_default(#10002,#10000,0,0,0,0)
+hasLocation(#10000,#10002)
+#20000=@"global_scope"
+scopes(#20000,0)
+#20001=@"script;{#10000},1,1"
+#20002=*
+lines(#20002,#20001,"goog.provide('test.x');","
+")
+#20003=@"loc,{#10000},1,1,1,23"
+locations_default(#20003,#10000,1,1,1,23)
+hasLocation(#20002,#20003)
+#20004=*
+lines(#20004,#20001,"","
+")
+#20005=@"loc,{#10000},2,1,2,0"
+locations_default(#20005,#10000,2,1,2,0)
+hasLocation(#20004,#20005)
+#20006=*
+lines(#20006,#20001,"test.x = 5;","
+")
+#20007=@"loc,{#10000},3,1,3,11"
+locations_default(#20007,#10000,3,1,3,11)
+hasLocation(#20006,#20007)
+numlines(#20001,3,2,0)
+#20008=*
+tokeninfo(#20008,6,#20001,0,"goog")
+#20009=@"loc,{#10000},1,1,1,4"
+locations_default(#20009,#10000,1,1,1,4)
+hasLocation(#20008,#20009)
+#20010=*
+tokeninfo(#20010,8,#20001,1,".")
+#20011=@"loc,{#10000},1,5,1,5"
+locations_default(#20011,#10000,1,5,1,5)
+hasLocation(#20010,#20011)
+#20012=*
+tokeninfo(#20012,6,#20001,2,"provide")
+#20013=@"loc,{#10000},1,6,1,12"
+locations_default(#20013,#10000,1,6,1,12)
+hasLocation(#20012,#20013)
+#20014=*
+tokeninfo(#20014,8,#20001,3,"(")
+#20015=@"loc,{#10000},1,13,1,13"
+locations_default(#20015,#10000,1,13,1,13)
+hasLocation(#20014,#20015)
+#20016=*
+tokeninfo(#20016,4,#20001,4,"'test.x'")
+#20017=@"loc,{#10000},1,14,1,21"
+locations_default(#20017,#10000,1,14,1,21)
+hasLocation(#20016,#20017)
+#20018=*
+tokeninfo(#20018,8,#20001,5,")")
+#20019=@"loc,{#10000},1,22,1,22"
+locations_default(#20019,#10000,1,22,1,22)
+hasLocation(#20018,#20019)
+#20020=*
+tokeninfo(#20020,8,#20001,6,";")
+#20021=@"loc,{#10000},1,23,1,23"
+locations_default(#20021,#10000,1,23,1,23)
+hasLocation(#20020,#20021)
+#20022=*
+tokeninfo(#20022,6,#20001,7,"test")
+#20023=@"loc,{#10000},3,1,3,4"
+locations_default(#20023,#10000,3,1,3,4)
+hasLocation(#20022,#20023)
+#20024=*
+tokeninfo(#20024,8,#20001,8,".")
+#20025=@"loc,{#10000},3,5,3,5"
+locations_default(#20025,#10000,3,5,3,5)
+hasLocation(#20024,#20025)
+#20026=*
+tokeninfo(#20026,6,#20001,9,"x")
+#20027=@"loc,{#10000},3,6,3,6"
+locations_default(#20027,#10000,3,6,3,6)
+hasLocation(#20026,#20027)
+#20028=*
+tokeninfo(#20028,8,#20001,10,"=")
+#20029=@"loc,{#10000},3,8,3,8"
+locations_default(#20029,#10000,3,8,3,8)
+hasLocation(#20028,#20029)
+#20030=*
+tokeninfo(#20030,3,#20001,11,"5")
+#20031=@"loc,{#10000},3,10,3,10"
+locations_default(#20031,#10000,3,10,3,10)
+hasLocation(#20030,#20031)
+#20032=*
+tokeninfo(#20032,8,#20001,12,";")
+#20033=@"loc,{#10000},3,11,3,11"
+locations_default(#20033,#10000,3,11,3,11)
+hasLocation(#20032,#20033)
+#20034=*
+tokeninfo(#20034,0,#20001,13,"")
+#20035=@"loc,{#10000},4,1,4,0"
+locations_default(#20035,#10000,4,1,4,0)
+hasLocation(#20034,#20035)
+toplevels(#20001,0)
+#20036=@"loc,{#10000},1,1,4,0"
+locations_default(#20036,#10000,1,1,4,0)
+hasLocation(#20001,#20036)
+#20037=*
+stmts(#20037,2,#20001,0,"goog.pr ... st.x');")
+hasLocation(#20037,#20003)
+stmtContainers(#20037,#20001)
+#20038=*
+exprs(#20038,13,#20037,0,"goog.pr ... est.x')")
+#20039=@"loc,{#10000},1,1,1,22"
+locations_default(#20039,#10000,1,1,1,22)
+hasLocation(#20038,#20039)
+enclosingStmt(#20038,#20037)
+exprContainers(#20038,#20001)
+#20040=*
+exprs(#20040,14,#20038,-1,"goog.provide")
+#20041=@"loc,{#10000},1,1,1,12"
+locations_default(#20041,#10000,1,1,1,12)
+hasLocation(#20040,#20041)
+enclosingStmt(#20040,#20037)
+exprContainers(#20040,#20001)
+#20042=*
+exprs(#20042,79,#20040,0,"goog")
+hasLocation(#20042,#20009)
+enclosingStmt(#20042,#20037)
+exprContainers(#20042,#20001)
+literals("goog","goog",#20042)
+#20043=@"var;{goog};{#20000}"
+variables(#20043,"goog",#20000)
+bind(#20042,#20043)
+#20044=*
+exprs(#20044,0,#20040,1,"provide")
+hasLocation(#20044,#20013)
+enclosingStmt(#20044,#20037)
+exprContainers(#20044,#20001)
+literals("provide","provide",#20044)
+#20045=*
+exprs(#20045,4,#20038,0,"'test.x'")
+hasLocation(#20045,#20017)
+enclosingStmt(#20045,#20037)
+exprContainers(#20045,#20001)
+literals("test.x","'test.x'",#20045)
+#20046=*
+stmts(#20046,2,#20001,1,"test.x = 5;")
+hasLocation(#20046,#20007)
+stmtContainers(#20046,#20001)
+#20047=*
+exprs(#20047,47,#20046,0,"test.x = 5")
+#20048=@"loc,{#10000},3,1,3,10"
+locations_default(#20048,#10000,3,1,3,10)
+hasLocation(#20047,#20048)
+enclosingStmt(#20047,#20046)
+exprContainers(#20047,#20001)
+#20049=*
+exprs(#20049,14,#20047,0,"test.x")
+#20050=@"loc,{#10000},3,1,3,6"
+locations_default(#20050,#10000,3,1,3,6)
+hasLocation(#20049,#20050)
+enclosingStmt(#20049,#20046)
+exprContainers(#20049,#20001)
+#20051=*
+exprs(#20051,79,#20049,0,"test")
+hasLocation(#20051,#20023)
+enclosingStmt(#20051,#20046)
+exprContainers(#20051,#20001)
+literals("test","test",#20051)
+#20052=@"var;{test};{#20000}"
+variables(#20052,"test",#20000)
+bind(#20051,#20052)
+#20053=*
+exprs(#20053,0,#20049,1,"x")
+hasLocation(#20053,#20027)
+enclosingStmt(#20053,#20046)
+exprContainers(#20053,#20001)
+literals("x","x",#20053)
+#20054=*
+exprs(#20054,3,#20047,1,"5")
+hasLocation(#20054,#20031)
+enclosingStmt(#20054,#20046)
+exprContainers(#20054,#20001)
+literals("5","5",#20054)
+#20055=*
+entry_cfg_node(#20055,#20001)
+#20056=@"loc,{#10000},1,1,1,0"
+locations_default(#20056,#10000,1,1,1,0)
+hasLocation(#20055,#20056)
+#20057=*
+exit_cfg_node(#20057,#20001)
+hasLocation(#20057,#20035)
+successor(#20046,#20051)
+successor(#20054,#20047)
+successor(#20053,#20049)
+successor(#20051,#20053)
+successor(#20049,#20054)
+successor(#20047,#20057)
+successor(#20037,#20042)
+successor(#20045,#20038)
+successor(#20044,#20040)
+successor(#20042,#20044)
+successor(#20040,#20045)
+successor(#20038,#20046)
+successor(#20055,#20037)
+numlines(#10000,3,2,0)
+filetype(#10000,"javascript")
diff --git a/javascript/extractor/tests/es2015/output/trap/export1.js.trap b/javascript/extractor/tests/es2015/output/trap/export1.js.trap
index b91210e75b3..d941f194304 100644
--- a/javascript/extractor/tests/es2015/output/trap/export1.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export1.js.trap
@@ -57,6 +57,7 @@ scopes(#20018,3)
scopenodes(#20001,#20018)
scopenesting(#20018,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20019=@"var;{x};{#20018}"
variables(#20019,"x",#20018)
#20020=*
diff --git a/javascript/extractor/tests/es2015/output/trap/export10.js.trap b/javascript/extractor/tests/es2015/output/trap/export10.js.trap
index b3a45738d78..11992095dfe 100644
--- a/javascript/extractor/tests/es2015/output/trap/export10.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export10.js.trap
@@ -85,6 +85,7 @@ scopes(#20029,3)
scopenodes(#20001,#20029)
scopenesting(#20029,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20030=@"var;{f};{#20029}"
variables(#20030,"f",#20029)
#20031=*
diff --git a/javascript/extractor/tests/es2015/output/trap/export11.js.trap b/javascript/extractor/tests/es2015/output/trap/export11.js.trap
index b5864d6de79..25f9caf1b81 100644
--- a/javascript/extractor/tests/es2015/output/trap/export11.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export11.js.trap
@@ -55,6 +55,7 @@ scopes(#20017,3)
scopenodes(#20001,#20017)
scopenesting(#20017,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20018=*
stmts(#20018,29,#20001,0,"export ... lass {}")
hasLocation(#20018,#20003)
diff --git a/javascript/extractor/tests/es2015/output/trap/export2.js.trap b/javascript/extractor/tests/es2015/output/trap/export2.js.trap
index 8ae4aee565e..27ec1b0d1d1 100644
--- a/javascript/extractor/tests/es2015/output/trap/export2.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export2.js.trap
@@ -67,6 +67,7 @@ scopes(#20022,3)
scopenodes(#20001,#20022)
scopenesting(#20022,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20023=@"var;{f};{#20022}"
variables(#20023,"f",#20022)
#20024=*
diff --git a/javascript/extractor/tests/es2015/output/trap/export3.js.trap b/javascript/extractor/tests/es2015/output/trap/export3.js.trap
index 9649c8047c0..bcb6b619e12 100644
--- a/javascript/extractor/tests/es2015/output/trap/export3.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export3.js.trap
@@ -72,6 +72,7 @@ scopes(#20024,3)
scopenodes(#20001,#20024)
scopenesting(#20024,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20025=@"var;{f};{#20024}"
variables(#20025,"f",#20024)
#20026=*
diff --git a/javascript/extractor/tests/es2015/output/trap/export4.js.trap b/javascript/extractor/tests/es2015/output/trap/export4.js.trap
index 789561f2787..541992fd903 100644
--- a/javascript/extractor/tests/es2015/output/trap/export4.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export4.js.trap
@@ -67,6 +67,7 @@ scopes(#20022,3)
scopenodes(#20001,#20022)
scopenesting(#20022,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20023=*
stmts(#20023,29,#20001,0,"export ... n () {}")
#20024=@"loc,{#10000},1,1,1,29"
diff --git a/javascript/extractor/tests/es2015/output/trap/export5.js.trap b/javascript/extractor/tests/es2015/output/trap/export5.js.trap
index cea3d37029e..11aee4bdd59 100644
--- a/javascript/extractor/tests/es2015/output/trap/export5.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export5.js.trap
@@ -125,6 +125,7 @@ scopes(#20045,3)
scopenodes(#20001,#20045)
scopenesting(#20045,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20046=@"var;{x};{#20045}"
variables(#20046,"x",#20045)
#20047=@"var;{y};{#20045}"
diff --git a/javascript/extractor/tests/es2015/output/trap/export6.js.trap b/javascript/extractor/tests/es2015/output/trap/export6.js.trap
index 9667678d047..be811781ee0 100644
--- a/javascript/extractor/tests/es2015/output/trap/export6.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export6.js.trap
@@ -52,6 +52,7 @@ scopes(#20016,3)
scopenodes(#20001,#20016)
scopenesting(#20016,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20017=*
stmts(#20017,28,#20001,0,"export * from 'foo';")
hasLocation(#20017,#20003)
diff --git a/javascript/extractor/tests/es2015/output/trap/export7.js.trap b/javascript/extractor/tests/es2015/output/trap/export7.js.trap
index a2e6ece468e..3856ac50b40 100644
--- a/javascript/extractor/tests/es2015/output/trap/export7.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export7.js.trap
@@ -82,6 +82,7 @@ scopes(#20028,3)
scopenodes(#20001,#20028)
scopenesting(#20028,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20029=*
stmts(#20029,30,#20001,0,"export ... 'foo';")
hasLocation(#20029,#20003)
diff --git a/javascript/extractor/tests/es2015/output/trap/export8.js.trap b/javascript/extractor/tests/es2015/output/trap/export8.js.trap
index a2b073979af..56b6518f11e 100644
--- a/javascript/extractor/tests/es2015/output/trap/export8.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export8.js.trap
@@ -75,6 +75,7 @@ scopes(#20025,3)
scopenodes(#20001,#20025)
scopenesting(#20025,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20026=*
stmts(#20026,2,#20001,0,"foo = 42;")
hasLocation(#20026,#20003)
diff --git a/javascript/extractor/tests/es2015/output/trap/export9.js.trap b/javascript/extractor/tests/es2015/output/trap/export9.js.trap
index 5ce81f3881a..65237923a52 100644
--- a/javascript/extractor/tests/es2015/output/trap/export9.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/export9.js.trap
@@ -75,6 +75,7 @@ scopes(#20025,3)
scopenodes(#20001,#20025)
scopenesting(#20025,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20026=@"var;{C};{#20025}"
variables(#20026,"C",#20025)
#20027=@"local_type_name;{C};{#20025}"
diff --git a/javascript/extractor/tests/es2015/output/trap/import1.js.trap b/javascript/extractor/tests/es2015/output/trap/import1.js.trap
index 698f6dc87c4..7e28e03e25f 100644
--- a/javascript/extractor/tests/es2015/output/trap/import1.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/import1.js.trap
@@ -52,6 +52,7 @@ scopes(#20016,3)
scopenodes(#20001,#20016)
scopenesting(#20016,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20017=@"var;{x};{#20016}"
variables(#20017,"x",#20016)
#20018=@"local_type_name;{x};{#20016}"
diff --git a/javascript/extractor/tests/es2015/output/trap/import2.js.trap b/javascript/extractor/tests/es2015/output/trap/import2.js.trap
index a59a9455879..b6385936355 100644
--- a/javascript/extractor/tests/es2015/output/trap/import2.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/import2.js.trap
@@ -62,6 +62,7 @@ scopes(#20020,3)
scopenodes(#20001,#20020)
scopenesting(#20020,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20021=@"var;{y};{#20020}"
variables(#20021,"y",#20020)
#20022=@"local_type_name;{y};{#20020}"
diff --git a/javascript/extractor/tests/es2015/output/trap/import3.js.trap b/javascript/extractor/tests/es2015/output/trap/import3.js.trap
index 43656eb918f..135adff01a2 100644
--- a/javascript/extractor/tests/es2015/output/trap/import3.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/import3.js.trap
@@ -72,6 +72,7 @@ scopes(#20024,3)
scopenodes(#20001,#20024)
scopenesting(#20024,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20025=@"var;{z};{#20024}"
variables(#20025,"z",#20024)
#20026=@"local_type_name;{z};{#20024}"
diff --git a/javascript/extractor/tests/es2015/output/trap/import4.js.trap b/javascript/extractor/tests/es2015/output/trap/import4.js.trap
index bb61d9c5706..2243a8fcd20 100644
--- a/javascript/extractor/tests/es2015/output/trap/import4.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/import4.js.trap
@@ -82,6 +82,7 @@ scopes(#20028,3)
scopenodes(#20001,#20028)
scopenesting(#20028,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20029=@"var;{x};{#20028}"
variables(#20029,"x",#20028)
#20030=@"var;{z};{#20028}"
diff --git a/javascript/extractor/tests/es2015/output/trap/import5.js.trap b/javascript/extractor/tests/es2015/output/trap/import5.js.trap
index 94c4cd1bfc2..ea5ea8a3836 100644
--- a/javascript/extractor/tests/es2015/output/trap/import5.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/import5.js.trap
@@ -62,6 +62,7 @@ scopes(#20020,3)
scopenodes(#20001,#20020)
scopenesting(#20020,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20021=@"var;{foo};{#20020}"
variables(#20021,"foo",#20020)
#20022=@"local_namespace_name;{foo};{#20020}"
diff --git a/javascript/extractor/tests/es2015/output/trap/import6.js.trap b/javascript/extractor/tests/es2015/output/trap/import6.js.trap
index 7e5540aaf9a..252b32338cf 100644
--- a/javascript/extractor/tests/es2015/output/trap/import6.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/import6.js.trap
@@ -42,6 +42,7 @@ scopes(#20012,3)
scopenodes(#20001,#20012)
scopenesting(#20012,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20013=*
stmts(#20013,27,#20001,0,"import 'foo';")
hasLocation(#20013,#20003)
diff --git a/javascript/extractor/tests/es2015/output/trap/import7.js.trap b/javascript/extractor/tests/es2015/output/trap/import7.js.trap
index 5ad6904fee2..974a3cc10b7 100644
--- a/javascript/extractor/tests/es2015/output/trap/import7.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/import7.js.trap
@@ -126,6 +126,7 @@ scopes(#20045,3)
scopenodes(#20001,#20045)
scopenesting(#20045,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20046=@"var;{x};{#20045}"
variables(#20046,"x",#20045)
#20047=@"var;{z};{#20045}"
diff --git a/javascript/extractor/tests/es2015/output/trap/nested_import.js.trap b/javascript/extractor/tests/es2015/output/trap/nested_import.js.trap
index cbad5c031dc..074cc3dce4d 100644
--- a/javascript/extractor/tests/es2015/output/trap/nested_import.js.trap
+++ b/javascript/extractor/tests/es2015/output/trap/nested_import.js.trap
@@ -179,6 +179,7 @@ scopes(#20064,3)
scopenodes(#20001,#20064)
scopenesting(#20064,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20065=@"var;{x};{#20064}"
variables(#20065,"x",#20064)
#20066=@"var;{y};{#20064}"
diff --git a/javascript/extractor/tests/es2017/output/trap/async-await.js.trap b/javascript/extractor/tests/es2017/output/trap/async-await.js.trap
index 067c2b17191..ec4802a50a2 100644
--- a/javascript/extractor/tests/es2017/output/trap/async-await.js.trap
+++ b/javascript/extractor/tests/es2017/output/trap/async-await.js.trap
@@ -196,6 +196,7 @@ scopes(#20069,3)
scopenodes(#20001,#20069)
scopenesting(#20069,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20070=@"var;{foo};{#20069}"
variables(#20070,"foo",#20069)
#20071=*
diff --git a/javascript/extractor/tests/es2017/output/trap/export-async-1.js.trap b/javascript/extractor/tests/es2017/output/trap/export-async-1.js.trap
index c5598843cdd..89d065a8b85 100644
--- a/javascript/extractor/tests/es2017/output/trap/export-async-1.js.trap
+++ b/javascript/extractor/tests/es2017/output/trap/export-async-1.js.trap
@@ -80,6 +80,7 @@ scopes(#20027,3)
scopenodes(#20001,#20027)
scopenesting(#20027,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20028=@"var;{f};{#20027}"
variables(#20028,"f",#20027)
#20029=*
diff --git a/javascript/extractor/tests/es2017/output/trap/export-async-2.js.trap b/javascript/extractor/tests/es2017/output/trap/export-async-2.js.trap
index d83028ac5cc..651e897f0ae 100644
--- a/javascript/extractor/tests/es2017/output/trap/export-async-2.js.trap
+++ b/javascript/extractor/tests/es2017/output/trap/export-async-2.js.trap
@@ -75,6 +75,7 @@ scopes(#20025,3)
scopenodes(#20001,#20025)
scopenesting(#20025,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20026=*
stmts(#20026,29,#20001,0,"export ... n () {}")
#20027=@"loc,{#10000},1,1,1,35"
diff --git a/javascript/extractor/tests/esnext/output/trap/dynamic-import.js.trap b/javascript/extractor/tests/esnext/output/trap/dynamic-import.js.trap
index 888665939e0..5539e654b68 100644
--- a/javascript/extractor/tests/esnext/output/trap/dynamic-import.js.trap
+++ b/javascript/extractor/tests/esnext/output/trap/dynamic-import.js.trap
@@ -192,6 +192,7 @@ scopes(#20071,3)
scopenodes(#20001,#20071)
scopenesting(#20071,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20072=*
stmts(#20072,2,#20001,0,"import(""m"");")
hasLocation(#20072,#20003)
diff --git a/javascript/extractor/tests/extensions/output/trap/tst.es6.trap b/javascript/extractor/tests/extensions/output/trap/tst.es6.trap
index 1c61912b6f2..26a437438c1 100644
--- a/javascript/extractor/tests/extensions/output/trap/tst.es6.trap
+++ b/javascript/extractor/tests/extensions/output/trap/tst.es6.trap
@@ -65,6 +65,7 @@ scopes(#20021,3)
scopenodes(#20001,#20021)
scopenesting(#20021,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20022=*
stmts(#20022,2,#20001,0,"console ... ES6"");")
hasLocation(#20022,#20003)
diff --git a/javascript/extractor/tests/extensions/output/trap/tst2.es.trap b/javascript/extractor/tests/extensions/output/trap/tst2.es.trap
index 6910645b5a8..30172094f29 100644
--- a/javascript/extractor/tests/extensions/output/trap/tst2.es.trap
+++ b/javascript/extractor/tests/extensions/output/trap/tst2.es.trap
@@ -65,6 +65,7 @@ scopes(#20021,3)
scopenodes(#20001,#20021)
scopenesting(#20021,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20022=*
stmts(#20022,2,#20001,0,"console ... o ES"");")
hasLocation(#20022,#20003)
diff --git a/javascript/extractor/tests/flow/output/trap/anonFunctionWithoutParens.js.trap b/javascript/extractor/tests/flow/output/trap/anonFunctionWithoutParens.js.trap
index a1f46308211..3385f035c92 100644
--- a/javascript/extractor/tests/flow/output/trap/anonFunctionWithoutParens.js.trap
+++ b/javascript/extractor/tests/flow/output/trap/anonFunctionWithoutParens.js.trap
@@ -77,6 +77,7 @@ scopes(#20026,3)
scopenodes(#20001,#20026)
scopenesting(#20026,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20027=*
entry_cfg_node(#20027,#20001)
#20028=@"loc,{#10000},1,1,1,0"
diff --git a/javascript/extractor/tests/flow/output/trap/anonIndexer.js.trap b/javascript/extractor/tests/flow/output/trap/anonIndexer.js.trap
index 11b67847df3..4b144a97faa 100644
--- a/javascript/extractor/tests/flow/output/trap/anonIndexer.js.trap
+++ b/javascript/extractor/tests/flow/output/trap/anonIndexer.js.trap
@@ -90,6 +90,7 @@ scopes(#20031,3)
scopenodes(#20001,#20031)
scopenesting(#20031,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20032=*
entry_cfg_node(#20032,#20001)
#20033=@"loc,{#10000},1,1,1,0"
diff --git a/javascript/extractor/tests/flow/output/trap/declared-module-imports.js.trap b/javascript/extractor/tests/flow/output/trap/declared-module-imports.js.trap
index d8b9dbbcd55..18c6aad67d3 100644
--- a/javascript/extractor/tests/flow/output/trap/declared-module-imports.js.trap
+++ b/javascript/extractor/tests/flow/output/trap/declared-module-imports.js.trap
@@ -133,6 +133,7 @@ scopes(#20046,3)
scopenodes(#20001,#20046)
scopenesting(#20046,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20047=*
entry_cfg_node(#20047,#20001)
#20048=@"loc,{#10000},1,1,1,0"
diff --git a/javascript/extractor/tests/flow/output/trap/export.js.trap b/javascript/extractor/tests/flow/output/trap/export.js.trap
index eafcd7d1e33..6e68301a275 100644
--- a/javascript/extractor/tests/flow/output/trap/export.js.trap
+++ b/javascript/extractor/tests/flow/output/trap/export.js.trap
@@ -223,6 +223,7 @@ scopes(#20083,3)
scopenodes(#20001,#20083)
scopenesting(#20083,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20084=*
stmts(#20084,30,#20001,0,"export ... om ""m"";")
hasLocation(#20084,#20003)
diff --git a/javascript/extractor/tests/flow/output/trap/exportOpaqueType.js.trap b/javascript/extractor/tests/flow/output/trap/exportOpaqueType.js.trap
index 9eb9d7a7a64..2b23e617e92 100644
--- a/javascript/extractor/tests/flow/output/trap/exportOpaqueType.js.trap
+++ b/javascript/extractor/tests/flow/output/trap/exportOpaqueType.js.trap
@@ -75,6 +75,7 @@ scopes(#20025,3)
scopenodes(#20001,#20025)
scopenesting(#20025,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20026=*
entry_cfg_node(#20026,#20001)
#20027=@"loc,{#10000},1,1,1,0"
diff --git a/javascript/extractor/tests/flow/output/trap/importType.js.trap b/javascript/extractor/tests/flow/output/trap/importType.js.trap
index bdae34bb5c5..51f24931866 100644
--- a/javascript/extractor/tests/flow/output/trap/importType.js.trap
+++ b/javascript/extractor/tests/flow/output/trap/importType.js.trap
@@ -186,6 +186,7 @@ scopes(#20069,3)
scopenodes(#20001,#20069)
scopenesting(#20069,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20070=@"var;{type};{#20069}"
variables(#20070,"type",#20069)
#20071=@"local_type_name;{type};{#20069}"
diff --git a/javascript/extractor/tests/flow/output/trap/importTypeInDeclaredModule.js.trap b/javascript/extractor/tests/flow/output/trap/importTypeInDeclaredModule.js.trap
index b5b081fa745..a4feff6195c 100644
--- a/javascript/extractor/tests/flow/output/trap/importTypeInDeclaredModule.js.trap
+++ b/javascript/extractor/tests/flow/output/trap/importTypeInDeclaredModule.js.trap
@@ -210,6 +210,7 @@ scopes(#20072,3)
scopenodes(#20001,#20072)
scopenesting(#20072,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20073=*
entry_cfg_node(#20073,#20001)
#20074=@"loc,{#10000},1,1,1,0"
diff --git a/javascript/extractor/tests/flow/output/trap/variance.js.trap b/javascript/extractor/tests/flow/output/trap/variance.js.trap
index 1524741d813..e032b8259d1 100644
--- a/javascript/extractor/tests/flow/output/trap/variance.js.trap
+++ b/javascript/extractor/tests/flow/output/trap/variance.js.trap
@@ -165,6 +165,7 @@ scopes(#20058,3)
scopenodes(#20001,#20058)
scopenesting(#20058,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20059=@"var;{Foo};{#20058}"
variables(#20059,"Foo",#20058)
#20060=@"local_type_name;{Foo};{#20058}"
diff --git a/javascript/extractor/tests/html/output/trap/module.html.trap b/javascript/extractor/tests/html/output/trap/module.html.trap
index 94d2e930ba2..a198c52ef7a 100644
--- a/javascript/extractor/tests/html/output/trap/module.html.trap
+++ b/javascript/extractor/tests/html/output/trap/module.html.trap
@@ -104,6 +104,7 @@ scopes(#20035,3)
scopenodes(#20001,#20035)
scopenesting(#20035,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20036=@"var;{foo};{#20035}"
variables(#20036,"foo",#20035)
#20037=@"local_type_name;{foo};{#20035}"
diff --git a/javascript/extractor/tests/html/output/trap/tst2.html.trap b/javascript/extractor/tests/html/output/trap/tst2.html.trap
index 15a80be2b9e..03904b418b9 100644
--- a/javascript/extractor/tests/html/output/trap/tst2.html.trap
+++ b/javascript/extractor/tests/html/output/trap/tst2.html.trap
@@ -175,6 +175,7 @@ scopes(#20053,3)
scopenodes(#20037,#20053)
scopenesting(#20053,#20000)
isModule(#20037)
+isES2015Module(#20037)
#20054=@"var;{inAModule};{#20053}"
variables(#20054,"inAModule",#20053)
#20055=*
@@ -259,6 +260,7 @@ scopes(#20079,3)
scopenodes(#20061,#20079)
scopenesting(#20079,#20000)
isModule(#20061)
+isES2015Module(#20061)
#20080=@"var;{f};{#20079}"
variables(#20080,"f",#20079)
#20081=@"local_type_name;{f};{#20079}"
diff --git a/javascript/extractor/tests/node/output/trap/tst.mjs.trap b/javascript/extractor/tests/node/output/trap/tst.mjs.trap
index 45d0f723864..dce8cdeaebe 100644
--- a/javascript/extractor/tests/node/output/trap/tst.mjs.trap
+++ b/javascript/extractor/tests/node/output/trap/tst.mjs.trap
@@ -50,6 +50,7 @@ scopes(#20016,3)
scopenodes(#20001,#20016)
scopenesting(#20016,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20017=@"var;{x};{#20016}"
variables(#20017,"x",#20016)
#20018=*
diff --git a/javascript/extractor/tests/ts/output/trap/decorators.ts.trap b/javascript/extractor/tests/ts/output/trap/decorators.ts.trap
index e8c22cfaa84..1c3b9822670 100644
--- a/javascript/extractor/tests/ts/output/trap/decorators.ts.trap
+++ b/javascript/extractor/tests/ts/output/trap/decorators.ts.trap
@@ -310,6 +310,7 @@ scopes(#20112,3)
scopenodes(#20001,#20112)
scopenesting(#20112,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20113=@"var;{fun};{#20112}"
variables(#20113,"fun",#20112)
#20114=@"var;{Class};{#20112}"
diff --git a/javascript/extractor/tests/ts/output/trap/export.ts.trap b/javascript/extractor/tests/ts/output/trap/export.ts.trap
index d98a2306d0b..fe88825fc6a 100644
--- a/javascript/extractor/tests/ts/output/trap/export.ts.trap
+++ b/javascript/extractor/tests/ts/output/trap/export.ts.trap
@@ -154,6 +154,7 @@ scopes(#20055,3)
scopenodes(#20001,#20055)
scopenesting(#20055,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20056=@"var;{f};{#20055}"
variables(#20056,"f",#20055)
#20057=@"var;{foo};{#20055}"
diff --git a/javascript/extractor/tests/ts/output/trap/export2.ts.trap b/javascript/extractor/tests/ts/output/trap/export2.ts.trap
index e45d6e63669..8511f3732ac 100644
--- a/javascript/extractor/tests/ts/output/trap/export2.ts.trap
+++ b/javascript/extractor/tests/ts/output/trap/export2.ts.trap
@@ -50,6 +50,7 @@ scopes(#20015,3)
scopenodes(#20001,#20015)
scopenesting(#20015,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20016=*
stmts(#20016,29,#20001,0,"export default 42;")
hasLocation(#20016,#20003)
diff --git a/javascript/extractor/tests/ts/output/trap/exportasnamespace.d.ts.trap b/javascript/extractor/tests/ts/output/trap/exportasnamespace.d.ts.trap
index eea4d7e1b4d..8b16ab7bef6 100644
--- a/javascript/extractor/tests/ts/output/trap/exportasnamespace.d.ts.trap
+++ b/javascript/extractor/tests/ts/output/trap/exportasnamespace.d.ts.trap
@@ -96,6 +96,7 @@ scopes(#20033,3)
scopenodes(#20001,#20033)
scopenesting(#20033,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20034=*
stmts(#20034,30,#20001,0,"export ... foo();")
hasLocation(#20034,#20003)
diff --git a/javascript/extractor/tests/ts/output/trap/exportassign.ts.trap b/javascript/extractor/tests/ts/output/trap/exportassign.ts.trap
index 092b9fcb8eb..035f5346697 100644
--- a/javascript/extractor/tests/ts/output/trap/exportassign.ts.trap
+++ b/javascript/extractor/tests/ts/output/trap/exportassign.ts.trap
@@ -50,6 +50,7 @@ scopes(#20015,3)
scopenodes(#20001,#20015)
scopenesting(#20015,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20016=*
stmts(#20016,33,#20001,0,"export = 42;")
hasLocation(#20016,#20003)
diff --git a/javascript/extractor/tests/ts/output/trap/importExport.ts.trap b/javascript/extractor/tests/ts/output/trap/importExport.ts.trap
index cf72ba04d5f..4b9267996c0 100644
--- a/javascript/extractor/tests/ts/output/trap/importExport.ts.trap
+++ b/javascript/extractor/tests/ts/output/trap/importExport.ts.trap
@@ -117,6 +117,7 @@ scopes(#20041,3)
scopenodes(#20001,#20041)
scopenesting(#20041,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20042=@"var;{Something};{#20041}"
variables(#20042,"Something",#20041)
#20043=@"var;{importExport};{#20041}"
diff --git a/javascript/extractor/tests/ts/output/trap/importassign.ts.trap b/javascript/extractor/tests/ts/output/trap/importassign.ts.trap
index 04788c62f71..10f8c933632 100644
--- a/javascript/extractor/tests/ts/output/trap/importassign.ts.trap
+++ b/javascript/extractor/tests/ts/output/trap/importassign.ts.trap
@@ -70,6 +70,7 @@ scopes(#20023,3)
scopenodes(#20001,#20023)
scopenesting(#20023,#20000)
isModule(#20001)
+isES2015Module(#20001)
#20024=@"var;{x};{#20023}"
variables(#20024,"x",#20023)
#20025=@"local_type_name;{x};{#20023}"
diff --git a/javascript/ql/src/Declarations/DeadStoreOfLocal.ql b/javascript/ql/src/Declarations/DeadStoreOfLocal.ql
index c996c74a723..d738227d1a5 100644
--- a/javascript/ql/src/Declarations/DeadStoreOfLocal.ql
+++ b/javascript/ql/src/Declarations/DeadStoreOfLocal.ql
@@ -49,5 +49,7 @@ where
// don't flag assignments in externs
not dead.(ASTNode).inExternsFile() and
// don't flag exported variables
- not any(ES2015Module m).exportsAs(v, _)
+ not any(ES2015Module m).exportsAs(v, _) and
+ // don't flag 'exports' assignments in closure modules
+ not any(Closure::ClosureModule mod).getExportsVariable() = v
select dead, "This definition of " + v.getName() + " is useless, since its value is never read."
diff --git a/javascript/ql/src/NodeJS/InvalidExport.ql b/javascript/ql/src/NodeJS/InvalidExport.ql
index df9a9b3387e..08925ab07d2 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 Closure::ClosureModule
select assgn, "Assigning to 'exports' does not export anything."
diff --git a/javascript/ql/src/javascript.qll b/javascript/ql/src/javascript.qll
index bbda30954e5..376d6fb3a7b 100644
--- a/javascript/ql/src/javascript.qll
+++ b/javascript/ql/src/javascript.qll
@@ -8,6 +8,7 @@ import semmle.javascript.AST
import semmle.javascript.BasicBlocks
import semmle.javascript.CFG
import semmle.javascript.Classes
+import semmle.javascript.Closure
import semmle.javascript.Comments
import semmle.javascript.Concepts
import semmle.javascript.Constants
diff --git a/javascript/ql/src/semmle/javascript/Closure.qll b/javascript/ql/src/semmle/javascript/Closure.qll
index 616e7746d7c..bea775c29ff 100644
--- a/javascript/ql/src/semmle/javascript/Closure.qll
+++ b/javascript/ql/src/semmle/javascript/Closure.qll
@@ -1,75 +1,209 @@
/**
- * Provides classes for working with Google Closure code.
+ * Provides classes for working with the Closure-Library module system.
*/
import javascript
-/**
- * A call to a function in the `goog` namespace such as `goog.provide` or `goog.load`.
- */
-class GoogFunctionCall extends CallExpr {
- GoogFunctionCall() {
- exists(GlobalVariable gv | gv.getName() = "goog" |
- this.getCallee().(DotExpr).getBase() = gv.getAnAccess()
+module Closure {
+ /**
+ * A call to a function in the `goog` namespace such as `goog.provide` or `goog.load`.
+ */
+ class GoogFunctionCall extends CallExpr {
+ GoogFunctionCall() {
+ exists(GlobalVariable gv | gv.getName() = "goog" |
+ this.getCallee().(DotExpr).getBase() = gv.getAnAccess()
+ )
+ }
+
+ /** Gets the name of the invoked function. */
+ string getFunctionName() { result = getCallee().(DotExpr).getPropertyName() }
+ }
+
+ /**
+ * An expression statement consisting of a call to a function
+ * in the `goog` namespace.
+ */
+ class GoogFunctionCallStmt extends ExprStmt {
+ GoogFunctionCallStmt() { super.getExpr() instanceof GoogFunctionCall }
+
+ override GoogFunctionCall getExpr() { result = super.getExpr() }
+
+ /** Gets the name of the invoked function. */
+ string getFunctionName() { result = getExpr().getFunctionName() }
+
+ /** Gets the `i`th argument to the invoked function. */
+ Expr getArgument(int i) { result = getExpr().getArgument(i) }
+
+ /** Gets an argument to the invoked function. */
+ Expr getAnArgument() { result = getArgument(_) }
+ }
+
+ abstract private class GoogNamespaceRef extends ExprOrStmt { abstract string getNamespaceId(); }
+
+ /**
+ * A call to `goog.provide`.
+ */
+ class GoogProvide extends GoogFunctionCallStmt, GoogNamespaceRef {
+ GoogProvide() { getFunctionName() = "provide" }
+
+ /** Gets the identifier of the namespace created by this call. */
+ override string getNamespaceId() { result = getArgument(0).getStringValue() }
+ }
+
+ /**
+ * A call to `goog.require`.
+ */
+ class GoogRequire extends GoogFunctionCall, GoogNamespaceRef {
+ GoogRequire() { getFunctionName() = "require" }
+
+ /** Gets the identifier of the namespace imported by this call. */
+ override string getNamespaceId() { result = getArgument(0).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 call to `goog.module` or `goog.declareModuleId`.
+ */
+ 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).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().getVariable("exports")
+ }
+
+ override predicate exports(string name, ASTNode export) {
+ exists(DataFlow::PropWrite write, Expr base |
+ write.getAstNode() = export and
+ write.writes(base.flow(), name, _) and
+ (
+ base = getExportsVariable().getAReference()
+ or
+ base = getExportsVariable().getAnAssignedExpr()
+ )
+ )
+ }
+ }
+
+ /**
+ * 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().(ExprStmt).getExpr() instanceof GoogRequire
+ }
+
+ /** Gets the identifier of a namespace required by this module. */
+ 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 isLibraryNamespacePath(string name) {
+ exists(string namespace | namespace = any(GoogNamespaceRef provide).getNamespaceId() |
+ name = namespace.substring(0, namespace.indexOf("."))
+ or
+ name = namespace
)
}
- /** Gets the name of the invoked function. */
- string getFunctionName() { result = getCallee().(DotExpr).getPropertyName() }
-}
-
-/**
- * An expression statement consisting of a call to a function
- * in the `goog` namespace.
- */
-class GoogFunctionCallStmt extends ExprStmt {
- GoogFunctionCallStmt() { super.getExpr() instanceof GoogFunctionCall }
-
- override GoogFunctionCall getExpr() { result = super.getExpr() }
-
- /** Gets the name of the invoked function. */
- string getFunctionName() { result = getExpr().getFunctionName() }
-
- /** Gets the `i`th argument to the invoked function. */
- Expr getArgument(int i) { result = getExpr().getArgument(i) }
-
- /** Gets an argument to the invoked function. */
- Expr getAnArgument() { result = getArgument(_) }
-}
-
-/**
- * A call to `goog.provide`.
- */
-class GoogProvide extends GoogFunctionCallStmt {
- GoogProvide() { getFunctionName() = "provide" }
-
- /** Gets the identifier of the namespace created by this call. */
- string getNamespaceId() { result = getArgument(0).(ConstantString).getStringValue() }
-}
-
-/**
- * A call to `goog.require`.
- */
-class GoogRequire extends GoogFunctionCallStmt {
- GoogRequire() { getFunctionName() = "require" }
-
- /** Gets the identifier of the namespace imported by this call. */
- string getNamespaceId() { result = getArgument(0).(ConstantString).getStringValue() }
-}
-
-/**
- * A Closure module, that is, a toplevel that contains a call to `goog.provide` or
- * `goog.require`.
- */
-class ClosureModule extends TopLevel {
- ClosureModule() {
- getAChildStmt() instanceof GoogProvide or
- getAChildStmt() instanceof GoogRequire
+ /**
+ * Gets the closure namespace path addressed by the given dataflow node, if any.
+ */
+ string getLibraryAccessPath(DataFlow::SourceNode node) {
+ isLibraryNamespacePath(result) and
+ node = DataFlow::globalVarRef(result)
+ or
+ isLibraryNamespacePath(result) and
+ exists(DataFlow::PropRead read | node = read |
+ result = getLibraryAccessPath(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 = getWrittenLibraryAccessPath(write)
+ )
+ or
+ result = node.asExpr().(GoogRequire).getNamespaceId()
}
- /** Gets the identifier of a namespace required by this module. */
- string getARequiredNamespace() { result = getAChildStmt().(GoogRequire).getNamespaceId() }
+ /**
+ * Gets the closure namespace path written to by the given property write, if any.
+ */
+ string getWrittenLibraryAccessPath(DataFlow::PropWrite node) {
+ result = getLibraryAccessPath(node.getBase().getALocalSource()) + "." + node.getPropertyName()
+ }
- /** Gets the identifer of a namespace provided by this module. */
- string getAProvidedNamespace() { result = getAChildStmt().(GoogProvide).getNamespaceId() }
+ /**
+ * Gets a dataflow node that refers to the given Closure module.
+ */
+ DataFlow::SourceNode moduleImport(string moduleName) {
+ getLibraryAccessPath(result) = moduleName
+ }
+
+ /**
+ * Gets a dataflow node that refers to the given member of a Closure module.
+ */
+ DataFlow::SourceNode moduleMember(string moduleName, string memberName) {
+ result = moduleImport(moduleName).getAPropertyRead(memberName)
+ }
}
diff --git a/javascript/ql/src/semmle/javascript/ES2015Modules.qll b/javascript/ql/src/semmle/javascript/ES2015Modules.qll
index 55e820f77ee..fa3d82c7720 100644
--- a/javascript/ql/src/semmle/javascript/ES2015Modules.qll
+++ b/javascript/ql/src/semmle/javascript/ES2015Modules.qll
@@ -7,8 +7,7 @@ import javascript
*/
class ES2015Module extends Module {
ES2015Module() {
- isModule(this) and
- not isNodejs(this)
+ isES2015Module(this)
}
override ModuleScope getScope() { result.getScopeElement() = this }
diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll
index 05a102d1c38..5aa7b6a56a3 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll
@@ -1039,7 +1039,7 @@ module DataFlow {
or
exists(GlobalVarAccess va |
nd = valueNode(va.(VarUse)) and
- cause = "global"
+ if Closure::isLibraryNamespacePath(va.getName()) then cause = "heap" else cause = "global"
)
or
exists(Expr e | e = nd.asExpr() and cause = "call" |
diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
index 7f8a2325ee2..da2b54fc14f 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
@@ -332,3 +332,48 @@ 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 {
+ override AssignExpr astNode;
+
+ Closure::ClosureModule mod;
+
+ AnalyzedClosureExportAssign() { astNode.getLhs() = mod.getExportsVariable().getAReference() }
+
+ override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) {
+ baseVal = TAbstractModuleObject(astNode.getTopLevel()) and
+ propName = "exports" and
+ source = astNode.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 = Closure::getLibraryAccessPath(this) }
+
+ override AnalyzedNode localFlowPred() {
+ exists(DataFlow::PropWrite write |
+ Closure::getWrittenLibraryAccessPath(write) = accessPath and
+ result = write.getRhs()
+ )
+ or
+ result = AnalyzedNode.super.localFlowPred()
+ }
+
+ override predicate reads(AbstractValue base, string propName) {
+ exists(Closure::ClosureModule mod |
+ mod.getNamespaceId() = accessPath and
+ base = TAbstractModuleObject(mod) and
+ propName = "exports"
+ )
+ }
+}
diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme b/javascript/ql/src/semmlecode.javascript.dbscheme
index 7eead88269b..eee08932d52 100644
--- a/javascript/ql/src/semmlecode.javascript.dbscheme
+++ b/javascript/ql/src/semmlecode.javascript.dbscheme
@@ -128,6 +128,8 @@ case @toplevel.kind of
isModule (int tl: @toplevel ref);
isNodejs (int tl: @toplevel ref);
+isES2015Module (int tl: @toplevel ref);
+isClosureModule (int tl: @toplevel ref);
// statements
#keyset[parent, idx]
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..24fc3e293ea
--- /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() {} | 0 |
+| tests/importFromEs6.js:10:1:10:18 | es6ModuleDefault() | tests/es6ModuleDefault.js:3:16:3:28 | function() {} | 0 |
+| tests/importFromEs6.js:12:1:12:16 | googModule.fun() | tests/googModule.js:4:6:4:10 | () {} | 0 |
+| tests/importFromEs6.js:13:1:13:19 | googModuleDefault() | tests/googModuleDefault.js:3:11:3:27 | function fun() {} | 0 |
+| tests/requireFromEs6.js:12:1:12:18 | globalModule.fun() | tests/globalModule.js:4:6:4:10 | () {} | 0 |
+| tests/requireFromEs6.js:13:1:13:21 | globalM ... fault() | tests/globalModuleDefault.js:3:23:3:39 | function fun() {} | 0 |
+| tests/requireFromEs6.js:15:1:15:15 | es6Module.fun() | tests/es6Module.js:3:8:3:24 | function fun() {} | 0 |
+| tests/requireFromEs6.js:16:1:16:18 | es6ModuleDefault() | tests/es6ModuleDefault.js:3:16:3:28 | function() {} | 0 |
+| tests/requireFromEs6.js:18:1:18:16 | googModule.fun() | tests/googModule.js:4:6:4:10 | () {} | 0 |
+| tests/requireFromEs6.js:19:1:19:19 | googModuleDefault() | tests/googModuleDefault.js:3:11:3:27 | function fun() {} | 0 |
+| tests/requireFromGlobalModule.js:10:1:10:18 | x.y.z.global.fun() | tests/globalModule.js:4:6:4:10 | () {} | 0 |
+| tests/requireFromGlobalModule.js:11:1:11:21 | x.y.z.g ... fault() | tests/globalModuleDefault.js:3:23:3:39 | function fun() {} | 0 |
+| tests/requireFromGlobalModule.js:13:1:13:16 | x.y.z.goog.fun() | tests/googModule.js:4:6:4:10 | () {} | 0 |
+| tests/requireFromGlobalModule.js:14:1:14:19 | x.y.z.googdefault() | tests/googModuleDefault.js:3:11:3:27 | function fun() {} | 0 |
+| tests/requireFromGlobalModule.js:16:1:16:15 | x.y.z.es6.fun() | tests/es6Module.js:3:8:3:24 | function fun() {} | 0 |
+| tests/requireFromGlobalModule.js:17:1:17:18 | x.y.z.es6default() | tests/es6ModuleDefault.js:3:16:3:28 | function() {} | 0 |
+| tests/requireFromGoogModule.js:12:1:12:18 | globalModule.fun() | tests/globalModule.js:4:6:4:10 | () {} | 0 |
+| tests/requireFromGoogModule.js:13:1:13:21 | globalM ... fault() | tests/globalModuleDefault.js:3:23:3:39 | function fun() {} | 0 |
+| tests/requireFromGoogModule.js:15:1:15:15 | es6Module.fun() | tests/es6Module.js:3:8:3:24 | function fun() {} | 0 |
+| tests/requireFromGoogModule.js:16:1:16:18 | es6ModuleDefault() | tests/es6ModuleDefault.js:3:16:3:28 | function() {} | 0 |
+| tests/requireFromGoogModule.js:18:1:18:16 | googModule.fun() | tests/googModule.js:4:6:4:10 | () {} | 0 |
+| tests/requireFromGoogModule.js:19:1:19:19 | googModuleDefault() | tests/googModuleDefault.js:3:11:3:27 | function fun() {} | 0 |
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..b18c29d2bfe
--- /dev/null
+++ b/javascript/ql/test/library-tests/Closure/CallGraph.ql
@@ -0,0 +1,4 @@
+import javascript
+
+from DataFlow::InvokeNode node, int imprecision
+select node, node.getACallee(imprecision), imprecision
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/StrictMode.expected b/javascript/ql/test/library-tests/Closure/StrictMode.expected
new file mode 100644
index 00000000000..a968cd35cae
--- /dev/null
+++ b/javascript/ql/test/library-tests/Closure/StrictMode.expected
@@ -0,0 +1,4 @@
+| tests/es6Module.js:0:0:0:0 | tests/es6Module.js |
+| tests/es6ModuleDefault.js:0:0:0:0 | tests/es6ModuleDefault.js |
+| tests/importFromEs6.js:0:0:0:0 | tests/importFromEs6.js |
+| tests/requireFromEs6.js:0:0:0:0 | tests/requireFromEs6.js |
diff --git a/javascript/ql/test/library-tests/Closure/StrictMode.ql b/javascript/ql/test/library-tests/Closure/StrictMode.ql
new file mode 100644
index 00000000000..211c306a3a5
--- /dev/null
+++ b/javascript/ql/test/library-tests/Closure/StrictMode.ql
@@ -0,0 +1,5 @@
+import javascript
+
+from TopLevel tl
+where tl.isStrict()
+select tl.getFile()
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/moduleImport.expected b/javascript/ql/test/library-tests/Closure/moduleImport.expected
new file mode 100644
index 00000000000..f143eea96aa
--- /dev/null
+++ b/javascript/ql/test/library-tests/Closure/moduleImport.expected
@@ -0,0 +1,51 @@
+| x | tests/globalModule.js:3:1:3:1 | x |
+| x | tests/globalModuleDefault.js:3:1:3:1 | x |
+| x | tests/requireFromGlobalModule.js:10:1:10:1 | x |
+| x | tests/requireFromGlobalModule.js:11:1:11:1 | x |
+| x | tests/requireFromGlobalModule.js:13:1:13:1 | x |
+| x | tests/requireFromGlobalModule.js:14:1:14:1 | x |
+| x | tests/requireFromGlobalModule.js:16:1:16:1 | x |
+| x | tests/requireFromGlobalModule.js:17:1:17:1 | x |
+| x.y | tests/globalModule.js:3:1:3:3 | x.y |
+| x.y | tests/globalModuleDefault.js:3:1:3:3 | x.y |
+| x.y | tests/requireFromGlobalModule.js:10:1:10:3 | x.y |
+| x.y | tests/requireFromGlobalModule.js:11:1:11:3 | x.y |
+| x.y | tests/requireFromGlobalModule.js:13:1:13:3 | x.y |
+| x.y | tests/requireFromGlobalModule.js:14:1:14:3 | x.y |
+| x.y | tests/requireFromGlobalModule.js:16:1:16:3 | x.y |
+| x.y | tests/requireFromGlobalModule.js:17:1:17:3 | x.y |
+| x.y.z | tests/globalModule.js:3:1:3:5 | x.y.z |
+| x.y.z | tests/globalModuleDefault.js:3:1:3:5 | x.y.z |
+| x.y.z | tests/requireFromGlobalModule.js:10:1:10:5 | x.y.z |
+| x.y.z | tests/requireFromGlobalModule.js:11:1:11:5 | x.y.z |
+| x.y.z | tests/requireFromGlobalModule.js:13:1:13:5 | x.y.z |
+| x.y.z | tests/requireFromGlobalModule.js:14:1:14:5 | x.y.z |
+| x.y.z | tests/requireFromGlobalModule.js:16:1:16:5 | x.y.z |
+| x.y.z | tests/requireFromGlobalModule.js:17:1:17:5 | x.y.z |
+| x.y.z.es6 | tests/requireFromEs6.js:6:17:6:41 | goog.re ... z.es6') |
+| x.y.z.es6 | tests/requireFromGlobalModule.js:7:1:7:25 | goog.re ... z.es6') |
+| x.y.z.es6 | tests/requireFromGlobalModule.js:16:1:16:9 | x.y.z.es6 |
+| x.y.z.es6 | tests/requireFromGoogModule.js:6:17:6:41 | goog.re ... z.es6') |
+| x.y.z.es6default | tests/requireFromEs6.js:7:24:7:55 | goog.re ... fault') |
+| x.y.z.es6default | tests/requireFromGlobalModule.js:8:1:8:32 | goog.re ... fault') |
+| x.y.z.es6default | tests/requireFromGlobalModule.js:17:1:17:16 | x.y.z.es6default |
+| x.y.z.es6default | tests/requireFromGoogModule.js:7:24:7:55 | goog.re ... fault') |
+| x.y.z.global | tests/globalModule.js:3:16:5:1 | {\\n fun() {}\\n} |
+| x.y.z.global | tests/requireFromEs6.js:3:20:3:47 | goog.re ... lobal') |
+| x.y.z.global | tests/requireFromGlobalModule.js:1:1:1:28 | goog.re ... lobal') |
+| x.y.z.global | tests/requireFromGlobalModule.js:10:1:10:12 | x.y.z.global |
+| x.y.z.global | tests/requireFromGoogModule.js:3:20:3:47 | goog.re ... lobal') |
+| x.y.z.global.fun | tests/globalModule.js:4:6:4:10 | () {} |
+| x.y.z.globaldefault | tests/globalModuleDefault.js:3:23:3:39 | function fun() {} |
+| x.y.z.globaldefault | tests/requireFromEs6.js:4:27:4:61 | goog.re ... fault') |
+| x.y.z.globaldefault | tests/requireFromGlobalModule.js:2:1:2:35 | goog.re ... fault') |
+| x.y.z.globaldefault | tests/requireFromGlobalModule.js:11:1:11:19 | x.y.z.globaldefault |
+| x.y.z.globaldefault | tests/requireFromGoogModule.js:4:27:4:61 | goog.re ... fault') |
+| x.y.z.goog | tests/requireFromEs6.js:9:18:9:43 | goog.re ... .goog') |
+| x.y.z.goog | tests/requireFromGlobalModule.js:4:1:4:26 | goog.re ... .goog') |
+| x.y.z.goog | tests/requireFromGlobalModule.js:13:1:13:10 | x.y.z.goog |
+| x.y.z.goog | tests/requireFromGoogModule.js:9:18:9:43 | goog.re ... .goog') |
+| x.y.z.googdefault | tests/requireFromEs6.js:10:25:10:57 | goog.re ... fault') |
+| x.y.z.googdefault | tests/requireFromGlobalModule.js:5:1:5:33 | goog.re ... fault') |
+| x.y.z.googdefault | tests/requireFromGlobalModule.js:14:1:14:17 | x.y.z.googdefault |
+| x.y.z.googdefault | tests/requireFromGoogModule.js:10:25:10:57 | goog.re ... fault') |
diff --git a/javascript/ql/test/library-tests/Closure/moduleImport.ql b/javascript/ql/test/library-tests/Closure/moduleImport.ql
new file mode 100644
index 00000000000..ac6cf21ccca
--- /dev/null
+++ b/javascript/ql/test/library-tests/Closure/moduleImport.ql
@@ -0,0 +1,4 @@
+import javascript
+
+from string name
+select name, Closure::moduleImport(name)
diff --git a/javascript/ql/test/library-tests/Closure/moduleMember.expected b/javascript/ql/test/library-tests/Closure/moduleMember.expected
new file mode 100644
index 00000000000..281e8176555
--- /dev/null
+++ b/javascript/ql/test/library-tests/Closure/moduleMember.expected
@@ -0,0 +1,31 @@
+| x | y | tests/globalModule.js:3:1:3:3 | x.y |
+| x | y | tests/globalModuleDefault.js:3:1:3:3 | x.y |
+| x | y | tests/requireFromGlobalModule.js:10:1:10:3 | x.y |
+| x | y | tests/requireFromGlobalModule.js:11:1:11:3 | x.y |
+| x | y | tests/requireFromGlobalModule.js:13:1:13:3 | x.y |
+| x | y | tests/requireFromGlobalModule.js:14:1:14:3 | x.y |
+| x | y | tests/requireFromGlobalModule.js:16:1:16:3 | x.y |
+| x | y | tests/requireFromGlobalModule.js:17:1:17:3 | x.y |
+| x.y | z | tests/globalModule.js:3:1:3:5 | x.y.z |
+| x.y | z | tests/globalModuleDefault.js:3:1:3:5 | x.y.z |
+| x.y | z | tests/requireFromGlobalModule.js:10:1:10:5 | x.y.z |
+| x.y | z | tests/requireFromGlobalModule.js:11:1:11:5 | x.y.z |
+| x.y | z | tests/requireFromGlobalModule.js:13:1:13:5 | x.y.z |
+| x.y | z | tests/requireFromGlobalModule.js:14:1:14:5 | x.y.z |
+| x.y | z | tests/requireFromGlobalModule.js:16:1:16:5 | x.y.z |
+| x.y | z | tests/requireFromGlobalModule.js:17:1:17:5 | x.y.z |
+| x.y.z | es6 | tests/requireFromGlobalModule.js:16:1:16:9 | x.y.z.es6 |
+| x.y.z | es6default | tests/requireFromGlobalModule.js:17:1:17:16 | x.y.z.es6default |
+| x.y.z | global | tests/requireFromGlobalModule.js:10:1:10:12 | x.y.z.global |
+| x.y.z | globaldefault | tests/requireFromGlobalModule.js:11:1:11:19 | x.y.z.globaldefault |
+| x.y.z | goog | tests/requireFromGlobalModule.js:13:1:13:10 | x.y.z.goog |
+| x.y.z | googdefault | tests/requireFromGlobalModule.js:14:1:14:17 | x.y.z.googdefault |
+| x.y.z.es6 | fun | tests/requireFromEs6.js:15:1:15:13 | es6Module.fun |
+| x.y.z.es6 | fun | tests/requireFromGlobalModule.js:16:1:16:13 | x.y.z.es6.fun |
+| x.y.z.es6 | fun | tests/requireFromGoogModule.js:15:1:15:13 | es6Module.fun |
+| x.y.z.global | fun | tests/requireFromEs6.js:12:1:12:16 | globalModule.fun |
+| x.y.z.global | fun | tests/requireFromGlobalModule.js:10:1:10:16 | x.y.z.global.fun |
+| x.y.z.global | fun | tests/requireFromGoogModule.js:12:1:12:16 | globalModule.fun |
+| x.y.z.goog | fun | tests/requireFromEs6.js:18:1:18:14 | googModule.fun |
+| x.y.z.goog | fun | tests/requireFromGlobalModule.js:13:1:13:14 | x.y.z.goog.fun |
+| x.y.z.goog | fun | tests/requireFromGoogModule.js:18:1:18:14 | googModule.fun |
diff --git a/javascript/ql/test/library-tests/Closure/moduleMember.ql b/javascript/ql/test/library-tests/Closure/moduleMember.ql
new file mode 100644
index 00000000000..8e4cf30998d
--- /dev/null
+++ b/javascript/ql/test/library-tests/Closure/moduleMember.ql
@@ -0,0 +1,4 @@
+import javascript
+
+from string mod, string name
+select mod, name, Closure::moduleMember(mod, name)
\ 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();
diff --git a/javascript/ql/test/library-tests/Modules/GlobalVariableRef.expected b/javascript/ql/test/library-tests/Modules/GlobalVariableRef.expected
new file mode 100644
index 00000000000..90cf72071ae
--- /dev/null
+++ b/javascript/ql/test/library-tests/Modules/GlobalVariableRef.expected
@@ -0,0 +1,3 @@
+| a.js:5:31:5:31 | o |
+| exports.js:3:9:3:15 | exports |
+| tst.html:6:3:6:7 | alert |
diff --git a/javascript/ql/test/library-tests/Modules/GlobalVariableRef.ql b/javascript/ql/test/library-tests/Modules/GlobalVariableRef.ql
new file mode 100644
index 00000000000..5b06a07a226
--- /dev/null
+++ b/javascript/ql/test/library-tests/Modules/GlobalVariableRef.ql
@@ -0,0 +1,5 @@
+import javascript
+
+from VarAccess access
+where access.getVariable() instanceof GlobalVariable
+select access
diff --git a/javascript/ql/test/library-tests/Modules/ImportNamespaceSpecifier.expected b/javascript/ql/test/library-tests/Modules/ImportNamespaceSpecifier.expected
index d7305160b05..61093b96451 100644
--- a/javascript/ql/test/library-tests/Modules/ImportNamespaceSpecifier.expected
+++ b/javascript/ql/test/library-tests/Modules/ImportNamespaceSpecifier.expected
@@ -1 +1,2 @@
+| exports.js:1:8:1:17 | * as dummy |
| m/c.js:1:8:1:13 | * as b |
diff --git a/javascript/ql/test/library-tests/Modules/ImportSpecifiers.expected b/javascript/ql/test/library-tests/Modules/ImportSpecifiers.expected
index 7abe8cb7244..73763be7450 100644
--- a/javascript/ql/test/library-tests/Modules/ImportSpecifiers.expected
+++ b/javascript/ql/test/library-tests/Modules/ImportSpecifiers.expected
@@ -1,6 +1,7 @@
| b.js:1:8:1:8 | f | b.js:1:8:1:8 | f |
| d.js:1:10:1:21 | default as g | d.js:1:21:1:21 | g |
| d.js:1:24:1:29 | x as y | d.js:1:29:1:29 | y |
+| exports.js:1:8:1:17 | * as dummy | exports.js:1:13:1:17 | dummy |
| f.ts:1:8:1:8 | g | f.ts:1:8:1:8 | g |
| g.ts:1:9:1:11 | foo | g.ts:1:9:1:11 | foo |
| import-in-mjs.mjs:1:8:1:24 | exported_from_mjs | import-in-mjs.mjs:1:8:1:24 | exported_from_mjs |
diff --git a/javascript/ql/test/library-tests/Modules/Imports.expected b/javascript/ql/test/library-tests/Modules/Imports.expected
index d3ef73bcc5b..723f3542039 100644
--- a/javascript/ql/test/library-tests/Modules/Imports.expected
+++ b/javascript/ql/test/library-tests/Modules/Imports.expected
@@ -1,6 +1,7 @@
| b.js:1:1:1:20 | import f from './a'; | b.js:1:15:1:19 | './a' | 1 |
| d.js:1:1:1:43 | import ... './a'; | d.js:1:38:1:42 | './a' | 2 |
| d.js:2:1:2:13 | import './b'; | d.js:2:8:2:12 | './b' | 0 |
+| exports.js:1:1:1:31 | import ... dummy'; | exports.js:1:24:1:30 | 'dummy' | 1 |
| f.ts:1:1:1:19 | import g from './e' | f.ts:1:15:1:19 | './e' | 1 |
| g.ts:1:1:1:23 | import ... m './f' | g.ts:1:19:1:23 | './f' | 1 |
| import-in-mjs.mjs:1:1:1:46 | import ... n-mjs'; | import-in-mjs.mjs:1:31:1:45 | 'export-in-mjs' | 1 |
diff --git a/javascript/ql/test/library-tests/Modules/exports.js b/javascript/ql/test/library-tests/Modules/exports.js
new file mode 100644
index 00000000000..c4a90d9da65
--- /dev/null
+++ b/javascript/ql/test/library-tests/Modules/exports.js
@@ -0,0 +1,4 @@
+import * as dummy from 'dummy'; // Recognize as ES6 module
+
+let x = exports; // global variable
+x.foo();
diff --git a/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/isES2015Module.ql b/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/isES2015Module.ql
new file mode 100644
index 00000000000..38971a50710
--- /dev/null
+++ b/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/isES2015Module.ql
@@ -0,0 +1,7 @@
+class TopLevel extends @toplevel {
+ string toString() { none() }
+}
+
+from TopLevel tl
+where isModule(tl) and not isNodejs(tl)
+select tl
diff --git a/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/old.dbscheme b/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/old.dbscheme
new file mode 100644
index 00000000000..7eead88269b
--- /dev/null
+++ b/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/old.dbscheme
@@ -0,0 +1,1088 @@
+/*** Standard fragments ***/
+
+/** Files and folders **/
+
+@location = @location_default;
+
+locations_default(unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+ );
+
+@sourceline = @locatable;
+
+numlines(int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+ );
+
+
+/*
+ fromSource(0) = unknown,
+ fromSource(1) = from source,
+ fromSource(2) = from library
+*/
+files(unique int id: @file,
+ varchar(900) name: string ref,
+ varchar(900) simple: string ref,
+ varchar(900) ext: string ref,
+ int fromSource: int ref);
+
+folders(unique int id: @folder,
+ varchar(900) name: string ref,
+ varchar(900) simple: string ref);
+
+
+@container = @folder | @file ;
+
+
+containerparent(int parent: @container ref,
+ unique int child: @container ref);
+
+/** Duplicate code **/
+
+duplicateCode(
+ unique int id : @duplication,
+ varchar(900) relativePath : string ref,
+ int equivClass : int ref);
+
+similarCode(
+ unique int id : @similarity,
+ varchar(900) relativePath : string ref,
+ int equivClass : int ref);
+
+@duplication_or_similarity = @duplication | @similarity;
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref);
+
+/** External data **/
+
+externalData(
+ int id : @externalDataElement,
+ varchar(900) path : string ref,
+ int column: int ref,
+ varchar(900) value : string ref
+);
+
+snapshotDate(unique date snapshotDate : date ref);
+
+sourceLocationPrefix(varchar(900) prefix : string ref);
+
+/** Version control data **/
+
+svnentries(
+ int id : @svnentry,
+ varchar(500) revision : string ref,
+ varchar(500) author : string ref,
+ date revisionDate : date ref,
+ int changeSize : int ref
+);
+
+svnaffectedfiles(
+ int id : @svnentry ref,
+ int file : @file ref,
+ varchar(500) action : string ref
+);
+
+svnentrymsg(
+ int id : @svnentry ref,
+ varchar(500) message : string ref
+);
+
+svnchurn(
+ int commit : @svnentry ref,
+ int file : @file ref,
+ int addedLines : int ref,
+ int deletedLines : int ref
+);
+
+
+/*** JavaScript-specific part ***/
+
+filetype(
+ int file: @file ref,
+ string filetype: string ref
+)
+
+// top-level code fragments
+toplevels (unique int id: @toplevel,
+ int kind: int ref);
+
+isExterns (int toplevel: @toplevel ref);
+
+case @toplevel.kind of
+ 0 = @script
+| 1 = @inline_script
+| 2 = @event_handler
+| 3 = @javascript_url;
+
+isModule (int tl: @toplevel ref);
+isNodejs (int tl: @toplevel ref);
+
+// statements
+#keyset[parent, idx]
+stmts (unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+
+stmtContainers (unique int stmt: @stmt ref,
+ int container: @stmt_container ref);
+
+jumpTargets (unique int jump: @stmt ref,
+ int target: @stmt ref);
+
+@stmtparent = @stmt | @toplevel | @functionexpr | @arrowfunctionexpr;
+@stmt_container = @toplevel | @function | @namespacedeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration;
+
+case @stmt.kind of
+ 0 = @emptystmt
+| 1 = @blockstmt
+| 2 = @exprstmt
+| 3 = @ifstmt
+| 4 = @labeledstmt
+| 5 = @breakstmt
+| 6 = @continuestmt
+| 7 = @withstmt
+| 8 = @switchstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @trystmt
+| 12 = @whilestmt
+| 13 = @dowhilestmt
+| 14 = @forstmt
+| 15 = @forinstmt
+| 16 = @debuggerstmt
+| 17 = @functiondeclstmt
+| 18 = @vardeclstmt
+| 19 = @case
+| 20 = @catchclause
+| 21 = @forofstmt
+| 22 = @constdeclstmt
+| 23 = @letstmt
+| 24 = @legacy_letstmt
+| 25 = @foreachstmt
+| 26 = @classdeclstmt
+| 27 = @importdeclaration
+| 28 = @exportalldeclaration
+| 29 = @exportdefaultdeclaration
+| 30 = @exportnameddeclaration
+| 31 = @namespacedeclaration
+| 32 = @importequalsdeclaration
+| 33 = @exportassigndeclaration
+| 34 = @interfacedeclaration
+| 35 = @typealiasdeclaration
+| 36 = @enumdeclaration
+| 37 = @externalmoduledeclaration
+| 38 = @exportasnamespacedeclaration
+| 39 = @globalaugmentationdeclaration
+;
+
+@declstmt = @vardeclstmt | @constdeclstmt | @letstmt | @legacy_letstmt;
+
+@exportdeclaration = @exportalldeclaration | @exportdefaultdeclaration | @exportnameddeclaration;
+
+@namespacedefinition = @namespacedeclaration | @enumdeclaration;
+@typedefinition = @classdefinition | @interfacedeclaration | @enumdeclaration | @typealiasdeclaration | @enum_member;
+
+isInstantiated(unique int decl: @namespacedeclaration ref);
+
+@declarablestmt = @declstmt | @namespacedeclaration | @classdeclstmt | @functiondeclstmt | @enumdeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration;
+hasDeclareKeyword(unique int stmt: @declarablestmt ref);
+
+isForAwaitOf(unique int forof: @forofstmt ref);
+
+// expressions
+#keyset[parent, idx]
+exprs (unique int id: @expr,
+ int kind: int ref,
+ int parent: @exprparent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+
+literals (varchar(900) value: string ref,
+ varchar(900) raw: string ref,
+ unique int expr: @exprortype ref);
+
+enclosingStmt (unique int expr: @exprortype ref,
+ int stmt: @stmt ref);
+
+exprContainers (unique int expr: @exprortype ref,
+ int container: @stmt_container ref);
+
+arraySize (unique int ae: @arraylike ref,
+ int sz: int ref);
+
+isDelegating (int yield: @yieldexpr ref);
+
+@exprorstmt = @expr | @stmt;
+@exprortype = @expr | @typeexpr;
+@exprparent = @exprorstmt | @property | @functiontypeexpr;
+@arraylike = @arrayexpr | @arraypattern;
+
+case @expr.kind of
+ 0 = @label
+| 1 = @nullliteral
+| 2 = @booleanliteral
+| 3 = @numberliteral
+| 4 = @stringliteral
+| 5 = @regexpliteral
+| 6 = @thisexpr
+| 7 = @arrayexpr
+| 8 = @objexpr
+| 9 = @functionexpr
+| 10 = @seqexpr
+| 11 = @conditionalexpr
+| 12 = @newexpr
+| 13 = @callexpr
+| 14 = @dotexpr
+| 15 = @indexexpr
+| 16 = @negexpr
+| 17 = @plusexpr
+| 18 = @lognotexpr
+| 19 = @bitnotexpr
+| 20 = @typeofexpr
+| 21 = @voidexpr
+| 22 = @deleteexpr
+| 23 = @eqexpr
+| 24 = @neqexpr
+| 25 = @eqqexpr
+| 26 = @neqqexpr
+| 27 = @ltexpr
+| 28 = @leexpr
+| 29 = @gtexpr
+| 30 = @geexpr
+| 31 = @lshiftexpr
+| 32 = @rshiftexpr
+| 33 = @urshiftexpr
+| 34 = @addexpr
+| 35 = @subexpr
+| 36 = @mulexpr
+| 37 = @divexpr
+| 38 = @modexpr
+| 39 = @bitorexpr
+| 40 = @xorexpr
+| 41 = @bitandexpr
+| 42 = @inexpr
+| 43 = @instanceofexpr
+| 44 = @logandexpr
+| 45 = @logorexpr
+| 47 = @assignexpr
+| 48 = @assignaddexpr
+| 49 = @assignsubexpr
+| 50 = @assignmulexpr
+| 51 = @assigndivexpr
+| 52 = @assignmodexpr
+| 53 = @assignlshiftexpr
+| 54 = @assignrshiftexpr
+| 55 = @assignurshiftexpr
+| 56 = @assignorexpr
+| 57 = @assignxorexpr
+| 58 = @assignandexpr
+| 59 = @preincexpr
+| 60 = @postincexpr
+| 61 = @predecexpr
+| 62 = @postdecexpr
+| 63 = @parexpr
+| 64 = @vardeclarator
+| 65 = @arrowfunctionexpr
+| 66 = @spreadelement
+| 67 = @arraypattern
+| 68 = @objectpattern
+| 69 = @yieldexpr
+| 70 = @taggedtemplateexpr
+| 71 = @templateliteral
+| 72 = @templateelement
+| 73 = @arraycomprehensionexpr
+| 74 = @generatorexpr
+| 75 = @forincomprehensionblock
+| 76 = @forofcomprehensionblock
+| 77 = @legacy_letexpr
+| 78 = @vardecl
+| 79 = @proper_varaccess
+| 80 = @classexpr
+| 81 = @superexpr
+| 82 = @newtargetexpr
+| 83 = @namedimportspecifier
+| 84 = @importdefaultspecifier
+| 85 = @importnamespacespecifier
+| 86 = @namedexportspecifier
+| 87 = @expexpr
+| 88 = @assignexpexpr
+| 89 = @jsxelement
+| 90 = @jsxqualifiedname
+| 91 = @jsxemptyexpr
+| 92 = @awaitexpr
+| 93 = @functionsentexpr
+| 94 = @decorator
+| 95 = @exportdefaultspecifier
+| 96 = @exportnamespacespecifier
+| 97 = @bindexpr
+| 98 = @externalmodulereference
+| 99 = @dynamicimport
+| 100 = @expressionwithtypearguments
+| 101 = @prefixtypeassertion
+| 102 = @astypeassertion
+| 103 = @export_varaccess
+| 104 = @decorator_list
+| 105 = @non_null_assertion
+| 106 = @bigintliteral
+| 107 = @nullishcoalescingexpr
+;
+
+@varaccess = @proper_varaccess | @export_varaccess;
+@varref = @vardecl | @varaccess;
+
+@identifier = @label | @varref | @typeidentifier;
+
+@literal = @nullliteral | @booleanliteral | @numberliteral | @stringliteral | @regexpliteral | @bigintliteral;
+
+@propaccess = @dotexpr | @indexexpr;
+
+@invokeexpr = @newexpr | @callexpr;
+
+@unaryexpr = @negexpr | @plusexpr | @lognotexpr | @bitnotexpr | @typeofexpr | @voidexpr | @deleteexpr | @spreadelement;
+
+@equalitytest = @eqexpr | @neqexpr | @eqqexpr | @neqqexpr;
+
+@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr;
+
+@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr | @nullishcoalescingexpr;
+
+@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr;
+
+@updateexpr = @preincexpr | @postincexpr | @predecexpr | @postdecexpr;
+
+@pattern = @varref | @arraypattern | @objectpattern;
+
+@comprehensionexpr = @arraycomprehensionexpr | @generatorexpr;
+
+@comprehensionblock = @forincomprehensionblock | @forofcomprehensionblock;
+
+@importspecifier = @namedimportspecifier | @importdefaultspecifier | @importnamespacespecifier;
+
+@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier;
+
+@typeassertion = @astypeassertion | @prefixtypeassertion;
+
+@classdefinition = @classdeclstmt | @classexpr;
+@interfacedefinition = @interfacedeclaration | @interfacetypeexpr;
+@classorinterface = @classdefinition | @interfacedefinition;
+
+@lexical_decl = @vardecl | @typedecl;
+@lexical_access = @varaccess | @localtypeaccess | @localvartypeaccess | @localnamespaceaccess;
+@lexical_ref = @lexical_decl | @lexical_access;
+
+// scopes
+scopes (unique int id: @scope,
+ int kind: int ref);
+
+case @scope.kind of
+ 0 = @globalscope
+| 1 = @functionscope
+| 2 = @catchscope
+| 3 = @modulescope
+| 4 = @blockscope
+| 5 = @forscope
+| 6 = @forinscope // for-of scopes work the same as for-in scopes
+| 7 = @comprehensionblockscope
+| 8 = @classexprscope
+| 9 = @namespacescope
+| 10 = @classdeclscope
+| 11 = @interfacescope
+| 12 = @typealiasscope
+| 13 = @mappedtypescope
+| 14 = @enumscope
+| 15 = @externalmodulescope
+| 16 = @conditionaltypescope;
+
+scopenodes (unique int node: @ast_node ref,
+ int scope: @scope ref);
+
+scopenesting (unique int inner: @scope ref,
+ int outer: @scope ref);
+
+// functions
+@function = @functiondeclstmt | @functionexpr | @arrowfunctionexpr;
+
+@parameterized = @function | @catchclause;
+@type_parameterized = @function | @classorinterface | @typealiasdeclaration | @mappedtypeexpr | @infertypeexpr;
+
+isGenerator (int fun: @function ref);
+hasRestParameter (int fun: @function ref);
+isAsync (int fun: @function ref);
+
+// variables and lexically scoped type names
+#keyset[scope, name]
+variables (unique int id: @variable,
+ varchar(900) name: string ref,
+ int scope: @scope ref);
+
+#keyset[scope, name]
+local_type_names (unique int id: @local_type_name,
+ varchar(900) name: string ref,
+ int scope: @scope ref);
+
+#keyset[scope, name]
+local_namespace_names (unique int id: @local_namespace_name,
+ varchar(900) name: string ref,
+ int scope: @scope ref);
+
+isArgumentsObject (int id: @variable ref);
+
+@lexical_name = @variable | @local_type_name | @local_namespace_name;
+
+@bind_id = @varaccess | @localvartypeaccess;
+bind (unique int id: @bind_id ref,
+ int decl: @variable ref);
+
+decl (unique int id: @vardecl ref,
+ int decl: @variable ref);
+
+@typebind_id = @localtypeaccess | @export_varaccess;
+typebind (unique int id: @typebind_id ref,
+ int decl: @local_type_name ref);
+
+@typedecl_id = @typedecl | @vardecl;
+typedecl (unique int id: @typedecl_id ref,
+ int decl: @local_type_name ref);
+
+namespacedecl (unique int id: @vardecl ref,
+ int decl: @local_namespace_name ref);
+
+@namespacebind_id = @localnamespaceaccess | @export_varaccess;
+namespacebind (unique int id: @namespacebind_id ref,
+ int decl: @local_namespace_name ref);
+
+
+// properties in object literals, property patterns in object patterns, and method declarations in classes
+#keyset[parent, index]
+properties (unique int id: @property,
+ int parent: @property_parent ref,
+ int index: int ref,
+ int kind: int ref,
+ varchar(900) tostring: string ref);
+
+case @property.kind of
+ 0 = @value_property
+| 1 = @property_getter
+| 2 = @property_setter
+| 3 = @jsx_attribute
+| 4 = @function_call_signature
+| 5 = @constructor_call_signature
+| 6 = @index_signature
+| 7 = @enum_member
+| 8 = @proper_field
+| 9 = @parameter_field
+;
+
+@property_parent = @objexpr | @objectpattern | @classdefinition | @jsxelement | @interfacedefinition | @enumdeclaration;
+@property_accessor = @property_getter | @property_setter;
+@call_signature = @function_call_signature | @constructor_call_signature;
+@field = @proper_field | @parameter_field;
+@field_or_vardeclarator = @field | @vardeclarator;
+
+isComputed (int id: @property ref);
+isMethod (int id: @property ref);
+isStatic (int id: @property ref);
+isAbstractMember (int id: @property ref);
+isConstEnum (int id: @enumdeclaration ref);
+isAbstractClass (int id: @classdeclstmt ref);
+
+hasPublicKeyword (int id: @property ref);
+hasPrivateKeyword (int id: @property ref);
+hasProtectedKeyword (int id: @property ref);
+hasReadonlyKeyword (int id: @property ref);
+isOptionalMember (int id: @property ref);
+hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref);
+
+#keyset[constructor, param_index]
+parameter_fields(
+ unique int field: @parameter_field ref,
+ int constructor: @functionexpr ref,
+ int param_index: int ref
+);
+
+// types
+#keyset[parent, idx]
+typeexprs (
+ unique int id: @typeexpr,
+ int kind: int ref,
+ int parent: @typeexpr_parent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref
+);
+
+case @typeexpr.kind of
+ 0 = @localtypeaccess
+| 1 = @typedecl
+| 2 = @keywordtypeexpr
+| 3 = @stringliteraltypeexpr
+| 4 = @numberliteraltypeexpr
+| 5 = @booleanliteraltypeexpr
+| 6 = @arraytypeexpr
+| 7 = @uniontypeexpr
+| 8 = @indexedaccesstypeexpr
+| 9 = @intersectiontypeexpr
+| 10 = @parenthesizedtypeexpr
+| 11 = @tupletypeexpr
+| 12 = @keyoftypeexpr
+| 13 = @qualifiedtypeaccess
+| 14 = @generictypeexpr
+| 15 = @typelabel
+| 16 = @typeoftypeexpr
+| 17 = @localvartypeaccess
+| 18 = @qualifiedvartypeaccess
+| 19 = @thisvartypeaccess
+| 20 = @istypeexpr
+| 21 = @interfacetypeexpr
+| 22 = @typeparameter
+| 23 = @plainfunctiontypeexpr
+| 24 = @constructortypeexpr
+| 25 = @localnamespaceaccess
+| 26 = @qualifiednamespaceaccess
+| 27 = @mappedtypeexpr
+| 28 = @conditionaltypeexpr
+| 29 = @infertypeexpr
+| 30 = @importtypeaccess
+| 31 = @importnamespaceaccess
+| 32 = @importvartypeaccess
+| 33 = @optionaltypeexpr
+| 34 = @resttypeexpr
+| 35 = @bigintliteraltypeexpr
+;
+
+@typeref = @typeaccess | @typedecl;
+@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess;
+@typeexpr_parent = @expr | @stmt | @property | @typeexpr;
+@literaltypeexpr = @stringliteraltypeexpr | @numberliteraltypeexpr | @booleanliteraltypeexpr | @bigintliteraltypeexpr;
+@typeaccess = @localtypeaccess | @qualifiedtypeaccess | @importtypeaccess;
+@vartypeaccess = @localvartypeaccess | @qualifiedvartypeaccess | @thisvartypeaccess | @importvartypeaccess;
+@namespaceaccess = @localnamespaceaccess | @qualifiednamespaceaccess | @importnamespaceaccess;
+@importtypeexpr = @importtypeaccess | @importnamespaceaccess | @importvartypeaccess;
+
+@functiontypeexpr = @plainfunctiontypeexpr | @constructortypeexpr;
+
+// types
+types (
+ unique int id: @type,
+ int kind: int ref,
+ varchar(900) tostring: string ref
+);
+
+#keyset[parent, idx]
+type_child (
+ int child: @type ref,
+ int parent: @type ref,
+ int idx: int ref
+);
+
+case @type.kind of
+ 0 = @anytype
+| 1 = @stringtype
+| 2 = @numbertype
+| 3 = @uniontype
+| 4 = @truetype
+| 5 = @falsetype
+| 6 = @typereference
+| 7 = @objecttype
+| 8 = @canonicaltypevariabletype
+| 9 = @typeoftype
+| 10 = @voidtype
+| 11 = @undefinedtype
+| 12 = @nulltype
+| 13 = @nevertype
+| 14 = @plainsymboltype
+| 15 = @uniquesymboltype
+| 16 = @objectkeywordtype
+| 17 = @intersectiontype
+| 18 = @tupletype
+| 19 = @lexicaltypevariabletype
+| 20 = @thistype
+| 21 = @numberliteraltype
+| 22 = @stringliteraltype
+| 23 = @unknowntype
+| 24 = @biginttype
+| 25 = @bigintliteraltype
+;
+
+@booleanliteraltype = @truetype | @falsetype;
+@symboltype = @plainsymboltype | @uniquesymboltype;
+@unionorintersectiontype = @uniontype | @intersectiontype;
+@typevariabletype = @canonicaltypevariabletype | @lexicaltypevariabletype;
+
+@typed_ast_node = @expr | @typeexpr | @function;
+ast_node_type(
+ unique int node: @typed_ast_node ref,
+ int typ: @type ref);
+
+invoke_expr_signature(
+ unique int node: @invokeexpr ref,
+ int sig: @signature_type ref
+);
+
+invoke_expr_overload_index(
+ unique int node: @invokeexpr ref,
+ int index: int ref
+);
+
+symbols (
+ unique int id: @symbol,
+ int kind: int ref,
+ varchar(900) name: string ref
+);
+
+symbol_parent (
+ unique int symbol: @symbol ref,
+ int parent: @symbol ref
+);
+
+symbol_module (
+ int symbol: @symbol ref,
+ varchar(900) moduleName: string ref
+);
+
+symbol_global (
+ int symbol: @symbol ref,
+ varchar(900) globalName: string ref
+);
+
+case @symbol.kind of
+ 0 = @root_symbol
+| 1 = @member_symbol
+| 2 = @other_symbol
+;
+
+@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype;
+@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr;
+
+ast_node_symbol(
+ unique int node: @ast_node_with_symbol ref,
+ int symbol: @symbol ref);
+
+type_symbol(
+ unique int typ: @type_with_symbol ref,
+ int symbol: @symbol ref);
+
+#keyset[typ, name]
+type_property(
+ int typ: @type ref,
+ varchar(900) name: string ref,
+ int propertyType: @type ref);
+
+@literaltype = @stringliteraltype | @numberliteraltype | @booleanliteraltype | @bigintliteraltype;
+@type_with_literal_value = @stringliteraltype | @numberliteraltype | @bigintliteraltype;
+type_literal_value(
+ unique int typ: @type_with_literal_value ref,
+ varchar(900) value: string ref);
+
+signature_types (
+ unique int id: @signature_type,
+ int kind: int ref,
+ varchar(900) tostring: string ref,
+ int type_parameters: int ref,
+ int required_params: int ref
+);
+
+case @signature_type.kind of
+ 0 = @function_signature_type
+| 1 = @constructor_signature_type
+;
+
+#keyset[typ, kind, index]
+type_contains_signature (
+ int typ: @type ref,
+ int kind: int ref, // constructor/call/index
+ int index: int ref, // ordering of overloaded signatures
+ int sig: @signature_type ref
+);
+
+#keyset[parent, index]
+signature_contains_type (
+ int child: @type ref,
+ int parent: @signature_type ref,
+ int index: int ref
+);
+
+#keyset[sig, index]
+signature_parameter_name (
+ int sig: @signature_type ref,
+ int index: int ref,
+ varchar(900) name: string ref
+);
+
+number_index_type (
+ unique int baseType: @type ref,
+ int propertyType: @type ref
+);
+
+string_index_type (
+ unique int baseType: @type ref,
+ int propertyType: @type ref
+);
+
+base_type_names(
+ int typeName: @symbol ref,
+ int baseTypeName: @symbol ref
+);
+
+self_types(
+ int typeName: @symbol ref,
+ int selfType: @typereference ref
+);
+
+tuple_type_min_length(
+ unique int typ: @type ref,
+ int minLength: int ref
+);
+
+tuple_type_rest(
+ unique int typ: @type ref
+);
+
+// comments
+comments (unique int id: @comment,
+ int kind: int ref,
+ int toplevel: @toplevel ref,
+ varchar(900) text: string ref,
+ varchar(900) tostring: string ref);
+
+case @comment.kind of
+ 0 = @slashslashcomment
+| 1 = @slashstarcomment
+| 2 = @doccomment
+| 3 = @htmlcommentstart
+| 4 = @htmlcommentend;
+
+@htmlcomment = @htmlcommentstart | @htmlcommentend;
+@linecomment = @slashslashcomment | @htmlcomment;
+@blockcomment = @slashstarcomment | @doccomment;
+
+// source lines
+lines (unique int id: @line,
+ int toplevel: @toplevel ref,
+ varchar(900) text: string ref,
+ varchar(2) terminator: string ref);
+indentation (int file: @file ref,
+ int lineno: int ref,
+ varchar(1) indentChar: string ref,
+ int indentDepth: int ref);
+
+// JavaScript parse errors
+jsParseErrors (unique int id: @js_parse_error,
+ int toplevel: @toplevel ref,
+ varchar(900) message: string ref,
+ varchar(900) line: string ref);
+
+// regular expressions
+#keyset[parent, idx]
+regexpterm (unique int id: @regexpterm,
+ int kind: int ref,
+ int parent: @regexpparent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+
+@regexpparent = @regexpterm | @regexpliteral;
+
+case @regexpterm.kind of
+ 0 = @regexp_alt
+| 1 = @regexp_seq
+| 2 = @regexp_caret
+| 3 = @regexp_dollar
+| 4 = @regexp_wordboundary
+| 5 = @regexp_nonwordboundary
+| 6 = @regexp_positive_lookahead
+| 7 = @regexp_negative_lookahead
+| 8 = @regexp_star
+| 9 = @regexp_plus
+| 10 = @regexp_opt
+| 11 = @regexp_range
+| 12 = @regexp_dot
+| 13 = @regexp_group
+| 14 = @regexp_normal_char
+| 15 = @regexp_hex_escape
+| 16 = @regexp_unicode_escape
+| 17 = @regexp_dec_escape
+| 18 = @regexp_oct_escape
+| 19 = @regexp_ctrl_escape
+| 20 = @regexp_char_class_escape
+| 21 = @regexp_id_escape
+| 22 = @regexp_backref
+| 23 = @regexp_char_class
+| 24 = @regexp_char_range
+| 25 = @regexp_positive_lookbehind
+| 26 = @regexp_negative_lookbehind
+| 27 = @regexp_unicode_property_escape;
+
+regexpParseErrors (unique int id: @regexp_parse_error,
+ int regexp: @regexpterm ref,
+ varchar(900) message: string ref);
+
+@regexp_quantifier = @regexp_star | @regexp_plus | @regexp_opt | @regexp_range;
+@regexp_escape = @regexp_char_escape | @regexp_char_class_escape | @regexp_unicode_property_escape;
+@regexp_char_escape = @regexp_hex_escape | @regexp_unicode_escape | @regexp_dec_escape | @regexp_oct_escape | @regexp_ctrl_escape | @regexp_id_escape;
+@regexp_constant = @regexp_normal_char | @regexp_char_escape;
+@regexp_lookahead = @regexp_positive_lookahead | @regexp_negative_lookahead;
+@regexp_lookbehind = @regexp_positive_lookbehind | @regexp_negative_lookbehind;
+@regexp_subpattern = @regexp_lookahead | @regexp_lookbehind;
+
+isGreedy (int id: @regexp_quantifier ref);
+rangeQuantifierLowerBound (unique int id: @regexp_range ref, int lo: int ref);
+rangeQuantifierUpperBound (unique int id: @regexp_range ref, int hi: int ref);
+isCapture (unique int id: @regexp_group ref, int number: int ref);
+isNamedCapture (unique int id: @regexp_group ref, string name: string ref);
+isInverted (int id: @regexp_char_class ref);
+regexpConstValue (unique int id: @regexp_constant ref, varchar(1) value: string ref);
+charClassEscape (unique int id: @regexp_char_class_escape ref, varchar(1) value: string ref);
+backref (unique int id: @regexp_backref ref, int value: int ref);
+namedBackref (unique int id: @regexp_backref ref, string name: string ref);
+unicodePropertyEscapeName (unique int id: @regexp_unicode_property_escape ref, string name: string ref);
+unicodePropertyEscapeValue (unique int id: @regexp_unicode_property_escape ref, string value: string ref);
+
+// tokens
+#keyset[toplevel, idx]
+tokeninfo (unique int id: @token,
+ int kind: int ref,
+ int toplevel: @toplevel ref,
+ int idx: int ref,
+ varchar(900) value: string ref);
+
+case @token.kind of
+ 0 = @token_eof
+| 1 = @token_null_literal
+| 2 = @token_boolean_literal
+| 3 = @token_numeric_literal
+| 4 = @token_string_literal
+| 5 = @token_regular_expression
+| 6 = @token_identifier
+| 7 = @token_keyword
+| 8 = @token_punctuator;
+
+// associate comments with the token immediately following them (which may be EOF)
+next_token (int comment: @comment ref, int token: @token ref);
+
+// JSON
+#keyset[parent, idx]
+json (unique int id: @json_value,
+ int kind: int ref,
+ int parent: @json_parent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+
+json_literals (varchar(900) value: string ref,
+ varchar(900) raw: string ref,
+ unique int expr: @json_value ref);
+
+json_properties (int obj: @json_object ref,
+ varchar(900) property: string ref,
+ int value: @json_value ref);
+
+json_errors (unique int id: @json_parse_error,
+ varchar(900) message: string ref);
+
+case @json_value.kind of
+ 0 = @json_null
+| 1 = @json_boolean
+| 2 = @json_number
+| 3 = @json_string
+| 4 = @json_array
+| 5 = @json_object;
+
+@json_parent = @json_object | @json_array | @file;
+
+// locations
+@ast_node = @toplevel | @stmt | @expr | @property | @typeexpr;
+
+@locatable = @file
+ | @ast_node
+ | @comment
+ | @line
+ | @js_parse_error | @regexp_parse_error | @json_parse_error
+ | @regexpterm
+ | @json_value
+ | @token
+ | @cfg_node
+ | @jsdoc | @jsdoc_type_expr | @jsdoc_tag
+ | @yaml_node | @yaml_error
+ | @xmllocatable;
+
+hasLocation (unique int locatable: @locatable ref,
+ int location: @location ref);
+
+// CFG
+entry_cfg_node (unique int id: @entry_node, int container: @stmt_container ref);
+exit_cfg_node (unique int id: @exit_node, int container: @stmt_container ref);
+guard_node (unique int id: @guard_node, int kind: int ref, int test: @expr ref);
+case @guard_node.kind of
+ 0 = @falsy_guard
+| 1 = @truthy_guard;
+@condition_guard = @falsy_guard | @truthy_guard;
+
+@synthetic_cfg_node = @entry_node | @exit_node | @guard_node;
+@cfg_node = @synthetic_cfg_node | @exprparent;
+
+successor (int pred: @cfg_node ref, int succ: @cfg_node ref);
+
+// JSDoc comments
+jsdoc (unique int id: @jsdoc, varchar(900) description: string ref, int comment: @comment ref);
+#keyset[parent, idx]
+jsdoc_tags (unique int id: @jsdoc_tag, varchar(900) title: string ref,
+ int parent: @jsdoc ref, int idx: int ref, varchar(900) tostring: string ref);
+jsdoc_tag_descriptions (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref);
+jsdoc_tag_names (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref);
+
+#keyset[parent, idx]
+jsdoc_type_exprs (unique int id: @jsdoc_type_expr,
+ int kind: int ref,
+ int parent: @jsdoc_type_expr_parent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+case @jsdoc_type_expr.kind of
+ 0 = @jsdoc_any_type_expr
+| 1 = @jsdoc_null_type_expr
+| 2 = @jsdoc_undefined_type_expr
+| 3 = @jsdoc_unknown_type_expr
+| 4 = @jsdoc_void_type_expr
+| 5 = @jsdoc_named_type_expr
+| 6 = @jsdoc_applied_type_expr
+| 7 = @jsdoc_nullable_type_expr
+| 8 = @jsdoc_non_nullable_type_expr
+| 9 = @jsdoc_record_type_expr
+| 10 = @jsdoc_array_type_expr
+| 11 = @jsdoc_union_type_expr
+| 12 = @jsdoc_function_type_expr
+| 13 = @jsdoc_optional_type_expr
+| 14 = @jsdoc_rest_type_expr
+;
+
+#keyset[id, idx]
+jsdoc_record_field_name (int id: @jsdoc_record_type_expr ref, int idx: int ref, varchar(900) name: string ref);
+jsdoc_prefix_qualifier (int id: @jsdoc_type_expr ref);
+jsdoc_has_new_parameter (int fn: @jsdoc_function_type_expr ref);
+
+@jsdoc_type_expr_parent = @jsdoc_type_expr | @jsdoc_tag;
+
+jsdoc_errors (unique int id: @jsdoc_error, int tag: @jsdoc_tag ref, varchar(900) message: string ref, varchar(900) tostring: string ref);
+
+// YAML
+#keyset[parent, idx]
+yaml (unique int id: @yaml_node,
+ int kind: int ref,
+ int parent: @yaml_node_parent ref,
+ int idx: int ref,
+ varchar(900) tag: string ref,
+ varchar(900) tostring: string ref);
+
+case @yaml_node.kind of
+ 0 = @yaml_scalar_node
+| 1 = @yaml_mapping_node
+| 2 = @yaml_sequence_node
+| 3 = @yaml_alias_node
+;
+
+@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node;
+
+@yaml_node_parent = @yaml_collection_node | @file;
+
+yaml_anchors (unique int node: @yaml_node ref,
+ varchar(900) anchor: string ref);
+
+yaml_aliases (unique int alias: @yaml_alias_node ref,
+ varchar(900) target: string ref);
+
+yaml_scalars (unique int scalar: @yaml_scalar_node ref,
+ int style: int ref,
+ varchar(900) value: string ref);
+
+yaml_errors (unique int id: @yaml_error,
+ varchar(900) message: string ref);
+
+/* XML Files */
+
+xmlEncoding(
+ unique int id: @file ref,
+ varchar(900) encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ varchar(900) root: string ref,
+ varchar(900) publicId: string ref,
+ varchar(900) systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ varchar(900) name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ varchar(900) name: string ref,
+ varchar(3600) value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ varchar(900) prefixName: string ref,
+ varchar(900) URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ varchar(3600) text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ varchar(3600) text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property;
+
+@optionalchainable = @callexpr | @propaccess;
+
+isOptionalChaining(int id: @optionalchainable ref);
+
+/* Last updated 2018/10/23. */
diff --git a/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/semmlecode.javascript.dbscheme b/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/semmlecode.javascript.dbscheme
new file mode 100644
index 00000000000..eee08932d52
--- /dev/null
+++ b/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/semmlecode.javascript.dbscheme
@@ -0,0 +1,1090 @@
+/*** Standard fragments ***/
+
+/** Files and folders **/
+
+@location = @location_default;
+
+locations_default(unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref
+ );
+
+@sourceline = @locatable;
+
+numlines(int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref
+ );
+
+
+/*
+ fromSource(0) = unknown,
+ fromSource(1) = from source,
+ fromSource(2) = from library
+*/
+files(unique int id: @file,
+ varchar(900) name: string ref,
+ varchar(900) simple: string ref,
+ varchar(900) ext: string ref,
+ int fromSource: int ref);
+
+folders(unique int id: @folder,
+ varchar(900) name: string ref,
+ varchar(900) simple: string ref);
+
+
+@container = @folder | @file ;
+
+
+containerparent(int parent: @container ref,
+ unique int child: @container ref);
+
+/** Duplicate code **/
+
+duplicateCode(
+ unique int id : @duplication,
+ varchar(900) relativePath : string ref,
+ int equivClass : int ref);
+
+similarCode(
+ unique int id : @similarity,
+ varchar(900) relativePath : string ref,
+ int equivClass : int ref);
+
+@duplication_or_similarity = @duplication | @similarity;
+
+tokens(
+ int id : @duplication_or_similarity ref,
+ int offset : int ref,
+ int beginLine : int ref,
+ int beginColumn : int ref,
+ int endLine : int ref,
+ int endColumn : int ref);
+
+/** External data **/
+
+externalData(
+ int id : @externalDataElement,
+ varchar(900) path : string ref,
+ int column: int ref,
+ varchar(900) value : string ref
+);
+
+snapshotDate(unique date snapshotDate : date ref);
+
+sourceLocationPrefix(varchar(900) prefix : string ref);
+
+/** Version control data **/
+
+svnentries(
+ int id : @svnentry,
+ varchar(500) revision : string ref,
+ varchar(500) author : string ref,
+ date revisionDate : date ref,
+ int changeSize : int ref
+);
+
+svnaffectedfiles(
+ int id : @svnentry ref,
+ int file : @file ref,
+ varchar(500) action : string ref
+);
+
+svnentrymsg(
+ int id : @svnentry ref,
+ varchar(500) message : string ref
+);
+
+svnchurn(
+ int commit : @svnentry ref,
+ int file : @file ref,
+ int addedLines : int ref,
+ int deletedLines : int ref
+);
+
+
+/*** JavaScript-specific part ***/
+
+filetype(
+ int file: @file ref,
+ string filetype: string ref
+)
+
+// top-level code fragments
+toplevels (unique int id: @toplevel,
+ int kind: int ref);
+
+isExterns (int toplevel: @toplevel ref);
+
+case @toplevel.kind of
+ 0 = @script
+| 1 = @inline_script
+| 2 = @event_handler
+| 3 = @javascript_url;
+
+isModule (int tl: @toplevel ref);
+isNodejs (int tl: @toplevel ref);
+isES2015Module (int tl: @toplevel ref);
+isClosureModule (int tl: @toplevel ref);
+
+// statements
+#keyset[parent, idx]
+stmts (unique int id: @stmt,
+ int kind: int ref,
+ int parent: @stmtparent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+
+stmtContainers (unique int stmt: @stmt ref,
+ int container: @stmt_container ref);
+
+jumpTargets (unique int jump: @stmt ref,
+ int target: @stmt ref);
+
+@stmtparent = @stmt | @toplevel | @functionexpr | @arrowfunctionexpr;
+@stmt_container = @toplevel | @function | @namespacedeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration;
+
+case @stmt.kind of
+ 0 = @emptystmt
+| 1 = @blockstmt
+| 2 = @exprstmt
+| 3 = @ifstmt
+| 4 = @labeledstmt
+| 5 = @breakstmt
+| 6 = @continuestmt
+| 7 = @withstmt
+| 8 = @switchstmt
+| 9 = @returnstmt
+| 10 = @throwstmt
+| 11 = @trystmt
+| 12 = @whilestmt
+| 13 = @dowhilestmt
+| 14 = @forstmt
+| 15 = @forinstmt
+| 16 = @debuggerstmt
+| 17 = @functiondeclstmt
+| 18 = @vardeclstmt
+| 19 = @case
+| 20 = @catchclause
+| 21 = @forofstmt
+| 22 = @constdeclstmt
+| 23 = @letstmt
+| 24 = @legacy_letstmt
+| 25 = @foreachstmt
+| 26 = @classdeclstmt
+| 27 = @importdeclaration
+| 28 = @exportalldeclaration
+| 29 = @exportdefaultdeclaration
+| 30 = @exportnameddeclaration
+| 31 = @namespacedeclaration
+| 32 = @importequalsdeclaration
+| 33 = @exportassigndeclaration
+| 34 = @interfacedeclaration
+| 35 = @typealiasdeclaration
+| 36 = @enumdeclaration
+| 37 = @externalmoduledeclaration
+| 38 = @exportasnamespacedeclaration
+| 39 = @globalaugmentationdeclaration
+;
+
+@declstmt = @vardeclstmt | @constdeclstmt | @letstmt | @legacy_letstmt;
+
+@exportdeclaration = @exportalldeclaration | @exportdefaultdeclaration | @exportnameddeclaration;
+
+@namespacedefinition = @namespacedeclaration | @enumdeclaration;
+@typedefinition = @classdefinition | @interfacedeclaration | @enumdeclaration | @typealiasdeclaration | @enum_member;
+
+isInstantiated(unique int decl: @namespacedeclaration ref);
+
+@declarablestmt = @declstmt | @namespacedeclaration | @classdeclstmt | @functiondeclstmt | @enumdeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration;
+hasDeclareKeyword(unique int stmt: @declarablestmt ref);
+
+isForAwaitOf(unique int forof: @forofstmt ref);
+
+// expressions
+#keyset[parent, idx]
+exprs (unique int id: @expr,
+ int kind: int ref,
+ int parent: @exprparent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+
+literals (varchar(900) value: string ref,
+ varchar(900) raw: string ref,
+ unique int expr: @exprortype ref);
+
+enclosingStmt (unique int expr: @exprortype ref,
+ int stmt: @stmt ref);
+
+exprContainers (unique int expr: @exprortype ref,
+ int container: @stmt_container ref);
+
+arraySize (unique int ae: @arraylike ref,
+ int sz: int ref);
+
+isDelegating (int yield: @yieldexpr ref);
+
+@exprorstmt = @expr | @stmt;
+@exprortype = @expr | @typeexpr;
+@exprparent = @exprorstmt | @property | @functiontypeexpr;
+@arraylike = @arrayexpr | @arraypattern;
+
+case @expr.kind of
+ 0 = @label
+| 1 = @nullliteral
+| 2 = @booleanliteral
+| 3 = @numberliteral
+| 4 = @stringliteral
+| 5 = @regexpliteral
+| 6 = @thisexpr
+| 7 = @arrayexpr
+| 8 = @objexpr
+| 9 = @functionexpr
+| 10 = @seqexpr
+| 11 = @conditionalexpr
+| 12 = @newexpr
+| 13 = @callexpr
+| 14 = @dotexpr
+| 15 = @indexexpr
+| 16 = @negexpr
+| 17 = @plusexpr
+| 18 = @lognotexpr
+| 19 = @bitnotexpr
+| 20 = @typeofexpr
+| 21 = @voidexpr
+| 22 = @deleteexpr
+| 23 = @eqexpr
+| 24 = @neqexpr
+| 25 = @eqqexpr
+| 26 = @neqqexpr
+| 27 = @ltexpr
+| 28 = @leexpr
+| 29 = @gtexpr
+| 30 = @geexpr
+| 31 = @lshiftexpr
+| 32 = @rshiftexpr
+| 33 = @urshiftexpr
+| 34 = @addexpr
+| 35 = @subexpr
+| 36 = @mulexpr
+| 37 = @divexpr
+| 38 = @modexpr
+| 39 = @bitorexpr
+| 40 = @xorexpr
+| 41 = @bitandexpr
+| 42 = @inexpr
+| 43 = @instanceofexpr
+| 44 = @logandexpr
+| 45 = @logorexpr
+| 47 = @assignexpr
+| 48 = @assignaddexpr
+| 49 = @assignsubexpr
+| 50 = @assignmulexpr
+| 51 = @assigndivexpr
+| 52 = @assignmodexpr
+| 53 = @assignlshiftexpr
+| 54 = @assignrshiftexpr
+| 55 = @assignurshiftexpr
+| 56 = @assignorexpr
+| 57 = @assignxorexpr
+| 58 = @assignandexpr
+| 59 = @preincexpr
+| 60 = @postincexpr
+| 61 = @predecexpr
+| 62 = @postdecexpr
+| 63 = @parexpr
+| 64 = @vardeclarator
+| 65 = @arrowfunctionexpr
+| 66 = @spreadelement
+| 67 = @arraypattern
+| 68 = @objectpattern
+| 69 = @yieldexpr
+| 70 = @taggedtemplateexpr
+| 71 = @templateliteral
+| 72 = @templateelement
+| 73 = @arraycomprehensionexpr
+| 74 = @generatorexpr
+| 75 = @forincomprehensionblock
+| 76 = @forofcomprehensionblock
+| 77 = @legacy_letexpr
+| 78 = @vardecl
+| 79 = @proper_varaccess
+| 80 = @classexpr
+| 81 = @superexpr
+| 82 = @newtargetexpr
+| 83 = @namedimportspecifier
+| 84 = @importdefaultspecifier
+| 85 = @importnamespacespecifier
+| 86 = @namedexportspecifier
+| 87 = @expexpr
+| 88 = @assignexpexpr
+| 89 = @jsxelement
+| 90 = @jsxqualifiedname
+| 91 = @jsxemptyexpr
+| 92 = @awaitexpr
+| 93 = @functionsentexpr
+| 94 = @decorator
+| 95 = @exportdefaultspecifier
+| 96 = @exportnamespacespecifier
+| 97 = @bindexpr
+| 98 = @externalmodulereference
+| 99 = @dynamicimport
+| 100 = @expressionwithtypearguments
+| 101 = @prefixtypeassertion
+| 102 = @astypeassertion
+| 103 = @export_varaccess
+| 104 = @decorator_list
+| 105 = @non_null_assertion
+| 106 = @bigintliteral
+| 107 = @nullishcoalescingexpr
+;
+
+@varaccess = @proper_varaccess | @export_varaccess;
+@varref = @vardecl | @varaccess;
+
+@identifier = @label | @varref | @typeidentifier;
+
+@literal = @nullliteral | @booleanliteral | @numberliteral | @stringliteral | @regexpliteral | @bigintliteral;
+
+@propaccess = @dotexpr | @indexexpr;
+
+@invokeexpr = @newexpr | @callexpr;
+
+@unaryexpr = @negexpr | @plusexpr | @lognotexpr | @bitnotexpr | @typeofexpr | @voidexpr | @deleteexpr | @spreadelement;
+
+@equalitytest = @eqexpr | @neqexpr | @eqqexpr | @neqqexpr;
+
+@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr;
+
+@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr | @nullishcoalescingexpr;
+
+@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr;
+
+@updateexpr = @preincexpr | @postincexpr | @predecexpr | @postdecexpr;
+
+@pattern = @varref | @arraypattern | @objectpattern;
+
+@comprehensionexpr = @arraycomprehensionexpr | @generatorexpr;
+
+@comprehensionblock = @forincomprehensionblock | @forofcomprehensionblock;
+
+@importspecifier = @namedimportspecifier | @importdefaultspecifier | @importnamespacespecifier;
+
+@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier;
+
+@typeassertion = @astypeassertion | @prefixtypeassertion;
+
+@classdefinition = @classdeclstmt | @classexpr;
+@interfacedefinition = @interfacedeclaration | @interfacetypeexpr;
+@classorinterface = @classdefinition | @interfacedefinition;
+
+@lexical_decl = @vardecl | @typedecl;
+@lexical_access = @varaccess | @localtypeaccess | @localvartypeaccess | @localnamespaceaccess;
+@lexical_ref = @lexical_decl | @lexical_access;
+
+// scopes
+scopes (unique int id: @scope,
+ int kind: int ref);
+
+case @scope.kind of
+ 0 = @globalscope
+| 1 = @functionscope
+| 2 = @catchscope
+| 3 = @modulescope
+| 4 = @blockscope
+| 5 = @forscope
+| 6 = @forinscope // for-of scopes work the same as for-in scopes
+| 7 = @comprehensionblockscope
+| 8 = @classexprscope
+| 9 = @namespacescope
+| 10 = @classdeclscope
+| 11 = @interfacescope
+| 12 = @typealiasscope
+| 13 = @mappedtypescope
+| 14 = @enumscope
+| 15 = @externalmodulescope
+| 16 = @conditionaltypescope;
+
+scopenodes (unique int node: @ast_node ref,
+ int scope: @scope ref);
+
+scopenesting (unique int inner: @scope ref,
+ int outer: @scope ref);
+
+// functions
+@function = @functiondeclstmt | @functionexpr | @arrowfunctionexpr;
+
+@parameterized = @function | @catchclause;
+@type_parameterized = @function | @classorinterface | @typealiasdeclaration | @mappedtypeexpr | @infertypeexpr;
+
+isGenerator (int fun: @function ref);
+hasRestParameter (int fun: @function ref);
+isAsync (int fun: @function ref);
+
+// variables and lexically scoped type names
+#keyset[scope, name]
+variables (unique int id: @variable,
+ varchar(900) name: string ref,
+ int scope: @scope ref);
+
+#keyset[scope, name]
+local_type_names (unique int id: @local_type_name,
+ varchar(900) name: string ref,
+ int scope: @scope ref);
+
+#keyset[scope, name]
+local_namespace_names (unique int id: @local_namespace_name,
+ varchar(900) name: string ref,
+ int scope: @scope ref);
+
+isArgumentsObject (int id: @variable ref);
+
+@lexical_name = @variable | @local_type_name | @local_namespace_name;
+
+@bind_id = @varaccess | @localvartypeaccess;
+bind (unique int id: @bind_id ref,
+ int decl: @variable ref);
+
+decl (unique int id: @vardecl ref,
+ int decl: @variable ref);
+
+@typebind_id = @localtypeaccess | @export_varaccess;
+typebind (unique int id: @typebind_id ref,
+ int decl: @local_type_name ref);
+
+@typedecl_id = @typedecl | @vardecl;
+typedecl (unique int id: @typedecl_id ref,
+ int decl: @local_type_name ref);
+
+namespacedecl (unique int id: @vardecl ref,
+ int decl: @local_namespace_name ref);
+
+@namespacebind_id = @localnamespaceaccess | @export_varaccess;
+namespacebind (unique int id: @namespacebind_id ref,
+ int decl: @local_namespace_name ref);
+
+
+// properties in object literals, property patterns in object patterns, and method declarations in classes
+#keyset[parent, index]
+properties (unique int id: @property,
+ int parent: @property_parent ref,
+ int index: int ref,
+ int kind: int ref,
+ varchar(900) tostring: string ref);
+
+case @property.kind of
+ 0 = @value_property
+| 1 = @property_getter
+| 2 = @property_setter
+| 3 = @jsx_attribute
+| 4 = @function_call_signature
+| 5 = @constructor_call_signature
+| 6 = @index_signature
+| 7 = @enum_member
+| 8 = @proper_field
+| 9 = @parameter_field
+;
+
+@property_parent = @objexpr | @objectpattern | @classdefinition | @jsxelement | @interfacedefinition | @enumdeclaration;
+@property_accessor = @property_getter | @property_setter;
+@call_signature = @function_call_signature | @constructor_call_signature;
+@field = @proper_field | @parameter_field;
+@field_or_vardeclarator = @field | @vardeclarator;
+
+isComputed (int id: @property ref);
+isMethod (int id: @property ref);
+isStatic (int id: @property ref);
+isAbstractMember (int id: @property ref);
+isConstEnum (int id: @enumdeclaration ref);
+isAbstractClass (int id: @classdeclstmt ref);
+
+hasPublicKeyword (int id: @property ref);
+hasPrivateKeyword (int id: @property ref);
+hasProtectedKeyword (int id: @property ref);
+hasReadonlyKeyword (int id: @property ref);
+isOptionalMember (int id: @property ref);
+hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref);
+
+#keyset[constructor, param_index]
+parameter_fields(
+ unique int field: @parameter_field ref,
+ int constructor: @functionexpr ref,
+ int param_index: int ref
+);
+
+// types
+#keyset[parent, idx]
+typeexprs (
+ unique int id: @typeexpr,
+ int kind: int ref,
+ int parent: @typeexpr_parent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref
+);
+
+case @typeexpr.kind of
+ 0 = @localtypeaccess
+| 1 = @typedecl
+| 2 = @keywordtypeexpr
+| 3 = @stringliteraltypeexpr
+| 4 = @numberliteraltypeexpr
+| 5 = @booleanliteraltypeexpr
+| 6 = @arraytypeexpr
+| 7 = @uniontypeexpr
+| 8 = @indexedaccesstypeexpr
+| 9 = @intersectiontypeexpr
+| 10 = @parenthesizedtypeexpr
+| 11 = @tupletypeexpr
+| 12 = @keyoftypeexpr
+| 13 = @qualifiedtypeaccess
+| 14 = @generictypeexpr
+| 15 = @typelabel
+| 16 = @typeoftypeexpr
+| 17 = @localvartypeaccess
+| 18 = @qualifiedvartypeaccess
+| 19 = @thisvartypeaccess
+| 20 = @istypeexpr
+| 21 = @interfacetypeexpr
+| 22 = @typeparameter
+| 23 = @plainfunctiontypeexpr
+| 24 = @constructortypeexpr
+| 25 = @localnamespaceaccess
+| 26 = @qualifiednamespaceaccess
+| 27 = @mappedtypeexpr
+| 28 = @conditionaltypeexpr
+| 29 = @infertypeexpr
+| 30 = @importtypeaccess
+| 31 = @importnamespaceaccess
+| 32 = @importvartypeaccess
+| 33 = @optionaltypeexpr
+| 34 = @resttypeexpr
+| 35 = @bigintliteraltypeexpr
+;
+
+@typeref = @typeaccess | @typedecl;
+@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess;
+@typeexpr_parent = @expr | @stmt | @property | @typeexpr;
+@literaltypeexpr = @stringliteraltypeexpr | @numberliteraltypeexpr | @booleanliteraltypeexpr | @bigintliteraltypeexpr;
+@typeaccess = @localtypeaccess | @qualifiedtypeaccess | @importtypeaccess;
+@vartypeaccess = @localvartypeaccess | @qualifiedvartypeaccess | @thisvartypeaccess | @importvartypeaccess;
+@namespaceaccess = @localnamespaceaccess | @qualifiednamespaceaccess | @importnamespaceaccess;
+@importtypeexpr = @importtypeaccess | @importnamespaceaccess | @importvartypeaccess;
+
+@functiontypeexpr = @plainfunctiontypeexpr | @constructortypeexpr;
+
+// types
+types (
+ unique int id: @type,
+ int kind: int ref,
+ varchar(900) tostring: string ref
+);
+
+#keyset[parent, idx]
+type_child (
+ int child: @type ref,
+ int parent: @type ref,
+ int idx: int ref
+);
+
+case @type.kind of
+ 0 = @anytype
+| 1 = @stringtype
+| 2 = @numbertype
+| 3 = @uniontype
+| 4 = @truetype
+| 5 = @falsetype
+| 6 = @typereference
+| 7 = @objecttype
+| 8 = @canonicaltypevariabletype
+| 9 = @typeoftype
+| 10 = @voidtype
+| 11 = @undefinedtype
+| 12 = @nulltype
+| 13 = @nevertype
+| 14 = @plainsymboltype
+| 15 = @uniquesymboltype
+| 16 = @objectkeywordtype
+| 17 = @intersectiontype
+| 18 = @tupletype
+| 19 = @lexicaltypevariabletype
+| 20 = @thistype
+| 21 = @numberliteraltype
+| 22 = @stringliteraltype
+| 23 = @unknowntype
+| 24 = @biginttype
+| 25 = @bigintliteraltype
+;
+
+@booleanliteraltype = @truetype | @falsetype;
+@symboltype = @plainsymboltype | @uniquesymboltype;
+@unionorintersectiontype = @uniontype | @intersectiontype;
+@typevariabletype = @canonicaltypevariabletype | @lexicaltypevariabletype;
+
+@typed_ast_node = @expr | @typeexpr | @function;
+ast_node_type(
+ unique int node: @typed_ast_node ref,
+ int typ: @type ref);
+
+invoke_expr_signature(
+ unique int node: @invokeexpr ref,
+ int sig: @signature_type ref
+);
+
+invoke_expr_overload_index(
+ unique int node: @invokeexpr ref,
+ int index: int ref
+);
+
+symbols (
+ unique int id: @symbol,
+ int kind: int ref,
+ varchar(900) name: string ref
+);
+
+symbol_parent (
+ unique int symbol: @symbol ref,
+ int parent: @symbol ref
+);
+
+symbol_module (
+ int symbol: @symbol ref,
+ varchar(900) moduleName: string ref
+);
+
+symbol_global (
+ int symbol: @symbol ref,
+ varchar(900) globalName: string ref
+);
+
+case @symbol.kind of
+ 0 = @root_symbol
+| 1 = @member_symbol
+| 2 = @other_symbol
+;
+
+@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype;
+@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr;
+
+ast_node_symbol(
+ unique int node: @ast_node_with_symbol ref,
+ int symbol: @symbol ref);
+
+type_symbol(
+ unique int typ: @type_with_symbol ref,
+ int symbol: @symbol ref);
+
+#keyset[typ, name]
+type_property(
+ int typ: @type ref,
+ varchar(900) name: string ref,
+ int propertyType: @type ref);
+
+@literaltype = @stringliteraltype | @numberliteraltype | @booleanliteraltype | @bigintliteraltype;
+@type_with_literal_value = @stringliteraltype | @numberliteraltype | @bigintliteraltype;
+type_literal_value(
+ unique int typ: @type_with_literal_value ref,
+ varchar(900) value: string ref);
+
+signature_types (
+ unique int id: @signature_type,
+ int kind: int ref,
+ varchar(900) tostring: string ref,
+ int type_parameters: int ref,
+ int required_params: int ref
+);
+
+case @signature_type.kind of
+ 0 = @function_signature_type
+| 1 = @constructor_signature_type
+;
+
+#keyset[typ, kind, index]
+type_contains_signature (
+ int typ: @type ref,
+ int kind: int ref, // constructor/call/index
+ int index: int ref, // ordering of overloaded signatures
+ int sig: @signature_type ref
+);
+
+#keyset[parent, index]
+signature_contains_type (
+ int child: @type ref,
+ int parent: @signature_type ref,
+ int index: int ref
+);
+
+#keyset[sig, index]
+signature_parameter_name (
+ int sig: @signature_type ref,
+ int index: int ref,
+ varchar(900) name: string ref
+);
+
+number_index_type (
+ unique int baseType: @type ref,
+ int propertyType: @type ref
+);
+
+string_index_type (
+ unique int baseType: @type ref,
+ int propertyType: @type ref
+);
+
+base_type_names(
+ int typeName: @symbol ref,
+ int baseTypeName: @symbol ref
+);
+
+self_types(
+ int typeName: @symbol ref,
+ int selfType: @typereference ref
+);
+
+tuple_type_min_length(
+ unique int typ: @type ref,
+ int minLength: int ref
+);
+
+tuple_type_rest(
+ unique int typ: @type ref
+);
+
+// comments
+comments (unique int id: @comment,
+ int kind: int ref,
+ int toplevel: @toplevel ref,
+ varchar(900) text: string ref,
+ varchar(900) tostring: string ref);
+
+case @comment.kind of
+ 0 = @slashslashcomment
+| 1 = @slashstarcomment
+| 2 = @doccomment
+| 3 = @htmlcommentstart
+| 4 = @htmlcommentend;
+
+@htmlcomment = @htmlcommentstart | @htmlcommentend;
+@linecomment = @slashslashcomment | @htmlcomment;
+@blockcomment = @slashstarcomment | @doccomment;
+
+// source lines
+lines (unique int id: @line,
+ int toplevel: @toplevel ref,
+ varchar(900) text: string ref,
+ varchar(2) terminator: string ref);
+indentation (int file: @file ref,
+ int lineno: int ref,
+ varchar(1) indentChar: string ref,
+ int indentDepth: int ref);
+
+// JavaScript parse errors
+jsParseErrors (unique int id: @js_parse_error,
+ int toplevel: @toplevel ref,
+ varchar(900) message: string ref,
+ varchar(900) line: string ref);
+
+// regular expressions
+#keyset[parent, idx]
+regexpterm (unique int id: @regexpterm,
+ int kind: int ref,
+ int parent: @regexpparent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+
+@regexpparent = @regexpterm | @regexpliteral;
+
+case @regexpterm.kind of
+ 0 = @regexp_alt
+| 1 = @regexp_seq
+| 2 = @regexp_caret
+| 3 = @regexp_dollar
+| 4 = @regexp_wordboundary
+| 5 = @regexp_nonwordboundary
+| 6 = @regexp_positive_lookahead
+| 7 = @regexp_negative_lookahead
+| 8 = @regexp_star
+| 9 = @regexp_plus
+| 10 = @regexp_opt
+| 11 = @regexp_range
+| 12 = @regexp_dot
+| 13 = @regexp_group
+| 14 = @regexp_normal_char
+| 15 = @regexp_hex_escape
+| 16 = @regexp_unicode_escape
+| 17 = @regexp_dec_escape
+| 18 = @regexp_oct_escape
+| 19 = @regexp_ctrl_escape
+| 20 = @regexp_char_class_escape
+| 21 = @regexp_id_escape
+| 22 = @regexp_backref
+| 23 = @regexp_char_class
+| 24 = @regexp_char_range
+| 25 = @regexp_positive_lookbehind
+| 26 = @regexp_negative_lookbehind
+| 27 = @regexp_unicode_property_escape;
+
+regexpParseErrors (unique int id: @regexp_parse_error,
+ int regexp: @regexpterm ref,
+ varchar(900) message: string ref);
+
+@regexp_quantifier = @regexp_star | @regexp_plus | @regexp_opt | @regexp_range;
+@regexp_escape = @regexp_char_escape | @regexp_char_class_escape | @regexp_unicode_property_escape;
+@regexp_char_escape = @regexp_hex_escape | @regexp_unicode_escape | @regexp_dec_escape | @regexp_oct_escape | @regexp_ctrl_escape | @regexp_id_escape;
+@regexp_constant = @regexp_normal_char | @regexp_char_escape;
+@regexp_lookahead = @regexp_positive_lookahead | @regexp_negative_lookahead;
+@regexp_lookbehind = @regexp_positive_lookbehind | @regexp_negative_lookbehind;
+@regexp_subpattern = @regexp_lookahead | @regexp_lookbehind;
+
+isGreedy (int id: @regexp_quantifier ref);
+rangeQuantifierLowerBound (unique int id: @regexp_range ref, int lo: int ref);
+rangeQuantifierUpperBound (unique int id: @regexp_range ref, int hi: int ref);
+isCapture (unique int id: @regexp_group ref, int number: int ref);
+isNamedCapture (unique int id: @regexp_group ref, string name: string ref);
+isInverted (int id: @regexp_char_class ref);
+regexpConstValue (unique int id: @regexp_constant ref, varchar(1) value: string ref);
+charClassEscape (unique int id: @regexp_char_class_escape ref, varchar(1) value: string ref);
+backref (unique int id: @regexp_backref ref, int value: int ref);
+namedBackref (unique int id: @regexp_backref ref, string name: string ref);
+unicodePropertyEscapeName (unique int id: @regexp_unicode_property_escape ref, string name: string ref);
+unicodePropertyEscapeValue (unique int id: @regexp_unicode_property_escape ref, string value: string ref);
+
+// tokens
+#keyset[toplevel, idx]
+tokeninfo (unique int id: @token,
+ int kind: int ref,
+ int toplevel: @toplevel ref,
+ int idx: int ref,
+ varchar(900) value: string ref);
+
+case @token.kind of
+ 0 = @token_eof
+| 1 = @token_null_literal
+| 2 = @token_boolean_literal
+| 3 = @token_numeric_literal
+| 4 = @token_string_literal
+| 5 = @token_regular_expression
+| 6 = @token_identifier
+| 7 = @token_keyword
+| 8 = @token_punctuator;
+
+// associate comments with the token immediately following them (which may be EOF)
+next_token (int comment: @comment ref, int token: @token ref);
+
+// JSON
+#keyset[parent, idx]
+json (unique int id: @json_value,
+ int kind: int ref,
+ int parent: @json_parent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+
+json_literals (varchar(900) value: string ref,
+ varchar(900) raw: string ref,
+ unique int expr: @json_value ref);
+
+json_properties (int obj: @json_object ref,
+ varchar(900) property: string ref,
+ int value: @json_value ref);
+
+json_errors (unique int id: @json_parse_error,
+ varchar(900) message: string ref);
+
+case @json_value.kind of
+ 0 = @json_null
+| 1 = @json_boolean
+| 2 = @json_number
+| 3 = @json_string
+| 4 = @json_array
+| 5 = @json_object;
+
+@json_parent = @json_object | @json_array | @file;
+
+// locations
+@ast_node = @toplevel | @stmt | @expr | @property | @typeexpr;
+
+@locatable = @file
+ | @ast_node
+ | @comment
+ | @line
+ | @js_parse_error | @regexp_parse_error | @json_parse_error
+ | @regexpterm
+ | @json_value
+ | @token
+ | @cfg_node
+ | @jsdoc | @jsdoc_type_expr | @jsdoc_tag
+ | @yaml_node | @yaml_error
+ | @xmllocatable;
+
+hasLocation (unique int locatable: @locatable ref,
+ int location: @location ref);
+
+// CFG
+entry_cfg_node (unique int id: @entry_node, int container: @stmt_container ref);
+exit_cfg_node (unique int id: @exit_node, int container: @stmt_container ref);
+guard_node (unique int id: @guard_node, int kind: int ref, int test: @expr ref);
+case @guard_node.kind of
+ 0 = @falsy_guard
+| 1 = @truthy_guard;
+@condition_guard = @falsy_guard | @truthy_guard;
+
+@synthetic_cfg_node = @entry_node | @exit_node | @guard_node;
+@cfg_node = @synthetic_cfg_node | @exprparent;
+
+successor (int pred: @cfg_node ref, int succ: @cfg_node ref);
+
+// JSDoc comments
+jsdoc (unique int id: @jsdoc, varchar(900) description: string ref, int comment: @comment ref);
+#keyset[parent, idx]
+jsdoc_tags (unique int id: @jsdoc_tag, varchar(900) title: string ref,
+ int parent: @jsdoc ref, int idx: int ref, varchar(900) tostring: string ref);
+jsdoc_tag_descriptions (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref);
+jsdoc_tag_names (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref);
+
+#keyset[parent, idx]
+jsdoc_type_exprs (unique int id: @jsdoc_type_expr,
+ int kind: int ref,
+ int parent: @jsdoc_type_expr_parent ref,
+ int idx: int ref,
+ varchar(900) tostring: string ref);
+case @jsdoc_type_expr.kind of
+ 0 = @jsdoc_any_type_expr
+| 1 = @jsdoc_null_type_expr
+| 2 = @jsdoc_undefined_type_expr
+| 3 = @jsdoc_unknown_type_expr
+| 4 = @jsdoc_void_type_expr
+| 5 = @jsdoc_named_type_expr
+| 6 = @jsdoc_applied_type_expr
+| 7 = @jsdoc_nullable_type_expr
+| 8 = @jsdoc_non_nullable_type_expr
+| 9 = @jsdoc_record_type_expr
+| 10 = @jsdoc_array_type_expr
+| 11 = @jsdoc_union_type_expr
+| 12 = @jsdoc_function_type_expr
+| 13 = @jsdoc_optional_type_expr
+| 14 = @jsdoc_rest_type_expr
+;
+
+#keyset[id, idx]
+jsdoc_record_field_name (int id: @jsdoc_record_type_expr ref, int idx: int ref, varchar(900) name: string ref);
+jsdoc_prefix_qualifier (int id: @jsdoc_type_expr ref);
+jsdoc_has_new_parameter (int fn: @jsdoc_function_type_expr ref);
+
+@jsdoc_type_expr_parent = @jsdoc_type_expr | @jsdoc_tag;
+
+jsdoc_errors (unique int id: @jsdoc_error, int tag: @jsdoc_tag ref, varchar(900) message: string ref, varchar(900) tostring: string ref);
+
+// YAML
+#keyset[parent, idx]
+yaml (unique int id: @yaml_node,
+ int kind: int ref,
+ int parent: @yaml_node_parent ref,
+ int idx: int ref,
+ varchar(900) tag: string ref,
+ varchar(900) tostring: string ref);
+
+case @yaml_node.kind of
+ 0 = @yaml_scalar_node
+| 1 = @yaml_mapping_node
+| 2 = @yaml_sequence_node
+| 3 = @yaml_alias_node
+;
+
+@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node;
+
+@yaml_node_parent = @yaml_collection_node | @file;
+
+yaml_anchors (unique int node: @yaml_node ref,
+ varchar(900) anchor: string ref);
+
+yaml_aliases (unique int alias: @yaml_alias_node ref,
+ varchar(900) target: string ref);
+
+yaml_scalars (unique int scalar: @yaml_scalar_node ref,
+ int style: int ref,
+ varchar(900) value: string ref);
+
+yaml_errors (unique int id: @yaml_error,
+ varchar(900) message: string ref);
+
+/* XML Files */
+
+xmlEncoding(
+ unique int id: @file ref,
+ varchar(900) encoding: string ref
+);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ varchar(900) root: string ref,
+ varchar(900) publicId: string ref,
+ varchar(900) systemId: string ref,
+ int fileid: @file ref
+);
+
+xmlElements(
+ unique int id: @xmlelement,
+ varchar(900) name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ varchar(900) name: string ref,
+ varchar(3600) value: string ref,
+ int idx: int ref,
+ int fileid: @file ref
+);
+
+xmlNs(
+ int id: @xmlnamespace,
+ varchar(900) prefixName: string ref,
+ varchar(900) URI: string ref,
+ int fileid: @file ref
+);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref
+);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ varchar(3600) text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref
+);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ varchar(3600) text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref
+);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref
+);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property;
+
+@optionalchainable = @callexpr | @propaccess;
+
+isOptionalChaining(int id: @optionalchainable ref);
+
+/* Last updated 2018/10/23. */
diff --git a/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/upgrade.properties b/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/upgrade.properties
new file mode 100644
index 00000000000..22cc684935f
--- /dev/null
+++ b/javascript/upgrades/7eead88269b20e9d61f61cd2291affb737dc831e/upgrade.properties
@@ -0,0 +1,4 @@
+description: add support for Closure modules
+compatibility: backwards
+
+isES2015Module.rel: run isES2015Module.qlo