@storybook/codemod 7.3.2 → 7.4.0-alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. package/package.json +11 -4
  2. package/project.json +0 -6
  3. package/src/index.ts +0 -103
  4. package/src/lib/utils.test.js +0 -9
  5. package/src/lib/utils.ts +0 -29
  6. package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.input.js +0 -44
  7. package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.output.snapshot +0 -68
  8. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.input.js +0 -25
  9. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.output.snapshot +0 -27
  10. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.input.js +0 -25
  11. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.output.snapshot +0 -28
  12. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.input.js +0 -13
  13. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.output.snapshot +0 -17
  14. package/src/transforms/__testfixtures__/mdx-to-csf/basic.input.mdx +0 -18
  15. package/src/transforms/__testfixtures__/mdx-to-csf/basic.output.snapshot +0 -40
  16. package/src/transforms/__testfixtures__/mdx-to-csf/component-id.input.mdx +0 -6
  17. package/src/transforms/__testfixtures__/mdx-to-csf/component-id.output.snapshot +0 -17
  18. package/src/transforms/__testfixtures__/mdx-to-csf/decorators.input.mdx +0 -8
  19. package/src/transforms/__testfixtures__/mdx-to-csf/decorators.output.snapshot +0 -18
  20. package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.input.mdx +0 -19
  21. package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.output.snapshot +0 -39
  22. package/src/transforms/__testfixtures__/mdx-to-csf/parameters.input.mdx +0 -14
  23. package/src/transforms/__testfixtures__/mdx-to-csf/parameters.output.snapshot +0 -23
  24. package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.input.mdx +0 -3
  25. package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.output.snapshot +0 -11
  26. package/src/transforms/__testfixtures__/mdx-to-csf/story-function.input.mdx +0 -10
  27. package/src/transforms/__testfixtures__/mdx-to-csf/story-function.output.snapshot +0 -18
  28. package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.input.mdx +0 -18
  29. package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.output.snapshot +0 -32
  30. package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.input.mdx +0 -22
  31. package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.output.snapshot +0 -34
  32. package/src/transforms/__testfixtures__/move-builtin-addons/default.input.js +0 -2
  33. package/src/transforms/__testfixtures__/move-builtin-addons/default.output.snapshot +0 -8
  34. package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.input.js +0 -3
  35. package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.output.snapshot +0 -7
  36. package/src/transforms/__testfixtures__/storiesof-to-csf/basic.input.js +0 -18
  37. package/src/transforms/__testfixtures__/storiesof-to-csf/basic.output.snapshot +0 -45
  38. package/src/transforms/__testfixtures__/storiesof-to-csf/collision.input.js +0 -11
  39. package/src/transforms/__testfixtures__/storiesof-to-csf/collision.output.snapshot +0 -38
  40. package/src/transforms/__testfixtures__/storiesof-to-csf/const.input.js +0 -1
  41. package/src/transforms/__testfixtures__/storiesof-to-csf/const.output.snapshot +0 -13
  42. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +0 -9
  43. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.output.snapshot +0 -18
  44. package/src/transforms/__testfixtures__/storiesof-to-csf/default.input.js +0 -7
  45. package/src/transforms/__testfixtures__/storiesof-to-csf/default.output.snapshot +0 -17
  46. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.input.js +0 -1
  47. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.js +0 -5
  48. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.snapshot +0 -9
  49. package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.input.js +0 -11
  50. package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.output.snapshot +0 -23
  51. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +0 -12
  52. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.output.snapshot +0 -23
  53. package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.input.js +0 -14
  54. package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.output.snapshot +0 -26
  55. package/src/transforms/__testfixtures__/storiesof-to-csf/exports.input.js +0 -2
  56. package/src/transforms/__testfixtures__/storiesof-to-csf/exports.output.snapshot +0 -16
  57. package/src/transforms/__testfixtures__/storiesof-to-csf/module.input.js +0 -12
  58. package/src/transforms/__testfixtures__/storiesof-to-csf/module.output.snapshot +0 -16
  59. package/src/transforms/__testfixtures__/storiesof-to-csf/multi.input.js +0 -14
  60. package/src/transforms/__testfixtures__/storiesof-to-csf/multi.output.snapshot +0 -39
  61. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.input.js +0 -8
  62. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.output.snapshot +0 -20
  63. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.input.js +0 -10
  64. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.output.snapshot +0 -23
  65. package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.input.js +0 -11
  66. package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.output.snapshot +0 -23
  67. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +0 -11
  68. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.output.snapshot +0 -29
  69. package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.input.js +0 -14
  70. package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.output.snapshot +0 -32
  71. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +0 -184
  72. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +0 -184
  73. package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.input.js +0 -19
  74. package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.output.snapshot +0 -23
  75. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.input.js +0 -3
  76. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.output.snapshot +0 -7
  77. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.input.js +0 -5
  78. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.output.snapshot +0 -9
  79. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.input.js +0 -8
  80. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.output.snapshot +0 -12
  81. package/src/transforms/__tests__/csf-2-to-3.test.ts +0 -439
  82. package/src/transforms/__tests__/mdx-to-csf.test.ts +0 -628
  83. package/src/transforms/__tests__/transforms.tests.js +0 -32
  84. package/src/transforms/__tests__/upgrade-deprecated-types.test.ts +0 -170
  85. package/src/transforms/add-component-parameters.js +0 -62
  86. package/src/transforms/csf-2-to-3.ts +0 -335
  87. package/src/transforms/csf-hoist-story-annotations.js +0 -97
  88. package/src/transforms/mdx-to-csf.ts +0 -340
  89. package/src/transforms/move-builtin-addons.js +0 -32
  90. package/src/transforms/storiesof-to-csf.js +0 -277
  91. package/src/transforms/update-addon-info.js +0 -114
  92. package/src/transforms/update-organisation-name.js +0 -71
  93. package/src/transforms/upgrade-deprecated-types.ts +0 -142
  94. package/src/transforms/upgrade-hierarchy-separators.js +0 -39
  95. package/tsconfig.json +0 -10
