rung-tools/rung-cli

View on GitHub
src/module.js

Summary

Maintainability
A
1 hr
Test Coverage
import fs from 'fs';
import { all, promisify, reject, resolve } from 'bluebird';
import {
    T,
    cond,
    curry,
    endsWith,
    fromPairs,
    map,
    merge,
    path,
    startsWith,
    uniq
} from 'ramda';
import { transform } from 'babel-core';
import { compileES6 } from './compiler';

const readFile = promisify(fs.readFile);

/**
 * Ensures a piece of source has no import or require declarations
 *
 * @param {String} source
 * @return {Promise}
 */
export const ensureNoImports = curry((filename, source) => {
    const { modules } = inspect(source);
    return modules.length
        ? reject(new Error(`Cannot import modules on autocomplete files (${filename})`))
        : resolve();
});

/**
 * Traverses the AST, getting the imported modules and compile to pairs
 *
 * @author Marcelo Haskel Camargo
 * @param {String} source
 * @return {Promise<String[][]>}
 */
export function compileModulesFromSource(source) {
    const { modules } = inspect(source);
    return all(modules.filter(startsWith('./')).map(compileModule));
}

/**
 * Compiles a module to a pair with (filename :: string, source :: string)
 *
 * @author Marcelo Haskell Camargo
 * @param {String} module
 * @return {Promise}
 */
export function compileModule(module) {
    const extensionIs = extension => ~endsWith(extension, module);

    return readFile(module, 'utf-8')
        .then(cond([
            [extensionIs('.js'), compileES6],
            [extensionIs('.json'), JSON.parse & JSON.stringify],
            [T, ~reject(new Error(`Unknown module loader for file ${module}`))]]))
        .then(source => [module, source]);
}

/**
 * Evaluates a list of pairs of modules. modules :: [(String, String)]
 *
 * @author Marcelo Haskell Camargo
 * @param {NodeVM} vm - Virtual machine instance to run
 * @param {String[][]} modules pairs, with (name :: string, source :: string)
 */
export const evaluateModules = (vm, modules) => fromPairs(map(([module, source]) => {
    // JSON doesn't need to run on VM. We can directly parse it
    const convertToBytecode = cond([
        [endsWith('.json'), ~JSON.parse(source)],
        [endsWith('.js'), vm.run(source, _)],
        [T, module => {
            throw new Error(`Unknown file type for ${module}`);
        }]
    ]);

    return [module, convertToBytecode(module)];
}, modules));

/**
 * Inspects a JS source and returns processed information, such as ES5 code,
 * the ast, source map and the used modules
 *
 * @author Marcelo Haskell Camargo
 * @param {String} source - ES6 source
 * @return {String[]}
 */
export function inspect(source) {
    const modules = [];
    const result = transform(source, {
        comments: false,
        compact: true,
        presets: ['es2015', 'react'],
        plugins: [
            ['transform-react-jsx', { pragma: '__render__' }],
            [~({
                visitor: {
                    ImportDeclaration({ node }) {
                        modules.push(node.source.value);
                    },
                    CallExpression({ node }) {
                        // Find and extract require(module)
                        const callee = path(['callee', 'name'], node);

                        if (callee === 'require') {
                            const [moduleNode] = node.arguments;
                            const isLiteralModule = moduleNode && moduleNode.type === 'StringLiteral';

                            if (isLiteralModule) {
                                modules.push(moduleNode.value);
                            }
                        }
                    }
                }
            })]
        ]
    });

    return merge(result, { modules: uniq(modules) });
}