story-exports.js 3.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. "use strict";
  2. /**
  3. * @fileoverview A story file must contain at least one story export
  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 utils_1 = require("../utils");
  9. const ast_1 = require("../utils/ast");
  10. module.exports = (0, create_storybook_rule_1.createStorybookRule)({
  11. name: 'story-exports',
  12. defaultOptions: [],
  13. meta: {
  14. type: 'problem',
  15. docs: {
  16. description: 'A story file must contain at least one story export',
  17. categories: [constants_1.CategoryId.RECOMMENDED, constants_1.CategoryId.CSF],
  18. recommended: 'error',
  19. },
  20. messages: {
  21. shouldHaveStoryExport: 'The file should have at least one story export',
  22. shouldHaveStoryExportWithFilters: 'The file should have at least one story export. Make sure the includeStories/excludeStories you defined are correct, otherwise Storybook will not use any stories for this file.',
  23. addStoryExport: 'Add a story export',
  24. },
  25. fixable: undefined,
  26. schema: [],
  27. },
  28. create(context) {
  29. // variables should be defined here
  30. //----------------------------------------------------------------------
  31. // Helpers
  32. //----------------------------------------------------------------------
  33. //----------------------------------------------------------------------
  34. // Public
  35. //----------------------------------------------------------------------
  36. let hasStoriesOfImport = false;
  37. let nonStoryExportsConfig = {};
  38. let meta;
  39. const namedExports = [];
  40. return {
  41. ImportSpecifier(node) {
  42. if (node.imported.name === 'storiesOf') {
  43. hasStoriesOfImport = true;
  44. }
  45. },
  46. ExportDefaultDeclaration: function (node) {
  47. meta = (0, utils_1.getMetaObjectExpression)(node, context);
  48. if (meta) {
  49. try {
  50. nonStoryExportsConfig = {
  51. excludeStories: (0, utils_1.getDescriptor)(meta, 'excludeStories'),
  52. includeStories: (0, utils_1.getDescriptor)(meta, 'includeStories'),
  53. };
  54. }
  55. catch (err) {
  56. //
  57. }
  58. }
  59. },
  60. ExportNamedDeclaration: function (node) {
  61. namedExports.push(...(0, utils_1.getAllNamedExports)(node));
  62. },
  63. 'Program:exit': function (program) {
  64. if (hasStoriesOfImport || !meta) {
  65. return;
  66. }
  67. const storyExports = namedExports.filter((exp) => (0, utils_1.isValidStoryExport)(exp, nonStoryExportsConfig));
  68. if (storyExports.length) {
  69. return;
  70. }
  71. const firstNonImportStatement = program.body.find((n) => !(0, ast_1.isImportDeclaration)(n));
  72. const node = firstNonImportStatement || program.body[0] || program;
  73. // @TODO: bring apply this autofix with CSF3 release
  74. // const fix = (fixer) => fixer.insertTextAfter(node, `\n\nexport const Default = {}`)
  75. const hasFilter = nonStoryExportsConfig.includeStories || nonStoryExportsConfig.excludeStories;
  76. const report = {
  77. node,
  78. messageId: hasFilter ? 'shouldHaveStoryExportWithFilters' : 'shouldHaveStoryExport',
  79. // fix,
  80. };
  81. context.report(report);
  82. },
  83. };
  84. },
  85. });