react-docgen-typescript icon indicating copy to clipboard operation
react-docgen-typescript copied to clipboard

Support files with multiple functions and one .displayName

Open jesstelford opened this issue 3 years ago • 4 comments

Hi! 👋

Firstly, thanks for your work on this project! 🙂

I have a file which exports a regular function who's first parameter is an object, plus a React function component with a defined displayName. When these conditions are met, the component's props are not output in the final result.

The minimum reproduction is:

export const Button = ({ label }: { label: string }) => (
  <button>{label}</button>
);
Button.displayName = 'Button';

export function areButtonsAwesome(obj = {}) {}

It appears the getTextValueOfFunctionProperty method is scanning the source file to generate display name's for each function by searching for the .displayName = assignment within the complete source file.

The issue occurs because it does not take into account the function on which the .displayName is being set; in the example above, both Button and areButtonsAwesome will end up with a displayName of Button thanks to the line Button.displayName = 'Button'. This on its own isn't an issue until later code deduplicates based on the generated displayName. In this case, the areButtonsAwesome wins, and Button is removed from the output as the "duplicate".

The fix is to check the .displayName is being assigned to the function in question, instead of just using the first one found.

Here is the diff that solved my problem:

diff --git a/node_modules/react-docgen-typescript/lib/parser.js b/node_modules/react-docgen-typescript/lib/parser.js
index e98cd64..09dc39e 100644
--- a/node_modules/react-docgen-typescript/lib/parser.js
+++ b/node_modules/react-docgen-typescript/lib/parser.js
@@ -797,7 +797,17 @@ function getTextValueOfFunctionProperty(exp, source, propertyName) {
         return (expr.left &&
             expr.left.name &&
             expr.left.name.escapedText ===
-                propertyName);
+                propertyName &&
+            // Ensure the .displayName is for the function we're processing. This
+            // avoids a situation where a file has multiple functions, only one of
+            // which has a .displayName; we don't want all functions inheriting that
+            // value by mistake.
+            statement.flowNode &&
+            statement.flowNode.node &&
+            statement.flowNode.node.name &&
+            statement.flowNode.node.name.escapedText &&
+            statement.flowNode.node.name.escapedText === exp.escapedName
+      );
     })
         .filter(function (statement) {
         return ts.isStringLiteral(statement

I'm not sure how to write this in TS, perhaps you can take my patch and apply it to the .ts file manually?

This issue body was partially generated by patch-package.

jesstelford avatar May 26 '22 07:05 jesstelford

Can you re-open this @pvasek - it was closed with #441

kylemh avatar Sep 17 '22 10:09 kylemh

Yes I have this issue! My current workaround:

Works (workaround):

export const Accordion = forwardRef<
  ElementRef<typeof AccordionPrimitive.Root>,
  ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>
+ >(function Accordion(props, ref) {
  return <AccordionPrimitive.Root ref={ref} {...props} />;
});

Doesn't work:

export const Accordion = forwardRef<
  ElementRef<typeof AccordionPrimitive.Root>,
  ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>
>((props, ref)  => (
 <AccordionPrimitive.Root ref={ref} {...props} />
));
Accordion.displayName = 'Accordion'

SanderCokart avatar Aug 30 '23 08:08 SanderCokart