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,14 @@
/**
* Helper functions to work with docblock comments.
*/
import type { NodePath } from '@babel/traverse';
/**
* Given a path, this function returns the closest preceding docblock if it
* exists.
*/
export declare function getDocblock(path: NodePath, trailing?: boolean): string | null;
/**
* Given a string, this functions returns an object with doclet names as keys
* and their "content" as values.
*/
export declare function getDoclets(str: string): Record<string, string>;

View File

@@ -0,0 +1,39 @@
/**
* Helper functions to work with docblock comments.
*/
const DOCLET_PATTERN = /^@(\w+)(?:$|\s((?:[^](?!^@\w))*))/gim;
function parseDocblock(str) {
// Does not use \s in the regex as this would match also \n and conflicts
// with windows line endings.
return str.replace(/^[ \t]*\*[ \t]?/gm, '').trim();
}
const DOCBLOCK_HEADER = /^\*\s/;
/**
* Given a path, this function returns the closest preceding docblock if it
* exists.
*/
export function getDocblock(path, trailing = false) {
let comments = [];
if (trailing && path.node.trailingComments) {
comments = path.node.trailingComments.filter((comment) => comment.type === 'CommentBlock' && DOCBLOCK_HEADER.test(comment.value));
}
else if (path.node.leadingComments) {
comments = path.node.leadingComments.filter((comment) => comment.type === 'CommentBlock' && DOCBLOCK_HEADER.test(comment.value));
}
if (comments.length > 0) {
return parseDocblock(comments[comments.length - 1].value);
}
return null;
}
/**
* Given a string, this functions returns an object with doclet names as keys
* and their "content" as values.
*/
export function getDoclets(str) {
const doclets = Object.create(null);
let match;
while ((match = DOCLET_PATTERN.exec(str))) {
doclets[match[1]] = match[2] || true;
}
return doclets;
}

View File

@@ -0,0 +1,12 @@
import type { NodePath } from '@babel/traverse';
import type { Node } from '@babel/types';
/**
* Splits a MemberExpression or CallExpression into parts.
* E.g. foo.bar.baz becomes ['foo', 'bar', 'baz']
*/
declare function toArray(path: NodePath<Node | null>): string[];
/**
* Creates a string representation of a member expression.
*/
declare function toString(path: NodePath<Node | null>): string;
export { toString as String, toArray as Array };

View File

@@ -0,0 +1,96 @@
/*eslint no-loop-func: 0, no-use-before-define: 0*/
import resolveToValue from './resolveToValue.js';
/**
* Splits a MemberExpression or CallExpression into parts.
* E.g. foo.bar.baz becomes ['foo', 'bar', 'baz']
*/
function toArray(path) {
const parts = [path];
let result = [];
while (parts.length > 0) {
path = parts.shift();
if (path.isCallExpression()) {
parts.push(path.get('callee'));
continue;
}
else if (path.isMemberExpression()) {
parts.push(path.get('object'));
const property = path.get('property');
if (path.node.computed) {
const resolvedPath = resolveToValue(property);
if (resolvedPath !== undefined) {
result = result.concat(toArray(resolvedPath));
}
else {
result.push('<computed>');
}
}
else if (property.isIdentifier()) {
result.push(property.node.name);
}
else if (property.isPrivateName()) {
// new test
result.push(`#${property.get('id').node.name}`);
}
continue;
}
else if (path.isIdentifier()) {
result.push(path.node.name);
continue;
}
else if (path.isTSAsExpression()) {
const expression = path.get('expression');
if (expression.isIdentifier()) {
result.push(expression.node.name);
}
continue;
}
else if (path.isLiteral() && path.node.extra?.raw) {
result.push(path.node.extra.raw);
continue;
}
else if (path.isThisExpression()) {
result.push('this');
continue;
}
else if (path.isObjectExpression()) {
const properties = path.get('properties').map(function (property) {
if (property.isSpreadElement()) {
return `...${toString(property.get('argument'))}`;
}
else if (property.isObjectProperty()) {
return (toString(property.get('key')) +
': ' +
toString(property.get('value')));
}
else if (property.isObjectMethod()) {
return toString(property.get('key')) + ': <function>';
}
else {
throw new Error('Unrecognized object property type');
}
});
result.push('{' + properties.join(', ') + '}');
continue;
}
else if (path.isArrayExpression()) {
result.push('[' +
path
.get('elements')
.map(function (el) {
return toString(el);
})
.join(', ') +
']');
continue;
}
}
return result.reverse();
}
/**
* Creates a string representation of a member expression.
*/
function toString(path) {
return toArray(path).join('.');
}
export { toString as String, toArray as Array };

View File

@@ -0,0 +1,3 @@
import type { NodePath } from '@babel/traverse';
import type { ComponentNode } from '../resolver/index.js';
export default function findComponentDefinition(path: NodePath): NodePath<ComponentNode> | null;

View File

@@ -0,0 +1,41 @@
import isReactComponentClass from './isReactComponentClass.js';
import isReactCreateClassCall from './isReactCreateClassCall.js';
import isReactForwardRefCall from './isReactForwardRefCall.js';
import isStatelessComponent from './isStatelessComponent.js';
import normalizeClassDefinition from './normalizeClassDefinition.js';
import resolveHOC from './resolveHOC.js';
import resolveToValue from './resolveToValue.js';
function isComponentDefinition(path) {
return (isReactCreateClassCall(path) ||
isReactComponentClass(path) ||
isStatelessComponent(path) ||
isReactForwardRefCall(path));
}
function resolveComponentDefinition(definition) {
if (isReactCreateClassCall(definition)) {
// return argument
const resolvedPath = resolveToValue(definition.get('arguments')[0]);
if (resolvedPath.isObjectExpression()) {
return resolvedPath;
}
}
else if (isReactComponentClass(definition)) {
normalizeClassDefinition(definition);
return definition;
}
else if (isStatelessComponent(definition) ||
isReactForwardRefCall(definition)) {
return definition;
}
return null;
}
export default function findComponentDefinition(path) {
let resolvedPath = path;
if (!isComponentDefinition(resolvedPath)) {
resolvedPath = resolveToValue(resolveHOC(resolvedPath));
if (!isComponentDefinition(resolvedPath)) {
return null;
}
}
return resolveComponentDefinition(resolvedPath);
}

View File

@@ -0,0 +1,11 @@
import type { NodePath } from '@babel/traverse';
type Predicate<T extends NodePath> = (path: NodePath) => path is T;
/**
* This can be used in two ways
* 1. Find the first return path that passes the predicate function
* (for example to check if a function is returning something)
* 2. Find all occurrences of return values
* For this the predicate acts more like a collector and always needs to return false
*/
export default function findFunctionReturn<T extends NodePath = NodePath>(path: NodePath, predicate: Predicate<T>): T | undefined;
export {};

View File

@@ -0,0 +1,102 @@
import { visitors } from '@babel/traverse';
import resolveToValue from './resolveToValue.js';
import { ignore } from './traverse.js';
const explodedVisitors = visitors.explode({
Function: { enter: ignore },
Class: { enter: ignore },
ObjectExpression: { enter: ignore },
ReturnStatement: {
enter: function (path, state) {
const argument = path.get('argument');
if (argument.hasNode()) {
const resolvedPath = resolvesToFinalValue(argument, state.predicate, state.seen);
if (resolvedPath) {
state.resolvedReturnPath = resolvedPath;
path.stop();
}
}
},
},
});
function resolvesToFinalValue(path, predicate, seen) {
// avoid returns with recursive function calls
if (seen.has(path)) {
return;
}
seen.add(path);
// Is the path already passes then return it.
if (predicate(path)) {
return path;
}
const resolvedPath = resolveToValue(path);
// If the resolved path is already passing then no need to further check
// Only do this if the resolvedPath actually resolved something as otherwise we did this check already
if (resolvedPath.node !== path.node && predicate(resolvedPath)) {
return resolvedPath;
}
// If the path points to a conditional expression, then we need to look only at
// the two possible paths
if (resolvedPath.isConditionalExpression()) {
return (resolvesToFinalValue(resolvedPath.get('consequent'), predicate, seen) ||
resolvesToFinalValue(resolvedPath.get('alternate'), predicate, seen));
}
// If the path points to a logical expression (AND, OR, ...), then we need to look only at
// the two possible paths
if (resolvedPath.isLogicalExpression()) {
return (resolvesToFinalValue(resolvedPath.get('left'), predicate, seen) ||
resolvesToFinalValue(resolvedPath.get('right'), predicate, seen));
}
// If we have a call expression, lets try to follow it
if (resolvedPath.isCallExpression()) {
const returnValue = findFunctionReturnWithCache(resolveToValue(resolvedPath.get('callee')), predicate, seen);
if (returnValue) {
return returnValue;
}
}
return;
}
/**
* This can be used in two ways
* 1. Find the first return path that passes the predicate function
* (for example to check if a function is returning something)
* 2. Find all occurrences of return values
* For this the predicate acts more like a collector and always needs to return false
*/
function findFunctionReturnWithCache(path, predicate, seen) {
let functionPath = path;
if (functionPath.isObjectProperty()) {
functionPath = functionPath.get('value');
}
else if (functionPath.isClassProperty()) {
const classPropertyValue = functionPath.get('value');
if (classPropertyValue.hasNode()) {
functionPath = classPropertyValue;
}
}
if (!functionPath.isFunction()) {
return;
}
// skip traversing for ArrowFunctionExpressions with no block
if (path.isArrowFunctionExpression()) {
const body = path.get('body');
if (!body.isBlockStatement()) {
return resolvesToFinalValue(body, predicate, seen);
}
}
const state = {
predicate,
seen,
};
path.traverse(explodedVisitors, state);
return state.resolvedReturnPath;
}
/**
* This can be used in two ways
* 1. Find the first return path that passes the predicate function
* (for example to check if a function is returning something)
* 2. Find all occurrences of return values
* For this the predicate acts more like a collector and always needs to return false
*/
export default function findFunctionReturn(path, predicate) {
return findFunctionReturnWithCache(path, predicate, new WeakSet());
}

View File

@@ -0,0 +1,13 @@
import type { NodePath } from '@babel/traverse';
import type { GenericTypeAnnotation } from '@babel/types';
/**
* See `supportedUtilityTypes` for which types are supported and
* https://flow.org/en/docs/types/utilities/ for which types are available.
*/
export declare function isSupportedUtilityType(path: NodePath): path is NodePath<GenericTypeAnnotation>;
/**
* Unwraps well known utility types. For example:
*
* $ReadOnly<T> => T
*/
export declare function unwrapUtilityType(path: NodePath): NodePath;

View File

@@ -0,0 +1,32 @@
/**
* See `supportedUtilityTypes` for which types are supported and
* https://flow.org/en/docs/types/utilities/ for which types are available.
*/
export function isSupportedUtilityType(path) {
if (path.isGenericTypeAnnotation()) {
const idPath = path.get('id');
if (idPath.isIdentifier()) {
const name = idPath.node.name;
return name === '$Exact' || name === '$ReadOnly';
}
}
return false;
}
/**
* Unwraps well known utility types. For example:
*
* $ReadOnly<T> => T
*/
export function unwrapUtilityType(path) {
let resultPath = path;
while (isSupportedUtilityType(resultPath)) {
const typeParameters = resultPath.get('typeParameters');
if (!typeParameters.hasNode())
break;
const firstParam = typeParameters.get('params')[0];
if (!firstParam)
break;
resultPath = firstParam;
}
return resultPath;
}

View File

@@ -0,0 +1,3 @@
import type { NodePath } from '@babel/traverse';
import type { ClassDeclaration, ClassExpression, ClassMethod, Expression } from '@babel/types';
export default function getClassMemberValuePath(classDefinition: NodePath<ClassDeclaration | ClassExpression>, memberName: string): NodePath<ClassMethod | Expression> | null;

View File

@@ -0,0 +1,23 @@
import getNameOrValue from './getNameOrValue.js';
export default function getClassMemberValuePath(classDefinition, memberName) {
const classMember = classDefinition
.get('body')
.get('body')
.find((memberPath) => {
if ((memberPath.isClassMethod() && memberPath.node.kind !== 'set') ||
memberPath.isClassProperty()) {
const key = memberPath.get('key');
return ((!memberPath.node.computed || key.isLiteral()) &&
getNameOrValue(key) === memberName);
}
return false;
});
if (classMember) {
// For ClassProperty we return the value and for ClassMethod
// we return itself
return classMember.isClassMethod()
? classMember
: classMember.get('value');
}
return null;
}

View File

@@ -0,0 +1,12 @@
import type { TypeParameters } from '../utils/getTypeParameters.js';
import type { TypeDescriptor } from '../Documentation.js';
import type { NodePath } from '@babel/traverse';
import type { FlowType } from '@babel/types';
/**
* 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.
*/
export default function getFlowType(path: NodePath<FlowType>, typeParams?: TypeParameters | null): TypeDescriptor;

View File

