support module live bindings in arbitary positions not in Program statement position - fixes #1760

This commit is contained in:
Sebastian McKenzie
2015-06-16 00:41:53 +01:00
parent 050bcec617
commit fb485567b9
11 changed files with 229 additions and 91 deletions

View File

@@ -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();
}
}

View File

@@ -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) {

View File

@@ -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));
}
}
}

View 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 = [];
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);