diff --git a/package.json b/package.json index 28009ef87681b7..56b6a5412e271b 100644 --- a/package.json +++ b/package.json @@ -178,6 +178,7 @@ "prettier": "^3.3.3", "pretty-quick": "^4.0.0", "process": "^0.11.10", + "react-docgen-typescript": "^2.2.2", "rimraf": "^5.0.10", "serve": "^14.2.3", "stylelint": "^15.11.0", diff --git a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts index 8d58633a29c1b6..86405f0566b974 100644 --- a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts +++ b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts @@ -1,36 +1,31 @@ -import { readFileSync, writeFileSync } from 'fs'; -import path from 'path'; -import * as astTypes from 'ast-types'; import * as babel from '@babel/core'; import traverse from '@babel/traverse'; +import { renderMarkdown } from '@mui/internal-markdown'; +import { Annotation, parse as parseDoctrine } from 'doctrine'; +import { readFileSync, writeFileSync } from 'fs'; import * as _ from 'lodash'; import kebabCase from 'lodash/kebabCase'; +import type { Link } from 'mdast'; +import path from 'path'; +import { ComponentDoc, parse as docgenParse } from 'react-docgen-typescript'; import remark from 'remark'; import remarkVisit from 'unist-util-visit'; -import type { Link } from 'mdast'; -import { defaultHandlers, parse as docgenParse } from 'react-docgen'; -import { renderMarkdown } from '@mui/internal-markdown'; -import { parse as parseDoctrine, Annotation } from 'doctrine'; import { ProjectSettings, SortingStrategiesType } from '../ProjectSettings'; import { toGitHubPath, writePrettifiedFile } from '../buildApiUtils'; -import muiDefaultPropsHandler from '../utils/defaultPropsHandler'; -import parseTest from '../utils/parseTest'; -import generatePropTypeDescription, { getChained } from '../utils/generatePropTypeDescription'; -import createDescribeableProp, { - CreateDescribeablePropSettings, - DescribeablePropDescriptor, -} from '../utils/createDescribeableProp'; -import generatePropDescription from '../utils/generatePropDescription'; -import { TypeScriptProject } from '../utils/createTypeScriptProject'; -import parseSlotsAndClasses from '../utils/parseSlotsAndClasses'; -import generateApiTranslations from '../utils/generateApiTranslation'; -import { sortAlphabetical } from '../utils/sortObjects'; import { AdditionalPropsInfo, ComponentApiContent, ComponentReactApi, } from '../types/ApiBuilder.types'; -import { Slot, ComponentInfo } from '../types/utils.types'; +import { ComponentInfo, Slot } from '../types/utils.types'; +import createDescribableProp, { DescribablePropDescriptor } from '../utils/createDescribableProp'; +import { TypeScriptProject } from '../utils/createTypeScriptProject'; +import generateApiTranslations from '../utils/generateApiTranslation'; +import generatePropDescription from '../utils/generatePropDescription'; +import generatePropTypeDescription from '../utils/generatePropTypeDescription'; +import parseSlotsAndClasses from '../utils/parseSlotsAndClasses'; +import parseTest from '../utils/parseTest'; +import { sortAlphabetical } from '../utils/sortObjects'; const cssComponents = ['Box', 'Grid', 'Typography', 'Stack']; @@ -394,11 +389,7 @@ const generateApiPage = async ( } }; -const attachTranslations = ( - reactApi: ComponentReactApi, - deprecationInfo: string | undefined, - settings?: CreateDescribeablePropSettings, -) => { +const attachTranslations = (reactApi: ComponentReactApi, deprecationInfo: string | undefined) => { const translations: ComponentReactApi['translations'] = { componentDescription: reactApi.description, deprecationInfo: deprecationInfo ? renderMarkdown(deprecationInfo) : undefined, @@ -406,9 +397,9 @@ const attachTranslations = ( classDescriptions: {}, }; Object.entries(reactApi.props!).forEach(([propName, propDescriptor]) => { - let prop: DescribeablePropDescriptor | null; + let prop: DescribablePropDescriptor | null; try { - prop = createDescribeableProp(propDescriptor, propName, settings); + prop = createDescribableProp(propDescriptor, propName); } catch (error) { prop = null; } @@ -463,17 +454,14 @@ const attachTranslations = ( reactApi.translations = translations; }; -const attachPropsTable = ( - reactApi: ComponentReactApi, - settings?: CreateDescribeablePropSettings, -) => { +const attachPropsTable = (reactApi: ComponentReactApi) => { const propErrors: Array<[propName: string, error: Error]> = []; type Pair = [string, ComponentReactApi['propsTable'][string]]; const componentProps: ComponentReactApi['propsTable'] = _.fromPairs( - Object.entries(reactApi.props!).map(([propName, propDescriptor]): Pair => { - let prop: DescribeablePropDescriptor | null; + Object.entries(reactApi.props).map(([propName, propDescriptor]): Pair => { + let prop: DescribablePropDescriptor | null; try { - prop = createDescribeableProp(propDescriptor, propName, settings); + prop = createDescribableProp(propDescriptor, propName); } catch (error) { propErrors.push([`[${reactApi.name}] \`${propName}\``, error as Error]); prop = null; @@ -483,7 +471,7 @@ const attachPropsTable = ( return [] as any; } - const defaultValue = propDescriptor.jsdocDefaultValue?.value; + const defaultValue = propDescriptor.defaultValue?.value; const { signature: signatureType, @@ -492,12 +480,8 @@ const attachPropsTable = ( seeMore, } = generatePropDescription(prop, propName); const propTypeDescription = generatePropTypeDescription(propDescriptor.type); - const chainedPropType = getChained(prop.type); - const requiredProp = - prop.required || - /\.isRequired/.test(prop.type.raw) || - (chainedPropType !== false && chainedPropType.required); + const requiredProp = prop.required; const deprecation = (propDescriptor.description || '').match(/@deprecated(\s+(?.*))?/); @@ -607,6 +591,18 @@ const defaultGetComponentImports = (name: string, filename: string) => { return [subpathImport, rootImport]; }; +const componentDocToComponentApi = (componentDoc?: ComponentDoc): ComponentReactApi => { + if (!componentDoc) { + return {} as ComponentReactApi; + } + + return { + description: componentDoc.description, + name: componentDoc.displayName, + props: componentDoc.props, + } as ComponentReactApi; +}; + /** * - Build react component (specified filename) api by lookup at its definition (.d.ts or ts) * and then generate the API page + json data @@ -626,66 +622,28 @@ export default async function generateComponentApi( } const filename = componentInfo.filename; - let reactApi: ComponentReactApi; + const options = { + savePropValueAsString: false, + shouldExtractLiteralValuesFromEnum: true, + shouldExtractValuesFromUnion: true, + shouldRemoveUndefinedFromOptional: true, + shouldIncludePropTagMap: true, + }; - if (componentInfo.isSystemComponent || componentInfo.name === 'Grid2') { - try { - reactApi = docgenParse( - src, - (ast) => { - let node; - astTypes.visit(ast, { - visitVariableDeclaration: (variablePath) => { - const definitions: any[] = []; - if (variablePath.node.declarations) { - variablePath - .get('declarations') - .each((declarator: any) => definitions.push(declarator.get('init'))); - } - - definitions.forEach((definition) => { - // definition.value.expression is defined when the source is in TypeScript. - const expression = definition.value?.expression - ? definition.get('expression') - : definition; - if (expression.value?.callee) { - const definitionName = expression.value.callee.name; - - if (definitionName === `create${componentInfo.name}`) { - node = expression; - } - } - }); - - return false; - }, - }); - - return node; - }, - defaultHandlers, - { filename }, - ); - } catch (error) { - // fallback to default logic if there is no `create*` definition. - if ((error as Error).message === 'No suitable component definition found.') { - reactApi = docgenParse(src, null, defaultHandlers.concat(muiDefaultPropsHandler), { - filename, - }); - } else { - throw error; - } - } - } else { - reactApi = docgenParse(src, null, defaultHandlers.concat(muiDefaultPropsHandler), { - filename, - }); + const reactApi = componentDocToComponentApi(docgenParse(componentInfo.filename, options)?.at(0)); + + if (!reactApi) { + throw new Error(`No suitable component definition found in ${filename}`); } if (!reactApi.props) { reactApi.props = {}; } + if (!reactApi.description) { + reactApi.description = ''; + } + const { getComponentImports = defaultGetComponentImports } = projectSettings; const componentJsdoc = parseDoctrine(reactApi.description); @@ -750,8 +708,8 @@ export default async function generateComponentApi( reactApi.deprecated = !!deprecation || undefined; - attachPropsTable(reactApi, projectSettings.propsSettings); - attachTranslations(reactApi, deprecationInfo, projectSettings.propsSettings); + attachPropsTable(reactApi); + attachTranslations(reactApi, deprecationInfo); // eslint-disable-next-line no-console console.log('Built API docs for', reactApi.apiPathname); diff --git a/packages/api-docs-builder/ProjectSettings.ts b/packages/api-docs-builder/ProjectSettings.ts index d6a1ec8b6f98a3..2032964deb7744 100644 --- a/packages/api-docs-builder/ProjectSettings.ts +++ b/packages/api-docs-builder/ProjectSettings.ts @@ -1,11 +1,11 @@ -import { CreateTypeScriptProjectOptions } from './utils/createTypeScriptProject'; -import { CreateDescribeablePropSettings } from './utils/createDescribeableProp'; import { ComponentClassDefinition, ComponentReactApi, HookReactApi, } from './types/ApiBuilder.types'; -import { Slot, ComponentInfo, HookInfo } from './types/utils.types'; +import { ComponentInfo, HookInfo, Slot } from './types/utils.types'; +import { CreateDescribablePropSettings } from './utils/createDescribableProp'; +import { CreateTypeScriptProjectOptions } from './utils/createTypeScriptProject'; export type SortingStrategiesType = { /** @@ -89,7 +89,7 @@ export interface ProjectSettings { /** * Settings to configure props definition tests. */ - propsSettings?: CreateDescribeablePropSettings; + propsSettings?: CreateDescribablePropSettings; /** * If `true`, the script does not generate JS page file. * Once we have the API tabs in all projects, we can make this `true` by default. diff --git a/packages/api-docs-builder/types/ApiBuilder.types.ts b/packages/api-docs-builder/types/ApiBuilder.types.ts index c80847ec6b093f..f9e1d19fdc204f 100644 --- a/packages/api-docs-builder/types/ApiBuilder.types.ts +++ b/packages/api-docs-builder/types/ApiBuilder.types.ts @@ -1,6 +1,6 @@ -import { ReactDocgenApi } from 'react-docgen'; +import { ComponentDoc } from 'react-docgen-typescript'; import { JSDocTagInfo } from 'typescript'; -import { ComponentInfo, Slot, HookInfo, SeeMore } from './utils.types'; +import { ComponentInfo, HookInfo, SeeMore, Slot } from './utils.types'; export type AdditionalPropsInfo = { cssApi?: boolean; @@ -14,7 +14,7 @@ export type AdditionalPropsInfo = { /** * Common interface for both Component and Hook API builders. */ -interface CommonReactApi extends ReactDocgenApi { +interface CommonReactApi extends Pick { demos: ReturnType; EOL: string; filename: string; diff --git a/packages/api-docs-builder/utils/createDescribableProp.ts b/packages/api-docs-builder/utils/createDescribableProp.ts new file mode 100644 index 00000000000000..1c34c8795b3683 --- /dev/null +++ b/packages/api-docs-builder/utils/createDescribableProp.ts @@ -0,0 +1,61 @@ +import * as doctrine from 'doctrine'; +import { PropItem, PropItemType } from 'react-docgen-typescript'; + +export interface DescribablePropDescriptor { + annotation: doctrine.Annotation; + defaultValue: string | null; + required: boolean; + type: PropItemType; + tags: PropItem['tags']; +} + +export type CreateDescribablePropSettings = { + /** + * Names of props that do not check if the annotations equal runtime default. + */ + propsWithoutDefaultVerification?: string[]; +}; + +/** + * Returns `null` if the prop should be ignored. + * Throws if it is invalid. + * @param prop + * @param propName + */ +export default function createDescribableProp( + prop: PropItem, + propName: string, +): DescribablePropDescriptor | null { + const { defaultValue, description, required, type, tags } = prop; + + const renderedDefaultValue = defaultValue?.value.replace(/\r?\n/g, ''); + const renderDefaultValue = Boolean( + renderedDefaultValue && + // Ignore "large" default values that would break the table layout. + renderedDefaultValue.length <= 150, + ); + + if (description === undefined) { + throw new Error(`The "${propName}" prop is missing a description.`); + } + + const annotation = doctrine.parse(description, { + sloppy: true, + }); + + if ( + description.trim() === '' || + // @ts-expect-error empty object type + tags?.ignore + ) { + return null; + } + + return { + annotation, + defaultValue: renderDefaultValue ? renderedDefaultValue! : null, + required: Boolean(required), + type, + tags: prop.tags, + }; +} diff --git a/packages/api-docs-builder/utils/createDescribeableProp.ts b/packages/api-docs-builder/utils/createDescribeableProp.ts deleted file mode 100644 index 0612c0ec742760..00000000000000 --- a/packages/api-docs-builder/utils/createDescribeableProp.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as doctrine from 'doctrine'; -import { PropDescriptor, PropTypeDescriptor } from 'react-docgen'; - -export interface DescribeablePropDescriptor { - annotation: doctrine.Annotation; - defaultValue: string | null; - required: boolean; - type: PropTypeDescriptor; -} - -export type CreateDescribeablePropSettings = { - /** - * Names of props that do not check if the annotations equal runtime default. - */ - propsWithoutDefaultVerification?: string[]; -}; - -/** - * Returns `null` if the prop should be ignored. - * Throws if it is invalid. - * @param prop - * @param propName - */ -export default function createDescribeableProp( - prop: PropDescriptor, - propName: string, - settings: CreateDescribeablePropSettings = {}, -): DescribeablePropDescriptor | null { - const { defaultValue, jsdocDefaultValue, description, required, type } = prop; - - const { propsWithoutDefaultVerification = [] } = settings; - - const renderedDefaultValue = defaultValue?.value.replace(/\r?\n/g, ''); - const renderDefaultValue = Boolean( - renderedDefaultValue && - // Ignore "large" default values that would break the table layout. - renderedDefaultValue.length <= 150, - ); - - if (description === undefined) { - throw new Error(`The "${propName}" prop is missing a description.`); - } - - const annotation = doctrine.parse(description, { - sloppy: true, - }); - - if ( - annotation.description.trim() === '' || - annotation.tags.some((tag) => tag.title === 'ignore') - ) { - return null; - } - - if (jsdocDefaultValue !== undefined && defaultValue === undefined) { - // Assume that this prop: - // 1. Is typed by another component - // 2. Is forwarded to that component - // Then validation is handled by the other component. - // Though this does break down if the prop is used in other capacity in the implementation. - // So let's hope we don't make this mistake too often. - } else if (jsdocDefaultValue === undefined && defaultValue !== undefined && renderDefaultValue) { - const shouldHaveDefaultAnnotation = - // Discriminator for polymorphism which is not documented at the component level. - // The documentation of `component` does not know in which component it is used. - propName !== 'component'; - - if (shouldHaveDefaultAnnotation) { - throw new Error( - `JSDoc @default annotation not found. Add \`@default ${defaultValue.value}\` to the JSDoc of this prop.`, - ); - } - } else if ( - jsdocDefaultValue !== undefined && - !propsWithoutDefaultVerification.includes(propName) - ) { - // `defaultValue` can't be undefined or we would've thrown earlier. - if (jsdocDefaultValue.value !== defaultValue!.value) { - throw new Error( - `Expected JSDoc @default annotation for prop '${propName}' of "${jsdocDefaultValue.value}" to equal runtime default value of "${defaultValue?.value}"`, - ); - } - } - - return { - annotation, - defaultValue: renderDefaultValue ? renderedDefaultValue! : null, - required: Boolean(required), - type, - }; -} diff --git a/packages/api-docs-builder/utils/defaultPropsHandler.ts b/packages/api-docs-builder/utils/defaultPropsHandler.ts index 9b49c019f1af88..f4221feebb4052 100644 --- a/packages/api-docs-builder/utils/defaultPropsHandler.ts +++ b/packages/api-docs-builder/utils/defaultPropsHandler.ts @@ -1,6 +1,6 @@ import { namedTypes as types } from 'ast-types'; -import { parse as parseDoctrine, Annotation } from 'doctrine'; -import { utils as docgenUtils, NodePath, Documentation, Importer, Handler } from 'react-docgen'; +import { Annotation, parse as parseDoctrine } from 'doctrine'; +import { utils as docgenUtils, Documentation, Handler, Importer, NodePath } from 'react-docgen'; const { getPropertyName, isReactForwardRefCall, printValue, resolveToValue } = docgenUtils; diff --git a/packages/api-docs-builder/utils/generatePropDescription.ts b/packages/api-docs-builder/utils/generatePropDescription.ts index e8214e67f31d7e..d6562bfcef0171 100644 --- a/packages/api-docs-builder/utils/generatePropDescription.ts +++ b/packages/api-docs-builder/utils/generatePropDescription.ts @@ -1,13 +1,11 @@ import * as doctrine from 'doctrine'; -import * as recast from 'recast'; -import { PropTypeDescriptor } from 'react-docgen'; +import { SeeMore } from '../types/utils.types'; +import { DescribablePropDescriptor } from './createDescribableProp'; +import escapeCell from './escapeCell'; import { - isElementTypeAcceptingRefProp, isElementAcceptingRefProp, + isElementTypeAcceptingRefProp, } from './generatePropTypeDescription'; -import { DescribeablePropDescriptor } from './createDescribeableProp'; -import escapeCell from './escapeCell'; -import { SeeMore } from '../types/utils.types'; function resolveType(type: NonNullable): string { if (type.type === 'AllLiteral') { @@ -56,16 +54,13 @@ function resolveType(type: NonNullable): string { throw new TypeError(`resolveType for '${type.type}' not implemented`); } -function getDeprecatedInfo(type: PropTypeDescriptor) { - const marker = /deprecatedPropType\((\r*\n)*\s*PropTypes\./g; - const match = type.raw.match(marker); - const startIndex = type.raw.search(marker); - if (match) { - const offset = match[0].length; - +function getDeprecatedInfo(prop: DescribablePropDescriptor) { + // @ts-expect-error empty object type + if (prop.tags?.deprecated) { return { - propTypes: type.raw.substring(startIndex + offset, type.raw.indexOf(',')), - explanation: recast.parse(type.raw).program.body[0].expression.arguments[1].value, + propTypes: prop.type.raw, + // @ts-expect-error empty object type + explanation: prop.tags.deprecated, }; } @@ -73,7 +68,7 @@ function getDeprecatedInfo(type: PropTypeDescriptor) { } export default function generatePropDescription( - prop: DescribeablePropDescriptor, + prop: DescribablePropDescriptor, propName: string, ): { deprecated: string; @@ -89,7 +84,7 @@ export default function generatePropDescription( let deprecated = ''; if (type.name === 'custom') { - const deprecatedInfo = getDeprecatedInfo(type); + const deprecatedInfo = getDeprecatedInfo(prop); if (deprecatedInfo) { deprecated = `*Deprecated*. ${deprecatedInfo.explanation}

`; } diff --git a/packages/api-docs-builder/utils/generatePropTypeDescription.ts b/packages/api-docs-builder/utils/generatePropTypeDescription.ts index 66fdf8335e078a..b311b5164bcd2f 100644 --- a/packages/api-docs-builder/utils/generatePropTypeDescription.ts +++ b/packages/api-docs-builder/utils/generatePropTypeDescription.ts @@ -1,24 +1,24 @@ +import { parse as docgenParse, PropItemType } from 'react-docgen-typescript'; import * as recast from 'recast'; -import { parse as docgenParse, PropTypeDescriptor } from 'react-docgen'; import escapeCell from './escapeCell'; -function getDeprecatedInfo(type: PropTypeDescriptor) { +function getDeprecatedInfo(type: PropItemType) { const marker = /deprecatedPropType\((\r*\n)*\s*PropTypes\./g; - const match = type.raw.match(marker); - const startIndex = type.raw.search(marker); + const match = type.raw?.match(marker); + const startIndex = type.raw?.search(marker); if (match) { const offset = match[0].length; return { - propTypes: type.raw.substring(startIndex + offset, type.raw.indexOf(',')), - explanation: recast.parse(type.raw).program.body[0].expression.arguments[1].value, + propTypes: type.raw?.substring((startIndex ?? 0) + offset, type.raw.indexOf(',')), + explanation: recast.parse(type.raw ?? '').program.body[0].expression.arguments[1].value, }; } return false; } -export function getChained(type: PropTypeDescriptor) { +export function getChained(type: PropItemType) { if (type.raw) { const marker = 'chainPropTypes'; const indexStart = type.raw.indexOf(marker); @@ -33,14 +33,10 @@ export function getChained(type: PropTypeDescriptor) { } export default Foo `, - null, - null, - // helps react-docgen pickup babel.config.js - { filename: './' }, - ); + ).at(0); return { - type: parsed.props.bar.type, - required: parsed.props.bar.required, + type: parsed?.props.bar.type, + required: parsed?.props.bar.required, }; } } @@ -48,23 +44,23 @@ export function getChained(type: PropTypeDescriptor) { return false; } -export function isElementTypeAcceptingRefProp(type: PropTypeDescriptor): boolean { +export function isElementTypeAcceptingRefProp(type: PropItemType): boolean { return type.raw === 'elementTypeAcceptingRef'; } -function isRefType(type: PropTypeDescriptor): boolean { +function isRefType(type: PropItemType): boolean { return type.raw === 'refType'; } -function isIntegerType(type: PropTypeDescriptor): boolean { - return type.raw.startsWith('integerPropType'); +function isIntegerType(type: PropItemType): boolean { + return type.raw?.startsWith('integerPropType') ?? false; } -export function isElementAcceptingRefProp(type: PropTypeDescriptor): boolean { - return /^elementAcceptingRef/.test(type.raw); +export function isElementAcceptingRefProp(type: PropItemType): boolean { + return /^elementAcceptingRef/.test(type.raw ?? ''); } -export default function generatePropTypeDescription(type: PropTypeDescriptor): string | undefined { +export default function generatePropTypeDescription(type: PropItemType): string | undefined { switch (type.name) { case 'custom': { if (isElementTypeAcceptingRefProp(type)) { @@ -94,11 +90,6 @@ export default function generatePropTypeDescription(type: PropTypeDescriptor): s } as any); } - const chained = getChained(type); - if (chained !== false) { - return generatePropTypeDescription(chained.type); - } - return type.raw; } @@ -114,16 +105,16 @@ export default function generatePropTypeDescription(type: PropTypeDescriptor): s case 'union': return ( - type.value + (type.value as { value: string }[]) .map((type2) => { - return generatePropTypeDescription(type2); + return escapeCell(type2.value); }) // Display one value per line as it's better for visibility. .join('
| ') ); case 'enum': return ( - type.value + (type.value as { value: string }[]) .map((type2) => { return escapeCell(type2.value); }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f15db9212e4c94..59b4bb927878c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -290,6 +290,9 @@ importers: process: specifier: ^0.11.10 version: 0.11.10 + react-docgen-typescript: + specifier: ^2.2.2 + version: 2.2.2(typescript@5.4.5) rimraf: specifier: ^5.0.10 version: 5.0.10 @@ -10455,6 +10458,11 @@ packages: react-display-name@0.2.5: resolution: {integrity: sha512-I+vcaK9t4+kypiSgaiVWAipqHRXYmZIuAiS8vzFvXHHXVigg/sMKwlRgLy6LH2i3rmP+0Vzfl5lFsFRwF1r3pg==} + react-docgen-typescript@2.2.2: + resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} + peerDependencies: + typescript: '>= 4.3.x' + react-docgen@5.4.3: resolution: {integrity: sha512-xlLJyOlnfr8lLEEeaDZ+X2J/KJoe6Nr9AzxnkdQWush5hz2ZSu66w6iLMOScMmxoSHWpWMn+k3v5ZiyCfcWsOA==} engines: {node: '>=8.10.0'} @@ -22465,6 +22473,10 @@ snapshots: react-display-name@0.2.5: {} + react-docgen-typescript@2.2.2(typescript@5.4.5): + dependencies: + typescript: 5.4.5 + react-docgen@5.4.3: dependencies: '@babel/core': 7.25.2