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

21
frontend/node_modules/react-docgen/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Facebook, Inc. and its affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,112 @@
export interface Documentation {
childContext?: Record<string, PropDescriptor>;
composes?: string[];
context?: Record<string, PropDescriptor>;
description?: string;
displayName?: string;
methods?: MethodDescriptor[];
props?: Record<string, PropDescriptor>;
}
export interface MethodParameter {
name: string;
description?: string;
optional: boolean;
type?: TypeDescriptor<FunctionSignatureType> | null;
}
export interface MethodReturn {
description?: string;
type: TypeDescriptor<FunctionSignatureType> | undefined;
}
export type MethodModifier = 'async' | 'generator' | 'get' | 'set' | 'static';
export interface MethodDescriptor {
name: string;
description?: string | null;
docblock: string | null;
modifiers: MethodModifier[];
params: MethodParameter[];
returns: MethodReturn | null;
}
export interface PropTypeDescriptor {
name: 'any' | 'array' | 'arrayOf' | 'bool' | 'custom' | 'element' | 'elementType' | 'enum' | 'exact' | 'func' | 'instanceOf' | 'node' | 'number' | 'object' | 'objectOf' | 'shape' | 'string' | 'symbol' | 'union';
value?: unknown;
raw?: string;
computed?: boolean;
description?: string;
required?: boolean;
}
export interface DefaultValueDescriptor {
value: unknown;
computed: boolean;
}
export interface BaseType {
required?: boolean;
nullable?: boolean;
alias?: string;
}
export interface SimpleType extends BaseType {
name: string;
raw?: string;
}
export interface LiteralType extends BaseType {
name: 'literal';
value: string;
}
export interface ElementsType<T = FunctionSignatureType> extends BaseType {
name: string;
raw: string;
elements: Array<TypeDescriptor<T>>;
}
export interface FunctionArgumentType<T> {
name: string;
type?: TypeDescriptor<T>;
rest?: boolean;
}
export interface FunctionSignatureType extends BaseType {
name: 'signature';
type: 'function';
raw: string;
signature: {
arguments: Array<FunctionArgumentType<FunctionSignatureType>>;
return?: TypeDescriptor<FunctionSignatureType>;
};
}
export interface TSFunctionSignatureType extends FunctionSignatureType {
signature: {
arguments: Array<FunctionArgumentType<TSFunctionSignatureType>>;
return?: TypeDescriptor<TSFunctionSignatureType>;
this?: TypeDescriptor<TSFunctionSignatureType>;
};
}
export interface ObjectSignatureType<T = FunctionSignatureType> extends BaseType {
name: 'signature';
type: 'object';
raw: string;
signature: {
properties: Array<{
key: TypeDescriptor<T> | string;
value: TypeDescriptor<T>;
description?: string;
}>;
constructor?: TypeDescriptor<T>;
};
}
export type TypeDescriptor<T = FunctionSignatureType> = ElementsType<T> | LiteralType | ObjectSignatureType<T> | SimpleType | T;
export interface PropDescriptor {
type?: PropTypeDescriptor;
flowType?: TypeDescriptor<FunctionSignatureType>;
tsType?: TypeDescriptor<TSFunctionSignatureType>;
required?: boolean;
defaultValue?: DefaultValueDescriptor;
description?: string;
}
export default class DocumentationBuilder {
#private;
constructor();
addComposes(moduleName: string): void;
set(key: string, value: unknown): void;
get<T>(key: string): T | null;
getPropDescriptor(propName: string): PropDescriptor;
getContextDescriptor(propName: string): PropDescriptor;
getChildContextDescriptor(propName: string): PropDescriptor;
build(): Documentation;
}

View File

