Merge pull request #1582 from TrevorEyre/codemod-addWithInfo

Added codemod for deprecated addon-info API
This commit is contained in:
Norbert de Langen 2017-08-14 17:45:21 +02:00 committed by GitHub
commit f3fa02f630
6 changed files with 556 additions and 1 deletions

View File

@ -38,7 +38,7 @@ Explanation:
## Transforms
### add-organisation-to-package-name
### update-organisation-name
Updates package names in imports to migrate to the new package names of storybook.
@ -61,3 +61,58 @@ Becomes
import { storiesOf } from '@storybook/react';
import { linkTo } from '@storybook/addon-links';
```
### update-addon-info
Replaces the Info addon's deprecated `addWithInfo` API with the standard `withInfo` API.
```sh
./node_modules/.bin/jscodeshift -t ./node_modules/@storybook/codemod/dist/transforms/update-addon-info.js . --ignore-pattern "node_modules|dist"
```
Simple example:
```js
storiesOf('Button').addWithInfo(
'simple usage',
'This is the basic usage of the button.',
() => (
<Button label="The Button" />
)
)
```
Becomes
```js
storiesOf('Button').add('simple usage', withInfo(
'This is the basic usage of the button.'
)(() => (
<Button label="The Button" />
)))
```
With options example:
```js
storiesOf('Button').addWithInfo(
'simple usage (disable source)',
'This is the basic usage of the button.',
() => (
<Button label="The Button" />
),
{ source: false, inline: true }
)
```
Becomes
```js
storiesOf('Button').add('simple usage (disable source)', withInfo({
text: 'This is the basic usage of the button.',
source: false,
inline: true
})(() => (
<Button label="The Button" />
)))
```

View File

@ -4,3 +4,7 @@ export {
default as updateOrganisationName,
packageNames,
} from './transforms/update-organisation-name';
export {
default as updateAddonInfo
} from './transforms/update-addon-info';

View File

@ -0,0 +1,190 @@
/* eslint-disable */
import React from 'react'
import Button from './Button'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
storiesOf(
'Button'
).addWithInfo(
'simple usage',
'This is the basic usage with the button with providing a label to show the text.',
() => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
<p>
Click the "?" mark at top-right to view the info.
</p>
</div>
)
)
storiesOf('Button').addWithInfo(
'simple usage (inline info)',
`
This is the basic usage with the button with providing a label to show the text.
`,
() => <Button label="The Button" onClick={action('onClick')} />,
{ inline: true }
)
storiesOf('Button').addWithInfo(
'simple usage (disable source)',
`
This is the basic usage with the button with providing a label to show the text.
`,
() => <Button label="The Button" onClick={action('onClick')} />,
{ source: false, inline: true }
)
storiesOf('Button').addWithInfo(
'simple usage (no header)',
`
This is the basic usage with the button with providing a label to show the text.
`,
() => <Button label="The Button" onClick={action('onClick')} />,
{ header: false, inline: true }
)
storiesOf('Button').addWithInfo(
'simple usage (no prop tables)',
`
This is the basic usage with the button with providing a label to show the text.
`,
() => <Button label="The Button" onClick={action('onClick')} />,
{ propTables: false, inline: true }
)
storiesOf('Button').addWithInfo(
'simple usage (custom propTables)',
`
This is the basic usage with the button with providing a label to show the text.
Since, the story source code is wrapped inside a div, info addon can't figure out propTypes on it's own.
So, we need to give relevant React component classes manually using \`propTypes\` option as shown below:
~~~js
storiesOf('Button')
.addWithInfo(
'simple usage (custom propTables)',
<info>,
<storyFn>,
{ inline: true, propTables: [Button]}
);
~~~
`,
() => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
</div>
),
{ inline: true, propTables: [Button] }
)
storiesOf('Button').addWithInfo(
'simple usage (JSX description)',
<div>
<h2>This is a JSX info section</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed ornare massa rutrum metus commodo, a mattis velit dignissim.
Fusce vestibulum turpis sed massa egestas pharetra. Sed at libero
nulla.
</p>
<p>
<a href="https://github.com/storybooks/react-storybook-addon-info">
This is a link
</a>
</p>
<p>
<img src="http://placehold.it/350x150" />
</p>
</div>,
() => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
<p>
Click the "?" mark at top-right to view the info.
</p>
</div>
)
)
storiesOf('Button').addWithInfo(
'simple usage (inline JSX description)',
<div>
<h2>This is a JSX info section</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed ornare massa rutrum metus commodo, a mattis velit dignissim.
Fusce vestibulum turpis sed massa egestas pharetra. Sed at libero
nulla.
</p>
<p>
<a href="https://github.com/storybooks/react-storybook-addon-info">
This is a link
</a>
</p>
<p>
<img src="http://placehold.it/350x150" />
</p>
</div>,
() => <Button label="The Button" onClick={action('onClick')} />,
{ inline: true }
)
storiesOf('Button').addWithInfo(
'simple usage (maxPropsInLine === 1)',
`
This is the basic usage with the button with providing a label to show the text.
`,
() => <Button label="The Button" onClick={action('onClick')} />,
{ inline: true, maxPropsIntoLine: 1 }
)
storiesOf('Button').addWithInfo(
'simple usage (maxPropObjectKeys === 5)',
`
This is the basic usage with the button with providing a label to show the text.
`,
() => <Button label="The Button" object={{ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }} />,
{ inline: true, maxPropObjectKeys: 5 }
)
storiesOf('Button').addWithInfo(
'simple usage (maxPropArrayLength === 8)',
`
This is the basic usage with the button with providing a label to show the text.
`,
() => <Button label="The Button" array={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]} />,
{ inline: true, maxPropArrayLength: 8 }
)
storiesOf('Button').addWithInfo(
'simple usage (maxPropStringLength === 10)',
`
This is the basic usage with the button with providing a label to show the text.
`,
() => <Button label="The Button" string="1 2 3 4 5 6 7 8" />,
{ inline: true, maxPropStringLength: 5 }
)
storiesOf('Button').addWithInfo(
'with custom styles',
`
This is an example of how to customize the styles of the info components.
For the full styles available, see \`./src/components/Story.js\`
`,
() => <Button label="The Button" onClick={action('onClick')} />,
{
inline: true,
styles: stylesheet => {
stylesheet.infoPage = {
backgroundColor: '#ccc'
}
return stylesheet
}
}
)

