Skip to content

Instantly share code, notes, and snippets.

@chjj
Created August 18, 2017 02:21

Revisions

  1. chjj created this gist Aug 18, 2017.
    156 changes: 156 additions & 0 deletions rewrite.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,156 @@
    'use strict';

    const path = require('path');
    const fs = require('./lib/utils/fs');

    async function findFiles(dir, match, ignore, files) {
    const list = await fs.readdir(dir);

    for (const name of list) {
    if (ignore.has(name))
    continue;

    const file = path.join(dir, name);
    const stat = await fs.lstat(file);

    if (stat.isSymbolicLink())
    continue;

    if (stat.isDirectory()) {
    await findFiles(file, match, ignore, files);
    continue;
    }

    if (!match.test(name))
    continue;

    files.push(file);
    }

    return files;
    }

    async function getFiles(dir, ignore, match) {
    const set = new Set();

    if (!ignore)
    ignore = [];

    for (const name of ignore)
    set.add(name);

    if (!match)
    match = /^[^\0]+\.js$/;

    return await findFiles(dir, match, set, []);
    }

    function indentLines(text, spaces) {
    const spacing = Array(spaces + 1).join(' ');
    const lines = text.split('\n');
    const indent = [];

    for (let line of lines) {
    if (line.length !== 0)
    line = spacing + line;
    indent.push(line);
    }

    return indent.join('\n');
    }

    function processFile(data) {
    let mod = false;

    const fnToClass =
    /\n(\/\*\*(?:\n \*[^\n]*)+?\n \*\/\s+)?function ([A-Z]\w*)\(([^)]*)\) \{([^\0]+?\n)\};?(?:\s*Object\.setPrototypeOf\([^,]+, ([^.]+)\.prototype\);\n)?/g;

    data = data.replace(fnToClass, (_, comment, name, args, body, parent) => {
    mod = true;

    if (!comment)
    comment = '';

    if (!parent)
    parent = '';

    comment = indentLines(comment, 1);
    body = indentLines(body, 1);

    if (parent)
    parent = ` extends ${parent}`;

    return `\nclass ${name}${parent} {\n${comment} constructor(${args}) {${body} }\n}`;
    });

    const methodToClass =
    /\n(\/\*\*(?:\n \*[^\n]*)+?\n \*\/\s+)?[A-Z]\w*(\.prototype)?\.\w+ = (async |)function ([^{]+)\{([^\0]*?\n)\};/g;

    data = data.replace(methodToClass, (_, comment, proto, asyn, nameargs, body) => {
    mod = true;

    if (!comment)
    comment = '';

    comment = indentLines(comment, 1);
    body = indentLines(body, 1);

    let stat = '';

    if (!proto)
    stat = 'static ';

    return `\n${comment} ${stat}${asyn}${nameargs}{${body} }`;
    });

    const ctorToSuper = /[A-Z]\w*\.call\(this(?:, ([^)]+))?\);/g;

    data = data.replace(ctorToSuper, (_, args) => {
    mod = true;

    if (!args)
    args = '';

    return `super(${args});`;
    });

    const wrapper =
    /\n *if \(!\(this instanceof ([A-Z]\w*)\)\)\n *return new \1[^\n]+\n/g;

    data = data.replace(wrapper, () => {
    mod = true;
    return '';
    });

    if (!mod)
    return null;

    return data;
    }

    async function rewriteFile(filename) {
    let data = await fs.readFile(filename, 'utf8');

    data = processFile(data);

    if (data === null)
    return;

    await fs.writeFile(filename, data);
    }

    async function rewriteFiles(files) {
    const jobs = files.map(file => rewriteFile(file));
    return await Promise.all(jobs);
    }

    (async () => {
    const target = process.argv[2] || '.';
    const cwd = path.resolve(process.cwd(), target);
    const ignore = ['node_modules', 'vendor'];
    const files = await getFiles(cwd, ignore);
    await rewriteFiles(files);
    })().catch((err) => {
    console.error(err.stack);
    process.exit(1);
    });