FindExportedDefinitionsResolver.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import isExportsOrModuleAssignment from '../utils/isExportsOrModuleAssignment.js';
  2. import resolveExportDeclaration from '../utils/resolveExportDeclaration.js';
  3. import resolveToValue from '../utils/resolveToValue.js';
  4. import { visitors } from '@babel/traverse';
  5. import { shallowIgnoreVisitors } from '../utils/traverse.js';
  6. import findComponentDefinition from '../utils/findComponentDefinition.js';
  7. import { ERROR_CODES, ReactDocgenError } from '../error.js';
  8. function exportDeclaration(path, state) {
  9. resolveExportDeclaration(path).forEach((exportedPath) => {
  10. const definition = findComponentDefinition(exportedPath);
  11. if (definition) {
  12. if (state.limit > 0 && state.foundDefinitions.size > 0) {
  13. // If a file exports multiple components, ... complain!
  14. throw new ReactDocgenError(ERROR_CODES.MULTIPLE_DEFINITIONS);
  15. }
  16. state.foundDefinitions.add(definition);
  17. }
  18. });
  19. return path.skip();
  20. }
  21. function assignmentExpressionVisitor(path, state) {
  22. // Ignore anything that is not `exports.X = ...;` or
  23. // `module.exports = ...;`
  24. if (!isExportsOrModuleAssignment(path)) {
  25. return path.skip();
  26. }
  27. // Resolve the value of the right hand side. It should resolve to a call
  28. // expression, something like React.createClass
  29. const resolvedPath = resolveToValue(path.get('right'));
  30. const definition = findComponentDefinition(resolvedPath);
  31. if (definition) {
  32. if (state.limit > 0 && state.foundDefinitions.size > 0) {
  33. // If a file exports multiple components, ... complain!
  34. throw new ReactDocgenError(ERROR_CODES.MULTIPLE_DEFINITIONS);
  35. }
  36. state.foundDefinitions.add(definition);
  37. }
  38. return path.skip();
  39. }
  40. const explodedVisitors = visitors.explode({
  41. ...shallowIgnoreVisitors,
  42. ExportNamedDeclaration: { enter: exportDeclaration },
  43. ExportDefaultDeclaration: { enter: exportDeclaration },
  44. AssignmentExpression: { enter: assignmentExpressionVisitor },
  45. });
  46. /**
  47. * Given an AST, this function tries to find the exported component definitions.
  48. *
  49. * The component definitions are either the ObjectExpression passed to
  50. * `React.createClass` or a `class` definition extending `React.Component` or
  51. * having a `render()` method.
  52. *
  53. * If a definition is part of the following statements, it is considered to be
  54. * exported:
  55. *
  56. * modules.exports = Definition;
  57. * exports.foo = Definition;
  58. * export default Definition;
  59. * export var Definition = ...;
  60. *
  61. * limit can be used to limit the components to be found. When the limit is reached an error will be thrown
  62. */
  63. export default class FindExportedDefinitionsResolver {
  64. constructor({ limit = 0 } = {}) {
  65. this.limit = limit;
  66. }
  67. resolve(file) {
  68. const state = {
  69. foundDefinitions: new Set(),
  70. limit: this.limit,
  71. };
  72. file.traverse(explodedVisitors, state);
  73. return Array.from(state.foundDefinitions);
  74. }
  75. }