From 744fba9db190c345d5642157ba394b4f1e521049 Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Fri, 6 Sep 2024 13:50:10 -0500 Subject: [PATCH 1/7] feat(notification): rename StaticNotification to Callout --- docs/experimental-code.md | 6 +- .../Notification/Notification-test.js | 40 ++++++++ .../components/Notification/Notification.mdx | 19 ++-- .../components/Notification/Notification.tsx | 53 +++++++--- .../src/components/Notification/index.tsx | 2 + .../Notification/stories/Callout.stories.js | 98 ++++++++++++++++++ .../stories/StaticNotification.mdx | 0 .../stories/StaticNotification.stories.js | 86 +--------------- packages/react/src/index.js | 3 +- packages/upgrade/src/upgrades.js | 31 ++++++ ...ame-staticnotification-to-callout.input.ts | 37 +++++++ ...me-staticnotification-to-callout.output.ts | 37 +++++++ .../rename-staticnotification-to-callout.js | 12 +++ .../rename-staticnotification-to-callout.js | 99 +++++++++++++++++++ 14 files changed, 413 insertions(+), 110 deletions(-) create mode 100644 packages/react/src/components/Notification/stories/Callout.stories.js create mode 100644 packages/react/src/components/Notification/stories/StaticNotification.mdx create mode 100644 packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts create mode 100644 packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts create mode 100644 packages/upgrade/transforms/__tests__/rename-staticnotification-to-callout.js create mode 100644 packages/upgrade/transforms/rename-staticnotification-to-callout.js diff --git a/docs/experimental-code.md b/docs/experimental-code.md index 3c938083869d..30b92124a901 100644 --- a/docs/experimental-code.md +++ b/docs/experimental-code.md @@ -38,20 +38,20 @@ const unstable_meta = { // An unstable component will retain its name, specifically for things like // the rules of hooks plugin which depend on the correct casing of the name -function StaticNotification(props) { +function ComponentName(props) { // ... } // However, when we export the component we will export it with the `unstable_` // prefix. (Similar to React.unstable_Suspense, React.unstable_Profiler) -export { default as unstable_StaticNotification } from './components/StaticNotification'; +export { default as unstable_ComponentName } from './components/ComponentName'; ``` For teams using these features, they will need to import the functionality by using the `unstable_` prefix. For example: ```jsx -import { unstable_StaticNotification as StaticNotification } from '@carbon/react'; +import { unstable_ComponentName as ComponentName } from '@carbon/react'; ``` ### Documenting components and exports prefixed with `unstable_` diff --git a/packages/react/src/components/Notification/Notification-test.js b/packages/react/src/components/Notification/Notification-test.js index 33f482a43a64..259768bb9f95 100644 --- a/packages/react/src/components/Notification/Notification-test.js +++ b/packages/react/src/components/Notification/Notification-test.js @@ -12,6 +12,8 @@ import { ToastNotification, InlineNotification, ActionableNotification, + StaticNotification, + Callout, } from './Notification'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; @@ -337,3 +339,41 @@ describe('ActionableNotification', () => { }); }); }); + +// TODO: Remove StaticNotification tests when Callout moves to stable OR in +// v12, whichever is first. Ensure test parity on Callout. +describe('StaticNotification', () => { + it('logs a deprecation notice when used', () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + + expect(() => { + render(); + }).not.toThrow(); + + expect(spy).toHaveBeenCalledWith( + 'Warning: `StaticNotification` has been renamed to `Callout`.' + + 'Run the following codemod to automatically update usages in your' + + 'project: `npx @carbon/upgrade migrate rename-staticnotification-to-callout --write`' + ); + spy.mockRestore(); + }); +}); + +describe('Callout', () => { + it('enforces aria-describedby on interactive children elements', () => { + const spy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + expect(() => { + render( + + + + ); + }).not.toThrow(); + + expect(spy).not.toHaveBeenCalled(); + spy.mockRestore(); + }); +}); diff --git a/packages/react/src/components/Notification/Notification.mdx b/packages/react/src/components/Notification/Notification.mdx index 12fc837e518d..1d21e2114e19 100644 --- a/packages/react/src/components/Notification/Notification.mdx +++ b/packages/react/src/components/Notification/Notification.mdx @@ -20,7 +20,7 @@ import { Canvas, ArgTypes, Meta } from '@storybook/blocks'; There are 4 different types of notification components: `ActionableNotification`, `InlineNotification`, `ToastNotification`, and -`unstable__StaticNotification`. +`unstable__Callout`. ### ActionableNotification @@ -37,19 +37,18 @@ focus until the action is acted upon or the notification is dismissed. elements or rich text. These are announced by screenreaders when rendered. They don't grab focus. Use them to provide the user with an alert, status, or log. -### unstable\_\_StaticNotification +### unstable\_\_Callout (previously StaticNotification) -`unstable__StaticNotification` is non-modal and should only be used inline with -content on the initial render of the page or modal because it will not be -announced to screenreader users like the other notification components. +`unstable__Callout` is non-modal and should only be used inline with content on +the initial render of the page or modal because it will not be announced to +screenreader users like the other notification components. As such, this should not be used for real-time notifications or notifications responding to user input (unless the page is completely refreshing and bumping -the users focus back to the first element in the dom/tab order). If a -StaticNotification is rendered after the initial render, screenreader users' -focus may have already passed this portion of the DOM and they will not know -that the notification has been rendered until they circle back to the beginning -of the page. +the users focus back to the first element in the dom/tab order). If a Callout is +rendered after the initial render, screenreader users' focus may have already +passed this portion of the DOM and they will not know that the notification has +been rendered until they circle back to the beginning of the page. This is the most passive notification component and is essentially just a styled div. If you place actions or interactive elements within this component, place diff --git a/packages/react/src/components/Notification/Notification.tsx b/packages/react/src/components/Notification/Notification.tsx index 83ce57ef3ac1..ee710e97a4c9 100644 --- a/packages/react/src/components/Notification/Notification.tsx +++ b/packages/react/src/components/Notification/Notification.tsx @@ -42,6 +42,7 @@ import { useId } from '../../internal/useId'; import { noopFn } from '../../internal/noopFn'; import wrapFocus, { wrapFocusWithoutSentinels } from '../../internal/wrapFocus'; import { useFeatureFlag } from '../FeatureFlags'; +import { warning } from '../../internal/warning'; /** * Conditionally call a callback when the escape key is pressed @@ -848,7 +849,7 @@ export interface ActionableNotificationProps /** * @deprecated This prop will be removed in the next major version, v12. - * Specify if focus should be moved to the component on render. To meet the spec for alertdialog, this must always be true. If you're setting this to false, explore using StaticNotification instead. https://github.com/carbon-design-system/carbon/pull/15532 + * Specify if focus should be moved to the component on render. To meet the spec for alertdialog, this must always be true. If you're setting this to false, explore using Callout instead. https://github.com/carbon-design-system/carbon/pull/15532 */ hasFocus?: boolean; @@ -1125,10 +1126,14 @@ ActionableNotification.propTypes = { closeOnEscape: PropTypes.bool, /** - * Deprecated, please use StaticNotification once it's available. Issue #15532 * Specify if focus should be moved to the component when the notification contains actions */ - hasFocus: deprecate(PropTypes.bool), + hasFocus: deprecate( + PropTypes.bool, + 'hasFocus is deprecated. To conform to accessibility requirements hasFocus ' + + 'should always be `true` for ActionableNotification. If you were ' + + 'setting this prop to `false`, consider using the Callout component instead.' + ), /** * Specify the close button should be disabled, or not @@ -1195,12 +1200,11 @@ ActionableNotification.propTypes = { }; /** - * StaticNotification + * Callout * ================== */ -export interface StaticNotificationProps - extends HTMLAttributes { +export interface CalloutProps extends HTMLAttributes { /** * Pass in the action button label that will be rendered within the ActionableNotification. */ @@ -1228,7 +1232,7 @@ export interface StaticNotificationProps | 'warning-alt'; /** - * Specify whether you are using the low contrast variant of the StaticNotification. + * Specify whether you are using the low contrast variant of the Callout. */ lowContrast?: boolean; @@ -1258,7 +1262,7 @@ export interface StaticNotificationProps titleId?: string; } -export function StaticNotification({ +export function Callout({ actionButtonLabel, children, onActionButtonClick, @@ -1270,7 +1274,7 @@ export function StaticNotification({ kind = 'error', lowContrast, ...rest -}: StaticNotificationProps) { +}: CalloutProps) { const prefix = usePrefix(); const containerClassName = cx(className, { [`${prefix}--actionable-notification`]: true, @@ -1326,7 +1330,7 @@ export function StaticNotification({ ); } -StaticNotification.propTypes = { +Callout.propTypes = { /** * Pass in the action button label that will be rendered within the ActionableNotification. */ @@ -1355,7 +1359,7 @@ StaticNotification.propTypes = { ]), /** - * Specify whether you are using the low contrast variant of the StaticNotification. + * Specify whether you are using the low contrast variant of the Callout. */ lowContrast: PropTypes.bool, @@ -1384,3 +1388,30 @@ StaticNotification.propTypes = { */ titleId: PropTypes.string, }; + +// In renaming StaticNotification to Callout, the legacy StaticNotification +// export and it's types should remain usable until Callout is moved to stable. +// The StaticNotification component below forwards props to Callout and inherits +// CalloutProps to ensure consumer usage is not impacted, while providing them +// a deprecation warning. +// TODO: remove this when Callout moves to stable OR in v12, whichever is first +/** + * @deprecated Use `CalloutProps` instead. + */ +export interface StaticNotificationProps extends CalloutProps {} +let didWarnAboutDeprecation = false; +export const StaticNotification: React.FC = ( + props +) => { + if (__DEV__) { + warning( + didWarnAboutDeprecation, + '`StaticNotification` has been renamed to `Callout`.' + + 'Run the following codemod to automatically update usages in your' + + 'project: `npx @carbon/upgrade migrate rename-staticnotification-to-callout --write`' + ); + didWarnAboutDeprecation = true; + } + + return ; +}; diff --git a/packages/react/src/components/Notification/index.tsx b/packages/react/src/components/Notification/index.tsx index a28475e11150..37432616086e 100644 --- a/packages/react/src/components/Notification/index.tsx +++ b/packages/react/src/components/Notification/index.tsx @@ -18,4 +18,6 @@ export { type ActionableNotificationProps, StaticNotification, type StaticNotificationProps, + Callout, + type CalloutProps, } from './Notification'; diff --git a/packages/react/src/components/Notification/stories/Callout.stories.js b/packages/react/src/components/Notification/stories/Callout.stories.js new file mode 100644 index 000000000000..530c3f3a3aac --- /dev/null +++ b/packages/react/src/components/Notification/stories/Callout.stories.js @@ -0,0 +1,98 @@ +/** + * Copyright IBM Corp. 2016, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import { Callout } from '../../Notification'; +import { Link } from '../../Link'; +import mdx from '../Notification.mdx'; + +export default { + title: 'Experimental/unstable__Callout', + component: Callout, + parameters: { + docs: { + page: mdx, + }, + }, + args: { + kind: 'error', + lowContrast: false, + statusIconDescription: 'notification', + }, +}; + +export const Default = () => ( + +); + +export const WithInteractiveElements = () => ( + +
+ Additional text can describe the notification, or a link to{' '} + + learn more + +
+
+); + +export const WithActionButtonOnly = () => ( + +
+ Here is some important info you might want to know.{' '} +
+
+); + +export const WithActionButtonAndLinks = () => ( + +
+ + Create + {' '} + or{' '} + + register + {' '} + a cluster before creating a Configuration. Some additional info could go + here to show that this notification subtitle goes below the title. +
+
+); + +export const Playground = (args) => ; + +Playground.argTypes = { + children: { + table: { + disable: true, + }, + }, + className: { + table: { + disable: true, + }, + }, +}; +Playground.args = { + title: 'Notification title', + subtitle: 'Subtitle text goes here', +}; diff --git a/packages/react/src/components/Notification/stories/StaticNotification.mdx b/packages/react/src/components/Notification/stories/StaticNotification.mdx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/react/src/components/Notification/stories/StaticNotification.stories.js b/packages/react/src/components/Notification/stories/StaticNotification.stories.js index c396f85d90e5..5c08736e9df4 100644 --- a/packages/react/src/components/Notification/stories/StaticNotification.stories.js +++ b/packages/react/src/components/Notification/stories/StaticNotification.stories.js @@ -6,97 +6,13 @@ */ import React from 'react'; -import { StaticNotification } from '../../Notification'; -import { Link } from '../../Link'; -import mdx from '../Notification.mdx'; +import mdx from './StaticNotification.mdx'; -// eslint-disable-next-line storybook/csf-component export default { title: 'Experimental/unstable__StaticNotification', - component: StaticNotification, parameters: { docs: { page: mdx, }, }, - args: { - kind: 'error', - lowContrast: false, - statusIconDescription: 'notification', - }, -}; - -export const Default = () => ( - -); - -export const WithInteractiveElements = () => ( - -
- Additional text can describe the notification, or a link to{' '} - - learn more - -
-
-); - -export const WithActionButtonOnly = () => ( - -
- Here is some important info you might want to know.{' '} -
-
-); - -export const WithActionButtonAndLinks = () => ( - -
- - Create - {' '} - or{' '} - - register - {' '} - a cluster before creating a Configuration. Some additional info could go - here to show that this notification subtitle goes below the title. -
-
-); - -export const Playground = (args) => ; - -Playground.argTypes = { - children: { - table: { - disable: true, - }, - }, - className: { - table: { - disable: true, - }, - }, -}; -Playground.args = { - title: 'Notification title', - subtitle: 'Subtitle text goes here', }; diff --git a/packages/react/src/index.js b/packages/react/src/index.js index e8d501b2f2cb..8700ebd8142e 100644 --- a/packages/react/src/index.js +++ b/packages/react/src/index.js @@ -106,7 +106,8 @@ export { InlineNotification, NotificationActionButton, NotificationButton, - StaticNotification as unstable__StaticNotification, + Callout as unstable__Callout, + Callout as unstable__StaticNotification, } from './components/Notification'; export { NumberInput, NumberInputSkeleton } from './components/NumberInput'; export { OrderedList } from './components/OrderedList'; diff --git a/packages/upgrade/src/upgrades.js b/packages/upgrade/src/upgrades.js index 486937bb7a06..df609f2fe46c 100644 --- a/packages/upgrade/src/upgrades.js +++ b/packages/upgrade/src/upgrades.js @@ -291,6 +291,37 @@ export const upgrades = [ }); }, }, + { + name: 'rename-staticnotification-to-callout', + description: + 'Rewrites imports and usages of StaticNotification to Callout', + migrate: async (options) => { + const transform = path.join( + TRANSFORM_DIR, + 'rename-staticnotification-to-callout.js' + ); + const paths = + Array.isArray(options.paths) && options.paths.length > 0 + ? options.paths + : await glob(['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'], { + cwd: options.workspaceDir, + ignore: [ + '**/es/**', + '**/lib/**', + '**/umd/**', + '**/node_modules/**', + '**/storybook-static/**', + ], + }); + + await run({ + dry: !options.write, + transform, + paths, + verbose: options.verbose, + }); + }, + }, ], }, { diff --git a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts new file mode 100644 index 000000000000..562b7ef8d6aa --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts @@ -0,0 +1,37 @@ +// Test lots of different import configurations, even though this would never be +// valid in a real context +import { unstable__StaticNotification } from '@carbon/react'; +import { unstable__StaticNotification as StaticNotification } from '@carbon/react'; +import { StaticNotification } from '@carbon/react'; +import { StaticNotification } from '@carbon/react/es/components/Notification'; +import { StaticNotification } from '@carbon/react/lib/components/Notification'; +import { StaticNotification as Callout } from '@carbon/react'; +import { StaticNotification as SomeOtherName } from '@carbon/react'; +import { unstable__Callout } from '@carbon/react'; +import { unstable__Callout as SomeOtherOtherName } from '@carbon/react'; +import { unstable__Callout as StaticNotification } from '@carbon/react'; +import { StaticNotificationProps } from '@carbon/react/es/components/Notification'; +import { StaticNotificationProps } from '@carbon/react/lib/components/Notification'; + +// Redefine the component with props to ensure type interface is handled +const Notification: React.FC = (props) => { + return ; +}; + +// Local renames like this are unlikely but technically possible +const LocallyRenamedStaticNotification = unstable__StaticNotification; +const LocallyRenamedCallout = unstable__Callout; + +// Component usages +const App = () => { + return ( + <> + + + + + + + + ); +}; diff --git a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts new file mode 100644 index 000000000000..df4fea497f5b --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts @@ -0,0 +1,37 @@ +// Test lots of different import configurations, even though this would never be +// valid in a real context +import { unstable__Callout } from '@carbon/react'; +import { unstable__Callout as StaticNotification } from '@carbon/react'; +import { Callout } from '@carbon/react'; +import { Callout } from '@carbon/react/es/components/Notification'; +import { Callout } from '@carbon/react/lib/components/Notification'; +import { Callout } from '@carbon/react'; +import { unstable__Callout as SomeOtherName } from '@carbon/react'; +import { unstable__Callout } from '@carbon/react'; +import { unstable__Callout as SomeOtherOtherName } from '@carbon/react'; +import { unstable__Callout as StaticNotification } from '@carbon/react'; +import { StaticNotificationProps } from '@carbon/react/es/components/Notification'; +import { StaticNotificationProps } from '@carbon/react/lib/components/Notification'; + +// Redefine the component with props to ensure type interface is handled +const Notification: React.FC = (props) => { + return ; +}; + +// Local renames like this are unlikely but technically possible +const LocallyRenamedStaticNotification = unstable__Callout; +const LocallyRenamedCallout = unstable__Callout; + +// Component usages +const App = () => { + return ( + <> + + + + + + + + ); +}; diff --git a/packages/upgrade/transforms/__tests__/rename-staticnotification-to-callout.js b/packages/upgrade/transforms/__tests__/rename-staticnotification-to-callout.js new file mode 100644 index 000000000000..be674e2cf044 --- /dev/null +++ b/packages/upgrade/transforms/__tests__/rename-staticnotification-to-callout.js @@ -0,0 +1,12 @@ +/** + * Copyright IBM Corp. 2016, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const { defineTest } = require('jscodeshift/dist/testUtils'); + +defineTest(__dirname, 'rename-staticnotification-to-callout'); diff --git a/packages/upgrade/transforms/rename-staticnotification-to-callout.js b/packages/upgrade/transforms/rename-staticnotification-to-callout.js new file mode 100644 index 000000000000..aeaf9cb7e6e7 --- /dev/null +++ b/packages/upgrade/transforms/rename-staticnotification-to-callout.js @@ -0,0 +1,99 @@ +// codemod.js +function transform(fileInfo, api, options) { + const printOptions = options.printOptions || defaultOptions; + const j = api.jscodeshift; + const root = j(fileInfo.source); + + // Update import declarations + root.find(j.ImportDeclaration).forEach((path) => { + const { specifiers } = path.node; + + specifiers.forEach((specifier) => { + const { imported, local } = specifier; + + // Early return if not dealing with StaticNotification or unstable__StaticNotification + if (!local || !imported) return; + + const { name: importedName } = imported; + const { name: localName } = local; + + // Skip transformation if `unstable__Callout` is already renamed to `StaticNotification` + if ( + importedName === 'unstable__Callout' && + localName === 'StaticNotification' + ) { + return; // Leave as is + } + + // Prefer transforming to unstable__Callout over Callout, unless already imported + if (importedName === 'StaticNotification') { + if (localName === 'Callout') { + // If `StaticNotification as Callout` exists, replace it with `unstable__Callout` + specifier.imported.name = 'unstable__Callout'; + specifier.local.name = 'unstable__Callout'; + } else { + // Prefer renaming to `unstable__Callout` where possible + specifier.imported.name = 'unstable__Callout'; + } + } else if (importedName === 'unstable__StaticNotification') { + specifier.imported.name = 'unstable__Callout'; + } + + // Handle aliasing (e.g., "as StaticNotification") + if (localName === 'StaticNotification') { + specifier.local.name = 'unstable__Callout'; + } else if (localName === 'unstable__StaticNotification') { + specifier.local.name = 'unstable__Callout'; + } + }); + }); + + // Update JSX elements and usage within code + root + .find(j.JSXIdentifier, { name: 'StaticNotification' }) + .replaceWith(j.jsxIdentifier('unstable__Callout')); + + root + .find(j.JSXIdentifier, { name: 'unstable__StaticNotification' }) + .replaceWith(j.jsxIdentifier('unstable__Callout')); + + root + .find(j.Identifier, { name: 'StaticNotification' }) + .replaceWith(j.identifier('unstable__Callout')); + + root + .find(j.Identifier, { name: 'unstable__StaticNotification' }) + .replaceWith(j.identifier('unstable__Callout')); + + // Update TypeScript interface names (StaticNotificationProps -> CalloutProps) + root + .find(j.Identifier, { name: 'StaticNotificationProps' }) + .replaceWith(j.identifier('CalloutProps')); + + // FINAL STEP: Rename all `unstable__Callout` to `Callout` + root.find(j.ImportSpecifier).forEach((path) => { + const { imported, local } = path.node; + + if (imported.name === 'unstable__Callout') { + imported.name = 'Callout'; + + // If the local name is also `unstable__Callout`, rename it to `Callout` + if (local.name === 'unstable__Callout') { + local.name = 'Callout'; + } + } + }); + + // Ensure all JSX and identifiers are also updated to `Callout` + root + .find(j.JSXIdentifier, { name: 'unstable__Callout' }) + .replaceWith(j.jsxIdentifier('Callout')); + + root + .find(j.Identifier, { name: 'unstable__Callout' }) + .replaceWith(j.identifier('Callout')); + + return root.toSource(printOptions); +} + +module.exports = transform; From 4600fa5a7cdb91c1c4327c7375559eb646ee0f68 Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Fri, 6 Sep 2024 13:59:36 -0500 Subject: [PATCH 2/7] chore: update test fixtures --- .../rename-staticnotification-to-callout.input.ts | 1 - .../rename-staticnotification-to-callout.output.ts | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts index 562b7ef8d6aa..e19974a2edd9 100644 --- a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts +++ b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts @@ -9,7 +9,6 @@ import { StaticNotification as Callout } from '@carbon/react'; import { StaticNotification as SomeOtherName } from '@carbon/react'; import { unstable__Callout } from '@carbon/react'; import { unstable__Callout as SomeOtherOtherName } from '@carbon/react'; -import { unstable__Callout as StaticNotification } from '@carbon/react'; import { StaticNotificationProps } from '@carbon/react/es/components/Notification'; import { StaticNotificationProps } from '@carbon/react/lib/components/Notification'; diff --git a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts index df4fea497f5b..c3d6d08dbc89 100644 --- a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts +++ b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts @@ -1,7 +1,7 @@ // Test lots of different import configurations, even though this would never be // valid in a real context import { unstable__Callout } from '@carbon/react'; -import { unstable__Callout as StaticNotification } from '@carbon/react'; +import { unstable__Callout as Callout } from '@carbon/react'; import { Callout } from '@carbon/react'; import { Callout } from '@carbon/react/es/components/Notification'; import { Callout } from '@carbon/react/lib/components/Notification'; @@ -9,13 +9,12 @@ import { Callout } from '@carbon/react'; import { unstable__Callout as SomeOtherName } from '@carbon/react'; import { unstable__Callout } from '@carbon/react'; import { unstable__Callout as SomeOtherOtherName } from '@carbon/react'; -import { unstable__Callout as StaticNotification } from '@carbon/react'; -import { StaticNotificationProps } from '@carbon/react/es/components/Notification'; -import { StaticNotificationProps } from '@carbon/react/lib/components/Notification'; +import { CalloutProps } from '@carbon/react/es/components/Notification'; +import { CalloutProps } from '@carbon/react/lib/components/Notification'; // Redefine the component with props to ensure type interface is handled -const Notification: React.FC = (props) => { - return ; +const Notification: React.FC = (props) => { + return ; }; // Local renames like this are unlikely but technically possible From fc2579c624a82d20a17c93561f6e71dec232df85 Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Fri, 6 Sep 2024 14:23:05 -0500 Subject: [PATCH 3/7] docs(notification): update StaticNotification stories --- .../stories/StaticNotification.mdx | 7 +++++++ .../stories/StaticNotification.stories.js | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/react/src/components/Notification/stories/StaticNotification.mdx b/packages/react/src/components/Notification/stories/StaticNotification.mdx index e69de29bb2d1..0304c6de19ae 100644 --- a/packages/react/src/components/Notification/stories/StaticNotification.mdx +++ b/packages/react/src/components/Notification/stories/StaticNotification.mdx @@ -0,0 +1,7 @@ +# StaticNotification has been renamed to Callout + +Run the following codemod to automatically update usages in your project: + +``` +npx @carbon/upgrade migrate rename-staticnotification-to-callout --write +``` diff --git a/packages/react/src/components/Notification/stories/StaticNotification.stories.js b/packages/react/src/components/Notification/stories/StaticNotification.stories.js index 5c08736e9df4..84842b1c1ac4 100644 --- a/packages/react/src/components/Notification/stories/StaticNotification.stories.js +++ b/packages/react/src/components/Notification/stories/StaticNotification.stories.js @@ -6,6 +6,9 @@ */ import React from 'react'; +import { StaticNotification } from '../../Notification'; +import { Link } from '../../Link'; +import { CodeSnippet } from '../../CodeSnippet'; import mdx from './StaticNotification.mdx'; export default { @@ -16,3 +19,19 @@ export default { }, }, }; + +export const Default = () => ( + <> + + +
+

+ Run the following codemod to automatically update usages in your + project: +

+ + npx @carbon/upgrade migrate rename-staticnotification-to-callout --write + +
+ +); From dd8af3efe310aa4c0d8a0819d8257866d9ff548d Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Mon, 9 Sep 2024 13:45:33 -0500 Subject: [PATCH 4/7] chore: update snaps --- .../__snapshots__/PublicAPI-test.js.snap | 89 ++++++++++--------- packages/react/src/__tests__/index-test.js | 1 + 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 17a2861679ad..6331375820c9 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -609,6 +609,50 @@ Map { "2": "bottom", "3": "left", }, + "Callout" => Object { + "propTypes": Object { + "actionButtonLabel": Object { + "type": "string", + }, + "children": Object { + "type": "node", + }, + "className": Object { + "type": "string", + }, + "kind": Object { + "args": Array [ + Array [ + "error", + "info", + "info-square", + "success", + "warning", + "warning-alt", + ], + ], + "type": "oneOf", + }, + "lowContrast": Object { + "type": "bool", + }, + "onActionButtonClick": Object { + "type": "func", + }, + "statusIconDescription": Object { + "type": "string", + }, + "subtitle": Object { + "type": "node", + }, + "title": Object { + "type": "string", + }, + "titleId": Object { + "type": "string", + }, + }, + }, "Checkbox" => Object { "$$typeof": Symbol(react.forward_ref), "propTypes": Object { @@ -7480,50 +7524,7 @@ Map { }, "render": [Function], }, - "StaticNotification" => Object { - "propTypes": Object { - "actionButtonLabel": Object { - "type": "string", - }, - "children": Object { - "type": "node", - }, - "className": Object { - "type": "string", - }, - "kind": Object { - "args": Array [ - Array [ - "error", - "info", - "info-square", - "success", - "warning", - "warning-alt", - ], - ], - "type": "oneOf", - }, - "lowContrast": Object { - "type": "bool", - }, - "onActionButtonClick": Object { - "type": "func", - }, - "statusIconDescription": Object { - "type": "string", - }, - "subtitle": Object { - "type": "node", - }, - "title": Object { - "type": "string", - }, - "titleId": Object { - "type": "string", - }, - }, - }, + "StaticNotification" => Object {}, "StructuredListBody" => Object { "propTypes": Object { "children": Object { diff --git a/packages/react/src/__tests__/index-test.js b/packages/react/src/__tests__/index-test.js index f83f5635a36a..3fc8a6767449 100644 --- a/packages/react/src/__tests__/index-test.js +++ b/packages/react/src/__tests__/index-test.js @@ -36,6 +36,7 @@ describe('Carbon Components React', () => { "ButtonSkeleton", "ButtonTooltipAlignments", "ButtonTooltipPositions", + "Callout", "Checkbox", "CheckboxGroup", "CheckboxSkeleton", From 78829a5f2c8886a4cb2dcf60b439bbc35d3dc958 Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Fri, 20 Sep 2024 13:20:51 -0500 Subject: [PATCH 5/7] refactor(callout): reworked import portion of codemod --- .../Notification/Notification-test.js | 2 +- .../components/Notification/Notification.tsx | 2 +- .../stories/StaticNotification.mdx | 2 +- .../stories/StaticNotification.stories.js | 2 +- packages/upgrade/src/upgrades.js | 7 +- .../refactor-to-callout.input.js | 24 ++++ .../refactor-to-callout.output.js | 24 ++++ .../refactor-to-callout2.input.js | 4 + .../refactor-to-callout2.output.js | 4 + .../refactor-to-callout3.input.js | 5 + .../refactor-to-callout3.output.js | 5 + .../refactor-to-callout4.input.js | 3 + .../refactor-to-callout4.output.js | 3 + .../refactor-to-callout5.input.ts | 30 +++++ .../refactor-to-callout5.output.ts | 29 +++++ ...ame-staticnotification-to-callout.input.ts | 36 ------ ...me-staticnotification-to-callout.output.ts | 36 ------ .../__tests__/refactor-to-callout.js | 16 +++ .../rename-staticnotification-to-callout.js | 12 -- .../upgrade/transforms/refactor-to-callout.js | 106 ++++++++++++++++++ .../rename-staticnotification-to-callout.js | 99 ---------------- 21 files changed, 259 insertions(+), 192 deletions(-) create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout.input.js create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout2.input.js create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout2.output.js create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout3.input.js create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout3.output.js create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout4.input.js create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout4.output.js create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.input.ts create mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.output.ts delete mode 100644 packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts delete mode 100644 packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts create mode 100644 packages/upgrade/transforms/__tests__/refactor-to-callout.js delete mode 100644 packages/upgrade/transforms/__tests__/rename-staticnotification-to-callout.js create mode 100644 packages/upgrade/transforms/refactor-to-callout.js delete mode 100644 packages/upgrade/transforms/rename-staticnotification-to-callout.js diff --git a/packages/react/src/components/Notification/Notification-test.js b/packages/react/src/components/Notification/Notification-test.js index 259768bb9f95..8e26e4aa6a88 100644 --- a/packages/react/src/components/Notification/Notification-test.js +++ b/packages/react/src/components/Notification/Notification-test.js @@ -353,7 +353,7 @@ describe('StaticNotification', () => { expect(spy).toHaveBeenCalledWith( 'Warning: `StaticNotification` has been renamed to `Callout`.' + 'Run the following codemod to automatically update usages in your' + - 'project: `npx @carbon/upgrade migrate rename-staticnotification-to-callout --write`' + 'project: `npx @carbon/upgrade migrate refactor-to-callout --write`' ); spy.mockRestore(); }); diff --git a/packages/react/src/components/Notification/Notification.tsx b/packages/react/src/components/Notification/Notification.tsx index ee710e97a4c9..ed089e2778dd 100644 --- a/packages/react/src/components/Notification/Notification.tsx +++ b/packages/react/src/components/Notification/Notification.tsx @@ -1408,7 +1408,7 @@ export const StaticNotification: React.FC = ( didWarnAboutDeprecation, '`StaticNotification` has been renamed to `Callout`.' + 'Run the following codemod to automatically update usages in your' + - 'project: `npx @carbon/upgrade migrate rename-staticnotification-to-callout --write`' + 'project: `npx @carbon/upgrade migrate refactor-to-callout --write`' ); didWarnAboutDeprecation = true; } diff --git a/packages/react/src/components/Notification/stories/StaticNotification.mdx b/packages/react/src/components/Notification/stories/StaticNotification.mdx index 0304c6de19ae..d9d578b7d060 100644 --- a/packages/react/src/components/Notification/stories/StaticNotification.mdx +++ b/packages/react/src/components/Notification/stories/StaticNotification.mdx @@ -3,5 +3,5 @@ Run the following codemod to automatically update usages in your project: ``` -npx @carbon/upgrade migrate rename-staticnotification-to-callout --write +npx @carbon/upgrade migrate refactor-to-callout --write ``` diff --git a/packages/react/src/components/Notification/stories/StaticNotification.stories.js b/packages/react/src/components/Notification/stories/StaticNotification.stories.js index 84842b1c1ac4..88c15127f01d 100644 --- a/packages/react/src/components/Notification/stories/StaticNotification.stories.js +++ b/packages/react/src/components/Notification/stories/StaticNotification.stories.js @@ -30,7 +30,7 @@ export const Default = () => ( project:

- npx @carbon/upgrade migrate rename-staticnotification-to-callout --write + npx @carbon/upgrade migrate refactor-to-callout --write diff --git a/packages/upgrade/src/upgrades.js b/packages/upgrade/src/upgrades.js index df609f2fe46c..12a5442a470c 100644 --- a/packages/upgrade/src/upgrades.js +++ b/packages/upgrade/src/upgrades.js @@ -292,14 +292,11 @@ export const upgrades = [ }, }, { - name: 'rename-staticnotification-to-callout', + name: 'refactor-to-callout', description: 'Rewrites imports and usages of StaticNotification to Callout', migrate: async (options) => { - const transform = path.join( - TRANSFORM_DIR, - 'rename-staticnotification-to-callout.js' - ); + const transform = path.join(TRANSFORM_DIR, 'refactor-to-callout.js'); const paths = Array.isArray(options.paths) && options.paths.length > 0 ? options.paths diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.input.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.input.js new file mode 100644 index 000000000000..1a8edc553f74 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.input.js @@ -0,0 +1,24 @@ +// Typical imports +import { unstable__StaticNotification as StaticNotification } from '@carbon/react'; +import { unstable__StaticNotification } from '@carbon/react'; + +// If they used a custom name +import { unstable__StaticNotification as SomeOtherName } from '@carbon/react'; + +// If they already renamed it Callout +import { unstable__StaticNotification as Callout } from '@carbon/react'; + +// Local renames like this are unlikely but technically possible +const LocallyRenamedStaticNotification = unstable__StaticNotification; + +// Component usages +const App = () => { + return ( + <> + + + + + + ); +}; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js new file mode 100644 index 000000000000..228e9e259d87 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js @@ -0,0 +1,24 @@ +// Typical imports +import { unstable__Callout as Callout } from '@carbon/react'; +import { unstable__Callout } from '@carbon/react'; + +// If they used a custom name +import { unstable__Callout as SomeOtherName } from '@carbon/react'; + +// If they already renamed it Callout +import { unstable__Callout as Callout } from '@carbon/react'; + +// Local renames like this are unlikely but technically possible +const LocallyRenamedStaticNotification = unstable__Callout; + +// Component usages +const App = () => { + return ( + <> + + + + + + ); +}; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout2.input.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout2.input.js new file mode 100644 index 000000000000..d688d2116013 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout2.input.js @@ -0,0 +1,4 @@ +// Existing usages should not be transformed +import { unstable__Callout } from '@carbon/react'; +import { unstable__Callout as Callout } from '@carbon/react'; +import { unstable__Callout as SomeOtherOtherName } from '@carbon/react'; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout2.output.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout2.output.js new file mode 100644 index 000000000000..d688d2116013 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout2.output.js @@ -0,0 +1,4 @@ +// Existing usages should not be transformed +import { unstable__Callout } from '@carbon/react'; +import { unstable__Callout as Callout } from '@carbon/react'; +import { unstable__Callout as SomeOtherOtherName } from '@carbon/react'; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout3.input.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout3.input.js new file mode 100644 index 000000000000..0ecf8551ef73 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout3.input.js @@ -0,0 +1,5 @@ +// Do not transform potential naming collisions from local paths +import { unstable__Callout } from './my/local/project'; +import { unstable__Callout as Callout } from './my/local/project'; +import { unstable_StaticNotification } from './my/local/project'; +import { unstable_StaticNotification as StaticNotification } from './my/local/project'; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout3.output.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout3.output.js new file mode 100644 index 000000000000..0ecf8551ef73 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout3.output.js @@ -0,0 +1,5 @@ +// Do not transform potential naming collisions from local paths +import { unstable__Callout } from './my/local/project'; +import { unstable__Callout as Callout } from './my/local/project'; +import { unstable_StaticNotification } from './my/local/project'; +import { unstable_StaticNotification as StaticNotification } from './my/local/project'; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout4.input.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout4.input.js new file mode 100644 index 000000000000..1b10fca69362 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout4.input.js @@ -0,0 +1,3 @@ +// Do not transform potential naming collisions from local paths +import { Callout } from '../my/local/project'; +import { StaticNotification } from './my/local/project'; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout4.output.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout4.output.js new file mode 100644 index 000000000000..1b10fca69362 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout4.output.js @@ -0,0 +1,3 @@ +// Do not transform potential naming collisions from local paths +import { Callout } from '../my/local/project'; +import { StaticNotification } from './my/local/project'; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.input.ts b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.input.ts new file mode 100644 index 000000000000..c6d02c69ce6a --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.input.ts @@ -0,0 +1,30 @@ +// Fully qualified import paths from within es +import { + type StaticNotificationProps, + StaticNotification, +} from '@carbon/react/es/components/Notification'; + +// If the transform results in duplicate imports, they should be deduped +import { unstable__StaticNotification } from '@carbon/react'; +import { unstable__Callout } from '@carbon/react'; + +// Redefine the component with props to ensure type interface is handled +const Notification: React.FC = (props) => { + return ; +}; + +// Local renames like this are unlikely but technically possible +const LocallyRenamedStaticNotification = unstable__StaticNotification; +const LocallyRenamedCallout = unstable__Callout; + +// Component usages +const App = () => { + return ( + <> + + + + + + ); +}; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.output.ts b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.output.ts new file mode 100644 index 000000000000..2ad045c9a76f --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.output.ts @@ -0,0 +1,29 @@ +// Fully qualified import paths from within es +import { + type CalloutProps, + Callout, +} from '@carbon/react/es/components/Notification'; + +// If the transform results in duplicate imports, they should be deduped// note the de +import { unstable__Callout } from '@carbon/react'; + +// Redefine the component with props to ensure type interface is handled +const Notification: React.FC = (props) => { + return ; +}; + +// Local renames like this are unlikely but technically possible +const LocallyRenamedStaticNotification = unstable__Callout; +const LocallyRenamedCallout = unstable__Callout; + +// Component usages +const App = () => { + return ( + <> + + + + + + ); +}; diff --git a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts deleted file mode 100644 index e19974a2edd9..000000000000 --- a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.input.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Test lots of different import configurations, even though this would never be -// valid in a real context -import { unstable__StaticNotification } from '@carbon/react'; -import { unstable__StaticNotification as StaticNotification } from '@carbon/react'; -import { StaticNotification } from '@carbon/react'; -import { StaticNotification } from '@carbon/react/es/components/Notification'; -import { StaticNotification } from '@carbon/react/lib/components/Notification'; -import { StaticNotification as Callout } from '@carbon/react'; -import { StaticNotification as SomeOtherName } from '@carbon/react'; -import { unstable__Callout } from '@carbon/react'; -import { unstable__Callout as SomeOtherOtherName } from '@carbon/react'; -import { StaticNotificationProps } from '@carbon/react/es/components/Notification'; -import { StaticNotificationProps } from '@carbon/react/lib/components/Notification'; - -// Redefine the component with props to ensure type interface is handled -const Notification: React.FC = (props) => { - return ; -}; - -// Local renames like this are unlikely but technically possible -const LocallyRenamedStaticNotification = unstable__StaticNotification; -const LocallyRenamedCallout = unstable__Callout; - -// Component usages -const App = () => { - return ( - <> - - - - - - - - ); -}; diff --git a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts b/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts deleted file mode 100644 index c3d6d08dbc89..000000000000 --- a/packages/upgrade/transforms/__testfixtures__/rename-staticnotification-to-callout.output.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Test lots of different import configurations, even though this would never be -// valid in a real context -import { unstable__Callout } from '@carbon/react'; -import { unstable__Callout as Callout } from '@carbon/react'; -import { Callout } from '@carbon/react'; -import { Callout } from '@carbon/react/es/components/Notification'; -import { Callout } from '@carbon/react/lib/components/Notification'; -import { Callout } from '@carbon/react'; -import { unstable__Callout as SomeOtherName } from '@carbon/react'; -import { unstable__Callout } from '@carbon/react'; -import { unstable__Callout as SomeOtherOtherName } from '@carbon/react'; -import { CalloutProps } from '@carbon/react/es/components/Notification'; -import { CalloutProps } from '@carbon/react/lib/components/Notification'; - -// Redefine the component with props to ensure type interface is handled -const Notification: React.FC = (props) => { - return ; -}; - -// Local renames like this are unlikely but technically possible -const LocallyRenamedStaticNotification = unstable__Callout; -const LocallyRenamedCallout = unstable__Callout; - -// Component usages -const App = () => { - return ( - <> - - - - - - - - ); -}; diff --git a/packages/upgrade/transforms/__tests__/refactor-to-callout.js b/packages/upgrade/transforms/__tests__/refactor-to-callout.js new file mode 100644 index 000000000000..4f3c0047e31d --- /dev/null +++ b/packages/upgrade/transforms/__tests__/refactor-to-callout.js @@ -0,0 +1,16 @@ +/** + * Copyright IBM Corp. 2016, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const { defineTest } = require('jscodeshift/dist/testUtils'); + +defineTest(__dirname, 'refactor-to-callout'); +defineTest(__dirname, 'refactor-to-callout', null, 'refactor-to-callout2'); +defineTest(__dirname, 'refactor-to-callout', null, 'refactor-to-callout3'); +defineTest(__dirname, 'refactor-to-callout', null, 'refactor-to-callout4'); +defineTest(__dirname, 'refactor-to-callout', null, 'refactor-to-callout5'); diff --git a/packages/upgrade/transforms/__tests__/rename-staticnotification-to-callout.js b/packages/upgrade/transforms/__tests__/rename-staticnotification-to-callout.js deleted file mode 100644 index be674e2cf044..000000000000 --- a/packages/upgrade/transforms/__tests__/rename-staticnotification-to-callout.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright IBM Corp. 2016, 2023 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -'use strict'; - -const { defineTest } = require('jscodeshift/dist/testUtils'); - -defineTest(__dirname, 'rename-staticnotification-to-callout'); diff --git a/packages/upgrade/transforms/refactor-to-callout.js b/packages/upgrade/transforms/refactor-to-callout.js new file mode 100644 index 000000000000..7738a4dd737d --- /dev/null +++ b/packages/upgrade/transforms/refactor-to-callout.js @@ -0,0 +1,106 @@ +/** + * Copyright IBM Corp. 2016, 2023 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const defaultOptions = { + quote: 'auto', + trailingComma: true, +}; + +function transform(fileInfo, api, options) { + const printOptions = options.printOptions || defaultOptions; + const j = api.jscodeshift; + const root = j(fileInfo.source); + + // Mapping function for typical imports + function updateImportSpecifier(specifier, sourceValue) { + // If the import is from '@carbon/react' + if (typeof sourceValue === 'string' && sourceValue === '@carbon/react') { + if ( + specifier.type === 'ImportSpecifier' && + specifier.imported.name === 'unstable__StaticNotification' + ) { + // Change unstable__StaticNotification to unstable__Callout + specifier.imported.name = 'unstable__Callout'; + // If the local alias is StaticNotification, change it to Callout + if (specifier.local.name === 'StaticNotification') { + specifier.local.name = 'Callout'; + } + } else if ( + specifier.type === 'ImportSpecifier' && + specifier.imported.name === 'StaticNotification' + ) { + // Change StaticNotification to Callout + specifier.imported.name = 'Callout'; + if (specifier.local.name === 'StaticNotification') { + specifier.local.name = 'Callout'; + } + } else if ( + specifier.type === 'ImportSpecifier' && + specifier.imported.name === 'StaticNotificationProps' + ) { + // Change StaticNotificationProps to CalloutProps + specifier.imported.name = 'CalloutProps'; + if (specifier.local.name === 'StaticNotificationProps') { + specifier.local.name = 'CalloutProps'; + } + } + } else if ( + typeof sourceValue === 'string' && + (sourceValue.startsWith('@carbon/react/es/components/Notification') || + sourceValue.startsWith('@carbon/react/lib/components/Notification')) + ) { + // For fully qualified paths, change StaticNotification to Callout and StaticNotificationProps to CalloutProps + if (specifier.imported.name === 'StaticNotification') { + specifier.imported.name = 'Callout'; + if (specifier.local.name === 'StaticNotification') { + specifier.local.name = 'Callout'; + } + } else if (specifier.imported.name === 'StaticNotificationProps') { + specifier.imported.name = 'CalloutProps'; + if (specifier.local.name === 'StaticNotificationProps') { + specifier.local.name = 'CalloutProps'; + } + } + } + } + + // Handle the transformation of imports + root.find(j.ImportDeclaration).forEach((path) => { + const sourceValue = path.node.source.value; + + // Ensure sourceValue is a string + if ( + typeof sourceValue !== 'string' || + !sourceValue.startsWith('@carbon/react') + ) { + return; + } + + // Transform the specifiers + path.node.specifiers.forEach((specifier) => { + // Handle only ImportSpecifier types + if (specifier.type === 'ImportSpecifier') { + // Avoid transforming `unstable__Callout` if already correct + if ( + specifier.imported.name === 'unstable__Callout' && + (specifier.local.name === 'Callout' || + specifier.local.name !== 'StaticNotification') + ) { + return; // Skip this, it's already correct + } + + updateImportSpecifier(specifier, sourceValue); + } + }); + }); + + return root.toSource(printOptions); +} + +module.exports = transform; diff --git a/packages/upgrade/transforms/rename-staticnotification-to-callout.js b/packages/upgrade/transforms/rename-staticnotification-to-callout.js deleted file mode 100644 index aeaf9cb7e6e7..000000000000 --- a/packages/upgrade/transforms/rename-staticnotification-to-callout.js +++ /dev/null @@ -1,99 +0,0 @@ -// codemod.js -function transform(fileInfo, api, options) { - const printOptions = options.printOptions || defaultOptions; - const j = api.jscodeshift; - const root = j(fileInfo.source); - - // Update import declarations - root.find(j.ImportDeclaration).forEach((path) => { - const { specifiers } = path.node; - - specifiers.forEach((specifier) => { - const { imported, local } = specifier; - - // Early return if not dealing with StaticNotification or unstable__StaticNotification - if (!local || !imported) return; - - const { name: importedName } = imported; - const { name: localName } = local; - - // Skip transformation if `unstable__Callout` is already renamed to `StaticNotification` - if ( - importedName === 'unstable__Callout' && - localName === 'StaticNotification' - ) { - return; // Leave as is - } - - // Prefer transforming to unstable__Callout over Callout, unless already imported - if (importedName === 'StaticNotification') { - if (localName === 'Callout') { - // If `StaticNotification as Callout` exists, replace it with `unstable__Callout` - specifier.imported.name = 'unstable__Callout'; - specifier.local.name = 'unstable__Callout'; - } else { - // Prefer renaming to `unstable__Callout` where possible - specifier.imported.name = 'unstable__Callout'; - } - } else if (importedName === 'unstable__StaticNotification') { - specifier.imported.name = 'unstable__Callout'; - } - - // Handle aliasing (e.g., "as StaticNotification") - if (localName === 'StaticNotification') { - specifier.local.name = 'unstable__Callout'; - } else if (localName === 'unstable__StaticNotification') { - specifier.local.name = 'unstable__Callout'; - } - }); - }); - - // Update JSX elements and usage within code - root - .find(j.JSXIdentifier, { name: 'StaticNotification' }) - .replaceWith(j.jsxIdentifier('unstable__Callout')); - - root - .find(j.JSXIdentifier, { name: 'unstable__StaticNotification' }) - .replaceWith(j.jsxIdentifier('unstable__Callout')); - - root - .find(j.Identifier, { name: 'StaticNotification' }) - .replaceWith(j.identifier('unstable__Callout')); - - root - .find(j.Identifier, { name: 'unstable__StaticNotification' }) - .replaceWith(j.identifier('unstable__Callout')); - - // Update TypeScript interface names (StaticNotificationProps -> CalloutProps) - root - .find(j.Identifier, { name: 'StaticNotificationProps' }) - .replaceWith(j.identifier('CalloutProps')); - - // FINAL STEP: Rename all `unstable__Callout` to `Callout` - root.find(j.ImportSpecifier).forEach((path) => { - const { imported, local } = path.node; - - if (imported.name === 'unstable__Callout') { - imported.name = 'Callout'; - - // If the local name is also `unstable__Callout`, rename it to `Callout` - if (local.name === 'unstable__Callout') { - local.name = 'Callout'; - } - } - }); - - // Ensure all JSX and identifiers are also updated to `Callout` - root - .find(j.JSXIdentifier, { name: 'unstable__Callout' }) - .replaceWith(j.jsxIdentifier('Callout')); - - root - .find(j.Identifier, { name: 'unstable__Callout' }) - .replaceWith(j.identifier('Callout')); - - return root.toSource(printOptions); -} - -module.exports = transform; From f200c8827dc1b68bb5ce3df33245b271426cf5c2 Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Fri, 27 Sep 2024 13:41:37 -0500 Subject: [PATCH 6/7] fix(callout): codemod improvements --- .../refactor-to-callout.input.js | 1 + .../refactor-to-callout.output.js | 15 +- .../refactor-to-callout5.input.ts | 30 --- .../refactor-to-callout5.output.ts | 29 --- .../upgrade/transforms/refactor-to-callout.js | 181 +++++++++++------- 5 files changed, 124 insertions(+), 132 deletions(-) delete mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.input.ts delete mode 100644 packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.output.ts diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.input.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.input.js index 1a8edc553f74..241aaadc5ba5 100644 --- a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.input.js +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.input.js @@ -12,6 +12,7 @@ import { unstable__StaticNotification as Callout } from '@carbon/react'; const LocallyRenamedStaticNotification = unstable__StaticNotification; // Component usages +// prettier-ignore const App = () => { return ( <> diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js index 228e9e259d87..196a33fcb8e1 100644 --- a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js @@ -12,13 +12,12 @@ import { unstable__Callout as Callout } from '@carbon/react'; const LocallyRenamedStaticNotification = unstable__Callout; // Component usages +// prettier-ignore const App = () => { - return ( - <> - - - - - - ); + return (<> + + + + + ); }; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.input.ts b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.input.ts deleted file mode 100644 index c6d02c69ce6a..000000000000 --- a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.input.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Fully qualified import paths from within es -import { - type StaticNotificationProps, - StaticNotification, -} from '@carbon/react/es/components/Notification'; - -// If the transform results in duplicate imports, they should be deduped -import { unstable__StaticNotification } from '@carbon/react'; -import { unstable__Callout } from '@carbon/react'; - -// Redefine the component with props to ensure type interface is handled -const Notification: React.FC = (props) => { - return ; -}; - -// Local renames like this are unlikely but technically possible -const LocallyRenamedStaticNotification = unstable__StaticNotification; -const LocallyRenamedCallout = unstable__Callout; - -// Component usages -const App = () => { - return ( - <> - - - - - - ); -}; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.output.ts b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.output.ts deleted file mode 100644 index 2ad045c9a76f..000000000000 --- a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout5.output.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Fully qualified import paths from within es -import { - type CalloutProps, - Callout, -} from '@carbon/react/es/components/Notification'; - -// If the transform results in duplicate imports, they should be deduped// note the de -import { unstable__Callout } from '@carbon/react'; - -// Redefine the component with props to ensure type interface is handled -const Notification: React.FC = (props) => { - return ; -}; - -// Local renames like this are unlikely but technically possible -const LocallyRenamedStaticNotification = unstable__Callout; -const LocallyRenamedCallout = unstable__Callout; - -// Component usages -const App = () => { - return ( - <> - - - - - - ); -}; diff --git a/packages/upgrade/transforms/refactor-to-callout.js b/packages/upgrade/transforms/refactor-to-callout.js index 7738a4dd737d..9ba8e18bb85b 100644 --- a/packages/upgrade/transforms/refactor-to-callout.js +++ b/packages/upgrade/transforms/refactor-to-callout.js @@ -17,87 +17,138 @@ function transform(fileInfo, api, options) { const j = api.jscodeshift; const root = j(fileInfo.source); - // Mapping function for typical imports - function updateImportSpecifier(specifier, sourceValue) { - // If the import is from '@carbon/react' - if (typeof sourceValue === 'string' && sourceValue === '@carbon/react') { - if ( - specifier.type === 'ImportSpecifier' && - specifier.imported.name === 'unstable__StaticNotification' - ) { - // Change unstable__StaticNotification to unstable__Callout - specifier.imported.name = 'unstable__Callout'; - // If the local alias is StaticNotification, change it to Callout - if (specifier.local.name === 'StaticNotification') { - specifier.local.name = 'Callout'; - } - } else if ( - specifier.type === 'ImportSpecifier' && - specifier.imported.name === 'StaticNotification' - ) { - // Change StaticNotification to Callout - specifier.imported.name = 'Callout'; - if (specifier.local.name === 'StaticNotification') { - specifier.local.name = 'Callout'; - } - } else if ( - specifier.type === 'ImportSpecifier' && - specifier.imported.name === 'StaticNotificationProps' - ) { - // Change StaticNotificationProps to CalloutProps - specifier.imported.name = 'CalloutProps'; - if (specifier.local.name === 'StaticNotificationProps') { - specifier.local.name = 'CalloutProps'; - } - } - } else if ( - typeof sourceValue === 'string' && - (sourceValue.startsWith('@carbon/react/es/components/Notification') || - sourceValue.startsWith('@carbon/react/lib/components/Notification')) - ) { - // For fully qualified paths, change StaticNotification to Callout and StaticNotificationProps to CalloutProps - if (specifier.imported.name === 'StaticNotification') { - specifier.imported.name = 'Callout'; - if (specifier.local.name === 'StaticNotification') { - specifier.local.name = 'Callout'; - } - } else if (specifier.imported.name === 'StaticNotificationProps') { - specifier.imported.name = 'CalloutProps'; - if (specifier.local.name === 'StaticNotificationProps') { - specifier.local.name = 'CalloutProps'; - } - } - } + // Helper function to check if the import source is from '@carbon/react' or its subpaths + function isCarbonReactImport(sourceValue) { + return ( + sourceValue === '@carbon/react' || + sourceValue.startsWith('@carbon/react/es') || + sourceValue.startsWith('@carbon/react/lib') + ); } - // Handle the transformation of imports + // Collect names of identifiers imported from '@carbon/react' or its subpaths + const importedIdentifiers = new Map(); // Map of local name to transformed name + + // Transform import declarations root.find(j.ImportDeclaration).forEach((path) => { const sourceValue = path.node.source.value; - // Ensure sourceValue is a string - if ( - typeof sourceValue !== 'string' || - !sourceValue.startsWith('@carbon/react') - ) { + // Only transform imports from '@carbon/react' and its subpaths + if (!isCarbonReactImport(sourceValue)) { return; } - // Transform the specifiers path.node.specifiers.forEach((specifier) => { - // Handle only ImportSpecifier types if (specifier.type === 'ImportSpecifier') { - // Avoid transforming `unstable__Callout` if already correct + let importedName = specifier.imported.name; + let localName = specifier.local ? specifier.local.name : importedName; + let transformedImportedName = importedName; + let transformedLocalName = localName; + + // Transform imported names and local names as necessary + if (importedName === 'unstable__StaticNotification') { + transformedImportedName = 'unstable__Callout'; + specifier.imported.name = transformedImportedName; + + if (localName === 'StaticNotification') { + transformedLocalName = 'Callout'; + specifier.local.name = transformedLocalName; + } else if (localName === 'unstable__StaticNotification') { + transformedLocalName = 'unstable__Callout'; + specifier.local.name = transformedLocalName; + } + // If local name is something else (e.g., SomeOtherName), leave it unchanged + } else if (importedName === 'StaticNotification') { + transformedImportedName = 'Callout'; + specifier.imported.name = transformedImportedName; + + if (localName === 'StaticNotification') { + transformedLocalName = 'Callout'; + specifier.local.name = transformedLocalName; + } + // If local name is different, leave it unchanged + } else if (importedName === 'StaticNotificationProps') { + transformedImportedName = 'CalloutProps'; + specifier.imported.name = transformedImportedName; + + if (localName === 'StaticNotificationProps') { + transformedLocalName = 'CalloutProps'; + specifier.local.name = transformedLocalName; + } + // If local name is different, leave it unchanged + } + + // If imported name and local name are the same after transformation, remove the alias if ( - specifier.imported.name === 'unstable__Callout' && - (specifier.local.name === 'Callout' || - specifier.local.name !== 'StaticNotification') + specifier.local && + specifier.local.name === specifier.imported.name ) { - return; // Skip this, it's already correct + delete specifier.local; + } + + // Update the mapping of imported identifiers + // Only add to the map if the local name or the transformed name is different + if (localName !== transformedLocalName) { + importedIdentifiers.set(localName, transformedLocalName); } + } + }); + }); + + // Deduplicate imports + const importDeclarations = root.find(j.ImportDeclaration); + + importDeclarations.forEach((path) => { + const sourceValue = path.node.source.value; - updateImportSpecifier(specifier, sourceValue); + // Only deduplicate imports from '@carbon/react' and its subpaths + if (!isCarbonReactImport(sourceValue)) { + return; + } + + const specifiers = path.node.specifiers; + const uniqueSpecifiers = []; + const seen = new Set(); + + specifiers.forEach((specifier) => { + const importedName = specifier.imported.name; + const localName = specifier.local ? specifier.local.name : importedName; + const key = `${importedName}:${localName}`; + + if (!seen.has(key)) { + seen.add(key); + uniqueSpecifiers.push(specifier); } }); + + path.node.specifiers = uniqueSpecifiers; + }); + + // Remove empty import declarations + importDeclarations.forEach((path) => { + if (path.node.specifiers.length === 0) { + j(path).remove(); + } + }); + + // Update usages in the code + root.find(j.Identifier).forEach((path) => { + const name = path.node.name; + + // Skip if the identifier is part of an import specifier + if ( + path.parent.node.type === 'ImportSpecifier' || + path.parent.node.type === 'ImportDefaultSpecifier' || + path.parent.node.type === 'ImportNamespaceSpecifier' + ) { + return; + } + + // Only transform identifiers that match the imported identifiers + if (importedIdentifiers.has(name)) { + const transformedName = importedIdentifiers.get(name); + path.node.name = transformedName; + } }); return root.toSource(printOptions); From 4c583ca690e3eaf5f2c5bc6e5dff123c6bd7f45e Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Fri, 27 Sep 2024 14:08:54 -0500 Subject: [PATCH 7/7] fix(callout): update codemod test fixture output formatting --- .../__testfixtures__/refactor-to-callout.output.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js index 196a33fcb8e1..933d59d08359 100644 --- a/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js +++ b/packages/upgrade/transforms/__testfixtures__/refactor-to-callout.output.js @@ -15,9 +15,9 @@ const LocallyRenamedStaticNotification = unstable__Callout; // prettier-ignore const App = () => { return (<> - - - - - ); + + + + + ); };