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); +}