Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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: 4 additions & 2 deletions 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;
name: 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,7 +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 { loadCsf } from 'storybook/internal/csf-tools';

Expand All @@ -13,10 +12,8 @@ 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.entries(csf._storyDeclarationPath)
.map(([name, path]) => getCodeSnippet(path, name, csf._metaNode ?? null, component))
.filter(Boolean);

return recast.print(t.program(snippets)).code;
Expand Down Expand Up @@ -81,8 +78,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 Down Expand Up @@ -248,7 +252,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 +512,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
Loading