@@ -0,0 +1,91 @@
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _DocumentationBuilder_props, _DocumentationBuilder_context, _DocumentationBuilder_childContext, _DocumentationBuilder_composes, _DocumentationBuilder_data;
class DocumentationBuilder {
constructor() {
_DocumentationBuilder_props.set(this, void 0);
_DocumentationBuilder_context.set(this, void 0);
_DocumentationBuilder_childContext.set(this, void 0);
_DocumentationBuilder_composes.set(this, void 0);
_DocumentationBuilder_data.set(this, void 0);
__classPrivateFieldSet(this, _DocumentationBuilder_props, new Map(), "f");
__classPrivateFieldSet(this, _DocumentationBuilder_context, new Map(), "f");
__classPrivateFieldSet(this, _DocumentationBuilder_childContext, new Map(), "f");
__classPrivateFieldSet(this, _DocumentationBuilder_composes, new Set(), "f");
__classPrivateFieldSet(this, _DocumentationBuilder_data, new Map(), "f");
}
addComposes(moduleName) {
__classPrivateFieldGet(this, _DocumentationBuilder_composes, "f").add(moduleName);
}
set(key, value) {
__classPrivateFieldGet(this, _DocumentationBuilder_data, "f").set(key, value);
}
get(key) {
return __classPrivateFieldGet(this, _DocumentationBuilder_data, "f").get(key);
}
getPropDescriptor(propName) {
let propDescriptor = __classPrivateFieldGet(this, _DocumentationBuilder_props, "f").get(propName);
if (!propDescriptor) {
__classPrivateFieldGet(this, _DocumentationBuilder_props, "f").set(propName, (propDescriptor = {}));
}
return propDescriptor;
}
getContextDescriptor(propName) {
let propDescriptor = __classPrivateFieldGet(this, _DocumentationBuilder_context, "f").get(propName);
if (!propDescriptor) {
__classPrivateFieldGet(this, _DocumentationBuilder_context, "f").set(propName, (propDescriptor = {}));
}
return propDescriptor;
}
getChildContextDescriptor(propName) {
let propDescriptor = __classPrivateFieldGet(this, _DocumentationBuilder_childContext, "f").get(propName);
if (!propDescriptor) {
__classPrivateFieldGet(this, _DocumentationBuilder_childContext, "f").set(propName, (propDescriptor = {}));
}
return propDescriptor;
}
build() {
const obj = {};
for (const [key, value] of __classPrivateFieldGet(this, _DocumentationBuilder_data, "f")) {
// @ts-expect-error custom handlers can add any properties to Documentation
obj[key] = value;
}
if (__classPrivateFieldGet(this, _DocumentationBuilder_props, "f").size > 0) {
obj.props = {};
for (const [propName, propDescriptor] of __classPrivateFieldGet(this, _DocumentationBuilder_props, "f")) {
if (Object.keys(propDescriptor).length > 0) {
obj.props[propName] = propDescriptor;
}
}
}
if (__classPrivateFieldGet(this, _DocumentationBuilder_context, "f").size > 0) {
obj.context = {};
for (const [contextName, contextDescriptor] of __classPrivateFieldGet(this, _DocumentationBuilder_context, "f")) {
if (Object.keys(contextDescriptor).length > 0) {
obj.context[contextName] = contextDescriptor;
}
}
}
if (__classPrivateFieldGet(this, _DocumentationBuilder_childContext, "f").size > 0) {
obj.childContext = {};
for (const [childContextName, childContextDescriptor] of __classPrivateFieldGet(this, _DocumentationBuilder_childContext, "f")) {
obj.childContext[childContextName] = childContextDescriptor;
}
}
if (__classPrivateFieldGet(this, _DocumentationBuilder_composes, "f").size > 0) {
obj.composes = Array.from(__classPrivateFieldGet(this, _DocumentationBuilder_composes, "f"));
}
return obj;
}
}
_DocumentationBuilder_props = new WeakMap(), _DocumentationBuilder_context = new WeakMap(), _DocumentationBuilder_childContext = new WeakMap(), _DocumentationBuilder_composes = new WeakMap(), _DocumentationBuilder_data = new WeakMap();
export default DocumentationBuilder;

31
frontend/node_modules/react-docgen/dist/FileState.d.ts generated vendored Normal file
View File

@@ -0,0 +1,31 @@
import type { HubInterface, Scope, Visitor } from '@babel/traverse';
import { NodePath } from '@babel/traverse';
import type { File, Program } from '@babel/types';
import type { Importer, ImportPath } from './importer/index.js';
import type { TransformOptions } from '@babel/core';
export default class FileState {
#private;
opts: TransformOptions;
path: NodePath<Program>;
ast: File;
scope: Scope;
code: string;
hub: HubInterface;
constructor(options: TransformOptions, { code, ast, importer }: {
code: string;
ast: File;
importer: Importer;
});
/**
* Try to resolve and import the ImportPath with the `name`
*/
import(path: ImportPath, name: string): NodePath | null;
/**
* Parse the content of a new file
* The `filename` is required so that potential imports inside the content can be correctly resolved and
* the correct babel config file could be loaded. `filename` needs to be an absolute path.
*/
parse(code: string, filename: string): FileState;
traverse<S>(visitors: Visitor<S>, state?: S): void;
traverse(visitors: Visitor): void;
}

77
frontend/node_modules/react-docgen/dist/FileState.js generated vendored Normal file
View File

@@ -0,0 +1,77 @@
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _FileState_importer;
import babelTraverse, { NodePath } from '@babel/traverse';
import babelParse from './babelParser.js';
// Workaround while babel is not a proper ES module
const traverse = babelTraverse.default ?? babelTraverse;
class FileState {
constructor(options, { code, ast, importer }) {
_FileState_importer.set(this, void 0);
this.hub = {
// keep it for the usage in babel-core, ex: path.hub.file.opts.filename
file: this,
parse: this.parse.bind(this),
import: this.import.bind(this),
getCode: () => this.code,
getScope: () => this.scope,
addHelper: () => undefined,
buildError: (node, msg, Error) => {
const err = new Error(msg);
err.node = node;
return err;
},
};
this.opts = options;
this.code = code;
this.ast = ast;
__classPrivateFieldSet(this, _FileState_importer, importer, "f");
this.path = NodePath.get({
hub: this.hub,
parentPath: null,
parent: this.ast,
container: this.ast,
key: 'program',
}).setContext();
this.scope = this.path.scope;
}
/**
* Try to resolve and import the ImportPath with the `name`
*/
import(path, name) {
return __classPrivateFieldGet(this, _FileState_importer, "f").call(this, path, name, this);
}
/**
* Parse the content of a new file
* The `filename` is required so that potential imports inside the content can be correctly resolved and
* the correct babel config file could be loaded. `filename` needs to be an absolute path.
*/
parse(code, filename) {
const newOptions = { ...this.opts, filename };
// We need to build a new parser, because there might be a new
// babel config file in effect, so we need to load it
const ast = babelParse(code, newOptions);
return new FileState(newOptions, {
ast,
code,
importer: __classPrivateFieldGet(this, _FileState_importer, "f"),
});
}
/**
* Traverse the current file
*/
traverse(visitors, state) {
traverse(this.ast, visitors, this.scope, state);
}
}
_FileState_importer = new WeakMap();
export default FileState;

View File

