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;