@storybook/codemod 7.0.0-alpha.37 → 7.0.0-alpha.39

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. package/dist/index.d.ts +261 -0
  2. package/dist/index.js +1 -0
  3. package/dist/index.mjs +1 -0
  4. package/package.json +24 -15
  5. package/src/index.js +77 -0
  6. package/src/lib/utils.test.js +9 -0
  7. package/{dist/esm/lib/utils.js → src/lib/utils.ts} +10 -11
  8. package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.input.js +44 -0
  9. package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.output.snapshot +68 -0
  10. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.input.js +25 -0
  11. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.output.snapshot +27 -0
  12. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.input.js +25 -0
  13. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.output.snapshot +28 -0
  14. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.input.js +13 -0
  15. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.output.snapshot +17 -0
  16. package/src/transforms/__testfixtures__/csf-to-mdx/basic.input.js +20 -0
  17. package/src/transforms/__testfixtures__/csf-to-mdx/basic.output.snapshot +18 -0
  18. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.input.js +9 -0
  19. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.output.snapshot +10 -0
  20. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.input.js +13 -0
  21. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.output.snapshot +12 -0
  22. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.input.js +23 -0
  23. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.output.snapshot +22 -0
  24. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.input.js +16 -0
  25. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.output.snapshot +17 -0
  26. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.input.js +19 -0
  27. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.output.snapshot +18 -0
  28. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.input.js +24 -0
  29. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.output.snapshot +22 -0
  30. package/src/transforms/__testfixtures__/mdx-to-csf/basic.input.js +18 -0
  31. package/src/transforms/__testfixtures__/mdx-to-csf/basic.output.snapshot +40 -0
  32. package/src/transforms/__testfixtures__/mdx-to-csf/component-id.input.js +6 -0
  33. package/src/transforms/__testfixtures__/mdx-to-csf/component-id.output.snapshot +17 -0
  34. package/src/transforms/__testfixtures__/mdx-to-csf/decorators.input.js +8 -0
  35. package/src/transforms/__testfixtures__/mdx-to-csf/decorators.output.snapshot +18 -0
  36. package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.input.js +19 -0
  37. package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.output.snapshot +39 -0
  38. package/src/transforms/__testfixtures__/mdx-to-csf/parameters.input.js +14 -0
  39. package/src/transforms/__testfixtures__/mdx-to-csf/parameters.output.snapshot +23 -0
  40. package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.input.js +3 -0
  41. package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.output.snapshot +11 -0
  42. package/src/transforms/__testfixtures__/mdx-to-csf/story-function.input.js +10 -0
  43. package/src/transforms/__testfixtures__/mdx-to-csf/story-function.output.snapshot +18 -0
  44. package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.input.js +18 -0
  45. package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.output.snapshot +32 -0
  46. package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.input.js +22 -0
  47. package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.output.snapshot +34 -0
  48. package/src/transforms/__testfixtures__/move-builtin-addons/default.input.js +2 -0
  49. package/src/transforms/__testfixtures__/move-builtin-addons/default.output.snapshot +8 -0
  50. package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.input.js +3 -0
  51. package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.output.snapshot +7 -0
  52. package/src/transforms/__testfixtures__/storiesof-to-csf/basic.input.js +18 -0
  53. package/src/transforms/__testfixtures__/storiesof-to-csf/basic.output.snapshot +45 -0
  54. package/src/transforms/__testfixtures__/storiesof-to-csf/collision.input.js +11 -0
  55. package/src/transforms/__testfixtures__/storiesof-to-csf/collision.output.snapshot +38 -0
  56. package/src/transforms/__testfixtures__/storiesof-to-csf/const.input.js +1 -0
  57. package/src/transforms/__testfixtures__/storiesof-to-csf/const.output.snapshot +13 -0
  58. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +9 -0
  59. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.output.snapshot +18 -0
  60. package/src/transforms/__testfixtures__/storiesof-to-csf/default.input.js +7 -0
  61. package/src/transforms/__testfixtures__/storiesof-to-csf/default.output.snapshot +17 -0
  62. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.input.js +1 -0
  63. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.js +5 -0
  64. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.snapshot +9 -0
  65. package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.input.js +11 -0
  66. package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.output.snapshot +23 -0
  67. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +12 -0
  68. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.output.snapshot +23 -0
  69. package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.input.js +14 -0
  70. package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.output.snapshot +26 -0
  71. package/src/transforms/__testfixtures__/storiesof-to-csf/exports.input.js +2 -0
  72. package/src/transforms/__testfixtures__/storiesof-to-csf/exports.output.snapshot +16 -0
  73. package/src/transforms/__testfixtures__/storiesof-to-csf/module.input.js +12 -0
  74. package/src/transforms/__testfixtures__/storiesof-to-csf/module.output.snapshot +16 -0
  75. package/src/transforms/__testfixtures__/storiesof-to-csf/multi.input.js +14 -0
  76. package/src/transforms/__testfixtures__/storiesof-to-csf/multi.output.snapshot +39 -0
  77. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.input.js +8 -0
  78. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.output.snapshot +20 -0
  79. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.input.js +10 -0
  80. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.output.snapshot +23 -0
  81. package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.input.js +11 -0
  82. package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.output.snapshot +23 -0
  83. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +11 -0
  84. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.output.snapshot +29 -0
  85. package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.input.js +14 -0
  86. package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.output.snapshot +32 -0
  87. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +198 -0
  88. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +198 -0
  89. package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.input.js +19 -0
  90. package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.output.snapshot +23 -0
  91. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.input.js +3 -0
  92. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.output.snapshot +7 -0
  93. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.input.js +5 -0
  94. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.output.snapshot +9 -0
  95. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.input.js +8 -0
  96. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.output.snapshot +12 -0
  97. package/src/transforms/__tests__/csf-2-to-3.test.ts +251 -0
  98. package/src/transforms/__tests__/transforms.tests.js +30 -0
  99. package/{dist/esm → src}/transforms/add-component-parameters.js +26 -21
  100. package/src/transforms/csf-2-to-3.ts +183 -0
  101. package/src/transforms/csf-hoist-story-annotations.js +97 -0
  102. package/src/transforms/csf-to-mdx.js +190 -0
  103. package/src/transforms/mdx-to-csf.js +184 -0
  104. package/src/transforms/move-builtin-addons.js +32 -0
  105. package/src/transforms/storiesof-to-csf.js +277 -0
  106. package/{dist/esm → src}/transforms/update-addon-info.js +44 -24
  107. package/{dist/esm → src}/transforms/update-organisation-name.js +19 -22
  108. package/src/transforms/upgrade-hierarchy-separators.js +39 -0
  109. package/tsconfig.json +13 -0
  110. package/dist/cjs/index.js +0 -139
  111. package/dist/cjs/lib/utils.js +0 -45
  112. package/dist/cjs/transforms/add-component-parameters.js +0 -64
  113. package/dist/cjs/transforms/csf-2-to-3.js +0 -180
  114. package/dist/cjs/transforms/csf-hoist-story-annotations.js +0 -93
  115. package/dist/cjs/transforms/csf-to-mdx.js +0 -196
  116. package/dist/cjs/transforms/mdx-to-csf.js +0 -153
  117. package/dist/cjs/transforms/move-builtin-addons.js +0 -55
  118. package/dist/cjs/transforms/storiesof-to-csf.js +0 -300
  119. package/dist/cjs/transforms/update-addon-info.js +0 -101
  120. package/dist/cjs/transforms/update-organisation-name.js +0 -83
  121. package/dist/cjs/transforms/upgrade-hierarchy-separators.js +0 -42
  122. package/dist/esm/index.js +0 -97
  123. package/dist/esm/transforms/csf-2-to-3.js +0 -163
  124. package/dist/esm/transforms/csf-hoist-story-annotations.js +0 -86
  125. package/dist/esm/transforms/csf-to-mdx.js +0 -188
  126. package/dist/esm/transforms/mdx-to-csf.js +0 -139
  127. package/dist/esm/transforms/move-builtin-addons.js +0 -48
  128. package/dist/esm/transforms/storiesof-to-csf.js +0 -287
  129. package/dist/esm/transforms/upgrade-hierarchy-separators.js +0 -35
  130. package/dist/types/lib/utils.d.ts +0 -2
  131. package/dist/types/transforms/csf-2-to-3.d.ts +0 -6