View File

@ -0,0 +1,186 @@
/* eslint-disable */
import React from 'react'
import Button from './Button'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { withInfo } from "@storybook/addon-info";
storiesOf(
'Button'
).add('simple usage', withInfo(
'This is the basic usage with the button with providing a label to show the text.'
)(() => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
<p>
Click the "?" mark at top-right to view the info.
</p>
</div>
)))
storiesOf('Button').add('simple usage (inline info)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
`,
inline: true
})(() => <Button label="The Button" onClick={action('onClick')} />))
storiesOf('Button').add('simple usage (disable source)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
`,
source: false,
inline: true
})(() => <Button label="The Button" onClick={action('onClick')} />))
storiesOf('Button').add('simple usage (no header)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
`,
header: false,
inline: true
})(() => <Button label="The Button" onClick={action('onClick')} />))
storiesOf('Button').add('simple usage (no prop tables)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
`,
propTables: false,
inline: true
})(() => <Button label="The Button" onClick={action('onClick')} />))
storiesOf('Button').add('simple usage (custom propTables)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
Since, the story source code is wrapped inside a div, info addon can't figure out propTypes on it's own.
So, we need to give relevant React component classes manually using \`propTypes\` option as shown below:
~~~js
storiesOf('Button')
.addWithInfo(
'simple usage (custom propTables)',
<info>,
<storyFn>,
{ inline: true, propTables: [Button]}
);
~~~
`,
inline: true,
propTables: [Button]
})(() => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
</div>
)))
storiesOf('Button').add('simple usage (JSX description)', withInfo(<div>
<h2>This is a JSX info section</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed ornare massa rutrum metus commodo, a mattis velit dignissim.
Fusce vestibulum turpis sed massa egestas pharetra. Sed at libero
nulla.
</p>
<p>
<a href="https://github.com/storybooks/react-storybook-addon-info">
This is a link
</a>
</p>
<p>
<img src="http://placehold.it/350x150" />
</p>
</div>)(() => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
<p>
Click the "?" mark at top-right to view the info.
</p>
</div>
)))
storiesOf('Button').add('simple usage (inline JSX description)', withInfo({
text: <div>
<h2>This is a JSX info section</h2>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed ornare massa rutrum metus commodo, a mattis velit dignissim.
Fusce vestibulum turpis sed massa egestas pharetra. Sed at libero
nulla.
</p>
<p>
<a href="https://github.com/storybooks/react-storybook-addon-info">
This is a link
</a>
</p>
<p>
<img src="http://placehold.it/350x150" />
</p>
</div>,
inline: true
})(() => <Button label="The Button" onClick={action('onClick')} />))
storiesOf('Button').add('simple usage (maxPropsInLine === 1)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
`,
inline: true,
maxPropsIntoLine: 1
})(() => <Button label="The Button" onClick={action('onClick')} />))
storiesOf('Button').add('simple usage (maxPropObjectKeys === 5)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
`,
inline: true,
maxPropObjectKeys: 5
})(
() => <Button label="The Button" object={{ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6 }} />
))
storiesOf('Button').add('simple usage (maxPropArrayLength === 8)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
`,
inline: true,
maxPropArrayLength: 8
})(
() => <Button label="The Button" array={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]} />
))
storiesOf('Button').add('simple usage (maxPropStringLength === 10)', withInfo({
text: `
This is the basic usage with the button with providing a label to show the text.
`,
inline: true,
maxPropStringLength: 5
})(() => <Button label="The Button" string="1 2 3 4 5 6 7 8" />))
storiesOf('Button').add('with custom styles', withInfo({
text: `
This is an example of how to customize the styles of the info components.
For the full styles available, see \`./src/components/Story.js\`
`,
inline: true,
styles: stylesheet => {
stylesheet.infoPage = {
backgroundColor: '#ccc'
}
return stylesheet
}
})(() => <Button label="The Button" onClick={action('onClick')} />))

