support module live bindings in arbitary positions not in Program statement position - fixes #1760
This commit is contained in:
@@ -1,78 +1,16 @@
|
||||
import * as messages from "../../messages";
|
||||
import Remaps from "./lib/remaps";
|
||||
import extend from "lodash/object/extend";
|
||||
import object from "../../helpers/object";
|
||||
import * as util from "../../util";
|
||||
import * as t from "../../types";
|
||||
|
||||
var remapVisitor = {
|
||||
enter(node) {
|
||||
if (node._skipModulesRemap) {
|
||||
return this.skip();
|
||||
}
|
||||
},
|
||||
|
||||
ReferencedIdentifier(node, parent, scope, formatter) {
|
||||
var remap = formatter.internalRemap[node.name];
|
||||
|
||||
if (remap && node !== remap) {
|
||||
if (!scope.hasBinding(node.name) || scope.bindingIdentifierEquals(node.name, formatter.localImports[node.name])) {
|
||||
if (this.key === "callee" && this.parentPath.isCallExpression()) {
|
||||
return t.sequenceExpression([t.literal(0), remap]);
|
||||
} else {
|
||||
return remap;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
AssignmentExpression: {
|
||||
exit(node, parent, scope, formatter) {
|
||||
if (!node._ignoreModulesRemap) {
|
||||
var exported = formatter.getExport(node.left, scope);
|
||||
if (exported) {
|
||||
return formatter.remapExportAssignment(node, exported);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
UpdateExpression(node, parent, scope, formatter) {
|
||||
var exported = formatter.getExport(node.argument, scope);
|
||||
if (!exported) return;
|
||||
|
||||
this.skip();
|
||||
|
||||
// expand to long file assignment expression
|
||||
var assign = t.assignmentExpression(node.operator[0] + "=", node.argument, t.literal(1));
|
||||
|
||||
// remap this assignment expression
|
||||
var remapped = formatter.remapExportAssignment(assign, exported);
|
||||
|
||||
// we don't need to change the result
|
||||
if (t.isExpressionStatement(parent) || node.prefix) {
|
||||
return remapped;
|
||||
}
|
||||
|
||||
var nodes = [];
|
||||
nodes.push(remapped);
|
||||
|
||||
var operator;
|
||||
if (node.operator === "--") {
|
||||
operator = "+";
|
||||
} else { // "++"
|
||||
operator = "-";
|
||||
}
|
||||
nodes.push(t.binaryExpression(operator, node.argument, t.literal(1)));
|
||||
|
||||
return t.sequenceExpression(nodes);
|
||||
}
|
||||
};
|
||||
|
||||
var metadataVisitor = {
|
||||
ModuleDeclaration: {
|
||||
enter(node, parent, scope, formatter) {
|
||||
if (node.source) {
|
||||
node.source.value = formatter.file.resolveModuleSource(node.source.value);
|
||||
formatter.addScope(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -218,18 +156,27 @@ var metadataVisitor = {
|
||||
}
|
||||
},
|
||||
|
||||
Scope() {
|
||||
this.skip();
|
||||
Scope(node, parent, scope, formatter) {
|
||||
if (!formatter.isLoose()) {
|
||||
this.skip();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default class DefaultFormatter {
|
||||
constructor(file) {
|
||||
this.internalRemap = object();
|
||||
this.defaultIds = object();
|
||||
this.scope = file.scope;
|
||||
this.file = file;
|
||||
this.ids = object();
|
||||
// object containg all module sources with the scope that they're contained in
|
||||
this.sourceScopes = object();
|
||||
|
||||
// ids for use in module ids
|
||||
this.defaultIds = object();
|
||||
this.ids = object();
|
||||
|
||||
// contains reference aliases for live bindings
|
||||
this.remaps = new Remaps(file, this);
|
||||
|
||||
this.scope = file.scope;
|
||||
this.file = file;
|
||||
|
||||
this.hasNonDefaultExports = false;
|
||||
|
||||
@@ -243,6 +190,18 @@ export default class DefaultFormatter {
|
||||
this.getMetadata();
|
||||
}
|
||||
|
||||
addScope(path) {
|
||||
var source = path.node.source && path.node.source.value;
|
||||
if (!source) return;
|
||||
|
||||
var existingScope = this.sourceScopes[source];
|
||||
if (existingScope && existingScope !== path.scope) {
|
||||
throw path.errorWithNode(messages.get("modulesDuplicateDeclarations"));
|
||||
}
|
||||
|
||||
this.sourceScopes[source] = path.scope;
|
||||
}
|
||||
|
||||
isModuleType(node, type) {
|
||||
var modules = this.file.dynamicImportTypes[type];
|
||||
return modules && modules.indexOf(node) >= 0;
|
||||
@@ -264,12 +223,14 @@ export default class DefaultFormatter {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has) this.file.path.traverse(metadataVisitor, this);
|
||||
if (has || this.isLoose()) {
|
||||
this.file.path.traverse(metadataVisitor, this);
|
||||
}
|
||||
}
|
||||
|
||||
remapAssignments() {
|
||||
if (this.hasLocalExports || this.hasLocalImports) {
|
||||
this.file.path.traverse(remapVisitor, this);
|
||||
this.remaps.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ export default class AMDFormatter extends DefaultFormatter {
|
||||
this.getExternalReference(node);
|
||||
}
|
||||
|
||||
importSpecifier(specifier, node, nodes) {
|
||||
importSpecifier(specifier, node, nodes, scope) {
|
||||
var key = node.source.value;
|
||||
var ref = this.getExternalReference(node);
|
||||
|
||||
@@ -90,7 +90,7 @@ export default class AMDFormatter extends DefaultFormatter {
|
||||
// import * as bar from "foo";
|
||||
} else if (!includes(this.file.dynamicImported, node) && t.isSpecifierDefault(specifier) && !this.noInteropRequireImport) {
|
||||
// import foo from "foo";
|
||||
var uid = this.scope.generateUidIdentifier(specifier.local.name);
|
||||
var uid = scope.generateUidIdentifier(specifier.local.name);
|
||||
nodes.push(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(uid, t.callExpression(this.file.addHelper("interop-require-default"), [ref]))
|
||||
]));
|
||||
@@ -102,7 +102,7 @@ export default class AMDFormatter extends DefaultFormatter {
|
||||
ref = t.memberExpression(ref, imported);
|
||||
}
|
||||
|
||||
this.internalRemap[specifier.local.name] = ref;
|
||||
this.remaps.add(scope, specifier.local.name, ref);
|
||||
}
|
||||
|
||||
exportSpecifier(specifier, node, nodes) {
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class CommonJSFormatter extends DefaultFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
importSpecifier(specifier, node, nodes) {
|
||||
importSpecifier(specifier, node, nodes, scope) {
|
||||
var variableName = specifier.local;
|
||||
|
||||
var ref = this.getExternalReference(node, nodes);
|
||||
@@ -47,9 +47,9 @@ export default class CommonJSFormatter extends DefaultFormatter {
|
||||
if (this.isModuleType(node, "absolute")) {
|
||||
// absolute module reference
|
||||
} else if (this.isModuleType(node, "absoluteDefault")) {
|
||||
this.internalRemap[variableName.name] = ref;
|
||||
this.remaps.add(scope, variableName.name, ref);
|
||||
} else if (this.noInteropRequireImport) {
|
||||
this.internalRemap[variableName.name] = t.memberExpression(ref, t.identifier("default"));
|
||||
this.remaps.add(scope, variableName.name, t.memberExpression(ref, t.identifier("default")));
|
||||
} else {
|
||||
var uid = this.scope.generateUidIdentifierBasedOnNode(node, "import");
|
||||
|
||||
@@ -57,7 +57,7 @@ export default class CommonJSFormatter extends DefaultFormatter {
|
||||
t.variableDeclarator(uid, t.callExpression(this.file.addHelper("interop-require-default"), [ref]))
|
||||
]));
|
||||
|
||||
this.internalRemap[variableName.name] = t.memberExpression(uid, t.identifier("default"));
|
||||
this.remaps.add(scope, variableName.name, t.memberExpression(uid, t.identifier("default")));
|
||||
}
|
||||
} else {
|
||||
if (t.isImportNamespaceSpecifier(specifier)) {
|
||||
@@ -71,7 +71,7 @@ export default class CommonJSFormatter extends DefaultFormatter {
|
||||
]));
|
||||
} else {
|
||||
// import { foo } from "foo";
|
||||
this.internalRemap[variableName.name] = t.memberExpression(ref, specifier.imported);
|
||||
this.remaps.add(scope, variableName.name, t.memberExpression(ref, specifier.imported));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
119
src/babel/transformation/modules/lib/remaps.js
Normal file
119
src/babel/transformation/modules/lib/remaps.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import * as t from "../../../types";
|
||||
|
||||
var remapVisitor = {
|
||||
enter(node) {
|
||||
if (node._skipModulesRemap) {
|
||||
return this.skip();
|
||||
}
|
||||
},
|
||||
|
||||
ReferencedIdentifier(node, parent, scope, remaps) {
|
||||
var remap = remaps.get(scope, node.name);
|
||||
if (!remap || node === remap) return;
|
||||
|
||||
if (!scope.hasBinding(node.name) ||
|
||||
scope.bindingIdentifierEquals(node.name, remaps.formatter.localImports[node.name])) {
|
||||
if (this.key === "callee" && this.parentPath.isCallExpression()) {
|
||||
return t.sequenceExpression([t.literal(0), remap]);
|
||||
} else {
|
||||
return remap;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
AssignmentExpression: {
|
||||
exit(node, parent, scope, { formatter }) {
|
||||
if (!node._ignoreModulesRemap) {
|
||||
var exported = formatter.getExport(node.left, scope);
|
||||
if (exported) {
|
||||
return formatter.remapExportAssignment(node, exported);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
UpdateExpression(node, parent, scope, { formatter }) {
|
||||
var exported = formatter.getExport(node.argument, scope);
|
||||
if (!exported) return;
|
||||
|
||||
this.skip();
|
||||
|
||||
// expand to long file assignment expression
|
||||
var assign = t.assignmentExpression(node.operator[0] + "=", node.argument, t.literal(1));
|
||||
|
||||
// remap this assignment expression
|
||||
var remapped = formatter.remapExportAssignment(assign, exported);
|
||||
|
||||
// we don't need to change the result
|
||||
if (t.isExpressionStatement(parent) || node.prefix) {
|
||||
return remapped;
|
||||
}
|
||||
|
||||
var nodes = [];
|
||||
nodes.push(remapped);
|
||||
|
||||
var operator;
|
||||
if (node.operator === "--") {
|
||||
operator = "+";
|
||||
} else { // "++"
|
||||
operator = "-";
|
||||
}
|
||||
nodes.push(t.binaryExpression(operator, node.argument, t.literal(1)));
|
||||
|
||||
return t.sequenceExpression(nodes);
|
||||
}
|
||||
};
|
||||
export default class Remaps {
|
||||
constructor(file, formatter) {
|
||||
this.formatter = formatter;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
run() {
|
||||
this.file.path.traverse(remapVisitor, this);
|
||||
}
|
||||
|
||||
_getKey(name) {
|
||||
return `${name}:moduleRemap`;
|
||||
}
|
||||
|
||||
get(scope, name) {
|
||||
return scope.getData(this._getKey(name));
|
||||
}
|
||||
|
||||
add(scope, name, val) {
|
||||
if (this.all) {
|
||||
this.all.push({
|
||||
name,
|
||||
scope,
|
||||
node: val
|
||||
});
|
||||
}
|
||||
|
||||
return scope.setData(this._getKey(name), val);
|
||||
}
|
||||
|
||||
remove(scope, name) {
|
||||
return scope.removeData(this._getKey(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* These methods are used by the system module formatter who needs access to all the remaps
|
||||
* so it can process them into it's specific setter method. We don't do this by default since
|
||||
* no other module formatters need access to this.
|
||||
*/
|
||||
|
||||
getAll() {
|
||||
return this.all;
|
||||
}
|
||||
|
||||
clearAll() {
|
||||
if (this.all) {
|
||||
for (var remap of (this.all: Array)) {
|
||||
remap.scope.removeData(this._getKey(remap.name));
|
||||
}
|
||||
}
|
||||
|
||||
this.all = [];
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,8 @@ export default class SystemFormatter extends AMDFormatter {
|
||||
this.exportIdentifier = file.scope.generateUidIdentifier("export");
|
||||
this.noInteropRequireExport = true;
|
||||
this.noInteropRequireImport = true;
|
||||
|
||||
this.remaps.clearAll();
|
||||
}
|
||||
|
||||
_addImportSource(node, exportNode) {
|
||||
@@ -137,13 +139,13 @@ export default class SystemFormatter extends AMDFormatter {
|
||||
importSpecifier(specifier, node, nodes) {
|
||||
AMDFormatter.prototype.importSpecifier.apply(this, arguments);
|
||||
|
||||
for (var name in this.internalRemap) {
|
||||
for (var remap of (this.remaps.getAll(): Array)) {
|
||||
nodes.push(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(t.identifier(name), this.internalRemap[name])
|
||||
t.variableDeclarator(t.identifier(remap.name), remap.node)
|
||||
]));
|
||||
}
|
||||
|
||||
this.internalRemap = object();
|
||||
this.remaps.clearAll();
|
||||
|
||||
this._addImportSource(last(nodes), node);
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ export var visitor = {
|
||||
|
||||
if (node.specifiers.length) {
|
||||
for (var specifier of (node.specifiers: Array)) {
|
||||
file.moduleFormatter.importSpecifier(specifier, node, nodes, parent);
|
||||
file.moduleFormatter.importSpecifier(specifier, node, nodes, scope);
|
||||
}
|
||||
} else {
|
||||
file.moduleFormatter.importDeclaration(node, nodes, parent);
|
||||
file.moduleFormatter.importDeclaration(node, nodes, scope);
|
||||
}
|
||||
|
||||
if (nodes.length === 1) {
|
||||
@@ -37,14 +37,14 @@ export var visitor = {
|
||||
|
||||
ExportAllDeclaration(node, parent, scope, file) {
|
||||
var nodes = [];
|
||||
file.moduleFormatter.exportAllDeclaration(node, nodes, parent);
|
||||
file.moduleFormatter.exportAllDeclaration(node, nodes, scope);
|
||||
keepBlockHoist(node, nodes);
|
||||
return nodes;
|
||||
},
|
||||
|
||||
ExportDefaultDeclaration(node, parent, scope, file) {
|
||||
var nodes = [];
|
||||
file.moduleFormatter.exportDeclaration(node, nodes, parent);
|
||||
file.moduleFormatter.exportDeclaration(node, nodes, scope);
|
||||
keepBlockHoist(node, nodes);
|
||||
return nodes;
|
||||
},
|
||||
@@ -63,10 +63,10 @@ export var visitor = {
|
||||
declar.init = declar.init || t.identifier("undefined");
|
||||
}
|
||||
|
||||
file.moduleFormatter.exportDeclaration(node, nodes, parent);
|
||||
file.moduleFormatter.exportDeclaration(node, nodes, scope);
|
||||
} else if (node.specifiers) {
|
||||
for (let i = 0; i < node.specifiers.length; i++) {
|
||||
file.moduleFormatter.exportSpecifier(node.specifiers[i], node, nodes, parent);
|
||||
file.moduleFormatter.exportSpecifier(node.specifiers[i], node, nodes, scope);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -598,6 +598,38 @@ export default class Scope {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set some arbitrary data on the current scope.
|
||||
*/
|
||||
|
||||
setData(key, val) {
|
||||
return this.data[key] = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively walk up scope tree looking for the data `key`.
|
||||
*/
|
||||
|
||||
getData(key) {
|
||||
var scope = this;
|
||||
do {
|
||||
var data = scope.data[key];
|
||||
if (data != null) return data;
|
||||
} while(scope = scope.parent);
|
||||
}
|
||||
/**
|
||||
* Recursively walk up scope tree looking for the data `key` and if it exists,
|
||||
* remove it.
|
||||
*/
|
||||
|
||||
removeData(key) {
|
||||
var scope = this;
|
||||
do {
|
||||
var data = scope.data[key];
|
||||
if (data != null) scope.data[key] = null;
|
||||
} while(scope = scope.parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description
|
||||
*/
|
||||
@@ -623,6 +655,7 @@ export default class Scope {
|
||||
bindings: object(),
|
||||
globals: object(),
|
||||
uids: object(),
|
||||
data: object(),
|
||||
};
|
||||
|
||||
extend(this, info);
|
||||
|
||||
Reference in New Issue
Block a user