Skip to content

Commit 447e0e8

Browse files
committed
Lookup return type of factory function for JSX expression return types
1 parent f7c4fef commit 447e0e8

File tree

49 files changed

+314
-192
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+314
-192
lines changed

src/compiler/checker.ts

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ namespace ts {
739739
if (jsxPragma) {
740740
const chosenpragma: any = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma; // TODO: GH#18217
741741
file.localJsxFactory = parseIsolatedEntityName(chosenpragma.arguments.factory, languageVersion);
742+
visitNode(file.localJsxFactory, markAsSynthetic);
742743
if (file.localJsxFactory) {
743744
return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText;
744745
}
@@ -749,6 +750,7 @@ namespace ts {
749750
_jsxNamespace = "React" as __String;
750751
if (compilerOptions.jsxFactory) {
751752
_jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion);
753+
visitNode(_jsxFactoryEntity, markAsSynthetic);
752754
if (_jsxFactoryEntity) {
753755
_jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText;
754756
}
@@ -758,6 +760,12 @@ namespace ts {
758760
}
759761
}
760762
return _jsxNamespace;
763+
764+
function markAsSynthetic(node: Node): VisitResult<Node> {
765+
node.pos = -1;
766+
node.end = -1;
767+
return visitEachChild(node, markAsSynthetic, nullTransformationContext);
768+
}
761769
}
762770

763771
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) {
@@ -2159,7 +2167,7 @@ namespace ts {
21592167
let symbol: Symbol | undefined;
21602168
if (name.kind === SyntaxKind.Identifier) {
21612169
const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name).escapedText);
2162-
const symbolFromJSPrototype = isInJSFile(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined;
2170+
const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined;
21632171
symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true);
21642172
if (!symbol) {
21652173
return symbolFromJSPrototype;
@@ -18535,9 +18543,9 @@ namespace ts {
1853518543
checkJsxOpeningLikeElementOrOpeningFragment(node);
1853618544
}
1853718545

18538-
function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type {
18546+
function checkJsxSelfClosingElement(node: JsxSelfClosingElement): Type {
1853918547
checkNodeDeferred(node);
18540-
return getJsxElementTypeAt(node) || anyType;
18548+
return getFactoryReturnTypeForJsxOpeningLikeElement(node);
1854118549
}
1854218550

1854318551
function checkJsxElementDeferred(node: JsxElement) {
@@ -18555,10 +18563,10 @@ namespace ts {
1855518563
checkJsxChildren(node);
1855618564
}
1855718565

18558-
function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type {
18566+
function checkJsxElement(node: JsxElement): Type {
1855918567
checkNodeDeferred(node);
1856018568

18561-
return getJsxElementTypeAt(node) || anyType;
18569+
return getFactoryReturnTypeForJsxOpeningLikeElement(node.openingElement);
1856218570
}
1856318571

1856418572
function checkJsxFragment(node: JsxFragment): Type {
@@ -18571,7 +18579,7 @@ namespace ts {
1857118579
}
1857218580

1857318581
checkJsxChildren(node);
18574-
return getJsxElementTypeAt(node) || anyType;
18582+
return getFactoryReturnTypeForJsxOpeningLikeElement(node.openingFragment);
1857518583
}
1857618584

1857718585
/**
@@ -18999,6 +19007,49 @@ namespace ts {
1899919007
}
1900019008
}
1900119009

19010+
function getFactoryReturnTypeForJsxOpeningLikeElement(node: JsxOpeningLikeElement | JsxOpeningFragment) {
19011+
const reactRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
19012+
const reactNamespace = getJsxNamespace(node);
19013+
const reactLocation = isJsxOpeningLikeElement(node) ? node.tagName : node;
19014+
const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
19015+
const factoryEntity = getJsxFactoryEntity(reactLocation);
19016+
if (!factoryEntity) {
19017+
return getJsxElementTypeAt(node) || errorType;
19018+
}
19019+
const factorySymbol = resolveEntityName(factoryEntity, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontUseResolveAlias*/ false, reactLocation);
19020+
if (reactSym && factorySymbol && factorySymbol !== unknownSymbol) {
19021+
// Mark local symbol as referenced here because it might not have been marked
19022+
// if jsx emit was not react as there wont be error being emitted
19023+
reactSym.isReferenced = SymbolFlags.All;
19024+
19025+
// If react symbol is alias, mark it as referenced
19026+
if (reactSym.flags & SymbolFlags.Alias && !isConstEnumOrConstEnumOnlyModule(resolveAlias(reactSym))) {
19027+
markAliasSymbolAsReferenced(reactSym);
19028+
}
19029+
const links = getNodeLinks(node);
19030+
if (!links.jsxFactoryCall) {
19031+
const factoryExpression = createSyntheticExpression(node, getTypeOfSymbol(factorySymbol));
19032+
const children = isJsxOpeningElement(node) ? node.parent.children : emptyArray;
19033+
links.jsxFactoryCall = createCall(factoryExpression, /*typeArguments*/ undefined, [
19034+
isJsxOpeningFragment(node)
19035+
? createSyntheticExpression(node, reactSym.exports ? getTypeOfSymbol(getSymbol(getExportsOfSymbol(reactSym), "Fragment" as __String, SymbolFlags.Value) || unknownSymbol) : emptyObjectType)
19036+
: isJsxIntrinsicIdentifier(node.tagName) ? createSyntheticExpression(node.tagName, getLiteralType(idText((node.tagName as Identifier)))) : node.tagName,
19037+
isJsxOpeningFragment(node) ? createSyntheticExpression(node, nullType) : createSyntheticExpression(node.attributes, checkMode => checkExpression(node.attributes, checkMode)),
19038+
...mapDefined(children, c => isJsxText(c) && c.containsOnlyWhiteSpaces ? undefined : createSyntheticExpression(c, isJsxText(c) ? stringType : (checkMode => checkExpression(c, checkMode))))
19039+
]);
19040+
links.jsxFactoryCall.pos = node.pos;
19041+
links.jsxFactoryCall.end = node.end;
19042+
links.jsxFactoryCall.parent = node.parent;
19043+
}
19044+
const result = getReturnTypeOfSignature(getResolvedSignature(links.jsxFactoryCall));
19045+
if (result === errorType) {
19046+
return getJsxElementTypeAt(node) || errorType;
19047+
}
19048+
return result;
19049+
}
19050+
return getJsxElementTypeAt(node) || errorType;
19051+
}
19052+
1900219053
function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) {
1900319054
const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node);
1900419055

@@ -20038,7 +20089,7 @@ namespace ts {
2003820089
// We are inferring from a spread expression in the last argument position, i.e. both the parameter
2003920090
// and the argument are ...x forms.
2004020091
return arg.kind === SyntaxKind.SyntheticExpression ?
20041-
createArrayType((<SyntheticExpression>arg).type) :
20092+
createArrayType(checkExpressionWithContextualType(<SyntheticExpression>arg, restType, context)) :
2004220093
getArrayifiedType(checkExpressionWithContextualType((<SpreadElement>arg).expression, restType, context));
2004320094
}
2004420095
}
@@ -20176,7 +20227,7 @@ namespace ts {
2017620227
}
2017720228
}
2017820229