@@ -0,0 +1,3 @@
import type { TransformOptions } from '@babel/core';
import type { File } from '@babel/types';
export default function babelParser(src: string, options?: TransformOptions): File;

64
frontend/node_modules/react-docgen/dist/babelParser.js generated vendored Normal file
View File

@@ -0,0 +1,64 @@
import { loadPartialConfig, parseSync } from '@babel/core';
import { extname } from 'path';
const TYPESCRIPT_EXTS = new Set(['.cts', '.mts', '.ts', '.tsx']);
function getDefaultPlugins(options) {
return [
'jsx',
options.filename && TYPESCRIPT_EXTS.has(extname(options.filename))
? 'typescript'
: 'flow',
'asyncDoExpressions',
'decimal',
['decorators', { decoratorsBeforeExport: false }],
'decoratorAutoAccessors',
'destructuringPrivate',
'doExpressions',
'exportDefaultFrom',
'functionBind',
'importAssertions',
'moduleBlocks',
'partialApplication',
['pipelineOperator', { proposal: 'minimal' }],
['recordAndTuple', { syntaxType: 'bar' }],
'regexpUnicodeSets',
'throwExpressions',
];
}
function buildPluginList(options) {
let plugins = [];
if (options.parserOpts?.plugins) {
plugins = [...options.parserOpts.plugins];
}
// Let's check if babel finds a config file for this source file
// If babel does find a config file we do not apply our defaults
const partialConfig = loadPartialConfig(options);
if (plugins.length === 0 &&
partialConfig &&
!partialConfig.hasFilesystemConfig()) {
plugins = getDefaultPlugins(options);
}
// Ensure that the estree plugin is never active
// TODO add test
return plugins.filter((plugin) => plugin !== 'estree');
}
function buildParserOptions(options) {
const plugins = buildPluginList(options);
return {
sourceType: 'unambiguous',
...(options.parserOpts || {}),
plugins,
tokens: false,
};
}
export default function babelParser(src, options = {}) {
const parserOpts = buildParserOptions(options);
const opts = {
...options,
parserOpts,
};
const ast = parseSync(src, opts);
if (!ast) {
throw new Error('Unable to parse source code.');
}
return ast;
}

19
frontend/node_modules/react-docgen/dist/config.d.ts generated vendored Normal file
View File

@@ -0,0 +1,19 @@
import type { TransformOptions } from '@babel/core';
import type { Handler } from './handlers/index.js';
import type { Importer } from './importer/index.js';
import type { Resolver } from './resolver/index.js';
export interface Config {
handlers?: Handler[];
importer?: Importer;
resolver?: Resolver;
/**
* shortcut for `babelOptions.filename`
* Set to an absolute path (recommended) to the file currently being parsed or
* to an relative path that is relative to the `babelOptions.cwd`.
*/
filename?: string;
babelOptions?: TransformOptions;
}
export type InternalConfig = Omit<Required<Config>, 'filename'>;
export declare const defaultHandlers: Handler[];
export declare function createConfig(inputConfig: Config): InternalConfig;

39
frontend/node_modules/react-docgen/dist/config.js generated vendored Normal file
View File

@@ -0,0 +1,39 @@
import { childContextTypeHandler, codeTypeHandler, componentDocblockHandler, componentMethodsHandler, componentMethodsJsDocHandler, contextTypeHandler, defaultPropsHandler, displayNameHandler, propDocblockHandler, propTypeCompositionHandler, propTypeHandler, } from './handlers/index.js';
import { fsImporter } from './importer/index.js';
import { ChainResolver, FindAnnotatedDefinitionsResolver, FindExportedDefinitionsResolver, } from './resolver/index.js';
const defaultResolvers = [
new FindExportedDefinitionsResolver({
limit: 1,
}),
new FindAnnotatedDefinitionsResolver(),
];
const defaultResolver = new ChainResolver(defaultResolvers, {
chainingLogic: ChainResolver.Logic.ALL,
});
const defaultImporter = fsImporter;
export const defaultHandlers = [
propTypeHandler,
contextTypeHandler,
childContextTypeHandler,
propTypeCompositionHandler,
propDocblockHandler,
codeTypeHandler,
defaultPropsHandler,
componentDocblockHandler,
displayNameHandler,
componentMethodsHandler,
componentMethodsJsDocHandler,
];
export function createConfig(inputConfig) {
const { babelOptions, filename, handlers, importer, resolver } = inputConfig;
const config = {
babelOptions: { ...babelOptions },
handlers: handlers ?? defaultHandlers,
importer: importer ?? defaultImporter,
resolver: resolver ?? defaultResolver,
};
if (filename) {
config.babelOptions.filename = filename;
}
return config;
}

8
frontend/node_modules/react-docgen/dist/error.d.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
export declare enum ERROR_CODES {
MISSING_DEFINITION = "ERR_REACTDOCGEN_MISSING_DEFINITION",
MULTIPLE_DEFINITIONS = "ERR_REACTDOCGEN_MULTIPLE_DEFINITIONS"
}
export declare class ReactDocgenError extends Error {
code: string | undefined;
constructor(code: ERROR_CODES);
}

