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

Lookup return type of factory function for JSX expression return types #29818

Closed
wants to merge 7 commits into from
Closed
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
88 changes: 75 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,7 @@ namespace ts {
if (jsxPragma) {
const chosenpragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma;
file.localJsxFactory = parseIsolatedEntityName(chosenpragma.arguments.factory, languageVersion);
visitNode(file.localJsxFactory, markAsSynthetic);
if (file.localJsxFactory) {
return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText;
}
Expand All @@ -898,15 +899,25 @@ namespace ts {
_jsxNamespace = "React" as __String;
if (compilerOptions.jsxFactory) {
_jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion);
visitNode(_jsxFactoryEntity, markAsSynthetic);
if (_jsxFactoryEntity) {
_jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText;
}
}
else if (compilerOptions.reactNamespace) {
_jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace);
}
if (!_jsxFactoryEntity) {
_jsxFactoryEntity = createQualifiedName(createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement");
}
}
return _jsxNamespace;

function markAsSynthetic(node: Node): VisitResult<Node> {
node.pos = -1;
node.end = -1;
return visitEachChild(node, markAsSynthetic, nullTransformationContext);
}
}

function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) {
Expand Down Expand Up @@ -2457,7 +2468,7 @@ namespace ts {
let symbol: Symbol | undefined;
if (name.kind === SyntaxKind.Identifier) {
const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name));
const symbolFromJSPrototype = isInJSFile(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined;
const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined;
symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true);
if (!symbol) {
return symbolFromJSPrototype;
Expand Down Expand Up @@ -16260,7 +16271,8 @@ namespace ts {
case "AsyncIterator":
return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later;
default:
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
// Check for `parent` to handle synthetic nodes (like this jsx namespace from the jsxFactory compiler option)
if (node.parent && node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer;
}
else {
Expand Down Expand Up @@ -19967,9 +19979,9 @@ namespace ts {
checkJsxOpeningLikeElementOrOpeningFragment(node);
}

function checkJsxSelfClosingElement(node: JsxSelfClosingElement, _checkMode: CheckMode | undefined): Type {
function checkJsxSelfClosingElement(node: JsxSelfClosingElement): Type {
checkNodeDeferred(node);
return getJsxElementTypeAt(node) || anyType;
return getFactoryReturnTypeForJsxOpeningLikeElement(node);
}

function checkJsxElementDeferred(node: JsxElement) {
Expand All @@ -19987,10 +19999,10 @@ namespace ts {
checkJsxChildren(node);
}

function checkJsxElement(node: JsxElement, _checkMode: CheckMode | undefined): Type {
function checkJsxElement(node: JsxElement): Type {
checkNodeDeferred(node);

return getJsxElementTypeAt(node) || anyType;
return getFactoryReturnTypeForJsxOpeningLikeElement(node.openingElement);
}

function checkJsxFragment(node: JsxFragment): Type {
Expand All @@ -20003,7 +20015,7 @@ namespace ts {
}

checkJsxChildren(node);
return getJsxElementTypeAt(node) || anyType;
return getFactoryReturnTypeForJsxOpeningLikeElement(node.openingFragment);
}

/**
Expand Down Expand Up @@ -20114,6 +20126,7 @@ namespace ts {
// Fake up a property declaration for the children
childrenPropSymbol.valueDeclaration = createPropertySignature(/*modifiers*/ undefined, unescapeLeadingUnderscores(jsxChildrenPropertyName), /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined);
childrenPropSymbol.valueDeclaration.parent = attributes;
childrenPropSymbol.valueDeclaration.original = parent;
childrenPropSymbol.valueDeclaration.symbol = childrenPropSymbol;
const childPropMap = createSymbolTable();
childPropMap.set(jsxChildrenPropertyName, childrenPropSymbol);
Expand Down Expand Up @@ -20429,6 +20442,49 @@ namespace ts {
}
}

function getFactoryReturnTypeForJsxOpeningLikeElement(node: JsxOpeningLikeElement | JsxOpeningFragment) {
const reactRefErr = diagnostics && compilerOptions.jsx === JsxEmit.React ? Diagnostics.Cannot_find_name_0 : undefined;
const reactNamespace = getJsxNamespace(node);
const reactLocation = isJsxOpeningLikeElement(node) ? node.tagName : node;
const reactSym = resolveName(reactLocation, reactNamespace, SymbolFlags.Value, reactRefErr, reactNamespace, /*isUse*/ true);
const factoryEntity = getJsxFactoryEntity(reactLocation);
if (!factoryEntity) {
return getJsxElementTypeAt(node) || errorType;
}
const factorySymbol = resolveEntityName(factoryEntity, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontUseResolveAlias*/ false, reactLocation);
if (reactSym && factorySymbol && factorySymbol !== unknownSymbol) {
// Mark local symbol as referenced here because it might not have been marked
// if jsx emit was not react as there wont be error being emitted
reactSym.isReferenced = SymbolFlags.All;

// If react symbol is alias, mark it as referenced
if (reactSym.flags & SymbolFlags.Alias && !isConstEnumOrConstEnumOnlyModule(resolveAlias(reactSym))) {
markAliasSymbolAsReferenced(reactSym);
}
const links = getNodeLinks(node);
if (!links.jsxFactoryCall) {
const factoryExpression = createSyntheticExpression(node, getTypeOfSymbol(factorySymbol));
const children = isJsxOpeningElement(node) ? node.parent.children : emptyArray;
links.jsxFactoryCall = createCall(factoryExpression, /*typeArguments*/ undefined, [
isJsxOpeningFragment(node)
? createSyntheticExpression(node, reactSym.exports ? getTypeOfSymbol(getSymbol(getExportsOfSymbol(reactSym), "Fragment" as __String, SymbolFlags.Value) || unknownSymbol) : emptyObjectType)
: isJsxIntrinsicIdentifier(node.tagName) ? createSyntheticExpression(node.tagName, getLiteralType(idText((node.tagName as Identifier)))) : node.tagName,
isJsxOpeningFragment(node) ? createSyntheticExpression(node, nullType) : createSyntheticExpression(node.attributes, checkMode => checkExpression(node.attributes, checkMode)),
...mapDefined(children, c => isJsxText(c) && c.containsOnlyTriviaWhiteSpaces ? undefined : createSyntheticExpression(c, isJsxText(c) ? stringType : (checkMode => checkExpression(c, checkMode))))
]);
links.jsxFactoryCall.pos = node.pos;
links.jsxFactoryCall.end = node.end;
links.jsxFactoryCall.parent = node.parent;
}
const result = getReturnTypeOfSignature(getResolvedSignature(links.jsxFactoryCall));
if (result === errorType) {
return getJsxElementTypeAt(node) || errorType;
}
return result;
}
return getJsxElementTypeAt(node) || errorType;
}

function checkJsxOpeningLikeElementOrOpeningFragment(node: JsxOpeningLikeElement | JsxOpeningFragment) {
const isNodeOpeningLikeElement = isJsxOpeningLikeElement(node);

Expand Down Expand Up @@ -21513,7 +21569,7 @@ namespace ts {
// We are inferring from a spread expression in the last argument position, i.e. both the parameter
// and the argument are ...x forms.
return arg.kind === SyntaxKind.SyntheticExpression ?
createArrayType((<SyntheticExpression>arg).type) :
createArrayType(checkExpressionWithContextualType(<SyntheticExpression>arg, restType, context, CheckMode.Normal)) :
getArrayifiedType(checkExpressionWithContextualType((<SpreadElement>arg).expression, restType, context, CheckMode.Normal));
}
}
Expand Down Expand Up @@ -21694,8 +21750,9 @@ namespace ts {
}
}

function createSyntheticExpression(parent: Node, type: Type, isSpread?: boolean) {
function createSyntheticExpression(parent: Node, type: Type | ((mode: CheckMode | undefined) => Type), isSpread?: boolean) {
const result = <SyntheticExpression>createNode(SyntaxKind.SyntheticExpression, parent.pos, parent.end);
result.original = parent;
result.parent = parent;
result.type = type;
result.isSpread = isSpread || false;
Expand Down Expand Up @@ -25445,13 +25502,14 @@ namespace ts {
case SyntaxKind.YieldExpression:
return checkYieldExpression(<YieldExpression>node);
case SyntaxKind.SyntheticExpression:
return (<SyntheticExpression>node).type;
const cbOrType = (<SyntheticExpression>node).type;
return typeof cbOrType === "function" ? cbOrType(checkMode) : cbOrType;
case SyntaxKind.JsxExpression:
return checkJsxExpression(<JsxExpression>node, checkMode);
case SyntaxKind.JsxElement:
return checkJsxElement(<JsxElement>node, checkMode);
return checkJsxElement(<JsxElement>node);
case SyntaxKind.JsxSelfClosingElement:
return checkJsxSelfClosingElement(<JsxSelfClosingElement>node, checkMode);
return checkJsxSelfClosingElement(<JsxSelfClosingElement>node);
case SyntaxKind.JsxFragment:
return checkJsxFragment(<JsxFragment>node);
case SyntaxKind.JsxAttributes:
Expand Down Expand Up @@ -31972,6 +32030,10 @@ namespace ts {
return literalTypeToNode(<FreshableType>type, node, tracker);
}

function getJsxFactoryEntity(location?: Node): EntityName | undefined {
return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity;
}

function createResolver(): EmitResolver {
// this variable and functions that use it are deliberately moved here from the outer scope
// to avoid scope pollution
Expand Down Expand Up @@ -32043,7 +32105,7 @@ namespace ts {
const symbol = node && getSymbolOfNode(node);
return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late);
},
getJsxFactoryEntity: location => location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity,
getJsxFactoryEntity,
getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations {
accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217
const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor;
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1404,7 +1404,7 @@ namespace ts {
export interface SyntheticExpression extends Expression {
kind: SyntaxKind.SyntheticExpression;
isSpread: boolean;
type: Type;
type: Type | ((mode: number | undefined) => Type);
}

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

export const enum TypeFlags {
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,11 @@ namespace ts {
export function getSourceFileOfNode(node: Node): SourceFile;
export function getSourceFileOfNode(node: Node | undefined): SourceFile | undefined;
export function getSourceFileOfNode(node: Node): SourceFile {
const root = node;
while (node && node.kind !== SyntaxKind.SourceFile) {
node = node.parent;
}
return <SourceFile>node;
return <SourceFile>node || (root && root.original && getSourceFileOfNode(root.original));
}

export function isStatementWithLocals(node: Node) {
Expand Down Expand Up @@ -938,6 +939,9 @@ namespace ts {
}

export function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpan {
while (node.original) {
node = node.original;
}
let errorNode: Node | undefined = node;
switch (node.kind) {
case SyntaxKind.SourceFile:
Expand Down Expand Up @@ -3038,6 +3042,7 @@ namespace ts {
case SyntaxKind.TemplateExpression:
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.OmittedExpression:
case SyntaxKind.SyntheticExpression:
return 20;

default:
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ declare namespace ts {
export interface SyntheticExpression extends Expression {
kind: SyntaxKind.SyntheticExpression;
isSpread: boolean;
type: Type;
type: Type | ((mode: number | undefined) => Type);
}
export type ExponentiationOperator = SyntaxKind.AsteriskAsteriskToken;
export type MultiplicativeOperator = SyntaxKind.AsteriskToken | SyntaxKind.SlashToken | SyntaxKind.PercentToken;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ declare namespace ts {
export interface SyntheticExpression extends Expression {
kind: SyntaxKind.SyntheticExpression;
isSpread: boolean;
type: Type;
type: Type | ((mode: number | undefined) => Type);
}
export type ExponentiationOperator = SyntaxKind.AsteriskAsteriskToken;
export type MultiplicativeOperator = SyntaxKind.AsteriskToken | SyntaxKind.SlashToken | SyntaxKind.PercentToken;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
tests/cases/compiler/index.tsx(7,23): error TS2554: Expected 1 arguments, but got 2.


==== tests/cases/compiler/index.tsx (1 errors) ====
declare global {
function __make (params: object): any;
}

declare var __foot: any;

const thing = <__foot />;

!!! error TS2554: Expected 1 arguments, but got 2.

export {}

Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ declare var __foot: any;
>__foot : any

const thing = <__foot />;
>thing : error
><__foot /> : error
>thing : any
><__foot /> : any
>__foot : any

export {}
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/inlineJsxFactoryDeclarations.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ declare global {
}
}
}
export function dom(): void;
export function otherdom(): void;
export function createElement(): void;
export function dom(...args: any[]): void;
export function otherdom(...args: any[]): void;
export function createElement(...args: any[]): void;
export { dom as default };
//// [otherreacty.tsx]
/** @jsx React.createElement */
Expand Down
13 changes: 8 additions & 5 deletions tests/baselines/reference/inlineJsxFactoryDeclarations.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ declare global {
}
}
}
export function dom(): void;
export function dom(...args: any[]): void;
>dom : Symbol(dom, Decl(renderer.d.ts, 6, 1))
>args : Symbol(args, Decl(renderer.d.ts, 7, 20))

export function otherdom(): void;
>otherdom : Symbol(otherdom, Decl(renderer.d.ts, 7, 28))
export function otherdom(...args: any[]): void;
>otherdom : Symbol(otherdom, Decl(renderer.d.ts, 7, 42))
>args : Symbol(args, Decl(renderer.d.ts, 8, 25))

export function createElement(): void;
>createElement : Symbol(createElement, Decl(renderer.d.ts, 8, 33))
export function createElement(...args: any[]): void;
>createElement : Symbol(createElement, Decl(renderer.d.ts, 8, 47))
>args : Symbol(args, Decl(renderer.d.ts, 9, 30))

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