Skip to content
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

Elaborate jsx children elementwise #29264

Merged

Conversation

weswigham
Copy link
Member

Fixes #29251

This really exposed how wonky our JSX children checking is. The fact that a single-element-child isn't assignable to an array target (because it's not inserted into an array) is real strange.

Anyways, @DanielRosenwasser 's examples also exposed some problems with how we calculated contextual types for JSX children, which are also fixed in this PR.

!!! error TS2322: Type 'string | Element' is not assignable to type 'Element'.
!!! error TS2322: Type 'string' is not assignable to type 'Element'.
~~
!!! error TS2322: Type 'string' is not assignable to type 'Element'.
Copy link
Member

Choose a reason for hiding this comment

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

This is a little too subtle. Is it possible to make this

'{0}' components don't accept text as child elements. Text in JSX has the type 'string', but the expected type of '{1}' is '{2}'.

return { errorNode: child, innerExpression: child.expression, nameType };
case SyntaxKind.JsxText:
if (child.containsOnlyWhiteSpaces) {
break; // Whitespace only jsx text isn't real jsx text
Copy link
Member

Choose a reason for hiding this comment

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

what

Copy link

@Jessidhia Jessidhia Jan 10, 2019

Choose a reason for hiding this comment

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

<Foo>
</Foo>

will have a JsxText of '\n' but it'll get eliminated before it's converted to the JSX factory call. It won't become a call to React.createElement(Foo, {}, '\n'), but rather React.createElement(Foo).


It will be real text if it's placed between two other JSX elements and doesn't look like newline + indent (<><Foo/> <Bar/></> has 3 children), but I think this is only checking the single child case.

"category": "Error",
"code": 2745
},
"Target JSX element expects a '{0}' prop of type '{1}', but multiple children were provided.": {
Copy link
Member

Choose a reason for hiding this comment

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

This JSX element expects its '{0}' prop to take a single child of type '{1}', but multiple children were provided.

@@ -2541,6 +2541,14 @@
"category": "Error",
"code": 2744
},
"Target JSX element expects a '{0}' prop of type '{1}', but only a single child was provided.": {
Copy link
Member

Choose a reason for hiding this comment

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

This JSX element's '{0}' prop expects type '{1}' which requires multiple children, but only a single child was provided.

if (!length(validChildren)) {
return result;
}
const moreThanOneRealChildren = length(validChildren) > 1;
Copy link

@Jessidhia Jessidhia Jan 10, 2019

Choose a reason for hiding this comment

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

This is hardcoding React-specific knowledge (preact makes no distinction between 1 or many children, it's always array), but it's still better than how it is right now.

This does mean that "The fact that a single-element-child isn't assignable to an array target (because it's not inserted into an array) is real strange." is actually a correct implementation of React at runtime.

Copy link
Member Author

Choose a reason for hiding this comment

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

ick, react-specific jsx checker code. This and the (now incorrect) sfc return type check. 🤕

Choose a reason for hiding this comment

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

This is why React tells you to always use React.Children when dealing with children, but nobody listens... 😥

Copy link
Member Author

Choose a reason for hiding this comment

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

React.Children provides utilities for dealing with the this.props.children opaque data structure.

opaque

image

OK.

}
const moreThanOneRealChildren = length(validChildren) > 1;
const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType);
const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t));

Choose a reason for hiding this comment

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

Do you think this fixes children not being caught in excess property checks? IIUC if there is no children prop these both should become neverType, though the error message will probably be confusing.

Copy link
Member Author

Choose a reason for hiding this comment

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

The react .d.ts unconditionally mixes children into class components, and so does React.FunctionComponent for those; so if you actually base your component type on the react builtin types, nope. However if you write your own unannotated function component, eg

const Tag = (x: {}) => <div></div>;

// OK
const k1 = <Tag />;
const k2 = <Tag></Tag>;

