Add comprehensive development roadmap via GitHub Issues

Created 10 detailed GitHub issues covering:
- Project activation and management UI (#1-2)
- Worker node coordination and visualization (#3-4)
- Automated GitHub repository scanning (#5)
- Intelligent model-to-issue matching (#6)
- Multi-model task execution system (#7)
- N8N workflow integration (#8)
- Hive-Bzzz P2P bridge (#9)
- Peer assistance protocol (#10)

Each issue includes detailed specifications, acceptance criteria,
technical implementation notes, and dependency mapping.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-12 19:41:01 +10:00
parent 9a6a06da89
commit e89f2f4b7b
4980 changed files with 1501266 additions and 57 deletions

View File

@@ -0,0 +1,8 @@
import type { Handler } from './index.js';
/**
* This handler tries to find flow and TS Type annotated react components and extract
* its types to the documentation. It also extracts docblock comments which are
* inlined in the type definition.
*/
declare const codeTypeHandler: Handler;
export default codeTypeHandler;

View File

@@ -0,0 +1,79 @@
import { unwrapUtilityType } from '../utils/flowUtilityTypes.js';
import getFlowType from '../utils/getFlowType.js';
import getTypeFromReactComponent, { applyToTypeProperties, } from '../utils/getTypeFromReactComponent.js';
import getPropertyName from '../utils/getPropertyName.js';
import getTSType from '../utils/getTSType.js';
import resolveToValue from '../utils/resolveToValue.js';
import setPropDescription from '../utils/setPropDescription.js';
function setPropDescriptor(documentation, path, typeParams) {
if (path.isObjectTypeSpreadProperty()) {
const argument = unwrapUtilityType(path.get('argument'));
if (argument.isObjectTypeAnnotation()) {
applyToTypeProperties(documentation, argument, (propertyPath, innerTypeParams) => {
setPropDescriptor(documentation, propertyPath, innerTypeParams);
}, typeParams);
return;
}
const id = argument.get('id');
if (!id.hasNode() || !id.isIdentifier()) {
return;
}
const resolvedPath = resolveToValue(id);
if (resolvedPath.isTypeAlias()) {
const right = resolvedPath.get('right');
applyToTypeProperties(documentation, right, (propertyPath, innerTypeParams) => {
setPropDescriptor(documentation, propertyPath, innerTypeParams);
}, typeParams);
}
else if (!argument.has('typeParameters')) {
documentation.addComposes(id.node.name);
}
}
else if (path.isObjectTypeProperty()) {
const type = getFlowType(path.get('value'), typeParams);
const propName = getPropertyName(path);
if (!propName)
return;
const propDescriptor = documentation.getPropDescriptor(propName);
propDescriptor.required = !path.node.optional;
propDescriptor.flowType = type;
// We are doing this here instead of in a different handler
// to not need to duplicate the logic for checking for
// imported types that are spread in to props.
setPropDescription(documentation, path);
}
else if (path.isTSPropertySignature()) {
const typeAnnotation = path.get('typeAnnotation');
if (!typeAnnotation.hasNode()) {
return;
}
const type = getTSType(typeAnnotation, typeParams);
const propName = getPropertyName(path);
if (!propName)
return;
const propDescriptor = documentation.getPropDescriptor(propName);
propDescriptor.required = !path.node.optional;
propDescriptor.tsType = type;
// We are doing this here instead of in a different handler
// to not need to duplicate the logic for checking for
// imported types that are spread in to props.
setPropDescription(documentation, path);
}
}
/**
* This handler tries to find flow and TS Type annotated react components and extract
* its types to the documentation. It also extracts docblock comments which are
* inlined in the type definition.
*/
const codeTypeHandler = function (documentation, componentDefinition) {
const typePaths = getTypeFromReactComponent(componentDefinition);
if (typePaths.length === 0) {
return;
}
for (const typePath of typePaths) {
applyToTypeProperties(documentation, typePath, (propertyPath, typeParams) => {
setPropDescriptor(documentation, propertyPath, typeParams);
}, null);
}
};
export default codeTypeHandler;

View File

@@ -0,0 +1,6 @@
import type { Handler } from './index.js';
/**
* Finds the nearest block comment before the component definition.
*/
declare const componentDocblockHandler: Handler;
export default componentDocblockHandler;

View File

@@ -0,0 +1,46 @@
import { getDocblock } from '../utils/docblock.js';
import isReactForwardRefCall from '../utils/isReactForwardRefCall.js';
import resolveToValue from '../utils/resolveToValue.js';
function getDocblockFromComponent(path) {
let description = null;
if (path.isClassDeclaration() || path.isClassExpression()) {
const decorators = path.get('decorators');
// If we have a class declaration or expression, then the comment might be
// attached to the last decorator instead as trailing comment.
if (decorators && decorators.length > 0) {
description = getDocblock(decorators[decorators.length - 1], true);
}
}
if (description == null) {
// Find parent statement (e.g. var Component = React.createClass(<path>);)
let searchPath = path;
while (searchPath && !searchPath.isStatement()) {
searchPath = searchPath.parentPath;
}
if (searchPath) {
// If the parent is an export statement, we have to traverse one more up
if (searchPath.parentPath.isExportNamedDeclaration() ||
searchPath.parentPath.isExportDefaultDeclaration()) {
searchPath = searchPath.parentPath;
}
description = getDocblock(searchPath);
}
}
if (!description) {
const searchPath = isReactForwardRefCall(path)
? path.get('arguments')[0]
: path;
const inner = resolveToValue(searchPath);
if (inner.node !== path.node) {
return getDocblockFromComponent(inner);
}
}
return description;
}
/**
* Finds the nearest block comment before the component definition.
*/
const componentDocblockHandler = function (documentation, componentDefinition) {
documentation.set('description', getDocblockFromComponent(componentDefinition) || '');
};
export default componentDocblockHandler;

View File

@@ -0,0 +1,7 @@
import type { Handler } from './index.js';
/**
* Extract all flow types for the methods of a react component. Doesn't
* return any react specific lifecycle methods.
*/
declare const componentMethodsHandler: Handler;
export default componentMethodsHandler;

View File

@@ -0,0 +1,168 @@
import getMemberValuePath from '../utils/getMemberValuePath.js';
import getMethodDocumentation from '../utils/getMethodDocumentation.js';
import isReactComponentClass from '../utils/isReactComponentClass.js';
import isReactComponentMethod from '../utils/isReactComponentMethod.js';
import { shallowIgnoreVisitors } from '../utils/traverse.js';
import resolveToValue from '../utils/resolveToValue.js';
import { visitors } from '@babel/traverse';
import { isReactBuiltinCall, isReactForwardRefCall, isStatelessComponent, findFunctionReturn, } from '../utils/index.js';
/**
* The following values/constructs are considered methods:
*
* - Method declarations in classes (except "constructor" and React lifecycle
* methods
* - Public class fields in classes whose value are a functions
* - Object properties whose values are functions
*/
function isMethod(path) {
let isProbablyMethod = (path.isClassMethod() && path.node.kind !== 'constructor') ||
path.isObjectMethod();
if (!isProbablyMethod &&
(path.isClassProperty() || path.isObjectProperty())) {
const value = resolveToValue(path.get('value'));
isProbablyMethod = value.isFunction();
}
return isProbablyMethod && !isReactComponentMethod(path);
}
const explodedVisitors = visitors.explode({
...shallowIgnoreVisitors,
AssignmentExpression: {
enter: function (assignmentPath, state) {
const { name, scope } = state;
const left = assignmentPath.get('left');
const binding = assignmentPath.scope.getBinding(name);
if (binding &&
left.isMemberExpression() &&
left.get('object').isIdentifier({ name }) &&
binding.scope === scope &&
resolveToValue(assignmentPath.get('right')).isFunction()) {
state.methods.push(assignmentPath);
}
assignmentPath.skip();
},
},
});
function isObjectExpression(path) {
return path.isObjectExpression();
}
const explodedImperativeHandleVisitors = visitors.explode({
...shallowIgnoreVisitors,
CallExpression: {
enter: function (path, state) {
if (!isReactBuiltinCall(path, 'useImperativeHandle')) {
return path.skip();
}
// useImperativeHandle(ref, () => ({ name: () => {}, ...}))
const arg = path.get('arguments')[1];
if (!arg || !arg.isFunction()) {
return path.skip();
}
const body = resolveToValue(arg.get('body'));
let definition;
if (body.isObjectExpression()) {
definition = body;
}
else {
definition = findFunctionReturn(arg, isObjectExpression);
}
// We found the object body, now add all of the properties as methods.
definition?.get('properties').forEach((p) => {
if (isMethod(p)) {
state.results.push(p);
}
});
path.skip();
},
},
});
function findStatelessComponentBody(componentDefinition) {
if (isStatelessComponent(componentDefinition)) {
const body = componentDefinition.get('body');
if (body.isBlockStatement()) {
return body;
}
}
else if (isReactForwardRefCall(componentDefinition)) {
const inner = resolveToValue(componentDefinition.get('arguments')[0]);
return findStatelessComponentBody(inner);
}
return undefined;
}
function findImperativeHandleMethods(componentDefinition) {
const body = findStatelessComponentBody(componentDefinition);
if (!body) {
return [];
}
const state = { results: [] };
body.traverse(explodedImperativeHandleVisitors, state);
return state.results.map((p) => ({ path: p }));
}
function findAssignedMethods(path, idPath) {
if (!idPath.hasNode() || !idPath.isIdentifier()) {
return [];
}
const name = idPath.node.name;
const binding = idPath.scope.getBinding(name);
if (!binding) {
return [];
}
const scope = binding.scope;
const state = {
scope,
name,
methods: [],
};
path.traverse(explodedVisitors, state);
return state.methods.map((p) => ({ path: p }));
}
/**
* Extract all flow types for the methods of a react component. Doesn't
* return any react specific lifecycle methods.
*/
const componentMethodsHandler = function (documentation, componentDefinition) {
// Extract all methods from the class or object.
let methodPaths = [];
const parent = componentDefinition.parentPath;
if (isReactComponentClass(componentDefinition)) {
methodPaths = componentDefinition
.get('body')
.get('body')
.filter(isMethod).map((p) => ({ path: p }));
}
else if (componentDefinition.isObjectExpression()) {
methodPaths = componentDefinition.get('properties').filter(isMethod).map((p) => ({ path: p }));
// Add the statics object properties.
const statics = getMemberValuePath(componentDefinition, 'statics');
if (statics && statics.isObjectExpression()) {
statics.get('properties').forEach((property) => {
if (isMethod(property)) {
methodPaths.push({
path: property,
isStatic: true,
});
}
});
}
}
else if (parent.isVariableDeclarator() &&
parent.node.init === componentDefinition.node &&
parent.get('id').isIdentifier()) {
methodPaths = findAssignedMethods(parent.scope.path, parent.get('id'));
}
else if (parent.isAssignmentExpression() &&
parent.node.right === componentDefinition.node &&
parent.get('left').isIdentifier()) {
methodPaths = findAssignedMethods(parent.scope.path, parent.get('left'));
}
else if (componentDefinition.isFunctionDeclaration()) {
methodPaths = findAssignedMethods(parent.scope.path, componentDefinition.get('id'));
}
const imperativeHandles = findImperativeHandleMethods(componentDefinition);
if (imperativeHandles) {
methodPaths = [...methodPaths, ...imperativeHandles];
}
documentation.set('methods', methodPaths
.map(({ path: p, isStatic }) => getMethodDocumentation(p, { isStatic }))
.filter(Boolean));
};
export default componentMethodsHandler;

View File

@@ -0,0 +1,7 @@
import type { Handler } from './index.js';
/**
* Extract info from the methods jsdoc blocks. Must be run after
* componentMethodsHandler.
*/
declare const componentMethodsJsDocHandler: Handler;
export default componentMethodsJsDocHandler;

View File

@@ -0,0 +1,43 @@
import parseJsDoc from '../utils/parseJsDoc.js';
function removeEmpty(obj) {
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null));
}
function merge(obj1, obj2) {
if (obj1 == null && obj2 == null) {
return null;
}
const merged = {
...removeEmpty(obj1 ?? {}),
...removeEmpty(obj2 ?? {}),
};
return merged;
}
/**
* Extract info from the methods jsdoc blocks. Must be run after
* componentMethodsHandler.
*/
const componentMethodsJsDocHandler = function (documentation) {
let methods = documentation.get('methods');
if (!methods) {
return;
}
methods = methods.map((method) => {
if (!method.docblock) {
return method;
}
const jsDoc = parseJsDoc(method.docblock);
const returns = merge(jsDoc.returns, method.returns);
const params = method.params.map((param) => {
const jsDocParam = jsDoc.params.find((p) => p.name === param.name);
return merge(jsDocParam, param);
});
return {
...method,
description: jsDoc.description || null,
returns,
params,
};
});
documentation.set('methods', methods);
};
export default componentMethodsJsDocHandler;

View File

@@ -0,0 +1,3 @@
import type { Handler } from './index.js';
declare const defaultPropsHandler: Handler;
export default defaultPropsHandler;

View File

@@ -0,0 +1,126 @@
import getPropertyName from '../utils/getPropertyName.js';
import getMemberValuePath from '../utils/getMemberValuePath.js';
import printValue from '../utils/printValue.js';
import resolveToValue from '../utils/resolveToValue.js';
import resolveFunctionDefinitionToReturnValue from '../utils/resolveFunctionDefinitionToReturnValue.js';
import isReactComponentClass from '../utils/isReactComponentClass.js';
import isReactForwardRefCall from '../utils/isReactForwardRefCall.js';
function getDefaultValue(path) {
let defaultValue;
let resolvedPath = path;
let valuePath = path;
if (path.isBooleanLiteral()) {
defaultValue = `${path.node.value}`;
}
else if (path.isNullLiteral()) {
defaultValue = 'null';
}
else if (path.isLiteral()) {
defaultValue = path.node.extra?.raw;
}
else {
if (path.isAssignmentPattern()) {
resolvedPath = resolveToValue(path.get('right'));
}
else {
resolvedPath = resolveToValue(path);
}
if (resolvedPath.parentPath?.isImportDeclaration() && path.isIdentifier()) {
defaultValue = path.node.name;
}
else {
valuePath = resolvedPath;
defaultValue = printValue(resolvedPath);
}
}
if (typeof defaultValue !== 'undefined') {
return {
value: defaultValue,
computed: valuePath.isCallExpression() ||
valuePath.isMemberExpression() ||
valuePath.isIdentifier(),
};
}
return null;
}
function getStatelessPropsPath(componentDefinition) {
let value = componentDefinition;
if (isReactForwardRefCall(componentDefinition)) {
value = resolveToValue(componentDefinition.get('arguments')[0]);
}
if (!value.isFunction()) {
return;
}
return value.get('params')[0];
}
function getDefaultPropsPath(componentDefinition) {
let defaultPropsPath = getMemberValuePath(componentDefinition, 'defaultProps');
if (!defaultPropsPath) {
return null;
}
defaultPropsPath = resolveToValue(defaultPropsPath);
if (!defaultPropsPath) {
return null;
}
if (defaultPropsPath.isFunctionExpression() ||
defaultPropsPath.isFunctionDeclaration() ||
defaultPropsPath.isClassMethod() ||
defaultPropsPath.isObjectMethod()) {
// Find the value that is returned from the function and process it if it is
// an object literal.
const returnValue = resolveFunctionDefinitionToReturnValue(defaultPropsPath);
if (returnValue && returnValue.isObjectExpression()) {
defaultPropsPath = returnValue;
}
}
return defaultPropsPath;
}
function getDefaultValuesFromProps(properties, documentation, isStateless) {
properties.forEach((propertyPath) => {
if (propertyPath.isObjectProperty()) {
const propName = getPropertyName(propertyPath);
if (!propName)
return;
let valuePath = propertyPath.get('value');
if (isStateless) {
if (valuePath.isAssignmentPattern()) {
valuePath = valuePath.get('right');
}
else {
// Don't evaluate property if component is functional and the node is not an AssignmentPattern
return;
}
}
// Initialize the prop descriptor here after the early return from above
const propDescriptor = documentation.getPropDescriptor(propName);
const defaultValue = getDefaultValue(valuePath);
if (defaultValue) {
propDescriptor.defaultValue = defaultValue;
}
}
else if (propertyPath.isSpreadElement()) {
const resolvedValuePath = resolveToValue(propertyPath.get('argument'));
if (resolvedValuePath.isObjectExpression()) {
getDefaultValuesFromProps(resolvedValuePath.get('properties'), documentation, isStateless);
}
}
});
}
const defaultPropsHandler = function (documentation, componentDefinition) {
let statelessProps;
const defaultPropsPath = getDefaultPropsPath(componentDefinition);
/**
* function, lazy, memo, forwardRef etc components can resolve default props as well
*/
if (!isReactComponentClass(componentDefinition)) {
statelessProps = getStatelessPropsPath(componentDefinition);
}
// Do both statelessProps and defaultProps if both are available so defaultProps can override
if (statelessProps && statelessProps.isObjectPattern()) {
getDefaultValuesFromProps(statelessProps.get('properties'), documentation, true);
}
if (defaultPropsPath && defaultPropsPath.isObjectExpression()) {
getDefaultValuesFromProps(defaultPropsPath.get('properties'), documentation, false);
}
};
export default defaultPropsHandler;

View File

@@ -0,0 +1,3 @@
import type { Handler } from './index.js';
declare const displayNameHandler: Handler;
export default displayNameHandler;

View File

@@ -0,0 +1,49 @@
import getMemberValuePath from '../utils/getMemberValuePath.js';
import getNameOrValue from '../utils/getNameOrValue.js';
import isReactForwardRefCall from '../utils/isReactForwardRefCall.js';
import resolveToValue from '../utils/resolveToValue.js';
import resolveFunctionDefinitionToReturnValue from '../utils/resolveFunctionDefinitionToReturnValue.js';
const displayNameHandler = function (documentation, componentDefinition) {
let displayNamePath = getMemberValuePath(componentDefinition, 'displayName');
if (!displayNamePath) {
// Function and class declarations need special treatment. The name of the
// function / class is the displayName
if ((componentDefinition.isClassDeclaration() ||
componentDefinition.isFunctionDeclaration()) &&
componentDefinition.has('id')) {
documentation.set('displayName', getNameOrValue(componentDefinition.get('id')));
}
else if (componentDefinition.isArrowFunctionExpression() ||
componentDefinition.isFunctionExpression() ||
isReactForwardRefCall(componentDefinition)) {
let currentPath = componentDefinition;
while (currentPath.parentPath) {
if (currentPath.parentPath.isVariableDeclarator()) {
documentation.set('displayName', getNameOrValue(currentPath.parentPath.get('id')));
return;
}
else if (currentPath.parentPath.isAssignmentExpression()) {
const leftPath = currentPath.parentPath.get('left');
if (leftPath.isIdentifier() || leftPath.isLiteral()) {
documentation.set('displayName', getNameOrValue(leftPath));
return;
}
}
currentPath = currentPath.parentPath;
}
}
return;
}
displayNamePath = resolveToValue(displayNamePath);
// If display name is defined as function somehow (getter, property with function)
// we resolve the return value of the function
if (displayNamePath.isFunction()) {
displayNamePath = resolveFunctionDefinitionToReturnValue(displayNamePath);
}
if (!displayNamePath ||
(!displayNamePath.isStringLiteral() && !displayNamePath.isNumericLiteral())) {
return;
}
documentation.set('displayName', displayNamePath.node.value);
};
export default displayNameHandler;

View File

@@ -0,0 +1,13 @@
import type { NodePath } from '@babel/traverse';
import type Documentation from '../Documentation.js';
import type { ComponentNode } from '../resolver/index.js';
export { default as componentDocblockHandler } from './componentDocblockHandler.js';
export { default as componentMethodsHandler } from './componentMethodsHandler.js';
export { default as componentMethodsJsDocHandler } from './componentMethodsJsDocHandler.js';
export { default as defaultPropsHandler } from './defaultPropsHandler.js';
export { default as displayNameHandler } from './displayNameHandler.js';
export { default as codeTypeHandler } from './codeTypeHandler.js';
export { default as propDocblockHandler } from './propDocblockHandler.js';
export { default as propTypeCompositionHandler } from './propTypeCompositionHandler.js';
export { propTypeHandler, contextTypeHandler, childContextTypeHandler, } from './propTypeHandler.js';
export type Handler = (documentation: Documentation, componentDefinition: NodePath<ComponentNode>) => void;

View File

@@ -0,0 +1,9 @@
export { default as componentDocblockHandler } from './componentDocblockHandler.js';
export { default as componentMethodsHandler } from './componentMethodsHandler.js';
export { default as componentMethodsJsDocHandler } from './componentMethodsJsDocHandler.js';
export { default as defaultPropsHandler } from './defaultPropsHandler.js';
export { default as displayNameHandler } from './displayNameHandler.js';
export { default as codeTypeHandler } from './codeTypeHandler.js';
export { default as propDocblockHandler } from './propDocblockHandler.js';
export { default as propTypeCompositionHandler } from './propTypeCompositionHandler.js';
export { propTypeHandler, contextTypeHandler, childContextTypeHandler, } from './propTypeHandler.js';

View File

@@ -0,0 +1,3 @@
import type { Handler } from './index.js';
declare const propDocblockHandler: Handler;
export default propDocblockHandler;

View File

@@ -0,0 +1,30 @@
import getMemberValuePath from '../utils/getMemberValuePath.js';
import resolveToValue from '../utils/resolveToValue.js';
import setPropDescription from '../utils/setPropDescription.js';
function resolveDocumentation(documentation, path) {
if (!path.isObjectExpression()) {
return;
}
path.get('properties').forEach((propertyPath) => {
if (propertyPath.isSpreadElement()) {
const resolvedValuePath = resolveToValue(propertyPath.get('argument'));
resolveDocumentation(documentation, resolvedValuePath);
}
else if (propertyPath.isObjectProperty() ||
propertyPath.isObjectMethod()) {
setPropDescription(documentation, propertyPath);
}
});
}
const propDocblockHandler = function (documentation, componentDefinition) {
let propTypesPath = getMemberValuePath(componentDefinition, 'propTypes');
if (!propTypesPath) {
return;
}
propTypesPath = resolveToValue(propTypesPath);
if (!propTypesPath) {
return;
}
resolveDocumentation(documentation, propTypesPath);
};
export default propDocblockHandler;

View File

@@ -0,0 +1,3 @@
import type { Handler } from './index.js';
declare const propTypeCompositionHandler: Handler;
export default propTypeCompositionHandler;

View File

@@ -0,0 +1,36 @@
import getMemberValuePath from '../utils/getMemberValuePath.js';
import resolveToModule from '../utils/resolveToModule.js';
import resolveToValue from '../utils/resolveToValue.js';
/**
* It resolves the path to its module name and adds it to the "composes" entry
* in the documentation.
*/
function amendComposes(documentation, path) {
const moduleName = resolveToModule(path);
if (moduleName) {
documentation.addComposes(moduleName);
}
}
function processObjectExpression(documentation, path) {
path.get('properties').forEach((propertyPath) => {
if (propertyPath.isSpreadElement()) {
amendComposes(documentation, resolveToValue(propertyPath.get('argument')));
}
});
}
const propTypeCompositionHandler = function (documentation, componentDefinition) {
let propTypesPath = getMemberValuePath(componentDefinition, 'propTypes');
if (!propTypesPath) {
return;
}
propTypesPath = resolveToValue(propTypesPath);
if (!propTypesPath) {
return;
}
if (propTypesPath.isObjectExpression()) {
processObjectExpression(documentation, propTypesPath);
return;
}
amendComposes(documentation, propTypesPath);
};
export default propTypeCompositionHandler;

View File

@@ -0,0 +1,4 @@
import type { Handler } from './index.js';
export declare const propTypeHandler: Handler;
export declare const contextTypeHandler: Handler;
export declare const childContextTypeHandler: Handler;

View File

@@ -0,0 +1,71 @@
import getPropType from '../utils/getPropType.js';
import getPropertyName from '../utils/getPropertyName.js';
import getMemberValuePath from '../utils/getMemberValuePath.js';
import isReactModuleName from '../utils/isReactModuleName.js';
import isRequiredPropType from '../utils/isRequiredPropType.js';
import printValue from '../utils/printValue.js';
import resolveToModule from '../utils/resolveToModule.js';
import resolveToValue from '../utils/resolveToValue.js';
function isPropTypesExpression(path) {
const moduleName = resolveToModule(path);
if (moduleName) {
return isReactModuleName(moduleName) || moduleName === 'ReactPropTypes';
}
return false;
}
function amendPropTypes(getDescriptor, path) {
if (!path.isObjectExpression()) {
return;
}
path.get('properties').forEach((propertyPath) => {
if (propertyPath.isObjectProperty()) {
const propName = getPropertyName(propertyPath);
if (!propName)
return;
const propDescriptor = getDescriptor(propName);
const valuePath = resolveToValue(propertyPath.get('value'));
const type = isPropTypesExpression(valuePath)
? getPropType(valuePath)
: { name: 'custom', raw: printValue(valuePath) };
if (type) {
propDescriptor.type = type;
propDescriptor.required =
type.name !== 'custom' && isRequiredPropType(valuePath);
}
}
if (propertyPath.isSpreadElement()) {
const resolvedValuePath = resolveToValue(propertyPath.get('argument'));
if (resolvedValuePath.isObjectExpression()) {
// normal object literal
amendPropTypes(getDescriptor, resolvedValuePath);
}
}
});
}
function getPropTypeHandler(propName) {
return function (documentation, componentDefinition) {
let propTypesPath = getMemberValuePath(componentDefinition, propName);
if (!propTypesPath) {
return;
}
propTypesPath = resolveToValue(propTypesPath);
if (!propTypesPath) {
return;
}
let getDescriptor;
switch (propName) {
case 'childContextTypes':
getDescriptor = documentation.getChildContextDescriptor;
break;
case 'contextTypes':
getDescriptor = documentation.getContextDescriptor;
break;
default:
getDescriptor = documentation.getPropDescriptor;
}
amendPropTypes(getDescriptor.bind(documentation), propTypesPath);
};
}
export const propTypeHandler = getPropTypeHandler('propTypes');
export const contextTypeHandler = getPropTypeHandler('contextTypes');
export const childContextTypeHandler = getPropTypeHandler('childContextTypes');