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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. package/README.md +0 -39
  2. package/dist/chunk-HBPKIMKE.mjs +1 -0
  3. package/dist/chunk-YH46OF24.mjs +2 -0
  4. package/dist/index.js +1 -1
  5. package/dist/index.mjs +1 -1
  6. package/dist/transforms/add-component-parameters.js +1 -1
  7. package/dist/transforms/csf-2-to-3.d.ts +5 -4
  8. package/dist/transforms/csf-2-to-3.js +3 -1
  9. package/dist/transforms/csf-2-to-3.mjs +2 -1
  10. package/dist/transforms/csf-hoist-story-annotations.js +1 -1
  11. package/dist/transforms/move-builtin-addons.js +1 -1
  12. package/dist/transforms/storiesof-to-csf.js +1 -1
  13. package/dist/transforms/storiesof-to-csf.mjs +1 -1
  14. package/dist/transforms/update-addon-info.js +1 -1
  15. package/dist/transforms/update-organisation-name.js +1 -1
  16. package/dist/transforms/upgrade-deprecated-types.d.ts +10 -0
  17. package/dist/transforms/upgrade-deprecated-types.js +2 -0
  18. package/dist/transforms/upgrade-deprecated-types.mjs +1 -0
  19. package/dist/transforms/upgrade-hierarchy-separators.js +1 -1
  20. package/jest.config.js +1 -0
  21. package/package.json +15 -10
  22. package/project.json +6 -0
  23. package/src/index.js +11 -1
  24. package/src/lib/utils.ts +2 -2
  25. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +1 -1
  26. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +1 -1
  27. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +1 -1
  28. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +34 -48
  29. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +33 -47
  30. package/src/transforms/__tests__/csf-2-to-3.test.ts +175 -57
  31. package/src/transforms/__tests__/upgrade-deprecated-types.test.ts +170 -0
  32. package/src/transforms/csf-2-to-3.ts +161 -33
  33. package/src/transforms/upgrade-deprecated-types.ts +142 -0
  34. package/dist/chunk-CO6EPEMB.mjs +0 -1
  35. package/dist/transforms/csf-to-mdx.d.ts +0 -29
  36. package/dist/transforms/csf-to-mdx.js +0 -3
  37. package/dist/transforms/csf-to-mdx.mjs +0 -3
  38. package/src/transforms/__testfixtures__/csf-to-mdx/basic.input.js +0 -20
  39. package/src/transforms/__testfixtures__/csf-to-mdx/basic.output.snapshot +0 -18
  40. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.input.js +0 -9
  41. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.output.snapshot +0 -10
  42. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.input.js +0 -13
  43. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.output.snapshot +0 -12
  44. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.input.js +0 -23
  45. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.output.snapshot +0 -22
  46. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.input.js +0 -16
  47. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.output.snapshot +0 -17
  48. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.input.js +0 -19
  49. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.output.snapshot +0 -18
  50. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.input.js +0 -24
  51. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.output.snapshot +0 -22
  52. package/src/transforms/csf-to-mdx.js +0 -190
@@ -1,9 +1,14 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
  import prettier from 'prettier';
3
3
  import * as t from '@babel/types';
4
+ import { isIdentifier, isTSTypeAnnotation, isTSTypeReference } from '@babel/types';
4
5
  import type { CsfFile } from '@storybook/csf-tools';
5
- import { formatCsf, loadCsf } from '@storybook/csf-tools';
6
- import { jscodeshiftToPrettierParser } from '../lib/utils';
6
+ import { loadCsf } from '@storybook/csf-tools';
7
+ import type { API, FileInfo } from 'jscodeshift';
8
+ import type { BabelFile, NodePath } from '@babel/core';
9
+ import * as babel from '@babel/core';
10
+ import * as recast from 'recast';
11
+ import { upgradeDeprecatedTypes } from './upgrade-deprecated-types';
7
12
 
8
13
  const logger = console;
9
14
 
