UNPKG

cordite-cli

Version:

a command line tool for accessing a Corda node running cordite cordapps or braid

146 lines (137 loc) 4.74 kB
/* * Copyright 2018, Cordite Foundation. * * 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. */ const walk = require('esprima-walk').walkAddParent; const recast = require('recast'); const builders = recast.types.builders; const namedTypes = recast.types.namedTypes; function rewriteProgram(ast) { const rewritten = ast.body.map(statement => { let resultStatement = null; let matchedNode = null; walk(statement, node => { if (matchTemplate(node, { type: "CallExpression" })) { matchedNode = node; } }); if (matchedNode !== null && isTopLevelCallExpression(matchedNode)) { resultStatement = rewriteCallExpressionStatement(matchedNode); } else { resultStatement = statement; } return resultStatement; }); const rewrittenProgram = builders.program(rewritten) return rewrittenProgram; } /** * check if `obj` includes all fields and values of `template` * @param {object} obj * @param {object} template */ function matchTemplate(obj, template) { for (const propertyName in template) { if (!obj.hasOwnProperty(propertyName)) { return false; } if (obj.hasOwnProperty(propertyName) && obj[propertyName] !== template[propertyName]) { return false; } } return true; } /** * This method is called when we discover a call site in a statement being processed * It's purpose is to ensure that the result of the call is a Promise * which is then piped through a sequence of `.then()` for each ancetor of the call site * * examples * * 1 - Synchronous calls * * given * mySyncFn().property * * it will generate * Promise.resolve(mySyncFn()) * .then(result => result.property) * * 2 - Asynchronous calls * * given * myAsyncFn().property * * it will generate * Promise.resolve(myAsyncFn()) * .then(result => result.property) * * * Both examples are exactly identical because of the behaviour of Promise.resolve * @param {CallExpression} callExpression */ function rewriteCallExpressionStatement(callExpression) { let expression = builders.callExpression( builders.memberExpression( builders.identifier('Promise'), builders.identifier('resolve') ), [callExpression] ) expression = buildParentHierarchyAsPromiseChain(expression, callExpression); return builders.expressionStatement(expression); } function isTopLevelCallExpression(node) { if (!node) return true; if (namedTypes.ArrowFunctionExpression.check(node) || namedTypes.BlockStatement.check(node) || namedTypes.FunctionDeclaration.check(node)) return false; return isTopLevelCallExpression(node.parent); } function buildParentHierarchyAsPromiseChain(expression, currentNode) { if (!currentNode.parent || (namedTypes.ExpressionStatement.check(currentNode.parent))) { // stop processing } else { let node = currentNode.parent; const resultId = builders.identifier('result'); if (!namedTypes.AssignmentExpression.check(node)) { node.object = resultId; } else { node.right = resultId; } node = builders.returnStatement( currentNode.parent ) expression = builders.callExpression( builders.memberExpression(expression, builders.identifier('then')), [ builders.arrowFunctionExpression([ resultId ], builders.blockStatement([ node ])) ] ) expression = buildParentHierarchyAsPromiseChain(expression, currentNode.parent); } return expression; } function rewrite(cmd) { const ast = recast.parse(cmd); const rewrite = rewriteProgram(ast.program); ast.program = rewrite; const script = recast.print(ast).code; return script; } module.exports = rewrite;