flow-upgrade 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +10 -0
  2. package/dist/Styled.js +12 -14
  3. package/dist/Types.js +21 -1
  4. package/dist/bin/runSpecificCodemod.js +117 -0
  5. package/dist/bin/upgrade.js +75 -0
  6. package/dist/codemodUtils/getClassMemberName.js +27 -0
  7. package/dist/codemodUtils/replaceMethodWithArrowProp.js +60 -0
  8. package/dist/codemods/collapseObjectInitialization.js +250 -0
  9. package/dist/codemods/convertImplicitInexactObjectTypes.js +35 -0
  10. package/dist/codemods/removeAnnotationsInDestructuring.js +40 -0
  11. package/dist/codemods/removeDuplicateClassProperties.js +268 -0
  12. package/dist/findFlowFiles.js +136 -158
  13. package/dist/runCodemods.js +36 -0
  14. package/dist/upgrade.js +127 -153
  15. package/dist/upgrades/0.170.0/index.js +25 -0
  16. package/dist/upgrades/0.176.0/index.js +25 -0
  17. package/dist/upgrades/0.84.0/index.js +25 -0
  18. package/dist/upgrades/index.js +29 -0
  19. package/dist/utils/redirectConsole.js +59 -0
  20. package/package.json +29 -21
  21. package/dist/codemods/ReactUtils.js +0 -140
  22. package/dist/codemods/createAggregateCodemod.js +0 -36
  23. package/dist/codemods/runCodemods.js +0 -54
  24. package/dist/index.js +0 -38
  25. package/dist/upgrades/0.53.0/ReactComponentExplicitTypeArgs/codemod.js +0 -154
  26. package/dist/upgrades/0.53.0/ReactComponentExplicitTypeArgs/index.js +0 -75
  27. package/dist/upgrades/0.53.0/ReactComponentSimplifyTypeArgs/codemod.js +0 -65
  28. package/dist/upgrades/0.53.0/ReactComponentSimplifyTypeArgs/index.js +0 -52
  29. package/dist/upgrades/0.53.0/ReactUtilityTypes/codemod.js +0 -156
  30. package/dist/upgrades/0.53.0/ReactUtilityTypes/index.js +0 -59
  31. package/dist/upgrades/0.84.0/ExplicitInexactObjectSyntax/codemod.js +0 -18
  32. package/dist/upgrades/0.84.0/ExplicitInexactObjectSyntax/index.js +0 -25
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _getClassMemberName = require("../codemodUtils/getClassMemberName");
9
+
10
+ var _replaceMethodWithArrowProp = require("../codemodUtils/replaceMethodWithArrowProp");
11
+
12
+ var _Types = require("../Types");
13
+
14
+ /**
15
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
16
+ *
17
+ * This source code is licensed under the MIT license found in the
18
+ * LICENSE file in the root directory of this source tree.
19
+ *
20
+ * @format
21
+ *
22
+ */
23
+ function getKind(member) {
24
+ switch (member.type) {
25
+ case 'MethodDefinition':
26
+ switch (member.kind) {
27
+ case 'get':
28
+ return 'Getter';
29
+
30
+ case 'set':
31
+ return 'Setter';
32
+
33
+ case 'method':
34
+ case 'constructor':
35
+ return 'Method';
36
+ }
37
+
38
+ throw new Error(`Unexpected method kind: ${member.kind}`);
39
+
40
+ case 'PropertyDefinition':
41
+ return 'Property';
42
+ }
43
+
44
+ throw new Error(`Unexpected member type: ${member.type}`);
45
+ }
46
+
47
+ function findDuplicates(membersMap) {
48
+ const toDelete = [];
49
+
50
+ for (const [, members] of membersMap) {
51
+ if (members.length === 1) {
52
+ continue;
53
+ }
54
+
55
+ let lastMember = null;
56
+
57
+ for (const member of members) {
58
+ const kind = getKind(member);
59
+
60
+ if (lastMember == null) {
61
+ lastMember = {
62
+ kind,
63
+ member
64
+ };
65
+ continue;
66
+ } // handle the valid cases
67
+
68
+
69
+ switch (lastMember.kind) {
70
+ case 'GetterSetter':
71
+ if (kind === 'Getter') {
72
+ const oldGetter = lastMember.getter;
73
+ lastMember.getter = member;
74
+ toDelete.push(oldGetter);
75
+ continue;
76
+ } else if (kind === 'Setter') {
77
+ const oldSetter = lastMember.setter;
78
+ lastMember.setter = member;
79
+ toDelete.push(oldSetter);
80
+ continue;
81
+ }
82
+
83
+ break;
84
+
85
+ case 'Getter':
86
+ if (kind === 'Setter') {
87
+ lastMember = {
88
+ kind: 'GetterSetter',
89
+ getter: lastMember.member,
90
+ setter: member
91
+ };
92
+ continue;
93
+ }
94
+
95
+ break;
96
+
97
+ case 'Setter':
98
+ if (kind === 'Getter') {
99
+ lastMember = {
100
+ kind: 'GetterSetter',
101
+ getter: member,
102
+ setter: lastMember.member
103
+ };
104
+ continue;
105
+ }
106
+
107
+ break;
108
+ } // overwrite and delete the old member as it's incompatible
109
+
110
+
111
+ if (lastMember.kind === 'GetterSetter') {
112
+ toDelete.push(lastMember.getter);
113
+ toDelete.push(lastMember.setter);
114
+ } else {
115
+ toDelete.push(lastMember.member);
116
+ }
117
+
118
+ lastMember = {
119
+ kind,
120
+ member
121
+ };
122
+ }
123
+ }
124
+
125
+ return toDelete;
126
+ }
127
+
128
+ var _default = (0, _Types.codemod)({
129
+ title: 'Remove Duplicate Class Properties',
130
+ description: 'Removes useless duplicate class properties and fixes bad constructor binding in those classes',
131
+ transform: context => {
132
+ let classStack = null;
133
+ return {
134
+ // find and delete duplicated members
135
+ 'ClassDeclaration, ClassExpression'(node) {
136
+ const staticMembers = new Map();
137
+ const instanceMembers = new Map();
138
+
139
+ for (const member of node.body.body) {
140
+ const membersMap = member.static ? staticMembers : instanceMembers;
141
+ const name = (0, _getClassMemberName.getClassMemberName)(member);
142
+
143
+ if (name == null) {
144
+ // unsupported computed member
145
+ continue;
146
+ }
147
+
148
+ const existingMember = membersMap.get(name);
149
+
150
+ if (existingMember == null) {
151
+ membersMap.set(name, [member]);
152
+ } else {
153
+ existingMember.push(member);
154
+ }
155
+ }
156
+
157
+ const toDelete = new Set(findDuplicates(staticMembers).concat(findDuplicates(instanceMembers)));
158
+
159
+ if (toDelete.size === 0) {
160
+ return;
161
+ } // mark members for deletion
162
+
163
+
164
+ for (const memberToDelete of toDelete) {
165
+ var _memberToDelete$value;
166
+
167
+ if (memberToDelete.type === 'PropertyDefinition' && ((_memberToDelete$value = memberToDelete.value) === null || _memberToDelete$value === void 0 ? void 0 : _memberToDelete$value.type) === 'CallExpression') {
168
+ console.log(context.buildCodeFrame(memberToDelete.value, 'Deleted a property with a CallExpression value. You should double check this was safe.'));
169
+ }
170
+
171
+ context.removeNode(memberToDelete);
172
+ } // we need the final list of instance members so we can
173
+ // fix up rebinding later
174
+
175
+
176
+ const newInstanceMembers = new Map();
177
+
178
+ for (const member of node.body.body) {
179
+ if (toDelete.has(member)) {
180
+ continue;
181
+ }
182
+
183
+ if (member.static) {
184
+ continue;
185
+ }
186
+
187
+ const name = (0, _getClassMemberName.getClassMemberName)(member);
188
+
189
+ if (name == null) {
190
+ continue;
191
+ }
192
+
193
+ const kind = getKind(member);
194
+ newInstanceMembers.set(name, {
195
+ kind,
196
+ member
197
+ });
198
+ } // push the stack
199
+
200
+
201
+ classStack = {
202
+ current: node,
203
+ instanceMembers: newInstanceMembers,
204
+ parent: classStack
205
+ };
206
+ },
207
+
208
+ // remove unnecessary .bind in constructor
209
+ 'MethodDefinition[kind = "constructor"] ExpressionStatement > AssignmentExpression[operator = "="] > MemberExpression[object.type = "ThisExpression"].left'(node) {
210
+ if (!classStack) {
211
+ return;
212
+ }
213
+
214
+ const instanceMembers = classStack.instanceMembers;
215
+
216
+ if (node.property.type !== 'Identifier') {
217
+ return;
218
+ }
219
+
220
+ const subjectName = node.property.name;
221
+ const member = instanceMembers.get(subjectName); // the subject member must be a method
222
+
223
+ if ((member === null || member === void 0 ? void 0 : member.kind) !== 'Method') {
224
+ return;
225
+ }
226
+
227
+ const assignmentExpr = node.parent;
228
+ const exprStatement = assignmentExpr.parent;
229
+
230
+ if (assignmentExpr.type !== 'AssignmentExpression' || exprStatement.type !== 'ExpressionStatement') {
231
+ throw new Error('this cannot happen');
232
+ }
233
+
234
+ const right = assignmentExpr.right; // we're looking for `this.thing = this.thing.bind(this)`
235
+
236
+ if (right.type !== 'CallExpression' || right.arguments.length !== 1 || right.arguments[0].type !== 'ThisExpression' || right.callee.type !== 'MemberExpression' || right.callee.computed || right.callee.property.type !== 'Identifier' || right.callee.property.name !== 'bind' || right.callee.object.type !== 'MemberExpression' || right.callee.object.computed || right.callee.object.object.type !== 'ThisExpression' || right.callee.object.property.type !== 'Identifier' || right.callee.object.property.name !== subjectName) {
237
+ console.log(context.buildCodeFrame(right, `${subjectName} was reassigned, but not to a bind. You can probably manually correct this.`));
238
+ return;
239
+ } // remove the bind assignment and...
240
+
241
+
242
+ context.removeStatement(exprStatement);
243
+ const method = member.member;
244
+
245
+ if (method.type !== 'MethodDefinition') {
246
+ throw new Error('this cannot happen');
247
+ } // convert the method to an arrow function
248
+
249
+
250
+ (0, _replaceMethodWithArrowProp.replaceMethodWithArrowProp)(context, method, subjectName);
251
+ },
252
+
253
+ // pop the stack
254
+ 'ClassDeclaration, ClassExpression:exit'(node) {
255
+ var _classStack;
256
+
257
+ if (classStack && ((_classStack = classStack) === null || _classStack === void 0 ? void 0 : _classStack.current) === node) {
258
+ var _classStack2;
259
+
260
+ classStack = (_classStack2 = classStack) === null || _classStack2 === void 0 ? void 0 : _classStack2.parent;
261
+ }
262
+ }
263
+
264
+ };
265
+ }
266
+ });
267
+
268
+ exports.default = _default;
@@ -1,181 +1,159 @@
1
- 'use strict';
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.findFlowFiles = findFlowFiles;
7
+ exports.findFlowFilesWithSpinner = findFlowFilesWithSpinner;
8
+
9
+ var _path = _interopRequireDefault(require("path"));
10
+
11
+ var _fsExtra = _interopRequireDefault(require("fs-extra"));
12
+
13
+ var _ora = _interopRequireDefault(require("ora"));
14
+
15
+ var _chalk = _interopRequireDefault(require("chalk"));
16
+
17
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
2
18
 
