Skip to content

Commit

Permalink
default-exports: Refactor rule to handle meta declaration
Browse files Browse the repository at this point in the history
  • Loading branch information
yannbf committed Nov 1, 2024
1 parent c16dd3a commit 0a87433
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 17 deletions.
11 changes: 11 additions & 0 deletions docs/rules/default-exports.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ export const Primary = {}

Examples of **correct** code for this rule:

```js
const meta = {
title: 'Button',
args: { primary: true },
component: Button,
}
export const meta

export const Primary = {}
```

```js
export default {
title: 'Button',
Expand Down
36 changes: 26 additions & 10 deletions lib/rules/default-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,9 @@ export = createStorybookRule({
},

create(context) {
// variables should be defined here

//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------

// any helper functions should go here or else delete this section
const getComponentName = (node: TSESTree.Program, filePath: string) => {
const name = path.basename(filePath).split('.')[0]
const imported = node.body.find((stmt: TSESTree.Node) => {
Expand All @@ -62,6 +58,7 @@ export = createStorybookRule({
//----------------------------------------------------------------------

let hasDefaultExport = false
let localMetaNode: TSESTree.Node
let hasStoriesOfImport = false

return {
Expand All @@ -70,6 +67,17 @@ export = createStorybookRule({
hasStoriesOfImport = true
}
},
VariableDeclaration(node) {
// Take `const meta = {};` into consideration
if (
node.kind === 'const' &&
node.declarations.length === 1 &&
node.declarations[0]?.id.type === 'Identifier' &&
node.declarations[0]?.id.name === 'meta'
) {
localMetaNode = node
}
},
ExportDefaultSpecifier: function () {
hasDefaultExport = true
},
Expand All @@ -78,20 +86,28 @@ export = createStorybookRule({
},
'Program:exit': function (program: TSESTree.Program) {
if (!hasDefaultExport && !hasStoriesOfImport) {
const componentName = getComponentName(program, context.getFilename())
const componentName = getComponentName(program, context.filename)
const firstNonImportStatement = program.body.find((n) => !isImportDeclaration(n))
const node = firstNonImportStatement || program.body[0] || program
const node = firstNonImportStatement ?? program.body[0] ?? program

const report = {
node,
messageId: 'shouldHaveDefaultExport',
} as const

const fix: TSESLint.ReportFixFunction = (fixer) => {
const metaDeclaration = componentName
? `export default { component: ${componentName} }\n`
: 'export default {}\n'
return fixer.insertTextBefore(node, metaDeclaration)
const sourceCode = context.sourceCode.getText()
// only add semicolons if needed
const semiCharacter = sourceCode.includes(';') ? ';' : ''
if (localMetaNode) {
const exportStatement = `\nexport default meta${semiCharacter}`
return fixer.insertTextAfter(localMetaNode, exportStatement)
} else {
const exportStatement = componentName
? `export default { component: ${componentName} }${semiCharacter}\n`
: `export default {}${semiCharacter}\n`
return fixer.insertTextBefore(node, exportStatement)
}
}

context.report({
Expand Down
54 changes: 47 additions & 7 deletions tests/lib/rules/default-exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ ruleTester.run('default-exports', rule, {
suggestions: [
{
messageId: 'fixSuggestion',
output: 'export default {}\nexport const Primary = () => <button>hello</button>',
output: dedent`
export default {}
export const Primary = () => <button>hello</button>
`,
},
],
},
Expand All @@ -70,8 +73,11 @@ ruleTester.run('default-exports', rule, {
suggestions: [
{
messageId: 'fixSuggestion',
output:
"import { MyComponent, Foo } from './MyComponent'\nexport default { component: MyComponent }\nexport const Primary = () => <button>hello</button>",
output: dedent`
import { MyComponent, Foo } from './MyComponent'
export default { component: MyComponent }
export const Primary = () => <button>hello</button>
`,
},
],
},
Expand All @@ -93,8 +99,11 @@ ruleTester.run('default-exports', rule, {
suggestions: [
{
messageId: 'fixSuggestion',
output:
"import MyComponent from './MyComponent'\nexport default { component: MyComponent }\nexport const Primary = () => <button>hello</button>",
output: dedent`
import MyComponent from './MyComponent'
export default { component: MyComponent }
export const Primary = () => <button>hello</button>
`,
},
],
},
Expand All @@ -116,8 +125,39 @@ ruleTester.run('default-exports', rule, {
suggestions: [
{
messageId: 'fixSuggestion',
output:
"import { MyComponentProps } from './MyComponent'\nexport default {}\nexport const Primary = () => <button>hello</button>",
output: dedent`
import { MyComponentProps } from './MyComponent'
export default {}
export const Primary = () => <button>hello</button>`,
},
],
},
],
},
{
code: dedent`
import { MyComponentProps } from './MyComponent';
export const Primary = () => <button>hello</button>;
const meta = { args: { foo: 'bar' } };
`,
output: dedent`
import { MyComponentProps } from './MyComponent';
export const Primary = () => <button>hello</button>;
const meta = { args: { foo: 'bar' } };
export default meta;
`,
errors: [
{
messageId: 'shouldHaveDefaultExport',
suggestions: [
{
messageId: 'fixSuggestion',
output: dedent`
import { MyComponentProps } from './MyComponent';
export const Primary = () => <button>hello</button>;
const meta = { args: { foo: 'bar' } };
export default meta;
`,
},
],
},
Expand Down

0 comments on commit 0a87433

Please sign in to comment.