eslint-plugin-storybook 0.4.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,54 @@
1
+ # v0.5.3 (Fri Dec 03 2021)
2
+
3
+ #### 🐛 Bug Fix
4
+
5
+ - extend story-exports rule to support export lists, fixes #57 [#58](https://github.com/storybookjs/eslint-plugin-storybook/pull/58) (stephen@Stephens-MacBook-Air.local [@yannbf](https://github.com/yannbf))
6
+
7
+ #### Authors: 2
8
+
9
+ - Stephen Marsh ([@stephenhmarsh](https://github.com/stephenhmarsh))
10
+ - Yann Braga ([@yannbf](https://github.com/yannbf))
11
+
12
+ ---
13
+
14
+ # v0.5.2 (Thu Dec 02 2021)
15
+
16
+ #### 🐛 Bug Fix
17
+
18
+ - fix: support jsx extension [#60](https://github.com/storybookjs/eslint-plugin-storybook/pull/60) ([@yannbf](https://github.com/yannbf))
19
+ - Update README.md spelling error, one letter. [#50](https://github.com/storybookjs/eslint-plugin-storybook/pull/50) ([@daylennguyen](https://github.com/daylennguyen))
20
+
21
+ #### Authors: 2
22
+
23
+ - Daylen Nguyen ([@daylennguyen](https://github.com/daylennguyen))
24
+ - Yann Braga ([@yannbf](https://github.com/yannbf))
25
+
26
+ ---
27
+
28
+ # v0.5.1 (Mon Nov 29 2021)
29
+
30
+ #### 🐛 Bug Fix
31
+
32
+ - Fix: define hasSuggestions to all fixable rules [#55](https://github.com/storybookjs/eslint-plugin-storybook/pull/55) ([@yannbf](https://github.com/yannbf))
33
+
34
+ #### Authors: 1
35
+
36
+ - Yann Braga ([@yannbf](https://github.com/yannbf))
37
+
38
+ ---
39
+
40
+ # v0.5.0 (Tue Nov 23 2021)
41
+
42
+ #### 🚀 Enhancement
43
+
44
+ - Rule: pass context when invoking play [#53](https://github.com/storybookjs/eslint-plugin-storybook/pull/53) ([@yannbf](https://github.com/yannbf))
45
+
46
+ #### Authors: 1
47
+
48
+ - Yann Braga ([@yannbf](https://github.com/yannbf))
49
+
50
+ ---
51
+
1
52
  # v0.4.2 (Fri Nov 19 2021)
2
53
 
3
54
  #### 🐛 Bug Fix
package/README.md CHANGED
@@ -72,12 +72,12 @@ Optionally, you can override, add or disable rules settings. You likely don't wa
72
72
  "overrides": [
73
73
  {
74
74
  // or whatever matches stories specified in .storybook/main.js
75
- "files": ['*.stories.@(ts|tsx|js|mjs|cjs)'],
75
+ "files": ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)'],
76
76
  "rules": {
77
77
  // example of overriding a rule
78
78
  'storybook/hierarchy-separator': 'error',
79
79
  // example of disabling a rule
80
- 'storybook/defaul-exports': 'off',
80
+ 'storybook/default-exports': 'off',
81
81
  }
82
82
  }
83
83
  ]
@@ -96,19 +96,20 @@ This plugin does not support MDX files.
96
96
 
97
97
  **Configurations**: csf, csf-strict, addon-interactions, recommended
98
98
 
99
- | Name | Description | 🔧 | Included in configurations |
100
- | ------------------------------------------------------------------------------------------ | --------------------------------------------------- | --- | -------------------------------------------------------- |
101
- | [`storybook/await-interactions`](./docs/rules/await-interactions.md) | Interactions should be awaited | 🔧 | <ul><li>addon-interactions</li><li>recommended</li></ul> |
102
- | [`storybook/csf-component`](./docs/rules/csf-component.md) | The component property should be set | | <ul><li>csf</li></ul> |
103
- | [`storybook/default-exports`](./docs/rules/default-exports.md) | Story files should have a default export | 🔧 | <ul><li>csf</li><li>recommended</li></ul> |
104
- | [`storybook/hierarchy-separator`](./docs/rules/hierarchy-separator.md) | Deprecated hierachy separator in title property | 🔧 | <ul><li>csf</li><li>recommended</li></ul> |
105
- | [`storybook/no-redundant-story-name`](./docs/rules/no-redundant-story-name.md) | A story should not have a redundant name property | 🔧 | <ul><li>csf</li><li>recommended</li></ul> |
106
- | [`storybook/no-stories-of`](./docs/rules/no-stories-of.md) | storiesOf is deprecated and should not be used | | <ul><li>csf-strict</li></ul> |
107
- | [`storybook/no-title-property-in-meta`](./docs/rules/no-title-property-in-meta.md) | Do not define a title in meta | 🔧 | <ul><li>csf-strict</li></ul> |
108
- | [`storybook/prefer-pascal-case`](./docs/rules/prefer-pascal-case.md) | Stories should use PascalCase | 🔧 | <ul><li>recommended</li></ul> |
109
- | [`storybook/story-exports`](./docs/rules/story-exports.md) | A story file must contain at least one story export | 🔧 | <ul><li>recommended</li><li>csf</li></ul> |
110
- | [`storybook/use-storybook-expect`](./docs/rules/use-storybook-expect.md) | Use expect from `@storybook/jest` | 🔧 | <ul><li>addon-interactions</li><li>recommended</li></ul> |
111
- | [`storybook/use-storybook-testing-library`](./docs/rules/use-storybook-testing-library.md) | Do not use testing-library directly on stories | 🔧 | <ul><li>addon-interactions</li><li>recommended</li></ul> |
99
+ | Name | Description | 🔧 | Included in configurations |
100
+ | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | --- | -------------------------------------------------------- |
101
+ | [`storybook/await-interactions`](./docs/rules/await-interactions.md) | Interactions should be awaited | 🔧 | <ul><li>addon-interactions</li><li>recommended</li></ul> |
102
+ | [`storybook/context-in-play-function`](./docs/rules/context-in-play-function.md) | Pass a context when invoking play function of another story | | <ul><li>recommended</li><li>addon-interactions</li></ul> |
103
+ | [`storybook/csf-component`](./docs/rules/csf-component.md) | The component property should be set | | <ul><li>csf</li></ul> |
104
+ | [`storybook/default-exports`](./docs/rules/default-exports.md) | Story files should have a default export | 🔧 | <ul><li>csf</li><li>recommended</li></ul> |
105
+ | [`storybook/hierarchy-separator`](./docs/rules/hierarchy-separator.md) | Deprecated hierachy separator in title property | 🔧 | <ul><li>csf</li><li>recommended</li></ul> |
106
+ | [`storybook/no-redundant-story-name`](./docs/rules/no-redundant-story-name.md) | A story should not have a redundant name property | 🔧 | <ul><li>csf</li><li>recommended</li></ul> |
107
+ | [`storybook/no-stories-of`](./docs/rules/no-stories-of.md) | storiesOf is deprecated and should not be used | | <ul><li>csf-strict</li></ul> |
108
+ | [`storybook/no-title-property-in-meta`](./docs/rules/no-title-property-in-meta.md) | Do not define a title in meta | 🔧 | <ul><li>csf-strict</li></ul> |
109
+ | [`storybook/prefer-pascal-case`](./docs/rules/prefer-pascal-case.md) | Stories should use PascalCase | 🔧 | <ul><li>recommended</li></ul> |
110
+ | [`storybook/story-exports`](./docs/rules/story-exports.md) | A story file must contain at least one story export | | <ul><li>recommended</li><li>csf</li></ul> |
111
+ | [`storybook/use-storybook-expect`](./docs/rules/use-storybook-expect.md) | Use expect from `@storybook/jest` | 🔧 | <ul><li>addon-interactions</li><li>recommended</li></ul> |
112
+ | [`storybook/use-storybook-testing-library`](./docs/rules/use-storybook-testing-library.md) | Do not use testing-library directly on stories | 🔧 | <ul><li>addon-interactions</li><li>recommended</li></ul> |
112
113
 
113
114
  <!-- RULES-LIST:END -->
114
115
 
@@ -3,10 +3,11 @@ module.exports = {
3
3
  plugins: ['storybook'],
4
4
  overrides: [
5
5
  {
6
- files: ['*.stories.@(ts|tsx|js|mjs|cjs)', '*.story.@(ts|tsx|js|mjs|cjs)'],
6
+ files: ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)', '*.story.@(ts|tsx|js|jsx|mjs|cjs)'],
7
7
  rules: {
8
8
  'import/no-anonymous-default-export': 'off',
9
9
  'storybook/await-interactions': 'error',
10
+ 'storybook/context-in-play-function': 'error',
10
11
  'storybook/use-storybook-expect': 'error',
11
12
  'storybook/use-storybook-testing-library': 'error',
12
13
  },
@@ -3,7 +3,7 @@ module.exports = {
3
3
  plugins: ['storybook'],
4
4
  overrides: [
5
5
  {
6
- files: ['*.stories.@(ts|tsx|js|mjs|cjs)', '*.story.@(ts|tsx|js|mjs|cjs)'],
6
+ files: ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)', '*.story.@(ts|tsx|js|jsx|mjs|cjs)'],
7
7
  rules: {
8
8
  'import/no-anonymous-default-export': 'off',
9
9
  'storybook/csf-component': 'warn',
@@ -3,10 +3,11 @@ module.exports = {
3
3
  plugins: ['storybook'],
4
4
  overrides: [
5
5
  {
6
- files: ['*.stories.@(ts|tsx|js|mjs|cjs)', '*.story.@(ts|tsx|js|mjs|cjs)'],
6
+ files: ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)', '*.story.@(ts|tsx|js|jsx|mjs|cjs)'],
7
7
  rules: {
8
8
  'import/no-anonymous-default-export': 'off',
9
9
  'storybook/await-interactions': 'error',
10
+ 'storybook/context-in-play-function': 'error',
10
11
  'storybook/default-exports': 'error',
11
12
  'storybook/hierarchy-separator': 'warn',
12
13
  'storybook/no-redundant-story-name': 'warn',
@@ -21,6 +21,7 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
21
21
  },
22
22
  type: 'problem',
23
23
  fixable: 'code',
24
+ hasSuggestions: true,
24
25
  schema: [],
25
26
  },
26
27
  create(context) {
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Pass a context object when invoking a play function
4
+ * @author Yann Braga
5
+ */
6
+ const create_storybook_rule_1 = require("../utils/create-storybook-rule");
7
+ const constants_1 = require("../utils/constants");
8
+ const ast_1 = require("../utils/ast");
9
+ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
10
+ name: 'context-in-play-function',
11
+ defaultOptions: [],
12
+ meta: {
13
+ type: 'problem',
14
+ docs: {
15
+ description: 'Pass a context when invoking play function of another story',
16
+ categories: [constants_1.CategoryId.RECOMMENDED, constants_1.CategoryId.ADDON_INTERACTIONS],
17
+ recommended: 'error',
18
+ },
19
+ messages: {
20
+ passContextToPlayFunction: 'Pass a context when invoking play function of another story',
21
+ },
22
+ fixable: null,
23
+ schema: [],
24
+ },
25
+ create(context) {
26
+ // variables should be defined here
27
+ //----------------------------------------------------------------------
28
+ // Helpers
29
+ //----------------------------------------------------------------------
30
+ // any helper functions should go here or else delete this section
31
+ const isPlayFunctionFromAnotherStory = (expr) => {
32
+ if ((0, ast_1.isTSNonNullExpression)(expr.callee) &&
33
+ (0, ast_1.isMemberExpression)(expr.callee.expression) &&
34
+ (0, ast_1.isIdentifier)(expr.callee.expression.property) &&
35
+ expr.callee.expression.property.name === 'play') {
36
+ return true;
37
+ }
38
+ if ((0, ast_1.isMemberExpression)(expr.callee) &&
39
+ (0, ast_1.isIdentifier)(expr.callee.property) &&
40
+ expr.callee.property.name === 'play') {
41
+ return true;
42
+ }
43
+ return false;
44
+ };
45
+ // Expression passing an argument called context OR spreading a variable called context
46
+ const isNotPassingContextCorrectly = (expr) => {
47
+ const firstExpressionArgument = expr.arguments[0];
48
+ if (!firstExpressionArgument) {
49
+ return true;
50
+ }
51
+ if (expr.arguments.length === 1 &&
52
+ (0, ast_1.isIdentifier)(firstExpressionArgument) &&
53
+ firstExpressionArgument.name === 'context') {
54
+ return false;
55
+ }
56
+ if ((0, ast_1.isObjectExpression)(firstExpressionArgument) &&
57
+ firstExpressionArgument.properties.some((prop) => {
58
+ return ((0, ast_1.isSpreadElement)(prop) && (0, ast_1.isIdentifier)(prop.argument) && prop.argument.name === 'context');
59
+ })) {
60
+ return false;
61
+ }
62
+ return true;
63
+ };
64
+ //----------------------------------------------------------------------
65
+ // Public
66
+ //----------------------------------------------------------------------
67
+ let invocationsWithoutProperContext = [];
68
+ return {
69
+ CallExpression(node) {
70
+ if (isPlayFunctionFromAnotherStory(node) && isNotPassingContextCorrectly(node)) {
71
+ invocationsWithoutProperContext.push(node);
72
+ }
73
+ },
74
+ 'Program:exit': function () {
75
+ invocationsWithoutProperContext.forEach((node) => {
76
+ context.report({
77
+ node,
78
+ messageId: 'passContextToPlayFunction',
79
+ });
80
+ });
81
+ },
82
+ };
83
+ },
84
+ });
@@ -25,6 +25,7 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
25
25
  fixSuggestion: 'Add default export',
26
26
  },
27
27
  fixable: 'code',
28
+ hasSuggestions: true,
28
29
  schema: [],
29
30
  },
30
31
  create(context) {
@@ -13,6 +13,7 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
13
13
  meta: {
14
14
  type: 'problem',
15
15
  fixable: 'code',
16
+ hasSuggestions: true,
16
17
  docs: {
17
18
  description: 'Deprecated hierachy separator in title property',
18
19
  categories: [constants_1.CategoryId.CSF, constants_1.CategoryId.RECOMMENDED],
@@ -12,6 +12,7 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
12
12
  meta: {
13
13
  type: 'suggestion',
14
14
  fixable: 'code',
15
+ hasSuggestions: true,
15
16
  docs: {
16
17
  description: 'A story should not have a redundant name property',
17
18
  categories: [constants_1.CategoryId.CSF, constants_1.CategoryId.RECOMMENDED],
@@ -12,6 +12,7 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
12
12
  meta: {
13
13
  type: 'problem',
14
14
  fixable: 'code',
15
+ hasSuggestions: true,
15
16
  docs: {
16
17
  description: 'Do not define a title in meta',
17
18
  categories: [constants_1.CategoryId.CSF_STRICT],
@@ -15,6 +15,7 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
15
15
  meta: {
16
16
  type: 'suggestion',
17
17
  fixable: 'code',
18
+ hasSuggestions: true,
18
19
  docs: {
19
20
  description: 'Stories should use PascalCase',
20
21
  categories: [constants_1.CategoryId.RECOMMENDED],
@@ -3,11 +3,9 @@
3
3
  * @fileoverview A story file must contain at least one story export
4
4
  * @author Yann Braga
5
5
  */
6
- const csf_1 = require("@storybook/csf");
7
6
  const create_storybook_rule_1 = require("../utils/create-storybook-rule");
8
7
  const constants_1 = require("../utils/constants");
9
8
  const utils_1 = require("../utils");
10
- const ast_1 = require("../utils/ast");
11
9
  module.exports = (0, create_storybook_rule_1.createStorybookRule)({
12
10
  name: 'story-exports',
13
11
  defaultOptions: [],
@@ -22,7 +20,7 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
22
20
  shouldHaveStoryExport: 'The file should have at least one story export',
23
21
  addStoryExport: 'Add a story export',
24
22
  },
25
- fixable: 'code',
23
+ fixable: null,
26
24
  schema: [],
27
25
  },
28
26
  create(context) {
@@ -30,7 +28,6 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
30
28
  //----------------------------------------------------------------------
31
29
  // Helpers
32
30
  //----------------------------------------------------------------------
33
- const isValidStoryExport = (node) => (0, csf_1.isExportStory)(node.name, nonStoryExportsConfig) && node.name !== '__namedExportsOrder';
34
31
  //----------------------------------------------------------------------
35
32
  // Public
36
33
  //----------------------------------------------------------------------
@@ -54,22 +51,13 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
54
51
  }
55
52
  },
56
53
  ExportNamedDeclaration: function (node) {
57
- // if there are specifiers, node.declaration should be null
58
- if (!node.declaration)
59
- return;
60
- const decl = node.declaration;
61
- if ((0, ast_1.isVariableDeclaration)(decl)) {
62
- const { id } = decl.declarations[0];
63
- if ((0, ast_1.isIdentifier)(id)) {
64
- namedExports.push(id);
65
- }
66
- }
54
+ namedExports.push(...(0, utils_1.getAllNamedExports)(node));
67
55
  },
68
56
  'Program:exit': function (node) {
69
57
  if (hasStoriesOfImport || !meta) {
70
58
  return;
71
59
  }
72
- const storyExports = namedExports.filter(isValidStoryExport);
60
+ const storyExports = namedExports.filter((exp) => (0, utils_1.isValidStoryExport)(exp, nonStoryExportsConfig));
73
61
  if (storyExports.length) {
74
62
  return;
75
63
  }
package/dist/utils/ast.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isTSNonNullExpression = exports.isTSAsExpression = exports.isTSInterfaceDeclaration = exports.isTSTypeAliasDeclaration = exports.isProgram = exports.isFunctionExpression = exports.isReturnStatement = exports.isProperty = exports.isObjectPattern = exports.isObjectExpression = exports.isNewExpression = exports.isMemberExpression = exports.isLiteral = exports.isJSXAttribute = exports.isImportSpecifier = exports.isImportNamespaceSpecifier = exports.isImportDefaultSpecifier = exports.isImportDeclaration = exports.isSequenceExpression = exports.isAssignmentExpression = exports.isVariableDeclaration = exports.isExpressionStatement = exports.isCallExpression = exports.isBlockStatement = exports.isArrowFunctionExpression = exports.isArrayExpression = exports.isVariableDeclarator = exports.isIdentifier = exports.isAwaitExpression = exports.ASTUtils = void 0;
3
+ exports.isTSNonNullExpression = exports.isTSAsExpression = exports.isTSInterfaceDeclaration = exports.isTSTypeAliasDeclaration = exports.isProgram = exports.isFunctionExpression = exports.isReturnStatement = exports.isSpreadElement = exports.isProperty = exports.isObjectPattern = exports.isObjectExpression = exports.isNewExpression = exports.isMemberExpression = exports.isLiteral = exports.isJSXAttribute = exports.isImportSpecifier = exports.isImportNamespaceSpecifier = exports.isImportDefaultSpecifier = exports.isImportDeclaration = exports.isSequenceExpression = exports.isAssignmentExpression = exports.isVariableDeclaration = exports.isExpressionStatement = exports.isCallExpression = exports.isBlockStatement = exports.isArrowFunctionExpression = exports.isArrayExpression = exports.isVariableDeclarator = exports.isIdentifier = exports.isAwaitExpression = exports.ASTUtils = void 0;
4
4
  const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
5
5
  var experimental_utils_2 = require("@typescript-eslint/experimental-utils");
6
6
  Object.defineProperty(exports, "ASTUtils", { enumerable: true, get: function () { return experimental_utils_2.ASTUtils; } });
@@ -27,6 +27,7 @@ exports.isNewExpression = isNodeOfType(experimental_utils_1.AST_NODE_TYPES.NewEx
27
27
  exports.isObjectExpression = isNodeOfType(experimental_utils_1.AST_NODE_TYPES.ObjectExpression);
28
28
  exports.isObjectPattern = isNodeOfType(experimental_utils_1.AST_NODE_TYPES.ObjectPattern);
29
29
  exports.isProperty = isNodeOfType(experimental_utils_1.AST_NODE_TYPES.Property);
30
+ exports.isSpreadElement = isNodeOfType(experimental_utils_1.AST_NODE_TYPES.SpreadElement);
30
31
  exports.isReturnStatement = isNodeOfType(experimental_utils_1.AST_NODE_TYPES.ReturnStatement);
31
32
  exports.isFunctionExpression = isNodeOfType(experimental_utils_1.AST_NODE_TYPES.FunctionExpression);
32
33
  exports.isProgram = isNodeOfType(experimental_utils_1.AST_NODE_TYPES.Program);
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDescriptor = exports.getMetaObjectExpression = exports.isPlayFunction = exports.docsUrl = void 0;
3
+ exports.getAllNamedExports = exports.isValidStoryExport = exports.getDescriptor = exports.getMetaObjectExpression = exports.isPlayFunction = exports.docsUrl = void 0;
4
+ const csf_1 = require("@storybook/csf");
4
5
  const ast_utils_1 = require("@typescript-eslint/experimental-utils/dist/ast-utils");
5
6
  const ast_1 = require("./ast");
6
7
  const docsUrl = (ruleName) => `https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/${ruleName}.md`;
@@ -47,3 +48,26 @@ const getDescriptor = (metaDeclaration, propertyName) => {
47
48
  }
48
49
  };
49
50
  exports.getDescriptor = getDescriptor;
51
+ const isValidStoryExport = (node, nonStoryExportsConfig) => (0, csf_1.isExportStory)(node.name, nonStoryExportsConfig) && node.name !== '__namedExportsOrder';
52
+ exports.isValidStoryExport = isValidStoryExport;
53
+ const getAllNamedExports = (node) => {
54
+ // e.g. export { MyStory }
55
+ if (!node.declaration && node.specifiers) {
56
+ return node.specifiers.reduce((acc, specifier) => {
57
+ if ((0, ast_1.isIdentifier)(specifier.exported)) {
58
+ acc.push(specifier.exported);
59
+ }
60
+ return acc;
61
+ }, []);
62
+ }
63
+ const decl = node.declaration;
64
+ if ((0, ast_1.isVariableDeclaration)(decl)) {
65
+ const { id } = decl.declarations[0];
66
+ // e.g. export const MyStory
67
+ if ((0, ast_1.isIdentifier)(id)) {
68
+ return [id];
69
+ }
70
+ }
71
+ return [];
72
+ };
73
+ exports.getAllNamedExports = getAllNamedExports;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-storybook",
3
- "version": "0.4.2",
3
+ "version": "0.5.3",
4
4
  "description": "Best practice rules for Storybook",
5
5
  "keywords": [
6
6
  "eslint",
@@ -71,7 +71,6 @@
71
71
  "ts-jest": "^27.0.7",
72
72
  "ts-migrate": "^0.1.26",
73
73
  "ts-node": "^10.4.0",
74
- "tsc": "^2.0.3",
75
74
  "typescript": "^4.4.4"
76
75
  },
77
76
  "engines": {