-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Elaborate jsx children elementwise #29264
Conversation
!!! 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'. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what
There was a problem hiding this comment.
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.
src/compiler/diagnosticMessages.json
Outdated
"category": "Error", | ||
"code": 2745 | ||
}, | ||
"Target JSX element expects a '{0}' prop of type '{1}', but multiple children were provided.": { |
There was a problem hiding this comment.
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.
src/compiler/diagnosticMessages.json
Outdated
@@ -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.": { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. 🤕
There was a problem hiding this comment.
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... 😥
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
const moreThanOneRealChildren = length(validChildren) > 1; | ||
const arrayLikeTargetParts = filterType(childrenTargetType, isArrayOrTupleLikeType); | ||
const nonArrayLikeTargetParts = filterType(childrenTargetType, t => !isArrayOrTupleLikeType(t)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5dc1782
to
71b417b
Compare
@DanielRosenwasser wanna look over the new messages? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bellissimo
|
||
|
||
|
||
{ user => ( | ||
~~~~~~~~~ |
There was a problem hiding this comment.
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.
* 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 ...
* 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 ...
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.