@@ -1,340 +0,0 @@
1
- /* eslint-disable no-param-reassign,@typescript-eslint/no-shadow */
2
- import type { FileInfo } from 'jscodeshift';
3
- import { babelParse, babelParseExpression } from '@storybook/csf-tools';
4
- import { remark } from 'remark';
5
- import type { Root } from 'remark-mdx';
6
- import remarkMdx from 'remark-mdx';
7
- import { SKIP, visit } from 'unist-util-visit';
8
- import { is } from 'unist-util-is';
9
- import type {
10
- MdxJsxAttribute,
11
- MdxJsxExpressionAttribute,
12
- MdxJsxFlowElement,
13
- MdxJsxTextElement,
14
- } from 'mdast-util-mdx-jsx';
15
- import type { MdxjsEsm } from 'mdast-util-mdxjs-esm';
16
- import * as t from '@babel/types';
17
- import type { BabelFile } from '@babel/core';
18
- import * as babel from '@babel/core';
19
- import * as recast from 'recast';
20
- import * as path from 'node:path';
21
- import prettier from 'prettier';
22
- import * as fs from 'node:fs';
23
- import camelCase from 'lodash/camelCase';
24
- import type { MdxFlowExpression } from 'mdast-util-mdx-expression';
25
-
26
- const mdxProcessor = remark().use(remarkMdx) as ReturnType<typeof remark>;
27
-
28
- export default function jscodeshift(info: FileInfo) {
29
- const parsed = path.parse(info.path);
30
-
31
- let baseName = path.join(
32
- parsed.dir,
33
- parsed.name.replace('.mdx', '').replace('.stories', '').replace('.story', '')
34
- );
35
-
36
- // make sure the new csf file we are going to create exists
37
- while (fs.existsSync(`${baseName}.stories.js`)) {
38
- baseName += '_';
39
- }
40
-
41
- const result = transform(info.source, path.basename(baseName));
42
-
43
- const [mdx, csf] = result;
44
-
45
- if (csf != null) {
46
- fs.writeFileSync(`${baseName}.stories.js`, csf);
47
- }
48
-
49
- return mdx;
50
- }
51
-
52
- export function transform(source: string, baseName: string): [mdx: string, csf: string] {
53
- const root = mdxProcessor.parse(source);
54
- const storyNamespaceName = nameToValidExport(`${baseName}Stories`);
55
-
56
- const metaAttributes: Array<MdxJsxAttribute | MdxJsxExpressionAttribute> = [];
57
- const storiesMap = new Map<
58
- string,
59
- | {
60
- type: 'value';
61
- attributes: Array<MdxJsxAttribute | MdxJsxExpressionAttribute>;
62
- children: (MdxJsxFlowElement | MdxJsxTextElement)['children'];
63
- }
64
- | {
65
- type: 'reference';
66
- }
67
- | {
68
- type: 'id';
69
- }
70
- >();
71
-
72
- // rewrite addon docs import
73
- visit(root, ['mdxjsEsm'], (node: MdxjsEsm) => {
74
- node.value = node.value
75
- .replaceAll('@storybook/addon-docs/blocks', '@storybook/blocks')
76
- .replaceAll('@storybook/addon-docs', '@storybook/blocks');
77
- });
78
-
79
- const file = getEsmAst(root);
80
-
81
- visit(
82
- root,
83
- ['mdxJsxFlowElement', 'mdxJsxTextElement'],
84
- (node: MdxJsxFlowElement | MdxJsxTextElement, index, parent) => {
85
- if (is(node, { name: 'Meta' })) {
86
- metaAttributes.push(...node.attributes);
87
- node.attributes = [
88
- {
89
- type: 'mdxJsxAttribute',
90
- name: 'of',
91
- value: {
92
- type: 'mdxJsxAttributeValueExpression',
93
- value: storyNamespaceName,
94
- },
95
- },
96
- ];
97
- }
98
- if (is(node, { name: 'Story' })) {
99
- const nameAttribute = node.attributes.find(
100
- (it) => it.type === 'mdxJsxAttribute' && it.name === 'name'
101
- );
102
- const idAttribute = node.attributes.find(
103
- (it) => it.type === 'mdxJsxAttribute' && it.name === 'id'
104
- );
105
- const storyAttribute = node.attributes.find(
106
- (it) => it.type === 'mdxJsxAttribute' && it.name === 'story'
107
- );
108
- if (typeof nameAttribute?.value === 'string') {
109
- let name = nameToValidExport(nameAttribute.value);
110
- while (variableNameExists(name)) name += '_';
111
-
112
- storiesMap.set(name, {
113
- type: 'value',
114
- attributes: node.attributes,
115
- children: node.children,
116
- });
117
- node.attributes = [
118
- {
119
- type: 'mdxJsxAttribute',
120
- name: 'of',
121
- value: {
122
- type: 'mdxJsxAttributeValueExpression',
123
- value: `${storyNamespaceName}.${name}`,
124
- },
125
- },
126
- ];
127
- node.children = [];
128
- } else if (idAttribute?.value) {
129
- // e.g. <Story id="button--primary" />
130
- // should be migrated manually as it is very hard to find out where the definition of such a string id is located
131
- const nodeString = mdxProcessor.stringify({ type: 'root', children: [node] }).trim();
132
- const newNode: MdxFlowExpression = {
133
- type: 'mdxFlowExpression',
134
- value: `/* ${nodeString} is deprecated, please migrate it to <Story of={referenceToStory} /> see: https://storybook.js.org/migration-guides/7.0 */`,
135
- };
136
- storiesMap.set(idAttribute.value as string, { type: 'id' });
137
- parent.children.splice(index, 0, newNode);
138
- // current index is the new comment, and index + 1 is current node
139
- // SKIP traversing current node, and continue with the node after that
140
- return [SKIP, index + 2];
141
- } else if (
142
- storyAttribute?.type === 'mdxJsxAttribute' &&
143
- typeof storyAttribute.value === 'object' &&
144
- storyAttribute.value.type === 'mdxJsxAttributeValueExpression'
145
- ) {
146
- // e.g. <Story story={Primary} />
147
-
148
- const name = storyAttribute.value.value;
149
- node.attributes = [
150
- {
151
- type: 'mdxJsxAttribute',
152
- name: 'of',
153
- value: {
154
- type: 'mdxJsxAttributeValueExpression',
155
- value: `${storyNamespaceName}.${name}`,
156
- },
157
- },
158
- ];
159
- node.children = [];
160
-
161
- storiesMap.set(name, { type: 'reference' });
162
- } else {
163
- parent.children.splice(index, 1);
164
- // Do not traverse `node`, continue at the node *now* at `index`.
165
- return [SKIP, index];
166
- }
167
- }
168
- return undefined;
169
- }
170
- );
171
-
172
- const metaProperties = metaAttributes.flatMap((attribute) => {
173
- if (attribute.type === 'mdxJsxAttribute') {
174
- if (typeof attribute.value === 'string') {
175
- return [t.objectProperty(t.identifier(attribute.name), t.stringLiteral(attribute.value))];
176
- }
177
- return [
178
- t.objectProperty(
179
- t.identifier(attribute.name),
180
- babelParseExpression(attribute.value.value) as any as t.Expression
181
- ),
182
- ];
183
- }
184
- return [];
185
- });
186
-
187
- file.path.traverse({
188
- // remove mdx imports from csf
189
- ImportDeclaration(path) {
190
- if (path.node.source.value === '@storybook/blocks') {
191
- path.remove();
192
- }
193
- },
194
- // remove exports from csf file
195
- ExportNamedDeclaration(path) {
196
- path.replaceWith(path.node.declaration);
197
- },
198
- });
199
-
200
- if (storiesMap.size === 0 && metaAttributes.length === 0) {
201
- // A CSF file must have at least one story, so skip migrating if this is the case.
202
- return [mdxProcessor.stringify(root), null];
203
- }
204
-
205
- addStoriesImport(root, baseName, storyNamespaceName);
206
-
207
- const newStatements: t.Statement[] = [
208
- t.exportDefaultDeclaration(t.objectExpression(metaProperties)),
209
- ];
210
-
211
- function mapChildrenToRender(children: (MdxJsxFlowElement | MdxJsxTextElement)['children']) {
212
- const child = children[0];
213
-
214
- if (!child) return undefined;
215
-
216
- if (child.type === 'text') {
217
- return t.arrowFunctionExpression([], t.stringLiteral(child.value));
218
- }
219
- if (child.type === 'mdxFlowExpression' || child.type === 'mdxTextExpression') {
220
- const expression = babelParseExpression(child.value) as any as t.Expression;
221
-
222
- // Recreating those lines: https://github.com/storybookjs/mdx1-csf/blob/f408fc97e9a63097ca1ee577df9315a3cccca975/src/sb-mdx-plugin.ts#L185-L198
223
- const BIND_REGEX = /\.bind\(.*\)/;
224
- if (BIND_REGEX.test(child.value)) {
225
- return expression;
226
- }
227
- if (t.isIdentifier(expression)) {
228
- return expression;
229
- }
230
- if (t.isArrowFunctionExpression(expression)) {
231
- return expression;
232
- }
233
- return t.arrowFunctionExpression([], expression);
234
- }
235
-
236
- const expression = babelParseExpression(
237
- mdxProcessor.stringify({ type: 'root', children: [child] })
238
- ) as any as t.Expression;
239
- return t.arrowFunctionExpression([], expression);
240
- }
241
-
242
- function variableNameExists(name: string) {
243
- let found = false;
244
- file.path.traverse({
245
- VariableDeclarator: (path) => {
246
- const lVal = path.node.id;
247
- if (t.isIdentifier(lVal) && lVal.name === name) found = true;
248
- },
249
- });
250
- return found;
251
- }
252
-
253
- newStatements.push(
254
- ...[...storiesMap].flatMap(([key, value]) => {
255
- if (value.type === 'id') return [];
256
- if (value.type === 'reference') {
257
- return [
258
- t.exportNamedDeclaration(null, [t.exportSpecifier(t.identifier(key), t.identifier(key))]),
259
- ];
260
- }
261
- const renderProperty = mapChildrenToRender(value.children);
262
- const newObject = t.objectExpression([
263
- ...(renderProperty
264
- ? [t.objectProperty(t.identifier('render'), mapChildrenToRender(value.children))]
265
- : []),
266
- ...value.attributes.flatMap((attribute) => {
267
- if (attribute.type === 'mdxJsxAttribute') {
268
- if (typeof attribute.value === 'string') {
269
- return [
270
- t.objectProperty(t.identifier(attribute.name), t.stringLiteral(attribute.value)),
271
- ];
272
- }
273
- return [
274
- t.objectProperty(
275
- t.identifier(attribute.name),
276
- babelParseExpression(attribute.value.value) as any as t.Expression
277
- ),
278
- ];
279
- }
280
- return [];
281
- }),
282
- ]);
283
-
284
- return [
285
- t.exportNamedDeclaration(
286
- t.variableDeclaration('const', [t.variableDeclarator(t.identifier(key), newObject)])
287
- ),
288
- ];
289
- })
290
- );
291
-
292
- file.path.node.body = [...file.path.node.body, ...newStatements];
293
-
294
- const newMdx = mdxProcessor.stringify(root);
295
- let output = recast.print(file.path.node).code;
296
-
297
- const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
298
- printWidth: 100,
299
- tabWidth: 2,
300
- bracketSpacing: true,
301
- trailingComma: 'es5',
302
- singleQuote: true,
303
- };
304
-
305
- output = prettier.format(output, { ...prettierConfig, filepath: `file.jsx` });
306
-
307
- return [newMdx, output];
308
- }
309
-
310
- function getEsmAst(root: Root) {
311
- const esm: string[] = [];
312
- visit(root, ['mdxjsEsm'], (node: MdxjsEsm) => {
313
- esm.push(node.value);
314
- });
315
- const esmSource = `${esm.join('\n\n')}`;
316
-
317
- // @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
318
- const file: BabelFile = new babel.File(
319
- { filename: 'info.path' },
320
- { code: esmSource, ast: babelParse(esmSource) }
321
- );
322
- return file;
323
- }
324
-
325
- function addStoriesImport(root: Root, baseName: string, storyNamespaceName: string): void {
326
- let found = false;
327
-
328
- visit(root, ['mdxjsEsm'], (node: MdxjsEsm) => {
329
- if (!found) {
330
- node.value += `\nimport * as ${storyNamespaceName} from './${baseName}.stories';`;
331
- found = true;
332
- }
333
- });
334
- }
335
-
336
- export function nameToValidExport(name: string) {
337
- const [first, ...rest] = Array.from(camelCase(name));
338
-
339
- return `${first.match(/[a-zA-Z_$]/) ? first.toUpperCase() : `$${first}`}${rest.join('')}`;
340
- }
@@ -1,32 +0,0 @@
1
- export default function transformer(file, api) {
2
- const j = api.jscodeshift;
3
-
4
- const createImportDeclaration = (specifiers, source) =>
5
- j.importDeclaration(
6
- specifiers.map((s) => j.importSpecifier(j.identifier(s))),
7
- j.literal(source)
8
- );
9
-
10
- const deprecates = {
11
- action: [['action'], '@storybook/addon-actions'],
12
- linkTo: [['linkTo'], '@storybook/addon-links'],
13
- };
14
-
15
- const transform = j(file.source)
16
- .find(j.ImportDeclaration)
17
- .filter((i) => i.value.source.value === '@storybook/react')
18
- .forEach((i) => {
19
- const importStatement = i.value;
20
- importStatement.specifiers = importStatement.specifiers.filter((specifier) => {
21
- const item = deprecates[specifier.local.name];
22
- if (item) {
23
- const [specifiers, moduleName] = item;
24
- i.insertAfter(createImportDeclaration(specifiers, moduleName));
25
- return false;
26
- }
27
- return specifier;
28
- });
29
- });
30
-
31
- return transform.toSource({ quote: 'single' });
32
- }
@@ -1,277 +0,0 @@
1
- import prettier from 'prettier';
2
- import { logger } from '@storybook/node-logger';
3
- import { storyNameFromExport } from '@storybook/csf';
4
- import { sanitizeName, jscodeshiftToPrettierParser } from '../lib/utils';
5
-
6
- /**
7
- * Convert a legacy story API to component story format
8
- *
9
- * For example:
10
- *
11
- * ```
12
- * input { Button } from './Button';
13
- * storiesOf('Button', module).add('story', () => <Button label="The Button" />);
14
- * ```
15
- *
16
- * Becomes:
17
- *
18
- * ```
19
- * input { Button } from './Button';
20
- * export default {
21
- * title: 'Button'
22
- * }
23
- * export const story = () => <Button label="The Button" />;
24
- *
25
- * NOTES: only support chained storiesOf() calls
26
- */
27
- export default function transformer(file, api, options) {
28
- const LITERAL = ['ts', 'tsx'].includes(options.parser) ? 'StringLiteral' : 'Literal';
29
-
30
- const j = api.jscodeshift;
31
- const root = j(file.source);
32
-
33
- function extractDecorators(parameters) {
34
- if (!parameters) {
35
- return {};
36
- }
37
- if (!parameters.properties) {
38
- return { storyParams: parameters };
39
- }
40
- let storyDecorators = parameters.properties.find((p) => p.key.name === 'decorators');
41
- if (!storyDecorators) {
42
- return { storyParams: parameters };
43
- }
44
- storyDecorators = storyDecorators.value;
45
- const storyParams = { ...parameters };
46
- storyParams.properties = storyParams.properties.filter((p) => p.key.name !== 'decorators');
47
- if (storyParams.properties.length === 0) {
48
- return { storyDecorators };
49
- }
50
- return { storyParams, storyDecorators };
51
- }
52
-
53
- function convertToModuleExports(path, originalExports) {
54
- const base = j(path);
55
-
56
- const statements = [];
57
- const extraExports = [];
58
-
59
- // .addDecorator
60
- const decorators = [];
61
- base
62
- .find(j.CallExpression)
63
- .filter(
64
- (call) => call.node.callee.property && call.node.callee.property.name === 'addDecorator'
65
- )
66
- .forEach((add) => {
67
- const decorator = add.node.arguments[0];
68
- decorators.push(decorator);
69
- });
70
- if (decorators.length > 0) {
71
- decorators.reverse();
72
- extraExports.push(
73
- j.property('init', j.identifier('decorators'), j.arrayExpression(decorators))
74
- );
75
- }
76
-
77
- // .addParameters
78
- const parameters = [];
79
- base
80
- .find(j.CallExpression)
81
- .filter(
82
- (call) => call.node.callee.property && call.node.callee.property.name === 'addParameters'
83
- )
84
- .forEach((add) => {
85
- // jscodeshift gives us the find results in reverse, but these args come in
86
- // order, so we double reverse here. ugh.
87
- const params = [...add.node.arguments[0].properties];
88
- params.reverse();
89
- params.forEach((prop) => parameters.push(prop));
90
- });
91
- if (parameters.length > 0) {
92
- parameters.reverse();
93
- extraExports.push(
94
- j.property('init', j.identifier('parameters'), j.objectExpression(parameters))
95
- );
96
- }
97
-
98
- if (originalExports.length > 0) {
99
- extraExports.push(
100
- j.property(
101
- 'init',
102
- j.identifier('excludeStories'),
103
- j.arrayExpression(originalExports.map((exp) => j.literal(exp)))
104
- )
105
- );
106
- }
107
-
108
- // storiesOf(...)
109
- base
110
- .find(j.CallExpression)
111
- .filter((call) => call.node.callee.name === 'storiesOf')
112
- .filter((call) => call.node.arguments.length > 0 && call.node.arguments[0].type === LITERAL)
113
- .forEach((storiesOf) => {
114
- const title = storiesOf.node.arguments[0].value;
115
- statements.push(
116
- j.exportDefaultDeclaration(
117
- j.objectExpression([
118
- j.property('init', j.identifier('title'), j.literal(title)),
119
- ...extraExports,
120
- ])
121
- )
122
- );
123
- });
124
-
125
- // .add(...)
126
- const adds = [];
127
- base
128
- .find(j.CallExpression)
129
- .filter((add) => add.node.callee.property && add.node.callee.property.name === 'add')
130
- .filter((add) => add.node.arguments.length >= 2 && add.node.arguments[0].type === LITERAL)
131
- .forEach((add) => adds.push(add));
132
-
133
- adds.reverse();
134
- adds.push(path);
135
-
136
- const identifiers = new Set();
137
- root.find(j.Identifier).forEach(({ value }) => identifiers.add(value.name));
138
- adds.forEach((add) => {
139
- let name = add.node.arguments[0].value;
140
- let key = sanitizeName(name);
141
- while (identifiers.has(key)) {
142
- key = `_${key}`;
143
- }
144
- identifiers.add(key);
145
- if (storyNameFromExport(key) === name) {
146
- name = null;
147
- }
148
-
149
- const val = add.node.arguments[1];
150
- statements.push(
151
- j.exportDeclaration(
152
- false,
153
- j.variableDeclaration('const', [j.variableDeclarator(j.identifier(key), val)])
154
- )
155
- );
156
-
157
- const storyAnnotations = [];
158
-
159
- if (name) {
160
- storyAnnotations.push(j.property('init', j.identifier('name'), j.literal(name)));
161
- }
162
-
163
- if (add.node.arguments.length > 2) {
164
- const originalStoryParams = add.node.arguments[2];
165
- const { storyParams, storyDecorators } = extractDecorators(originalStoryParams);
166
- if (storyParams) {
167
- storyAnnotations.push(j.property('init', j.identifier('parameters'), storyParams));
168
- }
169
- if (storyDecorators) {
170
- storyAnnotations.push(j.property('init', j.identifier('decorators'), storyDecorators));
171
- }
172
- }
173
-
174
- if (storyAnnotations.length > 0) {
175
- statements.push(
176
- j.assignmentStatement(
177
- '=',
178
- j.memberExpression(j.identifier(key), j.identifier('story')),
179
- j.objectExpression(storyAnnotations)
180
- )
181
- );
182
- }
183
- });
184
-
185
- const stmt = path.parent.node.type === 'VariableDeclarator' ? path.parent.parent : path.parent;
186
-
187
- statements.reverse();
188
- statements.forEach((s) => stmt.insertAfter(s));
189
- j(stmt).remove();
190
- }
191
-
192
- // Save the original storiesOf
193
- const initialStoriesOf = root
194
- .find(j.CallExpression)
195
- .filter((call) => call.node.callee.name === 'storiesOf');
196
-
197
- const defaultExports = root.find(j.ExportDefaultDeclaration);
198
- // If there's already a default export
199
- if (defaultExports.size() > 0) {
200
- if (initialStoriesOf.size() > 0) {
201
- logger.warn(
202
- `Found ${initialStoriesOf.size()} 'storiesOf' calls but existing default export, SKIPPING: '${
203
- file.path
204
- }'`
205
- );
206
- }
207
- return root.toSource();
208
- }
209
-
210
- // Exclude all the original named exports
211
- const originalExports = [];
212
- root.find(j.ExportNamedDeclaration).forEach((exp) => {
213
- const { declaration, specifiers } = exp.node;
214
- if (declaration) {
215
- const { id, declarations } = declaration;
216
- if (declarations) {
217
- declarations.forEach((decl) => {
218
- const { name, properties } = decl.id;
219
- if (name) {
220
- originalExports.push(name);
221
- } else if (properties) {
222
- properties.forEach((prop) => originalExports.push(prop.key.name));
223
- }
224
- });
225
- } else if (id) {
226
- originalExports.push(id.name);
227
- }
228
- } else if (specifiers) {
229
- specifiers.forEach((spec) => originalExports.push(spec.exported.name));
230
- }
231
- });
232
-
233
- // each top-level add expression corresponds to the last "add" of the chain.
234
- // replace it with the entire export statements
235
- root
236
- .find(j.CallExpression)
237
- .filter((add) => add.node.callee.property && add.node.callee.property.name === 'add')
238
- .filter((add) => add.node.arguments.length >= 2 && add.node.arguments[0].type === LITERAL)
239
- .filter((add) =>
240
- ['ExpressionStatement', 'VariableDeclarator'].includes(add.parentPath.node.type)
241
- )
242
- .forEach((path) => convertToModuleExports(path, originalExports));
243
-
244
- // remove storiesOf import
245
- root
246
- .find(j.ImportSpecifier)
247
- .filter(
248
- (spec) =>
249
- spec.node.imported.name === 'storiesOf' &&
250
- spec.parent.node.source.value.startsWith('@storybook/')
251
- )
252
- .forEach((spec) => {
253
- const toRemove = spec.parent.node.specifiers.length > 1 ? spec : spec.parent;
254
- j(toRemove).remove();
255
- });
256
-
257
- const source = root.toSource({ trailingComma: true, quote: 'single', tabWidth: 2 });
258
- if (initialStoriesOf.size() > 1) {
259
- logger.warn(
260
- `Found ${initialStoriesOf.size()} 'storiesOf' calls, PLEASE FIX BY HAND: '${file.path}'`
261
- );
262
- return source;
263
- }
264
-
265
- const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
266
- printWidth: 100,
267
- tabWidth: 2,
268
- bracketSpacing: true,
269
- trailingComma: 'es5',
270
- singleQuote: true,
271
- };
272
-
273
- return prettier.format(source, {
274
- ...prettierConfig,
275
- parser: jscodeshiftToPrettierParser(options.parser),
276
- });
277
- }