@storybook/codemod 7.0.0-alpha.6 → 7.0.0-alpha.60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. package/README.md +0 -36
  2. package/dist/index.d.ts +261 -0
  3. package/dist/index.js +1 -0
  4. package/dist/index.mjs +1 -0
  5. package/jest.config.js +7 -0
  6. package/package.json +32 -29
  7. package/src/index.js +77 -0
  8. package/src/lib/utils.test.js +9 -0
  9. package/{dist/esm/lib/utils.js → src/lib/utils.ts} +10 -11
  10. package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.input.js +44 -0
  11. package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.output.snapshot +68 -0
  12. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.input.js +25 -0
  13. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.output.snapshot +27 -0
  14. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.input.js +25 -0
  15. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.output.snapshot +28 -0
  16. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.input.js +13 -0
  17. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.output.snapshot +17 -0
  18. package/src/transforms/__testfixtures__/csf-to-mdx/basic.input.js +20 -0
  19. package/src/transforms/__testfixtures__/csf-to-mdx/basic.output.snapshot +18 -0
  20. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.input.js +9 -0
  21. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.output.snapshot +10 -0
  22. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.input.js +13 -0
  23. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.output.snapshot +12 -0
  24. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.input.js +23 -0
  25. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.output.snapshot +22 -0
  26. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.input.js +16 -0
  27. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.output.snapshot +17 -0
  28. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.input.js +19 -0
  29. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.output.snapshot +18 -0
  30. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.input.js +24 -0
  31. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.output.snapshot +22 -0
  32. package/src/transforms/__testfixtures__/mdx-to-csf/basic.input.js +18 -0
  33. package/src/transforms/__testfixtures__/mdx-to-csf/basic.output.snapshot +40 -0
  34. package/src/transforms/__testfixtures__/mdx-to-csf/component-id.input.js +6 -0
  35. package/src/transforms/__testfixtures__/mdx-to-csf/component-id.output.snapshot +17 -0
  36. package/src/transforms/__testfixtures__/mdx-to-csf/decorators.input.js +8 -0
  37. package/src/transforms/__testfixtures__/mdx-to-csf/decorators.output.snapshot +18 -0
  38. package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.input.js +19 -0
  39. package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.output.snapshot +39 -0
  40. package/src/transforms/__testfixtures__/mdx-to-csf/parameters.input.js +14 -0
  41. package/src/transforms/__testfixtures__/mdx-to-csf/parameters.output.snapshot +23 -0
  42. package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.input.js +3 -0
  43. package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.output.snapshot +11 -0
  44. package/src/transforms/__testfixtures__/mdx-to-csf/story-function.input.js +10 -0
  45. package/src/transforms/__testfixtures__/mdx-to-csf/story-function.output.snapshot +18 -0
  46. package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.input.js +18 -0
  47. package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.output.snapshot +32 -0
  48. package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.input.js +22 -0
  49. package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.output.snapshot +34 -0
  50. package/src/transforms/__testfixtures__/move-builtin-addons/default.input.js +2 -0
  51. package/src/transforms/__testfixtures__/move-builtin-addons/default.output.snapshot +8 -0
  52. package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.input.js +3 -0
  53. package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.output.snapshot +7 -0
  54. package/src/transforms/__testfixtures__/storiesof-to-csf/basic.input.js +18 -0
  55. package/src/transforms/__testfixtures__/storiesof-to-csf/basic.output.snapshot +45 -0
  56. package/src/transforms/__testfixtures__/storiesof-to-csf/collision.input.js +11 -0
  57. package/src/transforms/__testfixtures__/storiesof-to-csf/collision.output.snapshot +38 -0
  58. package/src/transforms/__testfixtures__/storiesof-to-csf/const.input.js +1 -0
  59. package/src/transforms/__testfixtures__/storiesof-to-csf/const.output.snapshot +13 -0
  60. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +9 -0
  61. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.output.snapshot +18 -0
  62. package/src/transforms/__testfixtures__/storiesof-to-csf/default.input.js +7 -0
  63. package/src/transforms/__testfixtures__/storiesof-to-csf/default.output.snapshot +17 -0
  64. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.input.js +1 -0
  65. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.js +5 -0
  66. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.snapshot +9 -0
  67. package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.input.js +11 -0
  68. package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.output.snapshot +23 -0
  69. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +12 -0
  70. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.output.snapshot +23 -0
  71. package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.input.js +14 -0
  72. package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.output.snapshot +26 -0
  73. package/src/transforms/__testfixtures__/storiesof-to-csf/exports.input.js +2 -0
  74. package/src/transforms/__testfixtures__/storiesof-to-csf/exports.output.snapshot +16 -0
  75. package/src/transforms/__testfixtures__/storiesof-to-csf/module.input.js +12 -0
  76. package/src/transforms/__testfixtures__/storiesof-to-csf/module.output.snapshot +16 -0
  77. package/src/transforms/__testfixtures__/storiesof-to-csf/multi.input.js +14 -0
  78. package/src/transforms/__testfixtures__/storiesof-to-csf/multi.output.snapshot +39 -0
  79. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.input.js +8 -0
  80. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.output.snapshot +20 -0
  81. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.input.js +10 -0
  82. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.output.snapshot +23 -0
  83. package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.input.js +11 -0
  84. package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.output.snapshot +23 -0
  85. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +11 -0
  86. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.output.snapshot +29 -0
  87. package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.input.js +14 -0
  88. package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.output.snapshot +32 -0
  89. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +198 -0
  90. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +198 -0
  91. package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.input.js +19 -0
  92. package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.output.snapshot +23 -0
  93. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.input.js +3 -0
  94. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.output.snapshot +7 -0
  95. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.input.js +5 -0
  96. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.output.snapshot +9 -0
  97. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.input.js +8 -0
  98. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.output.snapshot +12 -0
  99. package/src/transforms/__tests__/csf-2-to-3.test.ts +250 -0
  100. package/src/transforms/__tests__/transforms.tests.js +32 -0
  101. package/{dist/esm → src}/transforms/add-component-parameters.js +26 -21
  102. package/src/transforms/csf-2-to-3.ts +184 -0
  103. package/src/transforms/csf-hoist-story-annotations.js +97 -0
  104. package/src/transforms/csf-to-mdx.js +190 -0
  105. package/src/transforms/move-builtin-addons.js +32 -0
  106. package/src/transforms/storiesof-to-csf.js +277 -0
  107. package/{dist/esm → src}/transforms/update-addon-info.js +44 -24
  108. package/{dist/esm → src}/transforms/update-organisation-name.js +21 -24
  109. package/src/transforms/upgrade-hierarchy-separators.js +39 -0
  110. package/tsconfig.json +7 -0
  111. package/LICENSE +0 -21
  112. package/dist/cjs/index.js +0 -142
  113. package/dist/cjs/lib/utils.js +0 -45
  114. package/dist/cjs/transforms/add-component-parameters.js +0 -64
  115. package/dist/cjs/transforms/csf-2-to-3.js +0 -180
  116. package/dist/cjs/transforms/csf-hoist-story-annotations.js +0 -93
  117. package/dist/cjs/transforms/csf-to-mdx.js +0 -196
  118. package/dist/cjs/transforms/mdx-to-csf.js +0 -153
  119. package/dist/cjs/transforms/move-builtin-addons.js +0 -57
  120. package/dist/cjs/transforms/storiesof-to-csf.js +0 -300
  121. package/dist/cjs/transforms/update-addon-info.js +0 -101
  122. package/dist/cjs/transforms/update-organisation-name.js +0 -83
  123. package/dist/cjs/transforms/upgrade-hierarchy-separators.js +0 -42
  124. package/dist/esm/index.js +0 -99
  125. package/dist/esm/transforms/csf-2-to-3.js +0 -163
  126. package/dist/esm/transforms/csf-hoist-story-annotations.js +0 -86
  127. package/dist/esm/transforms/csf-to-mdx.js +0 -188
  128. package/dist/esm/transforms/mdx-to-csf.js +0 -139
  129. package/dist/esm/transforms/move-builtin-addons.js +0 -50
  130. package/dist/esm/transforms/storiesof-to-csf.js +0 -287
  131. package/dist/esm/transforms/upgrade-hierarchy-separators.js +0 -35
  132. package/dist/types/lib/utils.d.ts +0 -2
  133. package/dist/types/transforms/csf-2-to-3.d.ts +0 -6
