@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,251 @@
1
+ import { describe } from '@jest/globals';
2
+ import { dedent } from 'ts-dedent';
3
+ import _transform from '../csf-2-to-3';
4
+
5
+ // @ts-expect-error (Converted from ts-ignore)
6
+ expect.addSnapshotSerializer({
7
+ print: (val: any) => val,
8
+ test: (val) => true,
9
+ });
10
+
11
+ const jsTransform = (source: string) => _transform({ source }, null, {}).trim();
12
+ const tsTransform = (source: string) => _transform({ source }, null, { parser: 'tsx' }).trim();
13
+
14
+ describe('csf-2-to-3', () => {
15
+ describe('javascript', () => {
16
+ it('should replace non-simple function exports with objects', () => {
17
+ expect(
18
+ jsTransform(dedent`
19
+ export default { title: 'Cat' };
20
+ export const A = () => <Cat />;
21
+ export const B = (args) => <Button {...args} />;
22
+ `)
23
+ ).toMatchInlineSnapshot(`
24
+ export default {
25
+ title: 'Cat',
26
+ };
27
+ export const A = () => <Cat />;
28
+ export const B = {
29
+ render: (args) => <Button {...args} />,
30
+ };
31
+ `);
32
+ });
33
+
34
+ it('should move annotations into story objects', () => {
35
+ expect(
36
+ jsTransform(dedent`
37
+ export default { title: 'Cat' };
38
+ export const A = () => <Cat />;
39
+ A.storyName = 'foo';
40
+ A.parameters = { bar: 2 };
41
+ A.play = () => {};
42
+ `)
43
+ ).toMatchInlineSnapshot(`
44
+ export default {
45
+ title: 'Cat',
46
+ };
47
+ export const A = {
48
+ render: () => <Cat />,
49
+ name: 'foo',
50
+ parameters: {
51
+ bar: 2,
52
+ },
53
+ play: () => {},
54
+ };
55
+ `);
56
+ });
57
+
58
+ it('should ignore non-story exports, statements', () => {
59
+ expect(
60
+ jsTransform(dedent`
61
+ export default { title: 'components/Fruit', includeStories: ['A'] };
62
+ export const A = (args) => <Apple {...args} />;
63
+ export const B = (args) => <Banana {...args} />;
64
+ const C = (args) => <Cherry {...args} />;
65
+ `)
66
+ ).toMatchInlineSnapshot(`
67
+ export default {
68
+ title: 'components/Fruit',
69
+ includeStories: ['A'],
70
+ };
71
+ export const A = {
72
+ render: (args) => <Apple {...args} />,
73
+ };
74
+ export const B = (args) => <Banana {...args} />;
75
+ const C = (args) => <Cherry {...args} />;
76
+ `);
77
+ });
78
+
79
+ it('should do nothing when there is no meta', () => {
80
+ expect(
81
+ jsTransform(dedent`
82
+ export const A = () => <Apple />;
83
+ export const B = (args) => <Banana {...args} />;
84
+ `)
85
+ ).toMatchInlineSnapshot(`
86
+ export const A = () => <Apple />;
87
+ export const B = (args) => <Banana {...args} />;
88
+ `);
89
+ });
90
+
91
+ it('should remove implicit global render function (react)', () => {
92
+ expect(
93
+ jsTransform(dedent`
94
+ export default { title: 'Cat', component: Cat };
95
+ export const A = (args) => <Cat {...args} />;
96
+ export const B = (args) => <Banana {...args} />;
97
+ `)
98
+ ).toMatchInlineSnapshot(`
99
+ export default {
100
+ title: 'Cat',
101
+ component: Cat,
102
+ };
103
+ export const A = {};
104
+ export const B = {
105
+ render: (args) => <Banana {...args} />,
106
+ };
107
+ `);
108
+ });
109
+
110
+ it('should ignore object exports', () => {
111
+ expect(
112
+ jsTransform(dedent`
113
+ export default { title: 'Cat', component: Cat };
114
+ export const A = {
115
+ render: (args) => <Cat {...args} />
116
+ };
117
+ `)
118
+ ).toMatchInlineSnapshot(`
119
+ export default {
120
+ title: 'Cat',
121
+ component: Cat,
122
+ };
123
+ export const A = {
124
+ render: (args) => <Cat {...args} />,
125
+ };
126
+ `);
127
+ });
128
+
129
+ it('should hoist template.bind (if there is only one)', () => {
130
+ expect(
131
+ jsTransform(dedent`
132
+ export default { title: 'Cat' };
133
+ const Template = (args) => <Cat {...args} />;
134
+ export const A = Template.bind({});
135
+ A.args = { isPrimary: false };
136
+ `)
137
+ ).toMatchInlineSnapshot(`
138
+ export default {
139
+ title: 'Cat',
140
+ };
141
+ export const A = {
142
+ render: (args) => <Cat {...args} />,
143
+ args: {
144
+ isPrimary: false,
145
+ },
146
+ };
147
+ `);
148
+ });
149
+
150
+ it('should remove implicit global render for template.bind', () => {
151
+ expect(
152
+ jsTransform(dedent`
153
+ export default { title: 'Cat', component: Cat };
154
+ const Template = (args) => <Cat {...args} />;
155
+ export const A = Template.bind({});
156
+ A.args = { isPrimary: false };
157
+ const Template2 = (args) => <Banana {...args} />;
158
+ export const B = Template2.bind({});
159
+ B.args = { isPrimary: true };
160
+ `)
161
+ ).toMatchInlineSnapshot(`
162
+ export default {
163
+ title: 'Cat',
164
+ component: Cat,
165
+ };
166
+ export const A = {
167
+ args: {
168
+ isPrimary: false,
169
+ },
170
+ };
171
+ export const B = {
172
+ render: (args) => <Banana {...args} />,
173
+ args: {
174
+ isPrimary: true,
175
+ },
176
+ };
177
+ `);
178
+ });
179
+
180
+ it('should ignore no-arg stories without annotations', () => {
181
+ expect(
182
+ jsTransform(dedent`
183
+ export default { title: 'Cat', component: Cat };
184
+ export const A = (args) => <Cat {...args} />;
185
+ export const B = () => <Cat name="frisky" />;
186
+ export const C = () => <Cat name="fluffy" />;
187
+ C.parameters = { foo: 2 };
188
+ `)
189
+ ).toMatchInlineSnapshot(`
190
+ export default {
191
+ title: 'Cat',
192
+ component: Cat,
193
+ };
194
+ export const A = {};
195
+ export const B = () => <Cat name="frisky" />;
196
+ export const C = {
197
+ render: () => <Cat name="fluffy" />,
198
+ parameters: {
199
+ foo: 2,
200
+ },
201
+ };
202
+ `);
203
+ });
204
+
205
+ it('should work for v1-style annotations', () => {
206
+ expect(
207
+ jsTransform(dedent`
208
+ export default { title: 'Cat' };
209
+ export const A = (args) => <Cat {...args} />;
210
+ A.story = {
211
+ parameters: { foo: 2 }
212
+ };
213
+ `)
214
+ ).toMatchInlineSnapshot(`
215
+ export default {
216
+ title: 'Cat',
217
+ };
218
+ export const A = {
219
+ render: (args) => <Cat {...args} />,
220
+ parameters: {
221
+ foo: 2,
222
+ },
223
+ };
224
+ `);
225
+ });
226
+ });
227
+
228
+ describe('typescript', () => {
229
+ it('should replace function exports with objects', () => {
230
+ expect(
231
+ tsTransform(dedent`
232
+ export default { title: 'Cat' } as Meta<CatProps>;
233
+ export const A: Story<CatProps> = (args) => <Cat {...args} />;
234
+ export const B: any = (args) => <Button {...args} />;
235
+ export const C: Story<CatProps> = () => <Cat />;
236
+ `)
237
+ ).toMatchInlineSnapshot(`
238
+ export default {
239
+ title: 'Cat',
240
+ } as Meta<CatProps>;
241
+ export const A: Story<CatProps> = {
242
+ render: (args) => <Cat {...args} />,
243
+ };
244
+ export const B: any = {
245
+ render: (args) => <Button {...args} />,
246
+ };
247
+ export const C: Story<CatProps> = () => <Cat />;
248
+ `);
249
+ });
250
+ });
251
+ });
@@ -0,0 +1,30 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import 'jest-specific-snapshot';
4
+ import { applyTransform } from 'jscodeshift/dist/testUtils';
5
+
6
+ jest.mock('@storybook/node-logger');
7
+
8
+ const inputRegExp = /\.input\.js$/;
9
+
10
+ const fixturesDir = path.resolve(__dirname, '../__testfixtures__');
11
+ fs.readdirSync(fixturesDir).forEach((transformName) => {
12
+ const transformFixturesDir = path.join(fixturesDir, transformName);
13
+ describe(transformName, () =>
14
+ fs
15
+ .readdirSync(transformFixturesDir)
16
+ .filter((fileName) => inputRegExp.test(fileName))
17
+ .forEach((fileName) => {
18
+ const inputPath = path.join(transformFixturesDir, fileName);
19
+ it(`transforms correctly using "${fileName}" data`, () =>
20
+ expect(
21
+ applyTransform(
22
+ // eslint-disable-next-line global-require,import/no-dynamic-require
23
+ require(path.join(__dirname, '..', transformName)),
24
+ null,
25
+ { path: inputPath, source: fs.readFileSync(inputPath, 'utf8') }
26
+ )
27
+ ).toMatchSpecificSnapshot(inputPath.replace(inputRegExp, '.output.snapshot')));
28
+ })
29
+ );
30
+ });
@@ -18,40 +18,45 @@
18
18
  * - Button must be imported in the file
