@storybook/codemod 7.0.0-alpha.5 → 7.0.0-alpha.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +31 -28
  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 +20 -23
  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
+ }