20179-
function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean) {
20230+
function createSyntheticExpression(parent: Node, type: Type | ((mode: CheckMode | undefined) => Type), isSpread?: boolean) {
2018020231
const result = <SyntheticExpression>createNode(SyntaxKind.SyntheticExpression, parent.pos, parent.end);
2018120232
result.parent = parent;
2018220233
result.type = type;
@@ -23419,13 +23470,14 @@ namespace ts {
2341923470
case SyntaxKind.YieldExpression:
2342023471
return checkYieldExpression(<YieldExpression>node);
2342123472
case SyntaxKind.SyntheticExpression:
23422-
return (<SyntheticExpression>node).type;
23473+
const cbOrType = (<SyntheticExpression>node).type;
23474+
return typeof cbOrType === "function" ? cbOrType(checkMode) : cbOrType;
2342323475
case SyntaxKind.JsxExpression:
2342423476
return checkJsxExpression(<JsxExpression>node, checkMode);
2342523477
case SyntaxKind.JsxElement:
23426-
return checkJsxElement(<JsxElement>node, checkMode);
23478+
return checkJsxElement(<JsxElement>node);
2342723479
case SyntaxKind.JsxSelfClosingElement:
23428-
return checkJsxSelfClosingElement(<JsxSelfClosingElement>node, checkMode);
23480+
return checkJsxSelfClosingElement(<JsxSelfClosingElement>node);
2342923481
case SyntaxKind.JsxFragment:
2343023482
return checkJsxFragment(<JsxFragment>node);
2343123483
case SyntaxKind.JsxAttributes:
@@ -29619,6 +29671,10 @@ namespace ts {
2961929671
return literalTypeToNode(<FreshableType>type, node, tracker);
2962029672
}
2962129673

29674+
function getJsxFactoryEntity(location?: Node): EntityName | undefined {
29675+
return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity;
29676+
}
29677+
2962229678
function createResolver(): EmitResolver {
2962329679
// this variable and functions that use it are deliberately moved here from the outer scope
2962429680
// to avoid scope pollution
@@ -29690,7 +29746,7 @@ namespace ts {
2969029746
const symbol = node && getSymbolOfNode(node);
2969129747
return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late);
2969229748
},
29693-
getJsxFactoryEntity: location => location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity,
29749+
getJsxFactoryEntity,
2969429750
getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations {
2969529751
accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217
2969629752
const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor;

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1391,7 +1391,7 @@ namespace ts {
13911391
export interface SyntheticExpression extends Expression {
13921392
kind: SyntaxKind.SyntheticExpression;
13931393
isSpread: boolean;
1394-
type: Type;
1394+
type: Type | ((mode: number | undefined) => Type);
13951395
}
13961396

13971397
// see: https://tc39.github.io/ecma262/#prod-ExponentiationExpression
@@ -3814,6 +3814,7 @@ namespace ts {
38143814
contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive
38153815
deferredNodes?: Map<Node>; // Set of nodes whose checking has been deferred
38163816
capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement
3817+
jsxFactoryCall?: CallExpression; // Manufactured call expression node for checking jsx factory call
38173818
}
38183819

38193820
export const enum TypeFlags {

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ declare namespace ts {
915915
interface SyntheticExpression extends Expression {
916916
kind: SyntaxKind.SyntheticExpression;
917917
isSpread: boolean;
918-
type: Type;
918+
type: Type | ((mode: number | undefined) => Type);
919919
}
920920
type ExponentiationOperator = SyntaxKind.AsteriskAsteriskToken;
921921
type MultiplicativeOperator = SyntaxKind.AsteriskToken | SyntaxKind.SlashToken | SyntaxKind.PercentToken;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ declare namespace ts {
915915
interface SyntheticExpression extends Expression {
916916
kind: SyntaxKind.SyntheticExpression;
917917
isSpread: boolean;
918-
type: Type;
918+
type: Type | ((mode: number | undefined) => Type);
919919
}
920920
type ExponentiationOperator = SyntaxKind.AsteriskAsteriskToken;
921921
type MultiplicativeOperator = SyntaxKind.AsteriskToken | SyntaxKind.SlashToken | SyntaxKind.PercentToken;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
tests/cases/compiler/index.tsx(7,15): error TS2554: Expected 1 arguments, but got 2.
2+
3+
4+
==== tests/cases/compiler/index.tsx (1 errors) ====
5+
declare global {
6+
function __make (params: object): any;
7+
}
8+
9+
declare var __foot: any;
10+
11+
const thing = <__foot />;
12+
~~~~~~~~~~
13+
!!! error TS2554: Expected 1 arguments, but got 2.
14+
15+
export {}
16+

tests/baselines/reference/doubleUnderscoreReactNamespace.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ declare var __foot: any;
1111
>__foot : any
1212

1313
const thing = <__foot />;
14-
>thing : error
15-
><__foot /> : error
14+
>thing : any
15+
><__foot /> : any
1616
>__foot : any
1717

1818
export {}

tests/baselines/reference/inlineJsxFactoryDeclarations.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ declare global {
88
}
99
}
1010
}
11-
export function dom(): void;
12-
export function otherdom(): void;
13-
export function createElement(): void;
11+
export function dom(...args: any[]): void;
12+
export function otherdom(...args: any[]): void;
13+
export function createElement(...args: any[]): void;
1414
export { dom as default };
1515
//// [otherreacty.tsx]
1616
/** @jsx React.createElement */

tests/baselines/reference/inlineJsxFactoryDeclarations.symbols

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@ declare global {
1313
}
1414
}
1515
}
16-
export function dom(): void;
16+
export function dom(...args: any[]): void;
1717
>dom : Symbol(dom, Decl(renderer.d.ts, 6, 1))
18+
>args : Symbol(args, Decl(renderer.d.ts, 7, 20))
1819

19-
export function otherdom(): void;
20-
>otherdom : Symbol(otherdom, Decl(renderer.d.ts, 7, 28))
20+
export function otherdom(...args: any[]): void;
21+
>otherdom : Symbol(otherdom, Decl(renderer.d.ts, 7, 42))
22+
>args : Symbol(args, Decl(renderer.d.ts, 8, 25))
2123

22-
export function createElement(): void;
23-
>createElement : Symbol(createElement, Decl(renderer.d.ts, 8, 33))
24+
export function createElement(...args: any[]): void;
25+
>createElement : Symbol(createElement, Decl(renderer.d.ts, 8, 47))
26+
>args : Symbol(args, Decl(renderer.d.ts, 9, 30))
2427

2528
export { dom as default };
2629
>dom : Symbol(dom, Decl(renderer.d.ts, 6, 1))

tests/baselines/reference/inlineJsxFactoryDeclarations.types

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,55 +9,58 @@ declare global {
99
}
1010
}
1111
}
12-
export function dom(): void;
13-
>dom : () => void
12+
export function dom(...args: any[]): void;
13+
>dom : (...args: any[]) => void
14+
>args : any[]
1415

15-
export function otherdom(): void;
16-
>otherdom : () => void
16+
export function otherdom(...args: any[]): void;
17+
>otherdom : (...args: any[]) => void
18+
>args : any[]
1719

18-
export function createElement(): void;
19-
>createElement : () => void
20+
export function createElement(...args: any[]): void;
21+
>createElement : (...args: any[]) => void
22+
>args : any[]
2023

2124
export { dom as default };
22-
>dom : () => void
23-
>default : () => void
25+
>dom : (...args: any[]) => void
26+
>default : (...args: any[]) => void
2427

2528
=== tests/cases/conformance/jsx/inline/otherreacty.tsx ===
2629
/** @jsx React.createElement */
2730
import * as React from "./renderer";
2831
>React : typeof React
2932

3033
<h></h>
31-
><h></h> : error
34+
><h></h> : void
3235
>h : any
3336
>h : any
3437

3538
=== tests/cases/conformance/jsx/inline/other.tsx ===
3639
/** @jsx h */
3740
import { dom as h } from "./renderer"
38-
>dom : () => void
39-
>h : () => void
41+
>dom : (...args: any[]) => void
42+
>h : (...args: any[]) => void
4043

4144
export const prerendered = <h></h>;
42-
>prerendered : error
43-
><h></h> : error
44-
>h : () => void
45-
>h : () => void
45+
>prerendered : void
46+
><h></h> : void
47+
>h : (...args: any[]) => void
48+
>h : (...args: any[]) => void
4649

4750
=== tests/cases/conformance/jsx/inline/othernoalias.tsx ===
4851
/** @jsx otherdom */
4952
import { otherdom } from "./renderer"
50-
>otherdom : () => void
53+
>otherdom : (...args: any[]) => void
5154

5255
export const prerendered2 = <h></h>;
53-
>prerendered2 : error
54-
><h></h> : error
56+
>prerendered2 : void
57+
><h></h> : void
5558
>h : any
5659
>h : any
5760

5861
=== tests/cases/conformance/jsx/inline/reacty.tsx ===
5962
import React from "./renderer"
60-
>React : () => void
63+
>React : (...args: any[]) => void
6164

6265
export const prerendered3 = <h></h>;
6366
>prerendered3 : error
@@ -68,10 +71,10 @@ export const prerendered3 = <h></h>;
6871
=== tests/cases/conformance/jsx/inline/index.tsx ===
6972
/** @jsx dom */
7073
import { dom } from "./renderer"
71-
>dom : () => void
74+
>dom : (...args: any[]) => void
7275

7376
<h></h>
74-
><h></h> : error
77+
><h></h> : void
7578
>h : any
7679
>h : any
7780

tests/baselines/reference/inlineJsxFactoryDeclarationsLocalTypes.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
2727
interface ElementChildrenAttribute { children: any; }
2828
}
2929
}
30-
export function dom(): dom.JSX.Element;
30+
export function dom(...args: any[]): dom.JSX.Element;
3131
==== tests/cases/conformance/jsx/inline/renderer2.d.ts (0 errors) ====
3232
export namespace predom {
3333
namespace JSX {
@@ -47,7 +47,7 @@ tests/cases/conformance/jsx/inline/index.tsx(24,48): error TS2322: Type 'import(
4747
interface ElementChildrenAttribute { children: any; }
4848
}
4949
}
50-
export function predom(): predom.JSX.Element;
50+
export function predom(...args: any[]): predom.JSX.Element;
5151
==== tests/cases/conformance/jsx/inline/component.tsx (0 errors) ====
5252
/** @jsx predom */
5353
import { predom } from "./renderer2"

0 commit comments

Comments
 (0)