UNPKG

react-docgen

Version:

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

307 lines (263 loc) 8.73 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = getFlowType; var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread")); var _getPropertyName = _interopRequireDefault(require("./getPropertyName")); var _printValue = _interopRequireDefault(require("./printValue")); var _recast = _interopRequireDefault(require("recast")); var _getTypeAnnotation = _interopRequireDefault(require("../utils/getTypeAnnotation")); var _resolveToValue = _interopRequireDefault(require("../utils/resolveToValue")); var _resolveObjectKeysToArray = require("../utils/resolveObjectKeysToArray"); /* * 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. * * * */ /* eslint no-use-before-define: 0 */ const types = _recast.default.types.namedTypes; const flowTypes = { AnyTypeAnnotation: 'any', BooleanTypeAnnotation: 'boolean', MixedTypeAnnotation: 'mixed', NullLiteralTypeAnnotation: 'null', NumberTypeAnnotation: 'number', StringTypeAnnotation: 'string', VoidTypeAnnotation: 'void' }; const flowLiteralTypes = { BooleanLiteralTypeAnnotation: 1, NumberLiteralTypeAnnotation: 1, StringLiteralTypeAnnotation: 1 }; const namedTypes = { ArrayTypeAnnotation: handleArrayTypeAnnotation, GenericTypeAnnotation: handleGenericTypeAnnotation, ObjectTypeAnnotation: handleObjectTypeAnnotation, UnionTypeAnnotation: handleUnionTypeAnnotation, NullableTypeAnnotation: handleNullableTypeAnnotation, FunctionTypeAnnotation: handleFunctionTypeAnnotation, IntersectionTypeAnnotation: handleIntersectionTypeAnnotation, TupleTypeAnnotation: handleTupleTypeAnnotation, TypeofTypeAnnotation: handleTypeofTypeAnnotation }; function getFlowTypeWithRequirements(path) { const type = getFlowTypeWithResolvedTypes(path); type.required = !path.parentPath.node.optional; return type; } function handleKeysHelper(path) { let value = path.get('typeParameters', 'params', 0); if (types.TypeofTypeAnnotation.check(value.node)) { value = value.get('argument', 'id'); } else if (!types.ObjectTypeAnnotation.check(value.node)) { value = value.get('id'); } const resolvedPath = (0, _resolveToValue.default)(value); if (resolvedPath && (types.ObjectExpression.check(resolvedPath.node) || types.ObjectTypeAnnotation.check(resolvedPath.node))) { const keys = (0, _resolveObjectKeysToArray.resolveObjectToNameArray)(resolvedPath, true); if (keys) { return { name: 'union', raw: (0, _printValue.default)(path), elements: keys.map(key => ({ name: 'literal', value: key })) }; } } return null; } function handleArrayTypeAnnotation(path) { return { name: 'Array', elements: [getFlowTypeWithResolvedTypes(path.get('elementType'))], raw: (0, _printValue.default)(path) }; } function handleGenericTypeAnnotation(path) { if (path.node.id.name === '$Keys' && path.node.typeParameters) { return handleKeysHelper(path); } let type; if (types.QualifiedTypeIdentifier.check(path.node.id)) { const id = path.get('id'); if (id.node.qualification.name === 'React') { type = { name: `${id.node.qualification.name}${id.node.id.name}`, raw: (0, _printValue.default)(id) }; } else { type = { name: (0, _printValue.default)(id).replace(/<.*>$/, '') }; } } else { type = { name: path.node.id.name }; } if (path.node.typeParameters) { const params = path.get('typeParameters').get('params'); type = (0, _objectSpread2.default)({}, type, { elements: params.map(param => getFlowTypeWithResolvedTypes(param)), raw: (0, _printValue.default)(path) }); } else { const resolvedPath = (0, _resolveToValue.default)(path.get('id')); if (resolvedPath && resolvedPath.node.right) { type = getFlowTypeWithResolvedTypes(resolvedPath.get('right')); } } return type; } function handleObjectTypeAnnotation(path) { const type = { name: 'signature', type: 'object', raw: (0, _printValue.default)(path), signature: { properties: [] } }; path.get('callProperties').each(param => { type.signature.constructor = getFlowTypeWithResolvedTypes(param.get('value')); }); path.get('indexers').each(param => { type.signature.properties.push({ key: getFlowTypeWithResolvedTypes(param.get('key')), value: getFlowTypeWithRequirements(param.get('value')) }); }); path.get('properties').each(param => { type.signature.properties.push({ key: (0, _getPropertyName.default)(param), value: getFlowTypeWithRequirements(param.get('value')) }); }); return type; } function handleUnionTypeAnnotation(path) { return { name: 'union', raw: (0, _printValue.default)(path), elements: path.get('types').map(subType => getFlowTypeWithResolvedTypes(subType)) }; } function handleIntersectionTypeAnnotation(path) { return { name: 'intersection', raw: (0, _printValue.default)(path), elements: path.get('types').map(subType => getFlowTypeWithResolvedTypes(subType)) }; } function handleNullableTypeAnnotation(path) { const typeAnnotation = (0, _getTypeAnnotation.default)(path); if (!typeAnnotation) return null; const type = getFlowTypeWithResolvedTypes(typeAnnotation); type.nullable = true; return type; } function handleFunctionTypeAnnotation(path) { const type = { name: 'signature', type: 'function', raw: (0, _printValue.default)(path), signature: { arguments: [], return: getFlowTypeWithResolvedTypes(path.get('returnType')) } }; path.get('params').each(param => { const typeAnnotation = (0, _getTypeAnnotation.default)(param); if (!typeAnnotation) return; type.signature.arguments.push({ name: param.node.name ? param.node.name.name : '', type: getFlowTypeWithResolvedTypes(typeAnnotation) }); }); return type; } function handleTupleTypeAnnotation(path) { const type = { name: 'tuple', raw: (0, _printValue.default)(path), elements: [] }; path.get('types').each(param => { type.elements.push(getFlowTypeWithResolvedTypes(param)); }); return type; } function handleTypeofTypeAnnotation(path) { return getFlowTypeWithResolvedTypes(path.get('argument')); } let visitedTypes = {}; function getFlowTypeWithResolvedTypes(path) { const node = path.node; let type; const isTypeAlias = types.TypeAlias.check(path.parentPath.node); // When we see a typealias mark it as visited so that the next // call of this function does not run into an endless loop if (isTypeAlias) { if (visitedTypes[path.parentPath.node.id.name] === true) { // if we are currently visiting this node then just return the name // as we are starting to endless loop return { name: path.parentPath.node.id.name }; } else if (typeof visitedTypes[path.parentPath.node.id.name] === 'object') { // if we already resolved the type simple return it return visitedTypes[path.parentPath.node.id.name]; } // mark the type as visited visitedTypes[path.parentPath.node.id.name] = true; } if (types.FlowType.check(node)) { if (node.type in flowTypes) { type = { name: flowTypes[node.type] }; } else if (node.type in flowLiteralTypes) { type = { name: 'literal', value: node.raw || `${node.value}` }; } else if (node.type in namedTypes) { type = namedTypes[node.type](path); } } if (!type) { type = { name: 'unknown' }; } if (isTypeAlias) { // mark the type as unvisited so that further calls can resolve the type again visitedTypes[path.parentPath.node.id.name] = type; } return type; } /** * Tries to identify the flow type by inspecting the path for known * flow type names. This method doesn't check whether the found type is actually * existing. It simply assumes that a match is always valid. * * If there is no match, "unknown" is returned. */ function getFlowType(path) { // Empty visited types before an after run // Before: in case the detection threw and we rerun again // After: cleanup memory after we are done here visitedTypes = {}; const type = getFlowTypeWithResolvedTypes(path); visitedTypes = {}; return type; }