18
frontend/node_modules/react-docgen/dist/error.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
export var ERROR_CODES;
(function (ERROR_CODES) {
ERROR_CODES["MISSING_DEFINITION"] = "ERR_REACTDOCGEN_MISSING_DEFINITION";
ERROR_CODES["MULTIPLE_DEFINITIONS"] = "ERR_REACTDOCGEN_MULTIPLE_DEFINITIONS";
})(ERROR_CODES || (ERROR_CODES = {}));
const messages = new Map([
[ERROR_CODES.MISSING_DEFINITION, 'No suitable component definition found.'],
[
ERROR_CODES.MULTIPLE_DEFINITIONS,
'Multiple exported component definitions found.',
],
]);
export class ReactDocgenError extends Error {
constructor(code) {
super(messages.get(code));
this.code = code;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
declare const defaultFsImporter: import("./index.js").Importer;
export default defaultFsImporter;

View File

@@ -0,0 +1,3 @@
import makeFsImporter from './makeFsImporter.js';
const defaultFsImporter = makeFsImporter();
export default defaultFsImporter;

View File

@@ -0,0 +1,2 @@
declare const ignoreImporter: import("./index.js").Importer;
export default ignoreImporter;

View File

@@ -0,0 +1,3 @@
import makeIgnoreImporter from './makeIgnoreImporter.js';
const ignoreImporter = makeIgnoreImporter();
export default ignoreImporter;

View File

@@ -0,0 +1,9 @@
import type { NodePath } from '@babel/traverse';
import type { ExportAllDeclaration, ExportNamedDeclaration, ImportDeclaration } from '@babel/types';
import type FileState from '../FileState.js';
import ignoreImporter from './ignoreImporter.js';
import fsImporter from './fsImporter.js';
import makeFsImporter from './makeFsImporter.js';
export type ImportPath = NodePath<ExportAllDeclaration | ExportNamedDeclaration | ImportDeclaration>;
export type Importer = (path: ImportPath, name: string, file: FileState) => NodePath | null;
export { fsImporter, ignoreImporter, makeFsImporter };

View File

@@ -0,0 +1,4 @@
import ignoreImporter from './ignoreImporter.js';
import fsImporter from './fsImporter.js';
import makeFsImporter from './makeFsImporter.js';
export { fsImporter, ignoreImporter, makeFsImporter };

View File

@@ -0,0 +1,8 @@
import type { Importer } from './index.js';
import type FileState from '../FileState.js';
interface FsImporterCache {
parseCache: Map<string, FileState>;
resolveCache: Map<string, string | null>;
}
export default function makeFsImporter(lookupModule?: (filename: string, basedir: string) => string, { parseCache, resolveCache }?: FsImporterCache): Importer;
export {};

View File

@@ -0,0 +1,208 @@
import { shallowIgnoreVisitors } from '../utils/traverse.js';
import resolve from 'resolve';
import { dirname, extname } from 'path';
import fs from 'fs';
import { visitors } from '@babel/traverse';
import { resolveObjectPatternPropertyToValue } from '../utils/index.js';
// These extensions are sorted by priority
// resolve() will check for files in the order these extensions are sorted
const RESOLVE_EXTENSIONS = [
'.js',
'.ts',
'.tsx',
'.mjs',
'.cjs',
'.mts',
'.cts',
'.jsx',
];
function defaultLookupModule(filename, basedir) {
const resolveOptions = {
basedir,
extensions: RESOLVE_EXTENSIONS,
// we do not need to check core modules as we cannot import them anyway
includeCoreModules: false,
};
try {
return resolve.sync(filename, resolveOptions);
}
catch (error) {
const ext = extname(filename);
let newFilename;
// if we try to import a JavaScript file it might be that we are actually pointing to
// a TypeScript file. This can happen in ES modules as TypeScript requires to import other
// TypeScript files with .js extensions
// https://www.typescriptlang.org/docs/handbook/esm-node.html#type-in-packagejson-and-new-extensions
switch (ext) {
case '.js':
case '.mjs':
case '.cjs':
newFilename = `${filename.slice(0, -2)}ts`;
break;
case '.jsx':
newFilename = `${filename.slice(0, -3)}tsx`;
break;
default:
throw error;
}
return resolve.sync(newFilename, {
...resolveOptions,
// we already know that there is an extension at this point, so no need to check other extensions
extensions: [],
});
}
}
// Factory for the resolveImports importer
// If this resolver is used in an environment where the source files change (e.g. watch)
// then the cache needs to be cleared on file changes.
export default function makeFsImporter(lookupModule = defaultLookupModule, { parseCache, resolveCache } = {
parseCache: new Map(),
resolveCache: new Map(),
}) {
function resolveImportedValue(path, name, file, seen = new Set()) {
// Bail if no filename was provided for the current source file.
// Also never traverse into react itself.
const source = path.node.source?.value;
const { filename } = file.opts;
if (!source || !filename || source === 'react') {
return null;
}
// Resolve the imported module using the Node resolver
const basedir = dirname(filename);
const resolveCacheKey = `${basedir}|${source}`;
let resolvedSource = resolveCache.get(resolveCacheKey);
// We haven't found it before, so no need to look again
if (resolvedSource === null) {
return null;
}
// First time we try to resolve this file
if (resolvedSource === undefined) {
try {
resolvedSource = lookupModule(source, basedir);
}
catch (error) {
const { code } = error;
if (code === 'MODULE_NOT_FOUND' || code === 'INVALID_PACKAGE_MAIN') {
resolveCache.set(resolveCacheKey, null);
return null;
}
throw error;
}
resolveCache.set(resolveCacheKey, resolvedSource);
}
// Prevent recursive imports
if (seen.has(resolvedSource)) {
return null;
}
seen.add(resolvedSource);
let nextFile = parseCache.get(resolvedSource);
if (!nextFile) {
// Read and parse the code
const src = fs.readFileSync(resolvedSource, 'utf8');
nextFile = file.parse(src, resolvedSource);
parseCache.set(resolvedSource, nextFile);
}
return findExportedValue(nextFile, name, seen);
}
const explodedVisitors = visitors.explode({
...shallowIgnoreVisitors,
ExportNamedDeclaration: {
enter: function (path, state) {
const { file, name, seen } = state;
const declaration = path.get('declaration');
// export const/var ...
if (declaration.hasNode() && declaration.isVariableDeclaration()) {
for (const declPath of declaration.get('declarations')) {
const id = declPath.get('id');
const init = declPath.get('init');
if (id.isIdentifier({ name }) && init.hasNode()) {
// export const/var a = <init>
state.resultPath = init;
break;
}
else if (id.isObjectPattern()) {
// export const/var { a } = <init>
state.resultPath = id.get('properties').find((prop) => {
if (prop.isObjectProperty()) {
const value = prop.get('value');
return value.isIdentifier({ name });
}
// We don't handle RestElement here yet as complicated
return false;
});
if (state.resultPath) {
state.resultPath = resolveObjectPatternPropertyToValue(state.resultPath);
break;
}
}
// ArrayPattern not handled yet
}
}
else if (declaration.hasNode() &&
declaration.has('id') &&
declaration.get('id').isIdentifier({ name })) {
// export function/class/type/interface/enum ...
state.resultPath = declaration;
}
else if (path.has('specifiers')) {
// export { ... } or export x from ... or export * as x from ...
for (const specifierPath of path.get('specifiers')) {
if (specifierPath.isExportNamespaceSpecifier()) {
continue;
}
const exported = specifierPath.get('exported');
if (exported.isIdentifier({ name })) {
// export ... from ''
if (path.has('source')) {
const local = specifierPath.isExportSpecifier()
? specifierPath.node.local.name
: 'default';
state.resultPath = resolveImportedValue(path, local, file, seen);
if (state.resultPath) {
break;
}
}
else {
state.resultPath = specifierPath.get('local');
break;
}
}
}
}
return state.resultPath ? path.stop() : path.skip();
},
},
ExportDefaultDeclaration: {
enter: function (path, state) {
const { name } = state;
if (name === 'default') {
state.resultPath = path.get('declaration');
return path.stop();
}
path.skip();
},
},
ExportAllDeclaration: {
enter: function (path, state) {
const { name, file, seen } = state;
const resolvedPath = resolveImportedValue(path, name, file, seen);
if (resolvedPath) {
state.resultPath = resolvedPath;
return path.stop();
}
path.skip();
},
},
});
// Traverses the program looking for an export that matches the requested name
function findExportedValue(file, name, seen) {
const state = {
file,
name,
seen,
};
file.traverse(explodedVisitors, state);
return state.resultPath || null;
}
return resolveImportedValue;
}

View File

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

View File

@@ -0,0 +1,3 @@
export default function makeIgnoreImporter() {
return () => null;
}

46
frontend/node_modules/react-docgen/dist/main.d.ts generated vendored Normal file
View File

@@ -0,0 +1,46 @@
import type DocumentationBuilder from './Documentation.js';
import type { BaseType, Documentation, ElementsType, FunctionArgumentType, FunctionSignatureType, LiteralType, MethodParameter, MethodReturn, ObjectSignatureType, PropDescriptor, PropTypeDescriptor, SimpleType, TSFunctionSignatureType, TypeDescriptor } from './Documentation.js';
import type FileState from './FileState.js';
import type { Config } from './config.js';
import { defaultHandlers } from './config.js';
import { ERROR_CODES } from './error.js';
import type { Handler } from './handlers/index.js';
import * as builtinHandlers from './handlers/index.js';
import type { Importer } from './importer/index.js';
import { makeFsImporter } from './importer/index.js';
import type { Resolver, ResolverClass, ResolverFunction } from './resolver/index.js';
import * as builtinResolvers from './resolver/index.js';
import * as utils from './utils/index.js';
declare const builtinImporters: {
fsImporter: Importer;
ignoreImporter: Importer;
};
declare module '@babel/traverse' {
interface HubInterface {
file: FileState;
parse: typeof FileState.prototype.parse;
import: typeof FileState.prototype.import;
}
interface Hub {
file: FileState;
parse: typeof FileState.prototype.parse;
import: typeof FileState.prototype.import;
}
}
/**
* Parse the *src* and scan for react components based on the config
* that gets supplied.
*
* The default resolvers look for *exported* react components.
*
* By default all handlers are applied, so that all possible
* different use cases are covered.
*
* The default importer is the fs-importer that tries to resolve
* files based on the nodejs resolve algorithm.
*/
declare function defaultParse(src: Buffer | string, config?: Config): Documentation[];
export type { NodePath } from '@babel/traverse';
export type * as babelTypes from '@babel/types';
export { builtinHandlers, builtinImporters, builtinResolvers, defaultHandlers, ERROR_CODES, makeFsImporter, defaultParse as parse, utils, };
export type { BaseType, Config, Documentation, DocumentationBuilder, ElementsType, FileState, FunctionArgumentType, FunctionSignatureType, Handler, Importer, LiteralType, MethodParameter, MethodReturn, ObjectSignatureType, PropDescriptor, PropTypeDescriptor, Resolver, ResolverClass, ResolverFunction, SimpleType, TSFunctionSignatureType, TypeDescriptor, };

28
frontend/node_modules/react-docgen/dist/main.js generated vendored Normal file
View File

@@ -0,0 +1,28 @@
import { createConfig, defaultHandlers } from './config.js';
import { ERROR_CODES } from './error.js';
import * as builtinHandlers from './handlers/index.js';
import { fsImporter, ignoreImporter, makeFsImporter, } from './importer/index.js';
import parse from './parse.js';
import * as builtinResolvers from './resolver/index.js';
import * as utils from './utils/index.js';
const builtinImporters = {
fsImporter,
ignoreImporter,
};
/**
* Parse the *src* and scan for react components based on the config
* that gets supplied.
*
* The default resolvers look for *exported* react components.
*
* By default all handlers are applied, so that all possible
* different use cases are covered.
*
* The default importer is the fs-importer that tries to resolve
* files based on the nodejs resolve algorithm.
*/
function defaultParse(src, config = {}) {
const defaultConfig = createConfig(config);
return parse(String(src), defaultConfig);
}
export { builtinHandlers, builtinImporters, builtinResolvers, defaultHandlers, ERROR_CODES, makeFsImporter, defaultParse as parse, utils, };

20
frontend/node_modules/react-docgen/dist/parse.d.ts generated vendored Normal file
View File

@@ -0,0 +1,20 @@
import type { Documentation } from './Documentation.js';
import type { InternalConfig } from './config.js';
/**
* Takes JavaScript source code and returns an object with the information
* extract from it.
*
* `resolver` is a strategy to find the AST node(s) of the component
* definition(s) inside `src`.
* It is a function that gets passed the program AST node of
* the source as first argument, and a reference to the parser as second argument.
*
* This allows you define your own strategy for finding component definitions.
*
* `handlers` is an array of functions which are passed a reference to the
* component definitions (extracted by `resolver`) so that they can extract
* information from it. They get also passed a reference to a `Documentation`
* object to attach the information to. A reference to the parser is parsed as the
* last argument.
*/
export default function parse(code: string, config: InternalConfig): Documentation[];

44
frontend/node_modules/react-docgen/dist/parse.js generated vendored Normal file
View File

@@ -0,0 +1,44 @@
import DocumentationBuilder from './Documentation.js';
import postProcessDocumentation from './utils/postProcessDocumentation.js';
import babelParse from './babelParser.js';
import FileState from './FileState.js';
import { ERROR_CODES, ReactDocgenError } from './error.js';
import runResolver from './resolver/utils/runResolver.js';
function executeHandlers(handlers, componentDefinitions) {
return componentDefinitions.map((componentDefinition) => {
const documentation = new DocumentationBuilder();
handlers.forEach((handler) => handler(documentation, componentDefinition));
return postProcessDocumentation(documentation.build());
});
}
/**
* Takes JavaScript source code and returns an object with the information
* extract from it.
*
* `resolver` is a strategy to find the AST node(s) of the component
* definition(s) inside `src`.
* It is a function that gets passed the program AST node of
* the source as first argument, and a reference to the parser as second argument.
*
* This allows you define your own strategy for finding component definitions.
*
* `handlers` is an array of functions which are passed a reference to the
* component definitions (extracted by `resolver`) so that they can extract
* information from it. They get also passed a reference to a `Documentation`
* object to attach the information to. A reference to the parser is parsed as the
* last argument.
*/
export default function parse(code, config) {
const { babelOptions, handlers, importer, resolver } = config;
const ast = babelParse(code, babelOptions);
const fileState = new FileState(babelOptions, {
ast,
code,
importer,
});
const componentDefinitions = runResolver(resolver, fileState);
if (componentDefinitions.length === 0) {
throw new ReactDocgenError(ERROR_CODES.MISSING_DEFINITION);
}
return executeHandlers(handlers, componentDefinitions);
}

View File

@@ -0,0 +1,19 @@
import type FileState from '../FileState.js';
import type { ComponentNodePath, Resolver, ResolverClass } from './index.js';
declare enum ChainingLogic {
ALL = 0,
FIRST_FOUND = 1
}
interface ChainResolverOptions {
chainingLogic?: ChainingLogic;
}
export default class ChainResolver implements ResolverClass {
resolvers: Resolver[];
options: ChainResolverOptions;
static Logic: typeof ChainingLogic;
constructor(resolvers: Resolver[], options: ChainResolverOptions);
private resolveFirstOnly;
private resolveAll;
resolve(file: FileState): ComponentNodePath[];
}
export {};

View File

@@ -0,0 +1,39 @@
import runResolver from './utils/runResolver.js';
var ChainingLogic;
(function (ChainingLogic) {
ChainingLogic[ChainingLogic["ALL"] = 0] = "ALL";
ChainingLogic[ChainingLogic["FIRST_FOUND"] = 1] = "FIRST_FOUND";
})(ChainingLogic || (ChainingLogic = {}));
class ChainResolver {
constructor(resolvers, options) {
this.resolvers = resolvers;
this.options = options;
}
resolveFirstOnly(file) {
for (const resolver of this.resolvers) {
const components = runResolver(resolver, file);
if (components.length > 0) {
return components;
}
}
return [];
}
resolveAll(file) {
const allComponents = new Set();
for (const resolver of this.resolvers) {
const components = runResolver(resolver, file);
components.forEach((component) => {
allComponents.add(component);
});
}
return Array.from(allComponents);
}
resolve(file) {
if (this.options.chainingLogic === ChainingLogic.FIRST_FOUND) {
return this.resolveFirstOnly(file);
}
return this.resolveAll(file);
}
}
ChainResolver.Logic = ChainingLogic;
export default ChainResolver;

View File

@@ -0,0 +1,9 @@
import type FileState from '../FileState.js';
import type { ComponentNodePath, ResolverClass } from './index.js';
/**
* Given an AST, this function tries to find all object expressions that are
* passed to `React.createClass` calls, by resolving all references properly.
*/
export default class FindAllDefinitionsResolver implements ResolverClass {
resolve(file: FileState): ComponentNodePath[];
}

View File

@@ -0,0 +1,66 @@
import isReactComponentClass from '../utils/isReactComponentClass.js';
import isReactCreateClassCall from '../utils/isReactCreateClassCall.js';
import isReactForwardRefCall from '../utils/isReactForwardRefCall.js';
import isStatelessComponent from '../utils/isStatelessComponent.js';
import normalizeClassDefinition from '../utils/normalizeClassDefinition.js';
import resolveToValue from '../utils/resolveToValue.js';
import { visitors } from '@babel/traverse';
function classVisitor(path, state) {
if (isReactComponentClass(path)) {
normalizeClassDefinition(path);
state.foundDefinitions.add(path);
}
path.skip();
}
function statelessVisitor(path, state) {
if (isStatelessComponent(path)) {
state.foundDefinitions.add(path);
}
path.skip();
}
const explodedVisitors = visitors.explode({
FunctionDeclaration: { enter: statelessVisitor },
FunctionExpression: { enter: statelessVisitor },
ObjectMethod: { enter: statelessVisitor },
ArrowFunctionExpression: { enter: statelessVisitor },
ClassExpression: { enter: classVisitor },
ClassDeclaration: { enter: classVisitor },
CallExpression: {
enter: function (path, state) {
const argument = path.get('arguments')[0];
if (!argument) {
return;
}
if (isReactForwardRefCall(path)) {
// If the the inner function was previously identified as a component
// replace it with the parent node
const inner = resolveToValue(argument);
state.foundDefinitions.delete(inner);
state.foundDefinitions.add(path);
// Do not traverse into arguments
return path.skip();
}
else if (isReactCreateClassCall(path)) {
const resolvedPath = resolveToValue(argument);
if (resolvedPath.isObjectExpression()) {
state.foundDefinitions.add(resolvedPath);
}
// Do not traverse into arguments
return path.skip();
}
},
},
});
/**
* Given an AST, this function tries to find all object expressions that are
* passed to `React.createClass` calls, by resolving all references properly.
*/
export default class FindAllDefinitionsResolver {
resolve(file) {
const state = {
foundDefinitions: new Set(),
};
file.traverse(explodedVisitors, state);
return Array.from(state.foundDefinitions);
}
}

View File

@@ -0,0 +1,15 @@
import type FileState from '../FileState.js';
import type { ComponentNodePath, ResolverClass } from './index.js';
interface FindAnnotatedDefinitionsResolverOptions {
annotation?: string;
}
/**
* Given an AST, this function tries to find all react components which
* are annotated with an annotation
*/
export default class FindAnnotatedDefinitionsResolver implements ResolverClass {
annotation: string;
constructor({ annotation, }?: FindAnnotatedDefinitionsResolverOptions);
resolve(file: FileState): ComponentNodePath[];
}
export {};

View File

@@ -0,0 +1,62 @@
import normalizeClassDefinition from '../utils/normalizeClassDefinition.js';
import { visitors } from '@babel/traverse';
function isAnnotated(path, annotation) {
let inspectPath = path;
do {
const leadingComments = inspectPath.node.leadingComments;
// If an export doesn't have leading comments, we can simply continue
if (leadingComments && leadingComments.length > 0) {
// Search for the annotation in any comment.
const hasAnnotation = leadingComments.some(({ value }) => value.includes(annotation));
// if we found an annotation return true
if (hasAnnotation) {
return true;
}
}
// return false if the container of the current path is an array
// as we do not want to traverse up through this kind of nodes, like ArrayExpressions for example
// The only exception is variable declarations
if (Array.isArray(inspectPath.container) &&
!inspectPath.isVariableDeclarator() &&
!inspectPath.parentPath?.isCallExpression()) {
return false;
}
} while ((inspectPath = inspectPath.parentPath));
return false;
}
function classVisitor(path, state) {
if (isAnnotated(path, state.annotation)) {
normalizeClassDefinition(path);
state.foundDefinitions.add(path);
}
}
function statelessVisitor(path, state) {
if (isAnnotated(path, state.annotation)) {
state.foundDefinitions.add(path);
}
}
const explodedVisitors = visitors.explode({
ArrowFunctionExpression: { enter: statelessVisitor },
FunctionDeclaration: { enter: statelessVisitor },
FunctionExpression: { enter: statelessVisitor },
ObjectMethod: { enter: statelessVisitor },
ClassDeclaration: { enter: classVisitor },
ClassExpression: { enter: classVisitor },
});
/**
* Given an AST, this function tries to find all react components which
* are annotated with an annotation
*/
export default class FindAnnotatedDefinitionsResolver {
constructor({ annotation = '@component', } = {}) {
this.annotation = annotation;
}
resolve(file) {
const state = {
foundDefinitions: new Set(),
annotation: this.annotation,
};
file.traverse(explodedVisitors, state);
return Array.from(state.foundDefinitions);
}
}

View File

@@ -0,0 +1,28 @@
import type FileState from '../FileState.js';
import type { ComponentNodePath, ResolverClass } from './index.js';
interface FindExportedDefinitionsResolverOptions {
limit?: number;
}
/**
* Given an AST, this function tries to find the exported component definitions.
*
* The component definitions are either the ObjectExpression passed to
* `React.createClass` or a `class` definition extending `React.Component` or
* having a `render()` method.
*
* If a definition is part of the following statements, it is considered to be
* exported:
*
* modules.exports = Definition;
* exports.foo = Definition;
* export default Definition;
* export var Definition = ...;
*
* limit can be used to limit the components to be found. When the limit is reached an error will be thrown
*/
export default class FindExportedDefinitionsResolver implements ResolverClass {
limit: number;
constructor({ limit }?: FindExportedDefinitionsResolverOptions);
resolve(file: FileState): ComponentNodePath[];
}
export {};

View File

@@ -0,0 +1,75 @@
import isExportsOrModuleAssignment from '../utils/isExportsOrModuleAssignment.js';
import resolveExportDeclaration from '../utils/resolveExportDeclaration.js';
import resolveToValue from '../utils/resolveToValue.js';
import { visitors } from '@babel/traverse';
import { shallowIgnoreVisitors } from '../utils/traverse.js';
import findComponentDefinition from '../utils/findComponentDefinition.js';
import { ERROR_CODES, ReactDocgenError } from '../error.js';
function exportDeclaration(path, state) {
resolveExportDeclaration(path).forEach((exportedPath) => {
const definition = findComponentDefinition(exportedPath);
if (definition) {
if (state.limit > 0 && state.foundDefinitions.size > 0) {
// If a file exports multiple components, ... complain!
throw new ReactDocgenError(ERROR_CODES.MULTIPLE_DEFINITIONS);
}
state.foundDefinitions.add(definition);
}
});
return path.skip();
}
function assignmentExpressionVisitor(path, state) {
// Ignore anything that is not `exports.X = ...;` or
// `module.exports = ...;`
if (!isExportsOrModuleAssignment(path)) {
return path.skip();
}
// Resolve the value of the right hand side. It should resolve to a call
// expression, something like React.createClass
const resolvedPath = resolveToValue(path.get('right'));
const definition = findComponentDefinition(resolvedPath);
if (definition) {
if (state.limit > 0 && state.foundDefinitions.size > 0) {
// If a file exports multiple components, ... complain!
throw new ReactDocgenError(ERROR_CODES.MULTIPLE_DEFINITIONS);
}
state.foundDefinitions.add(definition);
}
return path.skip();
}
const explodedVisitors = visitors.explode({
...shallowIgnoreVisitors,
ExportNamedDeclaration: { enter: exportDeclaration },
ExportDefaultDeclaration: { enter: exportDeclaration },
AssignmentExpression: { enter: assignmentExpressionVisitor },
});
/**
* Given an AST, this function tries to find the exported component definitions.
*
* The component definitions are either the ObjectExpression passed to
* `React.createClass` or a `class` definition extending `React.Component` or
* having a `render()` method.
*
* If a definition is part of the following statements, it is considered to be
* exported:
*
* modules.exports = Definition;
* exports.foo = Definition;
* export default Definition;
* export var Definition = ...;
*
* limit can be used to limit the components to be found. When the limit is reached an error will be thrown
*/
export default class FindExportedDefinitionsResolver {
constructor({ limit = 0 } = {}) {
this.limit = limit;
}
resolve(file) {
const state = {
foundDefinitions: new Set(),
limit: this.limit,
};
file.traverse(explodedVisitors, state);
return Array.from(state.foundDefinitions);
}
}

View File

@@ -0,0 +1,16 @@
import ChainResolver from './ChainResolver.js';
import FindAllDefinitionsResolver from './FindAllDefinitionsResolver.js';
import FindAnnotatedDefinitionsResolver from './FindAnnotatedDefinitionsResolver.js';
import FindExportedDefinitionsResolver from './FindExportedDefinitionsResolver.js';
import type { NodePath } from '@babel/traverse';
import type FileState from '../FileState.js';
import type { ArrowFunctionExpression, CallExpression, ClassDeclaration, ClassExpression, FunctionDeclaration, FunctionExpression, ObjectExpression, ObjectMethod } from '@babel/types';
export type StatelessComponentNode = ArrowFunctionExpression | FunctionDeclaration | FunctionExpression | ObjectMethod;
export type ComponentNode = CallExpression | ClassDeclaration | ClassExpression | ObjectExpression | StatelessComponentNode;
export type ComponentNodePath = NodePath<ComponentNode>;
export type ResolverFunction = (file: FileState) => ComponentNodePath[];
export interface ResolverClass {
resolve: ResolverFunction;
}
export type Resolver = ResolverClass | ResolverFunction;
export { FindAllDefinitionsResolver, FindAnnotatedDefinitionsResolver, FindExportedDefinitionsResolver, ChainResolver, };

View File

@@ -0,0 +1,5 @@
import ChainResolver from './ChainResolver.js';
import FindAllDefinitionsResolver from './FindAllDefinitionsResolver.js';
import FindAnnotatedDefinitionsResolver from './FindAnnotatedDefinitionsResolver.js';
import FindExportedDefinitionsResolver from './FindExportedDefinitionsResolver.js';
export { FindAllDefinitionsResolver, FindAnnotatedDefinitionsResolver, FindExportedDefinitionsResolver, ChainResolver, };

View File

@@ -0,0 +1,3 @@
import type FileState from '../../FileState.js';
import type { Resolver, ResolverFunction } from '../index.js';
export default function runResolver(resolver: Resolver, file: FileState): ReturnType<ResolverFunction>;

View File

@@ -0,0 +1,6 @@
function isResolverClass(resolver) {
return typeof resolver === 'object';
}
export default function runResolver(resolver, file) {
return isResolverClass(resolver) ? resolver.resolve(file) : resolver(file);
}

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;

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