UNPKG

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