Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: Add codemod to upgrade deprecated types #20618

Merged
merged 1 commit into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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