forked from mui/material-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[codemod] Add accordion props deprecation (mui#40771)
- Loading branch information
1 parent
b842f94
commit c479c9b
Showing
17 changed files
with
453 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Contributing | ||
|
||
## Understanding the codemod | ||
|
||
The codemod is a tool that helps developers migrate thier codebase when we introduced changes in new version. The changes could be deprecations, enhancements, or breaking changes. | ||
|
||
The codemod is based on [jscodeshift](https://github.com/facebook/jscodeshift) which is a wrapper of [recast](https://github.com/benjamn/recast). | ||
|
||
## Adding a new codemod | ||
|
||
1. Create a new folder in `packages/mui-codemod/src/*/*` with the name of the codemod. | ||
2. The folder should include: | ||
- `<codemod>.js` - the transform implementation | ||
- `<codemod>.test.js` - tests for the codemod (use jscodeshift from the `testUtils` folder) | ||
- `test-cases` - folder with fixtures for the codemod | ||
- `actual.js` - the input for the codemod | ||
- `expected.js` - the expected output of the codemod | ||
3. Use [astexplorer](https://astexplorer.net/) to check the AST types and properties (set </> to @babel/parser because we use [`tsx`](https://github.com/benjamn/recast/blob/master/parsers/babel.ts) as a default parser for our codemod). | ||
4. [Test the codemod locally](#local) | ||
5. Add the codemod to README.md | ||
|
||
## Testing | ||
|
||
I recommend to follow these steps to test the codemod: | ||
|
||
- Create an `actual.js` file with the code you want to transform. | ||
- Run [local](#local) transformation to check if the codemod is correct. | ||
- Copy the transformed code to `expected.js`. | ||
- Run `pnpm tc <codemod>` to final check if the codemod is correct. | ||
|
||
💡 The reason that I don't recommend creating the `expected.js` and run the test with `pnpm` script is because the transformation is likely not pretty-printed and it's hard to compare the output with the expected output. | ||
|
||
### Local transformation (while developing) | ||
|
||
Open the terminal at root directory and run the codemod to test the transformation, for example, testing the `accordion-props` codemod: | ||
|
||
```bash | ||
node packages/mui-codemod/codemod deprecations/accordion-props packages/mui-codemod/src/deprecations/accordion-props/test-cases/theme.actual.js | ||
``` | ||
|
||
### CI (after opening a PR) | ||
|
||
To simulate a consumer-facing experience on any project before merging the PR, open the CodeSandbox CI build and copy the link from the "Local Install Instructions" section. | ||
|
||
Run the codemod to test the transformation: | ||
|
||
```bash | ||
npx @mui/codemod@<link> <codemod> <path> | ||
``` | ||
|
||
For example: | ||
|
||
```bash | ||
npx @mui/codemod@https://pkg.csb.dev/mui/material-ui/commit/39bf9464/@mui/codemod deprecations/accordion-props docs/src/modules/brandingTheme.ts | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
packages/mui-codemod/src/deprecations/accordion-props/accordion-props.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import findComponentJSX from '../../util/findComponentJSX'; | ||
import assignObject from '../../util/assignObject'; | ||
import appendAttribute from '../../util/appendAttribute'; | ||
|
||
/** | ||
* @param {import('jscodeshift').FileInfo} file | ||
* @param {import('jscodeshift').API} api | ||
*/ | ||
export default function transformer(file, api, options) { | ||
const j = api.jscodeshift; | ||
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)]), | ||
), | ||
); | ||
} | ||
}); | ||
|
||
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)]), | ||
), | ||
); | ||
} | ||
}); | ||
|
||
return root.toSource(printOptions); | ||
} |
53 changes: 53 additions & 0 deletions
53
packages/mui-codemod/src/deprecations/accordion-props/accordion-props.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import path from 'path'; | ||
import { expect } from 'chai'; | ||
import { jscodeshift } from '../../../testUtils'; | ||
import transform from './accordion-props'; | ||
import readFile from '../../util/readFile'; | ||
|
||
function read(fileName) { | ||
return readFile(path.join(__dirname, fileName)); | ||
} | ||
|
||
describe('@mui/codemod', () => { | ||
describe('deprecations', () => { | ||
describe('accordion-props', () => { | ||
it('transforms props as needed', () => { | ||
const actual = transform({ source: read('./test-cases/actual.js') }, { jscodeshift }, {}); | ||
|
||
const expected = read('./test-cases/expected.js'); | ||
expect(actual).to.equal(expected, 'The transformed version should be correct'); | ||
}); | ||
|
||
it('should be idempotent', () => { | ||
const actual = transform({ source: read('./test-cases/expected.js') }, { jscodeshift }, {}); | ||
|
||
const expected = read('./test-cases/expected.js'); | ||
expect(actual).to.equal(expected, 'The transformed version should be correct'); | ||
}); | ||
}); | ||
|
||
describe('[theme] accordion-props', () => { | ||
it('transforms props as needed', () => { | ||
const actual = transform( | ||
{ source: read('./test-cases/theme.actual.js') }, | ||
{ jscodeshift }, | ||
{}, | ||
); | ||
|
||
const expected = read('./test-cases/theme.expected.js'); | ||
expect(actual).to.equal(expected, 'The transformed version should be correct'); | ||
}); | ||
|
||
it('should be idempotent', () => { | ||
const actual = transform( | ||
{ source: read('./test-cases/theme.expected.js') }, | ||
{ jscodeshift }, | ||
{}, | ||
); | ||
|
||
const expected = read('./test-cases/theme.expected.js'); | ||
expect(actual).to.equal(expected, 'The transformed version should be correct'); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from './accordion-props'; |
36 changes: 36 additions & 0 deletions
36
packages/mui-codemod/src/deprecations/accordion-props/test-cases/actual.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import Accordion from '@mui/material/Accordion'; | ||
import { Accordion as MyAccordion } from '@mui/material'; | ||
|
||
<Accordion slots={{ | ||
transition: CustomTransition | ||
}} slotProps={{ | ||
transition: { unmountOnExit: true } | ||
}} />; | ||
<MyAccordion slots={{ | ||
transition: CustomTransition | ||
}} slotProps={{ | ||
transition: transitionVars | ||
}} />; | ||
<Accordion | ||
slots={{ | ||
root: 'div', | ||
transition: CustomTransition | ||
}} | ||
slotProps={{ | ||
root: { className: 'foo' }, | ||
transition: { unmountOnExit: true } | ||
}} />; | ||
<MyAccordion | ||
slots={{ | ||
...outerSlots, | ||
transition: CustomTransition | ||
}} | ||
slotProps={{ | ||
...outerSlotProps, | ||
transition: { unmountOnExit: true } | ||
}} />; | ||
// should skip non MUI components | ||
<NonMuiAccordion | ||
TransitionComponent={CustomTransition} | ||
TransitionProps={{ unmountOnExit: true }} | ||
/>; |
36 changes: 36 additions & 0 deletions
36
packages/mui-codemod/src/deprecations/accordion-props/test-cases/expected.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import Accordion from '@mui/material/Accordion'; | ||
import { Accordion as MyAccordion } from '@mui/material'; | ||
|
||
<Accordion slots={{ | ||
transition: CustomTransition | ||
}} slotProps={{ | ||
transition: { unmountOnExit: true } | ||
}} />; | ||
<MyAccordion slots={{ | ||
transition: CustomTransition | ||
}} slotProps={{ | ||
transition: transitionVars | ||
}} />; | ||
<Accordion | ||
slots={{ | ||
root: 'div', | ||
transition: CustomTransition | ||
}} | ||
slotProps={{ | ||
root: { className: 'foo' }, | ||
transition: { unmountOnExit: true } | ||
}} />; | ||
<MyAccordion | ||
slots={{ | ||
...outerSlots, | ||
transition: CustomTransition | ||
}} | ||
slotProps={{ | ||
...outerSlotProps, | ||
transition: { unmountOnExit: true } | ||
}} />; | ||
// should skip non MUI components | ||
<NonMuiAccordion | ||
TransitionComponent={CustomTransition} | ||
TransitionProps={{ unmountOnExit: true }} | ||
/>; |
Oops, something went wrong.