/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ import { namedTypes as t, visit } from 'ast-types'; const { isExportsOrModuleAssignment, isReactComponentClass, isReactCreateClassCall, isReactForwardRefCall, isStatelessComponent, normalizeClassDefinition, resolveExportDeclaration, resolveToValue, } = require('react-docgen').utils; import resolveHOC from './resolveHOC'; import resolveIIFE from './resolveIIFE'; import resolveTranspiledClass from './resolveTranspiledClass'; const ERROR_MULTIPLE_DEFINITIONS = 'Multiple exported component definitions found.'; function ignore() { return false; } function isComponentDefinition(path: any) { return ( isReactCreateClassCall(path) || isReactComponentClass(path) || isStatelessComponent(path) || isReactForwardRefCall(path) ); } function resolveDefinition(definition: any) { if (isReactCreateClassCall(definition)) { // return argument const resolvedPath = resolveToValue(definition.get('arguments', 0)); if (t.ObjectExpression.check(resolvedPath.node)) { return resolvedPath; } } else if (isReactComponentClass(definition)) { normalizeClassDefinition(definition); return definition; } else if ( isStatelessComponent(definition) || isReactForwardRefCall(definition) ) { return definition; } return null; } /** * Given an AST, this function tries to find the exported component definition. * * The component definition is either the ObjectExpression passed to * `React.createClass` or a `class` definition extending `React.Component` or * having a `render()` method. * * If a definition is part of the following statements, it is considered to be * exported: * * modules.exports = Definition; * exports.foo = Definition; * export default Definition; * export var Definition = ...; */ export default function findExportedComponentDefinition(ast: any) { let foundDefinition: any; function exportDeclaration(path: any) { const definitions = resolveExportDeclaration(path).reduce( (acc: any, definition: any) => { if (isComponentDefinition(definition)) { acc.push(definition); } else { definition = resolveToValue(resolveIIFE(definition)); if (!isComponentDefinition(definition)) { definition = resolveTranspiledClass(definition); // console.log("path"); } const resolved = resolveToValue(resolveHOC(definition)); if (isComponentDefinition(resolved)) { acc.push(resolved); } } return acc; }, [], ); if (definitions.length === 0) { return false; } if (definitions.length > 1 || foundDefinition) { // If a file exports multiple components, ... complain! throw new Error(ERROR_MULTIPLE_DEFINITIONS); } foundDefinition = resolveDefinition(definitions[0]); return false; } visit(ast, { visitFunctionDeclaration: ignore, visitFunctionExpression: ignore, visitClassDeclaration: ignore, visitClassExpression: ignore, visitIfStatement: ignore, visitWithStatement: ignore, visitSwitchStatement: ignore, visitWhileStatement: ignore, visitDoWhileStatement: ignore, visitForStatement: ignore, visitForInStatement: ignore, visitForOfStatement: ignore, visitImportDeclaration: ignore, visitExportNamedDeclaration: exportDeclaration, visitExportDefaultDeclaration: exportDeclaration, visitAssignmentExpression(path: any) { // Ignore anything that is not `exports.X = ...;` or // `module.exports = ...;` if (!isExportsOrModuleAssignment(path)) { return false; } // Resolve the value of the right hand side. It should resolve to a call // expression, something like React.createClass path = resolveToValue(path.get('right')); if (!isComponentDefinition(path)) { // path = resolveToValue(resolveHOC(path)); // if (!isComponentDefinition(path)) { path = resolveToValue(resolveIIFE(path)); if (!isComponentDefinition(path)) { path = resolveTranspiledClass(path); if (!isComponentDefinition(path)) { path = resolveToValue(resolveHOC(path)); } } } if (foundDefinition) { // If a file exports multiple components, ... complain! throw new Error(ERROR_MULTIPLE_DEFINITIONS); } foundDefinition = resolveDefinition(path); return false; }, }); return foundDefinition; }