UNPKG

react-docgen

Version:

A CLI and toolkit to extract information from React components for documentation generation.

171 lines (125 loc) 5.93 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = isStatelessComponent; var _getPropertyValuePath = _interopRequireDefault(require("./getPropertyValuePath")); var _isReactComponentClass = _interopRequireDefault(require("./isReactComponentClass")); var _isReactCreateClassCall = _interopRequireDefault(require("./isReactCreateClassCall")); var _isReactCreateElementCall = _interopRequireDefault(require("./isReactCreateElementCall")); var _isReactCloneElementCall = _interopRequireDefault(require("./isReactCloneElementCall")); var _isReactChildrenElementCall = _interopRequireDefault(require("./isReactChildrenElementCall")); var _recast = _interopRequireDefault(require("recast")); var _resolveToValue = _interopRequireDefault(require("./resolveToValue")); /* * Copyright (c) 2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * */ const types = _recast.default.types.namedTypes; const validPossibleStatelessComponentTypes = ['Property', 'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression']; function isJSXElementOrReactCall(path) { return path.node.type === 'JSXElement' || path.node.type === 'JSXFragment' || path.node.type === 'CallExpression' && (0, _isReactCreateElementCall.default)(path) || path.node.type === 'CallExpression' && (0, _isReactCloneElementCall.default)(path) || path.node.type === 'CallExpression' && (0, _isReactChildrenElementCall.default)(path); } function resolvesToJSXElementOrReactCall(path) { // Is the path is already a JSX element or a call to one of the React.* functions if (isJSXElementOrReactCall(path)) { return true; } const resolvedPath = (0, _resolveToValue.default)(path); // If the path points to a conditional expression, then we need to look only at // the two possible paths if (resolvedPath.node.type === 'ConditionalExpression') { return resolvesToJSXElementOrReactCall(resolvedPath.get('consequent')) || resolvesToJSXElementOrReactCall(resolvedPath.get('alternate')); } // If the path points to a logical expression (AND, OR, ...), then we need to look only at // the two possible paths if (resolvedPath.node.type === 'LogicalExpression') { return resolvesToJSXElementOrReactCall(resolvedPath.get('left')) || resolvesToJSXElementOrReactCall(resolvedPath.get('right')); } // Is the resolved path is already a JSX element or a call to one of the React.* functions // Only do this if the resolvedPath actually resolved something as otherwise we did this check already if (resolvedPath !== path && isJSXElementOrReactCall(resolvedPath)) { return true; } // If we have a call expression, lets try to follow it if (resolvedPath.node.type === 'CallExpression') { let calleeValue = (0, _resolveToValue.default)(resolvedPath.get('callee')); if (returnsJSXElementOrReactCall(calleeValue)) { return true; } let resolvedValue; const namesToResolve = [calleeValue.get('property')]; if (calleeValue.node.type === 'MemberExpression') { if (calleeValue.get('object').node.type === 'Identifier') { resolvedValue = (0, _resolveToValue.default)(calleeValue.get('object')); } else if (types.MemberExpression.check(calleeValue.node)) { do { calleeValue = calleeValue.get('object'); namesToResolve.unshift(calleeValue.get('property')); } while (types.MemberExpression.check(calleeValue.node)); resolvedValue = (0, _resolveToValue.default)(calleeValue.get('object')); } } if (resolvedValue && types.ObjectExpression.check(resolvedValue.node)) { const resolvedMemberExpression = namesToResolve.reduce((result, nodePath) => { if (!nodePath) { return result; } if (result) { result = (0, _getPropertyValuePath.default)(result, nodePath.node.name); if (result && types.Identifier.check(result.node)) { return (0, _resolveToValue.default)(result); } } return result; }, resolvedValue); if (!resolvedMemberExpression || returnsJSXElementOrReactCall(resolvedMemberExpression)) { return true; } } } return false; } function returnsJSXElementOrReactCall(path) { let visited = false; // early exit for ArrowFunctionExpressions if (path.node.type === 'ArrowFunctionExpression' && path.get('body').node.type !== 'BlockStatement' && resolvesToJSXElementOrReactCall(path.get('body'))) { return true; } let scope = path.scope; // If we get a property we want the function scope it holds and not its outer scope if (path.node.type === 'Property') { scope = path.get('value').scope; } _recast.default.visit(path, { visitReturnStatement(returnPath) { // Only check return statements which are part of the checked function scope if (returnPath.scope !== scope) return false; if (resolvesToJSXElementOrReactCall(returnPath.get('argument'))) { visited = true; return false; } this.traverse(returnPath); } }); return visited; } /** * Returns `true` if the path represents a function which returns a JSXElement */ function isStatelessComponent(path) { const node = path.node; if (validPossibleStatelessComponentTypes.indexOf(node.type) === -1) { return false; } if (node.type === 'Property') { if ((0, _isReactCreateClassCall.default)(path.parent) || (0, _isReactComponentClass.default)(path.parent)) { return false; } } if (returnsJSXElementOrReactCall(path)) { return true; } return false; }