Worker.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /**
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. 'use strict';
  8. const EventEmitter = require('events').EventEmitter;
  9. const async = require('neo-async');
  10. const fs = require('graceful-fs');
  11. const writeFileAtomic = require('write-file-atomic');
  12. const { DEFAULT_EXTENSIONS } = require('@babel/core');
  13. const getParser = require('./getParser');
  14. const jscodeshift = require('./core');
  15. let emitter;
  16. let finish;
  17. let notify;
  18. let transform;
  19. let parserFromTransform;
  20. if (module.parent) {
  21. emitter = new EventEmitter();
  22. emitter.send = (data) => { run(data); };
  23. finish = () => { emitter.emit('disconnect'); };
  24. notify = (data) => { emitter.emit('message', data); };
  25. module.exports = (args) => {
  26. setup(args[0], args[1]);
  27. return emitter;
  28. };
  29. } else {
  30. finish = () => setImmediate(() => process.disconnect());
  31. notify = (data) => { process.send(data); };
  32. process.on('message', (data) => { run(data); });
  33. setup(process.argv[2], process.argv[3]);
  34. }
  35. function prepareJscodeshift(options) {
  36. const parser = parserFromTransform ||
  37. getParser(options.parser, options.parserConfig);
  38. return jscodeshift.withParser(parser);
  39. }
  40. function setup(tr, babel) {
  41. if (babel === 'babel') {
  42. require('@babel/register')({
  43. babelrc: false,
  44. presets: [
  45. [
  46. require('@babel/preset-env').default,
  47. {targets: {node: true}},
  48. ],
  49. /\.tsx?$/.test(tr) ?
  50. require('@babel/preset-typescript').default :
  51. require('@babel/preset-flow').default,
  52. ],
  53. plugins: [
  54. require('@babel/plugin-proposal-class-properties').default,
  55. ],
  56. extensions: [...DEFAULT_EXTENSIONS, '.ts', '.tsx'],
  57. // By default, babel register only compiles things inside the current working directory.
  58. // https://github.com/babel/babel/blob/2a4f16236656178e84b05b8915aab9261c55782c/packages/babel-register/src/node.js#L140-L157
  59. ignore: [
  60. // Ignore parser related files
  61. /@babel\/parser/,
  62. /\/flow-parser\//,
  63. /\/recast\//,
  64. /\/ast-types\//,
  65. ],
  66. });
  67. }
  68. const module = require(tr);
  69. transform = typeof module.default === 'function' ?
  70. module.default :
  71. module;
  72. if (module.parser) {
  73. parserFromTransform = typeof module.parser === 'string' ?
  74. getParser(module.parser) :
  75. module.parser;
  76. }
  77. }
  78. function free() {
  79. notify({action: 'free'});
  80. }
  81. function updateStatus(status, file, msg) {
  82. msg = msg ? file + ' ' + msg : file;
  83. notify({action: 'status', status: status, msg: msg});
  84. }
  85. function report(file, msg) {
  86. notify({action: 'report', file, msg});
  87. }
  88. function empty() {}
  89. function stats(name, quantity) {
  90. quantity = typeof quantity !== 'number' ? 1 : quantity;
  91. notify({action: 'update', name: name, quantity: quantity});
  92. }
  93. function trimStackTrace(trace) {
  94. if (!trace) {
  95. return '';
  96. }
  97. // Remove this file from the stack trace of an error thrown in the transformer
  98. const lines = trace.split('\n');
  99. const result = [];
  100. lines.every(function(line) {
  101. if (line.indexOf(__filename) === -1) {
  102. result.push(line);
  103. return true;
  104. }
  105. });
  106. return result.join('\n');
  107. }
  108. function run(data) {
  109. const files = data.files;
  110. const options = data.options || {};
  111. if (!files.length) {
  112. finish();
  113. return;
  114. }
  115. async.each(
  116. files,
  117. function(file, callback) {
  118. fs.readFile(file, function(err, source) {
  119. if (err) {
  120. updateStatus('error', file, 'File error: ' + err);
  121. callback();
  122. return;
  123. }
  124. source = source.toString();
  125. try {
  126. const jscodeshift = prepareJscodeshift(options);
  127. const out = transform(
  128. {
  129. path: file,
  130. source: source,
  131. },
  132. {
  133. j: jscodeshift,
  134. jscodeshift: jscodeshift,
  135. stats: options.dry ? stats : empty,
  136. report: msg => report(file, msg),
  137. },
  138. options
  139. );
  140. if (!out || out === source) {
  141. updateStatus(out ? 'nochange' : 'skip', file);
  142. callback();
  143. return;
  144. }
  145. if (options.print) {
  146. console.log(out); // eslint-disable-line no-console
  147. }
  148. if (!options.dry) {
  149. writeFileAtomic(file, out, function(err) {
  150. if (err) {
  151. updateStatus('error', file, 'File writer error: ' + err);
  152. } else {
  153. updateStatus('ok', file);
  154. }
  155. callback();
  156. });
  157. } else {
  158. updateStatus('ok', file);
  159. callback();
  160. }
  161. } catch(err) {
  162. updateStatus(
  163. 'error',
  164. file,
  165. 'Transformation error ('+ err.message.replace(/\n/g, ' ') + ')\n' + trimStackTrace(err.stack)
  166. );
  167. callback();
  168. }
  169. });
  170. },
  171. function(err) {
  172. if (err) {
  173. updateStatus('error', '', 'This should never be shown!');
  174. }
  175. free();
  176. }
  177. );
  178. }