@carbon/upgrade 10.17.0-rc.0 → 10.17.1

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 (31) hide show
  1. package/README.md +9 -0
  2. package/cli.js +2288 -466
  3. package/package.json +12 -7
  4. package/telemetry.yml +7 -0
  5. package/transforms/ARCHITECTURE.md +47 -0
  6. package/transforms/__testfixtures__/icons-react-size-prop-object-key.input.js +16 -0
  7. package/transforms/__testfixtures__/icons-react-size-prop-object-key.output.js +16 -0
  8. package/transforms/__testfixtures__/icons-react-size-prop-rename.input.js +44 -0
  9. package/transforms/__testfixtures__/icons-react-size-prop-rename.output.js +44 -0
  10. package/transforms/__testfixtures__/icons-react-size-prop-with-prop.input.js +19 -0
  11. package/transforms/__testfixtures__/icons-react-size-prop-with-prop.output.js +22 -0
  12. package/transforms/__testfixtures__/size-prop-update.input.js +143 -0
  13. package/transforms/__testfixtures__/size-prop-update.output.js +143 -0
  14. package/transforms/__testfixtures__/small-to-size-prop.input.js +11 -0
  15. package/transforms/__testfixtures__/small-to-size-prop.output.js +11 -0
  16. package/transforms/__testfixtures__/sort-prop-types.input.js +7 -0
  17. package/transforms/__testfixtures__/sort-prop-types.output.js +7 -0
  18. package/transforms/__testfixtures__/sort-prop-types2.input.js +7 -0
  19. package/transforms/__testfixtures__/sort-prop-types2.output.js +7 -0
  20. package/transforms/__testfixtures__/update-carbon-components-react-import-to-scoped.input.js +8 -0
  21. package/transforms/__testfixtures__/update-carbon-components-react-import-to-scoped.output.js +8 -0
  22. package/transforms/__tests__/icons-react-size-prop.js +64 -0
  23. package/transforms/__tests__/size-prop-update-test.js +12 -0
  24. package/transforms/__tests__/small-to-size-test.js +12 -0
  25. package/transforms/__tests__/sort-prop-types-test.js +13 -0
  26. package/transforms/__tests__/update-carbon-components-react-import-to-scoped.js +12 -0
  27. package/transforms/icons-react-size-prop.js +324 -0
  28. package/transforms/size-prop-update.js +140 -0
  29. package/transforms/small-to-size-prop.js +56 -0
  30. package/transforms/sort-prop-types.js +88 -0
  31. package/transforms/update-carbon-components-react-import-to-scoped.js +33 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2020
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { defineTest } = require('jscodeshift/dist/testUtils');
11
+
12
+ describe('icons-react-size-prop', () => {
13
+ let mock;
14
+
15
+ beforeEach(() => {
16
+ mock = jest.spyOn(console, 'log').mockImplementation(() => {});
17
+ });
18
+
19
+ afterEach(() => {
20
+ mock.mockRestore();
21
+ });
22
+
23
+ defineTest(
24
+ __dirname,
25
+ 'icons-react-size-prop',
26
+ {
27
+ printOptions: {
28
+ quote: 'single',
29
+ },
30
+ },
31
+ 'icons-react-size-prop-rename',
32
+ {
33
+ parser: 'babylon',
34
+ }
35
+ );
36
+
37
+ defineTest(
38
+ __dirname,
39
+ 'icons-react-size-prop',
40
+ {
41
+ printOptions: {
42
+ quote: 'single',
43
+ },
44
+ },
45
+ 'icons-react-size-prop-with-prop',
46
+ {
47
+ parser: 'babylon',
48
+ }
49
+ );
50
+
51
+ defineTest(
52
+ __dirname,
53
+ 'icons-react-size-prop',
54
+ {
55
+ printOptions: {
56
+ quote: 'single',
57
+ },
58
+ },
59
+ 'icons-react-size-prop-object-key',
60
+ {
61
+ parser: 'babylon',
62
+ }
63
+ );
64
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2020
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { defineTest } = require('jscodeshift/dist/testUtils');
11
+
12
+ defineTest(__dirname, 'size-prop-update');
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2020
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { defineTest } = require('jscodeshift/dist/testUtils');
11
+
12
+ defineTest(__dirname, 'small-to-size-prop');
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2020
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { defineTest } = require('jscodeshift/dist/testUtils');
11
+
12
+ defineTest(__dirname, 'sort-prop-types');
13
+ defineTest(__dirname, 'sort-prop-types', null, 'sort-prop-types2');
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2020
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const { defineTest } = require('jscodeshift/dist/testUtils');
11
+
12
+ defineTest(__dirname, 'update-carbon-components-react-import-to-scoped');
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2020
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const defaultOptions = {
11
+ quote: 'auto',
12
+ trailingComma: true,
13
+ };
14
+ const defaultSize = 16;
15
+
16
+ function transform(fileInfo, api, options) {
17
+ const printOptions = options.printOptions || defaultOptions;
18
+ const j = api.jscodeshift;
19
+ const root = j(fileInfo.source);
20
+
21
+ // Find all the import declarations that come from `@carbon/icons-react`
22
+ const matches = root.find(j.ImportDeclaration, {
23
+ source: {
24
+ value: '@carbon/icons-react',
25
+ },
26
+ });
27
+
28
+ // If we cannot find any, then there is no work to do
29
+ if (matches.size() === 0) {
30
+ return null;
31
+ }
32
+
33
+ if (matches.size() > 1) {
34
+ throw new Error(
35
+ `Expected only one import to @carbon/icons-react, instead found: ` +
36
+ `${matches.size()} imports`
37
+ );
38
+ }
39
+
40
+ // For now, these icons are available under @carbon/icons-react/next
41
+ // TODO: remove in v11
42
+ matches.forEach((path) => {
43
+ path.get('source').get('value').replace('@carbon/icons-react/next');
44
+ });
45
+
46
+ // Otherwise, we will get our import to icons and update the imported icons to
47
+ // use the new format
48
+ const iconsImport = matches.get();
49
+ const importSpecifiers = new Set();
50
+
51
+ // Iterate through each of the imported icons, get their size and name, and
52
+ // update the import and all matches in the file
53
+ j(iconsImport)
54
+ .find(j.ImportSpecifier)
55
+ .forEach((path) => {
56
+ const rootScope = path.scope;
57
+ const SIZE_REGEX = /(.+)(\d\d)$/g;
58
+ const { imported, local } = path.node;
59
+ const match = SIZE_REGEX.exec(imported.name);
60
+
61
+ if (match === null) {
62
+ throw new Error(`Expected to find size for: ${imported.name}`);
63
+ }
64
+
65
+ const name = match[1];
66
+ const size = parseInt(match[2], 10);
67
+
68
+ if (isNaN(size)) {
69
+ throw new Error(`Unable to parse size for ${imported.name}`);
70
+ }
71
+
72
+ // If they renamed the icon in the import, use that as the local name
73
+ // import { IconName32 as CustomName } from '@carbon/icons-react';
74
+ const newBinding =
75
+ imported.name === local.name ? j.identifier(getSafeBinding()) : local;
76
+
77
+ function getSafeBinding() {
78
+ if (rootScope.declares(name)) {
79
+ return `${name}Icon`;
80
+ }
81
+ return name;
82
+ }
83
+
84
+ // Update the imported binding from IconName32 to IconName. Only do this
85
+ // replacement once if we have imports of the same icon but in different
86
+ // sizes.
87
+ if (!importSpecifiers.has(newBinding.name)) {
88
+ importSpecifiers.add(newBinding.name);
89
+ j(path).replaceWith(j.importSpecifier(j.identifier(name), newBinding));
90
+ } else {
91
+ j(path).remove();
92
+ }
93
+
94
+ // Finally, find all instances where we refer to this import and update
95
+ // its binding
96
+ root
97
+ .find(j.Identifier, { name: local.name })
98
+ .filter((path) => {
99
+ const { node: parent } = path.parent;
100
+
101
+ if (
102
+ j.MemberExpression.check(parent) &&
103
+ parent.property === path.node &&
104
+ !parent.computed
105
+ ) {
106
+ // obj.oldName
107
+ return false;
108
+ }
109
+
110
+ if (
111
+ j.ObjectProperty.check(parent) &&
112
+ parent.key === path.node &&
113
+ !parent.computed
114
+ ) {
115
+ // { oldName: 3 }
116
+ return false;
117
+ }
118
+
119
+ if (
120
+ j.MethodDefinition.check(parent) &&
121
+ parent.key === path.node &&
122
+ !parent.computed
123
+ ) {
124
+ // class A { oldName() {} }
125
+ return false;
126
+ }
127
+
128
+ if (
129
+ j.ClassProperty.check(parent) &&
130
+ parent.key === path.node &&
131
+ !parent.computed
132
+ ) {
133
+ // class A { oldName = 3 }
134
+ return false;
135
+ }
136
+
137
+ if (
138
+ j.JSXAttribute.check(parent) &&
139
+ parent.name === path.node &&
140
+ !parent.computed
141
+ ) {
142
+ // <Foo oldName={oldName} />
143
+ return false;
144
+ }
145
+ return true;
146
+ })
147
+ .forEach((path) => {
148
+ let scope = path.scope;
149
+ while (scope && scope !== rootScope) {
150
+ // If a scope already declares this binding, return early as it does
151
+ // not relate to our icon import
152
+ if (scope.declares(local.name)) {
153
+ return;
154
+ }
155
+ scope = scope.parent;
156
+ }
157
+
158
+ if (!scope) {
159
+ return;
160
+ }
161
+
162
+ const { node: parent } = path.parent;
163
+
164
+ // Replace the identifier name with the new binding name. If the parent
165
+ // node is an `ImportSpecifier`, we won't do this replacement as it is
166
+ // handled above
167
+ if (!j.ImportSpecifier.check(parent)) {
168
+ path.replace(newBinding);
169
+ }
170
+
171
+ // If our identifier is inside of a JSXOpeningElement, then we need to
172
+ // check to see if we need to add in the `size` prop that is now
173
+ // needed
174
+ if (j.JSXOpeningElement.check(parent) && size !== defaultSize) {
175
+ parent.attributes.unshift(
176
+ j.jsxAttribute(
177
+ j.jsxIdentifier('size'),
178
+ j.jsxExpressionContainer(j.numericLiteral(size))
179
+ )
180
+ );
181
+ }
182
+
183
+ // Handle cases where the icon is referred to in an object, for
184
+ // example:
185
+ //
186
+ // from:
187
+ // const alias = { name: IconName24 };
188
+ //
189
+ // to:
190
+ // const alias = { name: (props) => <IconName size={24} {...props} /> };
191
+ //
192
+ // Since the `size` information needs to be provided, otherwise the
193
+ // default size will be used
194
+ if (j.ObjectProperty.check(parent)) {
195
+ let replacement = null;
196
+
197
+ // map to React.createElement instead of using as the JSX Opening
198
+ // Element directly
199
+ if (newBinding.name[0] === newBinding.name[0].toLowerCase()) {
200
+ // Builds up this structure:
201
+ // (props) => React.createElement(iconName, {
202
+ // size: 20,
203
+ // ...props,
204
+ // });
205
+ replacement = j.arrowFunctionExpression(
206
+ [j.identifier('props')],
207
+ j.callExpression(
208
+ j.memberExpression(
209
+ j.identifier('React'),
210
+ j.identifier('createElement')
211
+ ),
212
+ [
213
+ newBinding,
214
+ j.objectExpression([
215
+ j.objectProperty(
216
+ j.identifier('size'),
217
+ j.numericLiteral(size)
218
+ ),
219
+ j.spreadElement(j.identifier('props')),
220
+ ]),
221
+ ]
222
+ )
223
+ );
224
+ } else {
225
+ // Build up this structure:
226
+ // (props) => <IconName size={20} {...props} />
227
+ replacement = j.arrowFunctionExpression(
228
+ [j.identifier('props')],
229
+ j.jsxElement(
230
+ j.jsxOpeningElement(
231
+ j.jsxIdentifier(newBinding.name),
232
+ [
233
+ j.jsxAttribute(
234
+ j.jsxIdentifier('size'),
235
+ j.jsxExpressionContainer(j.numericLiteral(size))
236
+ ),
237
+ j.jsxSpreadAttribute(j.identifier('props')),
238
+ ],
239
+ true
240
+ )
241
+ )
242
+ );
243
+ }
244
+
245
+ warn(fileInfo.path);
246
+
247
+ // Sometimes consumers will use the icon module name as a shorthand
248
+ // in an object property.
249
+ //
250
+ // Input:
251
+ // const o = { Add16, Add32 };
252
+ // Output:
253
+ // const o = { Add16: Add, Add32: (props) => <Add size={32} {...props} />
254
+ if (
255
+ parent.key.name !== newBinding.name &&
256
+ parent.shorthand === true
257
+ ) {
258
+ path.parent.get('shorthand').replace(false);
259
+ }
260
+
261
+ if (size !== defaultSize) {
262
+ path.parent.get('value').replace(replacement);
263
+ } else {
264
+ path.parent.get('value').replace(newBinding);
265
+ }
266
+ }
267
+
268
+ // Support `renderIcon` style props where you pass in an Icon by itself
269
+ // to the prop
270
+ //
271
+ // from:
272
+ // <Component renderIcon={Icon24} />
273
+ //
274
+ // to:
275
+ // <Component renderIcon={(props) => <Icon size={24} {...props} />} />
276
+ if (j.JSXExpressionContainer.check(parent) && size !== defaultSize) {
277
+ warn(fileInfo.path);
278
+ path.parentPath.replace(
279
+ j.jsxExpressionContainer(
280
+ j.arrowFunctionExpression(
281
+ [j.identifier('props')],
282
+ j.jsxElement(
283
+ j.jsxOpeningElement(
284
+ j.jsxIdentifier(path.node.name),
285
+ [
286
+ j.jsxAttribute(
287
+ j.jsxIdentifier('size'),
288
+ j.jsxExpressionContainer(j.numericLiteral(size))
289
+ ),
290
+ j.jsxSpreadAttribute(j.identifier('props')),
291
+ ],
292
+ true
293
+ )
294
+ )
295
+ )
296
+ )
297
+ );
298
+ }
299
+ });
300
+ });
301
+
302
+ return root.toSource(printOptions);
303
+ }
304
+
305
+ const manualCheckWarning = `[carbon] ${'='.repeat(71)}
306
+ We have updated the file: %s to the new icon API.
307
+
308
+ However, it may be that this update is missing a \`ref\` on the prop where the
309
+ icon is used. Please make sure to verify that this update is correct.
310
+
311
+ For more information, check out our migration guide: https://bit.ly/3o2vVQW\n`;
312
+
313
+ const files = new Set();
314
+
315
+ function warn(filepath) {
316
+ if (files.has(filepath)) {
317
+ return;
318
+ }
319
+
320
+ files.add(filepath);
321
+ console.log(manualCheckWarning, filepath);
322
+ }
323
+
324
+ module.exports = transform;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2020
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const defaultOptions = {
11
+ quote: 'auto',
12
+ trailingComma: true,
13
+ };
14
+
15
+ function transform(fileInfo, api, options) {
16
+ const printOptions = options.printOptions || defaultOptions;
17
+ const j = api.jscodeshift;
18
+ const root = j(fileInfo.source);
19
+
20
+ function replacePropForComponent(name) {
21
+ // Multiselect.Filterable has a different AST structure, so we need to
22
+ // adjust the query to find it properly. If it is not 'Filterable', we use
23
+ // the default query
24
+ let isFilterable = name === 'Filterable';
25
+ let openingElementQuery = {
26
+ name: {
27
+ name,
28
+ },
29
+ };
30
+ if (isFilterable) {
31
+ openingElementQuery = {
32
+ name: {
33
+ object: {
34
+ name: 'MultiSelect',
35
+ },
36
+ property: {
37
+ name: name,
38
+ },
39
+ },
40
+ };
41
+ }
42
+ root
43
+ .find(j.JSXOpeningElement, openingElementQuery)
44
+ .forEach((openingElement) => {
45
+ // Multiselect.Filterable has a different AST structure, so the name is located differently
46
+ const { name } = isFilterable
47
+ ? openingElement.node.name.property
48
+ : openingElement.node.name;
49
+
50
+ // Some components were originally set to `xl`, but are being reduced to `lg`
51
+ const downsizedComponents = [
52
+ 'Accordion',
53
+ 'ComboBox',
54
+ 'ContentSwitcher',
55
+ 'DatePickerInput',
56
+ 'Dropdown',
57
+ 'MultiSelect',
58
+ 'Filterable',
59
+ 'NumberInput',
60
+ 'OverflowMenu',
61
+ 'Search',
62
+ 'Select',
63
+ 'TextInput',
64
+ 'TimePicker',
65
+ ];
66
+
67
+ const isDownsized = downsizedComponents.includes(name);
68
+
69
+ j(openingElement)
70
+ .find(j.JSXAttribute, {
71
+ name: {
72
+ name: 'size',
73
+ },
74
+ })
75
+ .forEach((path) => {
76
+ j(path).replaceWith((nodePath) => {
77
+ const { node } = nodePath;
78
+ const { value } = node.value;
79
+
80
+ switch (value) {
81
+ case 'compact':
82
+ node.value.value = 'xs';
83
+ return node;
84
+ case 'short':
85
+ case 'small':
86
+ node.value.value = 'sm';
87
+ return node;
88
+ case 'field':
89
+ node.value.value = 'md';
90
+ return node;
91
+ case 'default':
92
+ case 'normal':
93
+ node.value.value = 'lg';
94
+ return node;
95
+ case 'tall':
96
+ case 'lg':
97
+ node.value.value = isDownsized ? 'md' : 'xl';
98
+ return node;
99
+ case 'xl':
100
+ node.value.value = isDownsized ? 'lg' : '2xl';
101
+ return node;
102
+ }
103
+ return node;
104
+ });
105
+ });
106
+ });
107
+ }
108
+
109
+ const components = [
110
+ 'Accordion',
111
+ 'Button',
112
+ 'ComboBox',
113
+ 'ContentSwitcher',
114
+ 'DataTable',
115
+ 'DatePickerInput',
116
+ 'Dropdown',
117
+ 'FileUploader',
118
+ 'FileUploaderButton',
119
+ 'FileUploaderDropContainer',
120
+ 'FileUploaderItem',
121
+ 'MultiSelect',
122
+ 'Filterable',
123
+ 'NumberInput',
124
+ 'OverflowMenu',
125
+ 'Search',
126
+ 'Select',
127
+ 'Table',
128
+ 'TableToolbar',
129
+ 'TextInput',
130
+ 'TimePicker',
131
+ ];
132
+
133
+ for (const component of components) {
134
+ replacePropForComponent(component);
135
+ }
136
+
137
+ return root.toSource(printOptions);
138
+ }
139
+
140
+ module.exports = transform;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Copyright IBM Corp. 2016, 2020
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const defaultOptions = {
11
+ quote: 'auto',
12
+ trailingComma: true,
13
+ };
14
+
15
+ function transform(fileInfo, api, options) {
16
+ const printOptions = options.printOptions || defaultOptions;
17
+ const j = api.jscodeshift;
18
+ const root = j(fileInfo.source);
19
+
20
+ const sizeProp = j.jsxAttribute(j.jsxIdentifier('size'), j.literal('sm'));
21
+
22
+ function replacePropForComponent(name) {
23
+ root
24
+ .find(j.JSXOpeningElement, {
25
+ name: {
26
+ name,
27
+ },
28
+ })
29
+ .forEach((openingElement) => {
30
+ j(openingElement)
31
+ .find(j.JSXAttribute, {
32
+ name: {
33
+ name: 'small',
34
+ },
35
+ })
36
+ .forEach((path) => {
37
+ j(path).replaceWith(sizeProp);
38
+ });
39
+ });
40
+ }
41
+
42
+ const components = [
43
+ 'Button',
44
+ 'DangerButton',
45
+ 'PrimaryButton',
46
+ 'SecondaryButton',
47
+ 'Search',
48
+ ];
49
+ for (const component of components) {
50
+ replacePropForComponent(component);
51
+ }
52
+
53
+ return root.toSource(printOptions);
54
+ }
55
+
56
+ module.exports = transform;