Skip to content

Support flow type spread #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 23, 2018
Merged

Conversation

elicwhite
Copy link
Collaborator

Supporting type spread props:

type Props = {|
  /**
   * Spread props potentially from another component
   */
  ...OtherProps,

  /**
   * The first prop
   */
  prop1: string,
|}

This is a stacked PR on top of #240

@yungsters yungsters requested review from fkling and danez January 5, 2018 17:04
@elicwhite
Copy link
Collaborator Author

It seems like we should change the getFlowType function to return something special for ObjectTypeSpread.

@danez
Copy link
Collaborator

danez commented Jan 5, 2018

I merged #240, could you please rebase?

@elicwhite
Copy link
Collaborator Author

elicwhite commented Jan 5, 2018

Rebased. The right approach for this PR was a little less obvious to me. I tried to follow the algorithm conventions that existed but I'm not sure if the output of the spread type is what it actually should be. I would appreciate a bit closer review of those pieces.

type: 'object',
signature: {
properties: [
{ key: 'OtherProps', value: { name: 'OtherProps', required: true } },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is not really clear what this is just from the json alone. It could be also interpreted as

type a = { OtherProps: OtherProps };

What i would try to do is resolve OtherProps and if it resolves to an other ObjectType within the same file then I would just inline this in the result. We do the same when resolving stuff here https://github.com/reactjs/react-docgen/blob/master/src/utils/resolveObjectKeysToArray.js#L59..L67

In the case when it cannot be resolved I'm not sure what the best approach is. We could either ignore the spread completely then, which we currently do for regular non-type ObjectSpread. Or somehow add a special field in the output aka "inherits" (or some better name) which just contains the names of the Type that was spreaded into the object. If I think about it spreading types is just some kind of multi inheritance and it is probably interesting to know.

{
  name: 'signature',
  type: 'object',
  includes/inherits/implements/??: [ 'OtherProps' ],
  signature: {
    properties: [
      { key: 'a', value: { name: 'string', required: true } },
    ]
  }
}

Not sure if that makes sense though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for resolving the local types is already being done. You can see it in action in the component snapshot test, prop OtherLocalProps.

Yeah, the real question is what to do when the type is imported from another file, 90% of our use case internally. We definitely don't want to ignore the spread. I think for us at least, saying what the types are is pretty explanatory. It's typically things like ...AccessibilityProps.

An explicit inherits field is interesting. I am not familiar enough with this project and how we consume the output to know what is ideal here. I think I defer to you.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example of a component where we type spread props in and how we'd probably expect them to show up in docs. https://facebook.github.io/react-native/docs/touchableopacity.html#props

},
"required": false,
},
"OtherLocalProps": Object {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this could be confused with a prop called OtherLocalProps that has these specific keys. However, since it is being spread in, the properties of OtherLocalProps should probably be flattened into the top level props? Is that correct?

@elicwhite
Copy link
Collaborator Author

@danez, do you have any more thoughts on these open questions?

@elicwhite
Copy link
Collaborator Author

elicwhite commented Jan 9, 2018

I'm thinking that we want to distinguish between the spread properties and the normal ones. Also, since we want to resolve local types and possibly at some point have a way to reference where the definition originally comes from, we want the inherited list to be objects instead of plain strings.

I'm thinking this structure:

 it('handles ObjectTypeSpreadProperty', () => {
    var typePath = statement(`
      var x: { ...OtherProps, ...MyType, a: string };

      type MyType = { myString: string };
    `).get('declarations', 0).get('id').get('typeAnnotation').get('typeAnnotation');

    expect(getFlowType(typePath)).toEqual({
      name: 'signature',
      type: 'object',
      signature: {
        inheritedProperties: [
          { name: 'OtherProps', value: { name: 'OtherProps' } },
          { name: 'MyType',
            value: {
              name: 'signature',
              type: 'object',
              signature: {
                properties: [
                  { key: 'myString', value: { name: 'string', required: true } },
                ],
              },
              raw: '{ myString: string }',
            },
          },
        ],
        properties: [
          { key: 'a', value: { name: 'string', required: true } },
        ],
      },
      raw: '{ ...OtherProps, ...MyType, a: string }'});
  });

"flowType": Object {
"name": "OtherComponentProps",
},
"inherited": true,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These have a flag for whether it is inherited. This was cleaner than adding a new top level key to the output documentation.

@elicwhite
Copy link
Collaborator Author

elicwhite commented Jan 9, 2018

Alright, I think this is good to go now. I broke this into two separate PRs to make it easier to review.

@elicwhite
Copy link
Collaborator Author

elicwhite commented Jan 11, 2018

@danez, kind bump. @fkling is on vacation so I can't bug him internally to get this merged and released. :)

Other people on my team like @yungsters, have merge permissions here but only you and felix can publish a new version. If you'd like to add the facebook npm user to have publish permissions: https://www.npmjs.com/~fb, then we can take care of it.

@danez
Copy link
Collaborator

danez commented Jan 11, 2018

Why did you decide to do it in 2 different ways? Once with inheritedProps and once with a inherited flag?

@elicwhite
Copy link
Collaborator Author

I could have gone either way, I actually initially had them both as a separate inherited object. The reason I didn't was partly because the code necessary to add inherited props to a new inheritedProps field in the documentation class is pretty hairy. Adding an inherited flag there was the simplest thing to do and required the most surface level changes that would be most likely to be merged. I figured if we wanted to split them out later it would be easier going this direction than the other direction.

There are a few different reasons I made getFlowType have a separate inheritedProperties. Fundamentally, the output from this function seems different than the component documentation output. One is describing the props, one is describing the type of a generic variable in JavaScript. The properties array also had key whereas the key of an inherited property doesn't actually make sense so inheritedProperties use name.

All in all, I don't feel strongly either direction. I'm happy to change approach if someone with more context has a preference. I mostly just want to get Facebook's UIDocs to stop blowing up on these new flow types. Perhaps we can merge and ship a PR that does that and then we can figure out the best output for these additional types.

@@ -267,6 +267,35 @@ Object {
"displayName": "MyComponent",
"methods": Array [],
"props": Object {
"OtherComponentProps": Object {
Copy link
Collaborator Author

@elicwhite elicwhite Jan 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to composes

"NonExistentFile.OtherCommmponentProps"

if default

then "URI"

},
"inherited": true,
},
"OtherLocalProps": Object {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline this into props.

}

setPropDescription(documentation, path);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment to this.

"description": "",
"displayName": "MyComponent",
"methods": Array [],
"props": Object {
"OtherLocalProps": Object {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. Not spread in far enough.

import { OtherComponentProps as OtherProps } from 'NonExistentFile';

type OtherLocalProps = {|
fooProp?: string,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add and pull in a docblock comment here.

@danez
Copy link
Collaborator

danez commented Jan 22, 2018

Looks good to me now. So you now changed it to use composes? That sounds really good to me. Should we try to align the other PR? composes and importedProperties... or do you think this are different usecases?

@elicwhite
Copy link
Collaborator Author

I think the other one needs a bit more work still. All of the inline comments I left were from doing an in person code review with @fkling. This PR should be good to go though. I’ll try to get Felix to review it closer today. :-)

@danez
Copy link
Collaborator

danez commented Jan 22, 2018

Okay I can also otherwise merge it . Sorry for the delay I'm really busy lately, but now off work for this week, so have more time.

setPropDescriptor(documentation, propertyPath)
});
} else {
documentation.addComposes(name.node.name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this takes the variable name used in the spread syntax, right? Did we plan to use the module name instead at some point? I can't remember.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think where it ended up was that it was too difficult to make something meaningful here.

import type { MyType } from 'MyFile';

type MyFileMyProperty = $PropertyType<MyType, 'myProperty'>;

type Props = {|
  ...MyFileMyProperty
|}

It doesn't make sense to say that it composes MyFile.MyFileMyProperty, in this case it would be MyFile.MyType.myProperty but the logic can get exceedingly complicated and it doesn't make sense for us to try to understand it statically. I think where we landed was to just use the alias, if they want it to be more explicit in the generated docs they can use a more explicit alias.

This still leaves us open to improving it at a future point though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright

@fkling fkling merged commit dace500 into reactjs:v3-dev Jan 23, 2018
@elicwhite elicwhite deleted the support-flow-type-spread branch January 23, 2018 00:44
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 28, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants