Skip to content

Commit 91104d9

Browse files
weswighamtypescript-bot
authored andcommitted
Cherry-pick PR microsoft#36643 into master
Component commits: 388801e Baseline arity checks for jsx sfc tags
1 parent 7ffaf2a commit 91104d9

7 files changed

+258
-4
lines changed

src/compiler/checker.ts

+84-4
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,7 @@ namespace ts {
939939
if (jsxPragma) {
940940
const chosenpragma = isArray(jsxPragma) ? jsxPragma[0] : jsxPragma;
941941
file.localJsxFactory = parseIsolatedEntityName(chosenpragma.arguments.factory, languageVersion);
942+
visitNode(file.localJsxFactory, markAsSynthetic);
942943
if (file.localJsxFactory) {
943944
return file.localJsxNamespace = getFirstIdentifier(file.localJsxFactory).escapedText;
944945
}
@@ -949,6 +950,7 @@ namespace ts {
949950
_jsxNamespace = "React" as __String;
950951
if (compilerOptions.jsxFactory) {
951952
_jsxFactoryEntity = parseIsolatedEntityName(compilerOptions.jsxFactory, languageVersion);
953+
visitNode(_jsxFactoryEntity, markAsSynthetic);
952954
if (_jsxFactoryEntity) {
953955
_jsxNamespace = getFirstIdentifier(_jsxFactoryEntity).escapedText;
954956
}
@@ -957,7 +959,16 @@ namespace ts {
957959
_jsxNamespace = escapeLeadingUnderscores(compilerOptions.reactNamespace);
958960
}
959961
}
962+
if (!_jsxFactoryEntity) {
963+
_jsxFactoryEntity = createQualifiedName(createIdentifier(unescapeLeadingUnderscores(_jsxNamespace)), "createElement");
964+
}
960965
return _jsxNamespace;
966+
967+
function markAsSynthetic(node: Node): VisitResult<Node> {
968+
node.pos = -1;
969+
node.end = -1;
970+
return visitEachChild(node, markAsSynthetic, nullTransformationContext);
971+
}
961972
}
962973

963974
function getEmitResolver(sourceFile: SourceFile, cancellationToken: CancellationToken) {
@@ -2801,8 +2812,8 @@ namespace ts {
28012812
const namespaceMeaning = SymbolFlags.Namespace | (isInJSFile(name) ? meaning & SymbolFlags.Value : 0);
28022813
let symbol: Symbol | undefined;
28032814
if (name.kind === SyntaxKind.Identifier) {
2804-
const message = meaning === namespaceMeaning ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name));
2805-
const symbolFromJSPrototype = isInJSFile(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined;
2815+
const message = meaning === namespaceMeaning || nodeIsSynthesized(name) ? Diagnostics.Cannot_find_namespace_0 : getCannotFindNameDiagnosticForName(getFirstIdentifier(name));
2816+
const symbolFromJSPrototype = isInJSFile(name) && !nodeIsSynthesized(name) ? resolveEntityNameFromAssignmentDeclaration(name, meaning) : undefined;
28062817
symbol = resolveName(location || name, name.escapedText, meaning, ignoreErrors || symbolFromJSPrototype ? undefined : message, name, /*isUse*/ true);
28072818
if (!symbol) {
28082819
return symbolFromJSPrototype;
@@ -2845,7 +2856,7 @@ namespace ts {
28452856
throw Debug.assertNever(name, "Unknown entity name kind.");
28462857
}
28472858
Debug.assert((getCheckFlags(symbol) & CheckFlags.Instantiated) === 0, "Should never get an instantiated symbol here.");
2848-
if (isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) {
2859+
if (!nodeIsSynthesized(name) && isEntityName(name) && (symbol.flags & SymbolFlags.Alias || name.parent.kind === SyntaxKind.ExportAssignment)) {
28492860
markSymbolOfAliasDeclarationIfTypeOnly(getAliasDeclarationFromName(name), symbol, /*finalTarget*/ undefined, /*overwriteEmpty*/ true);
28502861
}
28512862
return (symbol.flags & meaning) || dontResolveAlias ? symbol : resolveAlias(symbol);
@@ -24298,6 +24309,9 @@ namespace ts {
2429824309
// can be specified by users through attributes property.
2429924310
const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node);
2430024311
const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode);
24312+
if (!checkTagNameDoesNotExpectTooManyArguments()) {
24313+
return false;
24314+
}
2430124315
return checkTypeRelatedToAndOptionallyElaborate(
2430224316
attributesType,
2430324317
paramType,
@@ -24307,6 +24321,68 @@ namespace ts {
2430724321
/*headMessage*/ undefined,
2430824322
containingMessageChain,
2430924323
errorOutputContainer);
24324+
24325+
function checkTagNameDoesNotExpectTooManyArguments(): boolean {
24326+
const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined;
24327+
if (!tagType) {
24328+
return true;
24329+
}
24330+
const tagCallSignatures = getSignaturesOfType(tagType, SignatureKind.Call);
24331+
if (!length(tagCallSignatures)) {
24332+
return true;
24333+
}
24334+
const factory = getJsxFactoryEntity(node);
24335+
if (!factory) {
24336+
return true;
24337+
}
24338+
const factorySymbol = resolveEntityName(factory, SymbolFlags.Value, /*ignoreErrors*/ true, /*dontResolveAlias*/ false, node);
24339+
if (!factorySymbol) {
24340+
return true;
24341+
}
24342+
24343+
const factoryType = getTypeOfSymbol(factorySymbol);
24344+
const callSignatures = getSignaturesOfType(factoryType, SignatureKind.Call);
24345+
if (!length(callSignatures)) {
24346+
return true;
24347+
}
24348+
24349+
let hasFirstparamSignatures = false;
24350+
// Check that _some_
24351+
for (const sig of callSignatures) {
24352+
const firstparam = getTypeAtPosition(sig, 0);
24353+
const signaturesOfParam = getSignaturesOfType(firstparam, SignatureKind.Call);
24354+
if (!length(signaturesOfParam)) continue;
24355+
for (const paramSig of signaturesOfParam) {
24356+
hasFirstparamSignatures = true;
24357+
if (hasEffectiveRestParameter(paramSig)) {
24358+
return true; // some signature has a rest param, so function components can have an aritrary number of arguments
24359+
}
24360+
const paramCount = getParameterCount(paramSig);
24361+
for (const tagSig of tagCallSignatures) {
24362+
const tagParamCount = getParameterCount(tagSig);
24363+
if (tagParamCount <= paramCount) {
24364+
return true; // some signature accepts the number of arguments the function component provides
24365+
}
24366+
}
24367+
}
24368+
}
24369+
if (!hasFirstparamSignatures) {
24370+
// Not a single signature had a first parameter which expected a signature - for back compat, and
24371+
// to guard against generic factories which won't have signatures directly, return true
24372+
return true;
24373+
}
24374+
24375+
if (reportErrors) {
24376+
const diag = createDiagnosticForNode(node.tagName, Diagnostics.Function_like_tag_expects_more_arguments_than_the_JSX_factory_can_provide);
24377+
if (errorOutputContainer && errorOutputContainer.skipLogging) {
24378+
(errorOutputContainer.errors || (errorOutputContainer.errors = [])).push(diag);
24379+
}
24380+
if (!errorOutputContainer.skipLogging) {
24381+
diagnostics.add(diag);
24382+
}
24383+
}
24384+
return false;
24385+
}
2431024386
}
2431124387

2431224388
function getSignatureApplicabilityError(
@@ -35175,6 +35251,10 @@ namespace ts {
3517535251
return literalTypeToNode(<FreshableType>type, node, tracker);
3517635252
}
3517735253

35254+
function getJsxFactoryEntity(location: Node) {
35255+
return location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity;
35256+
}
35257+
3517835258
function createResolver(): EmitResolver {
3517935259
// this variable and functions that use it are deliberately moved here from the outer scope
3518035260
// to avoid scope pollution
@@ -35246,7 +35326,7 @@ namespace ts {
3524635326
const symbol = node && getSymbolOfNode(node);
3524735327
return !!(symbol && getCheckFlags(symbol) & CheckFlags.Late);
3524835328
},
35249-
getJsxFactoryEntity: location => location ? (getJsxNamespace(location), (getSourceFileOfNode(location).localJsxFactory || _jsxFactoryEntity)) : _jsxFactoryEntity,
35329+
getJsxFactoryEntity,
3525035330
getAllAccessorDeclarations(accessor: AccessorDeclaration): AllAccessorDeclarations {
3525135331
accessor = getParseTreeNode(accessor, isGetOrSetAccessorDeclaration)!; // TODO: GH#18217
3525235332
const otherKind = accessor.kind === SyntaxKind.SetAccessor ? SyntaxKind.GetAccessor : SyntaxKind.SetAccessor;

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -4280,6 +4280,10 @@
42804280
"category": "Message",
42814281
"code": 6228
42824282
},
4283+
"Function-like tag expects more arguments than the JSX factory can provide.": {
4284+
"category": "Error",
4285+
"code": 6229
4286+
},
42834287

42844288
"Projects to reference": {
42854289
"category": "Message",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
tests/cases/compiler/jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx(13,12): error TS6229: Function-like tag expects more arguments than the JSX factory can provide.
2+
3+
4+
==== tests/cases/compiler/jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx (1 errors) ====
5+
/// <reference path="/.lib/react16.d.ts" />
6+
7+
import * as React from "react";
8+
9+
interface MyProps {
10+
x: number;
11+
}
12+
13+
function MyComp(props: MyProps, context: any, bad: any, verybad: any) {
14+
return <div></div>;
15+
}
16+
17+
const a = <MyComp x={2}/>; // using `MyComp` as a component should error - it expects more arguments than react provides
18+
~~~~~~
19+
!!! error TS6229: Function-like tag expects more arguments than the JSX factory can provide.
20+
21+
function MyComp2(props: MyProps, context: any) {
22+
return <div></div>
23+
}
24+
const b = <MyComp2 x={2}/>; // Should be OK, `context` is allowed, per react rules
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
4+
import * as React from "react";
5+
6+
interface MyProps {
7+
x: number;
8+
}
9+
10+
function MyComp(props: MyProps, context: any, bad: any, verybad: any) {
11+
return <div></div>;
12+
}
13+
14+
const a = <MyComp x={2}/>; // using `MyComp` as a component should error - it expects more arguments than react provides
15+
16+
function MyComp2(props: MyProps, context: any) {
17+
return <div></div>
18+
}
19+
const b = <MyComp2 x={2}/>; // Should be OK, `context` is allowed, per react rules
20+
21+
//// [jsxIssuesErrorWhenTagExpectsTooManyArguments.js]
22+
"use strict";
23+
/// <reference path="react16.d.ts" />
24+
exports.__esModule = true;
25+
var React = require("react");
26+
function MyComp(props, context, bad, verybad) {
27+
return React.createElement("div", null);
28+
}
29+
var a = React.createElement(MyComp, { x: 2 }); // using `MyComp` as a component should error - it expects more arguments than react provides
30+
function MyComp2(props, context) {
31+
return React.createElement("div", null);
32+
}
33+
var b = React.createElement(MyComp2, { x: 2 }); // Should be OK, `context` is allowed, per react rules
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
=== tests/cases/compiler/jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import * as React from "react";
5+
>React : Symbol(React, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 2, 6))
6+
7+
interface MyProps {
8+
>MyProps : Symbol(MyProps, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 2, 31))
9+
10+
x: number;
11+
>x : Symbol(MyProps.x, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 4, 19))
12+
}
13+
14+
function MyComp(props: MyProps, context: any, bad: any, verybad: any) {
15+
>MyComp : Symbol(MyComp, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 6, 1))
16+
>props : Symbol(props, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 8, 16))
17+
>MyProps : Symbol(MyProps, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 2, 31))
18+
>context : Symbol(context, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 8, 31))
19+
>bad : Symbol(bad, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 8, 45))
20+
>verybad : Symbol(verybad, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 8, 55))
21+
22+
return <div></div>;
23+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
24+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
25+
}
26+
27+
const a = <MyComp x={2}/>; // using `MyComp` as a component should error - it expects more arguments than react provides
28+
>a : Symbol(a, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 12, 5))
29+
>MyComp : Symbol(MyComp, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 6, 1))
30+
>x : Symbol(x, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 12, 17))
31+
32+
function MyComp2(props: MyProps, context: any) {
33+
>MyComp2 : Symbol(MyComp2, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 12, 26))
34+
>props : Symbol(props, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 14, 17))
35+
>MyProps : Symbol(MyProps, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 2, 31))
36+
>context : Symbol(context, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 14, 32))
37+
38+
return <div></div>
39+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
40+
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2420, 114))
41+
}
42+
const b = <MyComp2 x={2}/>; // Should be OK, `context` is allowed, per react rules
43+
>b : Symbol(b, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 17, 5))
44+
>MyComp2 : Symbol(MyComp2, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 12, 26))
45+
>x : Symbol(x, Decl(jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx, 17, 19))
46+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
=== tests/cases/compiler/jsxIssuesErrorWhenTagExpectsTooManyArguments.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
4+
import * as React from "react";
5+
>React : typeof React
6+
7+
interface MyProps {
8+
x: number;
9+
>x : number
10+
}
11+
12+
function MyComp(props: MyProps, context: any, bad: any, verybad: any) {
13+
>MyComp : (props: MyProps, context: any, bad: any, verybad: any) => JSX.Element
14+
>props : MyProps
15+
>context : any
16+
>bad : any
17+
>verybad : any
18+
19+
return <div></div>;
20+
><div></div> : JSX.Element
21+
>div : any
22+
>div : any
23+
}
24+
25+
const a = <MyComp x={2}/>; // using `MyComp` as a component should error - it expects more arguments than react provides
26+
>a : JSX.Element
27+
><MyComp x={2}/> : JSX.Element
28+
>MyComp : (props: MyProps, context: any, bad: any, verybad: any) => JSX.Element
29+
>x : number
30+
>2 : 2
31+
32+
function MyComp2(props: MyProps, context: any) {
33+
>MyComp2 : (props: MyProps, context: any) => JSX.Element
34+
>props : MyProps
35+
>context : any
36+
37+
return <div></div>
38+
><div></div> : JSX.Element
39+
>div : any
40+
>div : any
41+
}
42+
const b = <MyComp2 x={2}/>; // Should be OK, `context` is allowed, per react rules
43+
>b : JSX.Element
44+
><MyComp2 x={2}/> : JSX.Element
45+
>MyComp2 : (props: MyProps, context: any) => JSX.Element
46+
>x : number
47+
>2 : 2
48+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @jsx: react
2+
/// <reference path="/.lib/react16.d.ts" />
3+
4+
import * as React from "react";
5+
6+
interface MyProps {
7+
x: number;
8+
}
9+
10+
function MyComp(props: MyProps, context: any, bad: any, verybad: any) {
11+
return <div></div>;
12+
}
13+
14+
const a = <MyComp x={2}/>; // using `MyComp` as a component should error - it expects more arguments than react provides
15+
16+
function MyComp2(props: MyProps, context: any) {
17+
return <div></div>
18+
}
19+
const b = <MyComp2 x={2}/>; // Should be OK, `context` is allowed, per react rules

0 commit comments

Comments
 (0)