-
Notifications
You must be signed in to change notification settings - Fork 257
Description
Summary
[email protected] crashes with TypeError: Cannot read properties of undefined (reading 'parameters') when parsing callable types that have @public tagged properties that are not callable (e.g., displayName).
Reproduction
https://codesandbox.io/p/devbox/wdz724
pnpm install
node test.jsExpected Behavior
The parser should gracefully handle callable types that have non-callable @public members, either by:
- Skipping members that have no call signature
- Treating them as properties instead of methods
Actual Behavior
TypeError: Cannot read properties of undefined (reading 'parameters')
at Parser.getParameterInfo (.../react-docgen-typescript/lib/parser.js:400:30)
at Parser.getMethodsInfo (.../react-docgen-typescript/lib/parser.js:365:32)
at Parser.getComponentInfo (.../react-docgen-typescript/lib/parser.js:254:28)
Root Cause
In parser.js, the getMethodsInfo function:
- Calls
isTaggedPublic(member)which returnstruefordisplayName(line 563) - Calls
getCallSignature(member)which returnsundefinedbecausedisplayNamehas no call signature (line 569) - Calls
getParameterInfo(callSignature)which tries to accesscallSignature.parametersbutcallSignatureisundefined(line 570)
react-docgen-typescript/src/parser.ts
Lines 559 to 571 in e1f8f5f
| public getMethodsInfo(type: ts.Type): Method[] { | |
| const members = this.extractMembersFromType(type); | |
| const methods: Method[] = []; | |
| members.forEach(member => { | |
| if (!this.isTaggedPublic(member)) { | |
| return; | |
| } | |
| const name = member.getName(); | |
| const docblock = this.getFullJsDocComment(member).fullComment; | |
| const callSignature = this.getCallSignature(member); | |
| const params = this.getParameterInfo(callSignature); | |
| const description = ts.displayPartsToString( |
public getMethodsInfo(type: ts.Type): Method[] {
const members = this.extractMembersFromType(type);
const methods: Method[] = [];
members.forEach(member => {
if (!this.isTaggedPublic(member)) {
return;
}
const name = member.getName();
const docblock = this.getFullJsDocComment(member).fullComment;
const callSignature = this.getCallSignature(member); // Returns undefined!
const params = this.getParameterInfo(callSignature); // Crashes here
//...
});
return methods;
}Trigger Condition
The bug is triggered when:
- A component is a callable type (has a call signature)
- The type also has members (properties or methods on
type.symbol.members) - One of those members is tagged with
@publicin JSDoc - That member does NOT have a call signature (it's a property, not a method)
This pattern is common in libraries like framer-motion where components like AnimatePresence are callable types with a displayName property.
Minimal Reproduction Code
// Component.tsx
import React from 'react';
interface AnimatePresenceProps {
children: React.ReactNode;
}
interface AnimatePresenceType {
(props: React.PropsWithChildren<AnimatePresenceProps>): JSX.Element | null;
/**
* @public <-- This is the problematic tag
*/
displayName?: string;
}
/**
* @public
*/
export const WrappedAnimator: AnimatePresenceType = Object.assign(
(props: React.PropsWithChildren<AnimatePresenceProps>) => {
return <div>{props.children}</div>;
},
{
/**
* @public
*/
displayName: 'WrappedAnimator' as string | undefined,
}
);Suggested Fix
Add a guard in getMethodsInfo to skip members that have no call signature:
public getMethodsInfo(type: ts.Type): Method[] {
const members = this.extractMembersFromType(type);
const methods: Method[] = [];
members.forEach(member => {
if (!this.isTaggedPublic(member)) {
return;
}
const callSignature = this.getCallSignature(member);
// Skip members that are not callable (properties, not methods)
if (!callSignature) {
return;
}
// ... rest of the function
});
return methods;
}Affected Version
[email protected]- Tested with
[email protected]
Context
This bug was discovered while using Storybook 9.x with framer-motion. The AnimatePresence component from framer-motion has this exact type structure, causing Storybook builds to fail.
Workaround
In Storybook's main.js, switch to using react-docgen instead of react-docgen-typescript:
typescript: {
reactDocgen: 'react-docgen', // Use 'react-docgen' instead of 'react-docgen-typescript'
},