Giter Site home page Giter Site logo

eslint-plugin-storybook's Introduction

Storybook

Build bulletproof UI components faster


Storybook Community Backers on Open Collective Sponsors on Open Collective Official Twitter Handle

eslint-plugin-storybook

Best practice rules for Storybook

Installation

You'll first need to install ESLint:

npm install eslint --save-dev
# or
yarn add eslint --dev

Next, install eslint-plugin-storybook:

npm install eslint-plugin-storybook --save-dev
# or
yarn add eslint-plugin-storybook --dev

And finally, add this to your .eslintignore file:

// Inside your .eslintignore file
!.storybook

This allows for this plugin to also lint your configuration files inside the .storybook folder, so that you always have a correct configuration and don't face any issues regarding mistyped addon names, for instance.

For more info on why this line is required in the .eslintignore file, check this ESLint documentation.

Usage

Use .eslintrc.* file to configure rules. See also: https://eslint.org/docs/user-guide/configuring

Add plugin:storybook/recommended to the extends section of your .eslintrc configuration file. Note that we can omit the eslint-plugin- prefix:

{
  // extend plugin:storybook/<configuration>, such as:
  "extends": ["plugin:storybook/recommended"]
}

This plugin will only be applied to files following the *.stories.* (we recommend this) or *.story.* pattern. This is an automatic configuration, so you don't have to do anything.

Overriding/disabling rules

Optionally, you can override, add or disable rules settings. You likely don't want these settings to be applied in every file, so make sure that you add a overrides section in your .eslintrc.* file that applies the overrides only to your stories files.

{
  "overrides": [
    {
      // or whatever matches stories specified in .storybook/main.js
      "files": ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)'],
      "rules": {
        // example of overriding a rule
        'storybook/hierarchy-separator': 'error',
        // example of disabling a rule
        'storybook/default-exports': 'off',
      }
    }
  ]
}

MDX Support

This plugin does not support MDX files.

Supported Rules and configurations

Key: 🔧 = fixable

Configurations: csf, csf-strict, addon-interactions, recommended

Name Description 🔧 Included in configurations
storybook/await-interactions Interactions should be awaited 🔧
  • addon-interactions
  • recommended
storybook/context-in-play-function Pass a context when invoking play function of another story
  • recommended
  • addon-interactions
storybook/csf-component The component property should be set
  • csf
storybook/default-exports Story files should have a default export 🔧
  • csf
  • recommended
storybook/hierarchy-separator Deprecated hierarchy separator in title property 🔧
  • csf
  • recommended
storybook/no-redundant-story-name A story should not have a redundant name property 🔧
  • csf
  • recommended
storybook/no-stories-of storiesOf is deprecated and should not be used
  • csf-strict
storybook/no-title-property-in-meta Do not define a title in meta 🔧
  • csf-strict
storybook/no-uninstalled-addons This rule identifies storybook addons that are invalid because they are either not installed or contain a typo in their name.
  • recommended
storybook/prefer-pascal-case Stories should use PascalCase 🔧
  • recommended
storybook/story-exports A story file must contain at least one story export
  • recommended
  • csf
storybook/use-storybook-expect Use expect from @storybook/jest 🔧
  • addon-interactions
  • recommended
storybook/use-storybook-testing-library Do not use testing-library directly on stories 🔧
  • addon-interactions
  • recommended

Contributors

Looking into improving this plugin? That would be awesome! Please refer to the contributing guidelines for steps to contributing.

License

MIT

eslint-plugin-storybook's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

eslint-plugin-storybook's Issues

ESLint error: `TypeError: Cannot read property 'properties' of undefined`

Describe the bug

Tried lint a project I'm working on with eslint-plugin-storybook and getting the following error:

TypeError: Cannot read property 'properties' of undefined

To Reproduce

Steps to reproduce the behavior:

  1. Clone this repository
  2. NPM linked (npm link)
added 1 package, and audited 3 packages in 815ms

found 0 vulnerabilities
  1. Go to the repository where you want to use Storybook's
  2. NPM link Storybook's ESLint repo (npm link eslint-plugin-storybook)
linked using [email protected]. These might not interact correctly.
/Users/xxx/yyy/node_modules/eslint-plugin-storybook -> 
/Users/xxx/...../eslint-plugin-storybook/lib/node_modules/eslint-plugin-storybook -> 
/Users/xxx/dev/open_source/storybookjs/eslint-plugin-storybook
  1. Update .eslintrc with Storybook's ESLint plugin
  2. Run lint in the project (npm run lint)
