/** * Copyright 2014 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /*global exports:true*/ /** * Implements ES6 destructuring assignment and pattern matchng. * * function init({port, ip, coords: [x, y]}) { * return (x && y) ? {id, port} : {ip}; * }; * * function init($__0) { * var * port = $__0.port, * ip = $__0.ip, * $__1 = $__0.coords, * x = $__1[0], * y = $__1[1]; * return (x && y) ? {id, port} : {ip}; * } * * var x, {ip, port} = init({ip, port}); * * var x, $__0 = init({ip, port}), ip = $__0.ip, port = $__0.port; * */ var Syntax = require('esprima-fb').Syntax; var utils = require('../src/utils'); var reservedWordsHelper = require('./reserved-words-helper'); var restParamVisitors = require('./es6-rest-param-visitors'); var restPropertyHelpers = require('./es7-rest-property-helpers'); // ------------------------------------------------------- // 1. Structured variable declarations. // // var [a, b] = [b, a]; // var {x, y} = {y, x}; // ------------------------------------------------------- function visitStructuredVariable(traverse, node, path, state) { // Allocate new temp for the pattern. utils.append(utils.getTempVar(state.localScope.tempVarIndex) + '=', state); // Skip the pattern and assign the init to the temp. utils.catchupWhiteSpace(node.init.range[0], state); traverse(node.init, path, state); utils.catchup(node.init.range[1], state); // Render the destructured data. utils.append(',' + getDestructuredComponents(node.id, state), state); state.localScope.tempVarIndex++; return false; } visitStructuredVariable.test = function(node, path, state) { return node.type === Syntax.VariableDeclarator && isStructuredPattern(node.id); }; function isStructuredPattern(node) { return node.type === Syntax.ObjectPattern || node.type === Syntax.ArrayPattern; } // Main function which does actual recursive destructuring // of nested complex structures. function getDestructuredComponents(node, state) { var tmpIndex = state.localScope.tempVarIndex; var components = []; var patternItems = getPatternItems(node); for (var idx = 0; idx < patternItems.length; idx++) { var item = patternItems[idx]; if (!item) { continue; } if (item.type === Syntax.SpreadElement) { // Spread/rest of an array. // TODO(dmitrys): support spread in the middle of a pattern // and also for function param patterns: [x, ...xs, y] components.push(item.argument.name + '=Array.prototype.slice.call(' + utils.getTempVar(tmpIndex) + ',' + idx + ')' ); continue; } if (item.type === Syntax.SpreadProperty) { var restExpression = restPropertyHelpers.renderRestExpression( utils.getTempVar(tmpIndex), patternItems ); components.push(item.argument.name + '=' + restExpression); continue; } // Depending on pattern type (Array or Object), we get // corresponding pattern item parts. var accessor = getPatternItemAccessor(node, item, tmpIndex, idx); var value = getPatternItemValue(node, item); // TODO(dmitrys): implement default values: {x, y=5} if (value.type === Syntax.Identifier) { // Simple pattern item. components.push(value.name + '=' + accessor); } else { // Complex sub-structure. components.push( utils.getTempVar(++state.localScope.tempVarIndex) + '=' + accessor + ',' + getDestructuredComponents(value, state) ); } } return components.join(','); } function getPatternItems(node) { return node.properties || node.elements; } function getPatternItemAccessor(node, patternItem, tmpIndex, idx) { var tmpName = utils.getTempVar(tmpIndex); if (node.type === Syntax.ObjectPattern) { if (reservedWordsHelper.isReservedWord(patternItem.key.name)) { return tmpName + '["' + patternItem.key.name + '"]'; } else if (patternItem.key.type === Syntax.Literal) { return tmpName + '[' + JSON.stringify(patternItem.key.value) + ']'; } else if (patternItem.key.type === Syntax.Identifier) { return tmpName + '.' + patternItem.key.name; } } else if (node.type === Syntax.ArrayPattern) { return tmpName + '[' + idx + ']'; } } function getPatternItemValue(node, patternItem) { return node.type === Syntax.ObjectPattern ? patternItem.value : patternItem; } // ------------------------------------------------------- // 2. Assignment expression. // // [a, b] = [b, a]; // ({x, y} = {y, x}); // ------------------------------------------------------- function visitStructuredAssignment(traverse, node, path, state) { var exprNode = node.expression; utils.append('var ' + utils.getTempVar(state.localScope.tempVarIndex) + '=', state); utils.catchupWhiteSpace(exprNode.right.range[0], state); traverse(exprNode.right, path, state); utils.catchup(exprNode.right.range[1], state); utils.append( ';' + getDestructuredComponents(exprNode.left, state) + ';', state ); utils.catchupWhiteSpace(node.range[1], state); state.localScope.tempVarIndex++; return false; } visitStructuredAssignment.test = function(node, path, state) { // We consider the expression statement rather than just assignment // expression to cover case with object patters which should be // wrapped in grouping operator: ({x, y} = {y, x}); return node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.AssignmentExpression && isStructuredPattern(node.expression.left); }; // ------------------------------------------------------- // 3. Structured parameter. // // function foo({x, y}) { ... } // ------------------------------------------------------- function visitStructuredParameter(traverse, node, path, state) { utils.append(utils.getTempVar(getParamIndex(node, path)), state); utils.catchupWhiteSpace(node.range[1], state); return true; } function getParamIndex(paramNode, path) { var funcNode = path[0]; var tmpIndex = 0; for (var k = 0; k < funcNode.params.length; k++) { var param = funcNode.params[k]; if (param === paramNode) { break; } if (isStructuredPattern(param)) { tmpIndex++; } } return tmpIndex; } visitStructuredParameter.test = function(node, path, state) { return isStructuredPattern(node) && isFunctionNode(path[0]); }; function isFunctionNode(node) { return (node.type == Syntax.FunctionDeclaration || node.type == Syntax.FunctionExpression || node.type == Syntax.MethodDefinition || node.type == Syntax.ArrowFunctionExpression); } // ------------------------------------------------------- // 4. Function body for structured parameters. // // function foo({x, y}) { x; y; } // ------------------------------------------------------- function visitFunctionBodyForStructuredParameter(traverse, node, path, state) { var funcNode = path[0]; utils.catchup(funcNode.body.range[0] + 1, state); renderDestructuredComponents(funcNode, state); if (funcNode.rest) { utils.append( restParamVisitors.renderRestParamSetup(funcNode, state), state ); } return true; } function renderDestructuredComponents(funcNode, state) { var destructuredComponents = []; for (var k = 0; k < funcNode.params.length; k++) { var param = funcNode.params[k]; if (isStructuredPattern(param)) { destructuredComponents.push( getDestructuredComponents(param, state) ); state.localScope.tempVarIndex++; } } if (destructuredComponents.length) { utils.append('var ' + destructuredComponents.join(',') + ';', state); } } visitFunctionBodyForStructuredParameter.test = function(node, path, state) { return node.type === Syntax.BlockStatement && isFunctionNode(path[0]); }; exports.visitorList = [ visitStructuredVariable, visitStructuredAssignment, visitStructuredParameter, visitFunctionBodyForStructuredParameter ]; exports.renderDestructuredComponents = renderDestructuredComponents;