View File

@ -0,0 +1,8 @@
import { defineTest } from 'jscodeshift/dist/testUtils';
defineTest(
__dirname,
'update-addon-info',
null,
'update-addon-info/update-addon-info'
);

View File

@ -0,0 +1,112 @@
/**
* Takes the deprecated addon-info API, addWithInfo, and
* converts to the new withInfo API.
*
* Example of deprecated addWithInfo API:
*
* storiesOf('Button')
* .addWithInfo(
* 'story name',
* 'Story description.',
* () => (
* <Button label="The Button" />
* )
* )
*
* Converts to the new withInfo API:
*
* storiesOf('Button')
* .add('story name', withInfo(
* 'Story description.'
* )(() => (
* <Button label="The Button" />
* )))
*/
export default function transformer (file, api) {
const j = api.jscodeshift;
const root = j(file.source);
/**
* Returns a list of parameters for the withInfo function. The contents
* of this list is either the second argument from the original
* addWithInfo function, if no additional options were used, or a
* combined object of all the options from the original function.
* @param {list} args - original addWithInfo function parameters
* @return {list} the modified list of parameters for the new function
*/
const getOptions = args => {
if (args[3] === undefined) {
return [args[1]];
}
return [
j.objectExpression([
j.property('init', j.identifier('text'), args[1]),
...args[3].properties
])
];
};
/**
* Constructs the new withInfo function from the parameters of the
* original addWithInfo function.
* @param {CallExpression} addWithInfoExpression - original function
* @returns {CallExpression} the new withInfo function
*/
const withInfo = addWithInfoExpression => {
const node = addWithInfoExpression.node;
const args = node.arguments;
node.callee.property.name = 'add';
node.arguments = [
args[0],
j.callExpression(
j.callExpression(
j.identifier('withInfo'),
getOptions(args)
),
[args[2]]
)
];
return node;
};
/**
* Checks for - import { withInfo } from "@storybook/addon-info";
* Adds the import if necessary.
*/
const checkWithInfoImport = () => {
const importExists = root.find(j.ImportDeclaration)
.filter(imp => imp.node.source.value === '@storybook/addon-info')
.size();
if (importExists) return;
root.find(j.ImportDeclaration)
.at(-1)
.insertAfter(
j.importDeclaration(
[j.importSpecifier(j.identifier('withInfo'))],
j.literal('@storybook/addon-info')
)
);
};
const addWithInfoExpressions = root.find(
j.CallExpression,
{
callee: {
property: {
name: 'addWithInfo'
}
}
}
);
if (addWithInfoExpressions.size()) {
checkWithInfoImport();
addWithInfoExpressions.replaceWith(withInfo);
}
return root.toSource();
}