Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33791,7 +33791,20 @@ 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 fragment children before checking them
let childrenContextualType: Type | undefined;
if (isJsxOpeningFragment(openingLikeElement) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
// 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) {
const contextualType = getTypeOfFirstParameterOfSignature(signatures[0]);
childrenContextualType = contextualType && getTypeOfPropertyOfContextualType(contextualType, jsxChildrenPropertyName);
}
}

// Check children with contextual type (only for fragments)
const childrenTypes: Type[] = checkJsxChildren(parent, checkMode, childrenContextualType);

if (!hasSpreadAnyType && jsxChildrenPropertyName && jsxChildrenPropertyName !== "") {
// Error if there is a attribute named "children" explicitly specified and children element.
Expand Down Expand Up @@ -33842,7 +33855,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
Expand All @@ -33856,7 +33869,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;
Expand Down Expand Up @@ -37418,15 +37437,19 @@ 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;
// When using react-jsx/react-jsxdev, the fragment comes from the jsx-runtime module as "Fragment" export
// Use "Fragment" in error message instead of the default "React" namespace
const isModernJsx = compilerOptions.jsx === JsxEmit.ReactJSX || compilerOptions.jsx === JsxEmit.ReactJSXDev;
const fragmentFactoryNameForError = isModernJsx ? ReactNames.Fragment : jsxFragmentFactoryName;
const jsxFactoryRefErr = diagnostics ? Diagnostics.Using_JSX_fragments_requires_fragment_factory_0_to_be_in_scope_but_it_could_not_be_found : undefined;
const jsxFactorySymbol = getJsxNamespaceContainerForImplicitImport(node) ??
resolveName(
node,
jsxFragmentFactoryName,
fragmentFactoryNameForError,
shouldModuleRefErr ? SymbolFlags.Value : SymbolFlags.Value & ~SymbolFlags.Enum,
/*nameNotFoundMessage*/ jsxFactoryRefErr,
/*isUse*/ true,
Expand Down
57 changes: 57 additions & 0 deletions tests/baselines/reference/jsxFragmentChildrenCheck.js
Original file line number Diff line number Diff line change
@@ -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";
}] });
80 changes: 80 additions & 0 deletions tests/baselines/reference/jsxFragmentChildrenCheck.symbols
Original file line number Diff line number Diff line change
@@ -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))

</>;

96 changes: 96 additions & 0 deletions tests/baselines/reference/jsxFragmentChildrenCheck.types
Original file line number Diff line number Diff line change
@@ -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"
> : ^^^^^^^^^^^^^^^^^^^

</>;

Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
jsxFragmentFactoryReference.tsx(3,9): error TS2875: This JSX tag requires the module path 'react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
jsxFragmentFactoryReference.tsx(3,9): error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.


==== jsxFragmentFactoryReference.tsx (1 errors) ====
==== jsxFragmentFactoryReference.tsx (2 errors) ====
export class LoggedOut {
content = () => (
<></>
~~
!!! error TS2875: This JSX tag requires the module path 'react/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
~~
!!! error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.
)
}

Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
jsxFragmentFactoryReference.tsx(3,9): error TS2875: This JSX tag requires the module path 'react/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
jsxFragmentFactoryReference.tsx(3,9): error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.


==== jsxFragmentFactoryReference.tsx (1 errors) ====
==== jsxFragmentFactoryReference.tsx (2 errors) ====
export class LoggedOut {
content = () => (
<></>
~~
!!! error TS2875: This JSX tag requires the module path 'react/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
~~
!!! error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.
)
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
jsxJsxsCjsTransformCustomImport.tsx(2,11): error TS2875: This JSX tag requires the module path 'preact/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
jsxJsxsCjsTransformCustomImport.tsx(2,11): error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.


==== jsxJsxsCjsTransformCustomImport.tsx (1 errors) ====
==== jsxJsxsCjsTransformCustomImport.tsx (2 errors) ====
/// <reference path="/.lib/react16.d.ts" />
const a = <>
~~
!!! error TS2875: This JSX tag requires the module path 'preact/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
~~
!!! error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.
<p></p>
text
<div className="foo"></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
jsxJsxsCjsTransformCustomImport.tsx(2,11): error TS2875: This JSX tag requires the module path 'preact/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
jsxJsxsCjsTransformCustomImport.tsx(2,11): error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.


==== jsxJsxsCjsTransformCustomImport.tsx (1 errors) ====
==== jsxJsxsCjsTransformCustomImport.tsx (2 errors) ====
/// <reference path="/.lib/react16.d.ts" />
const a = <>
~~
!!! error TS2875: This JSX tag requires the module path 'preact/jsx-dev-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
~~
!!! error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.
<p></p>
text
<div className="foo"></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
preact.tsx(3,11): error TS2875: This JSX tag requires the module path 'preact/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
preact.tsx(3,11): error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.


==== react.tsx (0 errors) ====
Expand All @@ -12,12 +13,14 @@ preact.tsx(3,11): error TS2875: This JSX tag requires the module path 'preact/js
</>

export {};
==== preact.tsx (1 errors) ====
==== preact.tsx (2 errors) ====
/// <reference path="/.lib/react16.d.ts" />
/* @jsxImportSource preact */
const a = <>
~~
!!! error TS2875: This JSX tag requires the module path 'preact/jsx-runtime' to exist, but none could be found. Make sure you have types for the appropriate package installed.
~~
!!! error TS2879: Using JSX fragments requires fragment factory 'Fragment' to be in scope, but it could not be found.
<p></p>
text
<div className="foo"></div>
Expand Down
Loading