19
19
  */
20
20
  export default function transformer(file, api) {
21
- var j = api.jscodeshift;
22
- var root = j(file.source); // Create a dictionary whose keys are all the named imports in the file.
21
+ const j = api.jscodeshift;
22
+ const root = j(file.source);
23
+
24
+ // Create a dictionary whose keys are all the named imports in the file.
23
25
  // For instance:
24
26
  //
25
27
  // import foo from 'foo';
26
28
  // import { bar, baz } from 'zoo';
27
29
  //
28
30
  // => { foo: true, bar: true, baz: true }
29
-
30
- var importMap = {};
31
- root.find(j.ImportDeclaration).forEach(function (imp) {
32
- return imp.node.specifiers.forEach(function (spec) {
31
+ const importMap = {};
32
+ root.find(j.ImportDeclaration).forEach((imp) =>
33
+ imp.node.specifiers.forEach((spec) => {
33
34
  importMap[spec.local.name] = true;
34
- });
35
- });
35
+ })
36
+ );
36
37
 
37
38
  function getLeafName(string) {
38
- var parts = string.split(/\/|\.|\|/);
39
+ const parts = string.split(/\/|\.|\|/);
39
40
  return parts[parts.length - 1];
40
41
  }
41
42
 
42
43
  function addComponentParameter(call) {
43
- var node = call.node;
44
- var leafName = getLeafName(node.arguments[0].value);
45
- return j.callExpression(j.memberExpression(node, j.identifier('addParameters')), [j.objectExpression([j.property('init', j.identifier('component'), j.identifier(leafName))])]);
44
+ const { node } = call;
45
+ const leafName = getLeafName(node.arguments[0].value);
46
+ return j.callExpression(j.memberExpression(node, j.identifier('addParameters')), [
47
+ j.objectExpression([j.property('init', j.identifier('component'), j.identifier(leafName))]),
48
+ ]);
46
49
  }
47
50
 
48
- root.find(j.CallExpression).filter(function (call) {
49
- return call.node.callee.name === 'storiesOf';
50
- }).filter(function (call) {
51
- return call.node.arguments.length > 0 && call.node.arguments[0].type === 'Literal';
52
- }).filter(function (call) {
53
- var leafName = getLeafName(call.node.arguments[0].value);
54
- return importMap[leafName];
55
- }).replaceWith(addComponentParameter);
51
+ root
52
+ .find(j.CallExpression)
53
+ .filter((call) => call.node.callee.name === 'storiesOf')
54
+ .filter((call) => call.node.arguments.length > 0 && call.node.arguments[0].type === 'Literal')
55
+ .filter((call) => {
56
+ const leafName = getLeafName(call.node.arguments[0].value);
57
+ return importMap[leafName];
58
+ })
59
+ .replaceWith(addComponentParameter);
60
+
56
61
  return root.toSource();
57
- }
62
+ }
@@ -0,0 +1,183 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ import prettier from 'prettier';
3
+ import * as t from '@babel/types';
4
+ import { CsfFile, formatCsf, loadCsf } from '@storybook/csf-tools';
5
+ import { jscodeshiftToPrettierParser } from '../lib/utils';
6
+
7
+ const logger = console;
8
+
9
+ const _rename = (annotation: string) => {
10
+ return annotation === 'storyName' ? 'name' : annotation;
11
+ };
12
+
13
+ const getTemplateBindVariable = (init: t.Expression) =>
14
+ t.isCallExpression(init) &&
15
+ t.isMemberExpression(init.callee) &&
16
+ t.isIdentifier(init.callee.object) &&
17
+ t.isIdentifier(init.callee.property) &&
18
+ init.callee.property.name === 'bind' &&
19
+ (init.arguments.length === 0 ||
20
+ (init.arguments.length === 1 &&
21
+ t.isObjectExpression(init.arguments[0]) &&
22
+ init.arguments[0].properties.length === 0))
23
+ ? init.callee.object.name
24
+ : null;
25
+
26
+ // export const A = ...
27
+ // A.parameters = { ... }; <===
28
+ const isStoryAnnotation = (stmt: t.Statement, objectExports: Record<string, any>) =>
29
+ t.isExpressionStatement(stmt) &&
30
+ t.isAssignmentExpression(stmt.expression) &&
31
+ t.isMemberExpression(stmt.expression.left) &&
32
+ t.isIdentifier(stmt.expression.left.object) &&
33
+ objectExports[stmt.expression.left.object.name];
34
+
35
+ const isTemplateDeclaration = (stmt: t.Statement, templates: Record<string, any>) =>
36
+ t.isVariableDeclaration(stmt) &&
37
+ stmt.declarations.length === 1 &&
38
+ t.isIdentifier(stmt.declarations[0].id) &&
39
+ templates[stmt.declarations[0].id.name];
40
+
41
+ const getNewExport = (stmt: t.Statement, objectExports: Record<string, any>) => {
42
+ if (
43
+ t.isExportNamedDeclaration(stmt) &&
44
+ t.isVariableDeclaration(stmt.declaration) &&
45
+ stmt.declaration.declarations.length === 1
46
+ ) {
47
+ const decl = stmt.declaration.declarations[0];
48
+ if (t.isVariableDeclarator(decl) && t.isIdentifier(decl.id)) {
49
+ return objectExports[decl.id.name];
50
+ }
51
+ }
52
+ return null;
53
+ };
54
+
55
+ // Remove render function when it matches the global render function in react
56
+ // export default { component: Cat };
57
+ // export const A = (args) => <Cat {...args} />;
58
+ const isReactGlobalRenderFn = (csf: CsfFile, storyFn: t.Expression) => {
59
+ if (
60
+ csf._meta?.component &&
61
+ t.isArrowFunctionExpression(storyFn) &&
62
+ storyFn.params.length === 1 &&
63
+ t.isJSXElement(storyFn.body)
64
+ ) {
65
+ const { openingElement } = storyFn.body;
66
+ if (
67
+ openingElement.selfClosing &&
68
+ t.isJSXIdentifier(openingElement.name) &&
69
+ openingElement.attributes.length === 1
70
+ ) {
71
+ const attr = openingElement.attributes[0];
72
+ const param = storyFn.params[0];
73
+ if (
74
+ t.isJSXSpreadAttribute(attr) &&
75
+ t.isIdentifier(attr.argument) &&
76
+ t.isIdentifier(param) &&
77
+ param.name === attr.argument.name &&
78
+ csf._meta.component === openingElement.name.name
79
+ ) {
80
+ return true;
81
+ }
82
+ }
83
+ }
84
+ return false;
85
+ };
86
+
87
+ // A simple CSF story is a no-arg story without any extra annotations (params, args, etc.)
88
+ const isSimpleCSFStory = (init: t.Expression, annotations: t.ObjectProperty[]) =>
89
+ annotations.length === 0 && t.isArrowFunctionExpression(init) && init.params.length === 0;
90
+
91
+ function transform({ source }: { source: string }, api: any, options: { parser?: string }) {
92
+ const makeTitle = (userTitle?: string) => {
93
+ return userTitle || 'FIXME';
94
+ };
95
+ const csf = loadCsf(source, { makeTitle });
96
+
97
+ try {
98
+ csf.parse();
99
+ } catch (err) {
100
+ logger.log(`Error ${err}, skipping`);
101
+ return source;
102
+ }
103
+
104
+ const objectExports: Record<string, t.Statement> = {};
105
+ Object.entries(csf._storyExports).forEach(([key, decl]) => {
106
+ const annotations = Object.entries(csf._storyAnnotations[key]).map(([annotation, val]) => {
107
+ return t.objectProperty(t.identifier(_rename(annotation)), val as t.Expression);
108
+ });
109
+
110
+ if (t.isVariableDeclarator(decl)) {
111
+ const { init, id } = decl;
112
+ // only replace arrow function expressions && template
113
+ // ignore no-arg stories without annotations
114
+ const template = getTemplateBindVariable(init);
115
+ if (
116
+ (!t.isArrowFunctionExpression(init) && !template) ||
117
+ isSimpleCSFStory(init, annotations)
118
+ ) {
119
+ return;
120
+ }
121
+
122
+ // Remove the render function when we can hoist the template
123
+ // const Template = (args) => <Cat {...args} />;
124
+ // export const A = Template.bind({});
125
+ let storyFn = template && csf._templates[template];
126
+ if (!storyFn) {
127
+ storyFn = init;
128
+ }
129
+
130
+ const keyId = t.identifier(key);
131
+ // @ts-expect-error (Converted from ts-ignore)
132
+ const { typeAnnotation } = id;
133
+ if (typeAnnotation) {
134
+ keyId.typeAnnotation = typeAnnotation;
135
+ }
136
+
137
+ const renderAnnotation = isReactGlobalRenderFn(csf, storyFn)
138
+ ? []
139
+ : [t.objectProperty(t.identifier('render'), storyFn)];
140
+
141
+ objectExports[key] = t.exportNamedDeclaration(
142
+ t.variableDeclaration('const', [
143
+ t.variableDeclarator(keyId, t.objectExpression([...renderAnnotation, ...annotations])),
144
+ ])
145
+ );
146
+ }
147
+ });
148
+
149
+ const updatedBody = csf._ast.program.body.reduce((acc, stmt) => {
150
+ // remove story annotations & template declarations
151
+ if (isStoryAnnotation(stmt, objectExports) || isTemplateDeclaration(stmt, csf._templates)) {
152
+ return acc;
153
+ }
154
+
155
+ // replace story exports with new object exports
156
+ const newExport = getNewExport(stmt, objectExports);
157
+ if (newExport) {
158
+ acc.push(newExport);
159
+ return acc;
160
+ }
161
+
162
+ // include unknown statements
163
+ acc.push(stmt);
164
+ return acc;
165
+ }, []);
166
+ csf._ast.program.body = updatedBody;
167
+ const output = formatCsf(csf);
168
+
169
+ const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
170
+ printWidth: 100,
171
+ tabWidth: 2,
172
+ bracketSpacing: true,
173
+ trailingComma: 'es5',
174
+ singleQuote: true,
175
+ };
176
+
177
+ return prettier.format(output, {
178
+ ...prettierConfig,
179
+ parser: jscodeshiftToPrettierParser(options?.parser),
180
+ });
181
+ }
182
+
183
+ export default transform;
@@ -0,0 +1,97 @@
1
+ const getContainingStatement = (n) => {
2
+ if (n.node.type.endsWith('Statement')) {
3
+ return n;
4
+ }
5
+ return getContainingStatement(n.parent);
6
+ };
7
+
8
+ /**
9
+ * Hoist CSF .story annotations
10
+ *
11
+ * For example:
12
+ *
13
+ * ```
14
+ * export const Basic = () => <Button />
15
+ * Basic.story = {
16
+ * name: 'foo',
17
+ * parameters: { ... },
18
+ * decorators = [ ... ],
19
+ * };
20
+ * ```
21
+ *
22
+ * Becomes:
23
+ *
24
+ * ```
25
+ * export const Basic = () => <Button />
26
+ * Basic.storyName = 'foo';
27
+ * Basic.parameters = { ... };
28
+ * Basic.decorators = [ ... ];
29
+ * ```
30
+ */
31
+ export default function transformer(file, api) {
32
+ const j = api.jscodeshift;
33
+ const root = j(file.source);
34
+
35
+ const renameKey = (exp) =>
36
+ exp.type === 'Identifier' && exp.name === 'name' ? j.identifier('storyName') : exp;
37
+
38
+ // 1. If the program does not have `export default { title: '....' }, skip it
39
+ const defaultExportWithTitle = root
40
+ .find(j.ExportDefaultDeclaration)
41
+ .filter(
42
+ (def) =>
43
+ def.node.declaration.type === 'ObjectExpression' &&
44
+ def.node.declaration.properties.map((p) => p.key.name).includes('title')
45
+ );
46
+ if (defaultExportWithTitle.size() === 0) {
47
+ return root.toSource();
48
+ }
49
+
50
+ // 2. Replace each Foo.story = { x: xVal } with Foo.x = xVal;
51
+ const storyAssignments = root.find(j.AssignmentExpression).filter((exp) => {
52
+ const { left, right } = exp.node;
53
+ return (
54
+ left.type === 'MemberExpression' &&
55
+ left.object.type === 'Identifier' &&
56
+ left.property.type === 'Identifier' &&
57
+ left.property.name === 'story' &&
58
+ right.type === 'ObjectExpression'
59
+ );
60
+ });
61
+ storyAssignments.forEach((exp) => {
62
+ const { left, right } = exp.node;
63
+ right.properties.forEach((prop) => {
64
+ const stmt = getContainingStatement(exp);
65
+ stmt.insertBefore(
66
+ j.assignmentStatement('=', j.memberExpression(left.object, renameKey(prop.key)), prop.value)
67
+ );
68
+ });
69
+ });
70
+
71
+ // 3. Remove the .story annotations
72
+ storyAssignments.remove();
73
+
74
+ // 4. Replace each Foo.story.x with Foo.x;
75
+ const storyOverrides = root.find(j.AssignmentExpression).filter((exp) => {
76
+ const { left } = exp.node;
77
+ return (
78
+ left.type === 'MemberExpression' &&
79
+ left.object.type === 'MemberExpression' &&
80
+ left.object.property.type === 'Identifier' &&
81
+ left.object.property.name === 'story' &&
82
+ left.property.type === 'Identifier'
83
+ // ?? ANNOTATION_FIELDS.includes(right.property.name)
84
+ );
85
+ });
86
+ storyOverrides.replaceWith((exp) => {
87
+ const { left, right } = exp.node;
88
+ return j.assignmentExpression(
89
+ '=',
90
+ j.memberExpression(left.object.object, renameKey(left.property)),
91
+ right
92
+ );
93
+ });
94
+
95
+ // 4. Render the updated tree
96
+ return root.toSource({ quote: 'single' });
97
+ }