@@ -0,0 +1,343 @@
import getPropertyName from './getPropertyName.js';
import printValue from './printValue.js';
import getTypeAnnotation from '../utils/getTypeAnnotation.js';
import resolveToValue from '../utils/resolveToValue.js';
import { resolveObjectToNameArray } from '../utils/resolveObjectKeysToArray.js';
import getTypeParameters from '../utils/getTypeParameters.js';
import { getDocblock } from './docblock.js';
const flowTypes = {
AnyTypeAnnotation: 'any',
BooleanTypeAnnotation: 'boolean',
MixedTypeAnnotation: 'mixed',
NullLiteralTypeAnnotation: 'null',
NumberTypeAnnotation: 'number',
StringTypeAnnotation: 'string',
VoidTypeAnnotation: 'void',
EmptyTypeAnnotation: 'empty',
};
const flowLiteralTypes = {
BooleanLiteralTypeAnnotation: 1,
NumberLiteralTypeAnnotation: 1,
StringLiteralTypeAnnotation: 1,
};
const namedTypes = {
ArrayTypeAnnotation: handleArrayTypeAnnotation,
GenericTypeAnnotation: handleGenericTypeAnnotation,
ObjectTypeAnnotation: handleObjectTypeAnnotation,
InterfaceDeclaration: handleInterfaceDeclaration,
UnionTypeAnnotation: handleUnionTypeAnnotation,
NullableTypeAnnotation: handleNullableTypeAnnotation,
FunctionTypeAnnotation: handleFunctionTypeAnnotation,
IntersectionTypeAnnotation: handleIntersectionTypeAnnotation,
TupleTypeAnnotation: handleTupleTypeAnnotation,
TypeofTypeAnnotation: handleTypeofTypeAnnotation,
IndexedAccessType: handleIndexedAccessType,
};
function getFlowTypeWithRequirements(path, typeParams) {
const type = getFlowTypeWithResolvedTypes(path, typeParams);
type.required =
'optional' in path.parentPath.node ? !path.parentPath.node.optional : true;
return type;
}
function handleKeysHelper(path) {
const typeParams = path.get('typeParameters');
if (!typeParams.hasNode()) {
return null;
}
let value = typeParams.get('params')[0];
if (!value) {
return null;
}
if (value.isTypeofTypeAnnotation()) {
value = value.get('argument').get('id');
}
else if (!value.isObjectTypeAnnotation()) {
value = value.get('id');
}
const resolvedPath = value.hasNode() ? resolveToValue(value) : value;
if (resolvedPath.isObjectExpression() ||
resolvedPath.isObjectTypeAnnotation()) {
const keys = resolveObjectToNameArray(resolvedPath, true);
if (keys) {
return {
name: 'union',
raw: printValue(path),
elements: keys.map((key) => ({ name: 'literal', value: key })),
};
}
}
return null;
}
function handleArrayTypeAnnotation(path, typeParams) {
return {
name: 'Array',
elements: [
getFlowTypeWithResolvedTypes(path.get('elementType'), typeParams),
],
raw: printValue(path),
};
}
function handleGenericTypeAnnotation(path, typeParams) {
const id = path.get('id');
const typeParameters = path.get('typeParameters');
if (id.isIdentifier({ name: '$Keys' }) && typeParameters.hasNode()) {
return handleKeysHelper(path);
}
let type;
if (id.isQualifiedTypeIdentifier()) {
const qualification = id.get('qualification');
if (qualification.isIdentifier({ name: 'React' })) {
type = {
name: `${qualification.node.name}${id.node.id.name}`,
raw: printValue(id),
};
}
else {
type = { name: printValue(id).replace(/<.*>$/, '') };
}
}
else {
type = { name: id.node.name };
}
const resolvedPath = (typeParams && typeParams[type.name]) || resolveToValue(path.get('id'));
if (typeParameters.hasNode() && resolvedPath.has('typeParameters')) {
typeParams = getTypeParameters(resolvedPath.get('typeParameters'), typeParameters, typeParams);
}
if (typeParams &&
typeParams[type.name] &&
typeParams[type.name].isGenericTypeAnnotation()) {
return type;
}
if (typeParams && typeParams[type.name]) {
type = getFlowTypeWithResolvedTypes(resolvedPath, typeParams);
}
if (resolvedPath && resolvedPath.has('right')) {
type = getFlowTypeWithResolvedTypes(resolvedPath.get('right'), typeParams);
}
else if (typeParameters.hasNode()) {
const params = typeParameters.get('params');
type = {
...type,
elements: params.map((param) => getFlowTypeWithResolvedTypes(param, typeParams)),
raw: printValue(path),
};
}
return type;
}
function handleObjectTypeAnnotation(path, typeParams) {
const type = {
name: 'signature',
type: 'object',
raw: printValue(path),
signature: { properties: [] },
};
const callProperties = path.get('callProperties');
if (Array.isArray(callProperties)) {
callProperties.forEach((param) => {
type.signature.constructor = getFlowTypeWithResolvedTypes(param.get('value'), typeParams);
});
}
const indexers = path.get('indexers');
if (Array.isArray(indexers)) {
indexers.forEach((param) => {
const typeDescriptor = {
key: getFlowTypeWithResolvedTypes(param.get('key'), typeParams),
value: getFlowTypeWithRequirements(param.get('value'), typeParams),
};
const docblock = getDocblock(param);
if (docblock) {
typeDescriptor.description = docblock;
}
type.signature.properties.push(typeDescriptor);
});
}
path.get('properties').forEach((param) => {
if (param.isObjectTypeProperty()) {
const typeDescriptor = {
// For ObjectTypeProperties `getPropertyName` always returns string
key: getPropertyName(param),
value: getFlowTypeWithRequirements(param.get('value'), typeParams),
};
const docblock = getDocblock(param);
if (docblock) {
typeDescriptor.description = docblock;
}
type.signature.properties.push(typeDescriptor);
}
else if (param.isObjectTypeSpreadProperty()) {
let spreadObject = resolveToValue(param.get('argument'));
if (spreadObject.isGenericTypeAnnotation()) {
const typeAlias = resolveToValue(spreadObject.get('id'));
if (typeAlias.isTypeAlias() &&
typeAlias.get('right').isObjectTypeAnnotation()) {
spreadObject = resolveToValue(typeAlias.get('right'));
}
}
if (spreadObject.isObjectTypeAnnotation()) {
const props = handleObjectTypeAnnotation(spreadObject, typeParams);
type.signature.properties.push(...props.signature.properties);
}
}
});
return type;
}
function handleInterfaceDeclaration(path) {
// Interfaces are handled like references which would be documented separately,
// rather than inlined like type aliases.
return {
name: path.node.id.name,
};
}
function handleUnionTypeAnnotation(path, typeParams) {
return {
name: 'union',
raw: printValue(path),
elements: path
.get('types')
.map((subType) => getFlowTypeWithResolvedTypes(subType, typeParams)),
};
}
function handleIntersectionTypeAnnotation(path, typeParams) {
return {
name: 'intersection',
raw: printValue(path),
elements: path
.get('types')
.map((subType) => getFlowTypeWithResolvedTypes(subType, typeParams)),
};
}
function handleNullableTypeAnnotation(path, typeParams) {
const typeAnnotation = getTypeAnnotation(path);
if (!typeAnnotation)
return null;
const type = getFlowTypeWithResolvedTypes(typeAnnotation, typeParams);
type.nullable = true;
return type;
}
function handleFunctionTypeAnnotation(path, typeParams) {
const type = {
name: 'signature',
type: 'function',
raw: printValue(path),
signature: {
arguments: [],
return: getFlowTypeWithResolvedTypes(path.get('returnType'), typeParams),
},
};
path.get('params').forEach((param) => {
const typeAnnotation = getTypeAnnotation(param);
type.signature.arguments.push({
name: param.node.name ? param.node.name.name : '',
type: typeAnnotation
? getFlowTypeWithResolvedTypes(typeAnnotation, typeParams)
: undefined,
});
});
const rest = path.get('rest');
if (rest.hasNode()) {
const typeAnnotation = getTypeAnnotation(rest);
type.signature.arguments.push({
name: rest.node.name ? rest.node.name.name : '',
type: typeAnnotation
? getFlowTypeWithResolvedTypes(typeAnnotation, typeParams)
: undefined,
rest: true,
});
}
return type;
}
function handleTupleTypeAnnotation(path, typeParams) {
const type = {
name: 'tuple',
raw: printValue(path),
elements: [],
};
path.get('types').forEach((param) => {
type.elements.push(getFlowTypeWithResolvedTypes(param, typeParams));
});
return type;
}
function handleTypeofTypeAnnotation(path, typeParams) {
return getFlowTypeWithResolvedTypes(path.get('argument'), typeParams);
}
function handleIndexedAccessType(path, typeParams) {
const objectType = getFlowTypeWithResolvedTypes(path.get('objectType'), typeParams);
const indexType = getFlowTypeWithResolvedTypes(path.get('indexType'), typeParams);
// We only get the signature if the objectType is a type (vs interface)
if (!objectType.signature) {
return {
name: `${objectType.name}[${indexType.value ? indexType.value.toString() : indexType.name}]`,
raw: printValue(path),
};
}
const resolvedType = objectType.signature.properties.find((p) => {
// indexType.value = "'foo'"
return indexType.value && p.key === indexType.value.replace(/['"]+/g, '');
});
if (!resolvedType) {
return { name: 'unknown' };
}
return {
name: resolvedType.value.name,
raw: printValue(path),
};
}
let visitedTypes = {};
function getFlowTypeWithResolvedTypes(path, typeParams) {
let type = null;
const parent = path.parentPath;
const isTypeAlias = parent.isTypeAlias();
// 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) {
const visitedType = visitedTypes[parent.node.id.name];
if (visitedType === true) {
// if we are currently visiting this node then just return the name
// as we are starting to endless loop
return { name: parent.node.id.name };
}
else if (typeof visitedType === 'object') {
// if we already resolved the type simple return it
return visitedType;
}
// mark the type as visited
visitedTypes[parent.node.id.name] = true;
}
if (path.node.type in flowTypes) {
type = { name: flowTypes[path.node.type] };
}
else if (path.node.type in flowLiteralTypes) {
type = {
name: 'literal',
value: path.node.extra?.raw ||
`${path.node.value}`,
};
}
else if (path.node.type in namedTypes) {
type = namedTypes[path.node.type](path, typeParams);
}
if (!type) {
type = { name: 'unknown' };
}
if (isTypeAlias) {
// mark the type as unvisited so that further calls can resolve the type again
visitedTypes[parent.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.
*/
export default function getFlowType(path, typeParams = null) {
// 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, typeParams);
visitedTypes = {};
return type;
}

View File

@@ -0,0 +1,11 @@
import type { NodePath } from '@babel/traverse';
import type { Expression, MemberExpression } from '@babel/types';
/**
* Returns the path to the first part of the MemberExpression. I.e. given a
* path representing
*
* foo.bar.baz
*
* it returns the path of/to `foo`.
*/
export default function getMemberExpressionRoot(memberExpressionPath: NodePath<MemberExpression>): NodePath<Expression>;

View File

@@ -0,0 +1,15 @@
/**
* Returns the path to the first part of the MemberExpression. I.e. given a
* path representing
*
* foo.bar.baz
*
* it returns the path of/to `foo`.
*/
export default function getMemberExpressionRoot(memberExpressionPath) {
let path = memberExpressionPath;
while (path.isMemberExpression()) {
path = path.get('object');
}
return path;
}

View File

@@ -0,0 +1,3 @@
import type { NodePath } from '@babel/traverse';
import type { Expression } from '@babel/types';
export default function getMemberExpressionValuePath(variableDefinition: NodePath, memberName: string): NodePath<Expression> | null;

View File

@@ -0,0 +1,83 @@
import { visitors } from '@babel/traverse';
import getNameOrValue from './getNameOrValue.js';
import { String as toString } from './expressionTo.js';
import isReactForwardRefCall from './isReactForwardRefCall.js';
function resolveName(path) {
if (path.isVariableDeclaration()) {
const declarations = path.get('declarations');
if (declarations.length > 1) {
throw new TypeError('Got unsupported VariableDeclaration. VariableDeclaration must only ' +
'have a single VariableDeclarator. Got ' +
declarations.length +
' declarations.');
}
// VariableDeclarator always has at least one declaration, hence the non-null-assertion
const id = declarations[0].get('id');
if (id.isIdentifier()) {
return id.node.name;
}
return;
}
if (path.isFunctionDeclaration()) {
const id = path.get('id');
if (id.isIdentifier()) {
return id.node.name;
}
return;
}
if (path.isFunctionExpression() ||
path.isArrowFunctionExpression() ||
path.isTaggedTemplateExpression() ||
path.isCallExpression() ||
isReactForwardRefCall(path)) {
let currentPath = path;
while (currentPath.parentPath) {
if (currentPath.parentPath.isVariableDeclarator()) {
const id = currentPath.parentPath.get('id');
if (id.isIdentifier()) {
return id.node.name;
}
return;
}
currentPath = currentPath.parentPath;
}
return;
}
throw new TypeError('Attempted to resolveName for an unsupported path. resolveName does not accept ' +
path.node.type +
'".');
}
const explodedVisitors = visitors.explode({
AssignmentExpression: {
enter: function (path, state) {
const memberPath = path.get('left');
if (!memberPath.isMemberExpression()) {
return;
}
const property = memberPath.get('property');
if (((!memberPath.node.computed && property.isIdentifier()) ||
property.isStringLiteral() ||
property.isNumericLiteral()) &&
getNameOrValue(property) === state.memberName &&
toString(memberPath.get('object')) === state.localName) {
state.result = path.get('right');
path.stop();
}
},
},
});
export default function getMemberExpressionValuePath(variableDefinition, memberName) {
const localName = resolveName(variableDefinition);
if (!localName) {
// likely an immediately exported and therefore nameless/anonymous node
// passed in
return null;
}
const state = {
localName,
memberName,
result: null,
};
variableDefinition.hub.file.traverse(explodedVisitors, state);
return state.result;
}

View File

@@ -0,0 +1,20 @@
import type { NodePath } from '@babel/traverse';
import type { CallExpression, ClassDeclaration, ClassExpression, ClassMethod, Expression, ObjectExpression, ObjectMethod, TaggedTemplateExpression, VariableDeclaration } from '@babel/types';
import type { StatelessComponentNode } from '../resolver/index.js';
type SupportedNodes = CallExpression | ClassDeclaration | ClassExpression | ObjectExpression | StatelessComponentNode | TaggedTemplateExpression | VariableDeclaration;
export declare function isSupportedDefinitionType(path: NodePath): path is NodePath<SupportedNodes>;
/**
* This is a helper method for handlers to make it easier to work either with
* an ObjectExpression from `React.createClass` class or with a class
* definition.
*
* Given a path and a name, this function will either return the path of the
* property value if the path is an ObjectExpression, or the value of the
* ClassProperty/MethodDefinition if it is a class definition (declaration or
* expression).
*
* It also normalizes the names so that e.g. `defaultProps` and
* `getDefaultProps` can be used interchangeably.
*/
export default function getMemberValuePath(componentDefinition: NodePath<SupportedNodes>, memberName: string): NodePath<ClassMethod | Expression | ObjectMethod> | null;
export {};

View File

@@ -0,0 +1,85 @@
import getClassMemberValuePath from './getClassMemberValuePath.js';
import getMemberExpressionValuePath from './getMemberExpressionValuePath.js';
import getPropertyValuePath from './getPropertyValuePath.js';
import resolveFunctionDefinitionToReturnValue from '../utils/resolveFunctionDefinitionToReturnValue.js';
const postprocessPropTypes = (path) => path.isFunction() ? resolveFunctionDefinitionToReturnValue(path) : path;
const POSTPROCESS_MEMBERS = new Map([['propTypes', postprocessPropTypes]]);
const SUPPORTED_DEFINITION_TYPES = [
// potential stateless function component
'ArrowFunctionExpression',
/**
* Adds support for libraries such as
* [system-components]{@link https://jxnblk.com/styled-system/system-components} that use
* CallExpressions to generate components.
*
* While react-docgen's built-in resolvers do not support resolving
* CallExpressions definitions, third-party resolvers (such as
* https://github.com/Jmeyering/react-docgen-annotation-resolver) could be
* used to add these definitions.
*/
'CallExpression',
'ClassDeclaration',
'ClassExpression',
// potential stateless function component
'FunctionDeclaration',
// potential stateless function component
'FunctionExpression',
'ObjectExpression',
// potential stateless function component
'ObjectMethod',
/**
* Adds support for libraries such as
* [styled components]{@link https://github.com/styled-components} that use
* TaggedTemplateExpression's to generate components.
*
* While react-docgen's built-in resolvers do not support resolving
* TaggedTemplateExpression definitions, third-party resolvers (such as
* https://github.com/Jmeyering/react-docgen-annotation-resolver) could be
* used to add these definitions.
*/
'TaggedTemplateExpression',
'VariableDeclaration',
];
export function isSupportedDefinitionType(path) {
return SUPPORTED_DEFINITION_TYPES.includes(path.node.type);
}
/**
* This is a helper method for handlers to make it easier to work either with
* an ObjectExpression from `React.createClass` class or with a class
* definition.
*
* Given a path and a name, this function will either return the path of the
* property value if the path is an ObjectExpression, or the value of the
* ClassProperty/MethodDefinition if it is a class definition (declaration or
* expression).
*
* It also normalizes the names so that e.g. `defaultProps` and
* `getDefaultProps` can be used interchangeably.
*/
export default function getMemberValuePath(componentDefinition, memberName) {
let result;
if (componentDefinition.isObjectExpression()) {
result = getPropertyValuePath(componentDefinition, memberName);
if (!result && memberName === 'defaultProps') {
result = getPropertyValuePath(componentDefinition, 'getDefaultProps');
}
}
else if (componentDefinition.isClassDeclaration() ||
componentDefinition.isClassExpression()) {
result = getClassMemberValuePath(componentDefinition, memberName);
if (!result && memberName === 'defaultProps') {
result = getClassMemberValuePath(componentDefinition, 'getDefaultProps');
}
}
else {
result = getMemberExpressionValuePath(componentDefinition, memberName);
if (!result && memberName === 'defaultProps') {
result = getMemberExpressionValuePath(componentDefinition, 'getDefaultProps');
}
}
const postprocessMethod = POSTPROCESS_MEMBERS.get(memberName);
if (result && postprocessMethod) {
result = postprocessMethod(result);
}
return result;
}

View File

@@ -0,0 +1,21 @@
import type { NodePath } from '@babel/traverse';
import type { Expression, PrivateName } from '@babel/types';
interface MemberDescriptor {
path: NodePath<Expression | PrivateName>;
computed: boolean;
argumentPaths: NodePath[];
}
/**
* Given a "nested" Member/CallExpression, e.g.
*
* foo.bar()[baz][42]
*
* this returns a list of "members". In this example it would be something like
* [
* {path: NodePath<bar>, arguments: NodePath<empty>, computed: false},
* {path: NodePath<baz>, arguments: null, computed: true},
* {path: NodePath<42>, arguments: null, computed: false}
* ]
*/
export default function getMembers(path: NodePath, includeRoot?: boolean): MemberDescriptor[];
export {};

View File

@@ -0,0 +1,50 @@
/**
* Given a "nested" Member/CallExpression, e.g.
*
* foo.bar()[baz][42]
*
* this returns a list of "members". In this example it would be something like
* [
* {path: NodePath<bar>, arguments: NodePath<empty>, computed: false},
* {path: NodePath<baz>, arguments: null, computed: true},
* {path: NodePath<42>, arguments: null, computed: false}
* ]
*/
export default function getMembers(path, includeRoot = false) {
const result = [];
let argumentPaths = [];
let resultPath = path;
while (true) {
if (resultPath.isMemberExpression()) {
const property = resultPath.get('property');
result.push({
path: property,
computed: resultPath.node.computed,
argumentPaths,
});
argumentPaths = [];
resultPath = resultPath.get('object');
}
else if (resultPath.isCallExpression()) {
const callee = resultPath.get('callee');
if (callee.isExpression()) {
argumentPaths = resultPath.get('arguments');
resultPath = callee;
}
else {
break;
}
}
else {
break;
}
}
if (includeRoot && result.length > 0) {
result.push({
path: resultPath,
computed: false,
argumentPaths,
});
}
return result.reverse();
}

View File

@@ -0,0 +1,7 @@
import type { NodePath } from '@babel/traverse';
import type { AssignmentExpression, ClassMethod, ClassPrivateMethod, ClassProperty, ObjectMethod, ObjectProperty } from '@babel/types';
import type { MethodDescriptor } from '../Documentation.js';
export type MethodNodePath = NodePath<AssignmentExpression> | NodePath<ClassMethod> | NodePath<ClassPrivateMethod> | NodePath<ClassProperty> | NodePath<ObjectMethod> | NodePath<ObjectProperty>;
export default function getMethodDocumentation(methodPath: MethodNodePath, options?: {
isStatic?: boolean;
}): MethodDescriptor | null;

View File

@@ -0,0 +1,166 @@
import { getDocblock } from './docblock.js';
import getFlowType from './getFlowType.js';
import getTSType from './getTSType.js';
import getParameterName from './getParameterName.js';
import getPropertyName from './getPropertyName.js';
import getTypeAnnotation from './getTypeAnnotation.js';
import resolveToValue from './resolveToValue.js';
import printValue from './printValue.js';
function getMethodFunctionExpression(methodPath) {
if (methodPath.isClassMethod() || methodPath.isObjectMethod()) {
return methodPath;
}
const potentialFunctionExpression = methodPath.isAssignmentExpression()
? methodPath.get('right')
: methodPath.get('value');
const functionExpression = resolveToValue(potentialFunctionExpression);
if (functionExpression.isFunction()) {
return functionExpression;
}
return null;
}
function getMethodParamOptional(path) {
let identifier = path;
if (identifier.isTSParameterProperty()) {
identifier = identifier.get('parameter');
}
if (identifier.isAssignmentPattern()) {
// A default value always makes the param optional
return true;
}
return identifier.isIdentifier() ? Boolean(identifier.node.optional) : false;
}
function getMethodParamsDoc(methodPath) {
const params = [];
const functionExpression = getMethodFunctionExpression(methodPath);
if (functionExpression) {
// Extract param types.
functionExpression.get('params').forEach((paramPath) => {
let type = null;
const typePath = getTypeAnnotation(paramPath);
if (typePath) {
if (typePath.isFlowType()) {
type = getFlowType(typePath, null);
if (typePath.isGenericTypeAnnotation()) {
type.alias = printValue(typePath.get('id'));
}
}
else if (typePath.isTSType()) {
type = getTSType(typePath, null);
if (typePath.isTSTypeReference()) {
type.alias = printValue(typePath.get('typeName'));
}
}
}
const param = {
name: getParameterName(paramPath),
optional: getMethodParamOptional(paramPath),
type,
};
params.push(param);
});
}
return params;
}
// Extract flow return type.
function getMethodReturnDoc(methodPath) {
const functionExpression = getMethodFunctionExpression(methodPath);
if (functionExpression && functionExpression.node.returnType) {
const returnType = getTypeAnnotation(functionExpression.get('returnType'));
if (!returnType) {
return null;
}
if (returnType.isFlowType()) {
return { type: getFlowType(returnType, null) };
}
else if (returnType.isTSType()) {
return { type: getTSType(returnType, null) };
}
}
return null;
}
function getMethodModifiers(methodPath, options) {
if (methodPath.isAssignmentExpression()) {
return ['static'];
}
// Otherwise this is a method/property node
const modifiers = [];
if (options.isStatic === true ||
((methodPath.isClassProperty() || methodPath.isClassMethod()) &&
methodPath.node.static)) {
modifiers.push('static');
}
const functionExpression = getMethodFunctionExpression(methodPath);
if (functionExpression) {
if (functionExpression.isClassMethod() ||
functionExpression.isObjectMethod()) {
if (functionExpression.node.kind === 'get' ||
functionExpression.node.kind === 'set') {
modifiers.push(functionExpression.node.kind);
}
}
if (functionExpression.node.generator) {
modifiers.push('generator');
}
if (functionExpression.node.async) {
modifiers.push('async');
}
}
return modifiers;
}
function getMethodName(methodPath) {
if (methodPath.isAssignmentExpression()) {
const left = methodPath.get('left');
if (left.isMemberExpression()) {
const property = left.get('property');
if (!left.node.computed && property.isIdentifier()) {
return property.node.name;
}
if (property.isStringLiteral() || property.isNumericLiteral()) {
return String(property.node.value);
}
}
return null;
}
return getPropertyName(methodPath);
}
function getMethodAccessibility(methodPath) {
if (methodPath.isClassMethod() || methodPath.isClassProperty()) {
return methodPath.node.accessibility || null;
}
// Otherwise this is a object method/property or assignment expression
return null;
}
function getMethodDocblock(methodPath) {
if (methodPath.isAssignmentExpression()) {
let path = methodPath;
do {
path = path.parentPath;
} while (path && !path.isExpressionStatement());
if (path) {
return getDocblock(path);
}
return null;
}
// Otherwise this is a method/property node
return getDocblock(methodPath);
}
// Gets the documentation object for a component method.
// Component methods may be represented as class/object method/property nodes
// or as assignment expression of the form `Component.foo = function() {}`
export default function getMethodDocumentation(methodPath, options = {}) {
if (getMethodAccessibility(methodPath) === 'private' ||
methodPath.isClassPrivateMethod()) {
return null;
}
const name = getMethodName(methodPath);
if (!name)
return null;
return {
name,
docblock: getMethodDocblock(methodPath),
modifiers: getMethodModifiers(methodPath, options),
params: getMethodParamsDoc(methodPath),
returns: getMethodReturnDoc(methodPath),
};
}

View File

@@ -0,0 +1,6 @@
import type { NodePath } from '@babel/traverse';
/**
* If node is an Identifier, it returns its name. If it is a literal, it returns
* its value.
*/
export default function getNameOrValue(path: NodePath): boolean | number | string | null;

View File

@@ -0,0 +1,25 @@
import printValue from './printValue.js';
/**
* If node is an Identifier, it returns its name. If it is a literal, it returns
* its value.
*/
export default function getNameOrValue(path) {
if (path.isIdentifier()) {
return path.node.name;
}
else if (path.isQualifiedTypeIdentifier() || path.isTSQualifiedName()) {
return printValue(path);
}
else if (path.isStringLiteral() ||
path.isNumericLiteral() ||
path.isBooleanLiteral()) {
return path.node.value;
}
else if (path.isRegExpLiteral()) {
return path.node.pattern;
}
else if (path.isNullLiteral()) {
return null;
}
throw new TypeError(`Argument must be Identifier, Literal, QualifiedTypeIdentifier or TSQualifiedName. Received '${path.node.type}'`);
}

View File

@@ -0,0 +1,5 @@
import type { NodePath } from '@babel/traverse';
import type { ArrayPattern, AssignmentPattern, Identifier, ObjectPattern, RestElement, TSParameterProperty } from '@babel/types';
type ParameterNodePath = NodePath<ArrayPattern | AssignmentPattern | Identifier | ObjectPattern | RestElement | TSParameterProperty>;
export default function getParameterName(parameterPath: ParameterNodePath): string;
export {};

View File

@@ -0,0 +1,21 @@
import printValue from './printValue.js';
export default function getParameterName(parameterPath) {
if (parameterPath.isIdentifier()) {
return parameterPath.node.name;
}
else if (parameterPath.isAssignmentPattern()) {
return getParameterName(parameterPath.get('left'));
}
else if (parameterPath.isObjectPattern() ||
parameterPath.isArrayPattern()) {
return printValue(parameterPath);
}
else if (parameterPath.isRestElement()) {
return `...${getParameterName(parameterPath.get('argument'))}`;
}
else if (parameterPath.isTSParameterProperty()) {
return getParameterName(parameterPath.get('parameter'));
}
throw new TypeError('Parameter name must be one of Identifier, AssignmentPattern, ArrayPattern, ' +
`ObjectPattern or RestElement, instead got ${parameterPath.node.type}`);
}

View File

@@ -0,0 +1,11 @@
import type { NodePath } from '@babel/traverse';
import type { PropTypeDescriptor } from '../Documentation.js';
/**
* Tries to identify the prop type by inspecting the path for known
* prop type names. This method doesn't check whether the found type is actually
* from React.PropTypes. It simply assumes that a match has the same meaning
* as the React.PropTypes one.
*
* If there is no match, "custom" is returned.
*/
export default function getPropType(path: NodePath): PropTypeDescriptor;

View File

@@ -0,0 +1,239 @@
import { getDocblock } from '../utils/docblock.js';
import getMembers from './getMembers.js';
import getPropertyName from './getPropertyName.js';
import isRequiredPropType from '../utils/isRequiredPropType.js';
import printValue from './printValue.js';
import resolveToValue from './resolveToValue.js';
import resolveObjectKeysToArray from './resolveObjectKeysToArray.js';
import resolveObjectValuesToArray from './resolveObjectValuesToArray.js';
function getEnumValuesFromArrayExpression(path) {
const values = [];
path.get('elements').forEach((elementPath) => {
if (!elementPath.hasNode())
return;
if (elementPath.isSpreadElement()) {
const value = resolveToValue(elementPath.get('argument'));
if (value.isArrayExpression()) {
// if the SpreadElement resolved to an Array, add all their elements too
return values.push(...getEnumValuesFromArrayExpression(value));
}
else {
// otherwise we'll just print the SpreadElement itself
return values.push({
value: printValue(elementPath),
computed: !elementPath.isLiteral(),
});
}
}
// try to resolve the array element to it's value
const value = resolveToValue(elementPath);
return values.push({
value: printValue(value.parentPath?.isImportDeclaration() ? elementPath : value),
computed: !value.isLiteral(),
});
});
return values;
}
function getPropTypeOneOf(type, argumentPath) {
const value = resolveToValue(argumentPath);
if (value.isArrayExpression()) {
type.value = getEnumValuesFromArrayExpression(value);
}
else {
const objectValues = resolveObjectKeysToArray(value) || resolveObjectValuesToArray(value);
if (objectValues) {
type.value = objectValues.map((objectValue) => ({
value: objectValue,
computed: false,
}));
}
else {
// could not easily resolve to an Array, let's print the original value
type.computed = true;
type.value = printValue(argumentPath);
}
}
return type;
}
function getPropTypeOneOfType(type, argumentPath) {
if (argumentPath.isArrayExpression()) {
type.value = argumentPath.get('elements').map((elementPath) => {
if (!elementPath.hasNode())
return;
const descriptor = getPropType(elementPath);
const docs = getDocblock(elementPath);
if (docs) {
descriptor.description = docs;
}
return descriptor;
});
}
return type;
}
function getPropTypeArrayOf(type, argumentPath) {
const docs = getDocblock(argumentPath);
if (docs) {
type.description = docs;
}
const subType = getPropType(argumentPath);
type.value = subType;
return type;
}
function getPropTypeObjectOf(type, argumentPath) {
const docs = getDocblock(argumentPath);
if (docs) {
type.description = docs;
}
const subType = getPropType(argumentPath);
type.value = subType;
return type;
}
function getFirstArgument(path) {
let argument;
if (path.isCallExpression()) {
argument = path.get('arguments')[0];
}
else {
const members = getMembers(path, true);
if (members[0] && members[0].argumentPaths[0]) {
argument = members[0].argumentPaths[0];
}
}
return argument;
}
function isCyclicReference(argument, argumentPath) {
return Boolean(argument && resolveToValue(argument) === argumentPath);
}
/**
* Handles shape and exact prop types
*/
function getPropTypeShapish(type, argumentPath) {
if (!argumentPath.isObjectExpression()) {
argumentPath = resolveToValue(argumentPath);
}
if (argumentPath.isObjectExpression()) {
const value = {};
let rawValue;
argumentPath.get('properties').forEach((propertyPath) => {
// We only handle ObjectProperty as there is nothing to handle for
// SpreadElements and ObjectMethods
if (propertyPath.isObjectProperty()) {
const propertyName = getPropertyName(propertyPath);
if (!propertyName)
return;
const valuePath = propertyPath.get('value');
const argument = getFirstArgument(valuePath);
// This indicates we have a cyclic reference in the shape
// In this case we simply print the argument to shape and bail
if (argument && isCyclicReference(argument, argumentPath)) {
rawValue = printValue(argument);
return;
}
const descriptor = getPropType(valuePath);
const docs = getDocblock(propertyPath);
if (docs) {
descriptor.description = docs;
}
descriptor.required = isRequiredPropType(valuePath);
value[propertyName] = descriptor;
}
});
type.value = rawValue ?? value;
}
return type;
}
function getPropTypeInstanceOf(_type, argumentPath) {
return {
name: 'instanceOf',
value: printValue(argumentPath),
};
}
const simplePropTypes = [
'array',
'bool',
'func',
'number',
'object',
'string',
'any',
'element',
'node',
'symbol',
'elementType',
];
function isSimplePropType(name) {
return simplePropTypes.includes(name);
}
const propTypes = new Map([
['oneOf', callPropTypeHandler.bind(null, 'enum', getPropTypeOneOf)],
['oneOfType', callPropTypeHandler.bind(null, 'union', getPropTypeOneOfType)],
[
'instanceOf',
callPropTypeHandler.bind(null, 'instanceOf', getPropTypeInstanceOf),
],
['arrayOf', callPropTypeHandler.bind(null, 'arrayOf', getPropTypeArrayOf)],
['objectOf', callPropTypeHandler.bind(null, 'objectOf', getPropTypeObjectOf)],
['shape', callPropTypeHandler.bind(null, 'shape', getPropTypeShapish)],
['exact', callPropTypeHandler.bind(null, 'exact', getPropTypeShapish)],
]);
function callPropTypeHandler(name, handler, argumentPath) {
let type = { name };
if (argumentPath) {
type = handler(type, argumentPath);
}
if (!type.value) {
// If there is no argument then leave the value an empty string
type.value = argumentPath ? printValue(argumentPath) : '';
type.computed = true;
}
return type;
}
/**
* Tries to identify the prop type by inspecting the path for known
* prop type names. This method doesn't check whether the found type is actually
* from React.PropTypes. It simply assumes that a match has the same meaning
* as the React.PropTypes one.
*
* If there is no match, "custom" is returned.
*/
export default function getPropType(path) {
let descriptor = null;
getMembers(path, true).some((member) => {
const memberPath = member.path;
let name = null;
if (memberPath.isStringLiteral()) {
name = memberPath.node.value;
}
else if (memberPath.isIdentifier() && !member.computed) {
name = memberPath.node.name;
}
if (name) {
if (isSimplePropType(name)) {
descriptor = { name };
return true;
}
const propTypeHandler = propTypes.get(name);
if (propTypeHandler) {
descriptor = propTypeHandler(member.argumentPaths[0]);
return true;
}
}
return;
});
if (descriptor) {
return descriptor;
}
if (path.isIdentifier() && isSimplePropType(path.node.name)) {
return { name: path.node.name };
}
if (path.isCallExpression()) {
const callee = path.get('callee');
if (callee.isIdentifier()) {
const propTypeHandler = propTypes.get(callee.node.name);
if (propTypeHandler) {
return propTypeHandler(path.get('arguments')[0]);
}
}
}
return { name: 'custom', raw: printValue(path) };
}

View File

@@ -0,0 +1,9 @@
import type { NodePath } from '@babel/traverse';
import type { ClassMethod, ClassProperty, ObjectMethod, ObjectProperty, ObjectTypeProperty, ObjectTypeSpreadProperty, SpreadElement, TSMethodSignature, TSPropertySignature } from '@babel/types';
export declare const COMPUTED_PREFIX = "@computed#";
/**
* In an ObjectExpression, the name of a property can either be an identifier
* or a literal (or dynamic, but we don't support those). This function simply
* returns the value of the literal or name of the identifier.
*/
export default function getPropertyName(propertyPath: NodePath<ClassMethod | ClassProperty | ObjectMethod | ObjectProperty | ObjectTypeProperty | ObjectTypeSpreadProperty | SpreadElement | TSMethodSignature | TSPropertySignature>): string | null;

View File

@@ -0,0 +1,36 @@
import getNameOrValue from './getNameOrValue.js';
import resolveToValue from './resolveToValue.js';
export const COMPUTED_PREFIX = '@computed#';
/**
* In an ObjectExpression, the name of a property can either be an identifier
* or a literal (or dynamic, but we don't support those). This function simply
* returns the value of the literal or name of the identifier.
*/
export default function getPropertyName(propertyPath) {
if (propertyPath.isObjectTypeSpreadProperty()) {
const argument = propertyPath.get('argument');
if (argument.isGenericTypeAnnotation()) {
return getNameOrValue(argument.get('id'));
}
return null;
}
else if (propertyPath.has('computed')) {
const key = propertyPath.get('key');
// Try to resolve variables and member expressions
if (key.isIdentifier() || key.isMemberExpression()) {
const valuePath = resolveToValue(key);
if (valuePath.isStringLiteral() || valuePath.isNumericLiteral()) {
return `${valuePath.node.value}`;
}
}
// generate name for identifier
if (key.isIdentifier()) {
return `${COMPUTED_PREFIX}${key.node.name}`;
}
if (key.isStringLiteral() || key.isNumericLiteral()) {
return `${key.node.value}`;
}
return null;
}
return `${getNameOrValue(propertyPath.get('key'))}`;
}

View File

@@ -0,0 +1,8 @@
import type { NodePath } from '@babel/traverse';
import type { Expression, ObjectExpression, ObjectMethod } from '@babel/types';
/**
* Given an ObjectExpression, this function returns the path of the value of
* the property with name `propertyName`. if the property is an ObjectMethod we
* return the ObjectMethod itself.
*/
export default function getPropertyValuePath(path: NodePath<ObjectExpression>, propertyName: string): NodePath<Expression | ObjectMethod> | null;

View File

@@ -0,0 +1,18 @@
import getPropertyName from './getPropertyName.js';
/**
* Given an ObjectExpression, this function returns the path of the value of
* the property with name `propertyName`. if the property is an ObjectMethod we
* return the ObjectMethod itself.
*/
export default function getPropertyValuePath(path, propertyName) {
const property = path
.get('properties')
.find((propertyPath) => !propertyPath.isSpreadElement() &&
getPropertyName(propertyPath) === propertyName);
if (property) {
return property.isObjectMethod()
? property
: property.get('value');
}
return null;
}

View File

@@ -0,0 +1,12 @@
import type { TypeParameters } from '../utils/getTypeParameters.js';
import type { TypeDescriptor, TSFunctionSignatureType } from '../Documentation.js';
import type { NodePath } from '@babel/traverse';
import type { TypeScript } from '@babel/types';
/**
* Tries to identify the typescript type by inspecting the path for known
* typescript 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.
*/
export default function getTSType(path: NodePath<TypeScript>, typeParamMap?: TypeParameters | null): TypeDescriptor<TSFunctionSignatureType>;

View File

@@ -0,0 +1,374 @@
import getPropertyName from './getPropertyName.js';
import printValue from './printValue.js';
import getTypeAnnotation from '../utils/getTypeAnnotation.js';
import resolveToValue from '../utils/resolveToValue.js';
import { resolveObjectToNameArray } from '../utils/resolveObjectKeysToArray.js';
import getTypeParameters from '../utils/getTypeParameters.js';
import { getDocblock } from './docblock.js';
const tsTypes = {
TSAnyKeyword: 'any',
TSBooleanKeyword: 'boolean',
TSUnknownKeyword: 'unknown',
TSNeverKeyword: 'never',
TSNullKeyword: 'null',
TSUndefinedKeyword: 'undefined',
TSNumberKeyword: 'number',
TSStringKeyword: 'string',
TSSymbolKeyword: 'symbol',
TSThisType: 'this',
TSObjectKeyword: 'object',
TSVoidKeyword: 'void',
};
const namedTypes = {
TSArrayType: handleTSArrayType,
TSTypeReference: handleTSTypeReference,
TSTypeLiteral: handleTSTypeLiteral,
TSInterfaceDeclaration: handleTSInterfaceDeclaration,
TSUnionType: handleTSUnionType,
TSFunctionType: handleTSFunctionType,
TSIntersectionType: handleTSIntersectionType,
TSMappedType: handleTSMappedType,
TSTupleType: handleTSTupleType,
TSTypeQuery: handleTSTypeQuery,
TSTypeOperator: handleTSTypeOperator,
TSIndexedAccessType: handleTSIndexedAccessType,
TSLiteralType: handleTSLiteralType,
};
function handleTSQualifiedName(path) {
const left = path.get('left');
const right = path.get('right');
if (left.isIdentifier({ name: 'React' }) && right.isIdentifier()) {
return {
name: `${left.node.name}${right.node.name}`,
raw: printValue(path),
};
}
return { name: printValue(path).replace(/<.*>$/, '') };
}
function handleTSLiteralType(path) {
const literal = path.get('literal');
return {
name: 'literal',
value: printValue(literal),
};
}
function handleTSArrayType(path, typeParams) {
return {
name: 'Array',
elements: [getTSTypeWithResolvedTypes(path.get('elementType'), typeParams)],
raw: printValue(path),
};
}
function handleTSTypeReference(path, typeParams) {
let type;
const typeName = path.get('typeName');
if (typeName.isTSQualifiedName()) {
type = handleTSQualifiedName(typeName);
}
else {
type = { name: typeName.node.name };
}
const resolvedPath = (typeParams && typeParams[type.name]) ||
resolveToValue(path.get('typeName'));
const typeParameters = path.get('typeParameters');
const resolvedTypeParameters = resolvedPath.get('typeParameters');
if (typeParameters.hasNode() && resolvedTypeParameters.hasNode()) {
typeParams = getTypeParameters(resolvedTypeParameters, typeParameters, typeParams);
}
if (typeParams && typeParams[type.name]) {
// Open question: Why is this `null` instead of `typeParams`
type = getTSTypeWithResolvedTypes(resolvedPath, null);
}
const resolvedTypeAnnotation = resolvedPath.get('typeAnnotation');
if (resolvedTypeAnnotation.hasNode()) {
type = getTSTypeWithResolvedTypes(resolvedTypeAnnotation, typeParams);
}
else if (typeParameters.hasNode()) {
const params = typeParameters.get('params');
type = {
...type,
elements: params.map((param) => getTSTypeWithResolvedTypes(param, typeParams)),
raw: printValue(path),
};
}
return type;
}
function getTSTypeWithRequirements(path, typeParams) {
const type = getTSTypeWithResolvedTypes(path, typeParams);
type.required =
!('optional' in path.parentPath.node) || !path.parentPath.node.optional;
return type;
}
function handleTSTypeLiteral(path, typeParams) {
const type = {
name: 'signature',
type: 'object',
raw: printValue(path),
signature: { properties: [] },
};
path.get('members').forEach((param) => {
const typeAnnotation = param.get('typeAnnotation');
if ((param.isTSPropertySignature() || param.isTSMethodSignature()) &&
typeAnnotation.hasNode()) {
const propName = getPropertyName(param);
if (!propName) {
return;
}
const docblock = getDocblock(param);
let doc = {};
if (docblock) {
doc = { description: docblock };
}
type.signature.properties.push({
key: propName,
value: getTSTypeWithRequirements(typeAnnotation, typeParams),
...doc,
});
}
else if (param.isTSCallSignatureDeclaration()) {
type.signature.constructor = handleTSFunctionType(param, typeParams);
}
else if (param.isTSIndexSignature() && typeAnnotation.hasNode()) {
const parameters = param.get('parameters');
if (parameters[0]) {
const idTypeAnnotation = parameters[0].get('typeAnnotation');
if (idTypeAnnotation.hasNode()) {
type.signature.properties.push({
key: getTSTypeWithResolvedTypes(idTypeAnnotation, typeParams),
value: getTSTypeWithRequirements(typeAnnotation, typeParams),
});
}
}
}
});
return type;
}
function handleTSInterfaceDeclaration(path) {
// Interfaces are handled like references which would be documented separately,
// rather than inlined like type aliases.
return {
name: path.node.id.name,
};
}
function handleTSUnionType(path, typeParams) {
return {
name: 'union',
raw: printValue(path),
elements: path
.get('types')
.map((subType) => getTSTypeWithResolvedTypes(subType, typeParams)),
};
}
function handleTSIntersectionType(path, typeParams) {
return {
name: 'intersection',
raw: printValue(path),
elements: path
.get('types')
.map((subType) => getTSTypeWithResolvedTypes(subType, typeParams)),
};
}
// type OptionsFlags<Type> = { [Property in keyof Type]; };
function handleTSMappedType(path, typeParams) {
const key = getTSTypeWithResolvedTypes(path.get('typeParameter').get('constraint'), typeParams);
key.required = !path.node.optional;
const typeAnnotation = path.get('typeAnnotation');
let value;
if (typeAnnotation.hasNode()) {
value = getTSTypeWithResolvedTypes(typeAnnotation, typeParams);
}
else {
value = { name: 'any' };
}
return {
name: 'signature',
type: 'object',
raw: printValue(path),
signature: {
properties: [
{
key,
value,
},
],
},
};
}
function handleTSFunctionType(path, typeParams) {
let returnType;
const annotation = path.get('typeAnnotation');
if (annotation.hasNode()) {
returnType = getTSTypeWithResolvedTypes(annotation, typeParams);
}
const type = {
name: 'signature',
type: 'function',
raw: printValue(path),
signature: {
arguments: [],
return: returnType,
},
};
path.get('parameters').forEach((param) => {
const typeAnnotation = getTypeAnnotation(param);
const arg = {
type: typeAnnotation
? getTSTypeWithResolvedTypes(typeAnnotation, typeParams)
: undefined,
name: '',
};
if (param.isIdentifier()) {
arg.name = param.node.name;
if (param.node.name === 'this') {
type.signature.this = arg.type;
return;
}
}
else if (param.isRestElement()) {
const restArgument = param.get('argument');
if (restArgument.isIdentifier()) {
arg.name = restArgument.node.name;
}
else {
arg.name = printValue(restArgument);
}
arg.rest = true;
}
type.signature.arguments.push(arg);
});
return type;
}
function handleTSTupleType(path, typeParams) {
const type = {
name: 'tuple',
raw: printValue(path),
elements: [],
};
path.get('elementTypes').forEach((param) => {
type.elements.push(getTSTypeWithResolvedTypes(param, typeParams));
});
return type;
}
function handleTSTypeQuery(path, typeParams) {
const exprName = path.get('exprName');
if (exprName.isIdentifier()) {
const resolvedPath = resolveToValue(path.get('exprName'));
if (resolvedPath.has('typeAnnotation')) {
return getTSTypeWithResolvedTypes(resolvedPath.get('typeAnnotation'), typeParams);
}
return { name: exprName.node.name };
}
else if (exprName.isTSQualifiedName()) {
return handleTSQualifiedName(exprName);
}
else {
// TSImportType
return { name: printValue(exprName) };
}
}
function handleTSTypeOperator(path, typeParams) {
if (path.node.operator !== 'keyof') {
return null;
}
let value = path.get('typeAnnotation');
if (value.isTSTypeQuery()) {
value = value.get('exprName');
}
else if ('id' in value.node) {
value = value.get('id');
}
else if (value.isTSTypeReference()) {
return getTSTypeWithResolvedTypes(value, typeParams);
}
const resolvedPath = resolveToValue(value);
if (resolvedPath.isObjectExpression() || resolvedPath.isTSTypeLiteral()) {
const keys = resolveObjectToNameArray(resolvedPath, true);
if (keys) {
return {
name: 'union',
raw: printValue(path),
elements: keys.map((key) => ({ name: 'literal', value: key })),
};
}
}
return null;
}
function handleTSIndexedAccessType(path, typeParams) {
const objectType = getTSTypeWithResolvedTypes(path.get('objectType'), typeParams);
const indexType = getTSTypeWithResolvedTypes(path.get('indexType'), typeParams);
// We only get the signature if the objectType is a type (vs interface)
if (!objectType.signature) {
return {
name: `${objectType.name}[${indexType.value ? indexType.value.toString() : indexType.name}]`,
raw: printValue(path),
};
}
const resolvedType = objectType.signature.properties.find((p) => {
// indexType.value = "'foo'"
return indexType.value && p.key === indexType.value.replace(/['"]+/g, '');
});
if (!resolvedType) {
return { name: 'unknown' };
}
return {
name: resolvedType.value.name,
raw: printValue(path),
};
}
let visitedTypes = {};
function getTSTypeWithResolvedTypes(path, typeParams) {
if (path.isTSTypeAnnotation()) {
path = path.get('typeAnnotation');
}
const node = path.node;
let type = null;
let typeAliasName = null;
if (path.parentPath.isTSTypeAliasDeclaration()) {
typeAliasName = path.parentPath.node.id.name;
}
// 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 (typeAliasName) {
if (visitedTypes[typeAliasName] === true) {
// if we are currently visiting this node then just return the name
// as we are starting to endless loop
return { name: typeAliasName };
}
else if (typeof visitedTypes[typeAliasName] === 'object') {
// if we already resolved the type simple return it
return visitedTypes[typeAliasName];
}
// mark the type as visited
visitedTypes[typeAliasName] = true;
}
if (node.type in tsTypes) {
type = { name: tsTypes[node.type] };
}
else if (node.type in namedTypes) {
type = namedTypes[node.type](path, typeParams);
}
if (!type) {
type = { name: 'unknown' };
}
if (typeAliasName) {
// mark the type as unvisited so that further calls can resolve the type again
visitedTypes[typeAliasName] = type;
}
return type;
}
/**
* Tries to identify the typescript type by inspecting the path for known
* typescript 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.
*/
export default function getTSType(path, typeParamMap = null) {
// 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 = getTSTypeWithResolvedTypes(path, typeParamMap);
visitedTypes = {};
return type;
}

View File

@@ -0,0 +1,7 @@
import type { NodePath } from '@babel/traverse';
import type { FlowType, Node, TSType } from '@babel/types';
/**
* Gets the most inner valuable TypeAnnotation from path. If no TypeAnnotation
* can be found null is returned
*/
export default function getTypeAnnotation<T extends Node = FlowType | TSType>(path: NodePath<Node | null | undefined>): NodePath<T> | null;

View File

@@ -0,0 +1,15 @@
/**
* Gets the most inner valuable TypeAnnotation from path. If no TypeAnnotation
* can be found null is returned
*/
export default function getTypeAnnotation(path) {
if (!path.has('typeAnnotation'))
return null;
let resultPath = path;
do {
resultPath = resultPath.get('typeAnnotation');
} while (resultPath.has('typeAnnotation') &&
!resultPath.isFlowType() &&
!resultPath.isTSType());
return resultPath;
}

View File

@@ -0,0 +1,12 @@
import type { NodePath } from '@babel/traverse';
import type Documentation from '../Documentation.js';
import type { TypeParameters } from './getTypeParameters.js';
/**
* Given an React component (stateless or class) tries to find
* flow or TS types for the props. It may find multiple types.
* If not found or it is not one of the supported component types,
* this function returns an empty array.
*/
declare const _default: (componentDefinition: NodePath) => NodePath[];
export default _default;
export declare function applyToTypeProperties(documentation: Documentation, path: NodePath, callback: (propertyPath: NodePath, params: TypeParameters | null) => void, typeParams: TypeParameters | null): void;

View File

@@ -0,0 +1,153 @@
import getMemberValuePath from './getMemberValuePath.js';
import getTypeAnnotation from './getTypeAnnotation.js';
import getTypeParameters from './getTypeParameters.js';
import isReactComponentClass from './isReactComponentClass.js';
import isReactForwardRefCall from './isReactForwardRefCall.js';
import resolveGenericTypeAnnotation from './resolveGenericTypeAnnotation.js';
import resolveToValue from './resolveToValue.js';
import getTypeIdentifier from './getTypeIdentifier.js';
import isReactBuiltinReference from './isReactBuiltinReference.js';
import unwrapBuiltinTSPropTypes from './unwrapBuiltinTSPropTypes.js';
function getStatelessPropsPath(componentDefinition) {
if (!componentDefinition.isFunction())
return;
return componentDefinition.get('params')[0];
}
function getForwardRefGenericsType(componentDefinition) {
const typeParameters = componentDefinition.get('typeParameters');
if (typeParameters && typeParameters.hasNode()) {
const params = typeParameters.get('params');
return params[1] ?? null;
}
return null;
}
function findAssignedVariableType(componentDefinition) {
const variableDeclarator = componentDefinition.findParent((path) => path.isVariableDeclarator());
if (!variableDeclarator)
return null;
const typeAnnotation = getTypeAnnotation(variableDeclarator.get('id'));
if (!typeAnnotation)
return null;
if (typeAnnotation.isTSTypeReference()) {
const typeName = typeAnnotation.get('typeName');
if (isReactBuiltinReference(typeName, 'FunctionComponent') ||
isReactBuiltinReference(typeName, 'FC') ||
isReactBuiltinReference(typeName, 'VoidFunctionComponent') ||
isReactBuiltinReference(typeName, 'VFC')) {
const typeParameters = typeAnnotation.get('typeParameters');
if (typeParameters.hasNode()) {
return typeParameters.get('params')[0] ?? null;
}
}
}
return null;
}
/**
* Given an React component (stateless or class) tries to find
* flow or TS types for the props. It may find multiple types.
* If not found or it is not one of the supported component types,
* this function returns an empty array.
*/
export default (componentDefinition) => {
const typePaths = [];
if (isReactComponentClass(componentDefinition)) {
const superTypes = componentDefinition.get('superTypeParameters');
if (superTypes.hasNode()) {
const params = superTypes.get('params');
if (params.length >= 1) {
typePaths.push(params[params.length === 3 ? 1 : 0]);
}
}
else {
const propsMemberPath = getMemberValuePath(componentDefinition, 'props');
if (!propsMemberPath) {
return [];
}
const typeAnnotation = getTypeAnnotation(propsMemberPath.parentPath);
if (typeAnnotation) {
typePaths.push(typeAnnotation);
}
}
}
else {
if (isReactForwardRefCall(componentDefinition)) {
const genericTypeAnnotation = getForwardRefGenericsType(componentDefinition);
if (genericTypeAnnotation) {
typePaths.push(genericTypeAnnotation);
}
componentDefinition = resolveToValue(componentDefinition.get('arguments')[0]);
}
const propsParam = getStatelessPropsPath(componentDefinition);
if (propsParam) {
const typeAnnotation = getTypeAnnotation(propsParam);
if (typeAnnotation) {
typePaths.push(typeAnnotation);
}
}
const assignedVariableType = findAssignedVariableType(componentDefinition);
if (assignedVariableType) {
typePaths.push(assignedVariableType);
}
}
return typePaths.map((typePath) => unwrapBuiltinTSPropTypes(typePath));
};
export function applyToTypeProperties(documentation, path, callback, typeParams) {
if (path.isObjectTypeAnnotation()) {
path
.get('properties')
.forEach((propertyPath) => callback(propertyPath, typeParams));
}
else if (path.isTSTypeLiteral()) {
path
.get('members')
.forEach((propertyPath) => callback(propertyPath, typeParams));
}
else if (path.isInterfaceDeclaration()) {
applyExtends(documentation, path, callback, typeParams);
path
.get('body')
.get('properties')
.forEach((propertyPath) => callback(propertyPath, typeParams));
}
else if (path.isTSInterfaceDeclaration()) {
applyExtends(documentation, path, callback, typeParams);
path
.get('body')
.get('body')
.forEach((propertyPath) => callback(propertyPath, typeParams));
}
else if (path.isIntersectionTypeAnnotation() ||
path.isTSIntersectionType()) {
path.get('types').forEach((typesPath) => applyToTypeProperties(documentation, typesPath, callback, typeParams));
}
else if (!path.isUnionTypeAnnotation()) {
// The react-docgen output format does not currently allow
// for the expression of union types
const typePath = resolveGenericTypeAnnotation(path);
if (typePath) {
applyToTypeProperties(documentation, typePath, callback, typeParams);
}
}
}
function applyExtends(documentation, path, callback, typeParams) {
const classExtends = path.get('extends');
if (!Array.isArray(classExtends)) {
return;
}
classExtends.forEach((extendsPath) => {
const resolvedPath = resolveGenericTypeAnnotation(extendsPath);
if (resolvedPath) {
if (resolvedPath.has('typeParameters') &&
extendsPath.node.typeParameters) {
typeParams = getTypeParameters(resolvedPath.get('typeParameters'), extendsPath.get('typeParameters'), typeParams);
}
applyToTypeProperties(documentation, resolvedPath, callback, typeParams);
}
else {
const idPath = getTypeIdentifier(extendsPath);
if (idPath && idPath.isIdentifier()) {
documentation.addComposes(idPath.node.name);
}
}
});
}

View File

@@ -0,0 +1,2 @@
import type { NodePath } from '@babel/traverse';
export default function getTypeIdentifier(path: NodePath): NodePath | null;

View File

@@ -0,0 +1,12 @@
export default function getTypeIdentifier(path) {
if (path.has('id')) {
return path.get('id');
}
else if (path.isTSTypeReference()) {
return path.get('typeName');
}
else if (path.isTSExpressionWithTypeArguments()) {
return path.get('expression');
}
return null;
}

View File

@@ -0,0 +1,4 @@
import type { NodePath } from '@babel/traverse';
import type { TSTypeParameterDeclaration, TSTypeParameterInstantiation, TypeParameterDeclaration, TypeParameterInstantiation } from '@babel/types';
export type TypeParameters = Record<string, NodePath>;
export default function getTypeParameters(declaration: NodePath<TSTypeParameterDeclaration | TypeParameterDeclaration>, instantiation: NodePath<TSTypeParameterInstantiation | TypeParameterInstantiation>, inputParams: TypeParameters | null | undefined): TypeParameters;

View File

@@ -0,0 +1,34 @@
import resolveGenericTypeAnnotation from '../utils/resolveGenericTypeAnnotation.js';
export default function getTypeParameters(declaration, instantiation, inputParams) {
const params = {};
const numInstantiationParams = instantiation.node.params.length;
let i = 0;
declaration
.get('params')
.forEach((paramPath) => {
const key = paramPath.node.name;
const defaultProp = paramPath.get('default');
const defaultTypePath = defaultProp.hasNode() ? defaultProp : null;
const typePath = i < numInstantiationParams
? instantiation.get('params')[i++]
: defaultTypePath;
if (typePath) {
let resolvedTypePath = resolveGenericTypeAnnotation(typePath) || typePath;
let typeName;
if (resolvedTypePath.isTSTypeReference()) {
typeName = resolvedTypePath.get('typeName');
}
else if (resolvedTypePath.isGenericTypeAnnotation()) {
typeName = resolvedTypePath.get('id');
}
if (typeName &&
inputParams &&
typeName.isIdentifier() &&
inputParams[typeName.node.name]) {
resolvedTypePath = inputParams[typeName.node.name];
}
params[key] = resolvedTypePath;
}
});
return params;
}

View File

@@ -0,0 +1,55 @@
import * as docblock from './docblock.js';
import * as expressionTo from './expressionTo.js';
import * as flowUtilityTypes from './flowUtilityTypes.js';
import * as traverse from './traverse.js';
export { docblock, expressionTo, flowUtilityTypes, traverse };
export { default as findFunctionReturn } from './findFunctionReturn.js';
export { default as getClassMemberValuePath } from './getClassMemberValuePath.js';
export { default as getFlowType } from './getFlowType.js';
export { default as getMemberExpressionRoot } from './getMemberExpressionRoot.js';
export { default as getMemberExpressionValuePath } from './getMemberExpressionValuePath.js';
export { default as getMembers } from './getMembers.js';
export { default as getMemberValuePath, isSupportedDefinitionType, } from './getMemberValuePath.js';
export { default as getMethodDocumentation } from './getMethodDocumentation.js';
export type { MethodNodePath } from './getMethodDocumentation.js';
export { default as getNameOrValue } from './getNameOrValue.js';
export { default as getParameterName } from './getParameterName.js';
export { default as getPropertyName, COMPUTED_PREFIX, } from './getPropertyName.js';
export { default as getPropertyValuePath } from './getPropertyValuePath.js';
export { default as getPropType } from './getPropType.js';
export { default as getTSType } from './getTSType.js';
export { default as getTypeAnnotation } from './getTypeAnnotation.js';
export { default as getTypeFromReactComponent, applyToTypeProperties, } from './getTypeFromReactComponent.js';
export { default as getTypeIdentifier } from './getTypeIdentifier.js';
export { default as getTypeParameters } from './getTypeParameters.js';
export type { TypeParameters } from './getTypeParameters.js';
export { default as isDestructuringAssignment } from './isDestructuringAssignment.js';
export { default as isExportsOrModuleAssignment } from './isExportsOrModuleAssignment.js';
export { default as isImportSpecifier } from './isImportSpecifier.js';
export { default as isReactBuiltinCall } from './isReactBuiltinCall.js';
export { default as isReactBuiltinReference } from './isReactBuiltinReference.js';
export { default as isReactChildrenElementCall } from './isReactChildrenElementCall.js';
export { default as isReactCloneElementCall } from './isReactCloneElementCall.js';
export { default as isReactComponentClass } from './isReactComponentClass.js';
export { default as isReactComponentMethod } from './isReactComponentMethod.js';
export { default as isReactCreateClassCall } from './isReactCreateClassCall.js';
export { default as isReactCreateElementCall } from './isReactCreateElementCall.js';
export { default as isReactForwardRefCall } from './isReactForwardRefCall.js';
export { default as isReactModuleName } from './isReactModuleName.js';
export { default as isRequiredPropType } from './isRequiredPropType.js';
export { default as isStatelessComponent } from './isStatelessComponent.js';
export { default as isUnreachableFlowType } from './isUnreachableFlowType.js';
export { default as normalizeClassDefinition } from './normalizeClassDefinition.js';
export { default as parseJsDoc } from './parseJsDoc.js';
export { default as postProcessDocumentation } from './postProcessDocumentation.js';
export { default as printValue } from './printValue.js';
export { default as resolveExportDeclaration } from './resolveExportDeclaration.js';
export { default as resolveFunctionDefinitionToReturnValue } from './resolveFunctionDefinitionToReturnValue.js';
export { default as resolveGenericTypeAnnotation } from './resolveGenericTypeAnnotation.js';
export { default as resolveHOC } from './resolveHOC.js';
export { default as resolveObjectPatternPropertyToValue } from './resolveObjectPatternPropertyToValue.js';
export { default as resolveObjectKeysToArray, resolveObjectToNameArray, } from './resolveObjectKeysToArray.js';
export { default as resolveObjectValuesToArray } from './resolveObjectValuesToArray.js';
export { default as resolveToModule } from './resolveToModule.js';
export { default as resolveToValue } from './resolveToValue.js';
export { default as setPropDescription } from './setPropDescription.js';

53
frontend/node_modules/react-docgen/dist/utils/index.js generated vendored Normal file
View File

@@ -0,0 +1,53 @@
import * as docblock from './docblock.js';
import * as expressionTo from './expressionTo.js';
import * as flowUtilityTypes from './flowUtilityTypes.js';
import * as traverse from './traverse.js';
export { docblock, expressionTo, flowUtilityTypes, traverse };
export { default as findFunctionReturn } from './findFunctionReturn.js';
export { default as getClassMemberValuePath } from './getClassMemberValuePath.js';
export { default as getFlowType } from './getFlowType.js';
export { default as getMemberExpressionRoot } from './getMemberExpressionRoot.js';
export { default as getMemberExpressionValuePath } from './getMemberExpressionValuePath.js';
export { default as getMembers } from './getMembers.js';
export { default as getMemberValuePath, isSupportedDefinitionType, } from './getMemberValuePath.js';
export { default as getMethodDocumentation } from './getMethodDocumentation.js';
export { default as getNameOrValue } from './getNameOrValue.js';
export { default as getParameterName } from './getParameterName.js';
export { default as getPropertyName, COMPUTED_PREFIX, } from './getPropertyName.js';
export { default as getPropertyValuePath } from './getPropertyValuePath.js';
export { default as getPropType } from './getPropType.js';
export { default as getTSType } from './getTSType.js';
export { default as getTypeAnnotation } from './getTypeAnnotation.js';
export { default as getTypeFromReactComponent, applyToTypeProperties, } from './getTypeFromReactComponent.js';
export { default as getTypeIdentifier } from './getTypeIdentifier.js';
export { default as getTypeParameters } from './getTypeParameters.js';
export { default as isDestructuringAssignment } from './isDestructuringAssignment.js';
export { default as isExportsOrModuleAssignment } from './isExportsOrModuleAssignment.js';
export { default as isImportSpecifier } from './isImportSpecifier.js';
export { default as isReactBuiltinCall } from './isReactBuiltinCall.js';
export { default as isReactBuiltinReference } from './isReactBuiltinReference.js';
export { default as isReactChildrenElementCall } from './isReactChildrenElementCall.js';
export { default as isReactCloneElementCall } from './isReactCloneElementCall.js';
export { default as isReactComponentClass } from './isReactComponentClass.js';
export { default as isReactComponentMethod } from './isReactComponentMethod.js';
export { default as isReactCreateClassCall } from './isReactCreateClassCall.js';
export { default as isReactCreateElementCall } from './isReactCreateElementCall.js';
export { default as isReactForwardRefCall } from './isReactForwardRefCall.js';
export { default as isReactModuleName } from './isReactModuleName.js';
export { default as isRequiredPropType } from './isRequiredPropType.js';
export { default as isStatelessComponent } from './isStatelessComponent.js';
export { default as isUnreachableFlowType } from './isUnreachableFlowType.js';
export { default as normalizeClassDefinition } from './normalizeClassDefinition.js';
export { default as parseJsDoc } from './parseJsDoc.js';
export { default as postProcessDocumentation } from './postProcessDocumentation.js';
export { default as printValue } from './printValue.js';
export { default as resolveExportDeclaration } from './resolveExportDeclaration.js';
export { default as resolveFunctionDefinitionToReturnValue } from './resolveFunctionDefinitionToReturnValue.js';
export { default as resolveGenericTypeAnnotation } from './resolveGenericTypeAnnotation.js';
export { default as resolveHOC } from './resolveHOC.js';
export { default as resolveObjectPatternPropertyToValue } from './resolveObjectPatternPropertyToValue.js';
export { default as resolveObjectKeysToArray, resolveObjectToNameArray, } from './resolveObjectKeysToArray.js';
export { default as resolveObjectValuesToArray } from './resolveObjectValuesToArray.js';
export { default as resolveToModule } from './resolveToModule.js';
export { default as resolveToValue } from './resolveToValue.js';
export { default as setPropDescription } from './setPropDescription.js';

View File

@@ -0,0 +1,6 @@
import type { NodePath } from '@babel/traverse';
/**
* Checks if the input Identifier is part of a destructuring Assignment
* and the name of the property key matches the input name
*/
export default function isDestructuringAssignment(path: NodePath, name: string): boolean;

View File

@@ -0,0 +1,11 @@
/**
* Checks if the input Identifier is part of a destructuring Assignment
* and the name of the property key matches the input name
*/
export default function isDestructuringAssignment(path, name) {
if (!path.isObjectProperty()) {
return false;
}
const id = path.get('key');
return id.isIdentifier({ name }) && path.parentPath.isObjectPattern();
}

View File

@@ -0,0 +1,6 @@
import type { NodePath } from '@babel/traverse';
/**
* Returns true if the expression is of form `exports.foo = ...;` or
* `modules.exports = ...;`.
*/
export default function isExportsOrModuleAssignment(path: NodePath): boolean;

View File

@@ -0,0 +1,14 @@
import * as expressionTo from './expressionTo.js';
/**
* Returns true if the expression is of form `exports.foo = ...;` or
* `modules.exports = ...;`.
*/
export default function isExportsOrModuleAssignment(path) {
if (!path.isAssignmentExpression() ||
!path.get('left').isMemberExpression()) {
return false;
}
const exprArr = expressionTo.Array(path.get('left'));
return ((exprArr[0] === 'module' && exprArr[1] === 'exports') ||
exprArr[0] === 'exports');
}

View File

@@ -0,0 +1,5 @@
import type { NodePath } from '@babel/traverse';
/**
* Checks if the path is a ImportSpecifier that imports the given named export
*/
export default function isImportSpecifier(path: NodePath, name: string): boolean;

View File

@@ -0,0 +1,8 @@
/**
* Checks if the path is a ImportSpecifier that imports the given named export
*/
export default function isImportSpecifier(path, name) {
return (path.isImportSpecifier() &&
(path.get('imported').isIdentifier({ name }) ||
path.get('imported').isStringLiteral({ value: name })));
}

View File

@@ -0,0 +1,9 @@
import type { NodePath } from '@babel/traverse';
import type { CallExpression } from '@babel/types';
/**
* Returns true if the expression is a function call of the form
* `React.foo(...)`.
*/
export default function isReactBuiltinCall(path: NodePath, name: string): path is NodePath<CallExpression & {
__reactBuiltinTypeHint: true;
}>;

View File

@@ -0,0 +1,12 @@
import isReactBuiltinReference from './isReactBuiltinReference.js';
/**
* Returns true if the expression is a function call of the form
* `React.foo(...)`.
*/
export default function isReactBuiltinCall(path, name) {
if (!path.isCallExpression()) {
return false;
}
const callee = path.get('callee');
return isReactBuiltinReference(callee, name);
}

View File

@@ -0,0 +1,5 @@
import type { NodePath } from '@babel/traverse';
/**
* Returns true if the expression is a reference to a react export.
*/
export default function isReactBuiltinReference(path: NodePath, name: string): boolean;

View File

@@ -0,0 +1,48 @@
import isReactModuleName from './isReactModuleName.js';
import resolveToModule from './resolveToModule.js';
import resolveToValue from './resolveToValue.js';
import isDestructuringAssignment from './isDestructuringAssignment.js';
import isImportSpecifier from './isImportSpecifier.js';
function isNamedMemberExpression(value, name) {
if (!value.isMemberExpression()) {
return false;
}
const property = value.get('property');
return property.isIdentifier() && property.node.name === name;
}
/**
* Returns true if the expression is a reference to a react export.
*/
export default function isReactBuiltinReference(path, name) {
if (path.isMemberExpression() &&
path.get('property').isIdentifier({ name })) {
const module = resolveToModule(path.get('object'));
return Boolean(module && isReactModuleName(module));
}
// Typescript
if (path.isTSQualifiedName() && path.get('right').isIdentifier({ name })) {
const module = resolveToModule(path.get('left'));
return Boolean(module && isReactModuleName(module));
}
// Flow
if (path.isQualifiedTypeIdentifier() &&
path.get('id').isIdentifier({ name })) {
const module = resolveToModule(path.get('qualification'));
return Boolean(module && isReactModuleName(module));
}
const value = resolveToValue(path);
if (value === path) {
return false;
}
if (
// const { x } = require('react')
isDestructuringAssignment(value, name) ||
// `require('react').createElement`
isNamedMemberExpression(value, name) ||
// `import { createElement } from 'react'`
isImportSpecifier(value, name)) {
const module = resolveToModule(value);
return Boolean(module && isReactModuleName(module));
}
return false;
}

View File

@@ -0,0 +1,9 @@
import type { NodePath } from '@babel/traverse';
import type { CallExpression } from '@babel/types';
/**
* Returns true if the expression is a function call of the form
* `React.Children.only(...)` or `React.Children.map(...)`.
*/
export default function isReactChildrenElementCall(path: NodePath): path is NodePath<CallExpression & {
__reactBuiltinTypeHint: true;
}>;

View File

@@ -0,0 +1,19 @@
import isReactBuiltinReference from './isReactBuiltinReference.js';
/**
* Returns true if the expression is a function call of the form
* `React.Children.only(...)` or `React.Children.map(...)`.
*/
export default function isReactChildrenElementCall(path) {
if (!path.isCallExpression()) {
return false;
}
const callee = path.get('callee');
if (callee.isMemberExpression()) {
const calleeProperty = callee.get('property');
if (calleeProperty.isIdentifier({ name: 'only' }) ||
calleeProperty.isIdentifier({ name: 'map' })) {
return isReactBuiltinReference(callee.get('object'), 'Children');
}
}
return false;
}

View File

@@ -0,0 +1,9 @@
import type { NodePath } from '@babel/traverse';
import type { CallExpression } from '@babel/types';
/**
* Returns true if the expression is a function call of the form
* `React.cloneElement(...)`.
*/
export default function isReactCloneElementCall(path: NodePath): path is NodePath<CallExpression & {
__reactBuiltinTypeHint: true;
}>;

View File

@@ -0,0 +1,8 @@
import isReactBuiltinCall from './isReactBuiltinCall.js';
/**
* Returns true if the expression is a function call of the form
* `React.cloneElement(...)`.
*/
export default function isReactCloneElementCall(path) {
return isReactBuiltinCall(path, 'cloneElement');
}

View File

@@ -0,0 +1,7 @@
import type { NodePath } from '@babel/traverse';
import type { ClassDeclaration, ClassExpression } from '@babel/types';
/**
* Returns `true` of the path represents a class definition which either extends
* `React.Component` or has a superclass and implements a `render()` method.
*/
export default function isReactComponentClass(path: NodePath): path is NodePath<ClassDeclaration | ClassExpression>;

View File

@@ -0,0 +1,66 @@
import isReactModuleName from './isReactModuleName.js';
import resolveToModule from './resolveToModule.js';
import resolveToValue from './resolveToValue.js';
import isDestructuringAssignment from './isDestructuringAssignment.js';
import isImportSpecifier from './isImportSpecifier.js';
function isRenderMethod(path) {
if ((!path.isClassMethod() || path.node.kind !== 'method') &&
!path.isClassProperty()) {
return false;
}
if (path.node.computed || path.node.static) {
return false;
}
const key = path.get('key');
if (!key.isIdentifier() || key.node.name !== 'render') {
return false;
}
return true;
}
function classExtendsReactComponent(path) {
if (path.isMemberExpression()) {
const property = path.get('property');
if (property.isIdentifier({ name: 'Component' }) ||
property.isIdentifier({ name: 'PureComponent' })) {
return true;
}
}
else if (isImportSpecifier(path, 'Component') ||
isImportSpecifier(path, 'PureComponent')) {
return true;
}
else if (isDestructuringAssignment(path, 'Component') ||
isDestructuringAssignment(path, 'PureComponent')) {
return true;
}
return false;
}
/**
* Returns `true` of the path represents a class definition which either extends
* `React.Component` or has a superclass and implements a `render()` method.
*/
export default function isReactComponentClass(path) {
if (!path.isClass()) {
return false;
}
// React.Component or React.PureComponent
const superClass = path.get('superClass');
if (superClass.hasNode()) {
const resolvedSuperClass = resolveToValue(superClass);
if (classExtendsReactComponent(resolvedSuperClass)) {
const module = resolveToModule(resolvedSuperClass);
if (module && isReactModuleName(module)) {
return true;
}
}
}
else {
// does not extend anything
return false;
}
// render method
if (path.get('body').get('body').some(isRenderMethod)) {
return true;
}
return false;
}

View File

@@ -0,0 +1,5 @@
import type { NodePath } from '@babel/traverse';
/**
* Returns if the method path is a Component method.
*/
export default function (methodPath: NodePath): boolean;

View File

@@ -0,0 +1,34 @@
import getPropertyName from './getPropertyName.js';
const componentMethods = [
'componentDidMount',
'componentDidReceiveProps',
'componentDidUpdate',
'componentWillMount',
'UNSAFE_componentWillMount',
'componentWillReceiveProps',
'UNSAFE_componentWillReceiveProps',
'componentWillUnmount',
'componentWillUpdate',
'UNSAFE_componentWillUpdate',
'getChildContext',
'getDefaultProps',
'getInitialState',
'render',
'shouldComponentUpdate',
'getDerivedStateFromProps',
'getDerivedStateFromError',
'getSnapshotBeforeUpdate',
'componentDidCatch',
];
/**
* Returns if the method path is a Component method.
*/
export default function (methodPath) {
if (!methodPath.isClassMethod() &&
!methodPath.isObjectMethod() &&
!methodPath.isObjectProperty()) {
return false;
}
const name = getPropertyName(methodPath);
return Boolean(name && componentMethods.indexOf(name) !== -1);
}

View File

@@ -0,0 +1,13 @@
import type { NodePath } from '@babel/traverse';
import type { CallExpression } from '@babel/types';
/**
* Returns true if the expression is a function call of the form
* `React.createClass(...)` or
* ```
* import createReactClass from 'create-react-class';
* createReactClass(...);
* ```
*/
export default function isReactCreateClassCall(path: NodePath): path is NodePath<CallExpression & {
__reactBuiltinTypeHint: true;
}>;

View File

@@ -0,0 +1,29 @@
import resolveToModule from './resolveToModule.js';
import isReactBuiltinCall from './isReactBuiltinCall.js';
/**
* Returns true if the expression is a function call of the form
* ```
* import createReactClass from 'create-react-class';
* createReactClass(...);
* ```
*/
function isReactCreateClassCallModular(path) {
if (!path.isCallExpression()) {
return false;
}
const module = resolveToModule(path);
return Boolean(module && module === 'create-react-class');
}
/**
* Returns true if the expression is a function call of the form
* `React.createClass(...)` or
* ```
* import createReactClass from 'create-react-class';
* createReactClass(...);
* ```
*/
export default function isReactCreateClassCall(path) {
return ((isReactBuiltinCall(path, 'createClass') &&
path.get('arguments').length === 1) ||
isReactCreateClassCallModular(path));
}

View File

@@ -0,0 +1,9 @@
import type { NodePath } from '@babel/traverse';
import type { CallExpression } from '@babel/types';
/**
* Returns true if the expression is a function call of the form
* `React.createElement(...)`.
*/
export default function isReactCreateElementCall(path: NodePath): path is NodePath<CallExpression & {
__reactBuiltinTypeHint: true;
}>;

View File

@@ -0,0 +1,8 @@
import isReactBuiltinCall from './isReactBuiltinCall.js';
/**
* Returns true if the expression is a function call of the form
* `React.createElement(...)`.
*/
export default function isReactCreateElementCall(path) {
return isReactBuiltinCall(path, 'createElement');
}

View File

@@ -0,0 +1,9 @@
import type { NodePath } from '@babel/traverse';
import type { CallExpression } from '@babel/types';
/**
* Returns true if the expression is a function call of the form
* `React.forwardRef(...)`.
*/
export default function isReactForwardRefCall(path: NodePath): path is NodePath<CallExpression & {
__reactBuiltinTypeHint: true;
}>;

View File

@@ -0,0 +1,9 @@
import isReactBuiltinCall from './isReactBuiltinCall.js';
/**
* Returns true if the expression is a function call of the form
* `React.forwardRef(...)`.
*/
export default function isReactForwardRefCall(path) {
return (isReactBuiltinCall(path, 'forwardRef') &&
path.get('arguments').length === 1);
}

View File

@@ -0,0 +1,5 @@
/**
* Takes a module name (string) and returns true if it refers to a root react
* module name.
*/
export default function isReactModuleName(moduleName: string): boolean;

View File

@@ -0,0 +1,14 @@
const reactModules = [
'react',
'react/addons',
'react-native',
'proptypes',
'prop-types',
];
/**
* Takes a module name (string) and returns true if it refers to a root react
* module name.
*/
export default function isReactModuleName(moduleName) {
return reactModules.includes(moduleName.toLowerCase());
}

View File

@@ -0,0 +1,5 @@
import type { NodePath } from '@babel/traverse';
/**
* Returns true of the prop is required, according to its type definition
*/
export default function isRequiredPropType(path: NodePath): boolean;

View File

@@ -0,0 +1,8 @@
import getMembers from '../utils/getMembers.js';
/**
* Returns true of the prop is required, according to its type definition
*/
export default function isRequiredPropType(path) {
return getMembers(path).some(({ computed, path: memberPath }) => (!computed && memberPath.isIdentifier({ name: 'isRequired' })) ||
memberPath.isStringLiteral({ value: 'isRequired' }));
}

View File

@@ -0,0 +1,6 @@
import type { NodePath } from '@babel/traverse';
import type { StatelessComponentNode } from '../resolver/index.js';
/**
* Returns `true` if the path represents a function which returns a JSXElement
*/
export default function isStatelessComponent(path: NodePath): path is NodePath<StatelessComponentNode>;

View File

@@ -0,0 +1,28 @@
import isReactCreateElementCall from './isReactCreateElementCall.js';
import isReactCloneElementCall from './isReactCloneElementCall.js';
import isReactChildrenElementCall from './isReactChildrenElementCall.js';
import findFunctionReturn from './findFunctionReturn.js';
const validPossibleStatelessComponentTypes = [
'ArrowFunctionExpression',
'FunctionDeclaration',
'FunctionExpression',
'ObjectMethod',
];
function isJSXElementOrReactCall(path) {
return (path.isJSXElement() ||
path.isJSXFragment() ||
(path.isCallExpression() &&
(isReactCreateElementCall(path) ||
isReactCloneElementCall(path) ||
isReactChildrenElementCall(path))));
}
/**
* Returns `true` if the path represents a function which returns a JSXElement
*/
export default function isStatelessComponent(path) {
if (!path.inType(...validPossibleStatelessComponentTypes)) {
return false;
}
const foundPath = findFunctionReturn(path, isJSXElementOrReactCall);
return Boolean(foundPath);
}

View File

@@ -0,0 +1,7 @@
import type { NodePath } from '@babel/traverse';
/**
* Returns true of the path is an unreachable TypePath
* This evaluates the NodePaths returned from resolveToValue
*/
declare const _default: (path: NodePath) => boolean;
export default _default;

View File

@@ -0,0 +1,9 @@
/**
* Returns true of the path is an unreachable TypePath
* This evaluates the NodePaths returned from resolveToValue
*/
export default (path) => {
return (path.isIdentifier() ||
path.parentPath?.isImportDeclaration() ||
path.isCallExpression());
};

View File

@@ -0,0 +1,22 @@
import type { NodePath } from '@babel/traverse';
import type { ClassDeclaration, ClassExpression } from '@babel/types';
/**
* Given a class definition (i.e. `class` declaration or expression), this
* function "normalizes" the definition, by looking for assignments of static
* properties and converting them to ClassProperties.
*
* Example:
*
* class MyComponent extends React.Component {
* // ...
* }
* MyComponent.propTypes = { ... };
*
* is converted to
*
* class MyComponent extends React.Component {
* // ...
* static propTypes = { ... };
* }
*/
export default function normalizeClassDefinition(classDefinition: NodePath<ClassDeclaration | ClassExpression>): void;

View File

@@ -0,0 +1,92 @@
import { classProperty, inheritsComments } from '@babel/types';
import getMemberExpressionRoot from '../utils/getMemberExpressionRoot.js';
import getMembers from '../utils/getMembers.js';
import { visitors } from '@babel/traverse';
import { ignore } from './traverse.js';
const explodedVisitors = visitors.explode({
Function: { enter: ignore },
Class: { enter: ignore },
Loop: { enter: ignore },
AssignmentExpression(path, state) {
const left = path.get('left');
if (left.isMemberExpression()) {
const first = getMemberExpressionRoot(left);
if (first.isIdentifier({ name: state.variableName })) {
const [member] = getMembers(left);
if (member &&
!member.path.has('computed') &&
!member.path.isPrivateName()) {
const property = classProperty(member.path.node, path.node.right, null, null, false, true);
inheritsComments(property, path.node);
if (path.parentPath.isExpressionStatement()) {
inheritsComments(property, path.parentPath.node);
}
state.classDefinition.get('body').unshiftContainer('body', property);
path.skip();
path.remove();
}
}
}
else {
path.skip();
}
},
});
/**
* Given a class definition (i.e. `class` declaration or expression), this
* function "normalizes" the definition, by looking for assignments of static
* properties and converting them to ClassProperties.
*
* Example:
*
* class MyComponent extends React.Component {
* // ...
* }
* MyComponent.propTypes = { ... };
*
* is converted to
*
* class MyComponent extends React.Component {
* // ...
* static propTypes = { ... };
* }
*/
export default function normalizeClassDefinition(classDefinition) {
let variableName;
if (classDefinition.isClassDeclaration()) {
// Class declarations may not have an id, e.g.: `export default class extends React.Component {}`
if (classDefinition.node.id) {
variableName = classDefinition.node.id.name;
}
}
else if (classDefinition.isClassExpression()) {
let parentPath = classDefinition.parentPath;
while (parentPath &&
parentPath.node !== classDefinition.scope.block &&
!parentPath.isBlockStatement()) {
if (parentPath.isVariableDeclarator()) {
const idPath = parentPath.get('id');
if (idPath.isIdentifier()) {
variableName = idPath.node.name;
break;
}
}
else if (parentPath.isAssignmentExpression()) {
const leftPath = parentPath.get('left');
if (leftPath.isIdentifier()) {
variableName = leftPath.node.name;
break;
}
}
parentPath = parentPath.parentPath;
}
}
if (!variableName) {
return;
}
const state = {
variableName,
classDefinition,
};
classDefinition.parentPath.scope.path.traverse(explodedVisitors, state);
}

View File

@@ -0,0 +1,22 @@
type JsDocType = JsDocBaseType | JsDocElementsType;
interface JsDocBaseType {
name: string;
}
interface JsDocElementsType extends JsDocBaseType {
elements: JsDocType[];
}
interface JsDocProperty {
description: string | null;
type: JsDocType | null;
}
interface JsDocParam extends JsDocProperty {
name: string;
optional?: boolean;
}
interface JsDoc {
description: string | null;
params: JsDocParam[];
returns: JsDocProperty | null;
}
export default function parseJsDoc(docblock: string): JsDoc;
export {};

View File

@@ -0,0 +1,91 @@
import doctrine from 'doctrine';
function getType(tagType) {
if (!tagType) {
return null;
}
switch (tagType.type) {
case 'NameExpression':
// {a}
return { name: tagType.name };
case 'UnionType':
// {a|b}
return {
name: 'union',
elements: tagType.elements
.map((element) => getType(element))
.filter(Boolean),
};
case 'AllLiteral':
// {*}
return { name: 'mixed' };
case 'TypeApplication':
// {Array<string>} or {string[]}
return {
name: 'name' in tagType.expression ? tagType.expression.name : '',
elements: tagType.applications
.map((element) => getType(element))
.filter(Boolean),
};
case 'ArrayType':
// {[number, string]}
return {
name: 'tuple',
elements: tagType.elements
.map((element) => getType(element))
.filter(Boolean),
};
default: {
const typeName = 'name' in tagType && tagType.name
? tagType.name
: 'expression' in tagType &&
tagType.expression &&
'name' in tagType.expression
? tagType.expression.name
: null;
if (typeName) {
return { name: typeName };
}
else {
return null;
}
}
}
}
function getOptional(tag) {
return !!(tag.type && tag.type.type && tag.type.type === 'OptionalType');
}
// Add jsdoc @return description.
function getReturnsJsDoc(jsDoc) {
const returnTag = jsDoc.tags.find((tag) => tag.title === 'return' || tag.title === 'returns');
if (returnTag) {
return {
description: returnTag.description,
type: getType(returnTag.type),
};
}
return null;
}
// Add jsdoc @param descriptions.
function getParamsJsDoc(jsDoc) {
if (!jsDoc.tags) {
return [];
}
return jsDoc.tags
.filter((tag) => tag.title === 'param')
.map((tag) => {
return {
name: tag.name || '',
description: tag.description,
type: getType(tag.type),
optional: getOptional(tag),
};
});
}
export default function parseJsDoc(docblock) {
const jsDoc = doctrine.parse(docblock);
return {
description: jsDoc.description || null,
params: getParamsJsDoc(jsDoc),
returns: getReturnsJsDoc(jsDoc),
};
}

View File

@@ -0,0 +1,2 @@
import type { Documentation } from '../Documentation.js';
export default function (documentation: Documentation): Documentation;

View File

@@ -0,0 +1,12 @@
export default function (documentation) {
const props = documentation.props;
if (props) {
Object.values(props).forEach((propInfo) => {
// props with default values should not be required
if (propInfo.defaultValue) {
propInfo.required = false;
}
});
}
return documentation;
}

View File

@@ -0,0 +1,5 @@
import type { NodePath } from '@babel/traverse';
/**
* Prints the given path without leading or trailing comments.
*/
export default function printValue(path: NodePath): string;

View File

@@ -0,0 +1,18 @@
import strip from 'strip-indent';
function deindent(code) {
const firstNewLine = code.indexOf('\n');
return (code.slice(0, firstNewLine + 1) +
// remove indentation from all lines except first.
strip(code.slice(firstNewLine + 1)));
}
/**
* Prints the given path without leading or trailing comments.
*/
export default function printValue(path) {
let source = path.getSource();
// variable declarations and interface/type/class members might end with one of these
if (source.endsWith(',') || source.endsWith(';')) {
source = source.slice(0, -1);
}
return deindent(source);
}

View File

@@ -0,0 +1,3 @@
import type { ExportDefaultDeclaration, ExportNamedDeclaration } from '@babel/types';
import type { NodePath } from '@babel/traverse';
export default function resolveExportDeclaration(path: NodePath<ExportDefaultDeclaration | ExportNamedDeclaration>): NodePath[];

View File

@@ -0,0 +1,28 @@
import resolveToValue from './resolveToValue.js';
export default function resolveExportDeclaration(path) {
const definitions = [];
if (path.isExportDefaultDeclaration()) {
definitions.push(path.get('declaration'));
}
else if (path.isExportNamedDeclaration()) {
if (path.has('declaration')) {
const declaration = path.get('declaration');
if (declaration.isVariableDeclaration()) {
declaration
.get('declarations')
.forEach((declarator) => definitions.push(declarator));
}
else if (declaration.isDeclaration()) {
definitions.push(declaration);
}
}
else if (path.has('specifiers')) {
path.get('specifiers').forEach((specifier) => {
if (specifier.isExportSpecifier()) {
definitions.push(specifier.get('local'));
}
});
}
}
return definitions.map((definition) => resolveToValue(definition));
}

View File

@@ -0,0 +1,3 @@
import type { NodePath } from '@babel/traverse';
import type { Expression, Function as BabelFunction } from '@babel/types';
export default function resolveFunctionDefinitionToReturnValue(path: NodePath<BabelFunction>): NodePath<Expression> | null;

View File

@@ -0,0 +1,23 @@
import { visitors } from '@babel/traverse';
import resolveToValue from './resolveToValue.js';
import { ignore, shallowIgnoreVisitors } from './traverse.js';
const explodedVisitors = visitors.explode({
...shallowIgnoreVisitors,
Function: { enter: ignore },
ReturnStatement: {
enter: function (nodePath, state) {
const argument = nodePath.get('argument');
if (argument.hasNode()) {
state.returnPath = resolveToValue(argument);
return nodePath.stop();
}
nodePath.skip();
},
},
});
export default function resolveFunctionDefinitionToReturnValue(path) {
const body = path.get('body');
const state = {};
body.traverse(explodedVisitors, state);
return state.returnPath || null;
}

View File

@@ -0,0 +1,7 @@
import type { NodePath } from '@babel/traverse';
/**
* Given an React component (stateless or class) tries to find the
* flow or ts type for the props. If not found or not one of the supported
* component types returns undefined.
*/
export default function resolveGenericTypeAnnotation(path: NodePath): NodePath | undefined;

View File

@@ -0,0 +1,33 @@
import isUnreachableFlowType from '../utils/isUnreachableFlowType.js';
import resolveToValue from '../utils/resolveToValue.js';
import { unwrapUtilityType } from './flowUtilityTypes.js';
import getTypeIdentifier from './getTypeIdentifier.js';
function tryResolveGenericTypeAnnotation(path) {
let typePath = unwrapUtilityType(path);
const idPath = getTypeIdentifier(typePath);
if (idPath) {
typePath = resolveToValue(idPath);
if (isUnreachableFlowType(typePath)) {
return;
}
if (typePath.isTypeAlias()) {
return tryResolveGenericTypeAnnotation(typePath.get('right'));
}
else if (typePath.isTSTypeAliasDeclaration()) {
return tryResolveGenericTypeAnnotation(typePath.get('typeAnnotation'));
}
return typePath;
}
return typePath;
}
/**
* Given an React component (stateless or class) tries to find the
* flow or ts type for the props. If not found or not one of the supported
* component types returns undefined.
*/
export default function resolveGenericTypeAnnotation(path) {
const typePath = tryResolveGenericTypeAnnotation(path);
if (!typePath || typePath === path)
return;
return typePath;
}

View File

@@ -0,0 +1,8 @@
import type { NodePath } from '@babel/traverse';
/**
* If the path is a call expression, it recursively resolves to the
* rightmost argument, stopping if it finds a React.createClass call expression
*
* Else the path itself is returned.
*/
export default function resolveHOC(path: NodePath): NodePath;

View File

@@ -0,0 +1,32 @@
import isReactCreateClassCall from './isReactCreateClassCall.js';
import isReactForwardRefCall from './isReactForwardRefCall.js';
import resolveToValue from './resolveToValue.js';
/**
* If the path is a call expression, it recursively resolves to the
* rightmost argument, stopping if it finds a React.createClass call expression
*
* Else the path itself is returned.
*/
export default function resolveHOC(path) {
if (path.isCallExpression() &&
!isReactCreateClassCall(path) &&
!isReactForwardRefCall(path)) {
const node = path.node;
const argumentLength = node.arguments.length;
if (argumentLength && argumentLength > 0) {
const args = path.get('arguments');
const firstArg = args[0];
// If the first argument is one of these types then the component might be the last argument
// If there are all identifiers then we cannot figure out exactly and have to assume it is the first
if (argumentLength > 1 &&
(firstArg.isLiteral() ||
firstArg.isObjectExpression() ||
firstArg.isArrayExpression() ||
firstArg.isSpreadElement())) {
return resolveHOC(resolveToValue(args[argumentLength - 1]));
}
return resolveHOC(resolveToValue(firstArg));
}
}
return path;
}

View File

@@ -0,0 +1,12 @@
import type { NodePath } from '@babel/traverse';
export declare function resolveObjectToNameArray(objectPath: NodePath, raw?: boolean): string[] | null;
/**
* Returns an ArrayExpression which contains all the keys resolved from an object
*
* Ignores setters in objects
*
* Returns null in case of
* unresolvable spreads
* computed identifier keys
*/
export default function resolveObjectKeysToArray(path: NodePath): string[] | null;

View File

@@ -0,0 +1,110 @@
import resolveToValue from './resolveToValue.js';
function isObjectKeysCall(path) {
if (!path.isCallExpression() || path.get('arguments').length !== 1) {
return false;
}
const callee = path.get('callee');
if (!callee.isMemberExpression()) {
return false;
}
const object = callee.get('object');
const property = callee.get('property');
return (object.isIdentifier({ name: 'Object' }) &&
property.isIdentifier({ name: 'keys' }));
}
function isWhitelistedObjectProperty(path) {
if (path.isSpreadElement())
return true;
if (path.isObjectProperty() ||
(path.isObjectMethod() &&
(path.node.kind === 'get' || path.node.kind === 'set'))) {
const key = path.get('key');
return ((key.isIdentifier() && !path.node.computed) ||
key.isStringLiteral() ||
key.isNumericLiteral());
}
return false;
}
function isWhiteListedObjectTypeProperty(path) {
return (path.isObjectTypeProperty() ||
path.isObjectTypeSpreadProperty() ||
path.isTSPropertySignature());
}
// Resolves an ObjectExpression or an ObjectTypeAnnotation
export function resolveObjectToNameArray(objectPath, raw = false) {
if ((objectPath.isObjectExpression() &&
objectPath.get('properties').every(isWhitelistedObjectProperty)) ||
(objectPath.isObjectTypeAnnotation() &&
objectPath.get('properties').every(isWhiteListedObjectTypeProperty)) ||
(objectPath.isTSTypeLiteral() &&
objectPath.get('members').every(isWhiteListedObjectTypeProperty))) {
let values = [];
let error = false;
const properties = objectPath.isTSTypeLiteral()
? objectPath.get('members')
: objectPath.get('properties');
properties.forEach((propPath) => {
if (error)
return;
if (propPath.isObjectProperty() ||
propPath.isObjectMethod() ||
propPath.isObjectTypeProperty() ||
propPath.isTSPropertySignature()) {
const key = propPath.get('key');
// Key is either Identifier or Literal
const name = key.isIdentifier()
? key.node.name
: raw
? key.node.extra?.raw
: `${key.node.value}`;
values.push(name);
}
else if (propPath.isSpreadElement() ||
propPath.isObjectTypeSpreadProperty()) {
let spreadObject = resolveToValue(propPath.get('argument'));
if (spreadObject.isGenericTypeAnnotation()) {
const typeAliasRight = resolveToValue(spreadObject.get('id')).get('right');
if (typeAliasRight.isObjectTypeAnnotation()) {
spreadObject = resolveToValue(typeAliasRight);
}
}
const spreadValues = resolveObjectToNameArray(spreadObject);
if (!spreadValues) {
error = true;
return;
}
values = [...values, ...spreadValues];
}
});
if (!error) {
return values;
}
}
return null;
}
/**
* Returns an ArrayExpression which contains all the keys resolved from an object
*
* Ignores setters in objects
*
* Returns null in case of
* unresolvable spreads
* computed identifier keys
*/
export default function resolveObjectKeysToArray(path) {
if (isObjectKeysCall(path)) {
const argument = path.get('arguments')[0];
const objectExpression = resolveToValue(
// isObjectKeysCall already asserts that there is at least one argument, hence the non-null-assertion
argument);
const values = resolveObjectToNameArray(objectExpression);
if (values) {
const nodes = values
//filter duplicates
.filter((value, index, array) => array.indexOf(value) === index)
.map((value) => `"${value}"`);
return nodes;
}
}
return null;
}

View File

@@ -0,0 +1,7 @@
import type { NodePath } from '@babel/traverse';
import type { ObjectProperty } from '@babel/types';
/**
* Resolve and ObjectProperty inside an ObjectPattern to its value if possible
* If not found `null` is returned
*/
export default function resolveObjectPatternPropertyToValue(path: NodePath<ObjectProperty>): NodePath | null;

View File

@@ -0,0 +1,35 @@
import getPropertyValuePath from './getPropertyValuePath.js';
import resolveToValue from './resolveToValue.js';
function resolveToObjectExpression(path) {
if (path.isVariableDeclarator()) {
const init = path.get('init');
if (init.hasNode()) {
return resolveToValue(init);
}
}
else if (path.isAssignmentExpression()) {
if (path.node.operator === '=') {
return resolveToValue(path.get('right'));
}
}
return null;
}
/**
* Resolve and ObjectProperty inside an ObjectPattern to its value if possible
* If not found `null` is returned
*/
export default function resolveObjectPatternPropertyToValue(path) {
if (!path.parentPath.isObjectPattern()) {
return null;
}
const resolved = resolveToObjectExpression(path.parentPath.parentPath);
if (resolved && resolved.isObjectExpression()) {
const propertyPath = getPropertyValuePath(resolved,
// Always id in ObjectPattern
path.get('key').node.name);
if (propertyPath) {
return resolveToValue(propertyPath);
}
}
return null;
}

View File

@@ -0,0 +1,11 @@
import type { NodePath } from '@babel/traverse';
/**
* Returns an ArrayExpression which contains all the values resolved from an object
*
* Ignores setters in objects
*
* Returns null in case of
* unresolvable spreads
* computed identifier values
*/
export default function resolveObjectValuesToArray(path: NodePath): string[] | null;

View File

@@ -0,0 +1,86 @@
import resolveToValue from './resolveToValue.js';
function isObjectValuesCall(path) {
if (!path.isCallExpression() || path.node.arguments.length !== 1) {
return false;
}
const callee = path.get('callee');
if (!callee.isMemberExpression()) {
return false;
}
const object = callee.get('object');
const property = callee.get('property');
return (object.isIdentifier({ name: 'Object' }) &&
property.isIdentifier({ name: 'values' }));
}
// Resolves an ObjectExpression or an ObjectTypeAnnotation
function resolveObjectToPropMap(object) {
if (object.isObjectExpression()) {
const values = new Map();
let error = false;
object.get('properties').forEach((propPath) => {
if (error || propPath.isObjectMethod())
return;
if (propPath.isObjectProperty()) {
const key = propPath.get('key');
let name;
// Key is either Identifier or Literal
if (key.isIdentifier()) {
name = key.node.name;
}
else if (key.isNumericLiteral() || key.isStringLiteral()) {
name = `${key.node.value}`;
}
else {
error = true;
return;
}
const valuePath = resolveToValue(propPath.get('value'));
const value = valuePath.isStringLiteral()
? `"${valuePath.node.value}"`
: valuePath.isNumericLiteral()
? `${valuePath.node.value}`
: // we return null here because there are a lot of cases and we don't know yet what we need to handle
'null';
values.set(name, value);
}
else if (propPath.isSpreadElement()) {
const spreadObject = resolveToValue(propPath.get('argument'));
const spreadValues = resolveObjectToPropMap(spreadObject);
if (!spreadValues) {
error = true;
return;
}
for (const entry of spreadValues.entries()) {
const [key, value] = entry;
values.set(key, value);
}
}
});
if (!error) {
return values;
}
}
return null;
}
/**
* Returns an ArrayExpression which contains all the values resolved from an object
*
* Ignores setters in objects
*
* Returns null in case of
* unresolvable spreads
* computed identifier values
*/
export default function resolveObjectValuesToArray(path) {
if (isObjectValuesCall(path)) {
const argument = path.get('arguments')[0];
const objectExpression = resolveToValue(
// isObjectValuesCall already asserts that there is at least one argument, hence the non-null-assertion
argument);
const values = resolveObjectToPropMap(objectExpression);
if (values) {
return Array.from(values.values());
}
}
return null;
}

Some files were not shown because too many files have changed in this diff Show More