Skip to content

Crash on Callable Types with @public Non-Callable Members #529

@manzoorwanijk

Description

@manzoorwanijk

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.js

Expected Behavior

The parser should gracefully handle callable types that have non-callable @public members, either by:

  1. Skipping members that have no call signature
  2. 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:

  1. Calls isTaggedPublic(member) which returns true for displayName (line 563)
  2. Calls getCallSignature(member) which returns undefined because displayName has no call signature (line 569)
  3. Calls getParameterInfo(callSignature) which tries to access callSignature.parameters but callSignature is undefined (line 570)

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:

  1. A component is a callable type (has a call signature)
  2. The type also has members (properties or methods on type.symbol.members)
  3. One of those members is tagged with @public in JSDoc
  4. 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

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'
},

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions