/** * Takes an object, and recursively converts it to a Babel AST literal node. This handles strings, * numbers, booleans, basic objects, and Arrays. This cannot handle circular references. * @param obj - The object to convert. * @returns A babel AST node. */ function objectToLiteral(obj: any): any { if ( typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || obj === null ) { // abc, 123, true, false, null return t.literal(obj); } else if (obj === undefined) { // undefined return t.identifier('undefined'); } else if (Array.isArray(obj)) { // [...] return t.arrayExpression(obj.map(elem => objectToLiteral(elem))); } else if (obj instanceof Map) { return t.newExpression( t.identifier('Map'), obj.size // new Map([...]) ? [objectToLiteral(Array.from(obj.entries()))] // new Map() : null ); } else if (typeof obj === 'object') { // {a: 1, b: 2} return t.objectExpression( Object.keys(obj).map(key => t.property('init', t.identifier(key), objectToLiteral(obj[key])) ) ); } throw new Error(`Cannot convert unknown type ${typeof obj} to literal.`); }
/** * Takes an object, and recursively converts it to a Babel AST literal node. This handles strings, * numbers, booleans, basic objects, and Arrays. This cannot handle circular references. * @param obj - The object to convert. * @returns A babel AST node. */ function objectToLiteral(obj: any): any { if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') { return t.literal(obj); } else if (obj instanceof Array) { return t.arrayExpression(obj.map(elem => objectToLiteral(elem))); } else if (typeof obj === 'object') { return t.objectExpression(Object.keys(obj).map(key => { return t.Property('init', t.identifier(key), objectToLiteral(obj[key])); })); } throw new Error(`Cannot convert unkown type ${typeof obj} to literal.`); }
/** * Helper function that generates a remote constructor proxy. * @param className - The name of the interface. * @param constructorArgs - The types of the arguments to the constructor. * @returns A MethodDefinition node that can be added to a ClassBody. */ function generateRemoteConstructor(className: string, constructorArgs: Array<Parameter>) { // arg0, .... argN const args = constructorArgs.map((arg, i) => t.identifier(`arg${i}`)); // [arg0, ... argN] const argsArray = t.arrayExpression(args); // [argType0, ... argTypeN] const argTypes = t.arrayExpression(constructorArgs.map(objectToLiteral)); // client.createRemoteObject(className, this, [arg0, arg1, .... argN], [argType0 ... argTypeN]) const rpcCallExpression = t.callExpression( createRemoteObjectExpression, [ t.literal(className), t.thisExpression(), argsArray, argTypes, ] ); // constructor(arg0, arg1, ..., argN) { ... } const constructor = t.FunctionExpression(null, args, t.blockStatement([rpcCallExpression])); return t.methodDefinition(t.identifier('constructor'), constructor, 'constructor', false, false); }