diff --git a/lib/rules/story-exports.ts b/lib/rules/story-exports.ts index 1b27eb6..afa7e6c 100644 --- a/lib/rules/story-exports.ts +++ b/lib/rules/story-exports.ts @@ -3,12 +3,14 @@ * @author Yann Braga */ -import { isExportStory } from '@storybook/csf' - import { createStorybookRule } from '../utils/create-storybook-rule' import { CategoryId } from '../utils/constants' -import { getDescriptor, getMetaObjectExpression } from '../utils' -import { isIdentifier, isVariableDeclaration } from '../utils/ast' +import { + getAllNamedExports, + getDescriptor, + getMetaObjectExpression, + isValidStoryExport, +} from '../utils' //------------------------------------------------------------------------------ // Rule Definition @@ -39,9 +41,6 @@ export = createStorybookRule({ // Helpers //---------------------------------------------------------------------- - const isValidStoryExport = (node) => - isExportStory(node.name, nonStoryExportsConfig) && node.name !== '__namedExportsOrder' - //---------------------------------------------------------------------- // Public //---------------------------------------------------------------------- @@ -67,23 +66,16 @@ export = createStorybookRule({ } }, ExportNamedDeclaration: function (node) { - // if there are specifiers, node.declaration should be null - if (!node.declaration) return - - const decl = node.declaration - if (isVariableDeclaration(decl)) { - const { id } = decl.declarations[0] - if (isIdentifier(id)) { - namedExports.push(id) - } - } + namedExports.push(...getAllNamedExports(node)) }, 'Program:exit': function (node) { if (hasStoriesOfImport || !meta) { return } - const storyExports = namedExports.filter(isValidStoryExport) + const storyExports = namedExports.filter((exp) => + isValidStoryExport(exp, nonStoryExportsConfig) + ) if (storyExports.length) { return diff --git a/lib/utils/index.ts b/lib/utils/index.ts index 795eb1b..6b82659 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -1,6 +1,13 @@ -import { ExportDefaultDeclaration, Node } from '@typescript-eslint/types/dist/ast-spec' +import { isExportStory } from '@storybook/csf' +import { ExportDefaultDeclaration } from '@typescript-eslint/types/dist/ast-spec' import { findVariable } from '@typescript-eslint/experimental-utils/dist/ast-utils' -import { isIdentifier, isObjectExpression, isTSAsExpression, isVariableDeclarator } from './ast' +import { + isIdentifier, + isObjectExpression, + isTSAsExpression, + isVariableDeclaration, + isVariableDeclarator, +} from './ast' export const docsUrl = (ruleName: any) => `https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/${ruleName}.md` @@ -50,3 +57,29 @@ export const getDescriptor = (metaDeclaration, propertyName) => { throw new Error(`Unexpected descriptor: ${type}`) } } + +export const isValidStoryExport = (node, nonStoryExportsConfig) => + isExportStory(node.name, nonStoryExportsConfig) && node.name !== '__namedExportsOrder' + +export const getAllNamedExports = (node) => { + // e.g. export { MyStory } + if (!node.declaration && node.specifiers) { + return node.specifiers.reduce((acc, specifier) => { + if (isIdentifier(specifier.exported)) { + acc.push(specifier.exported) + } + return acc + }, []) + } + + const decl = node.declaration + if (isVariableDeclaration(decl)) { + const { id } = decl.declarations[0] + // e.g. export const MyStory + if (isIdentifier(id)) { + return [id] + } + } + + return [] +} diff --git a/tests/lib/rules/story-exports.test.ts b/tests/lib/rules/story-exports.test.ts index 084a22f..dc33f4d 100644 --- a/tests/lib/rules/story-exports.test.ts +++ b/tests/lib/rules/story-exports.test.ts @@ -30,6 +30,22 @@ ruleTester.run('story-exports', rule, { import { storiesOf } from '@storybook/react' storiesOf('MyComponent', module) `, + ` + const Primary = {} + const Secondary = {} + export default {} + export { Primary, Secondary } + `, + dedent` + export default { + excludeStories: /.*Data$/, + } + + const mockData = {} + const Primary = {} + + export { mockData, Primary } + ` ], invalid: [ { @@ -73,5 +89,22 @@ ruleTester.run('story-exports', rule, { }, ], }, + { + code: dedent` + export default { + excludeStories: /.*Data$/, + } + + const mockData = {} + const Primary = {} + + export { mockData } + `, + errors: [ + { + messageId: 'shouldHaveStoryExport', + }, + ], + }, ], })