Skip to content

Commit a4159a6

Browse files
committed
resolve intrinsics elements by jsx namespaced tag names
1 parent bdcf8ab commit a4159a6

12 files changed

+273
-22
lines changed

src/compiler/binder.ts

+5
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import {
8989
getEnclosingBlockScopeContainer,
9090
getErrorSpanForNode,
9191
getEscapedTextOfIdentifierOrLiteral,
92+
getEscapedTextOfJsxAttributeName,
9293
getExpandoInitializer,
9394
getHostSignatureFromJSDoc,
9495
getImmediatelyInvokedFunctionExpression,
@@ -171,6 +172,7 @@ import {
171172
isJSDocTemplateTag,
172173
isJSDocTypeAlias,
173174
isJsonSourceFile,
175+
isJsxNamespacedName,
174176
isLeftHandSideExpression,
175177
isLogicalOrCoalescingAssignmentExpression,
176178
isLogicalOrCoalescingAssignmentOperator,
@@ -679,6 +681,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
679681
const containingClassSymbol = containingClass.symbol;
680682
return getSymbolNameForPrivateIdentifier(containingClassSymbol, name.escapedText);
681683
}
684+
if (isJsxNamespacedName(name)) {
685+
return getEscapedTextOfJsxAttributeName(name);
686+
}
682687
return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined;
683688
}
684689
switch (node.kind) {

src/compiler/checker.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ import {
276276
getErrorSpanForNode,
277277
getEscapedTextOfIdentifierOrLiteral,
278278
getEscapedTextOfJsxAttributeName,
279+
getEscapedTextOfJsxNamespacedName,
279280
getESModuleInterop,
280281
getExpandoInitializer,
281282
getExportAssignmentExpression,
@@ -429,6 +430,7 @@ import {
429430
InternalSymbolName,
430431
IntersectionType,
431432
IntersectionTypeNode,
433+
intrinsicTagNameToString,
432434
IntrinsicType,
433435
introducesArgumentsExoticObject,
434436
isAccessExpression,
@@ -782,6 +784,7 @@ import {
782784
JsxExpression,
783785
JsxFlags,
784786
JsxFragment,
787+
JsxNamespacedName,
785788
JsxOpeningElement,
786789
JsxOpeningFragment,
787790
JsxOpeningLikeElement,
@@ -29595,7 +29598,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2959529598
}
2959629599

2959729600
function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) {
29598-
if (isJsxIntrinsicIdentifier(context.tagName)) {
29601+
if (isJsxIntrinsicTagName(context.tagName)) {
2959929602
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context);
2960029603
const fakeSignature = createSignatureForJSXIntrinsic(context, result);
2960129604
return getOrCreateTypeFromSignature(fakeSignature);
@@ -30315,7 +30318,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3031530318
checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement);
3031630319

3031730320
// Perform resolution on the closing tag so that rename/go to definition/etc work
30318-
if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) {
30321+
if (isJsxIntrinsicTagName(node.closingElement.tagName)) {
3031930322
getIntrinsicTagSymbol(node.closingElement);
3032030323
}
3032130324
else {
@@ -30355,8 +30358,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3035530358
/**
3035630359
* Returns true iff React would emit this tag name as a string rather than an identifier or qualified name
3035730360
*/
30358-
function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): tagName is Identifier {
30359-
return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText);
30361+
function isJsxIntrinsicTagName(tagName: Node): tagName is Identifier | JsxNamespacedName {
30362+
return isIdentifier(tagName) && isIntrinsicJsxName(tagName.escapedText) || isJsxNamespacedName(tagName);
3036030363
}
3036130364

3036230365
function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) {
@@ -30561,8 +30564,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3056130564
const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node);
3056230565
if (!isErrorType(intrinsicElementsType)) {
3056330566
// Property case
30564-
if (!isIdentifier(node.tagName)) return Debug.fail();
30565-
const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText);
30567+
if (!isIdentifier(node.tagName) && !isJsxNamespacedName(node.tagName)) return Debug.fail();
30568+
const intrinsicProp = getPropertyOfType(intrinsicElementsType, isJsxNamespacedName(node.tagName) ? getEscapedTextOfJsxNamespacedName(node.tagName) : node.tagName.escapedText);
3056630569
if (intrinsicProp) {
3056730570
links.jsxFlags |= JsxFlags.IntrinsicNamedElement;
3056830571
return links.resolvedSymbol = intrinsicProp;
@@ -30576,7 +30579,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3057630579
}
3057730580

3057830581
// Wasn't found
30579-
error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements);
30582+
error(node, Diagnostics.Property_0_does_not_exist_on_type_1, intrinsicTagNameToString(node.tagName), "JSX." + JsxNames.IntrinsicElements);
3058030583
return links.resolvedSymbol = unknownSymbol;
3058130584
}
3058230585
else {
@@ -30785,7 +30788,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3078530788
* @param node an intrinsic JSX opening-like element
3078630789
*/
3078730790
function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type {
30788-
Debug.assert(isJsxIntrinsicIdentifier(node.tagName));
30791+
Debug.assert(isJsxIntrinsicTagName(node.tagName));
3078930792
const links = getNodeLinks(node);
3079030793
if (!links.resolvedJsxElementAttributesType) {
3079130794
const symbol = getIntrinsicTagSymbol(node);
@@ -30898,8 +30901,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3089830901
const elementTypeConstraint = getJsxElementTypeTypeAt(jsxOpeningLikeNode);
3089930902
if (elementTypeConstraint !== undefined) {
3090030903
const tagName = jsxOpeningLikeNode.tagName;
30901-
const tagType = isJsxIntrinsicIdentifier(tagName)
30902-
? getStringLiteralType(unescapeLeadingUnderscores(tagName.escapedText))
30904+
const tagType = isJsxIntrinsicTagName(tagName)
30905+
? getStringLiteralType(intrinsicTagNameToString(tagName))
3090330906
: checkExpression(tagName);
3090430907
checkTypeRelatedTo(tagType, elementTypeConstraint, assignableRelation, tagName, Diagnostics.Its_type_0_is_not_a_valid_JSX_element_type, () => {
3090530908
const componentName = getTextOfNode(tagName);
@@ -32519,7 +32522,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3251932522
}
3252032523

3252132524
function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind {
32522-
if (isJsxIntrinsicIdentifier(node.tagName)) {
32525+
if (isJsxIntrinsicTagName(node.tagName)) {
3252332526
return JsxReferenceKind.Mixed;
3252432527
}
3252532528
const tagType = getApparentType(checkExpression(node.tagName));
@@ -32566,7 +32569,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3256632569
if (getJsxNamespaceContainerForImplicitImport(node)) {
3256732570
return true; // factory is implicitly jsx/jsxdev - assume it fits the bill, since we don't strongly look for the jsx/jsxs/jsxDEV factory APIs anywhere else (at least not yet)
3256832571
}
32569-
const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined;
32572+
const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicTagName(node.tagName) ? checkExpression(node.tagName) : undefined;
3257032573
if (!tagType) {
3257132574
return true;
3257232575
}
@@ -33970,7 +33973,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3397033973
}
3397133974

3397233975
function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
33973-
if (isJsxIntrinsicIdentifier(node.tagName)) {
33976+
if (isJsxIntrinsicTagName(node.tagName)) {
3397433977
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
3397533978
const fakeSignature = createSignatureForJSXIntrinsic(node, result);
3397633979
checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes);
@@ -45435,7 +45438,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4543545438
const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName));
4543645439
const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value;
4543745440
if (name.kind === SyntaxKind.Identifier) {
45438-
if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) {
45441+
if (isJSXTagName(name) && isJsxIntrinsicTagName(name)) {
4543945442
const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement);
4544045443
return symbol === unknownSymbol ? undefined : symbol;
4544145444
}
@@ -45682,6 +45685,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4568245685
return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined;
4568345686
case SyntaxKind.MetaProperty:
4568445687
return checkExpression(node as Expression).symbol;
45688+
case SyntaxKind.JsxNamespacedName:
45689+
if (isJSXTagName(node) && isJsxIntrinsicTagName(node)) {
45690+
const symbol = getIntrinsicTagSymbol(node.parent as JsxOpeningLikeElement);
45691+
return symbol === unknownSymbol ? undefined : symbol;
45692+
}
45693+
// falls through
4568545694

4568645695
default:
4568745696
return undefined;

src/compiler/utilities.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ import {
360360
JsxElement,
361361
JsxEmit,
362362
JsxFragment,
363+
JsxNamespacedName,
363364
JsxOpeningElement,
364365
JsxOpeningLikeElement,
365366
JsxSelfClosingElement,
@@ -5819,7 +5820,7 @@ function isQuoteOrBacktick(charCode: number) {
58195820
/** @internal */
58205821
export function isIntrinsicJsxName(name: __String | string) {
58215822
const ch = (name as string).charCodeAt(0);
5822-
return (ch >= CharacterCodes.a && ch <= CharacterCodes.z) || stringContains((name as string), "-") || stringContains((name as string), ":");
5823+
return (ch >= CharacterCodes.a && ch <= CharacterCodes.z) || stringContains((name as string), "-");
58235824
}
58245825

58255826
const indentStrings: string[] = ["", " "];
@@ -10168,12 +10169,12 @@ export function tryGetJSDocSatisfiesTypeNode(node: Node) {
1016810169

1016910170
/** @internal */
1017010171
export function getEscapedTextOfJsxAttributeName(node: JsxAttributeName): __String {
10171-
return isIdentifier(node) ? node.escapedText : `${node.namespace.escapedText}:${idText(node.name)}` as __String;
10172+
return isIdentifier(node) ? node.escapedText : getEscapedTextOfJsxNamespacedName(node);
1017210173
}
1017310174

1017410175
/** @internal */
1017510176
export function getTextOfJsxAttributeName(node: JsxAttributeName): string {
10176-
return isIdentifier(node) ? idText(node) : `${idText(node.namespace)}:${idText(node.name)}`;
10177+
return isIdentifier(node) ? idText(node) : getTextOfJsxNamespacedName(node);
1017710178
}
1017810179

1017910180
/** @internal */
@@ -10182,3 +10183,18 @@ export function isJsxAttributeName(node: Node): node is JsxAttributeName {
1018210183
return kind === SyntaxKind.Identifier
1018310184
|| kind === SyntaxKind.JsxNamespacedName;
1018410185
}
10186+
10187+
/** @internal */
10188+
export function getEscapedTextOfJsxNamespacedName(node: JsxNamespacedName): __String {
10189+
return `${node.namespace.escapedText}:${idText(node.name)}` as __String;
10190+
}
10191+
10192+
/** @internal */
10193+
export function getTextOfJsxNamespacedName(node: JsxNamespacedName) {
10194+
return `${idText(node.namespace)}:${idText(node.name)}`;
10195+
}
10196+
10197+
/** @internal */
10198+
export function intrinsicTagNameToString(node: Identifier | JsxNamespacedName) {
10199+
return isIdentifier(node) ? idText(node) : getTextOfJsxNamespacedName(node);
10200+
}

src/services/services.ts

+4
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ import {
156156
isJsxClosingElement,
157157
isJsxElement,
158158
isJsxFragment,
159+
isJsxNamespacedName,
159160
isJsxOpeningElement,
160161
isJsxOpeningFragment,
161162
isJsxText,
@@ -2064,6 +2065,9 @@ export function createLanguageService(
20642065
if (isImportMeta(node.parent) && node.parent.name === node) {
20652066
return node.parent;
20662067
}
2068+
if (isJsxNamespacedName(node.parent)) {
2069+
return node.parent;
2070+
}
20672071
return node;
20682072
}
20692073

tests/baselines/reference/jsxElementType.errors.txt

+16-1
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ tests/cases/compiler/jsxElementType.tsx(91,2): error TS2786: 'ReactNativeFlatLis
3434
tests/cases/compiler/jsxElementType.tsx(95,11): error TS2322: Type '{}' is not assignable to type 'LibraryManagedAttributes<T, {}>'.
3535
tests/cases/compiler/jsxElementType.tsx(98,2): error TS2304: Cannot find name 'Unresolved'.
3636
tests/cases/compiler/jsxElementType.tsx(99,2): error TS2304: Cannot find name 'Unresolved'.
37+
tests/cases/compiler/jsxElementType.tsx(109,19): error TS2322: Type '{ a: string; b: string; }' is not assignable to type '{ a: string; }'.
38+
Property 'b' does not exist on type '{ a: string; }'.
3739

3840

39-
==== tests/cases/compiler/jsxElementType.tsx (18 errors) ====
41+
==== tests/cases/compiler/jsxElementType.tsx (19 errors) ====
4042
/// <reference path="/.lib/react16.d.ts" />
4143
import * as React from "react";
4244

@@ -197,4 +199,17 @@ tests/cases/compiler/jsxElementType.tsx(99,2): error TS2304: Cannot find name 'U
197199
<Unresolved foo="abc" />;
198200
~~~~~~~~~~
199201
!!! error TS2304: Cannot find name 'Unresolved'.
202+
203+
declare global {
204+
namespace JSX {
205+
interface IntrinsicElements {
206+
['a:b']: { a: string };
207+
}
208+
}
209+
}
210+
211+
<a:b a="accepted" b="rejected" />;
212+
~
213+
!!! error TS2322: Type '{ a: string; b: string; }' is not assignable to type '{ a: string; }'.
214+
!!! error TS2322: Property 'b' does not exist on type '{ a: string; }'.
200215

tests/baselines/reference/jsxElementType.js

+11
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ function f1<T extends (props: {}) => React.ReactElement<any>>(Component: T) {
9898

9999
<Unresolved />;
100100
<Unresolved foo="abc" />;
101+
102+
declare global {
103+
namespace JSX {
104+
interface IntrinsicElements {
105+
['a:b']: { a: string };
106+
}
107+
}
108+
}
109+
110+
<a:b a="accepted" b="rejected" />;
101111

102112

103113
//// [jsxElementType.js]
@@ -231,3 +241,4 @@ function f1(Component) {
231241
}
232242
React.createElement(Unresolved, null);
233243
React.createElement(Unresolved, { foo: "abc" });
244+
React.createElement("a:b", { a: "accepted", b: "rejected" });

tests/baselines/reference/jsxElementType.symbols

+24-3
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ type NewReactJSXElementConstructor<P> =
4949
>P : Symbol(P, Decl(jsxElementType.tsx, 16, 35))
5050

5151
declare global {
52-
>global : Symbol(global, Decl(jsxElementType.tsx, 18, 48))
52+
>global : Symbol(global, Decl(jsxElementType.tsx, 18, 48), Decl(jsxElementType.tsx, 98, 25))
5353

5454
namespace JSX {
55-
>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12), Decl(jsxElementType.tsx, 20, 16))
55+
>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12), Decl(jsxElementType.tsx, 20, 16), Decl(jsxElementType.tsx, 100, 16))
5656

5757
type ElementType = string | NewReactJSXElementConstructor<any>;
5858
>ElementType : Symbol(ElementType, Decl(jsxElementType.tsx, 21, 17))
5959
>NewReactJSXElementConstructor : Symbol(NewReactJSXElementConstructor, Decl(jsxElementType.tsx, 13, 30))
6060

6161
interface IntrinsicElements {
62-
>IntrinsicElements : Symbol(IntrinsicElements, Decl(react16.d.ts, 2514, 86), Decl(jsxElementType.tsx, 22, 67))
62+
>IntrinsicElements : Symbol(IntrinsicElements, Decl(react16.d.ts, 2514, 86), Decl(jsxElementType.tsx, 22, 67), Decl(jsxElementType.tsx, 101, 19))
6363

6464
['my-custom-element']: React.DOMAttributes<unknown>;
6565
>['my-custom-element'] : Symbol(IntrinsicElements['my-custom-element'], Decl(jsxElementType.tsx, 23, 33))
@@ -272,3 +272,24 @@ function f1<T extends (props: {}) => React.ReactElement<any>>(Component: T) {
272272
<Unresolved foo="abc" />;
273273
>foo : Symbol(foo, Decl(jsxElementType.tsx, 98, 11))
274274

275+
declare global {
276+
>global : Symbol(global, Decl(jsxElementType.tsx, 18, 48), Decl(jsxElementType.tsx, 98, 25))
277+
278+
namespace JSX {
279+
>JSX : Symbol(JSX, Decl(react16.d.ts, 2493, 12), Decl(jsxElementType.tsx, 20, 16), Decl(jsxElementType.tsx, 100, 16))
280+
281+
interface IntrinsicElements {
282+
>IntrinsicElements : Symbol(IntrinsicElements, Decl(react16.d.ts, 2514, 86), Decl(jsxElementType.tsx, 22, 67), Decl(jsxElementType.tsx, 101, 19))
283+
284+
['a:b']: { a: string };
285+
>['a:b'] : Symbol(IntrinsicElements['a:b'], Decl(jsxElementType.tsx, 102, 35))
286+
>'a:b' : Symbol(IntrinsicElements['a:b'], Decl(jsxElementType.tsx, 102, 35))
287+
>a : Symbol(a, Decl(jsxElementType.tsx, 103, 20))
288+
}
289+
}
290+
}
291+
292+
<a:b a="accepted" b="rejected" />;
293+
>a : Symbol(a, Decl(jsxElementType.tsx, 108, 4))
294+
>b : Symbol(b, Decl(jsxElementType.tsx, 108, 17))
295+

tests/baselines/reference/jsxElementType.types

+20
Original file line numberDiff line numberDiff line change
@@ -290,3 +290,23 @@ function f1<T extends (props: {}) => React.ReactElement<any>>(Component: T) {
290290
>Unresolved : any
291291
>foo : string
292292

293+
declare global {
294+
>global : any
295+
296+
namespace JSX {
297+
interface IntrinsicElements {
298+
['a:b']: { a: string };
299+
>['a:b'] : { a: string; }
300+
>'a:b' : "a:b"
301+
>a : string
302+
}
303+
}
304+
}
305+
306+
<a:b a="accepted" b="rejected" />;
307+
><a:b a="accepted" b="rejected" /> : JSX.Element
308+
>a : any
309+
>b : any
310+
>a : string
311+
>b : string
312+

tests/baselines/reference/jsxNamespacePrefixIntrinsics.errors.txt

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
tests/cases/compiler/jsxNamespacePrefixIntrinsics.tsx(15,18): error TS2339: Property 'element' does not exist on type 'JSX.IntrinsicElements'.
2+
tests/cases/compiler/jsxNamespacePrefixIntrinsics.tsx(16,30): error TS2322: Type '{ attribute: string; }' is not assignable to type '{ "ns:attribute": string; }'.
3+
Property 'attribute' does not exist on type '{ "ns:attribute": string; }'. Did you mean '"ns:attribute"'?
4+
tests/cases/compiler/jsxNamespacePrefixIntrinsics.tsx(17,30): error TS2322: Type '{ "ns:invalid": string; }' is not assignable to type '{ "ns:attribute": string; }'.
5+
Property 'ns:invalid' does not exist on type '{ "ns:attribute": string; }'.
26

37

4-
==== tests/cases/compiler/jsxNamespacePrefixIntrinsics.tsx (1 errors) ====
8+
==== tests/cases/compiler/jsxNamespacePrefixIntrinsics.tsx (3 errors) ====
59
declare namespace JSX {
610
interface IntrinsicElements {
711
"ns:element": {
@@ -20,5 +24,11 @@ tests/cases/compiler/jsxNamespacePrefixIntrinsics.tsx(15,18): error TS2339: Prop
2024
~~~~~~~~~~~
2125
!!! error TS2339: Property 'element' does not exist on type 'JSX.IntrinsicElements'.
2226
const invalid2 = <ns:element attribute="nope" />;
27+
~~~~~~~~~
28+
!!! error TS2322: Type '{ attribute: string; }' is not assignable to type '{ "ns:attribute": string; }'.
29+
!!! error TS2322: Property 'attribute' does not exist on type '{ "ns:attribute": string; }'. Did you mean '"ns:attribute"'?
2330
const invalid3 = <ns:element ns:invalid="nope" />;
31+
~~~~~~~~~~
32+
!!! error TS2322: Type '{ "ns:invalid": string; }' is not assignable to type '{ "ns:attribute": string; }'.
33+
!!! error TS2322: Property 'ns:invalid' does not exist on type '{ "ns:attribute": string; }'.
2434

0 commit comments

Comments
 (0)