@@ -89,19 +94,28 @@ const isReactGlobalRenderFn = (csf: CsfFile, storyFn: t.Expression) => {
89
94
  const isSimpleCSFStory = (init: t.Expression, annotations: t.ObjectProperty[]) =>
90
95
  annotations.length === 0 && t.isArrowFunctionExpression(init) && init.params.length === 0;
91
96
 
92
- function transform({ source }: { source: string }, api: any, options: { parser?: string }) {
97
+ export default function transform(info: FileInfo, api: API, options: { parser?: string }) {
93
98
  const makeTitle = (userTitle?: string) => {
94
99
  return userTitle || 'FIXME';
95
100
  };
96
- const csf = loadCsf(source, { makeTitle });
101
+ const csf = loadCsf(info.source, { makeTitle });
97
102
 
98
103
  try {
99
104
  csf.parse();
100
105
  } catch (err) {
101
106
  logger.log(`Error ${err}, skipping`);
102
- return source;
107
+ return info.source;
103
108
  }
104
109
 
110
+ // This allows for showing buildCodeFrameError messages
111
+ // @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
112
+ const file: BabelFile = new babel.File(
113
+ { filename: info.path },
114
+ { code: info.source, ast: csf._ast }
115
+ );
116
+
117
+ const importHelper = new StorybookImportHelper(file, info);
118
+
105
119
  const objectExports: Record<string, t.Statement> = {};
106
120
  Object.entries(csf._storyExports).forEach(([key, decl]) => {
107
121
  const annotations = Object.entries(csf._storyAnnotations[key]).map(([annotation, val]) => {
@@ -111,12 +125,15 @@ function transform({ source }: { source: string }, api: any, options: { parser?:
111
125
  if (t.isVariableDeclarator(decl)) {
112
126
  const { init, id } = decl;
113
127
  // only replace arrow function expressions && template
114
- // ignore no-arg stories without annotations
115
128
  const template = getTemplateBindVariable(init);
116
- if (
117
- (!t.isArrowFunctionExpression(init) && !template) ||
118
- isSimpleCSFStory(init, annotations)
119
- ) {
129
+ if (!t.isArrowFunctionExpression(init) && !template) return;
130
+ // Do change the type of no-arg stories without annotations to StoryFn when applicable
131
+ if (isSimpleCSFStory(init, annotations)) {
132
+ objectExports[key] = t.exportNamedDeclaration(
133
+ t.variableDeclaration('const', [
134
+ t.variableDeclarator(importHelper.updateTypeTo(id, 'StoryFn'), init),
135
+ ])
136
+ );
120
137
  return;
121
138
  }
122
139
 
@@ -128,26 +145,24 @@ function transform({ source }: { source: string }, api: any, options: { parser?:
128
145
  storyFn = init;
129
146
  }
130
147
 
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
148
  const renderAnnotation = isReactGlobalRenderFn(csf, storyFn)
139
149
  ? []
140
150
  : [t.objectProperty(t.identifier('render'), storyFn)];
141
151
 
142
152
  objectExports[key] = t.exportNamedDeclaration(
143
153
  t.variableDeclaration('const', [
144
- t.variableDeclarator(keyId, t.objectExpression([...renderAnnotation, ...annotations])),
154
+ t.variableDeclarator(
155
+ importHelper.updateTypeTo(id, 'StoryObj'),
156
+ t.objectExpression([...renderAnnotation, ...annotations])
157
+ ),
145
158
  ])
146
159
  );
147
160
  }
148
161
  });
149
162
 
150
- const updatedBody = csf._ast.program.body.reduce((acc, stmt) => {
163
+ importHelper.removeDeprecatedStoryImport();
164
+
165
+ csf._ast.program.body = csf._ast.program.body.reduce((acc, stmt) => {
151
166
  // remove story annotations & template declarations
152
167
  if (isStoryAnnotation(stmt, objectExports) || isTemplateDeclaration(stmt, csf._templates)) {
153
168
  return acc;
@@ -164,21 +179,134 @@ function transform({ source }: { source: string }, api: any, options: { parser?:
164
179
  acc.push(stmt);
165
180
  return acc;
166
181
  }, []);
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,
182
+
183
+ upgradeDeprecatedTypes(file);
184
+
185
+ let output = recast.print(csf._ast, {}).code;
186
+
187
+ try {
188
+ const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
189
+ printWidth: 100,
190
+ tabWidth: 2,
191
+ bracketSpacing: true,
192
+ trailingComma: 'es5',
193
+ singleQuote: true,
194
+ };
195
+
196
+ output = prettier.format(output, {
197
+ ...prettierConfig,
198
+ // This will infer the parser from the filename.
199
+ filepath: info.path,
200
+ });
201
+ } catch (e) {
202
+ logger.log(`Failed applying prettier to ${info.path}.`);
203
+ }
204
+
205
+ return output;
206
+ }
207
+
208
+ class StorybookImportHelper {
209
+ constructor(file: BabelFile, info: FileInfo) {
210
+ this.sbImportDeclarations = this.getAllSbImportDeclarations(file);
211
+ }
212
+
213
+ private sbImportDeclarations: NodePath<t.ImportDeclaration>[];
214
+
215
+ private getAllSbImportDeclarations = (file: BabelFile) => {
216
+ const found: NodePath<t.ImportDeclaration>[] = [];
217
+
218
+ file.path.traverse({
219
+ ImportDeclaration: (path) => {
220
+ const source = path.node.source.value;
221
+ if (source.startsWith('@storybook/csf') || !source.startsWith('@storybook')) return;
222
+ const isRendererImport = path.get('specifiers').some((specifier) => {
223
+ if (specifier.isImportNamespaceSpecifier()) {
224
+ throw path.buildCodeFrameError(
225
+ `This codemod does not support namespace imports for a ${path.node.source.value} package.\n` +
226
+ 'Replace the namespace import with named imports and try again.'
227
+ );
228
+ }
229
+ if (!specifier.isImportSpecifier()) return false;
230
+ const imported = specifier.get('imported');
231
+ if (!imported.isIdentifier()) return false;
232
+
233
+ return [
234
+ 'Story',
235
+ 'StoryFn',
236
+ 'StoryObj',
237
+ 'Meta',
238
+ 'ComponentStory',
239
+ 'ComponentStoryFn',
240
+ 'ComponentStoryObj',
241
+ 'ComponentMeta',
242
+ ].includes(imported.node.name);
243
+ });
244
+
245
+ if (isRendererImport) found.push(path);
246
+ },
247
+ });
248
+ return found;
176
249
  };
177
250
 
178
- return prettier.format(output, {
179
- ...prettierConfig,
180
- parser: jscodeshiftToPrettierParser(options?.parser),
181
- });
251
+ getOrAddImport = (type: string): string | undefined => {
252
+ // prefer type import
253
+ const sbImport =
254
+ this.sbImportDeclarations.find((path) => path.node.importKind === 'type') ??
255
+ this.sbImportDeclarations[0];
256
+ if (sbImport == null) return undefined;
257
+
258
+ const specifiers = sbImport.get('specifiers');
259
+ const importSpecifier = specifiers.find((specifier) => {
260
+ if (!specifier.isImportSpecifier()) return false;
261
+ const imported = specifier.get('imported');
262
+ if (!imported.isIdentifier()) return false;
263
+ return imported.node.name === type;
264
+ });
265
+ if (importSpecifier) return importSpecifier.node.local.name;
266
+ specifiers[0].insertBefore(t.importSpecifier(t.identifier(type), t.identifier(type)));
267
+ return type;
268
+ };
269
+
270
+ removeDeprecatedStoryImport = () => {
271
+ const specifiers = this.sbImportDeclarations.flatMap((it) => it.get('specifiers'));
272
+ const storyImports = specifiers.filter((specifier) => {
273
+ if (!specifier.isImportSpecifier()) return false;
274
+ const imported = specifier.get('imported');
275
+ if (!imported.isIdentifier()) return false;
276
+ return imported.node.name === 'Story';
277
+ });
278
+ storyImports.forEach((path) => path.remove());
279
+ };
280
+
281
+ getAllLocalImports = () => {
282
+ return this.sbImportDeclarations
283
+ .flatMap((it) => it.get('specifiers'))
284
+ .map((it) => it.node.local.name);
285
+ };
286
+
287
+ updateTypeTo = (id: t.LVal, type: string): t.LVal => {
288
+ if (
289
+ isIdentifier(id) &&
290
+ isTSTypeAnnotation(id.typeAnnotation) &&
291
+ isTSTypeReference(id.typeAnnotation.typeAnnotation) &&
292
+ isIdentifier(id.typeAnnotation.typeAnnotation.typeName)
293
+ ) {
294
+ const { name } = id.typeAnnotation.typeAnnotation.typeName;
295
+ if (this.getAllLocalImports().includes(name)) {
296
+ const localTypeImport = this.getOrAddImport(type);
297
+ return {
298
+ ...id,
299
+ typeAnnotation: t.tsTypeAnnotation(
300
+ t.tsTypeReference(
301
+ t.identifier(localTypeImport),
302
+ id.typeAnnotation.typeAnnotation.typeParameters
303
+ )
304
+ ),
305
+ };
306
+ }
307
+ }
308
+ return id;
309
+ };
182
310
  }
183
311
 
184
- export default transform;
312
+ export const parser = 'tsx';
@@ -0,0 +1,142 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ import prettier from 'prettier';
3
+ import type { API, FileInfo } from 'jscodeshift';
4
+ import type { BabelFile, NodePath } from '@babel/core';
5
+ import * as babel from '@babel/core';
6
+ import { loadCsf } from '@storybook/csf-tools';
7
+ import * as recast from 'recast';
8
+ import * as t from '@babel/types';
9
+
10
+ const logger = console;
11
+
12
+ const deprecatedTypes = [
13
+ 'ComponentStory',
14
+ 'ComponentStoryFn',
15
+ 'ComponentStoryObj',
16
+ 'ComponentMeta',
17
+ 'Story',
18
+ ];
19
+
20
+ function migrateType(oldType: string) {
21
+ if (oldType === 'Story' || oldType === 'ComponentStory') return 'StoryFn';
22
+ return oldType.replace('Component', '');
23
+ }
24
+
25
+ export default function transform(info: FileInfo, api: API, options: { parser?: string }) {
26
+ // TODO what do I need to with the title?
27
+ const fileNode = loadCsf(info.source, { makeTitle: (title) => title })._ast;
28
+ // @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
29
+ const file: BabelFile = new babel.File(
30
+ { filename: info.path },
31
+ { code: info.source, ast: fileNode }
32
+ );
33
+
34
+ upgradeDeprecatedTypes(file);
35
+
36
+ let output = recast.print(file.path.node).code;
37
+
38
+ try {
39
+ const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
40
+ printWidth: 100,
41
+ tabWidth: 2,
42
+ bracketSpacing: true,
43
+ trailingComma: 'es5',
44
+ singleQuote: true,
45
+ };
46
+
47
+ output = prettier.format(output, { ...prettierConfig, filepath: info.path });
48
+ } catch (e) {
49
+ logger.log(`Failed applying prettier to ${info.path}.`);
50
+ }
51
+
52
+ return output;
53
+ }
54
+
55
+ export const parser = 'tsx';
56
+
57
+ export function upgradeDeprecatedTypes(file: BabelFile) {
58
+ const importedNamespaces: Set<string> = new Set();
59
+ const typeReferencesToUpdate: Set<string> = new Set();
60
+ const existingImports: { name: string; isAlias: boolean; path: NodePath }[] = [];
61
+
62
+ file.path.traverse({
63
+ ImportDeclaration: (path) => {
64
+ existingImports.push(
65
+ ...path.get('specifiers').map((specifier) => ({
66
+ name: specifier.node.local.name,
67
+ isAlias: !(
68
+ specifier.isImportSpecifier() &&
69
+ t.isIdentifier(specifier.node.imported) &&
70
+ specifier.node.local.name === specifier.node.imported.name
71
+ ),
72
+ path: specifier,
73
+ }))
74
+ );
75
+
76
+ const source = path.node.source.value;
77
+ if (!source.startsWith('@storybook')) return;
78
+
79
+ path.get('specifiers').forEach((specifier) => {
80
+ if (specifier.isImportNamespaceSpecifier()) {
81
+ importedNamespaces.add(specifier.node.local.name);
82
+ }
83
+ if (!specifier.isImportSpecifier()) return;
84
+ const imported = specifier.get('imported');
85
+ if (!imported.isIdentifier()) return;
86
+
87
+ // if we find a deprecated import
88
+ if (deprecatedTypes.includes(imported.node.name)) {
89
+ // we don't have to rewrite type references for aliased imports
90
+ if (imported.node.name === specifier.node.local.name) {
91
+ typeReferencesToUpdate.add(specifier.node.local.name);
92
+ }
93
+
94
+ const newType = migrateType(imported.node.name);
95
+
96
+ // replace the deprecated import type when the new type isn't yet imported
97
+ // note that we don't replace the local name of the specifier
98
+ if (!existingImports.some((it) => it.name === newType)) {
99
+ imported.replaceWith(t.identifier(newType));
100
+ existingImports.push({ name: newType, isAlias: false, path: specifier });
101
+ } else {
102
+ // if the existing import has the same local name but is an alias we throw
103
+ // we could have imported the type with an alias, but seems to much effort
104
+ const existingImport = existingImports.find((it) => it.name === newType && it.isAlias);
105
+ if (existingImport) {
106
+ throw existingImport.path.buildCodeFrameError(
107
+ 'This codemod does not support local imports that are called the same as a storybook import.\n' +
108
+ 'Rename this local import and try again.'
109
+ );
110
+ } else {
111
+ // if the type already exists, without being aliased
112
+ // we can safely remove the deprecated import now
113
+ specifier.remove();
114
+ }
115
+ }
116
+ }
117
+ });
118
+ },
119
+ });
120
+
121
+ file.path.traverse({
122
+ TSTypeReference: (path) => {
123
+ const typeName = path.get('typeName');
124
+ if (typeName.isIdentifier()) {
125
+ if (typeReferencesToUpdate.has(typeName.node.name)) {
126
+ typeName.replaceWith(t.identifier(migrateType(typeName.node.name)));
127
+ }
128
+ } else if (typeName.isTSQualifiedName()) {
129
+ // For example SB.StoryObj
130
+ const namespace = typeName.get('left');
131
+ if (namespace.isIdentifier()) {
132
+ if (importedNamespaces.has(namespace.node.name)) {
133
+ const right = typeName.get('right');
134
+ if (deprecatedTypes.includes(right.node.name)) {
135
+ right.replaceWith(t.identifier(migrateType(right.node.name)));
136
+ }
137
+ }
138
+ }
139
+ }
140
+ },
141
+ });
142
+ }
@@ -1 +0,0 @@
1
- import camelCase from"lodash/camelCase";import upperFirst from"lodash/upperFirst";var sanitizeName=name=>{let key=upperFirst(camelCase(name));return/^\d/.test(key)&&(key=`_${key}`),/^\d/.test(key)&&(key=`_${key}`),key};function jscodeshiftToPrettierParser(parser){let parserMap={babylon:"babel",flow:"flow",ts:"typescript",tsx:"typescript"};return parser&&parserMap[parser]||"babel"}export{sanitizeName,jscodeshiftToPrettierParser};
@@ -1,29 +0,0 @@
1
- /**
2
- * Convert a component's module story file into an MDX file
3
- *
4
- * For example:
5
- *
6
- * ```
7
- * input { Button } from './Button';
8
- * export default {
9
- * title: 'Button'
10
- * }
11
- * export const story = () => <Button label="The Button" />;
12
- * ```
13
- *
14
- * Becomes:
15
- *
16
- * ```
17
- * import { Meta, Story } from '@storybook/addon-docs';
18
- * input { Button } from './Button';
19
- *
20
- * <Meta title='Button' />
21
- *
22
- * <Story name='story'>
23
- * <Button label="The Button" />
24
- * </Story>
25
- * ```
26
- */
27
- declare function transformer(file: any, api: any): any;
28
-
29
- export { transformer as default };
@@ -1,3 +0,0 @@
1
- var y=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var A=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var D=(s,r)=>{for(var t in r)y(s,t,{get:r[t],enumerable:!0})},M=(s,r,t,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of A(r))!C.call(s,o)&&o!==t&&y(s,o,{get:()=>r[o],enumerable:!(a=v(r,o))||a.enumerable});return s};var W=s=>M(y({},"__esModule",{value:!0}),s);var L={};D(L,{default:()=>g});module.exports=W(L);var E=require("recast"),x=require("@storybook/csf");function J(s,r){return s.__paths[0].node.program.body.map(a=>{let{code:o}=(0,E.prettyPrint)(a,r);return a.type==="JSXElement"?`${o}
2
- `:o}).join(`
3
- `)}function K(s){let{code:r}=(0,E.prettyPrint)(s,{});return(0,eval)(r)}function g(s,r){let t=r.jscodeshift,a=t(s.source),o={},p={};function f(n,e){return t.jsxAttribute(t.jsxIdentifier(n),e.type==="Literal"?e:t.jsxExpressionContainer(e))}function S(n){return n.type==="ArrowFunctionExpression"&&n.body.type==="JSXElement"?n.body:t.jsxExpressionContainer(n)}function b(n){let e=o[n];if(e){let i=e.properties.find(c=>c.key.name==="name");if(i&&i.value.type==="Literal")return i.value.value}return n}function I(n){let e=[],i=o[n];return i&&i.properties.forEach(c=>{let{key:l,value:d}=c;l.name!=="name"&&e.push(f(l.name,d))}),e}let u=a.find(t.ExportDefaultDeclaration).filter(n=>n.node.declaration.properties.map(e=>e.key.name).includes("title"));if(u.size()===0)return a.toSource();a.find(t.ImportDeclaration).at(-1).insertAfter(t.emptyStatement()).insertAfter(t.importDeclaration([t.importSpecifier(t.identifier("Meta")),t.importSpecifier(t.identifier("Story"))],t.literal("@storybook/addon-docs"))),a.find(t.ImportDeclaration).filter(n=>n.node.source.value==="react").remove(),u.forEach(n=>{n.node.declaration.properties.forEach(e=>{["includeStories","excludeStories"].includes(e.key.name)&&(p[e.key.name]=K(e.value))})});let j=a.find(t.ExportNamedDeclaration);j.forEach(n=>{let e=n.node.declaration.declarations[0].id.name;(0,x.isExportStory)(e,p)&&(o[e]=null)});let h=a.find(t.AssignmentExpression).filter(n=>{let{left:e}=n.node;return e.type==="MemberExpression"&&e.object.type==="Identifier"&&e.object.name in o&&e.property.type==="Identifier"&&e.property.name==="story"});return h.forEach(n=>{let{left:e,right:i}=n.node;o[e.object.name]=i}),h.remove(),u.replaceWith(n=>{let e=t.jsxIdentifier("Meta"),i=[];n.node.declaration.properties.forEach(l=>{let{key:d,value:m}=l;["includeStories","excludeStories"].includes(d.name)||i.push(f(d.name,m))});let c=t.jsxOpeningElement(e,i);return c.selfClosing=!0,t.jsxElement(c)}),j.replaceWith(n=>{let e=n.node.declaration.declarations[0].id.name;if(!(0,x.isExportStory)(e,p))return n.node;let i=t.jsxIdentifier("Story"),c=b(e),l=[f("name",t.literal(c)),...I(e)],d=t.jsxOpeningElement(i,l),m=t.jsxClosingElement(i),k=[S(n.node.declaration.declarations[0].init)];return t.jsxElement(d,m,k)}),J(a,{quote:"single",trailingComma:"true",tabWidth:2})}0&&(module.exports={});
@@ -1,3 +0,0 @@
1
- import{prettyPrint}from"recast";import{isExportStory}from"@storybook/csf";function exportMdx(root,options){return root.__paths[0].node.program.body.map(n=>{let{code}=prettyPrint(n,options);return n.type==="JSXElement"?`${code}
2
- `:code}).join(`
3
- `)}function parseIncludeExclude(prop){let{code}=prettyPrint(prop,{});return(0,eval)(code)}function transformer(file,api){let j=api.jscodeshift,root=j(file.source),storyKeyToStory={},meta={};function makeAttr(key,val){return j.jsxAttribute(j.jsxIdentifier(key),val.type==="Literal"?val:j.jsxExpressionContainer(val))}function getStoryContents(node){return node.type==="ArrowFunctionExpression"&&node.body.type==="JSXElement"?node.body:j.jsxExpressionContainer(node)}function getName(storyKey){let story=storyKeyToStory[storyKey];if(story){let name=story.properties.find(prop=>prop.key.name==="name");if(name&&name.value.type==="Literal")return name.value.value}return storyKey}function getStoryAttrs(storyKey){let attrs=[],story=storyKeyToStory[storyKey];return story&&story.properties.forEach(prop=>{let{key,value}=prop;key.name!=="name"&&attrs.push(makeAttr(key.name,value))}),attrs}let defaultExportWithTitle=root.find(j.ExportDefaultDeclaration).filter(def=>def.node.declaration.properties.map(p=>p.key.name).includes("title"));if(defaultExportWithTitle.size()===0)return root.toSource();root.find(j.ImportDeclaration).at(-1).insertAfter(j.emptyStatement()).insertAfter(j.importDeclaration([j.importSpecifier(j.identifier("Meta")),j.importSpecifier(j.identifier("Story"))],j.literal("@storybook/addon-docs"))),root.find(j.ImportDeclaration).filter(decl=>decl.node.source.value==="react").remove(),defaultExportWithTitle.forEach(exp=>{exp.node.declaration.properties.forEach(p=>{["includeStories","excludeStories"].includes(p.key.name)&&(meta[p.key.name]=parseIncludeExclude(p.value))})});let namedExports=root.find(j.ExportNamedDeclaration);namedExports.forEach(exp=>{let storyKey=exp.node.declaration.declarations[0].id.name;isExportStory(storyKey,meta)&&(storyKeyToStory[storyKey]=null)});let storyAssignments=root.find(j.AssignmentExpression).filter(exp=>{let{left}=exp.node;return left.type==="MemberExpression"&&left.object.type==="Identifier"&&left.object.name in storyKeyToStory&&left.property.type==="Identifier"&&left.property.name==="story"});return storyAssignments.forEach(exp=>{let{left,right}=exp.node;storyKeyToStory[left.object.name]=right}),storyAssignments.remove(),defaultExportWithTitle.replaceWith(exp=>{let jsxId=j.jsxIdentifier("Meta"),attrs=[];exp.node.declaration.properties.forEach(prop=>{let{key,value}=prop;["includeStories","excludeStories"].includes(key.name)||attrs.push(makeAttr(key.name,value))});let opening=j.jsxOpeningElement(jsxId,attrs);return opening.selfClosing=!0,j.jsxElement(opening)}),namedExports.replaceWith(exp=>{let storyKey=exp.node.declaration.declarations[0].id.name;if(!isExportStory(storyKey,meta))return exp.node;let jsxId=j.jsxIdentifier("Story"),name=getName(storyKey),attributes=[makeAttr("name",j.literal(name)),...getStoryAttrs(storyKey)],opening=j.jsxOpeningElement(jsxId,attributes),closing=j.jsxClosingElement(jsxId),children=[getStoryContents(exp.node.declaration.declarations[0].init)];return j.jsxElement(opening,closing,children)}),exportMdx(root,{quote:"single",trailingComma:"true",tabWidth:2})}export{transformer as default};
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- import Button from './Button';
3
- import { action } from '@storybook/addon-actions';
4
-
5
- export default {
6
- title: 'Button',
7
- };
8
-
9
- export const story1 = () => <Button label="Story 1" />;
10
-
11
- export const story2 = () => <Button label="Story 2" onClick={action('click')} />;
12
- story2.story = { name: 'second story' };
13
-
14
- export const story3 = () => (
15
- <div>
16
- <Button label="The Button" onClick={action('onClick')} />
17
- <br />
18
- </div>
19
- );
20
- story3.story = { name: 'complex story' };
@@ -1,18 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`csf-to-mdx transforms correctly using "basic.input.js" data 1`] = `
4
- "import Button from './Button';
5
- import { action } from '@storybook/addon-actions';
6
- import { Meta, Story } from '@storybook/addon-docs';
7
-
8
- <Meta title='Button' />
9
-
10
- <Story name='story1'><Button label='Story 1' /></Story>
11
-
12
- <Story name='second story'><Button label='Story 2' onClick={action('click')} /></Story>
13
-
14
- <Story name='complex story'><div>
15
- <Button label='The Button' onClick={action('onClick')} />
16
- <br />
17
- </div></Story>"
18
- `;
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
- import Button from './Button';
3
-
4
- export default {
5
- title: 'Button',
6
- id: 'button-id',
7
- };
8
-
9
- export const someStory = () => <Button label="Story 1" />;
@@ -1,10 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`csf-to-mdx transforms correctly using "component-id.input.js" data 1`] = `
4
- "import Button from './Button';
5
- import { Meta, Story } from '@storybook/addon-docs';
6
-
7
- <Meta title='Button' id='button-id' />
8
-
9
- <Story name='someStory'><Button label='Story 1' /></Story>"
10
- `;
@@ -1,13 +0,0 @@
1
- import React from 'react';
2
- import Button from './Button';
3
-
4
- export default {
5
- title: 'Some.Button',
6
- decorators: [withKnobs, storyFn => <div className="foo">{storyFn}</div>],
7
- };
8
-
9
- export const story1 = () => <Button label="The Button" />;
10
- story1.story = {
11
- name: 'with decorator',
12
- decorators: [withKnobs],
13
- };
@@ -1,12 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`csf-to-mdx transforms correctly using "decorators.input.js" data 1`] = `
4
- "import Button from './Button';
5
- import { Meta, Story } from '@storybook/addon-docs';
6
-
7
- <Meta
8
- title='Some.Button'
9
- decorators={[withKnobs, storyFn => <div className='foo'>{storyFn}</div>]} />
10
-
11
- <Story name='with decorator' decorators={[withKnobs]}><Button label='The Button' /></Story>"
12
- `;
@@ -1,23 +0,0 @@
1
- import React from 'react';
2
- import Button from './Button';
3
- import { action } from '@storybook/addon-actions';
4
-
5
- export default {
6
- title: 'Button',
7
- excludeStories: /.*Data$/,
8
- };
9
-
10
- export const rowData = { col1: 'a', col2: 2 };
11
-
12
- export const story1 = () => <Button label="Story 1" />;
13
-
14
- export const story2 = () => <Button label="Story 2" onClick={action('click')} />;
15
- story2.story = { name: 'second story' };
16
-
17
- export const story3 = () => (
18
- <div>
19
- <Button label="The Button" onClick={action('onClick')} />
20
- <br />
21
- </div>
22
- );
23
- story3.story = { name: 'complex story' };
@@ -1,22 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`csf-to-mdx transforms correctly using "exclude-stories.input.js" data 1`] = `
4
- "import Button from './Button';
5
- import { action } from '@storybook/addon-actions';
6
- import { Meta, Story } from '@storybook/addon-docs';
7
-
8
- <Meta title='Button' />
9
-
10
- export const rowData = {
11
- col1: 'a',
12
- col2: 2,
13
- };
14
- <Story name='story1'><Button label='Story 1' /></Story>
15
-
16
- <Story name='second story'><Button label='Story 2' onClick={action('click')} /></Story>
17
-
18
- <Story name='complex story'><div>
19
- <Button label='The Button' onClick={action('onClick')} />
20
- <br />
21
- </div></Story>"
22
- `;
@@ -1,16 +0,0 @@
1
- import React from 'react';
2
- import Button from './Button';
3
-
4
- import { storiesOf } from '@storybook/react';
5
-
6
- export default {
7
- title: 'Button',
8
- component: Button,
9
- parameters: {
10
- foo: 1,
11
- bar: 2,
12
- },
13
- };
14
-
15
- export const story1 = () => <Button label="The Button" />;
16
- story1.story = { name: 'with kind parameters' };
@@ -1,17 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`csf-to-mdx transforms correctly using "parameters.input.js" data 1`] = `
4
- "import Button from './Button';
5
- import { storiesOf } from '@storybook/react';
6
- import { Meta, Story } from '@storybook/addon-docs';
7
-
8
- <Meta
9
- title='Button'
10
- component={Button}
11
- parameters={{
12
- foo: 1,
13
- bar: 2,
14
- }} />
15
-
16
- <Story name='with kind parameters'><Button label='The Button' /></Story>"
17
- `;
@@ -1,19 +0,0 @@
1
- import global from 'global';
2
-
3
- const { document } = global;
4
-
5
- export default {
6
- title: 'Function',
7
- };
8
-
9
- export const functionStory = () => {
10
- const btn = document.createElement('button');
11
- btn.innerHTML = 'Hello Button';
12
- btn.addEventListener('click', action('Click'));
13
- return btn;
14
- };
15
-
16
- functionStory.story = {
17
- name: 'function',
18
- height: '100px',
19
- };
@@ -1,18 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`csf-to-mdx transforms correctly using "story-function.input.js" data 1`] = `
4
- "import global from 'global';
5
- import { Meta, Story } from '@storybook/addon-docs';
6
-
7
- const {
8
- document,
9
- } = global;
10
- <Meta title='Function' />
11
-
12
- <Story name='function' height='100px'>{() => {
13
- const btn = document.createElement('button');
14
- btn.innerHTML = 'Hello Button';
15
- btn.addEventListener('click', action('Click'));
16
- return btn;
17
- }}</Story>"
18
- `;