lively.source-transform
Version:
EcmaScript 6 classes for live development
943 lines (824 loc) • 37 kB
JavaScript
import { obj, chain, arr, fun, Path } from "lively.lang";
import { classToFunctionTransform } from "lively.classes";
import {
parse,
stringify,
query,
transform,
nodes,
BaseVisitor as Visitor,
ReplaceManyVisitor, ReplaceVisitor
} from "lively.ast";
var {
member,
prop,
varDecl,
assign,
id,
literal,
exprStmt,
conditional,
binaryExpr,
funcCall
} = nodes;
var { topLevelDeclsAndRefs, helpers: queryHelpers } = query;
var { transformSingleExpression, wrapInStartEndCall } = transform;
export function rewriteToCaptureTopLevelVariables(parsed, assignToObj, options) {
/* replaces var and function declarations with assignment statements.
* Example:
stringify(
rewriteToCaptureTopLevelVariables2(
parse("var x = 3, y = 2, z = 4"),
{name: "A", type: "Identifier"}, ['z']));
// => "A.x = 3; A.y = 2; z = 4"
*/
if (!assignToObj) assignToObj = {type: "Identifier", name: "__rec"};
options = {
ignoreUndeclaredExcept: null,
includeRefs: null,
excludeRefs: (options && options.exclude) || [],
includeDecls: null,
excludeDecls: (options && options.exclude) || [],
recordDefRanges: false,
es6ExportFuncId: null,
es6ImportFuncId: null,
captureObj: assignToObj,
moduleExportFunc: {name: options && options.es6ExportFuncId || "_moduleExport", type: "Identifier"},
moduleImportFunc: {name: options && options.es6ImportFuncId || "_moduleImport", type: "Identifier"},
declarationWrapper: undefined,
classToFunction: options && options.hasOwnProperty("classToFunction") ?
options.classToFunction : {
classHolder: assignToObj,
functionNode: {type: "Identifier", name: "_createOrExtendClass"},
declarationWrapper: options && options.declarationWrapper,
evalId: options && options.evalId,
sourceAccessorName: options && options.sourceAccessorName
},
...options
}
var rewritten = parsed;
// "ignoreUndeclaredExcept" is null if we want to capture all globals in the toplevel scope
// if it is a list of names we will capture all refs with those names
if (options.ignoreUndeclaredExcept) {
var topLevel = topLevelDeclsAndRefs(parsed);
options.excludeRefs = arr.withoutAll(topLevel.undeclaredNames, options.ignoreUndeclaredExcept).concat(options.excludeRefs);
options.excludeDecls = arr.withoutAll(topLevel.undeclaredNames, options.ignoreUndeclaredExcept).concat(options.excludeDecls);
}
options.excludeRefs = options.excludeRefs.concat(options.captureObj.name);
options.excludeDecls = options.excludeDecls.concat(options.captureObj.name);
// 1. def ranges so that we know at which source code positions the
// definitions are
var defRanges = options.recordDefRanges ? computeDefRanges(rewritten, options) : null;
// 2. find those var declarations that should not be rewritten. we
// currently ignore var declarations in for loops and the error parameter
// declaration in catch clauses. Also es6 import / export declaration need
// a special treatment
// DO NOT rewrite exports like "export { foo as bar }" => "export { _rec.foo as bar }"
// as this is not valid syntax. Instead we add a var declaration using the
// recorder as init for those exports later
options.excludeRefs = options.excludeRefs.concat(additionalIgnoredRefs(parsed, options));
options.excludeDecls = options.excludeDecls.concat(additionalIgnoredDecls(parsed, options));
rewritten = fixDefaultAsyncFunctionExportForRegeneratorBug(rewritten, options);
// 3. if the es6ExportFuncId options is defined we rewrite the es6 form into an
// obj assignment, converting es6 code to es5 using the extra
// options.moduleExportFunc and options.moduleImportFunc as capture / sources
if (options.es6ExportFuncId) {
options.excludeRefs.push(options.es6ExportFuncId);
options.excludeRefs.push(options.es6ImportFuncId);
rewritten = es6ModuleTransforms(rewritten, options);
}
// 4. make all references declared in the toplevel scope into property
// reads of captureObj
// Example "var foo = 3; 99 + foo;" -> "var foo = 3; 99 + Global.foo;"
rewritten = replaceRefs(rewritten, options);
// 5.a turn var declarations into assignments to captureObj
// Example: "var foo = 3; 99 + foo;" -> "Global.foo = 3; 99 + foo;"
// if declarationWrapper is requested:
// "var foo = 3;" -> "Global.foo = _define(3, 'foo', _rec, 'var');"
rewritten = replaceVarDecls(rewritten, options);
// 5.b record class declarations
// Example: "class Foo {}" -> "class Foo {}; Global.Foo = Foo;"
// if declarationWrapper is requested:
// "class Foo {}" -> "Global.Foo = _define(class Foo {});"
rewritten = replaceClassDecls(rewritten, options);
rewritten = splitExportDeclarations(rewritten, options);
// 6. es6 export declaration are left untouched but a capturing assignment
// is added after the export so that we get the value:
// "export var x = 23;" => "export var x = 23; Global.x = x;"
rewritten = insertCapturesForExportDeclarations(rewritten, options);
// 7. es6 import declaration are left untouched but a capturing assignment
// is added after the import so that we get the value:
// "import x from './some-es6-module.js';" =>
// "import x from './some-es6-module.js';\n_rec.x = x;"
rewritten = insertCapturesForImportDeclarations(rewritten, options);
// 8. Since variable declarations like "var x = 23" were transformed to sth
// like "_rex.x = 23" exports can't simply reference vars anymore and
// "export { _rec.x }" is invalid syntax. So in front of those exports we add
// var decls manually
rewritten = insertDeclarationsForExports(rewritten, options);
// 9. assignments for function declarations in the top level scope are
// put in front of everything else to mirror the func hoisting:
// "return bar(); function bar() { return 23 }" ->
// "Global.bar = bar; return bar(); function bar() { return 23 }"
// if declarationWrapper is requested:
// "Global.bar = _define(bar, 'bar', _rec, 'function'); function bar() {}"
rewritten = putFunctionDeclsInFront(rewritten, options);
return rewritten
}
export function rewriteToRegisterModuleToCaptureSetters(parsed, assignToObj, options) {
// for rewriting the setters part in code like
// ```js
// System.register(["a.js"], function (_export, _context) {
// var a, _rec;
// return {
// setters: [function(foo_a_js) { a = foo_a_js.x }],
// execute: function () { _rec.x = 23 + _rec.a; }
// };
// });
// ```
// This allows us to capture (and potentially re-export) imports and their
// changes without actively running the module again.
options = {
captureObj: assignToObj || {type: "Identifier", name: "__rec"},
exclude: [],
declarationWrapper: undefined,
...options
};
var registerCall = Path("body.0.expression").get(parsed);
if (registerCall.callee.object.name !== "System")
throw new Error(`rewriteToRegisterModuleToCaptureSetters: input doesn't seem to be a System.register call: ${stringify(parsed).slice(0,300)}...`)
if (registerCall.callee.property.name !== "register")
throw new Error(`rewriteToRegisterModuleToCaptureSetters: input doesn't seem to be a System.register call: ${stringify(parsed).slice(0,300)}...`)
var registerBody = Path("arguments.1.body.body").get(registerCall),
registerReturn = arr.last(registerBody);
if (registerReturn.type !== "ReturnStatement")
throw new Error(`rewriteToRegisterModuleToCaptureSetters: input doesn't seem to be a System.register call, at return statement: ${stringify(parsed).slice(0,300)}...`)
var setters = registerReturn.argument.properties.find(prop => prop.key.name === "setters");
if (!setters)
throw new Error(`rewriteToRegisterModuleToCaptureSetters: input doesn't seem to be a System.register call, at finding setters: ${stringify(parsed).slice(0,300)}...`)
var execute = registerReturn.argument.properties.find(prop => prop.key.name === "execute");
if (!execute)
throw new Error(`rewriteToRegisterModuleToCaptureSetters: input doesn't seem to be a System.register call, at finding execute: ${stringify(parsed).slice(0,300)}...`)
// in each setter function: intercept the assignments to local vars and inject capture object
setters.value.elements.forEach(funcExpr =>
funcExpr.body.body = funcExpr.body.body.map(stmt => {
if (stmt.type !== "ExpressionStatement"
|| stmt.expression.type !== "AssignmentExpression"
|| stmt.expression.left.type !== "Identifier"
|| arr.include(options.exclude, stmt.expression.left.name)) return stmt;
var id = stmt.expression.left,
rhs = options.declarationWrapper ?
declarationWrapperCall(
options.declarationWrapper,
null,
literal(id.name),
literal("var"),
stmt.expression,
options.captureObj,
options) :
stmt.expression;
return exprStmt(assign(member(options.captureObj, id), rhs))
}));
var captureInitialize = execute.value.body.body.find(stmt =>
stmt.type === "ExpressionStatement"
&& stmt.expression.type == "AssignmentExpression"
&& stmt.expression.left.name === options.captureObj.name);
if (!captureInitialize)
captureInitialize = execute.value.body.body.find(stmt =>
stmt.type === "VariableDeclaration"
&& stmt.declarations[0].id
&& stmt.declarations[0].id.name === options.captureObj.name);
if (captureInitialize) {
arr.remove(execute.value.body.body, captureInitialize);
arr.pushAt(registerBody, captureInitialize, registerBody.length-1);
}
if (options.sourceAccessorName) {
var origSourceInitialize = execute.value.body.body.find(stmt =>
stmt.type === "ExpressionStatement"
&& stmt.expression.type == "AssignmentExpression"
&& stmt.expression.left.name === options.sourceAccessorName);
if (!origSourceInitialize)
origSourceInitialize = execute.value.body.body.find(stmt =>
stmt.type === "VariableDeclaration"
&& stmt.declarations[0].id
&& stmt.declarations[0].id.name === options.sourceAccessorName);
if (origSourceInitialize) {
arr.remove(execute.value.body.body, origSourceInitialize);
arr.pushAt(registerBody, origSourceInitialize, registerBody.length-1);
}
}
return parsed;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// replacement helpers
function replaceRefs(parsed, options) {
var topLevel = topLevelDeclsAndRefs(parsed),
refsToReplace = topLevel.refs.filter(ref => shouldRefBeCaptured(ref, topLevel, options)),
locallyIgnored = [];
const replaced = ReplaceVisitor.run(parsed, (node, path) => {
// cs 2016/06/27, 1a4661
// ensure keys of shorthand properties are not renamed while capturing
if (node.type === "Property"
&& refsToReplace.includes(node.key)
&& node.shorthand)
return prop(id(node.key.name), node.value);
// don't replace var refs in expressions such as "export { x }" or "export var x;"
// We make sure that those var references are defined in insertDeclarationsForExports()
if (node.type === "ExportNamedDeclaration") {
var { declaration, specifiers } = node;
if (declaration) {
if (declaration.id) locallyIgnored.push(declaration.id)
else if (declaration.declarations)
locallyIgnored.push(...declaration.declarations.map(({id}) => id))
}
specifiers && specifiers.forEach(({local}) => locallyIgnored.push(local));
return node;
}
// declaration wrapper function for assignments
// "a = 3" => "a = _define('a', 'assignment', 3, _rec)"
if (node.type === "AssignmentExpression"
&& refsToReplace.includes(node.left)
&& options.declarationWrapper)
return {
...node,
right: declarationWrapperCall(
options.declarationWrapper,
null,
literal(node.left.name),
literal("assignment"),
node.right,
options.captureObj,
options)};
return node
});
return ReplaceVisitor.run(replaced, (node, path, parent) =>
refsToReplace.includes(node) && !locallyIgnored.includes(node) ?
member(options.captureObj, node) : node);
}
function replaceVarDecls(parsed, options) {
// rewrites var declarations so that they can be captured by
// `options.captureObj`.
// For normal vars we will do a transform like
// "var x = 23;" => "_rec.x = 23";
// For patterns (destructuring assignments) we will create assignments for
// all properties that are being destructured, creating helper vars as needed
// "var {x: [y]} = foo" => "var _1 = foo; var _1$x = _1.x; __rec.y = _1$x[0];"
var topLevel = topLevelDeclsAndRefs(parsed);
return ReplaceManyVisitor.run(parsed, node => {
if (!topLevel.varDecls.includes(node)
|| node.declarations.every(decl => !shouldDeclBeCaptured(decl, options))
) return node;
var replaced = [];
for (var i = 0; i < node.declarations.length; i++) {
var decl = node.declarations[i];
if (!shouldDeclBeCaptured(decl, options)) {
replaced.push({type: "VariableDeclaration", kind: node.kind || "var", declarations: [decl]});
continue;
}
var init = decl.init || {
operator: "||",
type: "LogicalExpression",
left: {computed: false, object: options.captureObj, property: decl.id, type: "MemberExpression"},
right: {name: "undefined", type: "Identifier"}
};
var initWrapped = options.declarationWrapper && decl.id.name ?
declarationWrapperCall(
options.declarationWrapper,
decl,
literal(decl.id.name),
literal(node.kind),
init, options.captureObj,
options) : init;
// Here we create the object pattern / destructuring replacements
if (decl.id.type.includes("Pattern")) {
var declRootName = generateUniqueName(topLevel.declaredNames, "destructured_1"),
declRoot = {type: "Identifier", name: declRootName},
state = {parent: declRoot, declaredNames: topLevel.declaredNames},
extractions = transformPattern(decl.id, state).map(decl =>
decl[annotationSym] && decl[annotationSym].capture ?
assignExpr(
options.captureObj,
decl.declarations[0].id,
options.declarationWrapper ?
declarationWrapperCall(
options.declarationWrapper,
null,
literal(decl.declarations[0].id.name),
literal(node.kind),
decl.declarations[0].init,
options.captureObj,
options) : decl.declarations[0].init,
false) : decl);
topLevel.declaredNames.push(declRootName);
replaced.push(...[varDecl(declRoot, initWrapped, node.kind)].concat(extractions));
continue;
}
// This is rewriting normal vars
replaced.push(assignExpr(options.captureObj, decl.id, initWrapped, false));
}
return replaced;
});
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// naming
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function generateUniqueName(declaredNames, hint) {
var unique = hint, n = 1;
while (declaredNames.indexOf(unique) > -1) {
if (n > 1000) throw new Error("Endless loop searching for unique variable " + unique);
unique = unique.replace(/_[0-9]+$|$/, "_" + (++n));
}
return unique;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// exclude / include helpers
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function additionalIgnoredDecls(parsed, options) {
var topLevel = topLevelDeclsAndRefs(parsed), ignoreDecls = [];
for (var i = 0; i < topLevel.scope.varDecls.length; i++) {
var decl = topLevel.scope.varDecls[i],
path = Path(topLevel.scope.varDeclPaths[i]),
parent = path.slice(0,-1).get(parsed);
if (parent.type === "ForStatement"
|| parent.type === "ForInStatement"
|| parent.type === "ForOfStatement"
|| parent.type === "ExportNamedDeclaration"
) ignoreDecls.push(...decl.declarations)
}
return topLevel.scope.catches.map(ea => ea.name)
.concat(ignoreDecls.map(ea => ea.id.name));
}
function additionalIgnoredRefs(parsed, options) {
// FIXME rk 2016-05-11: in shouldRefBeCaptured we now also test for import
// decls, this should somehow be consolidated with this function and with the
// fact that naming based ignores aren't good enough...
var topLevel = topLevelDeclsAndRefs(parsed);
var ignoreDecls = [];
for (var i = 0; i < topLevel.scope.varDecls.length; i++) {
var decl = topLevel.scope.varDecls[i],
path = Path(topLevel.scope.varDeclPaths[i]),
parent = path.slice(0,-1).get(parsed);
if (parent.type === "ForStatement"
|| parent.type === "ForInStatement"
|| parent.type === "ForOfStatement"
) ignoreDecls.push(...decl.declarations)
}
return topLevel.scope.catches.map(ea => ea.name)
.concat(queryHelpers.declIds(ignoreDecls.map(ea => ea.id)).map(ea => ea.name));
}
function shouldDeclBeCaptured(decl, options) {
return options.excludeDecls.indexOf(decl.id.name) === -1
&& (!options.includeDecls || options.includeDecls.indexOf(decl.id.name) > -1);
}
function shouldRefBeCaptured(ref, toplevel, options) {
if (toplevel.scope.importSpecifiers.includes(ref)) return false;
for (var i = 0; i < toplevel.scope.exportDecls.length; i++) {
var ea = toplevel.scope.exportDecls[i];
if (ea.declarations && ea.declarations.includes(ref)) return false;
if (ea.declaration === ref) return false;
}
if (options.excludeRefs.includes(ref.name)) return false;
if (options.includeRefs && !options.includeRefs.includes(ref.name)) return false;
return true;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// capturing specific code
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function replaceClassDecls(parsed, options) {
if (options.classToFunction)
return classToFunctionTransform(parsed, options.classToFunction);
var topLevel = topLevelDeclsAndRefs(parsed);
if (!topLevel.classDecls.length) return parsed;
for (var i = parsed.body.length - 1; i >= 0; i--) {
var stmt = parsed.body[i];
if (topLevel.classDecls.includes(stmt))
parsed.body.splice(i+1, 0, assignExpr(options.captureObj, stmt.id, stmt.id, false));
}
return parsed;
}
function splitExportDeclarations(parsed, options) {
var stmts = parsed.body, newNodes = parsed.body = [];
for (var i = 0; i < stmts.length; i++) {
var stmt = stmts[i];
if (stmt.type !== "ExportNamedDeclaration"
|| !stmt.declaration || stmt.declaration.type !== "VariableDeclaration"
|| stmt.declaration.declarations.length <= 1
) { newNodes.push(stmt); continue; }
var decls = stmt.declaration.declarations;
for (var j = 0; j < decls.length; j++) {
newNodes.push({
type: "ExportNamedDeclaration",
specifiers: [],
declaration: varDecl(decls[j].id, decls[j].init, stmt.declaration.kind)
});
}
}
return parsed;
}
function insertCapturesForExportDeclarations(parsed, options) {
var body = [];
for (var i = 0; i < parsed.body.length; i++) {
var stmt = parsed.body[i];
body.push(stmt);
// ExportNamedDeclaration can have specifieres = refs, those should already
// be captured. Only focus on export declarations and only those
// declarations that are no refs, i.e.
// ignore: "export default x;"
// capture: "export default function foo () {};", "export var x = 23, y = 3;"
if ((stmt.type !== "ExportNamedDeclaration" && stmt.type !== "ExportDefaultDeclaration")
|| !stmt.declaration) {
/*...*/
} else if (stmt.declaration.declarations) {
body.push(...stmt.declaration.declarations.map(decl => {
var assignVal = decl.id;
if (options.declarationWrapper) {
var alreadyWrapped = decl.init.callee
&& decl.init.callee.name === options.declarationWrapper.name
if (!alreadyWrapped)
assignVal = declarationWrapperCall(
options.declarationWrapper,
decl,
literal(decl.id.name),
literal("assignment"),
decl.id,
options.captureObj,
options);
}
return assignExpr(options.captureObj, decl.id, assignVal, false);
}));
} else if (stmt.declaration.type === "FunctionDeclaration") {
/*handled by function rewriter as last step*/
} else if (stmt.declaration.type === "ClassDeclaration") {
body.push(assignExpr(options.captureObj, stmt.declaration.id, stmt.declaration.id, false));
}
}
parsed.body = body;
return parsed;
}
function insertCapturesForImportDeclarations(parsed, options) {
parsed.body = parsed.body.reduce((stmts, stmt) =>
stmts.concat(stmt.type !== "ImportDeclaration" || !stmt.specifiers.length ? [stmt] :
[stmt].concat(stmt.specifiers.map(specifier =>
assignExpr(options.captureObj, specifier.local, specifier.local, false)))), []);
return parsed;
}
function insertDeclarationsForExports(parsed, options) {
var topLevel = topLevelDeclsAndRefs(parsed), body = [];
for (var i = 0; i < parsed.body.length; i++) {
var stmt = parsed.body[i];
if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration && stmt.declaration.type.indexOf("Declaration") === -1) {
body = body.concat([
varDeclOrAssignment(parsed, {
type: "VariableDeclarator",
id: stmt.declaration,
init: member(options.captureObj, stmt.declaration)
}),
stmt
]);
} else if (stmt.type !== "ExportNamedDeclaration" || !stmt.specifiers.length || stmt.source) {
body.push(stmt)
} else {
body = body.concat(stmt.specifiers.map(specifier =>
arr.include(topLevel.declaredNames, specifier.local.name) ?
null :
varDeclOrAssignment(parsed, {
type: "VariableDeclarator",
id: specifier.local,
init: member(options.captureObj, specifier.local)
})).filter(Boolean)).concat(stmt);
}
}
parsed.body = body;
return parsed;
}
function fixDefaultAsyncFunctionExportForRegeneratorBug(parsed, options) {
// rk 2016-06-02: see https://github.com/LivelyKernel/lively.modules/issues/9
// FIXME this needs to be removed as soon as the cause for the issue is fixed
var body = [];
for (var i = 0; i < parsed.body.length; i++) {
var stmt = parsed.body[i];
if (stmt.type === "ExportDefaultDeclaration"
&& stmt.declaration.type === "FunctionDeclaration"
&& stmt.declaration.id
&& stmt.declaration.async) {
body.push(stmt.declaration);
stmt.declaration = { type: "Identifier", name: stmt.declaration.id.name };
}
body.push(stmt);
}
parsed.body = body;
return parsed;
}
function es6ModuleTransforms(parsed, options) {
parsed.body = parsed.body.reduce((stmts, stmt) => {
var nodes;
if (stmt.type === "ExportNamedDeclaration") {
if (stmt.source) {
var key = moduleId = stmt.source;
nodes = stmt.specifiers.map(specifier => ({
type: "ExpressionStatement",
expression: exportFromImport(
{type: "Literal", value: specifier.exported.name},
{type: "Literal", value: specifier.local.name},
moduleId, options.moduleExportFunc, options.moduleImportFunc)}));
} else if (stmt.declaration) {
var decls = stmt.declaration.declarations
if (!decls) { // func decl or class
nodes = [stmt.declaration].concat(
exportCallStmt(options.moduleExportFunc, stmt.declaration.id.name, stmt.declaration.id));
} else {
nodes = decls.map(decl => {
options.excludeDecls.push(decl.id);
return varDecl(decl.id,
assignExpr(
options.captureObj, decl.id,
options.declarationWrapper ?
declarationWrapperCall(
options.declarationWrapper,
null,
literal(decl.id.name),
literal(stmt.declaration.kind),
decl, options.captureObj,
options) :
decl.init,
false),
stmt.declaration.kind);
})
.concat(decls.map(decl => exportCallStmt(options.moduleExportFunc, decl.id.name, decl.id)))
}
} else {
nodes = stmt.specifiers.map(specifier =>
exportCallStmt(options.moduleExportFunc, specifier.exported.name,
shouldDeclBeCaptured({id: specifier.local}, options) ?
member(options.captureObj, specifier.local) :
specifier.local))
}
} else if (stmt.type === "ExportDefaultDeclaration") {
if (stmt.declaration && stmt.declaration.id) {
nodes = [stmt.declaration].concat(exportCallStmt(options.moduleExportFunc, "default", stmt.declaration.id));
} else {
nodes = [exportCallStmt(options.moduleExportFunc, "default", stmt.declaration)];
}
} else if (stmt.type === "ExportAllDeclaration") {
var key = {name: options.es6ExportFuncId + "__iterator__", type: "Identifier"}, moduleId = stmt.source;
nodes = [
{
type: "ForInStatement",
body: {type: "ExpressionStatement", expression: exportFromImport(key, key, moduleId, options.moduleExportFunc, options.moduleImportFunc)},
left: {type: "VariableDeclaration", kind: "var", declarations: [{type: "VariableDeclarator", id: key, init: null}]},
right: importCall(null, moduleId, options.moduleImportFunc),
}
];
options.excludeRefs.push(key.name);
options.excludeDecls.push(key.name);
} else if (stmt.type === "ImportDeclaration") {
nodes = stmt.specifiers.length ?
stmt.specifiers.map(specifier => {
var local = specifier.local,
imported = (specifier.type === "ImportSpecifier" && specifier.imported.name)
|| (specifier.type === "ImportDefaultSpecifier" && "default")
|| null;
return varDeclAndImportCall(parsed, local, imported || null, stmt.source, options.moduleImportFunc);
}) : importCallStmt(null, stmt.source, options.moduleImportFunc);
} else nodes = [stmt];
return stmts.concat(nodes);
}, []);
return parsed;
}
function putFunctionDeclsInFront(parsed, options) {
var scope = topLevelDeclsAndRefs(parsed).scope,
funcDecls = scope.funcDecls;
if (!funcDecls.length) return parsed;
var putInFront = [];
for (var i = funcDecls.length; i--; ) {
var decl = funcDecls[i];
if (!shouldDeclBeCaptured(decl, options)) continue;
var parentPath = scope.funcDeclPaths[i].slice(0,-1),
// ge the parent so we can replace the original function:
parent = Path(parentPath).get(scope.node),
funcId = {type: "Identifier", name: decl.id.name},
// what we capture:
init = options.declarationWrapper ?
declarationWrapperCall(
options.declarationWrapper,
decl,
literal(funcId.name),
literal("function"),
funcId, options.captureObj,
options) :
funcId,
declFront = {...decl};
if (Array.isArray(parent)) {
// If the parent is a body array we remove the original func decl from it
// and replace it with a reference to the function
parent.splice(parent.indexOf(decl), 1, exprStmt(decl.id));
} else if (parent.type === "ExportNamedDeclaration") {
// If the function is exported we change the export declaration into a reference
var parentIndexInBody = scope.node.body.indexOf(parent);
if (parentIndexInBody > -1) {
scope.node.body.splice(parentIndexInBody, 1, {type: "ExportNamedDeclaration", specifiers: [{type: "ExportSpecifier", exported: decl.id, local: decl.id}]});
}
} else if (parent.type === "ExportDefaultDeclaration") {
parent.declaration = decl.id;
} else {
// ??? just leave it alone...
// decl.type = "EmptyStatement";
}
// hoist the function to the front, also it's capture
putInFront.unshift(assignExpr(options.captureObj, funcId, init, false));
putInFront.unshift(declFront);
}
parsed.body = putInFront.concat(parsed.body);
return parsed;
}
function computeDefRanges(parsed, options) {
var topLevel = topLevelDeclsAndRefs(parsed);
return chain(topLevel.scope.varDecls)
.pluck("declarations").flatten().value()
.concat(topLevel.scope.funcDecls)
.reduce((defs, decl) => {
if (!defs[decl.id.name]) defs[decl.id.name] = []
defs[decl.id.name].push({type: decl.type, start: decl.start, end: decl.end});
return defs;
}, {});
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// capturing oobject patters / destructuring
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
var annotationSym = Symbol("lively.ast-destructuring-transform");
function transformPattern(pattern, transformState) {
// For transforming destructuring expressions into plain vars and member access.
// Takes a var or argument pattern node (of type ArrayPattern or
// ObjectPattern) and transforms it into a set of var declarations that will
// "pull out" the nested properties
// Example:
// var parsed = parse("var [{b: {c: [a]}}] = foo;");
// var state = {parent: {type: "Identifier", name: "arg"}, declaredNames: ["foo"]}
// transformPattern(parsed.body[0].declarations[0].id, state).map(stringify).join("\n");
// // => "var arg$0 = arg[0];\n"
// // + "var arg$0$b = arg$0.b;\n"
// // + "var arg$0$b$c = arg$0$b.c;\n"
// // + "var a = arg$0$b$c[0];"
return pattern.type === "ArrayPattern" ?
transformArrayPattern(pattern, transformState) :
pattern.type === "ObjectPattern" ?
transformObjectPattern(pattern, transformState) : [];
}
function transformArrayPattern(pattern, transformState) {
var declaredNames = transformState.declaredNames,
p = annotationSym,
transformed = [];
for (var i = 0; i < pattern.elements.length; i++) {
var el = pattern.elements[i];
// like [a]
if (el.type === "Identifier") {
var decl = varDecl(el, member(transformState.parent, id(i), true));
decl[p] = {capture: true};
transformed.push(decl);
// like [...foo]
} else if (el.type === "RestElement") {
var decl = varDecl(el.argument, {
type: "CallExpression",
arguments: [{type: "Literal", value: i}],
callee: member(transformState.parent, id("slice"), false)});
decl[p] = {capture: true};
transformed.push(decl)
} else if (el.type == "AssignmentPattern") {
// like [x = 23]
var decl = varDecl(
el.left/*id*/,
conditional(
binaryExpr(member(transformState.parent, id(i), true), "===", id("undefined")),
el.right,
member(transformState.parent, id(i), true)));
decl[p] = {capture: true};
transformed.push(decl);
// like [{x}]
} else {
var helperVarId = id(generateUniqueName(declaredNames, transformState.parent.name + "$" + i)),
helperVar = varDecl(helperVarId, member(transformState.parent, i));
// helperVar[p] = {capture: true};
declaredNames.push(helperVarId.name);
transformed.push(helperVar);
transformed.push(...transformPattern(el, {parent: helperVarId, declaredNames}));
}
}
return transformed;
}
function transformObjectPattern(pattern, transformState) {
var declaredNames = transformState.declaredNames,
p = annotationSym,
transformed = [];
for (var i = 0; i < pattern.properties.length; i++) {
var prop = pattern.properties[i];
if (prop.value.type == "Identifier") {
// like {x: y}
var decl = varDecl(prop.value, member(transformState.parent, prop.key));
decl[p] = {capture: true};
transformed.push(decl);
} else if (prop.value.type == "AssignmentPattern") {
// like {x = 23}
var decl = varDecl(
prop.value.left/*id*/,
conditional(
binaryExpr(member(transformState.parent, prop.key), "===", id("undefined")),
prop.value.right,
member(transformState.parent, prop.key)));
decl[p] = {capture: true};
transformed.push(decl);
} else {
// like {x: {z}} or {x: [a]}
var helperVarId = id(generateUniqueName(declaredNames, transformState.parent.name + "$" + prop.key.name)),
helperVar = varDecl(helperVarId, member(transformState.parent, prop.key));
helperVar[p] = {capture: false};
declaredNames.push(helperVarId.name);
transformed.push(...[helperVar].concat(transformPattern(prop.value, {parent: helperVarId, declaredNames: declaredNames})));
}
}
return transformed;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// code generation helpers
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
function varDeclOrAssignment(parsed, declarator, kind) {
var topLevel = topLevelDeclsAndRefs(parsed),
name = declarator.id.name
return topLevel.declaredNames.indexOf(name) > -1 ?
// only create a new declaration if necessary
exprStmt(assign(declarator.id, declarator.init)) :
{
declarations: [declarator],
kind: kind || "var", type: "VariableDeclaration"
}
}
function assignExpr(assignee, propId, value, computed) {
return exprStmt(
assign(
member(assignee, propId, computed),
value || id("undefined")));
}
function exportFromImport(keyLeft, keyRight, moduleId, moduleExportFunc, moduleImportFunc) {
return exportCall(moduleExportFunc, keyLeft, importCall(keyRight, moduleId, moduleImportFunc));
}
function varDeclAndImportCall(parsed, localId, imported, moduleSource, moduleImportFunc) {
// return varDeclOrAssignment(parsed, {
// type: "VariableDeclarator",
// id: localId,
// init: importCall(imported, moduleSource, moduleImportFunc)
// });
return varDecl(localId, importCall(imported, moduleSource, moduleImportFunc));
}
function importCall(imported, moduleSource, moduleImportFunc) {
if (typeof imported === "string") imported = literal(imported);
return {
arguments: [moduleSource].concat(imported || []),
callee: moduleImportFunc, type: "CallExpression"
};
}
function importCallStmt(imported, moduleSource, moduleImportFunc) {
return exprStmt(importCall(imported, moduleSource, moduleImportFunc));
}
function exportCall(exportFunc, local, exportedObj) {
if (typeof local === "string") local = literal(local);
exportedObj = obj.deepCopy(exportedObj);
return funcCall(exportFunc, local, exportedObj);
}
function exportCallStmt(exportFunc, local, exportedObj) {
return exprStmt(exportCall(exportFunc, local, exportedObj));
}
function declarationWrapperCall(
declarationWrapperNode,
declNode,
varNameLiteral,
varKindLiteral,
valueNode,
recorder,
options
) {
if (declNode) {
// here we pass compile-time meta data into the runtime
var keyVals = [];
var addMeta = false;
if (declNode["x-lively-object-meta"]) {
var {start, end, evalId, sourceAccessorName} = declNode["x-lively-object-meta"];
addMeta = true;
keyVals.push("start", nodes.literal(start), "end", nodes.literal(end))
}
if (evalId === undefined && options.hasOwnProperty("evalId")) {
evalId = options.evalId;
addMeta = true;
}
if (sourceAccessorName === undefined && options.hasOwnProperty("sourceAccessorName")) {
sourceAccessorName = options.sourceAccessorName;
addMeta = true;
}
if (evalId !== undefined) keyVals.push("evalId", nodes.literal(evalId));
if (sourceAccessorName) keyVals.push("moduleSource", nodes.id(sourceAccessorName));
if (addMeta) {
return funcCall(
declarationWrapperNode, varNameLiteral, varKindLiteral, valueNode, recorder,
nodes.objectLiteral(keyVals)/*meta node*/);
}
}
return funcCall(declarationWrapperNode, varNameLiteral, varKindLiteral, valueNode, recorder);
}