1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | 'use strict';
|
10 |
|
11 | const EventEmitter = require('events').EventEmitter;
|
12 |
|
13 | const async = require('neo-async');
|
14 | const fs = require('graceful-fs');
|
15 | const writeFileAtomic = require('write-file-atomic');
|
16 | const { DEFAULT_EXTENSIONS } = require('@babel/core');
|
17 | const getParser = require('./getParser');
|
18 |
|
19 | const jscodeshift = require('./core');
|
20 |
|
21 | let presetEnv;
|
22 | try {
|
23 | presetEnv = require('@babel/preset-env');
|
24 | } catch (_) {}
|
25 |
|
26 | let emitter;
|
27 | let finish;
|
28 | let notify;
|
29 | let transform;
|
30 | let parserFromTransform;
|
31 |
|
32 | if (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 |
|
48 | function prepareJscodeshift(options) {
|
49 | const parser = parserFromTransform ||
|
50 | getParser(options.parser, options.parserConfig);
|
51 | return jscodeshift.withParser(parser);
|
52 | }
|
53 |
|
54 | function 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 |
|
81 |
|
82 | ignore: [
|
83 |
|
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 |
|
103 | function free() {
|
104 | notify({action: 'free'});
|
105 | }
|
106 |
|
107 | function updateStatus(status, file, msg) {
|
108 | msg = msg ? file + ' ' + msg : file;
|
109 | notify({action: 'status', status: status, msg: msg});
|
110 | }
|
111 |
|
112 | function report(file, msg) {
|
113 | notify({action: 'report', file, msg});
|
114 | }
|
115 |
|
116 | function empty() {}
|
117 |
|
118 | function stats(name, quantity) {
|
119 | quantity = typeof quantity !== 'number' ? 1 : quantity;
|
120 | notify({action: 'update', name: name, quantity: quantity});
|
121 | }
|
122 |
|
123 | function trimStackTrace(trace) {
|
124 | if (!trace) {
|
125 | return '';
|
126 | }
|
127 |
|
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 |
|
139 | function 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);
|
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 | }
|