@atlaskit/codemod-cli 0.24.1 → 0.24.3

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 (54) hide show
  1. package/CHANGELOG.md +335 -320
  2. package/dist/cjs/main.js +1 -1
  3. package/dist/cjs/presets/migrate-to-new-buttons/codemods/next-migrate-to-new-button-variants.js +126 -50
  4. package/dist/cjs/presets/migrate-to-new-buttons/codemods/next-split-imports.js +25 -9
  5. package/dist/cjs/presets/migrate-to-new-buttons/utils/constants.js +16 -3
  6. package/dist/cjs/presets/migrate-to-new-buttons/utils/generate-new-button-element.js +14 -5
  7. package/dist/cjs/presets/migrate-to-new-buttons/utils/get-default-imports.js +14 -0
  8. package/dist/cjs/presets/migrate-to-new-buttons/utils/get-specifier-names.js +14 -0
  9. package/dist/cjs/presets/migrate-to-new-buttons/utils/if-variant-already-imported.js +15 -7
  10. package/dist/cjs/presets/migrate-to-new-buttons/utils/rename-elements.js +18 -0
  11. package/dist/es2019/presets/migrate-to-new-buttons/codemods/next-migrate-to-new-button-variants.js +119 -46
  12. package/dist/es2019/presets/migrate-to-new-buttons/codemods/next-split-imports.js +25 -9
  13. package/dist/es2019/presets/migrate-to-new-buttons/utils/constants.js +16 -2
  14. package/dist/es2019/presets/migrate-to-new-buttons/utils/generate-new-button-element.js +12 -5
  15. package/dist/es2019/presets/migrate-to-new-buttons/utils/get-default-imports.js +6 -0
  16. package/dist/es2019/presets/migrate-to-new-buttons/utils/get-specifier-names.js +6 -0
  17. package/dist/es2019/presets/migrate-to-new-buttons/utils/if-variant-already-imported.js +9 -3
  18. package/dist/es2019/presets/migrate-to-new-buttons/utils/rename-elements.js +12 -0
  19. package/dist/esm/main.js +1 -1
  20. package/dist/esm/presets/migrate-to-new-buttons/codemods/next-migrate-to-new-button-variants.js +126 -51
  21. package/dist/esm/presets/migrate-to-new-buttons/codemods/next-split-imports.js +25 -9
  22. package/dist/esm/presets/migrate-to-new-buttons/utils/constants.js +15 -2
  23. package/dist/esm/presets/migrate-to-new-buttons/utils/generate-new-button-element.js +14 -5
  24. package/dist/esm/presets/migrate-to-new-buttons/utils/get-default-imports.js +8 -0
  25. package/dist/esm/presets/migrate-to-new-buttons/utils/get-specifier-names.js +8 -0
  26. package/dist/esm/presets/migrate-to-new-buttons/utils/if-variant-already-imported.js +14 -6
  27. package/dist/esm/presets/migrate-to-new-buttons/utils/rename-elements.js +12 -0
  28. package/dist/types/main.d.ts +1 -1
  29. package/dist/types/presets/migrate-to-new-buttons/utils/constants.d.ts +11 -1
  30. package/dist/types/presets/migrate-to-new-buttons/utils/get-default-imports.d.ts +5 -0
  31. package/dist/types/presets/migrate-to-new-buttons/utils/get-specifier-names.d.ts +5 -0
  32. package/dist/types/presets/migrate-to-new-buttons/utils/if-variant-already-imported.d.ts +2 -1
  33. package/dist/types/presets/migrate-to-new-buttons/utils/migrate-fit-container-icon-button.d.ts +1 -1
  34. package/dist/types/presets/migrate-to-new-buttons/utils/rename-elements.d.ts +2 -0
  35. package/dist/types/presets/styled-to-emotion/styled-to-emotion.d.ts +1 -1
  36. package/dist/types/presets/theme-remove-deprecated-mixins/theme-remove-deprecated-mixins.d.ts +1 -1
  37. package/dist/types/presets/upgrade-pragmatic-drag-and-drop-to-stable/upgrade-pragmatic-drag-and-drop-to-stable.d.ts +1 -1
  38. package/dist/types/sinceRef.d.ts +1 -1
  39. package/dist/types/transforms.d.ts +2 -2
  40. package/dist/types/types.d.ts +1 -1
  41. package/dist/types-ts4.5/main.d.ts +1 -1
  42. package/dist/types-ts4.5/presets/migrate-to-new-buttons/utils/constants.d.ts +11 -1
  43. package/dist/types-ts4.5/presets/migrate-to-new-buttons/utils/get-default-imports.d.ts +5 -0
  44. package/dist/types-ts4.5/presets/migrate-to-new-buttons/utils/get-specifier-names.d.ts +5 -0
  45. package/dist/types-ts4.5/presets/migrate-to-new-buttons/utils/if-variant-already-imported.d.ts +2 -1
  46. package/dist/types-ts4.5/presets/migrate-to-new-buttons/utils/migrate-fit-container-icon-button.d.ts +1 -1
  47. package/dist/types-ts4.5/presets/migrate-to-new-buttons/utils/rename-elements.d.ts +2 -0
  48. package/dist/types-ts4.5/presets/styled-to-emotion/styled-to-emotion.d.ts +1 -1
  49. package/dist/types-ts4.5/presets/theme-remove-deprecated-mixins/theme-remove-deprecated-mixins.d.ts +1 -1
  50. package/dist/types-ts4.5/presets/upgrade-pragmatic-drag-and-drop-to-stable/upgrade-pragmatic-drag-and-drop-to-stable.d.ts +1 -1
  51. package/dist/types-ts4.5/sinceRef.d.ts +1 -1
  52. package/dist/types-ts4.5/transforms.d.ts +2 -2
  53. package/dist/types-ts4.5/types.d.ts +1 -1
  54. package/package.json +3 -5
