context-in-play-function.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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: undefined,
  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. const getParentParameterName = (node) => {
  46. if (!(0, ast_1.isArrowFunctionExpression)(node)) {
  47. if (!node.parent) {
  48. return undefined;
  49. }
  50. return getParentParameterName(node.parent);
  51. }
  52. // No parameter found
  53. if (node.params.length === 0) {
  54. return undefined;
  55. }
  56. if (node.params.length >= 1) {
  57. const param = node.params[0];
  58. if ((0, ast_1.isIdentifier)(param)) {
  59. return param.name;
  60. }
  61. if ((0, ast_1.isObjectPattern)(param)) {
  62. const restElement = param.properties.find(ast_1.isRestElement);
  63. if (!restElement || !(0, ast_1.isIdentifier)(restElement.argument)) {
  64. // No rest element found
  65. return undefined;
  66. }
  67. return restElement.argument.name;
  68. }
  69. }
  70. return undefined;
  71. };
  72. // Expression passing an argument called context OR spreading a variable called context
  73. const isNotPassingContextCorrectly = (expr) => {
  74. const firstExpressionArgument = expr.arguments[0];
  75. if (!firstExpressionArgument) {
  76. return true;
  77. }
  78. const contextVariableName = getParentParameterName(expr);
  79. if (!contextVariableName) {
  80. return true;
  81. }
  82. if (expr.arguments.length === 1 &&
  83. (0, ast_1.isIdentifier)(firstExpressionArgument) &&
  84. firstExpressionArgument.name === contextVariableName) {
  85. return false;
  86. }
  87. if ((0, ast_1.isObjectExpression)(firstExpressionArgument) &&
  88. firstExpressionArgument.properties.some((prop) => {
  89. return ((0, ast_1.isSpreadElement)(prop) &&
  90. (0, ast_1.isIdentifier)(prop.argument) &&
  91. prop.argument.name === contextVariableName);
  92. })) {
  93. return false;
  94. }
  95. return true;
  96. };
  97. //----------------------------------------------------------------------
  98. // Public
  99. //----------------------------------------------------------------------
  100. const invocationsWithoutProperContext = [];
  101. return {
  102. CallExpression(node) {
  103. if (isPlayFunctionFromAnotherStory(node) && isNotPassingContextCorrectly(node)) {
  104. invocationsWithoutProperContext.push(node);
  105. }
  106. },
  107. 'Program:exit': function () {
  108. invocationsWithoutProperContext.forEach((node) => {
  109. context.report({
  110. node,
  111. messageId: 'passContextToPlayFunction',
  112. });
  113. });
  114. },
  115. };
  116. },
  117. });