default-exports.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. "use strict";
  2. /**
  3. * @fileoverview Story files should have a default export
  4. * @author Yann Braga
  5. */
  6. var __importDefault = (this && this.__importDefault) || function (mod) {
  7. return (mod && mod.__esModule) ? mod : { "default": mod };
  8. };
  9. const path_1 = __importDefault(require("path"));
  10. const constants_1 = require("../utils/constants");
  11. const ast_1 = require("../utils/ast");
  12. const create_storybook_rule_1 = require("../utils/create-storybook-rule");
  13. module.exports = (0, create_storybook_rule_1.createStorybookRule)({
  14. name: 'default-exports',
  15. defaultOptions: [],
  16. meta: {
  17. type: 'problem',
  18. docs: {
  19. description: 'Story files should have a default export',
  20. categories: [constants_1.CategoryId.CSF, constants_1.CategoryId.RECOMMENDED],
  21. recommended: 'error',
  22. },
  23. messages: {
  24. shouldHaveDefaultExport: 'The file should have a default export.',
  25. fixSuggestion: 'Add default export',
  26. },
  27. fixable: 'code',
  28. hasSuggestions: true,
  29. schema: [],
  30. },
  31. create(context) {
  32. // variables should be defined here
  33. //----------------------------------------------------------------------
  34. // Helpers
  35. //----------------------------------------------------------------------
  36. // any helper functions should go here or else delete this section
  37. const getComponentName = (node, filePath) => {
  38. const name = path_1.default.basename(filePath).split('.')[0];
  39. const imported = node.body.find((stmt) => {
  40. if ((0, ast_1.isImportDeclaration)(stmt) &&
  41. (0, ast_1.isLiteral)(stmt.source) &&
  42. stmt.source.value.startsWith(`./${name}`)) {
  43. return !!stmt.specifiers.find((spec) => (0, ast_1.isIdentifier)(spec.local) && spec.local.name === name);
  44. }
  45. });
  46. return imported ? name : null;
  47. };
  48. //----------------------------------------------------------------------
  49. // Public
  50. //----------------------------------------------------------------------
  51. let hasDefaultExport = false;
  52. let hasStoriesOfImport = false;
  53. return {
  54. ImportSpecifier(node) {
  55. if (node.imported.name === 'storiesOf') {
  56. hasStoriesOfImport = true;
  57. }
  58. },
  59. ExportDefaultSpecifier: function () {
  60. hasDefaultExport = true;
  61. },
  62. ExportDefaultDeclaration: function () {
  63. hasDefaultExport = true;
  64. },
  65. 'Program:exit': function (program) {
  66. if (!hasDefaultExport && !hasStoriesOfImport) {
  67. const componentName = getComponentName(program, context.getFilename());
  68. const firstNonImportStatement = program.body.find((n) => !(0, ast_1.isImportDeclaration)(n));
  69. const node = firstNonImportStatement || program.body[0] || program;
  70. const report = {
  71. node,
  72. messageId: 'shouldHaveDefaultExport',
  73. };
  74. const fix = (fixer) => {
  75. const metaDeclaration = componentName
  76. ? `export default { component: ${componentName} }\n`
  77. : 'export default {}\n';
  78. return fixer.insertTextBefore(node, metaDeclaration);
  79. };
  80. context.report(Object.assign(Object.assign({}, report), { fix, suggest: [
  81. {
  82. messageId: 'fixSuggestion',
  83. fix,
  84. },
  85. ] }));
  86. }
  87. },
  88. };
  89. },
  90. });