@storybook/codemod 7.4.0-alpha.0 → 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,170 +0,0 @@
1
- import { describe, expect, it } from '@jest/globals';
2
- import { dedent } from 'ts-dedent';
3
- import type { API } from 'jscodeshift';
4
- import ansiRegex from 'ansi-regex';
5
- import _transform from '../upgrade-deprecated-types';
6
-
7
- expect.addSnapshotSerializer({
8
- print: (val: any) => val,
9
- test: () => true,
10
- });
11
-
12
- const tsTransform = (source: string) =>
13
- _transform({ source, path: 'Component.stories.ts' }, {} as API, { parser: 'tsx' }).trim();
14
-
15
- describe('upgrade-deprecated-types', () => {
16
- describe('typescript', () => {
17
- it('upgrade regular imports', () => {
18
- expect(
19
- tsTransform(dedent`
20
- import { Story, ComponentMeta, Meta, ComponentStory, ComponentStoryObj, ComponentStoryFn } from '@storybook/react';
21
- import { Cat, CatProps } from './Cat';
22
-
23
- const meta = { title: 'Cat', component: Cat } satisfies ComponentMeta<typeof Cat>
24
- const meta2: Meta<CatProps> = { title: 'Cat', component: Cat };
25
- export default meta;
26
-
27
- export const A: ComponentStory<typeof Cat> = (args) => <Cat {...args} />;
28
- export const B: any = (args) => <Button {...args} />;
29
- export const C: ComponentStoryFn<typeof Cat> = (args) => <Cat {...args} />;
30
- export const D: ComponentStoryObj<typeof Cat> = {
31
- args: {
32
- name: 'Fluffy',
33
- },
34
- };
35
- export const E: Story<CatProps> = (args) => <Cat {...args} />;
36
- `)
37
- ).toMatchInlineSnapshot(`
38
- import { StoryFn, Meta, StoryObj } from '@storybook/react';
39
- import { Cat, CatProps } from './Cat';
40
-
41
- const meta = { title: 'Cat', component: Cat } satisfies Meta<typeof Cat>;
42
- const meta2: Meta<CatProps> = { title: 'Cat', component: Cat };
43
- export default meta;
44
-
45
- export const A: StoryFn<typeof Cat> = (args) => <Cat {...args} />;
46
- export const B: any = (args) => <Button {...args} />;
47
- export const C: StoryFn<typeof Cat> = (args) => <Cat {...args} />;
48
- export const D: StoryObj<typeof Cat> = {
49
- args: {
50
- name: 'Fluffy',
51
- },
52
- };
53
- export const E: StoryFn<CatProps> = (args) => <Cat {...args} />;
54
- `);
55
- });
56
-
57
- it('upgrade imports with local names', () => {
58
- expect(
59
- tsTransform(dedent`
60
- import { Story as Story_, ComponentMeta as ComponentMeta_, ComponentStory as Story__, ComponentStoryObj as ComponentStoryObj_, ComponentStoryFn as StoryFn_ } from '@storybook/react';
61
- import { Cat } from './Cat';
62
-
63
- const meta = { title: 'Cat', component: Cat } satisfies ComponentMeta_<typeof Cat>
64
- const meta2: ComponentMeta_<typeof Cat> = { title: 'Cat', component: Cat };
65
- export default meta;
66
-
67
- export const A: Story__<typeof Cat> = (args) => <Cat {...args} />;
68
- export const B: any = (args) => <Button {...args} />;
69
- export const C: StoryFn_<typeof Cat> = (args) => <Cat {...args} />;
70
- export const D: ComponentStoryObj_<typeof Cat> = {
71
- args: {
72
- name: 'Fluffy',
73
- },
74
- };
75
- export const E: Story_<CatProps> = (args) => <Cat {...args} />;
76
- `)
77
- ).toMatchInlineSnapshot(`
78
- import {
79
- StoryFn as Story_,
80
- Meta as ComponentMeta_,
81
- StoryObj as ComponentStoryObj_,
82
- } from '@storybook/react';
83
- import { Cat } from './Cat';
84
-
85
- const meta = { title: 'Cat', component: Cat } satisfies ComponentMeta_<typeof Cat>;
86
- const meta2: ComponentMeta_<typeof Cat> = { title: 'Cat', component: Cat };
87
- export default meta;
88
-
89
- export const A: Story__<typeof Cat> = (args) => <Cat {...args} />;
90
- export const B: any = (args) => <Button {...args} />;
91
- export const C: StoryFn_<typeof Cat> = (args) => <Cat {...args} />;
92
- export const D: ComponentStoryObj_<typeof Cat> = {
93
- args: {
94
- name: 'Fluffy',
95
- },
96
- };
97
- export const E: Story_<CatProps> = (args) => <Cat {...args} />;
98
- `);
99
- });
100
-
101
- it('upgrade imports with conflicting local names', () => {
102
- expect.addSnapshotSerializer({
103
- serialize: (value) => value.replace(ansiRegex(), ''),
104
- test: () => true,
105
- });
106
-
107
- expect(() =>
108
- tsTransform(dedent`
109
- import { ComponentMeta as Meta, ComponentStory as StoryFn } from '@storybook/react';
110
- import { Cat } from './Cat';
111
-
112
- const meta = { title: 'Cat', component: Cat } satisfies Meta<typeof Cat>
113
- export default meta;
114
-
115
- export const A: StoryFn<typeof Cat> = (args) => <Cat {...args} />;
116
-
117
- `)
118
- ).toThrowErrorMatchingInlineSnapshot(`
119
- This codemod does not support local imports that are called the same as a storybook import.
120
- Rename this local import and try again.
121
- > 1 | import { ComponentMeta as Meta, ComponentStory as StoryFn } from '@storybook/react';
122
- | ^^^^^^^^^^^^^^^^^^^^^
123
- 2 | import { Cat } from './Cat';
124
- 3 |
125
- 4 | const meta = { title: 'Cat', component: Cat } satisfies Meta<typeof Cat>
126
- `);
127
- });
128
-
129
- it('upgrade namespaces', () => {
130
- expect(
131
- tsTransform(dedent`
132
- import * as SB from '@storybook/react';
133
- import { Cat, CatProps } from './Cat';
134
-
135
- const meta = { title: 'Cat', component: Cat } satisfies SB.ComponentMeta<typeof Cat>;
136
- const meta2: SB.ComponentMeta<typeof Cat> = { title: 'Cat', component: Cat };
137
- export default meta;
138
-
139
- export const A: SB.ComponentStory<typeof Cat> = (args) => <Cat {...args} />;
140
- export const B: any = (args) => <Button {...args} />;
141
- export const C: SB.ComponentStoryFn<typeof Cat> = (args) => <Cat {...args} />;
142
- export const D: SB.ComponentStoryObj<typeof Cat> = {
143
- args: {
144
- name: 'Fluffy',
145
- },
146
- };
147
- export const E: SB.Story<CatProps> = (args) => <Cat {...args} />;
148
-
149
- `)
150
- ).toMatchInlineSnapshot(`
151
- import * as SB from '@storybook/react';
152
- import { Cat, CatProps } from './Cat';
153
-
154
- const meta = { title: 'Cat', component: Cat } satisfies SB.Meta<typeof Cat>;
155
- const meta2: SB.Meta<typeof Cat> = { title: 'Cat', component: Cat };
156
- export default meta;
157
-
158
- export const A: SB.StoryFn<typeof Cat> = (args) => <Cat {...args} />;
159
- export const B: any = (args) => <Button {...args} />;
160
- export const C: SB.StoryFn<typeof Cat> = (args) => <Cat {...args} />;
161
- export const D: SB.StoryObj<typeof Cat> = {
162
- args: {
163
- name: 'Fluffy',
164
- },
165
- };
166
- export const E: SB.StoryFn<CatProps> = (args) => <Cat {...args} />;
167
- `);
168
- });
169
- });
170
- });
@@ -1,62 +0,0 @@
1
- /**
2
- * Adds a `component` parameter for each storiesOf(...) call.
3
- *
4
- * For example:
5
- *
6
- * input { Button } from './Button';
7
- * storiesOf('Button', module).add('story', () => <Button label="The Button" />);
8
- *
9
- * Becomes:
10
- *
11
- * input { Button } from './Button';
12
- * storiesOf('Button', module)
13
- * .addParameters({ component: Button })
14
- * .add('story', () => <Button label="The Button" />);
15
- *
16
- * Heuristics:
17
- * - The storiesOf "kind" name must be Button
18
- * - Button must be imported in the file
19
- */
20
- export default function transformer(file, api) {
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.
25
- // For instance:
26
- //
27
- // import foo from 'foo';
28
- // import { bar, baz } from 'zoo';
29
- //
30
- // => { foo: true, bar: true, baz: true }
31
- const importMap = {};
32
- root.find(j.ImportDeclaration).forEach((imp) =>
33
- imp.node.specifiers.forEach((spec) => {
34
- importMap[spec.local.name] = true;
35
- })
36
- );
37
-
38
- function getLeafName(string) {
39
- const parts = string.split(/\/|\.|\|/);
40
- return parts[parts.length - 1];
41
- }
42
-
43
- function addComponentParameter(call) {
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
- ]);
49
- }
50
-
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
-
61
- return root.toSource();
62
- }
@@ -1,335 +0,0 @@
1
- /* eslint-disable no-underscore-dangle */
2
- import prettier from 'prettier';
3
- import * as t from '@babel/types';
4
- import { isIdentifier, isTSTypeAnnotation, isTSTypeReference } from '@babel/types';
5
- import type { CsfFile } from '@storybook/csf-tools';
6
- import { loadCsf, printCsf } 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 { upgradeDeprecatedTypes } from './upgrade-deprecated-types';
11
-
12
- const logger = console;
13
-
14
- const renameAnnotation = (annotation: string) => {
15
- return annotation === 'storyName' ? 'name' : annotation;
16
- };
17
-
18
- const getTemplateBindVariable = (init: t.Expression) =>
19
- t.isCallExpression(init) &&
20
- t.isMemberExpression(init.callee) &&
21
- t.isIdentifier(init.callee.object) &&
22
- t.isIdentifier(init.callee.property) &&
23
- init.callee.property.name === 'bind' &&
24
- (init.arguments.length === 0 ||
25
- (init.arguments.length === 1 &&
26
- t.isObjectExpression(init.arguments[0]) &&
27
- init.arguments[0].properties.length === 0))
28
- ? init.callee.object.name
29
- : null;
30
-
31
- // export const A = ...
32
- // A.parameters = { ... }; <===
33
- const isStoryAnnotation = (stmt: t.Statement, objectExports: Record<string, any>) =>
34
- t.isExpressionStatement(stmt) &&
35
- t.isAssignmentExpression(stmt.expression) &&
36
- t.isMemberExpression(stmt.expression.left) &&
37
- t.isIdentifier(stmt.expression.left.object) &&
38
- objectExports[stmt.expression.left.object.name];
39
-
40
- const getNewExport = (stmt: t.Statement, objectExports: Record<string, any>) => {
41
- if (
42
- t.isExportNamedDeclaration(stmt) &&
43
- t.isVariableDeclaration(stmt.declaration) &&
44
- stmt.declaration.declarations.length === 1
45
- ) {
46
- const decl = stmt.declaration.declarations[0];
47
- if (t.isVariableDeclarator(decl) && t.isIdentifier(decl.id)) {
48
- return objectExports[decl.id.name];
49
- }
50
- }
51
- return null;
52
- };
53
-
54
- // Remove render function when it matches the global render function in react
55
- // export default { component: Cat };
56
- // export const A = (args) => <Cat {...args} />;
57
- const isReactGlobalRenderFn = (csf: CsfFile, storyFn: t.Expression) => {
58
- if (
59
- csf._meta?.component &&
60
- t.isArrowFunctionExpression(storyFn) &&
61
- storyFn.params.length === 1 &&
62
- t.isJSXElement(storyFn.body)
63
- ) {
64
- const { openingElement } = storyFn.body;
65
- if (
66
- openingElement.selfClosing &&
67
- t.isJSXIdentifier(openingElement.name) &&
68
- openingElement.attributes.length === 1
69
- ) {
70
- const attr = openingElement.attributes[0];
71
- const param = storyFn.params[0];
72
- if (
73
- t.isJSXSpreadAttribute(attr) &&
74
- t.isIdentifier(attr.argument) &&
75
- t.isIdentifier(param) &&
76
- param.name === attr.argument.name &&
77
- csf._meta.component === openingElement.name.name
78
- ) {
79
- return true;
80
- }
81
- }
82
- }
83
- return false;
84
- };
85
-
86
- // A simple CSF story is a no-arg story without any extra annotations (params, args, etc.)
87
- const isSimpleCSFStory = (init: t.Expression, annotations: t.ObjectProperty[]) =>
88
- annotations.length === 0 && t.isArrowFunctionExpression(init) && init.params.length === 0;
89
-
90
- function removeUnusedTemplates(csf: CsfFile) {
91
- Object.entries(csf._templates).forEach(([template, templateExpression]) => {
92
- const references: NodePath[] = [];
93
- babel.traverse(csf._ast, {
94
- Identifier: (path) => {
95
- if (path.node.name === template) references.push(path);
96
- },
97
- });
98
- // if there is only one reference and this reference is the variable declaration initializing the template
99
- // then we are sure the template is unused
100
- if (references.length === 1) {
101
- const reference = references[0];
102
- if (
103
- reference.parentPath.isVariableDeclarator() &&
104
- reference.parentPath.node.init === templateExpression
105
- ) {
106
- reference.parentPath.remove();
107
- }
108
- }
109
- });
110
- }
111
-
112
- export default function transform(info: FileInfo, api: API, options: { parser?: string }) {
113
- const makeTitle = (userTitle?: string) => {
114
- return userTitle || 'FIXME';
115
- };
116
- const csf = loadCsf(info.source, { makeTitle });
117
-
118
- try {
119
- csf.parse();
120
- } catch (err) {
121
- logger.log(`Error ${err}, skipping`);
122
- return info.source;
123
- }
124
-
125
- // This allows for showing buildCodeFrameError messages
126
- // @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
127
- const file: BabelFile = new babel.File(
128
- { filename: info.path },
129
- { code: info.source, ast: csf._ast }
130
- );
131
-
132
- const importHelper = new StorybookImportHelper(file, info);
133
-
134
- const objectExports: Record<string, t.Statement> = {};
135
- Object.entries(csf._storyExports).forEach(([key, decl]) => {
136
- const annotations = Object.entries(csf._storyAnnotations[key]).map(([annotation, val]) => {
137
- return t.objectProperty(t.identifier(renameAnnotation(annotation)), val as t.Expression);
138
- });
139
-
140
- if (t.isVariableDeclarator(decl)) {
141
- const { init, id } = decl;
142
- // only replace arrow function expressions && template
143
- const template = getTemplateBindVariable(init);
144
- if (!t.isArrowFunctionExpression(init) && !template) return;
145
- // Do change the type of no-arg stories without annotations to StoryFn when applicable
146
- if (isSimpleCSFStory(init, annotations)) {
147
- objectExports[key] = t.exportNamedDeclaration(
148
- t.variableDeclaration('const', [
149
- t.variableDeclarator(importHelper.updateTypeTo(id, 'StoryFn'), init),
150
- ])
151
- );
152
- return;
153
- }
154
-
155
- let storyFn: t.Expression = template && t.identifier(template);
156
- if (!storyFn) {
157
- storyFn = init;
158
- }
159
-
160
- // Remove the render function when we can hoist the template
161
- // const Template = (args) => <Cat {...args} />;
162
- // export const A = Template.bind({});
163
- const renderAnnotation = isReactGlobalRenderFn(
164
- csf,
165
- template ? csf._templates[template] : storyFn
166
- )
167
- ? []
168
- : [t.objectProperty(t.identifier('render'), storyFn)];
169
-
170
- objectExports[key] = t.exportNamedDeclaration(
171
- t.variableDeclaration('const', [
172
- t.variableDeclarator(
173
- importHelper.updateTypeTo(id, 'StoryObj'),
174
- t.objectExpression([...renderAnnotation, ...annotations])
175
- ),
176
- ])
177
- );
178
- }
179
- });
180
-
181
- csf._ast.program.body = csf._ast.program.body.reduce((acc, stmt) => {
182
- const statement = stmt as t.Statement;
183
- // remove story annotations & template declarations
184
- if (isStoryAnnotation(statement, objectExports)) {
185
- return acc;
186
- }
187
-
188
- // replace story exports with new object exports
189
- const newExport = getNewExport(statement, objectExports);
190
- if (newExport) {
191
- acc.push(newExport);
192
- return acc;
193
- }
194
-
195
- // include unknown statements
196
- acc.push(statement);
197
- return acc;
198
- }, []);
199
-
200
- upgradeDeprecatedTypes(file);
201
- importHelper.removeDeprecatedStoryImport();
202
- removeUnusedTemplates(csf);
203
-
204
- let output = printCsf(csf).code;
205
-
206
- try {
207
- const prettierConfig = prettier.resolveConfig.sync('.', { editorconfig: true }) || {
208
- printWidth: 100,
209
- tabWidth: 2,
210
- bracketSpacing: true,
211
- trailingComma: 'es5',
212
- singleQuote: true,
213
- };
214
-
215
- output = prettier.format(output, {
216
- ...prettierConfig,
217
- // This will infer the parser from the filename.
218
- filepath: info.path,
219
- });
220
- } catch (e) {
221
- logger.log(`Failed applying prettier to ${info.path}.`);
222
- }
223
-
224
- return output;
225
- }
226
-
227
- class StorybookImportHelper {
228
- constructor(file: BabelFile, info: FileInfo) {
229
- this.sbImportDeclarations = this.getAllSbImportDeclarations(file);
230
- }
231
-
232
- private sbImportDeclarations: NodePath<t.ImportDeclaration>[];
233
-
234
- private getAllSbImportDeclarations = (file: BabelFile) => {
235
- const found: NodePath<t.ImportDeclaration>[] = [];
236
-
237
- file.path.traverse({
238
- ImportDeclaration: (path) => {
239
- const source = path.node.source.value;
240
- if (source.startsWith('@storybook/csf') || !source.startsWith('@storybook')) return;
241
- const isRendererImport = path.get('specifiers').some((specifier) => {
242
- if (specifier.isImportNamespaceSpecifier()) {
243
- // throw path.buildCodeFrameError(
244
- // `This codemod does not support namespace imports for a ${path.node.source.value} package.\n` +
245
- // 'Replace the namespace import with named imports and try again.'
246
- // );
247
- throw new Error(
248
- `This codemod does not support namespace imports for a ${path.node.source.value} package.\n` +
249
- 'Replace the namespace import with named imports and try again.'
250
- );
251
- }
252
- if (!specifier.isImportSpecifier()) return false;
253
- const imported = specifier.get('imported');
254
- if (!imported.isIdentifier()) return false;
255
-
256
- return [
257
- 'Story',
258
- 'StoryFn',
259
- 'StoryObj',
260
- 'Meta',
261
- 'ComponentStory',
262
- 'ComponentStoryFn',
263
- 'ComponentStoryObj',
264
- 'ComponentMeta',
265
- ].includes(imported.node.name);
266
- });
267
-
268
- if (isRendererImport) found.push(path);
269
- },
270
- });
271
- return found;
272
- };
273
-
274
- getOrAddImport = (type: string): string | undefined => {
275
- // prefer type import
276
- const sbImport =
277
- this.sbImportDeclarations.find((path) => path.node.importKind === 'type') ??
278
- this.sbImportDeclarations[0];
279
- if (sbImport == null) return undefined;
280
-
281
- const specifiers = sbImport.get('specifiers');
282
- const importSpecifier = specifiers.find((specifier) => {
283
- if (!specifier.isImportSpecifier()) return false;
284
- const imported = specifier.get('imported');
285
- if (!imported.isIdentifier()) return false;
286
- return imported.node.name === type;
287
- });
288
- if (importSpecifier) return importSpecifier.node.local.name;
289
- specifiers[0].insertBefore(t.importSpecifier(t.identifier(type), t.identifier(type)));
290
- return type;
291
- };
292
-
293
- removeDeprecatedStoryImport = () => {
294
- const specifiers = this.sbImportDeclarations.flatMap((it) => it.get('specifiers'));
295
- const storyImports = specifiers.filter((specifier) => {
296
- if (!specifier.isImportSpecifier()) return false;
297
- const imported = specifier.get('imported');
298
- if (!imported.isIdentifier()) return false;
299
- return imported.node.name === 'Story';
300
- });
301
- storyImports.forEach((path) => path.remove());
302
- };
303
-
304
- getAllLocalImports = () => {
305
- return this.sbImportDeclarations
306
- .flatMap((it) => it.get('specifiers'))
307
- .map((it) => it.node.local.name);
308
- };
309
-
310
- updateTypeTo = (id: t.LVal, type: string): t.LVal => {
311
- if (
312
- isIdentifier(id) &&
313
- isTSTypeAnnotation(id.typeAnnotation) &&
314
- isTSTypeReference(id.typeAnnotation.typeAnnotation) &&
315
- isIdentifier(id.typeAnnotation.typeAnnotation.typeName)
316
- ) {
317
- const { name } = id.typeAnnotation.typeAnnotation.typeName;
318
- if (this.getAllLocalImports().includes(name)) {
319
- const localTypeImport = this.getOrAddImport(type);
320
- return {
321
- ...id,
322
- typeAnnotation: t.tsTypeAnnotation(
323
- t.tsTypeReference(
324
- t.identifier(localTypeImport),
325
- id.typeAnnotation.typeAnnotation.typeParameters
326
- )
327
- ),
328
- };
329
- }
330
- }
331
- return id;
332
- };
333
- }
334
-
335
- export const parser = 'tsx';
@@ -1,97 +0,0 @@
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
- }