diff --git a/packages/mui-codemod/src/deprecations/accordion-props/accordion-props.js b/packages/mui-codemod/src/deprecations/accordion-props/accordion-props.js
index dddc01c2288104..386de541096490 100644
--- a/packages/mui-codemod/src/deprecations/accordion-props/accordion-props.js
+++ b/packages/mui-codemod/src/deprecations/accordion-props/accordion-props.js
@@ -1,6 +1,5 @@
-import findComponentJSX from '../../util/findComponentJSX';
-import assignObject from '../../util/assignObject';
-import appendAttribute from '../../util/appendAttribute';
+import movePropIntoSlots from '../utils/movePropIntoSlots';
+import movePropIntoSlotProps from '../utils/movePropIntoSlotProps';
/**
* @param {import('jscodeshift').FileInfo} file
@@ -11,84 +10,18 @@ export default function transformer(file, api, options) {
const root = j(file.source);
const printOptions = options.printOptions;
- findComponentJSX(j, { root, componentName: 'Accordion' }, (elementPath) => {
- let index = elementPath.node.openingElement.attributes.findIndex(
- (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'TransitionComponent',
- );
- if (index !== -1) {
- const removed = elementPath.node.openingElement.attributes.splice(index, 1);
- let hasNode = false;
- elementPath.node.openingElement.attributes.forEach((attr) => {
- if (attr.name?.name === 'slots') {
- hasNode = true;
- assignObject(j, {
- target: attr,
- key: 'transition',
- expression: removed[0].value.expression,
- });
- }
- });
- if (!hasNode) {
- appendAttribute(j, {
- target: elementPath.node,
- attributeName: 'slots',
- expression: j.objectExpression([
- j.objectProperty(j.identifier('transition'), removed[0].value.expression),
- ]),
- });
- }
- }
-
- index = elementPath.node.openingElement.attributes.findIndex(
- (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'TransitionProps',
- );
- if (index !== -1) {
- const removed = elementPath.node.openingElement.attributes.splice(index, 1);
- let hasNode = false;
- elementPath.node.openingElement.attributes.forEach((attr) => {
- if (attr.name?.name === 'slotProps') {
- hasNode = true;
- assignObject(j, {
- target: attr,
- key: 'transition',
- expression: removed[0].value.expression,
- });
- }
- });
- if (!hasNode) {
- appendAttribute(j, {
- target: elementPath.node,
- attributeName: 'slotProps',
- expression: j.objectExpression([
- j.objectProperty(j.identifier('transition'), removed[0].value.expression),
- ]),
- });
- }
- }
- });
-
- root.find(j.ObjectProperty, { key: { name: 'TransitionComponent' } }).forEach((path) => {
- if (path.parent?.parent?.parent?.parent?.node.key?.name === 'MuiAccordion') {
- path.replace(
- j.property(
- 'init',
- j.identifier('slots'),
- j.objectExpression([j.objectProperty(j.identifier('transition'), path.node.value)]),
- ),
- );
- }
+ movePropIntoSlots(j, {
+ root,
+ componentName: 'Accordion',
+ propName: 'TransitionComponent',
+ slotName: 'transition',
});
- root.find(j.ObjectProperty, { key: { name: 'TransitionProps' } }).forEach((path) => {
- if (path.parent?.parent?.parent?.parent?.node.key?.name === 'MuiAccordion') {
- path.replace(
- j.property(
- 'init',
- j.identifier('slotProps'),
- j.objectExpression([j.objectProperty(j.identifier('transition'), path.node.value)]),
- ),
- );
- }
+ movePropIntoSlotProps(j, {
+ root,
+ componentName: 'Accordion',
+ propName: 'TransitionProps',
+ slotName: 'transition',
});
return root.toSource(printOptions);
diff --git a/packages/mui-codemod/src/deprecations/accordion-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/accordion-props/test-cases/actual.js
index 20ac7fa81a3bb8..9f6a4672656efd 100644
--- a/packages/mui-codemod/src/deprecations/accordion-props/test-cases/actual.js
+++ b/packages/mui-codemod/src/deprecations/accordion-props/test-cases/actual.js
@@ -23,6 +23,8 @@ import { Accordion as MyAccordion } from '@mui/material';
...outerSlotProps,
}}
/>;
+;
+;
// should skip non MUI components
;
+;
+;
// should skip non MUI components
{
- const index = elementPath.node.openingElement.attributes.findIndex(
- (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'imgProps',
- );
- if (index !== -1) {
- const removed = elementPath.node.openingElement.attributes.splice(index, 1);
- let hasNode = false;
- elementPath.node.openingElement.attributes.forEach((attr) => {
- if (attr.name?.name === 'slotProps') {
- hasNode = true;
- assignObject(j, {
- target: attr,
- key: 'img',
- expression: removed[0].value.expression,
- });
- }
- });
- if (!hasNode) {
- appendAttribute(j, {
- target: elementPath.node,
- attributeName: 'slotProps',
- expression: j.objectExpression([
- j.objectProperty(j.identifier('img'), removed[0].value.expression),
- ]),
- });
- }
- }
- });
-
- root.find(j.ObjectProperty, { key: { name: 'imgProps' } }).forEach((path) => {
- if (path.parent?.parent?.parent?.parent?.node.key?.name === 'MuiAvatar') {
- path.replace(
- j.property(
- 'init',
- j.identifier('slotProps'),
- j.objectExpression([j.objectProperty(j.identifier('img'), path.node.value)]),
- ),
- );
- }
+ movePropIntoSlotProps(j, {
+ root,
+ componentName: 'Avatar',
+ propName: 'imgProps',
+ slotName: 'img',
});
return root.toSource(printOptions);
diff --git a/packages/mui-codemod/src/deprecations/avatar-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/avatar-props/test-cases/actual.js
index e553aae667fd1a..541efaabed3e79 100644
--- a/packages/mui-codemod/src/deprecations/avatar-props/test-cases/actual.js
+++ b/packages/mui-codemod/src/deprecations/avatar-props/test-cases/actual.js
@@ -13,6 +13,16 @@ import { Avatar as MyAvatar } from '@mui/material';
onLoad: () => {},
}}
/>;
+ {},
+ }}
+ slotProps={{
+ img: {
+ onError: () => {},
+ },
+ }}
+/>;
// should skip non MUI components
;
+ {},
+ },
+
+ ...{
+ onError: () => {},
+ }
+ },
+ }} />;
// should skip non MUI components
{},
+ },
+ slotProps: {
+ img: {
+ onError: () => {},
+ },
+ },
+ },
+ },
+});
diff --git a/packages/mui-codemod/src/deprecations/avatar-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/avatar-props/test-cases/theme.expected.js
index a4ac9d4cf5fccb..f1b14e5af41789 100644
--- a/packages/mui-codemod/src/deprecations/avatar-props/test-cases/theme.expected.js
+++ b/packages/mui-codemod/src/deprecations/avatar-props/test-cases/theme.expected.js
@@ -6,6 +6,24 @@ fn({
onError: () => {},
onLoad: () => {},
}
+ },
+ },
+ },
+});
+
+fn({
+ MuiAvatar: {
+ defaultProps: {
+ slotProps: {
+ img: {
+ ...{
+ onLoad: () => {},
+ },
+
+ ...{
+ onError: () => {},
+ }
+ },
}
},
},
diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.js b/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.js
index c444dfc97b1004..d462ae49ed5b1f 100644
--- a/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.js
+++ b/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.js
@@ -1,6 +1,4 @@
-import findComponentJSX from '../../util/findComponentJSX';
-import assignObject from '../../util/assignObject';
-import appendAttribute from '../../util/appendAttribute';
+import movePropIntoSlots from '../utils/movePropIntoSlots';
/**
* @param {import('jscodeshift').FileInfo} file
@@ -11,73 +9,11 @@ export default function transformer(file, api, options) {
const root = j(file.source);
const printOptions = options.printOptions;
- findComponentJSX(j, { root, componentName: 'Backdrop' }, (elementPath) => {
- const index = elementPath.node.openingElement.attributes.findIndex(
- (attr) => attr.type === 'JSXAttribute' && attr.name.name === 'TransitionComponent',
- );
-
- if (index !== -1) {
- const removed = elementPath.node.openingElement.attributes.splice(index, 1);
- let hasNode = false;
- elementPath.node.openingElement.attributes.forEach((attr) => {
- if (attr.name?.name === 'slots') {
- hasNode = true;
- assignObject(j, {
- target: attr,
- key: 'transition',
- expression: removed[0].value.expression,
- });
- }
- });
-
- if (!hasNode) {
- appendAttribute(j, {
- target: elementPath.node,
- attributeName: 'slots',
- expression: j.objectExpression([
- j.objectProperty(j.identifier('transition'), removed[0].value.expression),
- ]),
- });
- }
- }
- });
-
- root.find(j.ObjectProperty, { key: { name: 'TransitionComponent' } }).forEach((path) => {
- if (path.parent?.parent?.parent?.parent?.node.key?.name === 'MuiBackdrop') {
- const { properties: defaultPropsProperties } = path.parent.value;
-
- const existingSlots = defaultPropsProperties.find((prop) => prop.key.name === 'slots');
- const slots = existingSlots
- ? existingSlots.value.properties.reduce((acc, prop) => {
- return { ...acc, [prop.key.name]: prop.value };
- }, {})
- : {};
-
- const transitionComponent =
- defaultPropsProperties.find((prop) => prop.key.name === 'TransitionComponent') ?? {};
-
- const updatedSlots = j.objectExpression(
- Object.entries({
- transition: transitionComponent?.value,
- ...slots,
- }).map(([slot, value]) => {
- return j.objectProperty(j.identifier(slot), value);
- }),
- );
-
- if (existingSlots) {
- existingSlots.value = updatedSlots;
- path.prune();
- } else {
- path.replace(
- j.property(
- 'init',
- j.identifier('slots'),
- j.objectExpression([j.objectProperty(j.identifier('transition'), path.node.value)]),
- ),
- );
- }
- }
+ movePropIntoSlots(j, {
+ root,
+ componentName: 'Backdrop',
+ propName: 'TransitionComponent',
+ slotName: 'transition',
});
return root.toSource(printOptions);
diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.test.js b/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.test.js
index 4d825b0076014a..13949c52396b08 100644
--- a/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.test.js
+++ b/packages/mui-codemod/src/deprecations/backdrop-props/backdrop-props.test.js
@@ -31,7 +31,7 @@ describe('@mui/codemod', () => {
const actual = transform(
{ source: read('./test-cases/theme.actual.js') },
{ jscodeshift },
- { printOptions: { trailingComma: true } },
+ {},
);
const expected = read('./test-cases/theme.expected.js');
diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/actual.js b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/actual.js
index 8cca730fda99e4..00edff06a072ce 100644
--- a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/actual.js
+++ b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/actual.js
@@ -8,17 +8,18 @@ import { Backdrop as MyBackdrop } from '@mui/material';
slots={{
root: 'div',
}}
- slotProps={{
- root: { className: 'foo' },
- }}
/>;
;
+;
// should skip non MUI components
diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/expected.js b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/expected.js
index ca22506cedab88..60b85e0d7b2556 100644
--- a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/expected.js
+++ b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/expected.js
@@ -11,17 +11,16 @@ import { Backdrop as MyBackdrop } from '@mui/material';
slots={{
root: 'div',
transition: CustomTransition
- }}
- slotProps={{
- root: { className: 'foo' },
}} />;
;
+;
// should skip non MUI components
;
diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.actual.js b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.actual.js
index a8e62bd3583647..8488ea0176baaa 100644
--- a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.actual.js
+++ b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.actual.js
@@ -16,3 +16,15 @@ fn({
},
},
});
+
+fn({
+ MuiBackdrop: {
+ defaultProps: {
+ TransitionComponent: ComponentTransition,
+ slots: {
+ root: 'div',
+ transition: SlotTransition
+ },
+ },
+ },
+});
diff --git a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.expected.js b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.expected.js
index ccc52bc558129d..46e9175ad5fdab 100644
--- a/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.expected.js
+++ b/packages/mui-codemod/src/deprecations/backdrop-props/test-cases/theme.expected.js
@@ -2,8 +2,8 @@ fn({
MuiBackdrop: {
defaultProps: {
slots: {
- transition: CustomTransition,
- },
+ transition: CustomTransition
+ }
},
},
});
@@ -12,9 +12,20 @@ fn({
MuiBackdrop: {
defaultProps: {
slots: {
- transition: CustomTransition,
root: 'div',
- },
+ transition: CustomTransition
+ }
+ },
+ },
+});
+
+fn({
+ MuiBackdrop: {
+ defaultProps: {
+ slots: {
+ root: 'div',
+ transition: SlotTransition
+ }
},
},
});
diff --git a/packages/mui-codemod/src/deprecations/utils/movePropIntoSlotProps.js b/packages/mui-codemod/src/deprecations/utils/movePropIntoSlotProps.js
new file mode 100644
index 00000000000000..2e09e3d1d021cc
--- /dev/null
+++ b/packages/mui-codemod/src/deprecations/utils/movePropIntoSlotProps.js
@@ -0,0 +1,111 @@
+import findComponentJSX from '../../util/findComponentJSX';
+import findComponentDefaultProps from '../../util/findComponentDefaultProps';
+import assignObject from '../../util/assignObject';
+import appendAttribute from '../../util/appendAttribute';
+
+function moveJsxPropIntoSlotProps(j, element, propName, slotName) {
+ const propIndex = element.openingElement.attributes.findIndex(
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === propName,
+ );
+
+ if (propIndex !== -1) {
+ const removedValue = element.openingElement.attributes.splice(propIndex, 1)[0].value.expression;
+ let hasSlotProps = false;
+ element.openingElement.attributes.forEach((attr) => {
+ if (attr.name?.name === 'slotProps') {
+ hasSlotProps = true;
+ const slots = attr.value.expression;
+ const slotIndex = slots.properties.findIndex((prop) => prop?.key?.name === slotName);
+ if (slotIndex === -1) {
+ assignObject(j, {
+ target: attr,
+ key: slotName,
+ expression: removedValue,
+ });
+ } else {
+ const slotPropsSlotValue = slots.properties.splice(slotIndex, 1)[0].value;
+ assignObject(j, {
+ target: attr,
+ key: slotName,
+ expression: j.objectExpression([
+ j.spreadElement(removedValue),
+ j.spreadElement(slotPropsSlotValue),
+ ]),
+ });
+ }
+ }
+ });
+
+ if (!hasSlotProps) {
+ appendAttribute(j, {
+ target: element,
+ attributeName: 'slotProps',
+ expression: j.objectExpression([j.objectProperty(j.identifier(slotName), removedValue)]),
+ });
+ }
+ }
+}
+
+function moveDefaultPropsPropIntoslotProps(j, defaultPropsPathCollection, propName, slotName) {
+ defaultPropsPathCollection.find(j.ObjectProperty, { key: { name: propName } }).forEach((path) => {
+ const removedValue = path.value.value;
+ const defaultProps = path.parent.value;
+
+ let hasSlotProps = false;
+ defaultProps.properties.forEach((property) => {
+ if (property.key?.name === 'slotProps') {
+ hasSlotProps = true;
+ const slotIndex = property.value.properties.findIndex(
+ (prop) => prop?.key?.name === slotName,
+ );
+ if (slotIndex === -1) {
+ property.value.properties.push(j.objectProperty(j.identifier(slotName), removedValue));
+ } else {
+ const slotPropsSlotValue = property.value.properties.splice(slotIndex, 1)[0].value;
+ property.value.properties.push(
+ j.objectProperty(
+ j.identifier(slotName),
+ j.objectExpression([
+ j.spreadElement(removedValue),
+ j.spreadElement(slotPropsSlotValue),
+ ]),
+ ),
+ );
+ }
+ }
+ });
+
+ if (!hasSlotProps) {
+ defaultProps.properties.push(
+ j.objectProperty(
+ j.identifier('slotProps'),
+ j.objectExpression([j.objectProperty(j.identifier(slotName), removedValue)]),
+ ),
+ );
+ }
+
+ path.prune();
+ });
+}
+
+/**
+ * Moves prop into slotProps.
+ * If the slotProps prop exists, it will merge the prop into the slotProps.
+ * If there are duplicated values, the values will be spread.
+ *
+ * @param {import('jscodeshift')} j
+ * @param {{ root: import('jscodeshift').Collection; componentName: string, propName: string, slotName: string }} options
+ *
+ * @example =>
+ */
+export default function movePropIntoSlotProps(j, options) {
+ const { root, componentName, propName, slotName } = options;
+
+ findComponentJSX(j, { root, componentName }, (elementPath) => {
+ moveJsxPropIntoSlotProps(j, elementPath.node, propName, slotName);
+ });
+
+ const defaultPropsPathCollection = findComponentDefaultProps(j, { root, componentName });
+
+ moveDefaultPropsPropIntoslotProps(j, defaultPropsPathCollection, propName, slotName);
+}
diff --git a/packages/mui-codemod/src/deprecations/utils/movePropIntoSlots.js b/packages/mui-codemod/src/deprecations/utils/movePropIntoSlots.js
new file mode 100644
index 00000000000000..9910853c3b4949
--- /dev/null
+++ b/packages/mui-codemod/src/deprecations/utils/movePropIntoSlots.js
@@ -0,0 +1,91 @@
+import findComponentJSX from '../../util/findComponentJSX';
+import findComponentDefaultProps from '../../util/findComponentDefaultProps';
+import assignObject from '../../util/assignObject';
+import appendAttribute from '../../util/appendAttribute';
+
+function moveJsxPropIntoSlots(j, element, propName, slotName) {
+ const index = element.openingElement.attributes.findIndex(
+ (attr) => attr.type === 'JSXAttribute' && attr.name.name === propName,
+ );
+
+ if (index !== -1) {
+ const removedValue = element.openingElement.attributes.splice(index, 1)[0].value.expression;
+ let hasSlots = false;
+ element.openingElement.attributes.forEach((attr) => {
+ if (attr.name?.name === 'slots') {
+ hasSlots = true;
+ const slotIndex = attr.value.expression.properties.findIndex(
+ (prop) => prop?.key?.name === slotName,
+ );
+ if (slotIndex === -1) {
+ assignObject(j, {
+ target: attr,
+ key: slotName,
+ expression: removedValue,
+ });
+ }
+ }
+ });
+
+ if (!hasSlots) {
+ appendAttribute(j, {
+ target: element,
+ attributeName: 'slots',
+ expression: j.objectExpression([j.objectProperty(j.identifier(slotName), removedValue)]),
+ });
+ }
+ }
+}
+
+function moveDefaultPropsPropIntoSlots(j, defaultPropsPathCollection, propName, slotName) {
+ defaultPropsPathCollection.find(j.ObjectProperty, { key: { name: propName } }).forEach((path) => {
+ const removedValue = path.value.value;
+ const defaultProps = path.parent.value;
+
+ let hasSlots = false;
+ defaultProps.properties.forEach((property) => {
+ if (property.key?.name === 'slots') {
+ hasSlots = true;
+ const slots = property.value;
+ const slotIndex = slots.properties.findIndex((prop) => prop?.key?.name === slotName);
+ if (slotIndex === -1) {
+ slots.properties.push(j.objectProperty(j.identifier(slotName), removedValue));
+ }
+ }
+ });
+
+ if (!hasSlots) {
+ defaultProps.properties.push(
+ j.property(
+ 'init',
+ j.identifier('slots'),
+ j.objectExpression([j.objectProperty(j.identifier(slotName), removedValue)]),
+ ),
+ );
+ }
+
+ path.prune();
+ });
+}
+
+/**
+ * Moves prop into slots.
+ * If the slots prop exists, it will add the prop to the slots.
+ * If there are duplicated values, the slots values will be used.
+ *
+ * @param {import('jscodeshift')} j
+ * @param {{ root: import('jscodeshift').Collection; componentName: string, propName: string, slotName: string }} options
+ *
+ * @example =>
+ */
+export default function movePropIntoSlots(j, options) {
+ const { root, componentName, propName, slotName } = options;
+
+ findComponentJSX(j, { root, componentName }, (elementPath) => {
+ moveJsxPropIntoSlots(j, elementPath.node, propName, slotName);
+ });
+
+ const defaultPropsPathCollection = findComponentDefaultProps(j, { root, componentName });
+
+ moveDefaultPropsPropIntoSlots(j, defaultPropsPathCollection, propName, slotName);
+}