Skip to content
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ code/core/report
node_modules/.svelte2tsx-language-server-files

*storybook.log

.junie
54 changes: 36 additions & 18 deletions code/core/src/csf-tools/CsfFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface BabelFile {
opts: any;
hub: any;
metadata: object;
path: any;
path: NodePath<t.Program>;
scope: any;
inputMap: object | null;
code: string;
Expand Down Expand Up @@ -297,10 +297,15 @@ export class CsfFile {

_storyExports: Record<string, t.VariableDeclarator | t.FunctionDeclaration> = {};

_storyDeclarationPath: Record<string, NodePath<t.VariableDeclarator | t.FunctionDeclaration>> =
{};

_storyPaths: Record<string, NodePath<t.ExportNamedDeclaration>> = {};

_metaStatement: t.Statement | undefined;

_metaStatementPath: NodePath<t.Statement> | undefined;

_metaNode: t.ObjectExpression | undefined;

_metaPath: NodePath<t.ExportDefaultDeclaration> | undefined;
Expand Down Expand Up @@ -476,19 +481,24 @@ export class CsfFile {
// export default meta;
const variableName = (node.declaration as t.Identifier).name;
self._metaVariableName = variableName;
const isVariableDeclarator = (declaration: t.VariableDeclarator) =>
const isMetaVariable = (declaration: t.VariableDeclarator) =>
t.isIdentifier(declaration.id) && declaration.id.name === variableName;

self._metaStatement = self._ast.program.body.find(
(topLevelNode) =>
t.isVariableDeclaration(topLevelNode) &&
topLevelNode.declarations.find(isVariableDeclarator)
);
self._metaStatementPath = self._file.path
.get('body')
.find(
(path) =>
path.isVariableDeclaration() && path.node.declarations.some(isMetaVariable)
);

self._metaStatement = self._metaStatementPath?.node;

decl = ((self?._metaStatement as t.VariableDeclaration)?.declarations || []).find(
isVariableDeclarator
isMetaVariable
)?.init;
} else {
self._metaStatement = node;
self._metaStatementPath = path;
decl = node.declaration;
}

Expand Down Expand Up @@ -529,23 +539,28 @@ export class CsfFile {
ExportNamedDeclaration: {
enter(path) {
const { node, parent } = path;
const declaration = path.get('declaration');
let declarations;
if (t.isVariableDeclaration(node.declaration)) {
declarations = node.declaration.declarations.filter((d) => t.isVariableDeclarator(d));
} else if (t.isFunctionDeclaration(node.declaration)) {
declarations = [node.declaration];
if (declaration.isVariableDeclaration()) {
declarations = declaration.get('declarations').filter((d) => d.isVariableDeclarator());
} else if (declaration.isFunctionDeclaration()) {
declarations = [declaration];
}
if (declarations) {
// export const X = ...;
declarations.forEach((decl: t.VariableDeclarator | t.FunctionDeclaration) => {
if (t.isIdentifier(decl.id)) {
declarations.forEach((declPath) => {
const decl = declPath.node;
const id = declPath.node.id;

if (t.isIdentifier(id)) {
let storyIsFactory = false;
const { name: exportName } = decl.id;
if (exportName === '__namedExportsOrder' && t.isVariableDeclarator(decl)) {
self._namedExportsOrder = parseExportsOrder(decl.init as t.Expression);
const { name: exportName } = id;
if (exportName === '__namedExportsOrder' && declPath.isVariableDeclarator()) {
self._namedExportsOrder = parseExportsOrder(declPath.node.init as t.Expression);
return;
}
self._storyExports[exportName] = decl;
self._storyDeclarationPath[exportName] = declPath;
self._storyPaths[exportName] = path;
self._storyStatements[exportName] = node;
let name = storyNameFromExport(exportName);
Expand Down Expand Up @@ -1036,7 +1051,10 @@ export const babelParseFile = ({
filename?: string;
ast?: t.File;
}): BabelFile => {
return new BabelFileClass({ filename }, { code, ast: ast ?? babelParse(code) });
return new BabelFileClass(
{ filename, highlightCode: false },
{ code, ast: ast ?? babelParse(code) }
);
};

export const loadCsf = (code: string, options: CsfOptions) => {
Expand Down
6 changes: 2 additions & 4 deletions code/core/src/csf-tools/enrichCsf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ expect.addSnapshotSerializer({
const enrich = async (code: string, originalCode: string, options?: EnrichCsfOptions) => {
// we don't actually care about the title

const csf = loadCsf(code, {
makeTitle: (userTitle) => userTitle || 'default',
}).parse();
const csf = loadCsf(code, { makeTitle: (userTitle) => userTitle ?? 'Unknown' }).parse();
const csfSource = loadCsf(originalCode, {
makeTitle: (userTitle) => userTitle || 'default',
makeTitle: (userTitle) => userTitle ?? 'Unknown',
}).parse();
await enrichCsf(csf, csfSource, options);
return formatCsf(csf);
Expand Down
4 changes: 3 additions & 1 deletion code/core/src/types/modules/core-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,12 +347,14 @@ export type TagsOptions = Record<Tag, Partial<TagOptions>>;

export interface ComponentManifest {
id: string;
path: string;
name: string;
description?: string;
import?: string;
summary?: string;
examples: { name: string; snippet: string }[];
examples: { name: string; snippet?: string; error?: { message: string } }[];
jsDocTags: Record<string, string[]>;
error?: { message: string };
}

export interface ComponentsManifest {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { expect, test } from 'vitest';

import { recast } from 'storybook/internal/babel';
import type { NodePath } from 'storybook/internal/babel';
import { types as t } from 'storybook/internal/babel';
import { recast, types as t } from 'storybook/internal/babel';
import { loadCsf } from 'storybook/internal/csf-tools';

import { dedent } from 'ts-dedent';
Expand All @@ -11,12 +9,9 @@ import { getCodeSnippet } from './generateCodeSnippet';

function generateExample(code: string) {
const csf = loadCsf(code, { makeTitle: (userTitle?: string) => userTitle ?? 'title' }).parse();
const component = csf._meta?.component ?? 'Unknown';

const snippets = Object.values(csf._storyPaths)
.map((path: NodePath<t.ExportNamedDeclaration>) =>
getCodeSnippet(path, csf._metaNode ?? null, component)
)
const snippets = Object.keys(csf._storyExports)
.map((name) => getCodeSnippet(csf, name, csf._meta?.component ?? 'ComponentTitle'))
.filter(Boolean);

return recast.print(t.program(snippets)).code;
Expand Down Expand Up @@ -81,8 +76,15 @@ test('Edge case identifier we can not find', () => {
const input = withCSF3(`
export const Default = someImportOrWhatever;
`);
expect(generateExample(input)).toMatchInlineSnapshot(
`"const Default = () => <Button>Click me</Button>;"`
expect(() => generateExample(input)).toThrowErrorMatchingInlineSnapshot(
`
[SyntaxError: Expected story to be csf factory, function or an object expression
11 |
12 |
> 13 | export const Default = someImportOrWhatever;
| ^^^^^^^^^^^^^^^^^^^^
14 | ]
`
);
});

Expand All @@ -95,6 +97,15 @@ test('Default- CSF4', () => {
);
});

test('StoryWithoutArguments - CSF4', () => {
const input = withCSF4(`
export const StoryWithoutArguments = meta.story();
`);
expect(generateExample(input)).toMatchInlineSnapshot(
`"const StoryWithoutArguments = () => <Button>Click me</Button>;"`
);
});

test('Replace children', () => {
const input = withCSF3(dedent`
export const WithEmoji: Story = {
Expand Down Expand Up @@ -205,6 +216,28 @@ test('CustomRenderWithOverideArgs only', async () => {
);
});

test('Meta level render', async () => {
const input = dedent`
import type { Meta } from '@storybook/react';
import { Button } from '@design-system/button';

const meta: Meta<typeof Button> = {
render: (args) => <Button {...args} override="overide" />,
args: {
children: 'Click me'
}
};
export default meta;

export const CustomRenderWithOverideArgs = {
args: { foo: 'bar', override: 'value' }
};
`;
expect(generateExample(input)).toMatchInlineSnapshot(
`"const CustomRenderWithOverideArgs = () => <Button foo="bar" override="overide">Click me</Button>;"`
);
});

test('CustomRenderWithNoArgs only', async () => {
const input = withCSF3(
`export const CustomRenderWithNoArgs = {
Expand Down Expand Up @@ -248,7 +281,11 @@ test('CustomRenderBlockBody only', async () => {
};`
);
expect(generateExample(input)).toMatchInlineSnapshot(
`"const CustomRenderBlockBody = (args) => { return <Button {...args}>Render</Button> };"`
`
"const CustomRenderBlockBody = () => {
return <Button foo="bar">Render</Button>;
};"
`
);
});

Expand Down Expand Up @@ -504,8 +541,29 @@ test('top level args injection and spreading in different places', async () => {
`);
expect(generateExample(input)).toMatchInlineSnapshot(`
"const MultipleSpreads = () => <div count={0}>
<Button disabled={false} count={0} empty="" />
<Button disabled={false} count={0} empty="" />
<Button disabled={false} count={0} empty="">Click me</Button>
<Button disabled={false} count={0} empty="">Click me</Button>
</div>;"
`);
});

test('allow top level export functions', async () => {
const input = withCSF3(dedent`
export function Usage(args) {
return (
<div style={{ padding: 40 }}>
<Button {...args}></Button>
</div>
);
}
`);
expect(generateExample(input)).toMatchInlineSnapshot(`
"function Usage() {
return (
<div style={{ padding: 40 }}>
<Button>Click me</Button>
</div>
);
}"
`);
});
Loading