@@ -0,0 +1,190 @@
1
+ import { prettyPrint } from 'recast';
2
+ import { isExportStory } from '@storybook/csf';
3
+
4
+ function exportMdx(root, options) {
5
+ // eslint-disable-next-line no-underscore-dangle
6
+ const path = root.__paths[0];
7
+
8
+ // FIXME: insert the title as markdown after all of the imports
9
+ return path.node.program.body
10
+ .map((n) => {
11
+ const { code } = prettyPrint(n, options);
12
+ if (n.type === 'JSXElement') {
13
+ return `${code}\n`;
14
+ }
15
+ return code;
16
+ })
17
+ .join('\n');
18
+ }
19
+
20
+ function parseIncludeExclude(prop) {
21
+ const { code } = prettyPrint(prop, {});
22
+ // eslint-disable-next-line no-eval
23
+ return (0, eval)(code);
24
+ }
25
+
26
+ /**
27
+ * Convert a component's module story file into an MDX file
28
+ *
29
+ * For example:
30
+ *
31
+ * ```
32
+ * input { Button } from './Button';
33
+ * export default {
34
+ * title: 'Button'
35
+ * }
36
+ * export const story = () => <Button label="The Button" />;
37
+ * ```
38
+ *
39
+ * Becomes:
40
+ *
41
+ * ```
42
+ * import { Meta, Story } from '@storybook/addon-docs';
43
+ * input { Button } from './Button';
44
+ *
45
+ * <Meta title='Button' />
46
+ *
47
+ * <Story name='story'>
48
+ * <Button label="The Button" />
49
+ * </Story>
50
+ * ```
51
+ */
52
+ export default function transformer(file, api) {
53
+ const j = api.jscodeshift;
54
+ const root = j(file.source);
55
+
56
+ // FIXME: save out all the storyFn.story = { ... }
57
+ const storyKeyToStory = {};
58
+ // save out includeStories / excludeStories
59
+ const meta = {};
60
+
61
+ function makeAttr(key, val) {
62
+ return j.jsxAttribute(
63
+ j.jsxIdentifier(key),
64
+ val.type === 'Literal' ? val : j.jsxExpressionContainer(val)
65
+ );
66
+ }
67
+
68
+ function getStoryContents(node) {
69
+ return node.type === 'ArrowFunctionExpression' && node.body.type === 'JSXElement'
70
+ ? node.body
71
+ : j.jsxExpressionContainer(node);
72
+ }
73
+
74
+ function getName(storyKey) {
75
+ const story = storyKeyToStory[storyKey];
76
+ if (story) {
77
+ const name = story.properties.find((prop) => prop.key.name === 'name');
78
+ if (name && name.value.type === 'Literal') {
79
+ return name.value.value;
80
+ }
81
+ }
82
+ return storyKey;
83
+ }
84
+
85
+ function getStoryAttrs(storyKey) {
86
+ const attrs = [];
87
+ const story = storyKeyToStory[storyKey];
88
+ if (story) {
89
+ story.properties.forEach((prop) => {
90
+ const { key, value } = prop;
91
+ if (key.name !== 'name') {
92
+ attrs.push(makeAttr(key.name, value));
93
+ }
94
+ });
95
+ }
96
+ return attrs;
97
+ }
98
+
99
+ // 1. If the program does not have `export default { title: '....' }, skip it
100
+ const defaultExportWithTitle = root
101
+ .find(j.ExportDefaultDeclaration)
102
+ .filter((def) => def.node.declaration.properties.map((p) => p.key.name).includes('title'));
103
+ if (defaultExportWithTitle.size() === 0) {
104
+ return root.toSource();
105
+ }
106
+
107
+ // 2a. Add imports from '@storybook/addon-docs'
108
+ root
109
+ .find(j.ImportDeclaration)
110
+ .at(-1)
111
+ .insertAfter(j.emptyStatement())
112
+ .insertAfter(
113
+ j.importDeclaration(
114
+ [j.importSpecifier(j.identifier('Meta')), j.importSpecifier(j.identifier('Story'))],
115
+ j.literal('@storybook/addon-docs')
116
+ )
117
+ );
118
+ // 2b. Remove react import which is implicit
119
+ root
120
+ .find(j.ImportDeclaration)
121
+ .filter((decl) => decl.node.source.value === 'react')
122
+ .remove();
123
+
124
+ // 3. Save out all the excluded stories
125
+ defaultExportWithTitle.forEach((exp) => {
126
+ exp.node.declaration.properties.forEach((p) => {
127
+ if (['includeStories', 'excludeStories'].includes(p.key.name)) {
128
+ meta[p.key.name] = parseIncludeExclude(p.value);
129
+ }
130
+ });
131
+ });
132
+
133
+ // 4. Collect all the story exports in storyKeyToStory[key] = null;
134
+ const namedExports = root.find(j.ExportNamedDeclaration);
135
+ namedExports.forEach((exp) => {
136
+ const storyKey = exp.node.declaration.declarations[0].id.name;
137
+ if (isExportStory(storyKey, meta)) {
138
+ storyKeyToStory[storyKey] = null;
139
+ }
140
+ });
141
+
142
+ // 5. Collect all the storyKey.story in storyKeyToStory and also remove them
143
+ const storyAssignments = root.find(j.AssignmentExpression).filter((exp) => {
144
+ const { left } = exp.node;
145
+ return (
146
+ left.type === 'MemberExpression' &&
147
+ left.object.type === 'Identifier' &&
148
+ left.object.name in storyKeyToStory &&
149
+ left.property.type === 'Identifier' &&
150
+ left.property.name === 'story'
151
+ );
152
+ });
153
+ storyAssignments.forEach((exp) => {
154
+ const { left, right } = exp.node;
155
+ storyKeyToStory[left.object.name] = right;
156
+ });
157
+ storyAssignments.remove();
158
+
159
+ // 6. Convert the default export to <Meta />
160
+ defaultExportWithTitle.replaceWith((exp) => {
161
+ const jsxId = j.jsxIdentifier('Meta');
162
+ const attrs = [];
163
+ exp.node.declaration.properties.forEach((prop) => {
164
+ const { key, value } = prop;
165
+ if (!['includeStories', 'excludeStories'].includes(key.name)) {
166
+ attrs.push(makeAttr(key.name, value));
167
+ }
168
+ });
169
+ const opening = j.jsxOpeningElement(jsxId, attrs);
170
+ opening.selfClosing = true;
171
+ return j.jsxElement(opening);
172
+ });
173
+
174
+ // 7. Convert all the named exports to <Story>...</Story>
175
+ namedExports.replaceWith((exp) => {
176
+ const storyKey = exp.node.declaration.declarations[0].id.name;
177
+ if (!isExportStory(storyKey, meta)) {
178
+ return exp.node;
179
+ }
180
+ const jsxId = j.jsxIdentifier('Story');
181
+ const name = getName(storyKey);
182
+ const attributes = [makeAttr('name', j.literal(name)), ...getStoryAttrs(storyKey)];
183
+ const opening = j.jsxOpeningElement(jsxId, attributes);
184
+ const closing = j.jsxClosingElement(jsxId);
185
+ const children = [getStoryContents(exp.node.declaration.declarations[0].init)];
186
+ return j.jsxElement(opening, closing, children);
187
+ });
188
+
189
+ return exportMdx(root, { quote: 'single', trailingComma: 'true', tabWidth: 2 });
190
+ }
@@ -0,0 +1,184 @@
1
+ // import recast from 'recast';
2
+ import mdx from '@mdx-js/mdx';
3
+ import prettier from 'prettier';
4
+ import { sanitizeName } from '../lib/utils';
5
+
6
+ /**
7
+ * Convert a component's MDX file into module story format
8
+ */
9
+ export default function transformer(file, api) {
10
+ const j = api.jscodeshift;
11
+ const code = mdx.sync(file.source, {});
12
+ const root = j(code);
13
+
14
+ function parseJsxAttributes(attributes) {
15
+ const result = {};
16
+ attributes.forEach((attr) => {
17
+ const key = attr.name.name;
18
+ const val = attr.value.type === 'JSXExpressionContainer' ? attr.value.expression : attr.value;
19
+ result[key] = val;
20
+ });
21
+ return result;
22
+ }
23
+
24
+ function genObjectExpression(attrs) {
25
+ return j.objectExpression(
26
+ Object.entries(attrs).map(([key, val]) => j.property('init', j.identifier(key), val))
27
+ );
28
+ }
29
+
30
+ function convertToStories(path) {
31
+ const base = j(path);
32
+
33
+ const meta = {};
34
+ const includeStories = [];
35
+ const storyStatements = [];
36
+
37
+ // get rid of all mdxType junk
38
+ base
39
+ .find(j.JSXAttribute)
40
+ .filter((attr) => attr.node.name.name === 'mdxType')
41
+ .remove();
42
+
43
+ // parse <Meta title="..." />
44
+ base
45
+ .find(j.JSXElement)
46
+ .filter((elt) => elt.node.openingElement.name.name === 'Meta')
47
+ .forEach((elt) => {
48
+ const attrs = parseJsxAttributes(elt.node.openingElement.attributes);
49
+ Object.assign(meta, attrs);
50
+ });
51
+
52
+ // parse <Story name="..." />
53
+ base
54
+ .find(j.JSXElement)
55
+ .filter((elt) => elt.node.openingElement.name.name === 'Story')
56
+ .forEach((elt) => {
57
+ const attrs = parseJsxAttributes(elt.node.openingElement.attributes);
58
+ if (attrs.name) {
59
+ const storyKey = sanitizeName(attrs.name.value);
60
+ includeStories.push(storyKey);
61
+ if (storyKey === attrs.name.value) {
62
+ delete attrs.name;
63
+ }
64
+ let body =
65
+ elt.node.children.find((n) => n.type !== 'JSXText') ||
66
+ j.literal(elt.node.children[0].value);
67
+
68
+ if (body.type === 'JSXExpressionContainer') {
69
+ body = body.expression;
70
+ }
71
+
72
+ storyStatements.push(
73
+ j.exportDeclaration(
74
+ false,
75
+ j.variableDeclaration('const', [
76
+ j.variableDeclarator(
77
+ j.identifier(storyKey),
78
+ body.type === 'ArrowFunctionExpression'
79
+ ? body
80
+ : j.arrowFunctionExpression([], body)
81
+ ),
82
+ ])
83
+ )
84
+ );
85
+ if (Object.keys(attrs).length > 0) {
86
+ storyStatements.push(
87
+ j.assignmentStatement(
88
+ '=',
89
+ j.memberExpression(j.identifier(storyKey), j.identifier('story')),
90
+ genObjectExpression(attrs)
91
+ )
92
+ );
93
+ }
94
+ storyStatements.push(j.emptyStatement());
95
+ }
96
+ });
97
+
98
+ if (root.find(j.ExportNamedDeclaration).size() > 0) {
99
+ meta.includeStories = j.arrayExpression(includeStories.map((key) => j.literal(key)));
100
+ }
101
+ const statements = [
102
+ j.exportDefaultDeclaration(genObjectExpression(meta)),
103
+ j.emptyStatement(),
104
+ ...storyStatements,
105
+ ];
106
+
107
+ const lastStatement = root.find(j.Statement).at(-1);
108
+ statements.reverse().forEach((stmt) => {
109
+ lastStatement.insertAfter(stmt);
110
+ });
111
+ base.remove();
112
+ }
113
+
114
+ root.find(j.ExportDefaultDeclaration).forEach(convertToStories);
115
+
116
+ // strip out Story/Meta import and MDX junk
117
+
118
+ // /* @jsx mdx */
119
+ root
120
+ .find(j.ImportDeclaration)
121
+ .at(0)
122
+ .replaceWith((exp) => j.importDeclaration(exp.node.specifiers, exp.node.source));
123
+
124
+ // import { Story, Meta } from '@storybook/addon-docs';
125
+ root
126
+ .find(j.ImportDeclaration)
127
+ .filter((exp) => exp.node.source.value === '@storybook/addon-docs')
128
+ .remove();
129
+
130
+ // const makeShortcode = ...
131
+ // const layoutProps = {};
132
+ // const MDXLayout = 'wrapper';
133
+ const MDX_DECLS = ['makeShortcode', 'layoutProps', 'MDXLayout'];
134
+ root
135
+ .find(j.VariableDeclaration)
136
+ .filter(
137
+ (decl) =>
138
+ decl.node.declarations.length === 1 && MDX_DECLS.includes(decl.node.declarations[0].id.name)
139
+ )
140
+ .remove();
141
+
142
+ // const Source = makeShortcode('Source');
143
+ root
144
+ .find(j.VariableDeclarator)
145
+ .filter(
146
+ (expr) =>
147
+ expr.node.init.type === 'CallExpression' &&
148
+ expr.node.init.callee.type === 'Identifier' &&
149
+ expr.node.init.callee.name === 'makeShortcode'
150
+ )
151
+ .remove();
152
+
153
+ // MDXContent.isMDXComponent = true;
154
+ root
155
+ .find(j.AssignmentExpression)
156
+ .filter(
157
+ (expr) =>
158
+ expr.node.left.type === 'MemberExpression' &&
159
+ expr.node.left.object.type === 'Identifier' &&
160
+ expr.node.left.object.name === 'MDXContent'
161
+ )
162
+ .remove();
163
+
164
+ // Add back `import React from 'react';` which is implicit in MDX
165
+ const react = root.find(j.ImportDeclaration).filter((decl) => decl.node.source.value === 'react');
166
+ if (react.size() === 0) {
167
+ root
168
+ .find(j.Statement)
169
+ .at(0)
170
+ .insertBefore(
171
+ j.importDeclaration([j.importDefaultSpecifier(j.identifier('React'))], j.literal('react'))
172
+ );
173
+ }
174
+
175
+ const source = root.toSource({ trailingComma: true, quote: 'single', tabWidth: 2 });
176
+ return prettier.format(source, {
177
+ parser: 'babel',
178
+ printWidth: 100,
179
+ tabWidth: 2,
180
+ bracketSpacing: true,
181
+ trailingComma: 'es5',
182
+ singleQuote: true,
183
+ });
184
+ }
@@ -0,0 +1,32 @@
1
+ export default function transformer(file, api) {
2
+ const j = api.jscodeshift;
3
+
4
+ const createImportDeclaration = (specifiers, source) =>
5
+ j.importDeclaration(
6
+ specifiers.map((s) => j.importSpecifier(j.identifier(s))),
7
+ j.literal(source)
8
+ );
9
+
10
+ const deprecates = {
11
+ action: [['action'], '@storybook/addon-actions'],
12
+ linkTo: [['linkTo'], '@storybook/addon-links'],
13
+ };
14
+
15
+ const transform = j(file.source)
16
+ .find(j.ImportDeclaration)
17
+ .filter((i) => i.value.source.value === '@storybook/react')
18
+ .forEach((i) => {
19
+ const importStatement = i.value;
20
+ importStatement.specifiers = importStatement.specifiers.filter((specifier) => {
21
+ const item = deprecates[specifier.local.name];
22
+ if (item) {
23
+ const [specifiers, moduleName] = item;
24
+ i.insertAfter(createImportDeclaration(specifiers, moduleName));
25
+ return false;
26
+ }
27
+ return specifier;
28
+ });
29
+ });
30
+
31
+ return transform.toSource({ quote: 'single' });
32
+ }
@@ -0,0 +1,277 @@
1
+ import prettier from 'prettier';
2
+ import { logger } from '@storybook/node-logger';
3
+ import { storyNameFromExport } from '@storybook/csf';
4
+ import { sanitizeName, jscodeshiftToPrettierParser } from '../lib/utils';
5
+
6
+ /**
7
+ * Convert a legacy story API to component story format
8
+ *
9
+ * For example:
10
+ *
11
+ * ```
12
+ * input { Button } from './Button';
13
+ * storiesOf('Button', module).add('story', () => <Button label="The Button" />);
14
+ * ```
15
+ *
16
+ * Becomes:
17
+ *
18
+ * ```
19
+ * input { Button } from './Button';
20
+ * export default {
21
+ * title: 'Button'
22
+ * }
23
+ * export const story = () => <Button label="The Button" />;
24
+ *
25
+ * NOTES: only support chained storiesOf() calls
26
+ */
27
+ export default function transformer(file, api, options) {
28
+ const LITERAL = ['ts', 'tsx'].includes(options.parser) ? 'StringLiteral' : 'Literal';
29
+
30
+ const j = api.jscodeshift;
31
+ const root = j(file.source);
32
+
33
+ function extractDecorators(parameters) {
34
+ if (!parameters) {
35
+ return {};
36
+ }
37
+ if (!parameters.properties) {
38
+ return { storyParams: parameters };
39
+ }
40
+ let storyDecorators = parameters.properties.find((p) => p.key.name === 'decorators');
41
+ if (!storyDecorators) {
42
+ return { storyParams: parameters };
43
+ }
44
+ storyDecorators = storyDecorators.value;
45
+ const storyParams = { ...parameters };
46
+ storyParams.properties = storyParams.properties.filter((p) => p.key.name !== 'decorators');
47
+ if (storyParams.properties.length === 0) {
48
+ return { storyDecorators };
49
+ }
50
+ return { storyParams, storyDecorators };
51
+ }
52
+
53
+ function convertToModuleExports(path, originalExports) {
54
+ const base = j(path);
55
+
56
+ const statements = [];
57
+ const extraExports = [];
58
+
59
+ // .addDecorator
60
+ const decorators = [];
61
+ base
62
+ .find(j.CallExpression)
63
+ .filter(
64
+ (call) => call.node.callee.property && call.node.callee.property.name === 'addDecorator'
65
+ )
66
+ .forEach((add) => {
67
+ const decorator = add.node.arguments[0];
68
+ decorators.push(decorator);
69
+ });
70
+ if (decorators.length > 0) {
71
+ decorators.reverse();
72
+ extraExports.push(
73
+ j.property('init', j.identifier('decorators'), j.arrayExpression(decorators))
74
+ );
75
+ }
76
+
77
+ // .addParameters
78
+ const parameters = [];
79
+ base
80
+ .find(j.CallExpression)
81
+ .filter(
82
+ (call) => call.node.callee.property && call.node.callee.property.name === 'addParameters'
83
+ )
84
+ .forEach((add) => {
85
+ // jscodeshift gives us the find results in reverse, but these args come in
86
+ // order, so we double reverse here. ugh.
87
+ const params = [...add.node.arguments[0].properties];
88
+ params.reverse();
89
+ params.forEach((prop) => parameters.push(prop));
90
+ });
91
+ if (parameters.length > 0) {
92
+ parameters.reverse();
93
+ extraExports.push(
94
+ j.property('init', j.identifier('parameters'), j.objectExpression(parameters))
95
+ );
96
+ }
97
+
98
+ if (originalExports.length > 0) {
99
+ extraExports.push(
100
+ j.property(
101
+ 'init',
102
+ j.identifier('excludeStories'),
103
+ j.arrayExpression(originalExports.map((exp) => j.literal(exp)))
104
+ )
105
+ );
106
+ }
107
+
108
+ // storiesOf(...)
109
+ base
110
+ .find(j.CallExpression)
111
+ .filter((call) => call.node.callee.name === 'storiesOf')
112
+ .filter((call) => call.node.arguments.length > 0 && call.node.arguments[0].type === LITERAL)
113
+ .forEach((storiesOf) => {
114
+ const title = storiesOf.node.arguments[0].value;
115
+ statements.push(
116
+ j.exportDefaultDeclaration(
117
+ j.objectExpression([
118
+ j.property('init', j.identifier('title'), j.literal(title)),
119
+ ...extraExports,
120
+ ])
121
+ )
122
+ );
123
+ });
124
+
125
+ // .add(...)
126
+ const adds = [];
127
+ base
128
+ .find(j.CallExpression)
129
+ .filter((add) => add.node.callee.property && add.node.callee.property.name === 'add')
130
+ .filter((add) => add.node.arguments.length >= 2 && add.node.arguments[0].type === LITERAL)
131
+ .forEach((add) => adds.push(add));
132
+
133
+ adds.reverse();
134
+ adds.push(path);
135
+
136
+ const identifiers = new Set();
137
+ root.find(j.Identifier).forEach(({ value }) => identifiers.add(value.name));
138
+ adds.forEach((add) => {
139
+ let name = add.node.arguments[0].value;
140
+ let key = sanitizeName(name);
141
+ while (identifiers.has(key)) {
142
+ key = `_${key}`;
143
+ }
144
+ identifiers.add(key);
145
+ if (storyNameFromExport(key) === name) {
146
+ name = null;
147
+ }
148
+
149
+ const val = add.node.arguments[1];
150
+ statements.push(
151
+ j.exportDeclaration(
152
+ false,
153
+ j.variableDeclaration('const', [j.variableDeclarator(j.identifier(key), val)])
154
+ )
155
+ );
156
+
157
+ const storyAnnotations = [];
158
+
159
+ if (name) {
160
+ storyAnnotations.push(j.property('init', j.identifier('name'), j.literal(name)));
161
+ }
162
+
163
+ if (add.node.arguments.length > 2) {
164
+ const originalStoryParams = add.node.arguments[2];
165
+ const { storyParams, storyDecorators } = extractDecorators(originalStoryParams);
166
+ if (storyParams) {
167
+ storyAnnotations.push(j.property('init', j.identifier('parameters'), storyParams));
168
+ }
169
+ if (storyDecorators) {
170
+ storyAnnotations.push(j.property('init', j.identifier('decorators'), storyDecorators));
171
+ }
172
+ }
173
+
174
+ if (storyAnnotations.length > 0) {
175
+ statements.push(
176
+ j.assignmentStatement(
177
+ '=',
178
+ j.memberExpression(j.identifier(key), j.identifier('story')),
179
+ j.objectExpression(storyAnnotations)
180
+ )
181
+ );
182
+ }
183
+ });
184
+
185
+ const stmt = path.parent.node.type === 'VariableDeclarator' ? path.parent.parent : path.parent;
186
+
187
+ statements.reverse();
188
+ statements.forEach((s) => stmt.insertAfter(s));
189
+ j(stmt).remove();
190
+ }
191
+
192
+ // Save the original storiesOf
193
+ const initialStoriesOf = root
194
+ .find(j.CallExpression)
195
+ .filter((call) => call.node.callee.name === 'storiesOf');
196
+
197
+ const defaultExports = root.find(j.ExportDefaultDeclaration);
198
+ // If there's already a default export
199
+ if (defaultExports.size() > 0) {
200
+ if (initialStoriesOf.size() > 0) {
201
+ logger.warn(
202
+ `Found ${initialStoriesOf.size()} 'storiesOf' calls but existing default export, SKIPPING: '${
203
+ file.path
204
+ }'`
205
+ );
206
+ }
207
+ return root.toSource();
208
+ }
209
+
210
+ // Exclude all the original named exports
211
+ const originalExports = [];
212
+ root.find(j.ExportNamedDeclaration).forEach((exp) => {
213
+ const { declaration, specifiers } = exp.node;
214
+ if (declaration) {
215
+ const { id, declarations } = declaration;
216
+ if (declarations) {
217
+ declarations.forEach((decl) => {
218
+ const { name, properties } = decl.id;
219
+ if (name) {
220
+ originalExports.push(name);
221
+ } else if (properties) {
222
+ properties.forEach((prop) => originalExports.push(prop.key.name));
223
+ }
224
+ });
225
+ } else if (id) {
226
+ originalExports.push(id.name);
227
+ }
228
+ } else if (specifiers) {
229
+ specifiers.forEach((spec) => originalExports.push(spec.exported.name));
230
+ }
231
+ });
232
+
233
+ // each top-level add expression corresponds to the last "add" of the chain.
234
+ // replace it with the entire export statements
235
+ root
236
+ .find(j.CallExpression)
237
+ .filter((add) => add.node.callee.property && add.node.callee.property.name === 'add')
238
+ .filter((add) => add.node.arguments.length >= 2 && add.node.arguments[0].type === LITERAL)
239
+ .filter((add) =>
240
+ ['ExpressionStatement', 'VariableDeclarator'].includes(add.parentPath.node.type)
241
+ )
242
+ .forEach((path) => convertToModuleExports(path, originalExports));
243
+
244
+ // remove storiesOf import
245
+ root
246
+ .find(j.ImportSpecifier)
247
+ .filter(
248
+ (spec) =>
249
+ spec.node.imported.name === 'storiesOf' &&
250
+ spec.parent.node.source.value.startsWith('@storybook/')
251
+ )
252
+ .forEach((spec) => {
253
+ const toRemove = spec.parent.node.specifiers.length > 1 ? spec : spec.parent;
254
+ j(toRemove).remove();
255
+ });
256
+
257
+ const source = root.toSource({ trailingComma: true, quote: 'single', tabWidth: 2 });
258
+ if (initialStoriesOf.size() > 1) {
259
+ logger.warn(
260
+ `Found ${initialStoriesOf.size()} 'storiesOf' calls, PLEASE FIX BY HAND: '${file.path}'`
261
+ );
262
+ return source;
263
+ }
264
+
265
+ const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
266
+ printWidth: 100,
267
+ tabWidth: 2,
268
+ bracketSpacing: true,
269
+ trailingComma: 'es5',
270
+ singleQuote: true,
271
+ };
272
+
273
+ return prettier.format(source, {
274
+ ...prettierConfig,
275
+ parser: jscodeshiftToPrettierParser(options.parser),
276
+ });
277
+ }