jscodeshift
Version:
A toolkit for JavaScript codemods
180 lines (165 loc) • 4.47 kB
JavaScript
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
;
const EventEmitter = require('events').EventEmitter;
const async = require('neo-async');
const fs = require('fs');
const writeFileAtomic = require('write-file-atomic');
const getParser = require('./getParser');
const jscodeshift = require('./core');
let emitter;
let finish;
let notify;
let transform;
let parser;
if (module.parent) {
emitter = new EventEmitter();
emitter.send = (data) => { run(data); };
finish = () => { emitter.emit('disconnect'); };
notify = (data) => { emitter.emit('message', data); };
module.exports = (args) => {
setup(args[0], args[1]);
return emitter;
};
} else {
finish = () => setImmediate(() => process.disconnect());
notify = (data) => { process.send(data); };
process.on('message', (data) => { run(data); });
setup(process.argv[2], process.argv[3]);
}
function prepareJscodeshift(options) {
if (parser) {
return jscodeshift.withParser(parser);
} else if (options.parser) {
return jscodeshift.withParser(getParser(options.parser));
} else {
return jscodeshift;
}
}
function setup(tr, babel) {
if (babel === 'babel') {
require('babel-register')({
babelrc: false,
presets: [
require('babel-preset-es2015'),
require('babel-preset-stage-1'),
],
plugins: [
require('babel-plugin-transform-flow-strip-types'),
]
});
}
const module = require(tr);
transform = typeof module.default === 'function' ?
module.default :
module;
if (module.parser) {
parser = typeof module.parser === 'string' ?
getParser(module.parser) :
module.parser;
}
}
function free() {
notify({action: 'free'});
}
function updateStatus(status, file, msg) {
msg = msg ? file + ' ' + msg : file;
notify({action: 'status', status: status, msg: msg});
}
function empty() {}
function stats(name, quantity) {
quantity = typeof quantity !== 'number' ? 1 : quantity;
notify({action: 'update', name: name, quantity: quantity});
}
function trimStackTrace(trace) {
if (!trace) {
return '';
}
// Remove this file from the stack trace of an error thrown in the transformer
const lines = trace.split('\n');
const result = [];
lines.every(function(line) {
if (line.indexOf(__filename) === -1) {
result.push(line);
return true;
}
});
return result.join('\n');
}
function run(data) {
const files = data.files;
const options = data.options || {};
if (!files.length) {
finish();
return;
}
async.each(
files,
function(file, callback) {
fs.readFile(file, function(err, source) {
if (err) {
updateStatus('error', file, 'File error: ' + err);
callback();
return;
}
source = source.toString();
try {
const jscodeshift = prepareJscodeshift(options);
const out = transform(
{
path: file,
source: source,
},
{
j: jscodeshift,
jscodeshift: jscodeshift,
stats: options.dry ? stats : empty
},
options
);
if (!out || out === source) {
updateStatus(out ? 'nochange' : 'skip', file);
callback();
return;
}
if (options.print) {
console.log(out); // eslint-disable-line no-console
}
if (!options.dry) {
writeFileAtomic(file, out, function(err) {
if (err) {
updateStatus('error', file, 'File writer error: ' + err);
} else {
updateStatus('ok', file);
}
callback();
});
} else {
updateStatus('ok', file);
callback();
}
} catch(err) {
updateStatus(
'error',
file,
'Transformation error\n' + trimStackTrace(err.stack)
);
callback();
}
});
},
function(err) {
if (err) {
updateStatus('error', '', 'This should never be shown!');
}
free();
}
);
}