@storybook/codemod 0.0.0-pr-23611-sha-d83da132 → 0.0.0-pr-23853-sha-93b33e06

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/index.d.ts +3 -50
  2. package/dist/transforms/add-component-parameters.d.ts +22 -0
  3. package/dist/transforms/csf-hoist-story-annotations.d.ts +26 -0
  4. package/dist/transforms/move-builtin-addons.d.ts +3 -0
  5. package/dist/transforms/storiesof-to-csf.d.ts +24 -0
  6. package/dist/transforms/update-addon-info.d.ts +27 -0
  7. package/dist/transforms/update-organisation-name.d.ts +25 -0
  8. package/dist/transforms/upgrade-hierarchy-separators.d.ts +3 -0
  9. package/package.json +11 -4
  10. package/project.json +0 -6
  11. package/src/index.ts +0 -103
  12. package/src/lib/utils.test.js +0 -9
  13. package/src/lib/utils.ts +0 -29
  14. package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.input.js +0 -44
  15. package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.output.snapshot +0 -68
  16. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.input.js +0 -25
  17. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.output.snapshot +0 -27
  18. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.input.js +0 -25
  19. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.output.snapshot +0 -28
  20. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.input.js +0 -13
  21. package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.output.snapshot +0 -17
  22. package/src/transforms/__testfixtures__/mdx-to-csf/basic.input.mdx +0 -18
  23. package/src/transforms/__testfixtures__/mdx-to-csf/basic.output.snapshot +0 -40
  24. package/src/transforms/__testfixtures__/mdx-to-csf/component-id.input.mdx +0 -6
  25. package/src/transforms/__testfixtures__/mdx-to-csf/component-id.output.snapshot +0 -17
  26. package/src/transforms/__testfixtures__/mdx-to-csf/decorators.input.mdx +0 -8
  27. package/src/transforms/__testfixtures__/mdx-to-csf/decorators.output.snapshot +0 -18
  28. package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.input.mdx +0 -19
  29. package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.output.snapshot +0 -39
  30. package/src/transforms/__testfixtures__/mdx-to-csf/parameters.input.mdx +0 -14
  31. package/src/transforms/__testfixtures__/mdx-to-csf/parameters.output.snapshot +0 -23
  32. package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.input.mdx +0 -3
  33. package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.output.snapshot +0 -11
  34. package/src/transforms/__testfixtures__/mdx-to-csf/story-function.input.mdx +0 -10
  35. package/src/transforms/__testfixtures__/mdx-to-csf/story-function.output.snapshot +0 -18
  36. package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.input.mdx +0 -18
  37. package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.output.snapshot +0 -32
  38. package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.input.mdx +0 -22
  39. package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.output.snapshot +0 -34
  40. package/src/transforms/__testfixtures__/move-builtin-addons/default.input.js +0 -2
  41. package/src/transforms/__testfixtures__/move-builtin-addons/default.output.snapshot +0 -8
  42. package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.input.js +0 -3
  43. package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.output.snapshot +0 -7
  44. package/src/transforms/__testfixtures__/storiesof-to-csf/basic.input.js +0 -18
  45. package/src/transforms/__testfixtures__/storiesof-to-csf/basic.output.snapshot +0 -45
  46. package/src/transforms/__testfixtures__/storiesof-to-csf/collision.input.js +0 -11
  47. package/src/transforms/__testfixtures__/storiesof-to-csf/collision.output.snapshot +0 -38
  48. package/src/transforms/__testfixtures__/storiesof-to-csf/const.input.js +0 -1
  49. package/src/transforms/__testfixtures__/storiesof-to-csf/const.output.snapshot +0 -13
  50. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +0 -9
  51. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.output.snapshot +0 -18
  52. package/src/transforms/__testfixtures__/storiesof-to-csf/default.input.js +0 -7
  53. package/src/transforms/__testfixtures__/storiesof-to-csf/default.output.snapshot +0 -17
  54. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.input.js +0 -1
  55. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.js +0 -5
  56. package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.snapshot +0 -9
  57. package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.input.js +0 -11
  58. package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.output.snapshot +0 -23
  59. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +0 -12
  60. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.output.snapshot +0 -23
  61. package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.input.js +0 -14
  62. package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.output.snapshot +0 -26
  63. package/src/transforms/__testfixtures__/storiesof-to-csf/exports.input.js +0 -2
  64. package/src/transforms/__testfixtures__/storiesof-to-csf/exports.output.snapshot +0 -16
  65. package/src/transforms/__testfixtures__/storiesof-to-csf/module.input.js +0 -12
  66. package/src/transforms/__testfixtures__/storiesof-to-csf/module.output.snapshot +0 -16
  67. package/src/transforms/__testfixtures__/storiesof-to-csf/multi.input.js +0 -14
  68. package/src/transforms/__testfixtures__/storiesof-to-csf/multi.output.snapshot +0 -39
  69. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.input.js +0 -8
  70. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.output.snapshot +0 -20
  71. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.input.js +0 -10
  72. package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.output.snapshot +0 -23
  73. package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.input.js +0 -11
  74. package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.output.snapshot +0 -23
  75. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +0 -11
  76. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.output.snapshot +0 -29
  77. package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.input.js +0 -14
  78. package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.output.snapshot +0 -32
  79. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +0 -184
  80. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +0 -184
  81. package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.input.js +0 -19
  82. package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.output.snapshot +0 -23
  83. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.input.js +0 -3
  84. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.output.snapshot +0 -7
  85. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.input.js +0 -5
  86. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.output.snapshot +0 -9
  87. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.input.js +0 -8
  88. package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.output.snapshot +0 -12
  89. package/src/transforms/__tests__/csf-2-to-3.test.ts +0 -439
  90. package/src/transforms/__tests__/mdx-to-csf.test.ts +0 -628
  91. package/src/transforms/__tests__/transforms.tests.js +0 -32
  92. package/src/transforms/__tests__/upgrade-deprecated-types.test.ts +0 -170
  93. package/src/transforms/add-component-parameters.js +0 -62
  94. package/src/transforms/csf-2-to-3.ts +0 -335
  95. package/src/transforms/csf-hoist-story-annotations.js +0 -97
  96. package/src/transforms/mdx-to-csf.ts +0 -340
  97. package/src/transforms/move-builtin-addons.js +0 -32
  98. package/src/transforms/storiesof-to-csf.js +0 -277
  99. package/src/transforms/update-addon-info.js +0 -114
  100. package/src/transforms/update-organisation-name.js +0 -71
  101. package/src/transforms/upgrade-deprecated-types.ts +0 -142
  102. package/src/transforms/upgrade-hierarchy-separators.js +0 -39
  103. 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
- }