...
Oops! Something went wrong! :(

ESLint: 7.32.0

TypeError: Cannot read property 'properties' of undefined
...

Versions

version
node 16.13.0
eslint 7.3.2
storybook 6.3.8

Files

.eslintrc.js

module.exports = {
  extends: ["react-app", "react-app/jest", "plugin:prettier/recommended"],
  plugins: ["prettier", "storybook"],
  rules: {
    "prettier/prettier": "error",
  },
  overrides: [
    {
      // 3) Now we enable eslint-plugin-storybook rules or preset only for matching files!
      // you can use the one defined in your main.js
      files: ["src/**/*.stories.@(js|jsx|ts|tsx)"],
      extends: ["plugin:storybook/recommended"],

      // 4) Optional: you can override specific rules here if you want. Else delete this
      // rules: {
      //   "storybook/no-redundant-story-name": "error",
      // },
    },
  ],
};

Checkbox.stories.js

import Checkbox from "../components/Checkbox";

const CheckboxComponent = {
  title: "Checkbox",
  component: Checkbox
};

export default CheckboxComponent;

Terminal output

Click to expand!
npm run lint:js                                 20:48:42

> [email protected] lint:js /Users/xxx/dev/zzz
> eslint . --ext js --ext jsx --cache


Oops! Something went wrong! :(

ESLint: 7.32.0

TypeError: Cannot read property 'properties' of undefined
Occurred while linting /Users/xxx/dev/zzz/src/stories/Checkbox.stories.js:8
  at ExportDefaultDeclaration (/Users/xxx/dev/open_source/storybookjs/eslint-plugin-storybook/lib/rules/hierarchy-separator.js:34:91)
  at /Users/xxx/dev/zzz/node_modules/eslint/lib/linter/safe-emitter.js:45:58
  at Array.forEach (<anonymous>)
  at Object.emit (/Users/xxx/dev/zzz/node_modules/eslint/lib/linter/safe-emitter.js:45:38)
  at NodeEventGenerator.applySelector (/Users/xxx/dev/zzz/node_modules/eslint/lib/linter/node-event-generator.js:293:26)
  at NodeEventGenerator.applySelectors (/Users/xxx/dev/zzz/node_modules/eslint/lib/linter/node-event-generator.js:322:22)
  at NodeEventGenerator.enterNode (/Users/xxx/dev/zzz/node_modules/eslint/lib/linter/node-event-generator.js:336:14)
  at CodePathAnalyzer.enterNode (/Users/xxx/dev/zzz/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js:711:23)
  at /Users/xxx/dev/zzz/node_modules/eslint/lib/linter/linter.js:960:32
  at Array.forEach (<anonymous>)
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] lint:js: `eslint . --ext js --ext jsx --cache`
npm ERR! Exit status 2
npm ERR! 
npm ERR! Failed at the [email protected] lint:js script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/xxx/.npm/_logs/2021-11-01T19_49_02_318Z-debug.log

Expected behaviour

Not sure if I'm not writing a correct story but still I assume ESLint should complain but not throw an exception.

await-interactions fixer insert await without inserting async

Describe the bug
A clear and concise description of what the bug is.

To Reproduce

Steps to reproduce the behavior:

  1. Go to 'docs/rules/await-interactions.md'
  2. Copy the example of incorrect code
  3. Use the await-interactions rule fixer
  4. The generated code is invalid JavaScript code that can not run (cf example of correct code)
import { within, userEvent } from '@storybook/testing-library'

MyStory.play = (context) => {
  const canvas = within(context.canvasElement)
  // awaited 👍
  await userEvent.click(canvas.getByRole('button'))
}

Expected behavior

In the fixed code, the MyStory.play method should be defined changed to async:

MyStory.play = async (context) => {
  const canvas = within(context.canvasElement)
  // awaited 👍
  await userEvent.click(canvas.getByRole('button'))
}

Screenshots

image

New rule: checking setting story annotations out of order

Is your feature request related to a problem? Please describe.

From reddit, a user mentioned an example that happens quite often when users are writing CSF2:

export const AStory = Template.bind({})
AStory.args = { something: true }

export const AnotherStory = Template.bind({})
AStory.args = { something: false } // <-- supposed to be AnotherStory

This kind of thing brings confusion, so there should be a way to help detect that.

`storybook/no-uninstalled-addons` fails with locally references file addons

Describe the bug

The latest version 0.6.0 introduces storybook/no-uninstalled-addons which will fail if you use file path addons.

[eslint] storybook/main.js
[eslint] 91:5 error
The ./my-addon/preset.cjs is not installed. Did you forget to install it? storybook/no-uninstalled-addons

To Reproduce
Steps to reproduce the behavior:

  1. Add an addon to your main.js array using a relative file path, e.g. addons: ['./my-addon/preset.cjs']
  2. Run eslint
  3. See error

Expected behavior
Local file references should be ignored since there is no need to install them.

Add `no-uninstalled-addons` rule

Is your feature request related to a problem? Please describe.

Whenever users register addons in main.js, there's a possibility of:
1 - having typos
2 - registering addons that are not installed (not in package.json)

In either scenario, the users get this current error when running Storybook, which is not helpful at all:

WARN   Failed to load preset: {"type":"presets"} on level 1
ERR! TypeError [ERR_INVALID_ARG_TYPE]: The "id" argument must be of type string. Received an instance of Object

Describe the solution you'd like

There should be an eslint rule that warns users that addons were registered in main.js but they are not installed.

1 - Process main.js files
2 - Identify the addons field from it
3 - Go through each addon and check whether they exist in package.json(Ideally read once and cached or something like that)
4 - If the addon is not in package.json, report to users

Possible

Additional context

It's important to know the ways you can register an addon:

// .storybook/main.js
module.exports = {
  addons: [
    // default way of registering an addon
    '@storybook/addon-essentials',
    
    // advanced way of registering an addon
    { 
      name: '@storybook/addon-essentials',
      options: { docs: false }
    }
  ]
}

CSF default export with spreaded-in properties throws error

Describe the bug
When spreading in an object into the default export of a stories-file, I noticed that at least the title property must be set directly, or else the eslint process will throw an error: Cannot read property 'name' of undefined and the whole linting process is terminated.

To Reproduce
A minimal reproduction can be achieved by exporting the default export like this:

const storyTemplate = {
  title: "My Component",
  component: "my-component", // this line is not even necessary
};

export default {
  ...storyTemplate,
};

When running eslint, this will throw an error: Cannot read property 'name' of undefined (pointing to the line of the default export).

Linting will not break if you add in the "title" field:

export default {
  title: storyTemplate.title, // without this line, eslint rules for storybook will break
  ...storyTemplate,
};

Linting will also break, if the above title line is added after the spreaded-in ...storyTemplate:

export default {
  ...storyTemplate,
  title: storyTemplate.title, // this will break
};

Expected behavior
Linting should not throw an Error

Additional context
I'm using Storybook 6.4.9 for web-components and eslint-plugin-storybook 0.5.6

Rule: story-exports

Is your feature request related to a problem? Please describe.
The plugin should error in case there are no story exports in a file:

incorrect:

export default { ... }
// but no story!

or

export default { 
  ...,
  excludeStories: /.*Data$/,
}
export const mockData = {} // not a story!

correct:

export default { ... }
export const MyStory = {}

storiesOf:

import { storiesOf } from '@storybook/react'
storiesOf('MyComponent', module)

Rule to enforce CSF3 format in stories

Is your feature request related to a problem? Please describe.

I have teammates that keep writing the CSF2 format out of habit even after a CSF3 migration. 🙂

Describe the solution you'd like

An ESLint rule that enforces the CSF3 format in stories.

VS Code does not format files on save when using storybook in eslint

Describe the bug
Eslint throws error when trying to format onSave in Visual Studio Code. I have tried to delete node_modules and package-lock.json and installed all packages again but it didn't work. eslint-plugin-storybook exists in my node_modules folder.

(node:2024) UnhandledPromiseRejectionWarning: Error: Failed to load plugin 'storybook' declared in '.eslintrc.json': Cannot find module 'eslint-plugin-storybook'

Expected behavior
I expect eslint to run --fix when saving a file in VS Code.

Additional context
If I remove "plugin:storybook/recommended" from my extends in .eslintrc.json it runs eslint onSave without errors.

    "extends": [
		"standard",
		"plugin:react/recommended",
		"plugin:@typescript-eslint/eslint-recommended",
		"plugin:@typescript-eslint/recommended",
+		"plugin:react-hooks/recommended"
-		"plugin:react-hooks/recommended",
-               "plugin:storybook/recommended"
	],

Other eslint-plugins:

"eslint": "~7.27.0",
"eslint-config-standard": "~16.0.3",
"eslint-plugin-import": "~2.22.1",
"eslint-plugin-jest-dom": "~3.9.2",
"eslint-plugin-node": "~11.1.0",
"eslint-plugin-promise": "~4.2.1",
"eslint-plugin-react": "~7.23.2",
"eslint-plugin-react-hooks": "~4.2.0",
"eslint-plugin-storybook": "~0.5.1",
"eslint-plugin-testing-library": "~5.0.0",

New Rule: Disallow template strings in titles

Is your feature request related to a problem? Please describe.
According to https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#string-literal-titles, dynamic titles are going to be prohibited in 7.0. It might be nice to have a linting rule that flags if backticks are being used.

Describe the solution you'd like
A rule, not included in recommended (due to different versions of storybook having different requirements), which flags the use of backticks in story titles.

Additional context
https://discord.com/channels/486522875931656193/691505730125168682/928685457552863254

image

`prefer-pascal-case` and `story-exports` throw error if function's `name` property is used in `includeStories`/`excludeStories` arrays.

Describe the bug
prefer-pascal-case and story-exports throw error if function's name property is used in includeStories/excludeStories arrays.

To Reproduce
Exclude or include a story using the name property of it.
Example:

export default {
  title: "MyComponent",
  component: MyComponent,
  excludeStories: [MyComponent.name]
}

function MyComponent() {}

Produces an error such as:

Error: Unexpected descriptor element: MemberExpression
Occurred while linting C:\fake-path\MyComponent.stories.tsx:16
    at C:\fake-path\node_modules\eslint-plugin-storybook\dist\utils\index.js:39:27
    at Array.map (<anonymous>)
    at getDescriptor (C:\fake-path\node_modules\eslint-plugin-storybook\dist\utils\index.js:37:44)
    at ExportDefaultDeclaration (C:\fake-path\node_modules\eslint-plugin-storybook\dist\rules\prefer-pascal-case.js:104:67)
    at C:\fake-path\node_modules\eslint\lib\linter\safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (C:\fake-path\node_modules\eslint\lib\linter\safe-emitter.js:45:38)
    at NodeEventGenerator.applySelector (C:\fake-path\node_modules\eslint\lib\linter\node-event-generator.js:293:26)
    at NodeEventGenerator.applySelectors (C:\fake-path\node_modules\eslint\lib\linter\node-event-generator.js:322:22)
    at NodeEventGenerator.enterNode (C:\fake-path\node_modules\eslint\lib\linter\node-event-generator.js:336:14)
Process finished with exit code -1

Expected behavior
ESLint does not crash

Additional context
It seems that the problem is at https://github.com/storybookjs/eslint-plugin-storybook/blob/main/lib/utils/index.ts#L48, because it doesn't expect such usage as in my example. AST of my example produces a PropertyAccessExpression. It's valid string, but only at runtime.

I'm using such approach in order to automatically rename the story also in includeStories/excludeStories arrays when refactoring.

Would you consider expanding the linter rules to allow such approach?

Error `Cannot find module 'ts-dedent'`

Describe the bug
When trying to include eslint-plugin-storybook into .eslint configuration

  "extends": [
    ...
    "plugin:storybook/recommended"
  ],

and running eslint, following error is returned:
Error: Failed to load plugin 'storybook' declared in '.eslintrc': Cannot find module 'ts-dedent'

Add option to pass a custom location of the package.json in the no-uninstalled-addons rule

Is your feature request related to a problem? Please describe.
The no-uninstalled-addons rule does not provide a custom path for the package.json. It should provide that in the rule configuration.

Describe the solution you'd like
The solution would be to add a custom schema to this rule, where the location of the package.json can be specified in there.

Describe alternatives you've considered
That would be the best and most explicit option.

Additional context
No additional context.

Error: Rules with suggestions must set the meta.hasSuggestions property to true.

Describe the bug
I get the following in the ESLint output:

Oops! Something went wrong! :(

ESLint: 8.3.0

Error: Rules with suggestions must set the `meta.hasSuggestions` property to `true`.
Occurred while linting /src/components/MyComponent/index.stories.tsx:1
Rule: "storybook/prefer-pascal-case"
    at Object.report (/node_modules/eslint/lib/linter/linter.js:948:35)
    at checkAndReportError (/node_modules/eslint-plugin-storybook/dist/rules/prefer-pascal-case.js:54:25)
    at /node_modules/eslint-plugin-storybook/dist/rules/prefer-pascal-case.js:122:49
    at Array.forEach (<anonymous>)
    at Program:exit (/node_modules/eslint-plugin-storybook/dist/rules/prefer-pascal-case.js:122:34)
    at ruleErrorHandler (/node_modules/eslint/lib/linter/linter.js:966:28)
    at /node_modules/eslint/lib/linter/safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (/node_modules/eslint/lib/linter/safe-emitter.js:45:38)
    at NodeEventGenerator.applySelector (/node_modules/eslint/lib/linter/node-event-generator.js:297:26)

To Reproduce
Steps to reproduce the behavior:

  1. install eslint-plugin-storybook.

npm install eslint-plugin-storybook --save-dev

Here's my deps related to eslint and storybook from package.json

{
   "dependecies": {
       "@storybook/addon-a11y": "6.4.0",
        "@storybook/addon-console": "1.2.3",
        "@storybook/addon-essentials": "6.4.0",
        "@storybook/addon-links": "6.4.0",
        "@storybook/addon-storyshots": "6.4.0",
        "@storybook/react": "6.4.0",
        "eslint": "8.3.0",
        "eslint-config-next": "12.0.4",
        "eslint-config-prettier": "8.3.0",
        "eslint-plugin-import": "2.25.3",
        "eslint-plugin-react": "7.27.1",
        "eslint-plugin-storybook": "0.5.0",
}
  1. add plugin:storybook/recommended to the extends section of .eslintrc.json.
  2. run eslint

Expected behavior
Complete eslint without error.

Destructuring assignment exports show `storybook/story-exports` error

Describe the bug
An array Destructuring assignment export shows a storybook/story-exports error

To Reproduce

export default {
  title: 'Components/ComponentExample',
  component: ComponentExample,
} as ComponentMeta<typeof ComponentExample>;

export const [[WithXXX, WithOutXXX, WithAllXXX], [WithYYY, WithOutYYY, WithAllYYY]] = createStories(ComponentExample)

Note that if I export as export { WithXXX, WithOutXXX, WithAllXXX, ... } it works, but would be nice to export in-place like the previous example to avoid to write unnecessary code :)

Expected behavior
For the previous example, the error is shown, but the stories are being exported and it works perfectly so it should not to be an error. The rule should recognize restructure exports as part of "named exports"

Failed destructure of `id` from `decl.decrations[0]` as it is undefined

Describe the bug

I occasionally get a popup complaining about a failed destructure:

[Error - 1:05:57 PM] ESLint stack trace:
[Error - 1:05:57 PM] TypeError: Cannot destructure property 'id' of 'decl.declarations[0]' as it is undefined.
Occurred while linting /Users/calebjasik-defined/Github/defined.net/webclient/src/screens/hosts/detail/HostDetailPage.stories.tsx:67
Rule: "storybook/prefer-pascal-case"
    at ExportNamedDeclaration (/Users/calebjasik-defined/Github/defined.net/webclient/node_modules/eslint-plugin-storybook/dist/rules/prefer-pascal-case.js:114:29)
    at ruleErrorHandler (/Users/calebjasik-defined/Github/defined.net/webclient/node_modules/eslint/lib/linter/linter.js:1114:28)

To Reproduce
Steps to reproduce the behavior:
Run the plugin over a story w/ the name FFDefault and probably more bits... name: 'Default with HOST_BLOCKLIST',

Expected behavior
I would expect my eslint plugin not to throw errors

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
Add any other context about the problem here.

const { id } = decl.declarations[0]

Latest version of `@typescript-eslint/experimental-utils` breaks the ast-utils import

Describe the bug
The maintainers of @typescript-eslint/experimental-utils renamed the package to @typescript-eslint/utils in this release https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/experimental-utils/CHANGELOG.md#5100-2022-01-17, which seems to break the following imports in this plugin:

I believe this can be fixed by switching to what appears to be the intended import: https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/src/index.ts#L1

To Reproduce
Steps to reproduce the behavior:

  1. Run yarn upgrade
  2. Attempt to run yarn build-storybook
  3. See error

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
image

Additional context
Add any other context about the problem here.

The `no-redundant-story-name` rule doesn't account for automatic whitespace when names have numbers

Describe the bug
The no-redundant-story-name rule doesn't account for the whitespace that is automatically added by Storybook when it formats the story name for display, e.g. a story with the name H1 will be formatted for display as H 1, and adding H1.storyName = 'H1'; triggers this rule.

To Reproduce
Steps to reproduce the behavior:

  1. Create a story with the name H1
  2. Add H1.storyName = 'H1';
  3. See that the rule is triggered
  4. "Fix" the error so the rule is no longer triggered
  5. Observe the displayed name is H 1 in the rendered Storybook

Expected behavior
I expect this rule to be aware of Storybook's formatting behavior, specifically when story names include a number.

Screenshots
I don't think any are needed, but happy to add some if my explanation is unclear.

Additional context
To me, this is an extremely low priority thing since I can easily ignore the rule on a couple lines in my Typography docs, but I wanted to mention it just in case.

Add rule to check name property is a string literal

Is your feature request related to a problem? Please describe.

When overriding a story name it must be a string literal. Not a variable and not a template literal.

https://discord.com/channels/486522875931656193/1054051640946208768/1055259413415481394

Describe the solution you'd like

Check for this and warn the user. Ideally provide fix.

Describe alternatives you've considered

Making CsfFile smarter. But since there will always be limitations to static analysis, we need some kind of linting regardless

Additional context
Add any other context or screenshots about the feature request here.

[await-interactions] typescript warning: "'await' has no effect on the type of this expression."

Describe the bug
I'm not sure if this is necessarily a bug, but it's a bit unclear what the right thing to do is. When I await my userEvent calls, as directed by await-interactions, I get a typescript warning:

'await' has no effect on the type of this expression.

I guess that's because the typescript type from user-event is:

declare function click(element: Element, init?: MouseEventInit, { skipHover, clickCount }?: clickOptions): void;

So, it's unclear where the problem is, but there seems to be definite disagreement between this linting rule and typescript. :)

To Reproduce
Steps to reproduce the behavior:
In a typescript project, import userEvent from @storybook/testing-library, add an await, and see the typescript warning.

Expected behavior
No warning to be shown

Plans to release version 1 and follow semver?

Any plans to release version 1 or otherwise a reason to hold back?

From https://semver.org/

Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.

If your software is being used in production, it should probably already be 1.0.0. If you have a stable API on which users have come to depend, you should be 1.0.0. If you’re worrying a lot about backward compatibility, you should probably already be 1.0.0.

Support for "stories.*" pattern

Is your feature request related to a problem? Please describe.
In my project we use the filename stories.jsx instead of ComponentName.stories.jsx, and as mentioned in the readme, this plugin only supports the patterns *.stories.* and *.story.*.

Describe the solution you'd like
I would like to check if it would be possible to add support for the pattern stories.*.

Describe alternatives you've considered
We could rename all our stories files, but using stories.jsx is simpler in our case, and given all components are in their own folder there's no risk of having more than one stories file in each folder.

storybook/story-exports rule doesn't support named exports list syntax

Hello, apologies if this is not a bug but it sure seems like one to me.

Describe the bug
eslint-plugin-storybook is throwing error The file should have at least one story export storybook/story-exports when using "named export list" syntax.

Example:

This fails:

const quotesList = () => {
...
};

export { quotesList };

This passes:

export const quotesList = () => {
...
};

To Reproduce

  • You could clone: metmuseum/marble@38a1e98 - this commit introduces the eslint storybook plugin
  • Install deps
  • Run npm run lint

Expected behavior
The named exports list syntax should be supported and this rule should not throw any errors on the example above?

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
This seems to have come from #42 and #44 so cc @yannbf - thanks.

Limit play function rules to play functions

Example from official-storybook https://github.com/storybookjs/storybook/blob/main/examples/official-storybook/stories/addon-storyshots.stories.js

Block.parameters = {
  async puppeteerTest(page) {
    const element = await page.$('[data-test-block]');
    await element.hover();
    const textContent = await element.getProperty('textContent');
    const text = await textContent.jsonValue();
    // eslint-disable-next-line jest/no-standalone-expect
    expect(text).toBe('I am hovered');
  },
};
  • await-interactions
  • use-storybook-expect

storybook/no-uninstalled-addons false positives on monorepo

Describe the bug

Getting errors like these in a monorepo:

The @storybook/addon-links is not installed in pats/. Did you forget to install it or is your package.json in a different location?

the pats is the root of the monorepo.
Each package has storybook and addons installed.

Expected behavior
no error

Additional context
I'm not sure if storybook should be hoisted or install only at the top level of monorepo.

Right now, I have each package self-contained and each has their own copy of storybook.

I'm using pnpm.

await-interactions seems redundant

I've been debugging an issue where the await-interactions rule and @typescript-eslint/await-thenable conflict.
The type declarations of userEvent.click state that it returns void and so adding an await causes the typescript rule to complain.

Trying to pick apart the code, as far as I can tell once a function, fn, is wrapped in instrument it will return a promise if and only if the original fn returned a promise. So assuming that it is always awaitable seems wrong.

Certainly, when I run and debug userEvent.click() in my story it returns 'undefined' rather than a Promise.

If the types in @storybook/testing-library are correct then @typescript-eslint/no-floating-promises would ensure that all Promises are awaited and the await-interactions rule seems redundant.

Improve use-storybook-testing-library rule

The rule should work with the following scenarios. It currently only works with scenario 1:

import { within } from '@testing-library/react'

// becomes
import { within } from '@storybook/testing-library'
import userEvent from '@storybook/user-event'

// becomes
import { userEvent } from '@storybook/testing-library'
import { within } from '@testing-library/react'
import { userEvent } from '@storybook/user-event'

// becomes
import { within, userEvent } from '@storybook/testing-library'

ESLint error: `TypeError: Cannot read property 'name' of undefined`

Describe the bug

Tried lint a project I'm working on with eslint-plugin-storybook and getting the following error:

TypeError: Cannot read property 'name' of undefined

To Reproduce

Steps to reproduce the behaviour:

  1. Clone this repository
  2. NPM linked (npm link)
added 1 package, and audited 3 packages in 815ms

found 0 vulnerabilities
  1. Go to the repository where you want to use Storybook's
  2. NPM link Storybook's ESLint repo (npm link eslint-plugin-storybook)
linked using [email protected]. These might not interact correctly.
/Users/xxx/yyy/node_modules/eslint-plugin-storybook -> 
/Users/xxx/...../eslint-plugin-storybook/lib/node_modules/eslint-plugin-storybook -> 
/Users/xxx/dev/open_source/storybookjs/eslint-plugin-storybook
  1. Update .eslintrc with Storybook's ESLint plugin
  2. Run lint in the project (npm run lint)
...
Oops! Something went wrong! :(

ESLint: 7.32.0

TypeError: Cannot read property 'name' of undefined
...

Versions

version
node 16.13.0
eslint 7.3.2
storybook 6.3.8

Files

.eslintrc.js

module.exports = {
  extends: ["react-app", "react-app/jest", "plugin:prettier/recommended"],
  plugins: ["prettier", "storybook"],
  rules: {
    "prettier/prettier": "error",
  },
  overrides: [
    {
      // 3) Now we enable eslint-plugin-storybook rules or preset only for matching files!
      // you can use the one defined in your main.js
      files: ["src/**/*.stories.@(js|jsx|ts|tsx)"],
      extends: ["plugin:storybook/recommended"],

      // 4) Optional: you can override specific rules here if you want. Else delete this
      // rules: {
      //   "storybook/no-redundant-story-name": "error",
      // },
    },
  ],
};

Completed.stories.js

import React from "react";
import UploadComplete from "../../components/Upload/Complete";

import maximilian from "../assets/images/maximilian.png";
import mrsExample from "../assets/images/mrs-example.webp";

const Images = {
  maximilian: maximilian,
  mrsExample: mrsExample,
};

const Meta = {
  title: "UploadComplete",
  component: UploadComplete,
  argTypes: {
    image: {
      options: ["maximilian", "mrsExample"],
      control: { type: "radio" },
    },
  },
};

export default Meta;

const Template = ({ name, image }) => {
  image = Images[image];
  return <UploadComplete name={name} image={image} />;
};

export const Primary = Template.bind({});
Primary.args = {
  name: "Maximilian",
  image: "maximilian",
};

Terminal output

Click to expand!
npm run lint:js                                 20:48:42

> [email protected] lint:js /Users/xxx/dev/zzz
> eslint . --ext js --ext jsx --cache


Oops! Something went wrong! :(

ESLint: 7.32.0

TypeError: Cannot read property 'name' of undefined
Occurred while linting /Users/xxx/yyy/src/stories/Upload/Complete.stories.js:26
  at isPlayFunction (/Users/xxx/zzz/eslint-plugin-storybook/lib/rules/use-storybook-expect.js:40:23)
  at AssignmentExpression (/Users/xxx/zzz/eslint-plugin-storybook/lib/rules/use-storybook-expect.js:72:13)
  at /Users/xxx/yyy/node_modules/eslint/lib/linter/safe-emitter.js:45:58
  at Array.forEach (<anonymous>)
  at Object.emit (/Users/xxx/yyy/node_modules/eslint/lib/linter/safe-emitter.js:45:38)
  at NodeEventGenerator.applySelector (/Users/xxx/yyy/cv2profile/node_modules/eslint/lib/linter/node-event-generator.js:293:26)
  at NodeEventGenerator.applySelectors (/Users/xxx/yyy/node_modules/eslint/lib/linter/node-event-generator.js:322:22)
  at NodeEventGenerator.enterNode (/Users/xxx/yyy/node_modules/eslint/lib/linter/node-event-generator.js:336:14)
  at CodePathAnalyzer.enterNode (/Users/xxx/yyy/node_modules/eslint/lib/linter/code-path-analysis/code-path-analyzer.js:711:23)
  at /Users/xxx/yyy/node_modules/eslint/lib/linter/linter.js:960:32
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] lint:js: `eslint . --ext js --ext jsx --cache`
npm ERR! Exit status 2
npm ERR! 
npm ERR! Failed at the [email protected] lint:js script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/xxx/.npm/_logs/2021-11-02T08_18_15_415Z-debug.log

Expected behaviour

  • Is the story I shared correct?
  • I assume ESLint should complain but not throw an exception.

Migrate to typescript

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

`context-in-play-function` does not check invocked element type

Describe the bug
The new context-in-play-function returns an error when calling play() function on a object which is not a story.
I don't think this should be the case

Expected behavior
I expect the context-in-play error to be triggered only when called against a story element

Screenshots

Screenshot 2021-12-16 at 08 21 46

Support .jsx extension

At the moment, this plugin does not support .jsx extension, which is commonly used for react projects. This leads to the plugin not being usable in projects that use this convention.

As a dev who would love to use this to ensure consistency in our .stories.jsx files, adding this extension would be very useful.

Change would require adding the extension into https://github.com/storybookjs/eslint-plugin-storybook/blob/0c413c9497b487234356b5dca0f460f98ce6c108/tools/update-configs.ts

Add rule to check exports against `includeStories`

Is your feature request related to a problem? Please describe.
Some of our CSF3 stories export option arrays. These get imported by other stories, for example to loop through options in a composite story:

export const ALIGN: Array<TypographyProps["align"]> = [
  "left",
  "center",
  "right",
  "justify",
];

To ensure this doesn't gum up the Storybook UI, we define includeStories: /[a-z]/ in our default exports, so that our all-caps exports are excluded.

Describe the solution you'd like
A new rule should check that all named exports matching includeStories are in fact stories. This way, if we forget to define includeStories, the rule will notice that we're exporting named non-stories and flag them.

Describe alternatives you've considered
We could move the exports to other files, but this is exactly the reason includeStories exists. We'd prefer not to move Storybook-only utility constants into non-Storybook files.

Add rule to prevent empty args

It is possible to specify an empty object for args:

// Button.stories.js
export default {
  title: 'Button',
  component: Button,
  args: {},
};

Should such a case be detected by a rule?

await-interactions false positive in case of userEvent.setup function suggested by v14 api

Describe the bug

Latest version of user-event lib recommends to use a little bit different API. Instead of using userEvent.click they suggest to create user-event instance first by invoking const events = userEvent.setup(). And then use any event available e.g. await events.click(). userEvent.setup returns an object directly without promise, so it shouldn't be awaiten. But the rule "storybook/await-interactions" reports an issue there.

To Reproduce

  1. Install latest version of @storybook/testing-library (0.2.0 in my case)
  2. Import userEvent constructor.
  3. Define play function to any story you need.
  4. Declare userEvent.setup(); statement as it recommended in latest user-event section.
  5. See the validation error.

Expected behavior
Validation error should not be triggered on setup method.

Screenshots
image

Add rule to check that `satisfies Meta<>` is used

Is your feature request related to a problem? Please describe.
I'd like to ensure that my team remembers to add satisfies Meta<> in story metadata.

Describe the solution you'd like
Introduce a new rule to ensure that a default export in a TypeScript story is always followed by satisfies Meta

Specify a Licence

Is your feature request related to a problem? Please describe.
Really great eslint plugin and totally agree it will makes my codebase better.
I would like to use it but I couldn't find a LICENSE file.

Describe the solution you'd like
Add a LICENSE file

Describe alternatives you've considered
declaration in README.MD

awaited chain expression is seen as invalid

Describe the bug

with the await-interaction rule enabled, if a play function is being composed from another story, but the expression has an optional chain, it's seen as invalid; even if it's correctly awaited. because the expression will be seen as false and the auto-fix doesn't fix it; instead many redundant awaits are added.

To Reproduce

  1. lint the following code with the await-interaction enabled

    export const SecondStory = {
      play: async (context) => {
        await FirstStory.play?.(context)
      }
    }
  2. run auto-fix

Expected behavior

it should be seen as valid and the auto-fix shouldn't alter the code

Screenshots

valid code is seen as invalid: image

post auto-fix: image

Linting mdx stories

Is your feature request related to a problem? Please describe.
When refactoring stories, I accidentally wrote <Story name="Foobar" /> instead of <Story name="Foobar">{Template.bind({})}</Story>. It still build fine, but the whole storybook did not load anymore and all I got were cryptic error messages.

Error loading story index:
TypeError: render is undefined

Describe the solution you'd like
A linter could help find common mistakes like these much more quickly. Here are some suggestions:

Reuse rules from csf:

  • storybook/await-interactions
  • storybook/use-storybook-expect
  • storybook/use-storybook-testing-library

New rules:

  • Story contains minimal data to be renderable (not self-closing tag; name required?)
  • Only allow valid parameters (e.g. not define chromatic parameters directly, but only under the chromatic key)
  • Use Meta-Tag with recommended attributes
  • No duplicate story names (at least check in context of one file. often happens when copying stories)

Describe alternatives you've considered
I also tried installing a pure mdx-linter, but setup was complicated and I expect to gain not as much from it, when most problems occur in the storybook-specific semantics. So I’ve postponed its setup for now.

Additional context
This is in a vue-cli project, where there are already a lot of moving parts, especially wrt eslint. (vue sfc, script-setup, typescript, prettier)

Add rule to prevent adding argType annotations matching actions.argTypesRegex

When the user specifies a value for actions.argTypesRegex such as this one (copy/pasted from the actions addon documentation):

// .storybook/preview.js

export const parameters = {
  actions: { argTypesRegex: '^on.*' }
}

then there is no need to also specify actions matching the regular expression:

// MyButton.stories.js

export default {
  title: 'Button',
  component: Button,
  argTypes: {
    onClick: { action: 'onClick' } // unnecessary because it matches `argTypesRegex`
  }
};

What about adding a rule to detect and fix such cases?

Should the rule contain a configuration argument to flag/ignore an action whose name doesn't match the parameter name such as in:

// MyButton.stories.js

export default {
  title: 'Button',
  component: Button,
  argTypes: {
    onClick: { action: 'clicked' } // clicked ≠ onClick
  }
};

Await expect linting error

Describe the bug
When using expect or userEvent TS and eslint seem to not agree on what needs to be used

To Reproduce

play: async ({ canvasElement }) => {
		const canvas = within(canvasElement);

		const btn = canvas.getByRole('button');
		const style = window.getComputedStyle(btn);

		await userEvent.click(btn);
                // await shows a TS error "'await' has no effect on the type of this expression.ts(80007)"
		expect(btn).toBeVisible();
                
		expect(style.backgroundColor).toBe('rgb(255, 233, 120)');
		expect(btn.innerText).toBe('Large');
		expect(btn.offsetHeight).toBe(48);
                // all of the expect functions are lining with a message "Interaction should be awaited: toBeeslint[storybook/await-interactions](https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/await-interactions.md)"
	},

Expected behavior

"Expect" functions do not need to be awaited to work yet eslint is asking for them to be awaited. If you await them the 80007 TS error appears

False positive when using storybook/default-exports rule and having default export in the export block

Describe the bug
The storybook/default-exports rule should check if file contains default export, but for cases when one would want to have only one export clause in all file it is breaking and reporting false positives.

To Reproduce
Create story similar to this - using Vue3,

import type { Meta, StoryObj } from '@storybook/vue3'
import ButtonComponent from '../components/Button.vue'

const meta: Meta = {
  component: ButtonComponent
}

const Button: StoryObj = {
  render: args => ({
    components: { ButtonComponent },
    setup() {
      return { args }
    },
    template: `<ButtonComponent v-bind="args" />`
  })
}

export {
  meta as default,
  Button
}

Expected behavior
Running eslint on the code above with applied storybook plugin should not report an error

context-in-play-function support only "context" as a variable name

Describe the bug
Context in play function only support only "context" as a variable names. Any other variable names gives a lint error.

To Reproduce
Given this code :

const Story1 = Template.bind({});
Story1.play = async (ctx) => {
...
}

const Story2 = Template.bind({});
Story2.play = async (ctx) => {
	await Story1.play(ctx)
}

the lint should not fail since we are passing the context, but with a different variable name

Expected behavior
There should not have any lint errors since it respects the lint rule.

`no-redundant-story-name` case insensitivity

Is your feature request related to a problem? Please describe.
Warn/error when storyName is a case-insensitive variant of the name generated by the export name:

e.g. WithText.storyName = 'With Text'; will currently warn/error but WithText.storyName = 'with text'; will not

Describe the solution you'd like
The ability to configure the no-redundant-story-name rule to use/ignore case when determining in the name is redundant

Describe alternatives you've considered
Would also be open to this being the default behaviour if that's easier.

Additional context
I am working on a codebase where we used the storybook provided codemon to migrate our old storiesOf stories to CSF. Due to most of our old story names being all in lower-case, we now have a lot of instances where PascalCase.storyName = 'lower case'. Engineers are then copying this format and spreading the issue. So while I can fix the existing set, I'd like a way to catch any future instances

csf-strict config not scoped to story files

Describe the bug

I enabled the csf-strict config in my project by adding plugin:storybook/csf-strict to the extends array in .eslintrc.js.

When running ESLint, it fails with an exception:

TypeError: Cannot use 'in' operator to search for 'name' in undefined
Occurred while linting <snip>/packages/<snip>/src/index.js:12
    at <snip>/node_modules/eslint-plugin-storybook/dist/rules/no-title-property-in-meta.js:35:127
    at Array.find (<anonymous>)
    at ExportDefaultDeclaration (<snip>/node_modules/eslint-plugin-storybook/dist/rules/no-title-property-in-meta.js:35:51)
    at <snip>/node_modules/eslint/lib/util/safe-emitter.js:45:58
    at Array.forEach (<anonymous>)
    at Object.emit (<snip>/node_modules/eslint/lib/util/safe-emitter.js:45:38)
    at NodeEventGenerator.applySelector (<snip>/node_modules/eslint/lib/util/node-event-generator.js:251:26)
    at NodeEventGenerator.applySelectors (<snip>/node_modules/eslint/lib/util/node-event-generator.js:280:22)
    at NodeEventGenerator.enterNode (<snip>/node_modules/eslint/lib/util/node-event-generator.js:294:14)
    at CodePathAnalyzer.enterNode (<snip>/node_modules/eslint/lib/code-path-analysis/code-path-analyzer.js:632:23)

This filename (src/index.js) doesn't match the patterns used by other configs (like in the csf config) so the csf-strict config must not be inheriting the scoping logic when it extends csf.

To Reproduce
Not sure exactly what syntax repros the error, but to confirm that the csf-strict config runs in non-story files, you can:

  1. Create a project with ESLint and eslint-plugin-storybook, extending the csf-strict config.
  2. Create a file in the project that does match a story file name (example: src/index.js)
  3. Export an anonymous object containing a title field.
  4. See an error due to the failing no-title-property-in-meta rule.

Expected behavior
No failing ESLint rules.

Screenshots
n/a

Additional context

eslint-plugin-storybook version 0.6.12

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.