prefer-pascal-case.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. "use strict";
  2. /**
  3. * @fileoverview Prefer pascal case
  4. * @author Yann Braga
  5. */
  6. const utils_1 = require("@typescript-eslint/utils");
  7. const csf_1 = require("@storybook/csf");
  8. const utils_2 = require("../utils");
  9. const ast_1 = require("../utils/ast");
  10. const constants_1 = require("../utils/constants");
  11. const create_storybook_rule_1 = require("../utils/create-storybook-rule");
  12. module.exports = (0, create_storybook_rule_1.createStorybookRule)({
  13. name: 'prefer-pascal-case',
  14. defaultOptions: [],
  15. meta: {
  16. type: 'suggestion',
  17. fixable: 'code',
  18. hasSuggestions: true,
  19. docs: {
  20. description: 'Stories should use PascalCase',
  21. categories: [constants_1.CategoryId.RECOMMENDED],
  22. recommended: 'warn',
  23. },
  24. messages: {
  25. convertToPascalCase: 'Use pascal case',
  26. usePascalCase: 'The story should use PascalCase notation: {{name}}',
  27. },
  28. schema: [],
  29. },
  30. create(context) {
  31. // variables should be defined here
  32. //----------------------------------------------------------------------
  33. // Helpers
  34. //----------------------------------------------------------------------
  35. const isPascalCase = (str) => /^[A-Z]+([a-z0-9]?)+/.test(str);
  36. const toPascalCase = (str) => {
  37. return str
  38. .replace(new RegExp(/[-_]+/, 'g'), ' ')
  39. .replace(new RegExp(/[^\w\s]/, 'g'), '')
  40. .replace(new RegExp(/\s+(.)(\w+)/, 'g'), (_, $2, $3) => `${$2.toUpperCase() + $3.toLowerCase()}`)
  41. .replace(new RegExp(/\s/, 'g'), '')
  42. .replace(new RegExp(/\w/), (s) => s.toUpperCase());
  43. };
  44. const checkAndReportError = (id, nonStoryExportsConfig = {}) => {
  45. const { name } = id;
  46. if (!(0, csf_1.isExportStory)(name, nonStoryExportsConfig) || name === '__namedExportsOrder') {
  47. return null;
  48. }
  49. if (!name.startsWith('_') && !isPascalCase(name)) {
  50. context.report({
  51. node: id,
  52. messageId: 'usePascalCase',
  53. data: {
  54. name,
  55. },
  56. suggest: [
  57. {
  58. messageId: 'convertToPascalCase',
  59. *fix(fixer) {
  60. var _a;
  61. const fullText = context.getSourceCode().text;
  62. const fullName = fullText.slice(id.range[0], id.range[1]);
  63. const suffix = fullName.substring(name.length);
  64. const pascal = toPascalCase(name);
  65. yield fixer.replaceTextRange(id.range, pascal + suffix);
  66. const scope = context.getScope().childScopes[0];
  67. if (scope) {
  68. const variable = utils_1.ASTUtils.findVariable(scope, name);
  69. const referenceCount = ((_a = variable === null || variable === void 0 ? void 0 : variable.references) === null || _a === void 0 ? void 0 : _a.length) || 0;
  70. for (let i = 0; i < referenceCount; i++) {
  71. const ref = variable === null || variable === void 0 ? void 0 : variable.references[i];
  72. if (ref && !ref.init) {
  73. yield fixer.replaceTextRange(ref.identifier.range, pascal);
  74. }
  75. }
  76. }
  77. },
  78. },
  79. ],
  80. });
  81. }
  82. };
  83. //----------------------------------------------------------------------
  84. // Public
  85. //----------------------------------------------------------------------
  86. let meta;
  87. let nonStoryExportsConfig;
  88. const namedExports = [];
  89. let hasStoriesOfImport = false;
  90. return {
  91. ImportSpecifier(node) {
  92. if (node.imported.name === 'storiesOf') {
  93. hasStoriesOfImport = true;
  94. }
  95. },
  96. ExportDefaultDeclaration: function (node) {
  97. meta = (0, utils_2.getMetaObjectExpression)(node, context);
  98. if (meta) {
  99. try {
  100. nonStoryExportsConfig = {
  101. excludeStories: (0, utils_2.getDescriptor)(meta, 'excludeStories'),
  102. includeStories: (0, utils_2.getDescriptor)(meta, 'includeStories'),
  103. };
  104. }
  105. catch (err) {
  106. //
  107. }
  108. }
  109. },
  110. ExportNamedDeclaration: function (node) {
  111. // if there are specifiers, node.declaration should be null
  112. if (!node.declaration)
  113. return;
  114. const decl = node.declaration;
  115. if ((0, ast_1.isVariableDeclaration)(decl)) {
  116. const declaration = decl.declarations[0];
  117. if (declaration == null)
  118. return;
  119. const { id } = declaration;
  120. if ((0, ast_1.isIdentifier)(id)) {
  121. namedExports.push(id);
  122. }
  123. }
  124. },
  125. 'Program:exit': function () {
  126. if (namedExports.length && !hasStoriesOfImport) {
  127. namedExports.forEach((n) => checkAndReportError(n, nonStoryExportsConfig));
  128. }
  129. },
  130. };
  131. },
  132. });