@storybook/codemod 7.3.2 → 7.4.0-alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +11 -4
- package/project.json +0 -6
- package/src/index.ts +0 -103
- package/src/lib/utils.test.js +0 -9
- package/src/lib/utils.ts +0 -29
- package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.input.js +0 -44
- package/src/transforms/__testfixtures__/add-component-parameters/add-component-parameters.output.snapshot +0 -68
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.input.js +0 -25
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/basic.output.snapshot +0 -27
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.input.js +0 -25
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/overrides.output.snapshot +0 -28
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.input.js +0 -13
- package/src/transforms/__testfixtures__/csf-hoist-story-annotations/variable.output.snapshot +0 -17
- package/src/transforms/__testfixtures__/mdx-to-csf/basic.input.mdx +0 -18
- package/src/transforms/__testfixtures__/mdx-to-csf/basic.output.snapshot +0 -40
- package/src/transforms/__testfixtures__/mdx-to-csf/component-id.input.mdx +0 -6
- package/src/transforms/__testfixtures__/mdx-to-csf/component-id.output.snapshot +0 -17
- package/src/transforms/__testfixtures__/mdx-to-csf/decorators.input.mdx +0 -8
- package/src/transforms/__testfixtures__/mdx-to-csf/decorators.output.snapshot +0 -18
- package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.input.mdx +0 -19
- package/src/transforms/__testfixtures__/mdx-to-csf/exclude-stories.output.snapshot +0 -39
- package/src/transforms/__testfixtures__/mdx-to-csf/parameters.input.mdx +0 -14
- package/src/transforms/__testfixtures__/mdx-to-csf/parameters.output.snapshot +0 -23
- package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.input.mdx +0 -3
- package/src/transforms/__testfixtures__/mdx-to-csf/plaintext.output.snapshot +0 -11
- package/src/transforms/__testfixtures__/mdx-to-csf/story-function.input.mdx +0 -10
- package/src/transforms/__testfixtures__/mdx-to-csf/story-function.output.snapshot +0 -18
- package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.input.mdx +0 -18
- package/src/transforms/__testfixtures__/mdx-to-csf/story-parameters.output.snapshot +0 -32
- package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.input.mdx +0 -22
- package/src/transforms/__testfixtures__/mdx-to-csf/story-refs.output.snapshot +0 -34
- package/src/transforms/__testfixtures__/move-builtin-addons/default.input.js +0 -2
- package/src/transforms/__testfixtures__/move-builtin-addons/default.output.snapshot +0 -8
- package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.input.js +0 -3
- package/src/transforms/__testfixtures__/move-builtin-addons/with-no-change.output.snapshot +0 -7
- package/src/transforms/__testfixtures__/storiesof-to-csf/basic.input.js +0 -18
- package/src/transforms/__testfixtures__/storiesof-to-csf/basic.output.snapshot +0 -45
- package/src/transforms/__testfixtures__/storiesof-to-csf/collision.input.js +0 -11
- package/src/transforms/__testfixtures__/storiesof-to-csf/collision.output.snapshot +0 -38
- package/src/transforms/__testfixtures__/storiesof-to-csf/const.input.js +0 -1
- package/src/transforms/__testfixtures__/storiesof-to-csf/const.output.snapshot +0 -13
- package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +0 -9
- package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.output.snapshot +0 -18
- package/src/transforms/__testfixtures__/storiesof-to-csf/default.input.js +0 -7
- package/src/transforms/__testfixtures__/storiesof-to-csf/default.output.snapshot +0 -17
- package/src/transforms/__testfixtures__/storiesof-to-csf/digit.input.js +0 -1
- package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.js +0 -5
- package/src/transforms/__testfixtures__/storiesof-to-csf/digit.output.snapshot +0 -9
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.input.js +0 -11
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-destructuring.output.snapshot +0 -23
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +0 -12
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.output.snapshot +0 -23
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.input.js +0 -14
- package/src/transforms/__testfixtures__/storiesof-to-csf/export-names.output.snapshot +0 -26
- package/src/transforms/__testfixtures__/storiesof-to-csf/exports.input.js +0 -2
- package/src/transforms/__testfixtures__/storiesof-to-csf/exports.output.snapshot +0 -16
- package/src/transforms/__testfixtures__/storiesof-to-csf/module.input.js +0 -12
- package/src/transforms/__testfixtures__/storiesof-to-csf/module.output.snapshot +0 -16
- package/src/transforms/__testfixtures__/storiesof-to-csf/multi.input.js +0 -14
- package/src/transforms/__testfixtures__/storiesof-to-csf/multi.output.snapshot +0 -39
- package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.input.js +0 -8
- package/src/transforms/__testfixtures__/storiesof-to-csf/parameters-as-var.output.snapshot +0 -20
- package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.input.js +0 -10
- package/src/transforms/__testfixtures__/storiesof-to-csf/parameters.output.snapshot +0 -23
- package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.input.js +0 -11
- package/src/transforms/__testfixtures__/storiesof-to-csf/storiesof-var.output.snapshot +0 -23
- package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +0 -11
- package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.output.snapshot +0 -29
- package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.input.js +0 -14
- package/src/transforms/__testfixtures__/storiesof-to-csf/story-parameters.output.snapshot +0 -32
- package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +0 -184
- package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +0 -184
- package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.input.js +0 -19
- package/src/transforms/__testfixtures__/update-organisation-name/update-organisation-name.output.snapshot +0 -23
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.input.js +0 -3
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/csf.output.snapshot +0 -7
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.input.js +0 -5
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/dynamic-storiesof.output.snapshot +0 -9
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.input.js +0 -8
- package/src/transforms/__testfixtures__/upgrade-hierarchy-separators/storiesof.output.snapshot +0 -12
- package/src/transforms/__tests__/csf-2-to-3.test.ts +0 -439
- package/src/transforms/__tests__/mdx-to-csf.test.ts +0 -628
- package/src/transforms/__tests__/transforms.tests.js +0 -32
- package/src/transforms/__tests__/upgrade-deprecated-types.test.ts +0 -170
- package/src/transforms/add-component-parameters.js +0 -62
- package/src/transforms/csf-2-to-3.ts +0 -335
- package/src/transforms/csf-hoist-story-annotations.js +0 -97
- package/src/transforms/mdx-to-csf.ts +0 -340
- package/src/transforms/move-builtin-addons.js +0 -32
- package/src/transforms/storiesof-to-csf.js +0 -277
- package/src/transforms/update-addon-info.js +0 -114
- package/src/transforms/update-organisation-name.js +0 -71
- package/src/transforms/upgrade-deprecated-types.ts +0 -142
- package/src/transforms/upgrade-hierarchy-separators.js +0 -39
- 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
|
-
}
|