@@ -0,0 +1,250 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { dedent } from 'ts-dedent';
3
+ import _transform from '../csf-2-to-3';
4
+
5
+ expect.addSnapshotSerializer({
6
+ print: (val: any) => val,
7
+ test: () => true,
8
+ });
9
+
10
+ const jsTransform = (source: string) => _transform({ source }, null, {}).trim();
11
+ const tsTransform = (source: string) => _transform({ source }, null, { parser: 'tsx' }).trim();
12
+
13
+ describe('csf-2-to-3', () => {
14
+ describe('javascript', () => {
15
+ it('should replace non-simple function exports with objects', () => {
16
+ expect(
17
+ jsTransform(dedent`
18
+ export default { title: 'Cat' };
19
+ export const A = () => <Cat />;
20
+ export const B = (args) => <Button {...args} />;
21
+ `)
22
+ ).toMatchInlineSnapshot(`
23
+ export default {
24
+ title: 'Cat',
25
+ };
26
+ export const A = () => <Cat />;
27
+ export const B = {
28
+ render: (args) => <Button {...args} />,
29
+ };
30
+ `);
31
+ });
32
+
33
+ it('should move annotations into story objects', () => {
34
+ expect(
35
+ jsTransform(dedent`
36
+ export default { title: 'Cat' };
37
+ export const A = () => <Cat />;
38
+ A.storyName = 'foo';
39
+ A.parameters = { bar: 2 };
40
+ A.play = () => {};
41
+ `)
42
+ ).toMatchInlineSnapshot(`
43
+ export default {
44
+ title: 'Cat',
45
+ };
46
+ export const A = {
47
+ render: () => <Cat />,
48
+ name: 'foo',
49
+ parameters: {
50
+ bar: 2,
51
+ },
52
+ play: () => {},
53
+ };
54
+ `);
55
+ });
56
+
57
+ it('should ignore non-story exports, statements', () => {
58
+ expect(
59
+ jsTransform(dedent`
60
+ export default { title: 'components/Fruit', includeStories: ['A'] };
61
+ export const A = (args) => <Apple {...args} />;
62
+ export const B = (args) => <Banana {...args} />;
63
+ const C = (args) => <Cherry {...args} />;
64
+ `)
65
+ ).toMatchInlineSnapshot(`
66
+ export default {
67
+ title: 'components/Fruit',
68
+ includeStories: ['A'],
69
+ };
70
+ export const A = {
71
+ render: (args) => <Apple {...args} />,
72
+ };
73
+ export const B = (args) => <Banana {...args} />;
74
+ const C = (args) => <Cherry {...args} />;
75
+ `);
76
+ });
77
+
78
+ it('should do nothing when there is no meta', () => {
79
+ expect(
80
+ jsTransform(dedent`
81
+ export const A = () => <Apple />;
82
+ export const B = (args) => <Banana {...args} />;
83
+ `)
84
+ ).toMatchInlineSnapshot(`
85
+ export const A = () => <Apple />;
86
+ export const B = (args) => <Banana {...args} />;
87
+ `);
88
+ });
89
+
90
+ it('should remove implicit global render function (react)', () => {
91
+ expect(
92
+ jsTransform(dedent`
93
+ export default { title: 'Cat', component: Cat };
94
+ export const A = (args) => <Cat {...args} />;
95
+ export const B = (args) => <Banana {...args} />;
96
+ `)
97
+ ).toMatchInlineSnapshot(`
98
+ export default {
99
+ title: 'Cat',
100
+ component: Cat,
101
+ };
102
+ export const A = {};
103
+ export const B = {
104
+ render: (args) => <Banana {...args} />,
105
+ };
106
+ `);
107
+ });
108
+
109
+ it('should ignore object exports', () => {
110
+ expect(
111
+ jsTransform(dedent`
112
+ export default { title: 'Cat', component: Cat };
113
+ export const A = {
114
+ render: (args) => <Cat {...args} />
115
+ };
116
+ `)
117
+ ).toMatchInlineSnapshot(`
118
+ export default {
119
+ title: 'Cat',
120
+ component: Cat,
121
+ };
122
+ export const A = {
123
+ render: (args) => <Cat {...args} />,
124
+ };
125
+ `);
126
+ });
127
+
128
+ it('should hoist template.bind (if there is only one)', () => {
129
+ expect(
130
+ jsTransform(dedent`
131
+ export default { title: 'Cat' };
132
+ const Template = (args) => <Cat {...args} />;
133
+ export const A = Template.bind({});
134
+ A.args = { isPrimary: false };
135
+ `)
136
+ ).toMatchInlineSnapshot(`
137
+ export default {
138
+ title: 'Cat',
139
+ };
140
+ export const A = {
141
+ render: (args) => <Cat {...args} />,
142
+ args: {
143
+ isPrimary: false,
144
+ },
145
+ };
146
+ `);
147
+ });
148
+
149
+ it('should remove implicit global render for template.bind', () => {
150
+ expect(
151
+ jsTransform(dedent`
152
+ export default { title: 'Cat', component: Cat };
153
+ const Template = (args) => <Cat {...args} />;
154
+ export const A = Template.bind({});
155
+ A.args = { isPrimary: false };
156
+ const Template2 = (args) => <Banana {...args} />;
157
+ export const B = Template2.bind({});
158
+ B.args = { isPrimary: true };
159
+ `)
160
+ ).toMatchInlineSnapshot(`
161
+ export default {
162
+ title: 'Cat',
163
+ component: Cat,
164
+ };
165
+ export const A = {
166
+ args: {
167
+ isPrimary: false,
168
+ },
169
+ };
170
+ export const B = {
171
+ render: (args) => <Banana {...args} />,
172
+ args: {
173
+ isPrimary: true,
174
+ },
175
+ };
176
+ `);
177
+ });
178
+
179
+ it('should ignore no-arg stories without annotations', () => {
180
+ expect(
181
+ jsTransform(dedent`
182
+ export default { title: 'Cat', component: Cat };
183
+ export const A = (args) => <Cat {...args} />;
184
+ export const B = () => <Cat name="frisky" />;
185
+ export const C = () => <Cat name="fluffy" />;
186
+ C.parameters = { foo: 2 };
187
+ `)
188
+ ).toMatchInlineSnapshot(`
189
+ export default {
190
+ title: 'Cat',
191
+ component: Cat,
192
+ };
193
+ export const A = {};
194
+ export const B = () => <Cat name="frisky" />;
195
+ export const C = {
196
+ render: () => <Cat name="fluffy" />,
197
+ parameters: {
198
+ foo: 2,
199
+ },
200
+ };
201
+ `);
202
+ });
203
+
204
+ it('should work for v1-style annotations', () => {
205
+ expect(
206
+ jsTransform(dedent`
207
+ export default { title: 'Cat' };
208
+ export const A = (args) => <Cat {...args} />;
209
+ A.story = {
210
+ parameters: { foo: 2 }
211
+ };
212
+ `)
213
+ ).toMatchInlineSnapshot(`
214
+ export default {
215
+ title: 'Cat',
216
+ };
217
+ export const A = {
218
+ render: (args) => <Cat {...args} />,
219
+ parameters: {
220
+ foo: 2,
221
+ },
222
+ };
223
+ `);
224
+ });
225
+ });
226
+
227
+ describe('typescript', () => {
228
+ it('should replace function exports with objects', () => {
229
+ expect(
230
+ tsTransform(dedent`
231
+ export default { title: 'Cat' } as Meta<CatProps>;
232
+ export const A: Story<CatProps> = (args) => <Cat {...args} />;
233
+ export const B: any = (args) => <Button {...args} />;
234
+ export const C: Story<CatProps> = () => <Cat />;
235
+ `)
236
+ ).toMatchInlineSnapshot(`
237
+ export default {
238
+ title: 'Cat',
239
+ } as Meta<CatProps>;
240
+ export const A: Story<CatProps> = {
241
+ render: (args) => <Cat {...args} />,
242
+ };
243
+ export const B: any = {
244
+ render: (args) => <Button {...args} />,
245
+ };
246
+ export const C: Story<CatProps> = () => <Cat />;
247
+ `);
248
+ });
249
+ });
250
+ });
@@ -0,0 +1,32 @@
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
+ // FIXME: delete after https://github.com/storybookjs/storybook/issues/19497
13
+ if (transformName === 'mdx-to-csf') return;
14
+
15
+ const transformFixturesDir = path.join(fixturesDir, transformName);
16
+ describe(`${transformName}`, () => {
17
+ fs.readdirSync(transformFixturesDir)
18
+ .filter((fileName) => inputRegExp.test(fileName))
19
+ .forEach((fileName) => {
20
+ const inputPath = path.join(transformFixturesDir, fileName);
21
+ it(`transforms correctly using "${fileName}" data`, () =>
22
+ expect(
23
+ applyTransform(
24
+ // eslint-disable-next-line global-require,import/no-dynamic-require
25
+ require(path.join(__dirname, '..', transformName)),
26
+ null,
27
+ { path: inputPath, source: fs.readFileSync(inputPath, 'utf8') }
28
+ )
29
+ ).toMatchSpecificSnapshot(inputPath.replace(inputRegExp, '.output.snapshot')));
30
+ });
31
+ });
32
+ });
@@ -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
- var storiesOfCalls = 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,184 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ import prettier from 'prettier';
3
+ import * as t from '@babel/types';
4
+ import type { CsfFile } from '@storybook/csf-tools';
5
+ import { formatCsf, loadCsf } from '@storybook/csf-tools';
6
+ import { jscodeshiftToPrettierParser } from '../lib/utils';
7
+
8
+ const logger = console;
9
+
10
+ const renameAnnotation = (annotation: string) => {
11
+ return annotation === 'storyName' ? 'name' : annotation;
12
+ };
13
+
14
+ const getTemplateBindVariable = (init: t.Expression) =>
15
+ t.isCallExpression(init) &&
16
+ t.isMemberExpression(init.callee) &&
17
+ t.isIdentifier(init.callee.object) &&
18
+ t.isIdentifier(init.callee.property) &&
19
+ init.callee.property.name === 'bind' &&
20
+ (init.arguments.length === 0 ||
21
+ (init.arguments.length === 1 &&
22
+ t.isObjectExpression(init.arguments[0]) &&
23
+ init.arguments[0].properties.length === 0))
24
+ ? init.callee.object.name
25
+ : null;
26
+
27
+ // export const A = ...
28
+ // A.parameters = { ... }; <===
29
+ const isStoryAnnotation = (stmt: t.Statement, objectExports: Record<string, any>) =>
30
+ t.isExpressionStatement(stmt) &&
31
+ t.isAssignmentExpression(stmt.expression) &&
32
+ t.isMemberExpression(stmt.expression.left) &&
33
+ t.isIdentifier(stmt.expression.left.object) &&
34
+ objectExports[stmt.expression.left.object.name];
35
+
36
+ const isTemplateDeclaration = (stmt: t.Statement, templates: Record<string, any>) =>
37
+ t.isVariableDeclaration(stmt) &&
38
+ stmt.declarations.length === 1 &&
39
+ t.isIdentifier(stmt.declarations[0].id) &&
40
+ templates[stmt.declarations[0].id.name];
41
+
42
+ const getNewExport = (stmt: t.Statement, objectExports: Record<string, any>) => {
43
+ if (
44
+ t.isExportNamedDeclaration(stmt) &&
45
+ t.isVariableDeclaration(stmt.declaration) &&
46
+ stmt.declaration.declarations.length === 1
47
+ ) {
48
+ const decl = stmt.declaration.declarations[0];
49
+ if (t.isVariableDeclarator(decl) && t.isIdentifier(decl.id)) {
50
+ return objectExports[decl.id.name];
51
+ }
52
+ }
53
+ return null;
54
+ };
55
+
56
+ // Remove render function when it matches the global render function in react
57
+ // export default { component: Cat };
58
+ // export const A = (args) => <Cat {...args} />;
59
+ const isReactGlobalRenderFn = (csf: CsfFile, storyFn: t.Expression) => {
60
+ if (
61
+ csf._meta?.component &&
62
+ t.isArrowFunctionExpression(storyFn) &&
63
+ storyFn.params.length === 1 &&
64
+ t.isJSXElement(storyFn.body)
65
+ ) {
66
+ const { openingElement } = storyFn.body;
67
+ if (
68
+ openingElement.selfClosing &&
69
+ t.isJSXIdentifier(openingElement.name) &&
70
+ openingElement.attributes.length === 1
71
+ ) {
72
+ const attr = openingElement.attributes[0];
73
+ const param = storyFn.params[0];
74
+ if (
75
+ t.isJSXSpreadAttribute(attr) &&
76
+ t.isIdentifier(attr.argument) &&
77
+ t.isIdentifier(param) &&
78
+ param.name === attr.argument.name &&
79
+ csf._meta.component === openingElement.name.name
80
+ ) {
81
+ return true;
82
+ }
83
+ }
84
+ }
85
+ return false;
86
+ };
87
+
88
+ // A simple CSF story is a no-arg story without any extra annotations (params, args, etc.)
89
+ const isSimpleCSFStory = (init: t.Expression, annotations: t.ObjectProperty[]) =>
90
+ annotations.length === 0 && t.isArrowFunctionExpression(init) && init.params.length === 0;
91
+
92
+ function transform({ source }: { source: string }, api: any, options: { parser?: string }) {
93
+ const makeTitle = (userTitle?: string) => {
94
+ return userTitle || 'FIXME';
95
+ };
96
+ const csf = loadCsf(source, { makeTitle });
97
+
98
+ try {
99
+ csf.parse();
100
+ } catch (err) {
101
+ logger.log(`Error ${err}, skipping`);
102
+ return source;
103
+ }
104
+
105
+ const objectExports: Record<string, t.Statement> = {};
106
+ Object.entries(csf._storyExports).forEach(([key, decl]) => {
107
+ const annotations = Object.entries(csf._storyAnnotations[key]).map(([annotation, val]) => {
108
+ return t.objectProperty(t.identifier(renameAnnotation(annotation)), val as t.Expression);
109
+ });
110
+
111
+ if (t.isVariableDeclarator(decl)) {
112
+ const { init, id } = decl;
113
+ // only replace arrow function expressions && template
114
+ // ignore no-arg stories without annotations
115
+ const template = getTemplateBindVariable(init);
116
+ if (
117
+ (!t.isArrowFunctionExpression(init) && !template) ||
118
+ isSimpleCSFStory(init, annotations)
119
+ ) {
120
+ return;
121
+ }
122
+
123
+ // Remove the render function when we can hoist the template
124
+ // const Template = (args) => <Cat {...args} />;
125
+ // export const A = Template.bind({});
126
+ let storyFn = template && csf._templates[template];
127
+ if (!storyFn) {
128
+ storyFn = init;
129
+ }
130
+
131
+ const keyId = t.identifier(key);
132
+ // @ts-expect-error (Converted from ts-ignore)
133
+ const { typeAnnotation } = id;
134
+ if (typeAnnotation) {
135
+ keyId.typeAnnotation = typeAnnotation;
136
+ }
137
+
138
+ const renderAnnotation = isReactGlobalRenderFn(csf, storyFn)
139
+ ? []
140
+ : [t.objectProperty(t.identifier('render'), storyFn)];
141
+
142
+ objectExports[key] = t.exportNamedDeclaration(
143
+ t.variableDeclaration('const', [
144
+ t.variableDeclarator(keyId, t.objectExpression([...renderAnnotation, ...annotations])),
145
+ ])
146
+ );
147
+ }
148
+ });
149
+
150
+ const updatedBody = csf._ast.program.body.reduce((acc, stmt) => {
151
+ // remove story annotations & template declarations
152
+ if (isStoryAnnotation(stmt, objectExports) || isTemplateDeclaration(stmt, csf._templates)) {
153
+ return acc;
154
+ }
155
+
156
+ // replace story exports with new object exports
157
+ const newExport = getNewExport(stmt, objectExports);
158
+ if (newExport) {
159
+ acc.push(newExport);
160
+ return acc;
161
+ }
162
+
163
+ // include unknown statements
164
+ acc.push(stmt);
165
+ return acc;
166
+ }, []);
167
+ csf._ast.program.body = updatedBody;
168
+ const output = formatCsf(csf);
169
+
170
+ const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
171
+ printWidth: 100,
172
+ tabWidth: 2,
173
+ bracketSpacing: true,
174
+ trailingComma: 'es5',
175
+ singleQuote: true,
176
+ };
177
+
178
+ return prettier.format(output, {
179
+ ...prettierConfig,
180
+ parser: jscodeshiftToPrettierParser(options?.parser),
181
+ });
182
+ }
183
+
184
+ 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
+ }