diff --git a/packages/upgrade/src/upgrades.js b/packages/upgrade/src/upgrades.js index 01376c13746a..17ba125c7d0f 100644 --- a/packages/upgrade/src/upgrades.js +++ b/packages/upgrade/src/upgrades.js @@ -331,6 +331,43 @@ export const upgrades = [ }); }, }, + { + name: 'refactor-light-to-layer', + description: ` + Refactor 'light' prop usage to instead wrap components with Layer + Transforms: + + Into: + + `, + + migrate: async (options) => { + const transform = path.join( + TRANSFORM_DIR, + 'refactor-light-to-layer.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, + }); + }, + }, { name: 'refactor-to-callout', description: diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-light-to-layer.input.js b/packages/upgrade/transforms/__testfixtures__/refactor-light-to-layer.input.js new file mode 100644 index 000000000000..df406e1b67e2 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-light-to-layer.input.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { Button, Layer } from '@carbon/react'; + +function TestComponent() { +
+ + + + + +
; +} + +export default TestComponent; diff --git a/packages/upgrade/transforms/__testfixtures__/refactor-light-to-layer.output.js b/packages/upgrade/transforms/__testfixtures__/refactor-light-to-layer.output.js new file mode 100644 index 000000000000..829a1ab9cd91 --- /dev/null +++ b/packages/upgrade/transforms/__testfixtures__/refactor-light-to-layer.output.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { Button, Layer } from '@carbon/react'; + +function TestComponent() { +
+ + + + + +
; +} + +export default TestComponent; diff --git a/packages/upgrade/transforms/__tests__/refactor-light-to-layer-test.js b/packages/upgrade/transforms/__tests__/refactor-light-to-layer-test.js new file mode 100644 index 000000000000..189dc72c8328 --- /dev/null +++ b/packages/upgrade/transforms/__tests__/refactor-light-to-layer-test.js @@ -0,0 +1,5 @@ +'use strict'; + +const { defineTest } = require('jscodeshift/dist/testUtils'); + +defineTest(__dirname, 'refactor-light-to-layer'); diff --git a/packages/upgrade/transforms/refactor-light-to-layer.js b/packages/upgrade/transforms/refactor-light-to-layer.js new file mode 100644 index 000000000000..d469afc949e8 --- /dev/null +++ b/packages/upgrade/transforms/refactor-light-to-layer.js @@ -0,0 +1,108 @@ +/** + * Copyright IBM Corp. 2024 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + * + * Deprecate the `light` prop and wrap components with `Layer` + * + * Transforms: + * + * + * + * Into: + * + * + * + * + */ + +'use strict'; + +const defaultOptions = { + quote: 'single', + trailingComma: true, +}; + +function transform(fileInfo, api, options) { + const { jscodeshift: j } = api; + const root = j(fileInfo.source); + const printOptions = options.printOptions || defaultOptions; + + // Check if there are any components with the 'light' prop + const hasLightProp = + root.find(j.JSXAttribute, { name: { name: 'light' } }).size() > 0; + + if (!hasLightProp) { + return null; // if no 'light' prop found, don't modify & return the file + } + + // Import Layer component if not already imported + const layerImport = root.find(j.ImportDeclaration, { + source: { value: '@carbon/react' }, + }); + + if (layerImport.length) { + const specifiers = layerImport.get('specifiers'); + const hasLayerImport = specifiers.value.some( + (specifier) => specifier.imported && specifier.imported.name === 'Layer' + ); + + if (!hasLayerImport) { + specifiers.value.push(j.importSpecifier(j.identifier('Layer'))); + } + } else { + const newImport = j.importDeclaration( + [j.importSpecifier(j.identifier('Layer'))], + j.literal('@carbon/react') + ); + // Find the first import declaration + const firstImport = root.find(j.ImportDeclaration).at(0); + + if (firstImport.length) { + // Insert the new import before the first existing import + firstImport.insertAfter(newImport); + } else { + // If no imports, find the first non-comment node + const firstNonCommentNode = root + .find(j.Program) + .get('body') + .filter( + (path) => + path.value.type !== 'CommentBlock' && + path.value.type !== 'CommentLine' + )[0]; + + // Insert the new import before the first non-comment node + j(firstNonCommentNode).insertBefore(newImport); + } + } + + // Find all JSX elements with a 'light' prop + root.find(j.JSXElement).forEach((path) => { + const lightProp = path.node.openingElement.attributes.find( + (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'light' + ); + + if (lightProp) { + // Remove the 'light' prop + path.node.openingElement.attributes = + path.node.openingElement.attributes.filter( + (attr) => attr !== lightProp + ); + // Wrap the component with Layer + const layerElement = j.jsxElement( + j.jsxOpeningElement(j.jsxIdentifier('Layer'), []), + j.jsxClosingElement(j.jsxIdentifier('Layer')), + [path.node] + ); + + // Replace the original element with the wrapped version + j(path).replaceWith(layerElement); + } + }); + + return root.toSource(printOptions); +} + +module.exports = transform;