@@ -1,6 +1,8 @@
1
1
  import { addCommentBefore } from '@atlaskit/codemod-utils';
2
2
  import { getDefaultImportSpecifierName } from '@hypermod/utils';
3
- import { PRINT_SETTINGS, NEW_BUTTON_VARIANTS, entryPointsMapping, NEW_BUTTON_ENTRY_POINT, linkButtonMissingHrefComment, buttonPropsNoLongerSupportedComment, unsupportedProps } from '../utils/constants';
3
+ import { PRINT_SETTINGS, OLD_BUTTON_VARIANTS, NEW_BUTTON_VARIANTS, entryPointsMapping, OLD_BUTTON_ENTRY_POINT, NEW_BUTTON_ENTRY_POINT, linkButtonMissingHrefComment, buttonPropsNoLongerSupportedComment, unsupportedProps, loadingButtonComment } from '../utils/constants';
4
+ import getDefaultImports from '../utils/get-default-imports';
5
+ import getSpecifierNames from '../utils/get-specifier-names';
4
6
  import { generateNewElement, handleIconAttributes } from '../utils/generate-new-button-element';
5
7
  import { ifHasUnsupportedProps } from '../utils/has-unsupported-props';
6
8
  import { checkIfVariantAlreadyImported } from '../utils/if-variant-already-imported';
