cordite-cli
Version:
a command line tool for accessing a Corda node running cordite cordapps or braid
146 lines (137 loc) • 4.74 kB
JavaScript
/*
* 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;