eslint-plugin-storybook 0.2.1 → 0.2.2

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.
@@ -35,41 +35,66 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
35
35
  .split(' ')
36
36
  .filter(Boolean)
37
37
  .join(' ');
38
- // any helper functions should go here or else delete this section
39
38
  //----------------------------------------------------------------------
40
39
  // Public
41
40
  //----------------------------------------------------------------------
42
41
  return {
42
+ // CSF3
43
43
  ExportNamedDeclaration: function (node) {
44
44
  // if there are specifiers, node.declaration should be null
45
45
  if (!node.declaration)
46
46
  return;
47
- const { type } = node.declaration;
48
- if (type === 'TSTypeAliasDeclaration' ||
49
- type === 'TypeAlias' ||
50
- type === 'TSInterfaceDeclaration' ||
51
- type === 'InterfaceDeclaration') {
52
- return;
47
+ const decl = node.declaration;
48
+ if ((0, ast_1.isVariableDeclaration)(decl)) {
49
+ const { id, init } = decl.declarations[0];
50
+ if ((0, ast_1.isIdentifier)(id) && (0, ast_1.isObjectExpression)(init)) {
51
+ const storyNameNode = init.properties.find((prop) => {
52
+ var _a, _b;
53
+ return (0, ast_1.isProperty)(prop) &&
54
+ (0, ast_1.isIdentifier)(prop.key) &&
55
+ (((_a = prop.key) === null || _a === void 0 ? void 0 : _a.name) === 'name' || ((_b = prop.key) === null || _b === void 0 ? void 0 : _b.name) === 'storyName');
56
+ });
57
+ if (!storyNameNode) {
58
+ return;
59
+ }
60
+ const { name } = id;
61
+ const resolvedStoryName = resolveStoryName(name);
62
+ //@ts-ignore
63
+ if ((0, ast_1.isLiteral)(storyNameNode.value) && storyNameNode.value.value === resolvedStoryName) {
64
+ context.report({
65
+ node: storyNameNode,
66
+ messageId: 'storyNameIsRedundant',
67
+ suggest: [
68
+ {
69
+ messageId: 'removeRedundantName',
70
+ fix: function (fixer) {
71
+ return fixer.remove(storyNameNode);
72
+ },
73
+ },
74
+ ],
75
+ });
76
+ }
77
+ }
53
78
  }
54
- const { id: identifier, init: { properties }, } = node.declaration.declarations[0];
55
- if (!properties) {
79
+ },
80
+ // CSF2
81
+ AssignmentExpression: function (node) {
82
+ if (!(0, ast_1.isExpressionStatement)(node.parent))
56
83
  return;
57
- }
58
- const storyNameNode = properties.find(
59
- //@ts-ignore
60
- (prop) => (0, ast_1.isProperty)(prop) && prop.key.name === 'name');
61
- if (storyNameNode) {
62
- const { name } = identifier;
63
- const resolvedStoryName = resolveStoryName(name);
64
- if (storyNameNode.value.value === resolvedStoryName) {
84
+ const { left, right } = node;
85
+ if ((0, ast_1.isIdentifier)(left.property) && left.property.name === 'storyName') {
86
+ const propertyName = left.object.name;
87
+ const propertyValue = right.value;
88
+ const resolvedStoryName = resolveStoryName(propertyName);
89
+ if (propertyValue === resolvedStoryName) {
65
90
  context.report({
66
- node: storyNameNode,
91
+ node: node,
67
92
  messageId: 'storyNameIsRedundant',
68
93
  suggest: [
69
94
  {
70
95
  messageId: 'removeRedundantName',
71
96
  fix: function (fixer) {
72
- return fixer.remove(storyNameNode);
97
+ return fixer.remove(node);
73
98
  },
74
99
  },
75
100
  ],
@@ -4,6 +4,8 @@
4
4
  * @author Yann Braga
5
5
  */
6
6
  const ast_utils_1 = require("@typescript-eslint/experimental-utils/dist/ast-utils");
7
+ const csf_1 = require("@storybook/csf");
8
+ const utils_1 = require("../utils");
7
9
  const ast_1 = require("../utils/ast");
8
10
  const constants_1 = require("../utils/constants");
9
11
  const create_storybook_rule_1 = require("../utils/create-storybook-rule");
@@ -43,10 +45,60 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
43
45
  .replace(new RegExp(/\s/, 'g'), '')
44
46
  .replace(new RegExp(/\w/), (s) => s.toUpperCase()));
45
47
  };
48
+ const checkAndReportError = (id, nonStoryExportsConfig = {}) => {
49
+ const { name } = id;
50
+ if (!(0, csf_1.isExportStory)(name, nonStoryExportsConfig)) {
51
+ return null;
52
+ }
53
+ if (!isPascalCase(name)) {
54
+ context.report({
55
+ node: id,
56
+ messageId: 'usePascalCase',
57
+ data: {
58
+ name,
59
+ },
60
+ suggest: [
61
+ {
62
+ messageId: 'convertToPascalCase',
63
+ *fix(fixer) {
64
+ var _a;
65
+ const fullText = context.getSourceCode().text;
66
+ const fullName = fullText.slice(id.range[0], id.range[1]);
67
+ const suffix = fullName.substring(name.length);
68
+ const pascal = toPascalCase(name);
69
+ yield fixer.replaceTextRange(id.range, pascal + suffix);
70
+ const scope = context.getScope().childScopes[0];
71
+ if (scope) {
72
+ const variable = (0, ast_utils_1.findVariable)(scope, name);
73
+ for (let i = 0; i < ((_a = variable === null || variable === void 0 ? void 0 : variable.references) === null || _a === void 0 ? void 0 : _a.length); i++) {
74
+ const ref = variable.references[i];
75
+ if (!ref.init) {
76
+ yield fixer.replaceTextRange(ref.identifier.range, pascal);
77
+ }
78
+ }
79
+ }
80
+ },
81
+ },
82
+ ],
83
+ });
84
+ }
85
+ };
46
86
  //----------------------------------------------------------------------
47
87
  // Public
48
88
  //----------------------------------------------------------------------
89
+ let meta;
90
+ let nonStoryExportsConfig;
91
+ let namedExports = [];
49
92
  return {
93
+ ExportDefaultDeclaration: function (node) {
94
+ meta = (0, utils_1.getMetaObjectExpression)(node, context);
95
+ if (meta) {
96
+ nonStoryExportsConfig = {
97
+ excludeStories: (0, utils_1.getDescriptor)(meta, 'excludeStories'),
98
+ includeStories: (0, utils_1.getDescriptor)(meta, 'includeStories'),
99
+ };
100
+ }
101
+ },
50
102
  ExportNamedDeclaration: function (node) {
51
103
  // if there are specifiers, node.declaration should be null
52
104
  if (!node.declaration)
@@ -55,38 +107,15 @@ module.exports = (0, create_storybook_rule_1.createStorybookRule)({
55
107
  if ((0, ast_1.isVariableDeclaration)(decl)) {
56
108
  const { id } = decl.declarations[0];
57
109
  if ((0, ast_1.isIdentifier)(id)) {
58
- const { name } = id;
59
- if (!isPascalCase(name)) {
60
- context.report({
61
- node: id,
62
- messageId: 'usePascalCase',
63
- data: {
64
- name,
65
- },
66
- suggest: [
67
- {
68
- messageId: 'convertToPascalCase',
69
- *fix(fixer) {
70
- const fullText = context.getSourceCode().text;
71
- const fullName = fullText.slice(id.range[0], id.range[1]);
72
- const suffix = fullName.substring(name.length);
73
- const pascal = toPascalCase(name);
74
- yield fixer.replaceTextRange(id.range, pascal + suffix);
75
- const variable = (0, ast_utils_1.findVariable)(context.getScope(), name);
76
- for (let i = 0; i < variable.references.length; i++) {
77
- const ref = variable.references[i];
78
- if (!ref.init) {
79
- yield fixer.replaceTextRange(ref.identifier.range, pascal);
80
- }
81
- }
82
- },
83
- },
84
- ],
85
- });
86
- }
110
+ namedExports.push(id);
87
111
  }
88
112
  }
89
113
  },
114
+ 'Program:exit': function () {
115
+ if (namedExports.length) {
116
+ namedExports.forEach((n) => checkAndReportError(n, nonStoryExportsConfig));
117
+ }
118
+ },
90
119
  };
91
120
  },
92
121
  });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getMetaObjectExpression = exports.isPlayFunction = exports.docsUrl = void 0;
3
+ exports.getDescriptor = exports.getMetaObjectExpression = exports.isPlayFunction = exports.docsUrl = void 0;
4
4
  const ast_utils_1 = require("@typescript-eslint/experimental-utils/dist/ast-utils");
5
5
  const ast_1 = require("./ast");
6
6
  const docsUrl = (ruleName) => `https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/${ruleName}.md`;
@@ -25,3 +25,25 @@ const getMetaObjectExpression = (node, context) => {
25
25
  return (0, ast_1.isObjectExpression)(meta) ? meta : null;
26
26
  };
27
27
  exports.getMetaObjectExpression = getMetaObjectExpression;
28
+ const getDescriptor = (metaDeclaration, propertyName) => {
29
+ const property = metaDeclaration && metaDeclaration.properties.find((p) => p.key && p.key.name === propertyName);
30
+ if (!property) {
31
+ return undefined;
32
+ }
33
+ const { type } = property.value;
34
+ switch (type) {
35
+ case 'ArrayExpression':
36
+ return property.value.elements.map((t) => {
37
+ if (!['StringLiteral', 'Literal'].includes(t.type)) {
38
+ throw new Error(`Unexpected descriptor element: ${t.type}`);
39
+ }
40
+ return t.value;
41
+ });
42
+ case 'Literal':
43
+ case 'RegExpLiteral':
44
+ return property.value.value;
45
+ default:
46
+ throw new Error(`Unexpected descriptor: ${type}`);
47
+ }
48
+ };
49
+ exports.getDescriptor = getDescriptor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-storybook",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Best practice rules for Storybook",
5
5
  "keywords": [
6
6
  "eslint",
@@ -45,6 +45,7 @@
45
45
  "release": "yarn build && auto shipit"
46
46
  },
47
47
  "dependencies": {
48
+ "@storybook/csf": "^0.0.1",
48
49
  "@typescript-eslint/experimental-utils": "^5.3.0",
49
50
  "requireindex": "^1.1.0"
50
51
  },