@@ -12,22 +14,38 @@ const transformer = (file, api) => {
12
14
  const j = api.jscodeshift;
13
15
  const fileSource = j(file.source);
14
16
  addCommentForCustomThemeButtons(fileSource, j);
15
- const buttonImports = fileSource.find(j.ImportDeclaration).filter(path => path.node.source.value === entryPointsMapping.Button || path.node.source.value === entryPointsMapping.LoadingButton || path.node.source.value === NEW_BUTTON_ENTRY_POINT);
16
- if (!buttonImports.length) {
17
- return fileSource.toSource();
18
- }
19
- const defaultButtonImport = buttonImports.find(j.Specifier).filter(path => path.node.type === 'ImportDefaultSpecifier');
20
- if (defaultButtonImport.length === 0) {
17
+
18
+ // Find old buttons
19
+ const oldButtonImports = fileSource.find(j.ImportDeclaration).filter(path => path.node.source.value === entryPointsMapping.Button || path.node.source.value === entryPointsMapping.LoadingButton || path.node.source.value === OLD_BUTTON_ENTRY_POINT);
20
+ if (!oldButtonImports.length) {
21
21
  return fileSource.toSource();
22
22
  }
23
- const specifierIdentifier = defaultButtonImport.get(0).node.local.name;
24
- let hasLinkIconButton = checkIfVariantAlreadyImported(NEW_BUTTON_VARIANTS.linkIcon, fileSource, j);
25
- let hasLinkButton = checkIfVariantAlreadyImported(NEW_BUTTON_VARIANTS.link, fileSource, j);
26
- let hasIconButton = checkIfVariantAlreadyImported(NEW_BUTTON_VARIANTS.icon, fileSource, j);
27
- const allButtons = fileSource.find(j.JSXElement).filter(path => path.value.openingElement.name.type === 'JSXIdentifier' && path.value.openingElement.name.name === specifierIdentifier);
28
- const buttonsWithoutUnsupportedProps = allButtons.filter(path => !ifHasUnsupportedProps(path.value.openingElement.attributes));
29
- const loadingButtonImportName = getDefaultImportSpecifierName(j, fileSource, entryPointsMapping.LoadingButton);
30
- buttonsWithoutUnsupportedProps.forEach(element => {
23
+ const oldDefaultDefaultImports = getDefaultImports(oldButtonImports, j);
24
+ const oldDefaultImportSpecifiers = getSpecifierNames(oldDefaultDefaultImports);
25
+
26
+ // Find new buttons
27
+ const newButtonImports = fileSource.find(j.ImportDeclaration).filter(path => path.node.source.value === NEW_BUTTON_ENTRY_POINT);
28
+ const newDefaultDefaultImports = getDefaultImports(newButtonImports, j);
29
+ const newDefaultImportSpecifiers = getSpecifierNames(newDefaultDefaultImports);
30
+ const loadingButtonDirectImportName = getDefaultImportSpecifierName(j, fileSource, entryPointsMapping.LoadingButton);
31
+
32
+ /**
33
+ * Which variants should be in this file?
34
+ *
35
+ * Any variants enabled here will be added to the final import statement.
36
+ * Initial values check if the variant is already imported in the file.
37
+ */
38
+ let hasVariant = {
39
+ defaultButton: checkIfVariantAlreadyImported(NEW_BUTTON_VARIANTS.default, NEW_BUTTON_ENTRY_POINT, fileSource, j, true),
40
+ linkButton: checkIfVariantAlreadyImported(NEW_BUTTON_VARIANTS.link, NEW_BUTTON_ENTRY_POINT, fileSource, j),
41
+ iconButton: checkIfVariantAlreadyImported(NEW_BUTTON_VARIANTS.icon, NEW_BUTTON_ENTRY_POINT, fileSource, j),
42
+ linkIconButton: checkIfVariantAlreadyImported(NEW_BUTTON_VARIANTS.linkIcon, NEW_BUTTON_ENTRY_POINT, fileSource, j)
43
+ };
44
+ const oldButtonElements = fileSource.find(j.JSXElement).filter(path => {
45
+ return path.value.openingElement.name.type === 'JSXIdentifier' && ((oldDefaultImportSpecifiers === null || oldDefaultImportSpecifiers === void 0 ? void 0 : oldDefaultImportSpecifiers.includes(path.value.openingElement.name.name)) || Object.values(OLD_BUTTON_VARIANTS).includes(path.value.openingElement.name.name));
46
+ });
47
+ const oldButtonsWithoutUnsupportedProps = oldButtonElements.filter(path => !ifHasUnsupportedProps(path.value.openingElement.attributes));
48
+ oldButtonsWithoutUnsupportedProps.forEach(element => {
31
49
  var _element$value$childr;
32
50
  const {
33
51
  attributes
@@ -43,9 +61,14 @@ const transformer = (file, api) => {
43
61
  const isLinkIconButton = hasHref && hasIcon && hasNoChildren && !isFitContainerIconButton;
44
62
  const isLinkButton = hasHref && !isLinkIconButton;
45
63
  let isIconButton = !hasHref && hasIcon && hasNoChildren && !isFitContainerIconButton;
46
- const isDefaultButtonWithAnIcon = !isLinkIconButton && !isIconButton && !isFitContainerIconButton && hasIcon;
47
- const isLoadingButton = element.value.openingElement.name.type === 'JSXIdentifier' && element.value.openingElement.name.name === loadingButtonImportName;
48
- if (isDefaultButtonWithAnIcon) {
64
+ const isDefaultButton = !isLinkButton && !isLinkIconButton && !isIconButton && !isFitContainerIconButton;
65
+ const isDefaultVariantWithAnIcon = !isLinkIconButton && !isIconButton && !isFitContainerIconButton && hasIcon;
66
+ const isLoadingButton = element.value.openingElement.name.type === 'JSXIdentifier' && element.value.openingElement.name.name === loadingButtonDirectImportName;
67
+ const linkAppearanceAttribute = attributes.find(node => {
68
+ var _node$value, _node$name, _node$value2, _node$value3;
69
+ return node.type === 'JSXAttribute' && ((_node$value = node.value) === null || _node$value === void 0 ? void 0 : _node$value.type) === 'StringLiteral' && (node === null || node === void 0 ? void 0 : (_node$name = node.name) === null || _node$name === void 0 ? void 0 : _node$name.name) === 'appearance' && ((node === null || node === void 0 ? void 0 : (_node$value2 = node.value) === null || _node$value2 === void 0 ? void 0 : _node$value2.value) === 'link' || (node === null || node === void 0 ? void 0 : (_node$value3 = node.value) === null || _node$value3 === void 0 ? void 0 : _node$value3.value) === 'subtle-link');
70
+ });
71
+ if (isDefaultVariantWithAnIcon) {
49
72
  handleIconAttributes(element.value, j);
50
73
  }
51
74
  if (isFitContainerIconButton) {
@@ -54,61 +77,74 @@ const transformer = (file, api) => {
54
77
  isIconButton = true;
55
78
  }
56
79
  }
57
- if (isLinkIconButton) {
58
- hasLinkIconButton = true;
80
+ if (isLinkIconButton && !isLoadingButton) {
81
+ hasVariant.linkIconButton = true;
59
82
  j(element).replaceWith(generateNewElement(NEW_BUTTON_VARIANTS.linkIcon, element.value, j));
60
83
  }
61
- if (isIconButton) {
62
- hasIconButton = true;
84
+ if (isIconButton && !isLoadingButton) {
85
+ hasVariant.iconButton = true;
63
86
  j(element).replaceWith(generateNewElement(NEW_BUTTON_VARIANTS.icon, element.value, j));
64
87
  }
65
- if (isLinkButton) {
66
- hasLinkButton = true;
88
+ if (isLinkButton && !isLoadingButton) {
89
+ hasVariant.linkButton = true;
67
90
  j(element).replaceWith(generateNewElement(NEW_BUTTON_VARIANTS.link, element.value, j));
68
91
  }
69
- if (isLoadingButton) {
70
- j(element).replaceWith(generateNewElement(NEW_BUTTON_VARIANTS.default, element.value, j));
92
+ if (isDefaultButton && !isLoadingButton) {
93
+ hasVariant.defaultButton = true;
94
+ j(element).replaceWith(generateNewElement(newDefaultImportSpecifiers !== null && newDefaultImportSpecifiers !== void 0 && newDefaultImportSpecifiers.length ?
95
+ // If new button already has a default import, use that incase it's aliased
96
+ newDefaultImportSpecifiers[0] : NEW_BUTTON_VARIANTS.default, element.value, j));
71
97
  }
72
- const linkAppearanceAttribute = attributes.find(node => {
73
- var _node$value, _node$name, _node$value2, _node$value3;
74
- return node.type === 'JSXAttribute' && ((_node$value = node.value) === null || _node$value === void 0 ? void 0 : _node$value.type) === 'StringLiteral' && (node === null || node === void 0 ? void 0 : (_node$name = node.name) === null || _node$name === void 0 ? void 0 : _node$name.name) === 'appearance' && ((node === null || node === void 0 ? void 0 : (_node$value2 = node.value) === null || _node$value2 === void 0 ? void 0 : _node$value2.value) === 'link' || (node === null || node === void 0 ? void 0 : (_node$value3 = node.value) === null || _node$value3 === void 0 ? void 0 : _node$value3.value) === 'subtle-link');
75
- });
76
- if (!hasHref && linkAppearanceAttribute) {
98
+ if (isLoadingButton) {
99
+ const newElement = generateNewElement(NEW_BUTTON_VARIANTS[isIconButton || isLinkIconButton ? 'icon' : 'default'], element.value, j);
100
+ if (isIconButton || isLinkIconButton) {
101
+ hasVariant.iconButton = true;
102
+ } else {
103
+ hasVariant.defaultButton = true;
104
+ }
105
+ j(element).replaceWith(newElement);
106
+ if (hasHref || linkAppearanceAttribute) {
107
+ addCommentBefore(j, j(newElement), loadingButtonComment({
108
+ hasHref,
109
+ hasLinkAppearance: Boolean(linkAppearanceAttribute)
110
+ }), 'block');
111
+ }
112
+ } else if (!hasHref && linkAppearanceAttribute) {
77
113
  addCommentBefore(j, j(linkAppearanceAttribute), linkButtonMissingHrefComment, 'line');
78
114
  }
79
115
  });
80
116
 
81
117
  // modify import declarations
82
118
  let specifiers = [];
83
- [hasLinkButton ? 'link' : null, hasIconButton ? 'icon' : null, hasLinkIconButton ? 'linkIcon' : null].forEach(variant => {
119
+ [hasVariant.linkButton ? 'link' : null, hasVariant.iconButton ? 'icon' : null, hasVariant.linkIconButton ? 'linkIcon' : null].forEach(variant => {
84
120
  if (variant) {
85
121
  specifiers.push(j.importSpecifier(j.identifier(NEW_BUTTON_VARIANTS[variant])));
86
122
  }
87
123
  });
88
- const oldButtonImport = fileSource.find(j.ImportDeclaration).filter(path => path.node.source.value === NEW_BUTTON_ENTRY_POINT || path.node.source.value === entryPointsMapping.Button || path.node.source.value === entryPointsMapping.LoadingButton);
89
- const remainingDefaultButtons = fileSource.find(j.JSXElement).filter(path => path.value.openingElement.name.type === 'JSXIdentifier' && path.value.openingElement.name.name === specifierIdentifier && !ifHasUnsupportedProps(path.value.openingElement.attributes)).length > 0 || fileSource.find(j.CallExpression).filter(path => path.node.arguments.map(argument => argument.type === 'Identifier' && (argument === null || argument === void 0 ? void 0 : argument.name)).includes(specifierIdentifier)).length > 0;
124
+ const remainingDefaultButtons = fileSource.find(j.JSXElement).filter(path => path.value.openingElement.name.type === 'JSXIdentifier' && Boolean(oldDefaultImportSpecifiers === null || oldDefaultImportSpecifiers === void 0 ? void 0 : oldDefaultImportSpecifiers.includes(path.value.openingElement.name.name)) && !ifHasUnsupportedProps(path.value.openingElement.attributes)).length > 0 || fileSource.find(j.CallExpression).filter(path => path.node.arguments.map(argument => argument.type === 'Identifier' && (argument === null || argument === void 0 ? void 0 : argument.name)).some(name => name && (oldDefaultImportSpecifiers === null || oldDefaultImportSpecifiers === void 0 ? void 0 : oldDefaultImportSpecifiers.includes(name)))).length > 0;
90
125
 
91
126
  // Loading buttons map back to default imports
92
- if (specifierIdentifier === loadingButtonImportName) {
127
+ if (loadingButtonDirectImportName && oldDefaultImportSpecifiers !== null && oldDefaultImportSpecifiers !== void 0 && oldDefaultImportSpecifiers.includes(loadingButtonDirectImportName)) {
93
128
  fileSource
94
129
  // rename LoadingButton to Button in all call expressions i.e. render(LoadingButton), find(LoadingButton)
95
130
  .find(j.CallExpression).find(j.Identifier).forEach(path => {
96
- if (path.node.name === loadingButtonImportName) {
131
+ if (path.node.name === loadingButtonDirectImportName) {
97
132
  path.node.name = NEW_BUTTON_VARIANTS.default;
98
133
  }
99
134
  });
100
- specifiers.push(j.importDefaultSpecifier(j.identifier(NEW_BUTTON_VARIANTS.default)));
101
135
  }
102
- if (!specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier') && remainingDefaultButtons) {
136
+
137
+ // Only add the Button import if we found a default button, not icon only
138
+ if (hasVariant.defaultButton || !specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier') && remainingDefaultButtons) {
103
139
  specifiers.push(j.importDefaultSpecifier(j.identifier(NEW_BUTTON_VARIANTS.default)));
104
140
  }
105
141
 
106
142
  // update import path for types imports
107
- specifiers = importTypesFromNewEntryPoint(buttonImports, specifiers, j, fileSource);
108
- const buttonsWithUnsupportedProps = allButtons.filter(path => ifHasUnsupportedProps(path.value.openingElement.attributes));
109
- if (buttonsWithUnsupportedProps.length) {
143
+ specifiers = importTypesFromNewEntryPoint(oldButtonImports, specifiers, j, fileSource);
144
+ const oldButtonsWithUnsupportedProps = oldButtonElements.filter(path => ifHasUnsupportedProps(path.value.openingElement.attributes));
145
+ if (oldButtonsWithUnsupportedProps.length) {
110
146
  // add comment to all buttons with unsupported props: "component", "style", "css"
111
- buttonsWithUnsupportedProps.forEach(element => {
147
+ oldButtonsWithUnsupportedProps.forEach(element => {
112
148
  var _element$value$openin;
113
149
  const attribute = (_element$value$openin = element.value.openingElement.attributes) === null || _element$value$openin === void 0 ? void 0 : _element$value$openin.find(node => node.type === 'JSXAttribute' && typeof node.name.name === 'string' && unsupportedProps.includes(node.name.name));
114
150
  if (attribute) {
@@ -118,16 +154,53 @@ const transformer = (file, api) => {
118
154
 
119
155
  // rename all buttons with unsupported props to LegacyButton if default new button is imported
120
156
  if (specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier')) {
121
- renameDefaultButtonToLegacyButtonImport(oldButtonImport, buttonsWithUnsupportedProps, j);
157
+ renameDefaultButtonToLegacyButtonImport(oldButtonImports, oldButtonsWithUnsupportedProps, j);
122
158
  }
123
159
  }
124
160
  if (specifiers.length) {
125
- const newButtonImport = j.importDeclaration(specifiers, j.stringLiteral(NEW_BUTTON_ENTRY_POINT));
126
- oldButtonImport.forEach(path => {
161
+ const existingNewButtonImports = fileSource.find(j.ImportDeclaration).filter(path => path.node.source.value === NEW_BUTTON_ENTRY_POINT);
162
+ let newButtonImport = j.importDeclaration(specifiers, j.stringLiteral(NEW_BUTTON_ENTRY_POINT));
163
+ oldButtonImports.forEach(path => {
127
164
  var _path$node;
128
165
  newButtonImport.comments = path !== null && path !== void 0 && (_path$node = path.node) !== null && _path$node !== void 0 && _path$node.comments ? path.node.comments : undefined;
129
166
  });
130
- oldButtonImport.replaceWith(newButtonImport);
167
+ if (existingNewButtonImports.length) {
168
+ existingNewButtonImports.forEach(path => {
169
+ // Merge specifiers from existing new button import with added specifiers
170
+ const mergedSpecifiers = [...specifiers.filter(specifier => {
171
+ var _path$node$specifiers;
172
+ if (specifier.type !== 'ImportDefaultSpecifier') {
173
+ return true;
174
+ }
175
+
176
+ // Ensure we don't add a duplicate default specifier if one is already imported
177
+ return !((_path$node$specifiers = path.node.specifiers) !== null && _path$node$specifiers !== void 0 && _path$node$specifiers.find(s => s.type === 'ImportDefaultSpecifier'));
178
+ }), ...(path.node.specifiers ? path.node.specifiers : [])]
179
+ // Filter duplicates
180
+ .filter(specifier => {
181
+ if (specifier.type === 'ImportDefaultSpecifier') {
182
+ return true;
183
+ }
184
+ const isAlreadyImported = specifiers.find(s => {
185
+ var _specifier$local;
186
+ return s.type === 'ImportSpecifier' && s.imported.name === ((_specifier$local = specifier.local) === null || _specifier$local === void 0 ? void 0 : _specifier$local.name);
187
+ });
188
+ return !isAlreadyImported;
189
+ });
190
+ newButtonImport = j.importDeclaration(mergedSpecifiers, j.stringLiteral(NEW_BUTTON_ENTRY_POINT));
191
+ });
192
+ oldButtonImports.remove();
193
+ existingNewButtonImports.replaceWith(newButtonImport);
194
+ } else {
195
+ oldButtonImports.replaceWith((_, index) => {
196
+ // Only replace the first import
197
+ if (index === 0) {
198
+ return newButtonImport;
199
+ }
200
+ // Remove the rest
201
+ return null;
202
+ });
203
+ }
131
204
  }
132
205
 
133
206
  // remove empty import declarations
@@ -1,4 +1,6 @@
1
1
  import { PRINT_SETTINGS, entryPointsMapping, BUTTON_TYPES, NEW_BUTTON_ENTRY_POINT } from '../utils/constants';
2
+ import renameElements from '../utils/rename-elements';
3
+ import { getDefaultImportSpecifierName } from '@hypermod/utils';
2
4
  const transformer = (file, api) => {
3
5
  const j = api.jscodeshift;
4
6
  const fileSource = j(file.source);
@@ -14,17 +16,25 @@ const transformer = (file, api) => {
14
16
  specifiers,
15
17
  source
16
18
  } = node.node;
19
+
20
+ // Return early if the import is not a named import
17
21
  if ([...Object.values(entryPointsMapping), NEW_BUTTON_ENTRY_POINT, '@atlaskit/button/types'].includes(source.value)) {
18
22
  return fileSource.toSource(PRINT_SETTINGS);
19
23
  }
20
24
  const defaultSpecifier = specifiers === null || specifiers === void 0 ? void 0 : specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier');
21
25
  if (defaultSpecifier && defaultSpecifier.local) {
22
- const defaultButtonImport = j.importDeclaration([j.importDefaultSpecifier(j.identifier(defaultSpecifier.local.name))], j.stringLiteral(entryPointsMapping.Button));
23
- j(node).insertAfter(defaultButtonImport);
26
+ const existingSpecifier = getDefaultImportSpecifierName(j, fileSource, entryPointsMapping.Button);
27
+ if (existingSpecifier) {
28
+ var _defaultSpecifier$loc;
29
+ renameElements((_defaultSpecifier$loc = defaultSpecifier.local) === null || _defaultSpecifier$loc === void 0 ? void 0 : _defaultSpecifier$loc.name, existingSpecifier, fileSource, j);
30
+ } else {
31
+ const newImport = j.importDeclaration([j.importDefaultSpecifier(j.identifier(defaultSpecifier.local.name))], j.stringLiteral(entryPointsMapping.Button));
32
+ j(node).insertAfter(newImport);
33
+ }
24
34
  }
25
- const valueSpecifiers = specifiers === null || specifiers === void 0 ? void 0 : specifiers.filter(specifier => specifier.type === 'ImportSpecifier');
26
- const newTypeSpecifier = valueSpecifiers === null || valueSpecifiers === void 0 ? void 0 : valueSpecifiers.filter(specifier => specifier.type === 'ImportSpecifier' && (specifier.imported.name === 'Appearance' || specifier.imported.name === 'Spacing'));
27
- const otherTypeSpecifiers = valueSpecifiers === null || valueSpecifiers === void 0 ? void 0 : valueSpecifiers.filter(specifier => BUTTON_TYPES.includes(specifier.imported.name));
35
+ const namedSpecifiers = specifiers === null || specifiers === void 0 ? void 0 : specifiers.filter(specifier => specifier.type === 'ImportSpecifier');
36
+ const newTypeSpecifier = namedSpecifiers === null || namedSpecifiers === void 0 ? void 0 : namedSpecifiers.filter(specifier => specifier.type === 'ImportSpecifier' && (specifier.imported.name === 'Appearance' || specifier.imported.name === 'Spacing'));
37
+ const otherTypeSpecifiers = namedSpecifiers === null || namedSpecifiers === void 0 ? void 0 : namedSpecifiers.filter(specifier => BUTTON_TYPES.includes(specifier.imported.name));
28
38
  if (newTypeSpecifier !== null && newTypeSpecifier !== void 0 && newTypeSpecifier.length) {
29
39
  const typeImport = j.importDeclaration(newTypeSpecifier, j.stringLiteral('@atlaskit/button/types'));
30
40
  j(node).insertAfter(typeImport);
@@ -33,11 +43,17 @@ const transformer = (file, api) => {
33
43
  const typeImport = j.importDeclaration(otherTypeSpecifiers, j.stringLiteral('@atlaskit/button'));
34
44
  j(node).insertAfter(typeImport);
35
45
  }
36
- if (valueSpecifiers !== null && valueSpecifiers !== void 0 && valueSpecifiers.length) {
37
- valueSpecifiers.forEach(specifier => {
46
+ if (namedSpecifiers !== null && namedSpecifiers !== void 0 && namedSpecifiers.length) {
47
+ namedSpecifiers.forEach(specifier => {
38
48
  if (specifier.local && specifier.type === 'ImportSpecifier' && specifier.local.name && entryPointsMapping[specifier.imported.name]) {
39
- const newImport = j.importDeclaration([j.importDefaultSpecifier(j.identifier(specifier.local.name))], j.stringLiteral(entryPointsMapping[specifier.imported.name]));
40
- j(node).insertAfter(newImport);
49
+ const existingSpecifier = getDefaultImportSpecifierName(j, fileSource, entryPointsMapping[specifier.imported.name]);
50
+ if (existingSpecifier) {
51
+ var _specifier$local;
52
+ renameElements((_specifier$local = specifier.local) === null || _specifier$local === void 0 ? void 0 : _specifier$local.name, existingSpecifier, fileSource, j);
53
+ } else {
54
+ const newImport = j.importDeclaration([j.importDefaultSpecifier(j.identifier(specifier.local.name))], j.stringLiteral(entryPointsMapping[specifier.imported.name]));
55
+ j(node).insertAfter(newImport);
56
+ }
41
57
  }
42
58
  });
43
59
  }
@@ -1,13 +1,21 @@
1
1
  export const PRINT_SETTINGS = {
2
2
  quote: 'single'
3
3
  };
4
+
5
+ /** NEW button **/
6
+ export const NEW_BUTTON_ENTRY_POINT = '@atlaskit/button/new';
4
7
  export const NEW_BUTTON_VARIANTS = {
5
8
  default: 'Button',
6
9
  link: 'LinkButton',
7
10
  icon: 'IconButton',
8
11
  linkIcon: 'LinkIconButton'
9
12
  };
10
- export const NEW_BUTTON_ENTRY_POINT = '@atlaskit/button/new';
13
+
14
+ /** OLD button **/
15
+ export const OLD_BUTTON_ENTRY_POINT = '@atlaskit/button';
16
+ export const OLD_BUTTON_VARIANTS = {
17
+ loading: 'LoadingButton'
18
+ };
11
19
  export const entryPointsMapping = {
12
20
  Button: '@atlaskit/button/standard-button',
13
21
  LoadingButton: '@atlaskit/button/loading-button',
@@ -25,4 +33,10 @@ export const linkButtonMissingHrefComment = `"link" and "subtle-link" appearance
25
33
  export const buttonPropsNoLongerSupportedComment = `Buttons with "component", "css" or "style" prop can't be automatically migrated with codemods. Please migrate it manually.`;
26
34
  export const migrateFitContainerButtonToDefaultButtonComment = `Migrated to a default button with text which is from the icon label.`;
27
35
  export const migrateFitContainerButtonToIconButtonComment = `"shouldFitContainer" is not available in icon buttons, please consider using a default button with text.`;
28
- export const customThemeButtonComment = `CustomThemeButton will be deprecated. Please consider migrating to Pressable or Anchor Primitives with custom styles.`;
36
+ export const customThemeButtonComment = `CustomThemeButton will be deprecated. Please consider migrating to Pressable or Anchor Primitives with custom styles.`;
37
+ export const loadingButtonComment = ({
38
+ hasLinkAppearance,
39
+ hasHref
40
+ }) => {
41
+ return `This should be migrated to a new Button with a \`isLoading\` prop. ${hasLinkAppearance ? `"link" and "subtle-link" appearances are not available for new loading buttons."` : ''}${hasHref ? `The \`href\` attribute it not compatible with new loading buttons, because links should not need loading states.` : ''} Please reconsider the design or change the appearance of the button.`;
42
+ };
@@ -48,7 +48,8 @@ export const handleIconAttributes = (element, j, iconRenamed = false) => {
48
48
  // takes precedence over icon label.
49
49
 
50
50
  const buttonAlreadyHasLabelProp = buttonAttributes === null || buttonAttributes === void 0 ? void 0 : buttonAttributes.find(buttonAttribute => buttonAttribute.type === 'JSXAttribute' && buttonAttribute.name.name === 'label');
51
- if (!buttonAlreadyHasLabelProp) {
51
+ const buttonAlreadyHasAriaLabelProp = buttonAttributes === null || buttonAttributes === void 0 ? void 0 : buttonAttributes.find(buttonAttribute => buttonAttribute.type === 'JSXAttribute' && buttonAttribute.name.name === 'aria-label');
52
+ if (!buttonAlreadyHasLabelProp && !buttonAlreadyHasAriaLabelProp) {
52
53
  const labelAttribute = iconAttributes.find(attribute => attribute.type === 'JSXAttribute' && attribute.name.name === 'label');
53
54
  if (labelAttribute && labelAttribute.type === 'JSXAttribute' && iconRenamed) {
54
55
  buttonAttributes === null || buttonAttributes === void 0 ? void 0 : buttonAttributes.unshift(labelAttribute);
@@ -108,12 +109,18 @@ export const generateNewElement = (variant, element, j) => {
108
109
  if (ariaLabelAttr.length) {
109
110
  const hasNoLabelProp = !(attributes !== null && attributes !== void 0 && attributes.find(attribute => attribute.type === 'JSXAttribute' && attribute.name.name === 'label'));
110
111
  if (hasNoLabelProp && attributes) {
111
- attributes.push(j.jsxAttribute(j.jsxIdentifier('label'), ariaLabelAttr.get().node.value));
112
+ attributes.unshift(j.jsxAttribute.from({
113
+ name: j.jsxIdentifier('label'),
114
+ value: j.literal(ariaLabelAttr.get().value.value.value)
115
+ }));
112
116
  }
113
117
  ariaLabelAttr.remove();
114
118
  }
115
119
  }
116
- return j.jsxElement(
117
- // self closing if it's an icon button or icon link button
118
- j.jsxOpeningElement(j.jsxIdentifier(variant), attributes, isIconOrLinkIcon), isIconOrLinkIcon ? null : j.jsxClosingElement(j.jsxIdentifier(variant)), element.children);
120
+ return j.jsxElement.from({
121
+ openingElement: j.jsxOpeningElement(j.jsxIdentifier(variant), attributes, isIconOrLinkIcon),
122
+ // self closing if it's an icon button or icon link button
123
+ closingElement: isIconOrLinkIcon ? null : j.jsxClosingElement(j.jsxIdentifier(variant)),
124
+ children: element.children
125
+ });
119
126
  };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns default import specifiers from the given import declarations.
3
+ */
4
+ export default function getDefaultImports(importDeclarations, j) {
5
+ return importDeclarations.find(j.Specifier).filter(path => path.node.type === 'ImportDefaultSpecifier');
6
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns a list of specifier names from the given specifiers.
3
+ */
4
+ export default function getSpecifierNames(specifiers) {
5
+ return specifiers.length ? specifiers.paths().map(path => path.get().value.local.name) : undefined;
6
+ }
@@ -1,4 +1,10 @@
1
- import { NEW_BUTTON_ENTRY_POINT } from './constants';
2
- export const checkIfVariantAlreadyImported = (variant, fileSource, j) => {
3
- return fileSource.find(j.ImportDeclaration).filter(path => path.node.source.value === NEW_BUTTON_ENTRY_POINT).find(j.ImportSpecifier).filter(path => path.node.imported.name === variant).length > 0;
1
+ export const findVariantAlreadyImported = (variant, entryPoint, fileSource, j, isDefaultSpecifier = false) => {
2
+ const imports = fileSource.find(j.ImportDeclaration).filter(path => path.node.source.value === entryPoint);
3
+ if (isDefaultSpecifier) {
4
+ return imports.find(j.ImportDefaultSpecifier);
5
+ }
6
+ return imports.find(j.ImportSpecifier).filter(path => path.node.imported.name === variant);
7
+ };
8
+ export const checkIfVariantAlreadyImported = (variant, entryPoint, fileSource, j, isDefaultSpecifier = false) => {
9
+ return findVariantAlreadyImported(variant, entryPoint, fileSource, j, isDefaultSpecifier).length > 0;
4
10
  };
@@ -0,0 +1,12 @@
1
+ export default function renameElements(elementName, newElementName, fileSource, j) {
2
+ const oldElements = fileSource.find(j.JSXElement).filter(path => {
3
+ return path.value.openingElement.name.type === 'JSXIdentifier' && path.value.openingElement.name.name === elementName;
4
+ });
5
+
6
+ // Rename elements to match existing import name
7
+ oldElements.forEach(element => {
8
+ var _element$value$childr, _element$value$childr2;
9
+ const newElement = j.jsxElement(j.jsxOpeningElement(j.jsxIdentifier(newElementName), element.value.openingElement.attributes, ((_element$value$childr = element.value.children) === null || _element$value$childr === void 0 ? void 0 : _element$value$childr.length) === 0), ((_element$value$childr2 = element.value.children) === null || _element$value$childr2 === void 0 ? void 0 : _element$value$childr2.length) === 0 ? null : j.jsxClosingElement(j.jsxIdentifier(newElementName)), element.value.children);
10
+ j(element).replaceWith(newElement);
11
+ });
12
+ }
package/dist/esm/main.js CHANGED
@@ -298,7 +298,7 @@ function _main() {
298
298
  case 4:
299
299
  _yield$parseArgs = _context5.sent;
300
300
  packages = _yield$parseArgs.packages;
301
- _process$env$_PACKAGE = "0.24.1", _PACKAGE_VERSION_ = _process$env$_PACKAGE === void 0 ? '0.0.0-dev' : _process$env$_PACKAGE;
301
+ _process$env$_PACKAGE = "0.24.3", _PACKAGE_VERSION_ = _process$env$_PACKAGE === void 0 ? '0.0.0-dev' : _process$env$_PACKAGE;
302
302
  logger.log(chalk.bgBlue(chalk.black("\uD83D\uDCDA Atlassian-Frontend codemod library @ ".concat(_PACKAGE_VERSION_, " \uD83D\uDCDA"))));
303
303
  if (packages && packages.length > 0) {
304
304
  logger.log(chalk.gray("Searching for codemods for newer versions of the following packages: ".concat(packages.map(function (pkg) {