Skip to content

Commit

Permalink
Merge pull request #20618 from storybookjs/upgrade-deprecated-types
Browse files Browse the repository at this point in the history
CLI: Add codemod to upgrade deprecated types
  • Loading branch information
shilman authored Jan 16, 2023
2 parents ea01187 + 12d3dc7 commit f4d8aa4
Show file tree
Hide file tree
Showing 5 changed files with 342 additions and 13 deletions.
2 changes: 2 additions & 0 deletions code/lib/codemod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"./dist/transforms/storiesof-to-csf.js": "./dist/transforms/storiesof-to-csf.js",
"./dist/transforms/update-addon-info.js": "./dist/transforms/update-addon-info.js",
"./dist/transforms/update-organisation-name.js": "./dist/transforms/update-organisation-name.js",
"./dist/transforms/upgrade-deprecated-types.js": "./dist/transforms/upgrade-deprecated-types.js",
"./dist/transforms/upgrade-hierarchy-separators.js": "./dist/transforms/upgrade-hierarchy-separators.js",
"./package.json": "./package.json"
},
Expand Down Expand Up @@ -81,6 +82,7 @@
"./src/transforms/storiesof-to-csf.js",
"./src/transforms/update-addon-info.js",
"./src/transforms/update-organisation-name.js",
"./src/transforms/upgrade-deprecated-types.ts",
"./src/transforms/upgrade-hierarchy-separators.js"
]
},
Expand Down
17 changes: 14 additions & 3 deletions code/lib/codemod/src/transforms/__tests__/csf-2-to-3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ describe('csf-2-to-3', () => {
`)
).toThrowErrorMatchingInlineSnapshot(`
This codemod does not support namespace imports for a @storybook/react package.
Replace the namespace import with named imports and try again.
Replace the namespace import with named imports and try again.
> 1 | import * as SB from '@storybook/react';
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 | import { CatProps } from './Cat';
Expand Down Expand Up @@ -286,7 +286,7 @@ describe('csf-2-to-3', () => {
it('should replace function exports with objects and update type', () => {
expect(
tsTransform(dedent`
import { Story, StoryFn, ComponentStory } from '@storybook/react';
import { Story, StoryFn, ComponentStory, ComponentStoryObj } from '@storybook/react';
// some extra whitespace to test
Expand Down Expand Up @@ -315,9 +315,14 @@ describe('csf-2-to-3', () => {
name: "Fluffy"
};
export const G: ComponentStoryObj<typeof Cat> = {
args: {
name: 'Fluffy',
},
};
`)
).toMatchInlineSnapshot(`
import { StoryObj, StoryFn, ComponentStory } from '@storybook/react';
import { StoryObj, StoryFn } from '@storybook/react';
// some extra whitespace to test
Expand Down Expand Up @@ -351,6 +356,12 @@ describe('csf-2-to-3', () => {
name: 'Fluffy',
},
};
export const G: StoryObj<typeof Cat> = {
args: {
name: 'Fluffy',
},
};
`);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { describe, expect, it } from '@jest/globals';
import { dedent } from 'ts-dedent';
import type { API } from 'jscodeshift';
import ansiRegex from 'ansi-regex';
import _transform from '../upgrade-deprecated-types';

expect.addSnapshotSerializer({
print: (val: any) => val,
test: () => true,
});

const tsTransform = (source: string) =>
_transform({ source, path: 'Component.stories.ts' }, {} as API, { parser: 'tsx' }).trim();

describe('upgrade-deprecated-types', () => {
describe('typescript', () => {
it('upgrade regular imports', () => {
expect(
tsTransform(dedent`
import { Story, ComponentMeta, Meta, ComponentStory, ComponentStoryObj, ComponentStoryFn } from '@storybook/react';
import { Cat, CatProps } from './Cat';
const meta = { title: 'Cat', component: Cat } satisfies ComponentMeta<typeof Cat>
const meta2: Meta<CatProps> = { title: 'Cat', component: Cat };
export default meta;
export const A: ComponentStory<typeof Cat> = (args) => <Cat {...args} />;
export const B: any = (args) => <Button {...args} />;
export const C: ComponentStoryFn<typeof Cat> = (args) => <Cat {...args} />;
export const D: ComponentStoryObj<typeof Cat> = {
args: {
name: 'Fluffy',
},
};
export const E: Story<CatProps> = (args) => <Cat {...args} />;
`)
).toMatchInlineSnapshot(`
import { StoryFn, Meta, StoryObj } from '@storybook/react';
import { Cat, CatProps } from './Cat';
const meta = { title: 'Cat', component: Cat } satisfies Meta<typeof Cat>;
const meta2: Meta<CatProps> = { title: 'Cat', component: Cat };
export default meta;
export const A: StoryFn<typeof Cat> = (args) => <Cat {...args} />;
export const B: any = (args) => <Button {...args} />;
export const C: StoryFn<typeof Cat> = (args) => <Cat {...args} />;
export const D: StoryObj<typeof Cat> = {
args: {
name: 'Fluffy',
},
};
export const E: StoryFn<CatProps> = (args) => <Cat {...args} />;
`);
});

it('upgrade imports with local names', () => {
expect(
tsTransform(dedent`
import { Story as Story_, ComponentMeta as ComponentMeta_, ComponentStory as Story__, ComponentStoryObj as ComponentStoryObj_, ComponentStoryFn as StoryFn_ } from '@storybook/react';
import { Cat } from './Cat';
const meta = { title: 'Cat', component: Cat } satisfies ComponentMeta_<typeof Cat>
const meta2: ComponentMeta_<typeof Cat> = { title: 'Cat', component: Cat };
export default meta;
export const A: Story__<typeof Cat> = (args) => <Cat {...args} />;
export const B: any = (args) => <Button {...args} />;
export const C: StoryFn_<typeof Cat> = (args) => <Cat {...args} />;
export const D: ComponentStoryObj_<typeof Cat> = {
args: {
name: 'Fluffy',
},
};
export const E: Story_<CatProps> = (args) => <Cat {...args} />;
`)
).toMatchInlineSnapshot(`
import {
StoryFn as Story_,
Meta as ComponentMeta_,
StoryObj as ComponentStoryObj_,
} from '@storybook/react';
import { Cat } from './Cat';
const meta = { title: 'Cat', component: Cat } satisfies ComponentMeta_<typeof Cat>;
const meta2: ComponentMeta_<typeof Cat> = { title: 'Cat', component: Cat };
export default meta;
export const A: Story__<typeof Cat> = (args) => <Cat {...args} />;
export const B: any = (args) => <Button {...args} />;
export const C: StoryFn_<typeof Cat> = (args) => <Cat {...args} />;
export const D: ComponentStoryObj_<typeof Cat> = {
args: {
name: 'Fluffy',
},
};
export const E: Story_<CatProps> = (args) => <Cat {...args} />;
`);
});

it('upgrade imports with conflicting local names', () => {
expect.addSnapshotSerializer({
serialize: (value) => value.replace(ansiRegex(), ''),
test: () => true,
});

expect(() =>
tsTransform(dedent`
import { ComponentMeta as Meta, ComponentStory as StoryFn } from '@storybook/react';
import { Cat } from './Cat';
const meta = { title: 'Cat', component: Cat } satisfies Meta<typeof Cat>
export default meta;
export const A: StoryFn<typeof Cat> = (args) => <Cat {...args} />;
`)
).toThrowErrorMatchingInlineSnapshot(`
This codemod does not support local imports that are called the same as a storybook import.
Rename this local import and try again.
> 1 | import { ComponentMeta as Meta, ComponentStory as StoryFn } from '@storybook/react';
| ^^^^^^^^^^^^^^^^^^^^^
2 | import { Cat } from './Cat';
3 |
4 | const meta = { title: 'Cat', component: Cat } satisfies Meta<typeof Cat>
`);
});

it('upgrade namespaces', () => {
expect(
tsTransform(dedent`
import * as SB from '@storybook/react';
import { Cat, CatProps } from './Cat';
const meta = { title: 'Cat', component: Cat } satisfies SB.ComponentMeta<typeof Cat>;
const meta2: SB.ComponentMeta<typeof Cat> = { title: 'Cat', component: Cat };
export default meta;
export const A: SB.ComponentStory<typeof Cat> = (args) => <Cat {...args} />;
export const B: any = (args) => <Button {...args} />;
export const C: SB.ComponentStoryFn<typeof Cat> = (args) => <Cat {...args} />;
export const D: SB.ComponentStoryObj<typeof Cat> = {
args: {
name: 'Fluffy',
},
};
export const E: SB.Story<CatProps> = (args) => <Cat {...args} />;
`)
).toMatchInlineSnapshot(`
import * as SB from '@storybook/react';
import { Cat, CatProps } from './Cat';
const meta = { title: 'Cat', component: Cat } satisfies SB.Meta<typeof Cat>;
const meta2: SB.Meta<typeof Cat> = { title: 'Cat', component: Cat };
export default meta;
export const A: SB.StoryFn<typeof Cat> = (args) => <Cat {...args} />;
export const B: any = (args) => <Button {...args} />;
export const C: SB.StoryFn<typeof Cat> = (args) => <Cat {...args} />;
export const D: SB.StoryObj<typeof Cat> = {
args: {
name: 'Fluffy',
},
};
export const E: SB.StoryFn<CatProps> = (args) => <Cat {...args} />;
`);
});
});
});
24 changes: 14 additions & 10 deletions code/lib/codemod/src/transforms/csf-2-to-3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { API, FileInfo } from 'jscodeshift';
import type { BabelFile, NodePath } from '@babel/core';
import * as babel from '@babel/core';
import * as recast from 'recast';
import { upgradeDeprecatedTypes } from './upgrade-deprecated-types';

const logger = console;

Expand Down Expand Up @@ -106,7 +107,14 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
return info.source;
}

const importHelper = new StorybookImportHelper(csf, info);
// This allows for showing buildCodeFrameError messages
// @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
const file: BabelFile = new babel.File(
{ filename: info.path },
{ code: info.source, ast: csf._ast }
);

const importHelper = new StorybookImportHelper(file, info);

const objectExports: Record<string, t.Statement> = {};
Object.entries(csf._storyExports).forEach(([key, decl]) => {
Expand Down Expand Up @@ -172,6 +180,8 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
return acc;
}, []);

upgradeDeprecatedTypes(file);

let output = recast.print(csf._ast, {}).code;

try {
Expand All @@ -196,13 +206,7 @@ export default function transform(info: FileInfo, api: API, options: { parser?:
}

class StorybookImportHelper {
constructor(csf: CsfFile, info: FileInfo) {
// This allows for showing buildCodeFrameError messages
// @ts-expect-error File is not yet exposed, see https://github.com/babel/babel/issues/11350#issuecomment-644118606
const file: BabelFile = new babel.File(
{ filename: info.path },
{ code: info.source, ast: csf._ast }
);
constructor(file: BabelFile, info: FileInfo) {
this.sbImportDeclarations = this.getAllSbImportDeclarations(file);
}

Expand All @@ -218,8 +222,8 @@ class StorybookImportHelper {
const isRendererImport = path.get('specifiers').some((specifier) => {
if (specifier.isImportNamespaceSpecifier()) {
throw path.buildCodeFrameError(
`This codemod does not support namespace imports for a ${path.node.source.value} package.
Replace the namespace import with named imports and try again.`
`This codemod does not support namespace imports for a ${path.node.source.value} package.\n` +
'Replace the namespace import with named imports and try again.'
);
}
if (!specifier.isImportSpecifier()) return false;
Expand Down
Loading

0 comments on commit f4d8aa4

Please sign in to comment.