ion
Version:
ion language ========================
1,627 lines (1,572 loc) • 61 kB
JavaScript
void (function(){var _ion_compiler_postprocessor_ = function(module,exports,require){var activateStatements, addOrderPropertyToStatements, addPropertyDeclaration, addStatement, addUseStrictAndRequireIon, arrayComprehensionsToES5, assertStatements, basicTraverse, block, callFunctionBindForFatArrows, checkVariableDeclarations, classExpressions, convertForInToForLength, createForInLoopValueVariable, createTemplateFunctionClone, createTemplateRuntime, defaultAssignmentsToDefaultOperators, defaultOperatorsToConditionals, destructuringAssignments, ensureIonVariable, existentialExpression, extractForLoopRightVariable, extractForLoopsInnerAndTest, extractReactiveForPatterns, falseExpression, forEachDestructuringAssignment, functionDeclarations, functionParameterDefaultValuesToES5, getExternalIdentifiers, getPathExpression, getReferenceIdentifiers, globalOptions, hoistVariables, ion, ionExpression, isAncestorObjectExpression, isConstantLiteral, isFunctionNode, isObjectPatch, isPattern, isReferenceNode, isSimpleObjectExpression, isSuperExpression, isVariableReference, javascriptExpressions, letAndConstToVar, namedFunctionsAndNewArguments, nodeToLiteral, nodejsModules, nodes, nullExpression, patchAssignmentExpression, propertyDefinitions, propertyStatements, removeLocationInfo, setNodeOutputValues, spreadExpressions, superExpressions, thisExpression, traverse, trueExpression, typedObjectExpressions, undefinedExpression, validateTemplateNodes, variableDeclarationExpressions, wrapTemplateInnerFunctions;
({traverse} = require('./traverseAst'));
basicTraverse = require('./traverse').traverse;
({addStatement, forEachDestructuringAssignment} = require('./astFunctions'));
nodes = require('./nodes');
ion = require('../');
undefinedExpression = Object.freeze({
type: 'UnaryExpression',
argument: {
type: 'Literal',
value: 0
},
operator: 'void',
prefix: true
});
nullExpression = Object.freeze({
type: 'Literal',
value: null
});
trueExpression = Object.freeze({
type: 'Literal',
value: true
});
falseExpression = Object.freeze({
type: 'Literal',
value: false
});
ionExpression = Object.freeze({
type: 'Identifier',
name: 'ion'
});
thisExpression = Object.freeze({
type: 'ThisExpression'
});
isPattern = function(node) {
return (node.properties != null) || (node.elements != null);
};
isVariableReference = function(node, parent, key) {
return node.type === 'Identifier' && ((parent == null) || !(parent.type === 'MemberExpression' && key === 'property' || parent.type === 'Property' && key === 'key'));
};
isObjectPatch = function(node) {
return (node != null) && node.type === 'ObjectExpression' && ((node.objectType != null) || (node.create != null));
};
isConstantLiteral = function(node, parent, key) {
var computed, index, j, k, len, len1, ref1, ref2, type, value;
if (isObjectPatch(node)) {
return false;
}
if (node == null) {
return false;
}
if (node.type === 'Literal') {
return true;
}
if (isVariableReference(node, parent, key)) {
return false;
}
if (node.type === 'ObjectExpression') {
ref1 = node.properties;
for (index = j = 0, len = ref1.length; j < len; index = ++j) {
({type, value, computed} = ref1[index]);
if (computed || type !== 'Property' || !isConstantLiteral(value, node, index)) {
return false;
}
}
return true;
} else if (node.type === 'ArrayExpression') {
ref2 = node.elements;
for (index = k = 0, len1 = ref2.length; k < len1; index = ++k) {
value = ref2[index];
if (!isConstantLiteral(value, node, index)) {
return false;
}
}
return true;
} else {
return false;
}
};
nodeToLiteral = function(object, checkForLiteral) {
var item, key, node, value;
if (checkForLiteral !== false && isObjectPatch(object)) {
checkForLiteral = false;
}
node = null;
if ((object != null ? object.toLiteral : void 0) != null) {
node = object != null ? object.toLiteral() : void 0;
// else if checkForLiteral isnt false and object? and (object.type is 'ArrayExpression' or object.type is 'ObjectExpression') and isConstantLiteral(object)
// node =
// type: 'ObjectExpression'
// properties: [
// {type:'Property',key:{type:"Identifier",name:"type"},value:{type:"Literal",value:"Literal"}}
// {type:'Property',key:{type:"Identifier",name:"value"},value:object}
// ]
} else if (Array.isArray(object)) {
node = {
type: 'ArrayExpression',
elements: (function() {
var j, len, results1;
results1 = [];
for (j = 0, len = object.length; j < len; j++) {
item = object[j];
results1.push(nodeToLiteral(item, checkForLiteral));
}
return results1;
})()
};
} else if ((object != null ? object.constructor : void 0) === Object) {
node = {
type: 'ObjectExpression',
properties: []
};
for (key in object) {
value = object[key];
if (value !== void 0) {
node.properties.push({
key: {
type: 'Identifier',
name: key
},
value: nodeToLiteral(value, checkForLiteral),
kind: 'init'
});
}
}
} else {
node = {
type: 'Literal',
value: object
};
}
return node;
};
getPathExpression = function(path) {
var i, j, len, result, step, steps;
steps = path.split('.');
if (steps[0] === 'this') {
result = {
type: 'ThisExpression'
};
} else {
result = {
type: 'Identifier',
name: steps[0]
};
}
for (i = j = 0, len = steps.length; j < len; i = ++j) {
step = steps[i];
if (i > 0) {
result = {
type: 'MemberExpression',
object: result,
property: {
type: 'Identifier',
name: step
}
};
}
}
return result;
};
isFunctionNode = function(node) {
var ref1, ref2;
return (ref1 = (ref2 = nodes[node != null ? node.type : void 0]) != null ? ref2.isFunction : void 0) != null ? ref1 : false;
};
// wraps a node in a BlockStatement if it isn't already.
block = function(node) {
if (node.type !== 'BlockStatement') {
node = {
type: 'BlockStatement',
body: [node]
};
}
return node;
};
extractReactiveForPatterns = function(node, context) {
var declarator, index, j, len, ref, ref1, results1;
if (!context.reactive) {
return;
}
if (node.type === 'ForOfStatement' || node.type === 'ForInStatement') {
ref1 = node.left.declarations;
results1 = [];
for (index = j = 0, len = ref1.length; j < len; index = ++j) {
declarator = ref1[index];
if (!(isPattern(declarator.id))) {
continue;
}
ref = context.getNewInternalIdentifier();
// extract the object pattern into a destructuring assignment from a new variable
context.addStatement({
type: 'VariableDeclaration',
declarations: [
{
type: 'VariableDeclarator',
id: declarator.id,
init: ref
}
]
});
// replace it with a reference to the new variable
results1.push(declarator.id = ref);
}
return results1;
}
};
extractForLoopRightVariable = function(node, context) {
var ref, right;
if (context.reactive) {
return;
}
if (node.type === 'ForOfStatement' || (node.type === 'ForInStatement' && node.left.declarations.length > 1)) {
if (node.left.declarations.length > 2) {
throw context.error("too many declarations", node.left.declarations[2]);
}
right = node.right;
if (right.type !== "Identifier") {
ref = context.getNewInternalIdentifier();
node.right = ref;
return context.replace({
type: "BlockStatement",
body: [
{
type: "VariableDeclaration",
declarations: [
{
type: "VariableDeclarator",
id: ref,
init: right
}
],
kind: node.left.kind
},
node
]
});
}
}
};
createForInLoopValueVariable = function(node, context) {
var valueDeclarator;
if (context.reactive) {
return;
}
if (node.type === 'ForInStatement' && node.left.declarations.length > 1) {
valueDeclarator = node.left.declarations[1];
// TODO: Come up with a better fix for this.
// Find out why it's being visited more than once.
// It's probably because of all the tree manipulation.
// I imagine an insertion may be shifting the elements
// ahead to be retraversed while traversing an array.
// -Kody
if (node.visited_createForInLoopValueVariable) {
return;
}
node.visited_createForInLoopValueVariable = true;
return context.addVariable({
id: valueDeclarator.id,
init: {
type: 'MemberExpression',
computed: true,
object: node.right,
property: node.left.declarations[0].id
}
});
}
};
convertForInToForLength = function(node, context) {
var loopIndex, ref1, userIndex;
if (context.reactive) {
return;
}
if (node.type === 'ForOfStatement') {
userIndex = (ref1 = node.left.declarations[1]) != null ? ref1.id : void 0;
loopIndex = context.getNewInternalIdentifier("_i");
addStatement(node, {
type: "VariableDeclaration",
declarations: [
{
type: "VariableDeclarator",
id: node.left.declarations[0].id,
init: {
type: "MemberExpression",
object: node.right,
property: loopIndex,
computed: true
}
}
],
kind: node.left.kind
});
if (userIndex != null) {
addStatement(node, {
type: "VariableDeclaration",
declarations: [
{
type: "VariableDeclarator",
id: userIndex,
init: loopIndex
}
],
kind: node.left.kind
});
}
return context.replace({
type: 'ForStatement',
init: {
type: "VariableDeclaration",
declarations: [
{
type: "VariableDeclarator",
id: loopIndex,
init: {
type: "Literal",
value: 0
}
}
],
kind: 'let'
},
test: {
type: "BinaryExpression",
operator: "<",
left: loopIndex,
right: {
type: "MemberExpression",
object: node.right,
property: {
type: "Identifier",
name: "length"
},
computed: false
}
},
update: {
type: "UpdateExpression",
operator: "++",
argument: loopIndex,
prefix: false
},
body: node.body
});
}
};
callFunctionBindForFatArrows = function(node, context) {
if (node.type === 'FunctionExpression' && node.bound) {
delete node.bound;
ensureIonVariable(context);
return context.replace({
type: "CallExpression",
callee: getPathExpression('ion.bind'),
arguments: [node, thisExpression]
});
}
};
nodejsModules = function(node, context) {
var declarator, j, ref1, results1;
// convert ImportExpression{name} into require(name)
if (node.type === 'ImportExpression') {
node.type = 'CallExpression';
node.callee = {
type: 'Identifier',
name: 'require'
};
node.arguments = [node.name];
return delete node.name;
} else if (node.type === 'ExportStatement') {
if (node.value.type === 'VariableDeclaration') {
// variable export
context.exports = true;
// replace this node with the VariableDeclaration
context.replace(node.value);
ref1 = node.value.declarations;
// then make each init also assign to it's export variable.
results1 = [];
for (j = ref1.length - 1; j >= 0; j += -1) {
declarator = ref1[j];
if (declarator.init == null) {
throw context.error("Export variables must have an init value", declarator);
}
results1.push(declarator.init = {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'exports'
},
property: declarator.id
},
right: declarator.init
});
}
return results1;
} else {
// default export
if (context.exports) {
throw context.error("default export must be first");
}
return context.replace({
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
object: {
type: 'Identifier',
name: 'module'
},
property: {
type: 'Identifier',
name: 'exports'
}
},
right: {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'Identifier',
name: 'exports'
},
right: node.value
}
}
});
}
}
};
// separateAllVariableDeclarations = (node, context) ->
// if node.type is 'VariableDeclaration' and context.isParentBlock()
// while node.declarations.length > 1
// declaration = node.declarations.pop()
// context.addStatement
// type: node.type
// declarations: [declaration]
// kind: node.kind
destructuringAssignments = function(node, context) {
var count, declarator, declaratorIndex, expression, index, j, k, l, len, len1, len2, pattern, ref1, ref2, statement, statements, tempId;
// function parameters
if (isFunctionNode(node)) {
ref1 = node.params;
for (index = j = 0, len = ref1.length; j < len; index = ++j) {
pattern = ref1[index];
if (!(isPattern(pattern))) {
continue;
}
tempId = context.getNewInternalIdentifier();
node.params[index] = tempId;
statements = [];
forEachDestructuringAssignment(pattern, tempId, function(id, expression) {
return statements.unshift({
type: 'VariableDeclaration',
declarations: [
{
type: 'VariableDeclarator',
id: id,
init: expression
}
],
kind: 'let'
});
});
for (k = 0, len1 = statements.length; k < len1; k++) {
statement = statements[k];
context.addStatement(statement);
}
}
}
// variable assignments
if (node.type === 'VariableDeclaration' && context.isParentBlock()) {
count = 0;
ref2 = node.declarations;
for (declaratorIndex = l = 0, len2 = ref2.length; l < len2; declaratorIndex = ++l) {
declarator = ref2[declaratorIndex];
if (!(isPattern(declarator.id))) {
continue;
}
pattern = declarator.id;
tempId = context.getNewInternalIdentifier();
declarator.id = tempId;
// we must extract the init and add it later, otherwise...
// it may reference a value which hasn't been destructured yet from a previous declarator
if ((declarator.init != null) && declaratorIndex > 0) {
context.addStatement({
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
operator: '=',
left: tempId,
right: declarator.init
}
}, ++count);
declarator.init = null;
node.kind = 'let';
}
forEachDestructuringAssignment(pattern, tempId, function(id, expression) {
return context.addStatement({
type: 'VariableDeclaration',
declarations: [
{
type: 'VariableDeclarator',
id: id,
init: expression
}
],
kind: 'let'
}, ++count);
});
}
}
// other assignments
if (node.type === 'ExpressionStatement' && node.expression.operator === '=') {
expression = node.expression;
pattern = expression.left;
if (isPattern(pattern)) {
tempId = context.getNewInternalIdentifier();
context.replace({
type: 'VariableDeclaration',
declarations: [
{
type: 'VariableDeclarator',
id: tempId,
init: expression.right
}
],
kind: 'const'
});
count = 0;
return forEachDestructuringAssignment(pattern, tempId, function(id, expression) {
return context.addStatement({
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
operator: '=',
left: id,
right: expression
}
}, ++count);
});
}
}
};
defaultOperatorsToConditionals = function(node, context) {
if (node.type === 'BinaryExpression' && (node.operator === '??' || node.operator === '?')) {
return context.replace({
type: 'ConditionalExpression',
test: {
type: 'BinaryExpression',
operator: '!=',
left: node.left,
right: node.operator === '??' ? undefinedExpression : nullExpression
},
consequent: node.left,
alternate: node.right
});
}
};
defaultAssignmentsToDefaultOperators = function(node, context) {
if (node.type === 'AssignmentExpression' && (node.operator === '?=' || node.operator === '??=')) {
// a ?= b --> a = a ? b
node.right = {
type: 'BinaryExpression',
operator: node.operator === '?=' ? '?' : '??',
left: node.left,
right: node.right
};
return node.operator = '=';
}
};
existentialExpression = function(node, context) {
var existentialChild, existentialChildObject, getExistentialDescendantObject, ref1;
if (node.type === 'UnaryExpression' && node.operator === '?') {
context.replace({
type: 'BinaryExpression',
operator: '!=',
left: node.argument,
right: nullExpression
});
}
// this could be more efficient by caching the left values
// especially when the left side involves existential CallExpressions
// should only apply within an imperative context
if (node.type === 'MemberExpression' || node.type === 'CallExpression' && !context.reactive) {
// search descendant objects for deepest existential child
getExistentialDescendantObject = function(check) {
var ref1, result;
result = null;
if (check.type === 'MemberExpression' || check.type === 'CallExpression') {
result = getExistentialDescendantObject((ref1 = check.object) != null ? ref1 : check.callee);
if (check.existential) {
if (result == null) {
result = check;
}
}
}
return result;
};
// create temp ref variable
// a?.b --> a != null ? a.b : undefined
existentialChild = getExistentialDescendantObject(node);
if (existentialChild != null) {
existentialChildObject = (ref1 = existentialChild.object) != null ? ref1 : existentialChild.callee;
delete existentialChild.existential;
return context.replace({
type: 'ConditionalExpression',
test: {
type: 'BinaryExpression',
operator: '!=',
left: existentialChildObject,
right: nullExpression
},
consequent: node,
alternate: undefinedExpression
});
}
}
};
ensureIonVariable = function(context, required = true) {
return context.ancestorNodes[0].requiresIon = required;
};
addUseStrictAndRequireIon = {
enter: function(node, context) {
var d, j, len, ref1, ref2, results1;
// see if we are already importing ion at the Program scope
if (node.type === 'VariableDeclaration' && ((ref1 = context.parentNode()) != null ? ref1.type : void 0) === 'Program') {
ref2 = node.declarations;
results1 = [];
for (j = 0, len = ref2.length; j < len; j++) {
d = ref2[j];
if (!(d.id.name === 'ion')) {
continue;
}
// we don't need to import ion because the user already did
ensureIonVariable(context, false);
break;
}
return results1;
}
},
exit: function(node, context) {
if (node.type === 'Program') {
if (node.requiresIon) {
delete node.requiresIon;
context.addVariable({
offset: Number.MIN_VALUE,
kind: 'const',
id: ionExpression,
init: {
type: 'ImportExpression',
name: {
type: 'Literal',
value: 'ion'
}
}
});
}
return node.body.unshift({
type: 'ExpressionStatement',
expression: {
type: 'Literal',
value: 'use strict'
}
});
}
}
};
extractForLoopsInnerAndTest = function(node, context) {
if (node.type === 'ForInStatement' || node.type === 'ForOfStatement') {
if (node.inner != null) {
node.inner.body = node.body;
node.body = node.inner;
delete node.inner;
}
if (node.test != null) {
node.body = block({
type: 'IfStatement',
test: node.test,
consequent: block(node.body)
});
return delete node.test;
}
}
};
arrayComprehensionsToES5 = function(node, context) {
var forStatement, tempId;
if (node.type === 'ArrayExpression' && (node.value != null) && (node.comprehension != null)) {
if (context.reactive) {
// convert it to a typed object expression
forStatement = node.comprehension;
forStatement.body = {
type: 'ExpressionStatement',
expression: node.value
};
return context.replace({
type: 'ObjectExpression',
objectType: {
type: 'ArrayExpression',
elements: []
},
properties: [forStatement]
});
} else {
// add a statement
tempId = context.addVariable({
offset: 0,
init: {
type: 'ArrayExpression',
elements: []
}
});
forStatement = node.comprehension;
forStatement.body = {
type: 'ExpressionStatement',
expression: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: tempId,
property: {
type: 'Identifier',
name: 'push'
}
},
arguments: [node.value]
}
};
context.addStatement(0, forStatement);
return context.replace(tempId);
}
}
};
functionParameterDefaultValuesToES5 = function(node, context) {
var defaultValue, index, j, param, ref1, ref2, results1;
if (context.reactive) {
return;
}
if (isFunctionNode(node) && (node.params != null) && (node.defaults != null)) {
ref1 = node.params;
results1 = [];
for (index = j = ref1.length - 1; j >= 0; index = j += -1) {
param = ref1[index];
defaultValue = (ref2 = node.defaults) != null ? ref2[index] : void 0;
if (defaultValue != null) {
context.addStatement({
type: 'IfStatement',
test: {
type: 'BinaryExpression',
operator: '==',
left: param,
right: nullExpression
},
consequent: {
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
operator: '=',
left: param,
right: defaultValue
}
}
});
results1.push(node.defaults[index] = void 0);
} else {
results1.push(void 0);
}
}
return results1;
}
};
isSimpleObjectExpression = function(node) {
var isArray, isSimple, j, len, property, ref1, ref2;
if (node.type !== 'ObjectExpression') {
return false;
}
isArray = ((ref1 = node.objectType) != null ? ref1.type : void 0) === "ArrayExpression";
isSimple = true;
if (node.properties != null) {
ref2 = node.properties;
for (j = 0, len = ref2.length; j < len; j++) {
property = ref2[j];
if (isArray) {
if (property.type !== 'ExpressionStatement') {
isSimple = false;
break;
}
} else {
if (property.type !== 'Property' || property.computed) {
isSimple = false;
break;
}
}
}
}
return isSimple;
};
typedObjectExpressions = function(node, context) {
var addPosition, element, elements, expressionStatement, getExistingObjectIdIfTempVarNotNeeded, grandNode, initialValue, isArray, isSimple, j, k, len, len1, objectId, objectType, parentNode, ref1, ref2, ref3, statements;
// only for imperative code
if (context.reactive) {
return;
}
if (node.type === 'ObjectExpression' && node.simple !== true) {
isArray = ((ref1 = node.objectType) != null ? ref1.type : void 0) === "ArrayExpression";
isSimple = isSimpleObjectExpression(node);
// empty object expression without properties {}
if (isSimple) {
if (isArray) {
elements = [];
if (node.objectType != null) {
ref2 = node.objectType.elements;
for (j = 0, len = ref2.length; j < len; j++) {
element = ref2[j];
elements.push(element);
}
}
ref3 = node.properties;
for (k = 0, len1 = ref3.length; k < len1; k++) {
expressionStatement = ref3[k];
elements.push(expressionStatement.expression);
}
context.replace({
type: "ArrayExpression",
elements: elements
});
return;
}
if ((node.objectType == null) || (node.objectType.type === 'ObjectExpression' && node.objectType.properties.length === 0)) {
// check that our properties ONLY contain normal Property objects with no computed values
delete node.objectType;
// set simple to true, but make it non-enumerable so we don't write it out
Object.defineProperty(node, 'simple', {
value: true
});
return;
}
// convert this simple expression to an ion.patch call
objectType = node.objectType;
delete node.objectType;
// if the objectType is simple as well, then merge them
if (isSimpleObjectExpression(objectType)) {
node.properties = objectType.properties.concat(node.properties);
Object.defineProperty(node, 'simple', {
value: true
});
return;
} else {
ensureIonVariable(context);
context.replace({
type: 'CallExpression',
callee: getPathExpression('ion.patch.combine'),
arguments: [objectType, node]
});
return;
}
}
if (node.objectType == null) {
initialValue = {
type: 'ObjectExpression',
properties: []
};
} else {
initialValue = node.objectType;
}
parentNode = context.parentNode();
grandNode = context.ancestorNodes[context.ancestorNodes.length - 2];
addPosition = 0;
getExistingObjectIdIfTempVarNotNeeded = function(node, parentNode, grandNode) {
// don't need a temp variable because nothing can trigger on variable declaration
if (parentNode.type === 'VariableDeclarator') {
return parentNode.id;
}
// don't need a temp variable because nothing can trigger on variable assignment
if (parentNode.type === 'AssignmentExpression' && parentNode.left.type === 'Identifier' && (grandNode != null ? grandNode.type : void 0) === 'ExpressionStatement') {
return parentNode.left;
}
// for everything else we must use a temp variable and assign all sub properties
// before using the final value in an expression, because it may trigger a setter
// or be a parameter in a function call or constructor
return null;
};
objectId = getExistingObjectIdIfTempVarNotNeeded(node, parentNode, grandNode);
if (objectId != null) {
// replace this with the initial value
context.replace(initialValue);
addPosition = 1;
} else {
// create a temp variable
objectId = context.addVariable({
offset: 0,
init: initialValue
});
// replace this with a reference to the variable
context.replace(objectId);
}
statements = [];
setNodeOutputValues(context, node.properties, objectId, statements, isArray);
if (statements.length === 1) {
return context.addStatement(statements[0], addPosition);
} else {
return context.addStatement({
type: 'BlockStatement',
body: statements
}, addPosition);
}
}
};
setNodeOutputValues = function(context, nodes, outputId, statements = [], isArray) {
var subnodeEnter, subnodeExit;
// traverse all properties and expression statements
// add a new property that indicates their output scope
subnodeEnter = function(subnode, subcontext) {
if (subcontext.outputStack == null) {
subcontext.outputStack = [outputId];
}
if (subnode.type === 'ObjectExpression' || subnode.type === 'ArrayExpression') {
return subcontext.skip();
}
if (subnode.type === 'Property') { //or subnode.type is 'ExpressionStatement'
// we convert the node to a Property: ObjectExpression node
// it will be handled correctly by the later propertyStatements rule
subnode.output = subcontext.outputStack[subcontext.outputStack.length - 1];
subcontext.outputStack.push({
type: 'MemberExpression',
object: subnode.output,
property: subnode.key,
computed: subnode.computed || subnode.key.type !== 'Identifier'
});
} else if (isFunctionNode(subnode)) {
subcontext.skip();
} else if (subnode.type === 'ExpressionStatement') {
if (!isArray) {
ensureIonVariable(context);
}
subnode = subcontext.replace({
type: 'ExpressionStatement',
expression: {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: isArray ? outputId : ionExpression,
property: {
type: 'Identifier',
name: isArray ? 'push' : 'add'
}
},
arguments: isArray ? [subnode.expression] : [outputId, subnode.expression]
}
});
subcontext.skip();
}
if (subcontext.parentNode() == null) {
// add this statement to the current context
return statements.push(subnode);
}
};
subnodeExit = function(subnode, subcontext) {
if (subnode.type === 'Property') {
return subcontext.outputStack.pop();
}
};
traverse(nodes, subnodeEnter, subnodeExit);
return statements;
};
propertyStatements = function(node, context) {
var left, parent, right;
if (context.reactive) {
return;
}
parent = context.parentNode();
if (node.type === 'Property' && !(parent.type === 'ObjectExpression' || parent.type === 'ObjectPattern')) {
if (node.output != null) {
if (node.value.type === 'ObjectExpression') {
left = {
type: 'MemberExpression',
object: node.output,
property: node.key,
computed: node.computed
};
if (node.value.type === 'ObjectExpression' && (node.value.objectType == null)) {
ensureIonVariable(context);
right = {
type: 'CallExpression',
callee: getPathExpression('ion.patch.combine'),
arguments: [ion.clone(left, true), node.value]
};
} else {
right = node.value;
}
return context.replace({
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
operator: '=',
left: left,
right: right
}
});
} else {
return context.replace({
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
object: node.output,
property: node.key,
computed: node.computed
},
right: node.value
}
});
}
} else {
throw context.error("Property assignment only valid within an object declaration", node);
}
}
};
patchAssignmentExpression = function(node, context) {
if (node.type === 'AssignmentExpression' && node.operator === ':=') {
ensureIonVariable(context);
return context.replace({
type: 'AssignmentExpression',
operator: '=',
left: node.left,
right: {
type: 'CallExpression',
callee: getPathExpression('ion.patch.combine'),
arguments: [ion.clone(node.left, true), node.right]
}
});
}
};
classExpressions = function(node, context) {
var base, classExpression, hasIdentifierName, j, len, name, properties, property;
if (node.type === 'ClassExpression') {
ensureIonVariable(context);
properties = node.properties;
hasIdentifierName = (node.name != null) && !node.computed;
if (node.name != null) {
name = hasIdentifierName ? {
type: 'Literal',
value: node.name.name
} : node.name;
// add name to the properties
properties = [
{
type: 'Property',
key: {
type: 'Identifier',
name: 'name'
},
value: name
}
].concat(properties);
}
// set the class name on the constructor function
if (hasIdentifierName) {
for (j = 0, len = properties.length; j < len; j++) {
property = properties[j];
if (property.key.name === 'constructor') {
if ((base = property.value).id == null) {
base.id = node.name;
}
}
}
}
classExpression = {
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: ionExpression,
property: {
type: 'Identifier',
name: 'defineClass'
}
},
arguments: [
{
type: 'ObjectExpression',
properties: properties
}
].concat(node.extends)
};
if (hasIdentifierName) {
context.addVariable({
id: node.name,
kind: 'const',
init: classExpression,
offset: 0
});
return context.replace(node.name);
} else {
return context.replace(classExpression);
}
}
};
checkVariableDeclarations = {
enter: function(node, context) {
var base, variable;
// check assigning to a constant
if (node.type === 'AssignmentExpression') {
if (node.left.type === 'Identifier') {
variable = context.getVariableInfo(node.left.name);
if (variable == null) {
throw context.error(`cannot assign to undeclared variable ${node.left.name}`);
}
if (variable.kind === 'const') {
throw context.error("cannot assign to a const", node.left);
}
}
if (context.reactive) {
throw context.error("cannot assign within templates", node);
}
}
// track variable usage on a scope
if (isVariableReference(node, context.parentNode(), context.key())) {
// then this is a variable usage, so we will track it.
return ((base = context.scope()).usage != null ? base.usage : base.usage = {})[node.name] = node;
}
},
variable: function(variable, context) {
var checkScope, existing, j, k, ref1, ref2, ref3, ref4, ref5, results1, scope, shadow, used;
scope = context.scope();
// check that we arent redeclaring a variable
existing = context.getVariableInfo(variable.name);
if (existing != null) {
// check to see if shadowing is allowed.
// walk the scope stack backwards
shadow = false;
ref1 = context.scopeStack;
for (j = ref1.length - 1; j >= 0; j += -1) {
checkScope = ref1[j];
// we only check back until we hit the scope
// where the existing variable was declared
if (checkScope === (existing != null ? existing.scope : void 0)) {
break;
}
// if we pass a scope that allows shadowing then we are ok
if ((ref2 = nodes[checkScope.node.type]) != null ? ref2.shadow : void 0) {
shadow = true;
break;
}
}
if (!shadow) {
throw context.error(`Cannot redeclare variable ${variable.name}`, variable.node);
}
}
ref3 = context.scopeStack;
// make sure we havent used this variable before declaration
results1 = [];
for (k = ref3.length - 1; k >= 0; k += -1) {
checkScope = ref3[k];
used = (ref4 = checkScope.usage) != null ? ref4[variable.name] : void 0;
if (used != null) {
throw context.error(`Cannot use variable '${variable.name}' before declaration`, used);
}
// we only check back to a shadow, max
if ((ref5 = nodes[checkScope.node.type]) != null ? ref5.shadow : void 0) {
break;
} else {
results1.push(void 0);
}
}
return results1;
}
};
isAncestorObjectExpression = function(context) {
var ancestor, j, ref1;
ref1 = context.ancestorNodes;
for (j = ref1.length - 1; j >= 0; j += -1) {
ancestor = ref1[j];
if (ancestor.type === 'ObjectExpression') {
return true;
}
if (isFunctionNode(ancestor)) {
return false;
}
}
return false;
};
namedFunctionsAndNewArguments = function(node, context) {
var base, base1, ref1;
if (context.reactive) {
return;
}
if (node.type === 'NewExpression') {
if (node.arguments == null) {
node.arguments = [];
}
}
// these names are used later by the classExpression rule
// add an internal name to functions declared as variables
if (node.type === 'VariableDeclarator' && ((ref1 = node.init) != null ? ref1.type : void 0) === 'FunctionExpression') {
if ((base = node.init).name == null) {
base.name = node.id;
}
}
// add an internal name to functions declared as properties
if (node.type === 'Property' && node.value.type === 'FunctionExpression' && node.key.type === 'Identifier') {
if (node.key.name !== 'constructor') {
return (base1 = node.value).name != null ? base1.name : base1.name = node.key;
}
}
};
assertStatements = function(node, context) {
if (node.type === 'AssertStatement') {
return context.replace({
type: 'IfStatement',
test: {
type: 'UnaryExpression',
prefix: true,
operator: '!',
argument: node.expression
},
consequent: {
type: 'ThrowStatement',
argument: {
type: 'NewExpression',
callee: {
type: 'Identifier',
name: 'Error'
},
arguments: [
{
type: 'Literal',
value: `Assertion Failed: (${node.text})`
}
]
}
}
});
}
};
isSuperExpression = function(node, context) {
var parentNode;
parentNode = context.parentNode();
if (node.type === 'Identifier' && node.name === 'super' && parentNode.type !== 'CallExpression' && parentNode.type !== 'MemberExpression') {
return true;
}
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'super') {
return true;
}
return false;
};
superExpressions = function(node, context) {
var applyOrCall, args, classNode, functionNode, functionProperty, isConstructor, ref1, ref2, superFunction;
if (isSuperExpression(node, context)) {
classNode = context.getAncestor(function(node) {
return node.type === 'ClassExpression';
});
functionNode = context.getAncestor(isFunctionNode);
functionProperty = context.ancestorNodes[context.ancestorNodes.indexOf(functionNode) - 1];
isConstructor = (functionProperty != null ? (ref1 = functionProperty.key) != null ? ref1.name : void 0 : void 0) === 'constructor';
if ((classNode == null) || !(((functionNode != null ? functionNode.name : void 0) != null) || isConstructor)) {
throw context.error("super can only be used within named class functions", node);
}
args = [
{
type: 'ThisExpression'
}
];
if (node.type === 'Identifier') {
args.push({
type: 'Identifier',
name: 'arguments'
});
applyOrCall = 'apply';
} else {
args = args.concat(node.arguments);
applyOrCall = 'call';
}
superFunction = getPathExpression(`${classNode.name.name}.super`);
if (!isConstructor) {
superFunction = {
type: 'MemberExpression',
object: {
type: 'MemberExpression',
object: superFunction,
property: {
type: 'Identifier',
name: 'prototype'
}
},
property: (ref2 = functionNode.name) != null ? ref2 : 'constructor'
};
}
return context.replace({
type: 'CallExpression',
callee: {
type: 'MemberExpression',
object: superFunction,
property: {
type: 'Identifier',
name: applyOrCall
}
},
arguments: args
});
}
};
spreadExpressions = function(node, context) {
var args, finalParameters, getOffsetFromArgumentsLength, index, j, len, param, ref1, spread, spreadIndex;
// function rest parameters
if (isFunctionNode(node)) {
spread = null;
spreadIndex = null;
ref1 = node.params;
for (index = j = 0, len = ref1.length; j < len; index = ++j) {
param = ref1[index];
if (param.type === 'SpreadExpression') {
spread = param;
spreadIndex = index;
break;
}
}
if (spread != null) {
// replace the spread parameter with a placeholder named parameter
node.params[spreadIndex] = {
type: 'Identifier',
name: "___" + spread.expression.name
};
// add a variable that extracts the spread
args = [
{
type: 'Identifier',
name: 'arguments'
},
{
type: 'Literal',
value: spreadIndex
}
];
finalParameters = node.params.length - 1 - spreadIndex;
if (finalParameters > 0) {
// add a third arg to the slice that removes the final parameters from the end
getOffsetFromArgumentsLength = function(offset) {
return {
type: 'BinaryExpression',
operator: '-',
left: getPathExpression('arguments.length'),
right: {
type: 'Literal',
value: offset
}
};
};
args.push(getOffsetFromArgumentsLength(finalParameters));
// extract the correct values for the final variables.
index = node.params.length - 1;
while (index > spreadIndex) {
param = node.params[index--];
context.addStatement({
type: 'ExpressionStatement',
expression: {
type: 'AssignmentExpression',
operator: '=',
left: param,
right: {
type: 'MemberExpression',
computed: true,
object: getPathExpression('arguments'),
property: getOffsetFromArgumentsLength(node.params.length - 1 - index)
}
}
});
}
}
return context.addVariable({
id: spread.expression,
init: {
type: 'CallExpression',
callee: getPathExpression('Array.prototype.slice.call'),
arguments: args
}
});
}
}
};
validateTemplateNodes = function(node, context) {
var ref1;
if (context.reactive) {
if (((ref1 = nodes[node.type]) != null ? ref1.allowedInReactive : void 0) === false) {
throw context.error(node.type + " not allowed in templates", node);
}
}
};
removeLocationInfo = function(node) {
return traverse(node, function(node) {
var ref1;
if ((node.loc != null) && !((ref1 = nodes[node != null ? node.type : void 0]) != null ? ref1.location : void 0)) {
delete node.loc;
}
return node;
});
};
isReferenceNode = function(node, context) {
var parentNode;
if (node.type !== 'Identifier') {
return false;
}
parentNode = context.parentNode();
// ignore function names
if (isFunctionNode(parentNode)) {
return false;
}
// ignore variable declarations
if ((parentNode != null ? parentNode.type : void 0) === 'VariableDeclarator' && context.key() === 'id') {
return false;
}
// ignore member expression right hand identifiers
if ((parentNode != null ? parentNode.type : void 0) === 'MemberExpression' && !(parentNode != null ? parentNode.computed : void 0) && context.key() === 'property') {
return false;
}
// ignore object property keys
if ((parentNode != null ? parentNode.type : void 0) === 'Property' && context.key() === 'key') {
return false;
}
return true;
};
// gets all identifiers, except member access properties
getReferenceIdentifiers = function(node, callback) {
var results;
results = {};
if (callback == null) {
callback = function(node) {
return results[node.name] = node;
};
}
traverse(node, function(node, context) {
var isRef;
isRef = isReferenceNode(node, context);
if (isRef) {
return callback(node, context);
}
});
return results;
};
// gets all identifiers, except member access properties
getExternalIdentifiers = function(node, callback) {
getReferenceIdentifiers(node, function(node, context) {
// ignore internally defined variables
if (context.getVariableInfo(node.name) != null) {
return;
}
return callback(node, context);
});
};
wrapTemplateInnerFunctions = function(node, context) {
var contextId, id, name, requiresWrapper, variables;
if (context.parentReactive()) {
if (node.type === 'FunctionExpression' && (node.toLiteral == null)) {
// see if we need to replace any properties in this function or not.
variables = {};
getExternalIdentifiers(node, function(id) {
var ref1, ref2;
if (id.name === 'ion' || (id.name !== ((ref1 = node.id) != null ? ref1.name : void 0) && (((ref2 = context.scope()) != null ? ref2.variables[id.name] : void 0) != null))) {
return variables[id.name] = id;
}
});
requiresWrapper = Object.keys(variables).length > 0;
if (requiresWrapper) {
// now convert the node to a new wrapped node
// add a statement extracting each needed variable from the reactive context
contextId = context.getNewInternalIdentifier('_context');
node.body.body.unshift({
type: 'VariableDeclaration',
kind: 'const',
declarations: (function() {
var results1;
results1 = [];
for (name in variables) {
id = variables[name];
results1.push({
type: 'VariableDeclarator',
id: id,
init: {
type: 'CallExpression',
callee: getPathExpression(`${contextId.name}.get`),
arguments: [
{
type: 'Literal',
value: id.name
}
]
}
});
}
return results1;
})()
});
node = {
type: 'FunctionExpression',
params: [contextId],
body: {
type: 'BlockStatement',
body: [
{
type: 'ReturnStatement',
argument: node
}
]
}
};
}
node.toLiteral = function() {
return this;
};
return context.replace({
type: 'Function',
context: requiresWrapper,
value: node
});
}
}
};
createTemplateFunctionClone = function(node, context) {
var inner, newNode, scope;
if (isFunctionNode(node) && node.template === true) {
delete node.template;
node.type = 'Template';
ensureIonVariable(context);
newNode = {
type: 'CallExpression',
callee: getPathExpression('ion.template'),
arguments: [node],
toLiteral: function() {
return this;
}
};
// if this is an inner template we must wrap it to get the parents scoped variables.
inner = context.parentReactive();
if (inner) {
scope = context.getNewInternalIdentifier('_ps');
node.scope = scope;
// replace the node with a function that will be called with context
newNode = {
type: 'Function',
context: true,
value: {
type: 'FunctionExpression',
params: [scope],
toLiteral: function() {
return this;
},
body: block({
type: 'ReturnStatement',
argument: newNode
})
}
};
}
return context.replace(newNode);
}
};
createTemplateRuntime = function(node, context) {
var args, id, j, k, key, len, len1, name, newNode, params, ref1, ref2, ref3, ref4, referenceIds, template, templateId, value, variables;
if (node.type === 'Template') {
template = removeLocationInfo(node);
templateId = node.id;
// create an arguments object that contains all the parameter values.
args = {
type: 'ObjectExpression',
properties: []
};
variables = {
this: thisExpression
};
// get all reference identifiers
referenceIds = getReferen