// Not OK (excess children)
const k3 = <Tag children={<div></div>} />;
const k4 = <Tag><div></div></Tag>;
const k5 = <Tag><div></div><div></div></Tag>;

it seems to work OK.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's not dependent on this PR, however - that's how it currently behaves. (This PR only adjusts error reporting mostly, after all)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ick, works less OK when you have props in common:

const k4 = <Tag key="1"><div></div></Tag>; // should error but doesn't
const k5 = <Tag key="1"><div></div><div></div></Tag>; // should error but doesn't

Copy link
Member Author

Choose a reason for hiding this comment

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

Hahahahahahaha the children prop thing is defs a bug on our part. It's excluded from excess property checks because it's missing a valueDeclaration (whose parent is the containing attributes).

Copy link

@Jessidhia Jessidhia Jan 11, 2019

Choose a reason for hiding this comment

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

Yep, this is that bug I found that time I did try to stop the unconditional mixing in of children :)

Copy link
Member Author

Choose a reason for hiding this comment

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

@weswigham weswigham force-pushed the elaborate-jsx-children-elementwise branch from 5dc1782 to 71b417b Compare January 11, 2019 00:46
@weswigham
Copy link
Member Author

@DanielRosenwasser wanna look over the new messages?

Copy link
Member

@DanielRosenwasser DanielRosenwasser left a comment

Choose a reason for hiding this comment

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

Bellissimo




{ user => (
~~~~~~~~~
Copy link
Member

Choose a reason for hiding this comment

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

Would be awesome if we could make multi-line error spans less, uh, bad.

@weswigham weswigham merged commit 4968989 into microsoft:master Jan 15, 2019
@weswigham weswigham deleted the elaborate-jsx-children-elementwise branch January 15, 2019 19:32
errendir added a commit to errendir/TypeScript that referenced this pull request Jan 20, 2019
* origin/master: (64 commits)
  Fix resolution of properties from prototype assignment in JS (microsoft#29302)
  Include all flow nodes made within `try` blocks as antecedents for `catch` or `finally` blocks (microsoft#29466)
  Don't treat interfaces as implementations
  Make the relationship between partial mapped types and the empty object not apply for subtype relationship (microsoft#29384)
  Add missing arity check on second inference pass (microsoft#29386)
  renames
  add missing type annotation
  PR feedback
  Illustrate a case that isn't handled correctly
  Add fourslash tests
  Consider JSX namespace imports when moving statements between files
  Fix gulp builds not building some targets
  Update user baselines (microsoft#29444)
  Add opt-in user preference for prefix and suffix text on renames (microsoft#29314)
  Fake up value declaration for synthetic jsx children symbol so they get excess property checked (microsoft#29359)
  Add regression test. (microsoft#29433)
  Elaborate jsx children elementwise (microsoft#29264)
  Add regression test
  PR feedback
  Fix trailing whitespace
  ...
errendir added a commit to errendir/TypeScript that referenced this pull request Jan 20, 2019
* origin/master: (64 commits)
  Fix resolution of properties from prototype assignment in JS (microsoft#29302)
  Include all flow nodes made within `try` blocks as antecedents for `catch` or `finally` blocks (microsoft#29466)
  Don't treat interfaces as implementations
  Make the relationship between partial mapped types and the empty object not apply for subtype relationship (microsoft#29384)
  Add missing arity check on second inference pass (microsoft#29386)
  renames
  add missing type annotation
  PR feedback
  Illustrate a case that isn't handled correctly
  Add fourslash tests
  Consider JSX namespace imports when moving statements between files
  Fix gulp builds not building some targets
  Update user baselines (microsoft#29444)
  Add opt-in user preference for prefix and suffix text on renames (microsoft#29314)
  Fake up value declaration for synthetic jsx children symbol so they get excess property checked (microsoft#29359)
  Add regression test. (microsoft#29433)
  Elaborate jsx children elementwise (microsoft#29264)
  Add regression test
  PR feedback
  Fix trailing whitespace
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Better errors for JSX children
3 participants