@storybook/codemod 7.0.0-alpha.8 → 7.0.0-beta.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +0 -36
- package/dist/index.d.ts +261 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/jest.config.js +7 -0
- package/package.json +32 -29
- package/src/index.js +77 -0
- package/src/lib/utils.test.js +9 -0
- package/{dist/esm/lib/utils.js → src/lib/utils.ts} +10 -11
- package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.input.js +44 -0
- package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.output.snapshot +68 -0
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.input.js +25 -0
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.output.snapshot +27 -0
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.input.js +25 -0
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.output.snapshot +28 -0
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.input.js +13 -0
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.output.snapshot +17 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/basic.input.js +20 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/basic.output.snapshot +18 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/component-id.input.js +9 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/component-id.output.snapshot +10 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/decorators.input.js +13 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/decorators.output.snapshot +12 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.input.js +23 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.output.snapshot +22 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/parameters.input.js +16 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/parameters.output.snapshot +17 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/story-function.input.js +19 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/story-function.output.snapshot +18 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.input.js +24 -0
- package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.output.snapshot +22 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/basic.input.js +18 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/basic.output.snapshot +40 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/component-id.input.js +6 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/component-id.output.snapshot +17 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/decorators.input.js +8 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/decorators.output.snapshot +18 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.input.js +19 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.output.snapshot +39 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/parameters.input.js +14 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/parameters.output.snapshot +23 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.input.js +3 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.output.snapshot +11 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/story-function.input.js +10 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/story-function.output.snapshot +18 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.input.js +18 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.output.snapshot +32 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.input.js +22 -0
- package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.output.snapshot +34 -0
- package/src/transforms/__testfixtures__/move-builtin-addons/default.input.js +2 -0
- package/src/transforms/__testfixtures__/move-builtin-addons/default.output.snapshot +8 -0
- package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.input.js +3 -0
- package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.output.snapshot +7 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/basic.input.js +18 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/basic.output.snapshot +45 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/collision.input.js +11 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/collision.output.snapshot +38 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/const.input.js +1 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/const.output.snapshot +13 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +9 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.output.snapshot +18 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/default.input.js +7 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/default.output.snapshot +17 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/digit.input.js +1 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.js +5 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.snapshot +9 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.input.js +11 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.output.snapshot +23 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +12 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.output.snapshot +23 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.input.js +14 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.output.snapshot +26 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/exports.input.js +2 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/exports.output.snapshot +16 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/module.input.js +12 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/module.output.snapshot +16 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/multi.input.js +14 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/multi.output.snapshot +39 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.input.js +8 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.output.snapshot +20 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.input.js +10 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.output.snapshot +23 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.input.js +11 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.output.snapshot +23 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +11 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.output.snapshot +29 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.input.js +14 -0
- package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.output.snapshot +32 -0
- package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +198 -0
- package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +198 -0
- package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.input.js +19 -0
- package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.output.snapshot +23 -0
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.input.js +3 -0
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.output.snapshot +7 -0
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.input.js +5 -0
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.output.snapshot +9 -0
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.input.js +8 -0
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.output.snapshot +12 -0
- package/src/transforms/__tests__/csf-2-to-3.test.ts +250 -0
- package/src/transforms/__tests__/transforms.tests.js +32 -0
- package/{dist/esm → src}/transforms/add-component-parameters.js +26 -21
- package/src/transforms/csf-2-to-3.ts +184 -0
- package/src/transforms/csf-hoist-story-annotations.js +97 -0
- package/src/transforms/csf-to-mdx.js +190 -0
- package/src/transforms/move-builtin-addons.js +32 -0
- package/src/transforms/storiesof-to-csf.js +277 -0
- package/{dist/esm → src}/transforms/update-addon-info.js +44 -24
- package/{dist/esm → src}/transforms/update-organisation-name.js +21 -24
- package/src/transforms/upgrade-hierarchy-separators.js +39 -0
- package/tsconfig.json +7 -0
- package/LICENSE +0 -21
- package/dist/cjs/index.js +0 -142
- package/dist/cjs/lib/utils.js +0 -45
- package/dist/cjs/transforms/add-component-parameters.js +0 -64
- package/dist/cjs/transforms/csf-2-to-3.js +0 -180
- package/dist/cjs/transforms/csf-hoist-story-annotations.js +0 -93
- package/dist/cjs/transforms/csf-to-mdx.js +0 -196
- package/dist/cjs/transforms/mdx-to-csf.js +0 -153
- package/dist/cjs/transforms/move-builtin-addons.js +0 -57
- package/dist/cjs/transforms/storiesof-to-csf.js +0 -300
- package/dist/cjs/transforms/update-addon-info.js +0 -101
- package/dist/cjs/transforms/update-organisation-name.js +0 -83
- package/dist/cjs/transforms/upgrade-hierarchy-separators.js +0 -42
- package/dist/esm/index.js +0 -99
- package/dist/esm/transforms/csf-2-to-3.js +0 -163
- package/dist/esm/transforms/csf-hoist-story-annotations.js +0 -86
- package/dist/esm/transforms/csf-to-mdx.js +0 -188
- package/dist/esm/transforms/mdx-to-csf.js +0 -139
- package/dist/esm/transforms/move-builtin-addons.js +0 -50
- package/dist/esm/transforms/storiesof-to-csf.js +0 -287
- package/dist/esm/transforms/upgrade-hierarchy-separators.js +0 -35
- package/dist/types/lib/utils.d.ts +0 -2
- 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
|
-
|
22
|
-
|
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
|
-
|
31
|
-
|
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
|
-
|
39
|
+
const parts = string.split(/\/|\.|\|/);
|
39
40
|
return parts[parts.length - 1];
|
40
41
|
}
|
41
42
|
|
42
43
|
function addComponentParameter(call) {
|
43
|
-
|
44
|
-
|
45
|
-
return j.callExpression(j.memberExpression(node, j.identifier('addParameters')), [
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
+
}
|