mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Add test case and implement fix for JSX Fragment typing with react-jsx mode
Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
This commit is contained in:
+29
-7
@@ -33791,7 +33791,26 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
(isJsxElement(parent) && parent.openingElement === openingLikeElement || isJsxFragment(parent) && parent.openingFragment === openingLikeElement) &&
|
||||
getSemanticJsxChildren(parent.children).length > 0
|
||||
) {
|
||||
const childrenTypes: Type[] = checkJsxChildren(parent, checkMode);
|
||||
// Compute contextual type for children before checking them
|
||||
let childrenContextualType: Type | undefined;
|
||||
if (jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
|
||||
let contextualType: Type | undefined;
|
||||
if (isJsxOpeningElement(openingLikeElement)) {
|
||||
contextualType = getApparentTypeOfContextualType(openingLikeElement.attributes, /*contextFlags*/ undefined);
|
||||
}
|
||||
else if (isJsxOpeningFragment(openingLikeElement)) {
|
||||
// For fragments, get the props type from the Fragment factory's signature
|
||||
const fragmentType = getJSXFragmentType(openingLikeElement);
|
||||
const signatures = getSignaturesOfType(fragmentType, SignatureKind.Call);
|
||||
if (signatures.length > 0) {
|
||||
contextualType = getTypeOfFirstParameterOfSignature(signatures[0]);
|
||||
}
|
||||
}
|
||||
childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
|
||||
}
|
||||
|
||||
// Check children with contextual type
|
||||
const childrenTypes: Type[] = checkJsxChildren(parent, checkMode, childrenContextualType);
|
||||
|
||||
if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
|
||||
// Error if there is a attribute named "children" explicitly specified and children element.
|
||||
@@ -33800,9 +33819,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
if (explicitlySpecifyChildrenAttribute) {
|
||||
error(attributeParent, Diagnostics._0_are_specified_twice_The_attribute_named_0_will_be_overwritten, unescapeLeadingUnderscores(jsxChildrenPropertyName));
|
||||
}
|
||||
|
||||
const contextualType = isJsxOpeningElement(openingLikeElement) ? getApparentTypeOfContextualType(openingLikeElement.attributes, /*contextFlags*/ undefined) : undefined;
|
||||
const childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
|
||||
// If there are children in the body of JSX element, create dummy attribute "children" with the union of children types so that it will pass the attribute checking process
|
||||
const childrenPropSymbol = createSymbol(SymbolFlags.Property, jsxChildrenPropertyName);
|
||||
childrenPropSymbol.links.type = childrenTypes.length === 1 ? childrenTypes[0] :
|
||||
@@ -33842,7 +33858,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return result;
|
||||
}
|
||||
|
||||
function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode) {
|
||||
function checkJsxChildren(node: JsxElement | JsxFragment, checkMode?: CheckMode, childrenContextualType?: Type) {
|
||||
const childrenTypes: Type[] = [];
|
||||
for (const child of node.children) {
|
||||
// In React, JSX text that contains only whitespaces will be ignored so we don't want to type-check that
|
||||
@@ -33856,7 +33872,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
continue; // empty jsx expressions don't *really* count as present children
|
||||
}
|
||||
else {
|
||||
childrenTypes.push(checkExpressionForMutableLocation(child, checkMode));
|
||||
// If we have a contextual type for children, use it when checking child expressions
|
||||
if (childrenContextualType && child.kind === SyntaxKind.JsxExpression && child.expression) {
|
||||
childrenTypes.push(checkExpressionForMutableLocationWithContextualType(child.expression, childrenContextualType));
|
||||
}
|
||||
else {
|
||||
childrenTypes.push(checkExpressionForMutableLocation(child, checkMode));
|
||||
}
|
||||
}
|
||||
}
|
||||
return childrenTypes;
|
||||
@@ -37418,7 +37440,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const jsxFragmentFactoryName = getJsxNamespace(node);
|
||||
|
||||
// #38720/60122, allow null as jsxFragmentFactory
|
||||
const shouldResolveFactoryReference = (compilerOptions.jsx === JsxEmit.React || compilerOptions.jsxFragmentFactory !== undefined) && jsxFragmentFactoryName !== "null";
|
||||
const shouldResolveFactoryReference = (compilerOptions.jsx === JsxEmit.React || compilerOptions.jsx === JsxEmit.ReactJSX || compilerOptions.jsx === JsxEmit.ReactJSXDev || compilerOptions.jsxFragmentFactory !== undefined) && jsxFragmentFactoryName !== "null";
|
||||
if (!shouldResolveFactoryReference) return sourceFileLinks.jsxFragmentType = anyType;
|
||||
|
||||
const shouldModuleRefErr = compilerOptions.jsx !== JsxEmit.Preserve && compilerOptions.jsx !== JsxEmit.ReactNative;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//// [tests/cases/compiler/jsxFragmentChildrenCheck.tsx] ////
|
||||
|
||||
//// [index.d.ts]
|
||||
export const jsx: any;
|
||||
export const jsxs: any;
|
||||
|
||||
type JsxElement =
|
||||
| JsxElementArray
|
||||
| undefined
|
||||
| string
|
||||
| ((arg: { foo: "bar" }) => void);
|
||||
interface JsxElementArray extends Array<JsxElement> {}
|
||||
|
||||
interface FragmentProps {
|
||||
children?: JsxElement;
|
||||
}
|
||||
|
||||
export const Fragment: (props: FragmentProps) => any;
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
div: any;
|
||||
span: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//// [index.tsx]
|
||||
import { Fragment } from "@test/jsx-runtime";
|
||||
|
||||
// This should pass - using explicit Fragment
|
||||
<Fragment>
|
||||
{"ok"}
|
||||
{({ foo }) => "also ok"}
|
||||
</Fragment>;
|
||||
|
||||
// This should also pass - using <> syntax should be equivalent
|
||||
<>
|
||||
{"ok"}
|
||||
{({ foo }) => "should also be ok"}
|
||||
</>;
|
||||
|
||||
|
||||
//// [index.js]
|
||||
import { jsxs as _jsxs, Fragment as _Fragment } from "@test/jsx-runtime";
|
||||
import { Fragment } from "@test/jsx-runtime";
|
||||
// This should pass - using explicit Fragment
|
||||
_jsxs(Fragment, { children: ["ok", function (_a) {
|
||||
var foo = _a.foo;
|
||||
return "also ok";
|
||||
}] });
|
||||
// This should also pass - using <> syntax should be equivalent
|
||||
_jsxs(_Fragment, { children: ["ok", function (_a) {
|
||||
var foo = _a.foo;
|
||||
return "should also be ok";
|
||||
}] });
|
||||
@@ -0,0 +1,80 @@
|
||||
//// [tests/cases/compiler/jsxFragmentChildrenCheck.tsx] ////
|
||||
|
||||
=== node_modules/@test/jsx-runtime/index.d.ts ===
|
||||
export const jsx: any;
|
||||
>jsx : Symbol(jsx, Decl(index.d.ts, 0, 12))
|
||||
|
||||
export const jsxs: any;
|
||||
>jsxs : Symbol(jsxs, Decl(index.d.ts, 1, 12))
|
||||
|
||||
type JsxElement =
|
||||
>JsxElement : Symbol(JsxElement, Decl(index.d.ts, 1, 23))
|
||||
|
||||
| JsxElementArray
|
||||
>JsxElementArray : Symbol(JsxElementArray, Decl(index.d.ts, 7, 36))
|
||||
|
||||
| undefined
|
||||
| string
|
||||
| ((arg: { foo: "bar" }) => void);
|
||||
>arg : Symbol(arg, Decl(index.d.ts, 7, 6))
|
||||
>foo : Symbol(foo, Decl(index.d.ts, 7, 12))
|
||||
|
||||
interface JsxElementArray extends Array<JsxElement> {}
|
||||
>JsxElementArray : Symbol(JsxElementArray, Decl(index.d.ts, 7, 36))
|
||||
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))
|
||||
>JsxElement : Symbol(JsxElement, Decl(index.d.ts, 1, 23))
|
||||
|
||||
interface FragmentProps {
|
||||
>FragmentProps : Symbol(FragmentProps, Decl(index.d.ts, 8, 54))
|
||||
|
||||
children?: JsxElement;
|
||||
>children : Symbol(FragmentProps.children, Decl(index.d.ts, 10, 25))
|
||||
>JsxElement : Symbol(JsxElement, Decl(index.d.ts, 1, 23))
|
||||
}
|
||||
|
||||
export const Fragment: (props: FragmentProps) => any;
|
||||
>Fragment : Symbol(Fragment, Decl(index.d.ts, 14, 12))
|
||||
>props : Symbol(props, Decl(index.d.ts, 14, 24))
|
||||
>FragmentProps : Symbol(FragmentProps, Decl(index.d.ts, 8, 54))
|
||||
|
||||
declare global {
|
||||
>global : Symbol(global, Decl(index.d.ts, 14, 53))
|
||||
|
||||
namespace JSX {
|
||||
>JSX : Symbol(JSX, Decl(index.d.ts, 16, 16))
|
||||
|
||||
interface IntrinsicElements {
|
||||
>IntrinsicElements : Symbol(IntrinsicElements, Decl(index.d.ts, 17, 17))
|
||||
|
||||
div: any;
|
||||
>div : Symbol(IntrinsicElements.div, Decl(index.d.ts, 18, 33))
|
||||
|
||||
span: any;
|
||||
>span : Symbol(IntrinsicElements.span, Decl(index.d.ts, 19, 15))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
=== index.tsx ===
|
||||
import { Fragment } from "@test/jsx-runtime";
|
||||
>Fragment : Symbol(Fragment, Decl(index.tsx, 0, 8))
|
||||
|
||||
// This should pass - using explicit Fragment
|
||||
<Fragment>
|
||||
>Fragment : Symbol(Fragment, Decl(index.tsx, 0, 8))
|
||||
|
||||
{"ok"}
|
||||
{({ foo }) => "also ok"}
|
||||
>foo : Symbol(foo, Decl(index.tsx, 5, 5))
|
||||
|
||||
</Fragment>;
|
||||
>Fragment : Symbol(Fragment, Decl(index.tsx, 0, 8))
|
||||
|
||||
// This should also pass - using <> syntax should be equivalent
|
||||
<>
|
||||
{"ok"}
|
||||
{({ foo }) => "should also be ok"}
|
||||
>foo : Symbol(foo, Decl(index.tsx, 11, 5))
|
||||
|
||||
</>;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
//// [tests/cases/compiler/jsxFragmentChildrenCheck.tsx] ////
|
||||
|
||||
=== node_modules/@test/jsx-runtime/index.d.ts ===
|
||||
export const jsx: any;
|
||||
>jsx : any
|
||||
|
||||
export const jsxs: any;
|
||||
>jsxs : any
|
||||
|
||||
type JsxElement =
|
||||
>JsxElement : JsxElement
|
||||
> : ^^^^^^^^^^
|
||||
|
||||
| JsxElementArray
|
||||
| undefined
|
||||
| string
|
||||
| ((arg: { foo: "bar" }) => void);
|
||||
>arg : { foo: "bar"; }
|
||||
> : ^^^^^^^ ^^^
|
||||
>foo : "bar"
|
||||
> : ^^^^^
|
||||
|
||||
interface JsxElementArray extends Array<JsxElement> {}
|
||||
|
||||
interface FragmentProps {
|
||||
children?: JsxElement;
|
||||
>children : JsxElement
|
||||
> : ^^^^^^^^^^
|
||||
}
|
||||
|
||||
export const Fragment: (props: FragmentProps) => any;
|
||||
>Fragment : (props: FragmentProps) => any
|
||||
> : ^ ^^ ^^^^^
|
||||
>props : FragmentProps
|
||||
> : ^^^^^^^^^^^^^
|
||||
|
||||
declare global {
|
||||
>global : any
|
||||
> : ^^^
|
||||
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
div: any;
|
||||
>div : any
|
||||
|
||||
span: any;
|
||||
>span : any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
=== index.tsx ===
|
||||
import { Fragment } from "@test/jsx-runtime";
|
||||
>Fragment : (props: import("node_modules/@test/jsx-runtime/index").FragmentProps) => any
|
||||
> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
// This should pass - using explicit Fragment
|
||||
<Fragment>
|
||||
><Fragment> {"ok"} {({ foo }) => "also ok"}</Fragment> : error
|
||||
>Fragment : (props: import("node_modules/@test/jsx-runtime/index").FragmentProps) => any
|
||||
> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
{"ok"}
|
||||
>"ok" : "ok"
|
||||
> : ^^^^
|
||||
|
||||
{({ foo }) => "also ok"}
|
||||
>({ foo }) => "also ok" : ({ foo }: { foo: "bar"; }) => string
|
||||
> : ^ ^^^^^^^^^ ^^^^^^^^^^^^^^
|
||||
>foo : "bar"
|
||||
> : ^^^^^
|
||||
>"also ok" : "also ok"
|
||||
> : ^^^^^^^^^
|
||||
|
||||
</Fragment>;
|
||||
>Fragment : (props: import("node_modules/@test/jsx-runtime/index").FragmentProps) => any
|
||||
> : ^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
// This should also pass - using <> syntax should be equivalent
|
||||
<>
|
||||
><> {"ok"} {({ foo }) => "should also be ok"}</> : any
|
||||
|
||||
{"ok"}
|
||||
>"ok" : "ok"
|
||||
> : ^^^^
|
||||
|
||||
{({ foo }) => "should also be ok"}
|
||||
>({ foo }) => "should also be ok" : ({ foo }: { foo: "bar"; }) => string
|
||||
> : ^ ^^^^^^^^^ ^^^^^^^^^^^^^^
|
||||
>foo : "bar"
|
||||
> : ^^^^^
|
||||
>"should also be ok" : "should also be ok"
|
||||
> : ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
</>;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// @jsx: react-jsx
|
||||
// @strict: true
|
||||
// @module: esnext
|
||||
// @skipLibCheck: true
|
||||
// @jsxImportSource: @test
|
||||
|
||||
// @filename: node_modules/@test/jsx-runtime/index.d.ts
|
||||
export const jsx: any;
|
||||
export const jsxs: any;
|
||||
|
||||
type JsxElement =
|
||||
| JsxElementArray
|
||||
| undefined
|
||||
| string
|
||||
| ((arg: { foo: "bar" }) => void);
|
||||
interface JsxElementArray extends Array<JsxElement> {}
|
||||
|
||||
interface FragmentProps {
|
||||
children?: JsxElement;
|
||||
}
|
||||
|
||||
export const Fragment: (props: FragmentProps) => any;
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
div: any;
|
||||
span: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @filename: index.tsx
|
||||
import { Fragment } from "@test/jsx-runtime";
|
||||
|
||||
// This should pass - using explicit Fragment
|
||||
<Fragment>
|
||||
{"ok"}
|
||||
{({ foo }) => "also ok"}
|
||||
</Fragment>;
|
||||
|
||||
// This should also pass - using <> syntax should be equivalent
|
||||
<>
|
||||
{"ok"}
|
||||
{({ foo }) => "should also be ok"}
|
||||
</>;
|
||||
Reference in New Issue
Block a user