3
19
  /**
20
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
21
+ *
22
+ * This source code is licensed under the MIT license found in the
23
+ * LICENSE file in the root directory of this source tree.
24
+ *
4
25
  * @format
5
26
  *
6
27
  */
7
28
 
8
- const path = require('path');
9
- const fs = require('graceful-fs');
10
-
11
29
  /**
12
30
  * How many bytes we should look at for the Flow pragma.
13
31
  */
14
32
  const PRAGMA_BYTES = 5000;
15
-
16
33
  /**
17
34
  * Finds all of the Flow files in the provided directory as efficiently as
18
35
  * possible.
19
36
  */
20
- // If we use promises then Node.js will quickly run out of memory on
21
- // large codebases. Instead we use the callback API.
22
- module.exports = function findFlowFiles(rootDirectory, options) {
23
- return new Promise((_resolve, _reject) => {
24
- // Tracks whether or not we have rejected our promise.
25
- let rejected = false;
26
- // How many asynchronous tasks are waiting at the moment.
27
- let waiting = 0;
28
- // All the valid file paths that we have found.
29
- const filePaths = [];
30
-
31
- // Begin the recursion!
32
- processDirectory(rootDirectory);
33
-
34
- /**
35
- * Process a directory by looking at all of its entries and recursing
36
- * through child directories as is appropriate.
37
- */
38
- function processDirectory(directory) {
39
- // If we were rejected then we should not continue.
40
- if (rejected === true) {
41
- return;
42
- }
43
- // We are now waiting on this asynchronous task.
44
- waiting++;
45
- // Read the directory...
46
- fs.readdir(directory, (error, fileNames) => {
47
- if (error) {
48
- return reject(error);
49
- }
50
- // Process every file name that we got from reading the directory.
51
- for (let i = 0; i < fileNames.length; i++) {
52
- processFilePath(directory, fileNames[i]);
53
- }
54
- // We are done with this async task.
55
- done();
56
- });
57
- }
58
37
 
59
- /**
60
- * Process a directory file path by seeing if it is a directory and either
61
- * recursing or adding it to filePaths.
62
- */
63
- function processFilePath(directory, fileName) {
64
- // If we were rejected then we should not continue.
65
- if (rejected === true) {
66
- return;
38
+ async function findFlowFiles({
39
+ includeNonAtFlow,
40
+ rootDirectory
41
+ }) {
42
+ // All the valid file paths that we have found.
43
+ const filePaths = []; // Begin the recursion!
44
+
45
+ await processDirectory(rootDirectory);
46
+ return filePaths;
47
+ /**
48
+ * Process a directory by looking at all of its entries and recursing
49
+ * through child directories as is appropriate.
50
+ */
51
+
52
+ async function processDirectory(directory) {
53
+ // Read the directory...
54
+ const fileNames = await _fsExtra.default.readdir(directory); // Process every file name that we got from reading the directory.
55
+
56
+ await Promise.all(fileNames.map(fileName => processFilePath(directory, fileName)));
57
+ }
58
+ /**
59
+ * Process a directory file path by seeing if it is a directory and either
60
+ * recursing or adding it to filePaths.
61
+ */
62
+
63
+
64
+ async function processFilePath(directory, fileName) {
65
+ // Get the file file path for this file.
66
+ const filePath = _path.default.join(directory, fileName); // Get the stats for the file.
67
+
68
+
69
+ const stats = await _fsExtra.default.lstat(filePath); // If this is a directory...
70
+
71
+ if (stats.isDirectory()) {
72
+ // ...and it is not an ignored directory...
73
+ if (fileName !== 'node_modules' && fileName !== 'flow-typed' && fileName !== '__flowtests__') {
74
+ // ...then recursively process the directory.
75
+ await processDirectory(filePath);
67
76
  }
68
- // We are now waiting on this asynchronous task.
69
- waiting++;
70
- // Get the file file path for this file.
71
- const filePath = path.join(directory, fileName);
72
- // Get the stats for the file.
73
- fs.lstat(filePath, (error, stats) => {
74
- if (error) {
75
- return reject(error);
76
- }
77
- // If this is a directory...
78
- if (stats.isDirectory()) {
79
- // ...and it is not an ignored directory...
80
- if (fileName !== 'node_modules' && fileName !== 'flow-typed' && fileName !== '__flowtests__') {
81
- // ...then recursively process the directory.
82
- processDirectory(filePath);
83
- }
84
- } else if (stats.isFile()) {
85
- // Otherwise if this is a JavaScript/JSX file and it is not an ignored
86
- // JavaScript file...
87
- const fileIsJsOrJsx = /\.jsx?$/.test(fileName);
88
- const fileIsIgnored = fileName.endsWith('-flowtest.js');
89
- if (fileIsJsOrJsx && !fileIsIgnored) {
90
- // Then process the file path as JavaScript.
91
- processJavaScriptFilePath(filePath, stats.size);
92
- }
93
- // If this is a Flow file then we don't need to check the file pragma
94
- // and can add the file to our paths immediately.
95
- if (fileName.endsWith('.flow')) {
96
- filePaths.push(filePath);
97
- }
98
- }
99
- // We are done with this async task
100
- done();
101
- });
102
- }
77
+ } else if (stats.isFile()) {
78
+ // Otherwise if this is a JavaScript/JSX file and it is not an ignored
79
+ // JavaScript file...
80
+ const fileIsJsOrJsx = /\.jsx?$/.test(fileName);
81
+ const fileIsIgnored = fileName.endsWith('-flowtest.js');
82
+
83
+ if (fileIsJsOrJsx && !fileIsIgnored) {
84
+ // Then process the file path as JavaScript.
85
+ await processJavaScriptFilePath(filePath, stats.size);
86
+ } // If this is a Flow file then we don't need to check the file pragma
87
+ // and can add the file to our paths immediately.
103
88
 
104
- /**
105
- * Check if a file path really is a Flow file by looking for the
106
- * header pragma.
107
- */
108
- function processJavaScriptFilePath(filePath, fileByteSize) {
109
- // If `all` was configured then we don't need to check for the Flow
110
- // header pragma.
111
- if (options.all) {
89
+
90
+ if (fileName.endsWith('.flow')) {
112
91
  filePaths.push(filePath);
113
- return;
114
- }
115
- // If we were rejected then we should not continue.
116
- if (rejected === true) {
117
- return;
118
92
  }
119
- // We are now waiting on this asynchronous task.
120
- waiting++;
121
- // Open the file path.
122
- fs.open(filePath, 'r', (error, file) => {
123
- if (error) {
124
- return reject(error);
125
- }
126
- // Get the smaller of our pragma chars constant and the file byte size.
127
- const bytes = Math.min(PRAGMA_BYTES, fileByteSize);
128
- // Create the buffer we will read to.
129
- const buffer = new Buffer(bytes);
130
- // Read a set number of bytes from the file.
131
- fs.read(file, buffer, 0, bytes, 0, error => {
132
- if (error) {
133
- return reject(error);
134
- }
135
- // If the buffer has the pragma then add the file path to our
136
- // final file paths array.
137
- if (buffer.includes('@flow')) {
138
- filePaths.push(filePath);
139
- }
140
- // Close the file.
141
- fs.close(file, error => {
142
- if (error) {
143
- return reject(error);
144
- }
145
- // We are done with this async task
146
- done();
147
- });
148
- });
149
- });
150
93
  }
94
+ }
95
+ /**
96
+ * Check if a file path really is a Flow file by looking for the
97
+ * header pragma.
98
+ */
151
99
 
152
- /**
153
- * Our implementation of resolve that will only actually resolve if we are
154
- * done waiting everywhere.
155
- */
156
- function done() {
157
- // We don't care if we were rejected.
158
- if (rejected === true) {
159
- return;
160
- }
161
- // Decrement the number of async tasks we are waiting on.
162
- waiting--;
163
- // If we are finished waiting then we want to resolve our promise.
164
- if (waiting <= 0) {
165
- if (waiting === 0) {
166
- _resolve(filePaths);
167
- } else {
168
- reject(new Error(`Expected a positive number: ${waiting}`));
169
- }
170
- }
171
- }
172
100
 
173
- /**
174
- * Our implementation of reject that also sets `rejected` to false.
175
- */
176
- function reject(error) {
177
- rejected = true;
178
- _reject(error);
101
+ async function processJavaScriptFilePath(filePath, fileByteSize) {
102
+ // If `all` was configured then we don't need to check for the Flow
103
+ // header pragma.
104
+ if (includeNonAtFlow) {
105
+ filePaths.push(filePath);
106
+ return;
107
+ } // Open the file path.
108
+
109
+
110
+ const file = await _fsExtra.default.open(filePath, 'r'); // Get the smaller of our pragma chars constant and the file byte size.
111
+
112
+ const bytes = Math.min(PRAGMA_BYTES, fileByteSize); // Create the buffer we will read to.
113
+
114
+ const buffer = Buffer.alloc(bytes); // Read a set number of bytes from the file.
115
+
116
+ await _fsExtra.default.read(file, buffer, 0, bytes, 0); // If the buffer has the pragma then add the file path to our
117
+ // final file paths array.
118
+
119
+ if (buffer.includes('@flow')) {
120
+ filePaths.push(filePath);
121
+ } // Close the file.
122
+
123
+
124
+ await _fsExtra.default.close(file);
125
+ }
126
+ }
127
+
128
+ async function findFlowFilesWithSpinner(rootDirectory, options) {
129
+ // Create a new spinner.
130
+ const spinner = (0, _ora.default)({
131
+ text: _chalk.default.italic.cyan('Finding all the Flow files to be upgraded...'),
132
+ color: 'cyan',
133
+ isSilent: options.silent
134
+ }); // Start the spinner.
135
+
136
+ spinner.start(); // Find all of the Flow files in the directory we are upgrading.
137
+
138
+ const filePaths = await (() => {
139
+ try {
140
+ return findFlowFiles({
141
+ rootDirectory,
142
+ includeNonAtFlow: options.all
143
+ });
144
+ } catch (error) {
145
+ // Stop the spinner if we get an error.
146
+ spinner.stop();
147
+ throw error;
179
148
  }
180
- });
181
- };
149
+ })(); // Stop the spinner.
150
+
151
+ spinner.stop(); // Log the number of Flow files that we found.
152
+
153
+ if (!options.silent) {
154
+ console.log(`Found ${_chalk.default.bold.cyan(filePaths.length)} Flow files.`);
155
+ console.log();
156
+ }
157
+
158
+ return filePaths;
159
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = runCodemods;
7
+
8
+ var _fsExtra = _interopRequireDefault(require("fs-extra"));
9
+
10
+ var _hermesTransform = require("hermes-transform");
11
+
12
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+
14
+ /**
15
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
16
+ *
17
+ * This source code is licensed under the MIT license found in the
18
+ * LICENSE file in the root directory of this source tree.
19
+ *
20
+ * @format
21
+ *
22
+ */
23
+ async function runCodemods(codemods, filePaths, options) {
24
+ const results = await Promise.allSettled(filePaths.map(async filePath => {
25
+ const originalContents = await _fsExtra.default.readFile(filePath, 'utf8');
26
+ let contents = originalContents;
27
+
28
+ for (const codemod of codemods) {
29
+ contents = (0, _hermesTransform.transform)(contents, codemod.transform, options.prettierOptions);
30
+ }
31
+
32
+ if (originalContents !== contents) {
33
+ await _fsExtra.default.writeFile(filePath, contents, 'utf8');
34
+ }
35
+ }));
36
+ }