feat(7411): Resolve intrinsics elements by JSX namespaced tag names (#53799)

This commit is contained in:
Oleksandr T
2023-04-16 17:20:10 -07:00
committed by GitHub
parent f8b3ea7972
commit 378ffa4bc9
12 changed files with 273 additions and 22 deletions
+5
View File
@@ -89,6 +89,7 @@ import {
getEnclosingBlockScopeContainer,
getErrorSpanForNode,
getEscapedTextOfIdentifierOrLiteral,
getEscapedTextOfJsxAttributeName,
getExpandoInitializer,
getHostSignatureFromJSDoc,
getImmediatelyInvokedFunctionExpression,
@@ -171,6 +172,7 @@ import {
isJSDocTemplateTag,
isJSDocTypeAlias,
isJsonSourceFile,
isJsxNamespacedName,
isLeftHandSideExpression,
isLogicalOrCoalescingAssignmentExpression,
isLogicalOrCoalescingAssignmentOperator,
@@ -679,6 +681,9 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
const containingClassSymbol = containingClass.symbol;
return getSymbolNameForPrivateIdentifier(containingClassSymbol, name.escapedText);
}
if (isJsxNamespacedName(name)) {
return getEscapedTextOfJsxAttributeName(name);
}
return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined;
}
switch (node.kind) {
+23 -14
View File
@@ -276,6 +276,7 @@ import {
getErrorSpanForNode,
getEscapedTextOfIdentifierOrLiteral,
getEscapedTextOfJsxAttributeName,
getEscapedTextOfJsxNamespacedName,
getESModuleInterop,
getExpandoInitializer,
getExportAssignmentExpression,
@@ -429,6 +430,7 @@ import {
InternalSymbolName,
IntersectionType,
IntersectionTypeNode,
intrinsicTagNameToString,
IntrinsicType,
introducesArgumentsExoticObject,
isAccessExpression,
@@ -782,6 +784,7 @@ import {
JsxExpression,
JsxFlags,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxOpeningLikeElement,
@@ -29597,7 +29600,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getStaticTypeOfReferencedJsxConstructor(context: JsxOpeningLikeElement) {
if (isJsxIntrinsicIdentifier(context.tagName)) {
if (isJsxIntrinsicTagName(context.tagName)) {
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(context);
const fakeSignature = createSignatureForJSXIntrinsic(context, result);
return getOrCreateTypeFromSignature(fakeSignature);
@@ -30317,7 +30320,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
checkJsxOpeningLikeElementOrOpeningFragment(node.openingElement);
// Perform resolution on the closing tag so that rename/go to definition/etc work
if (isJsxIntrinsicIdentifier(node.closingElement.tagName)) {
if (isJsxIntrinsicTagName(node.closingElement.tagName)) {
getIntrinsicTagSymbol(node.closingElement);
}
else {
@@ -30357,8 +30360,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
/**
* Returns true iff React would emit this tag name as a string rather than an identifier or qualified name
*/
function isJsxIntrinsicIdentifier(tagName: JsxTagNameExpression): tagName is Identifier {
return tagName.kind === SyntaxKind.Identifier && isIntrinsicJsxName(tagName.escapedText);
function isJsxIntrinsicTagName(tagName: Node): tagName is Identifier | JsxNamespacedName {
return isIdentifier(tagName) && isIntrinsicJsxName(tagName.escapedText) || isJsxNamespacedName(tagName);
}
function checkJsxAttribute(node: JsxAttribute, checkMode?: CheckMode) {
@@ -30563,8 +30566,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const intrinsicElementsType = getJsxType(JsxNames.IntrinsicElements, node);
if (!isErrorType(intrinsicElementsType)) {
// Property case
if (!isIdentifier(node.tagName)) return Debug.fail();
const intrinsicProp = getPropertyOfType(intrinsicElementsType, node.tagName.escapedText);
if (!isIdentifier(node.tagName) && !isJsxNamespacedName(node.tagName)) return Debug.fail();
const intrinsicProp = getPropertyOfType(intrinsicElementsType, isJsxNamespacedName(node.tagName) ? getEscapedTextOfJsxNamespacedName(node.tagName) : node.tagName.escapedText);
if (intrinsicProp) {
links.jsxFlags |= JsxFlags.IntrinsicNamedElement;
return links.resolvedSymbol = intrinsicProp;
@@ -30578,7 +30581,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// Wasn't found
error(node, Diagnostics.Property_0_does_not_exist_on_type_1, idText(node.tagName), "JSX." + JsxNames.IntrinsicElements);
error(node, Diagnostics.Property_0_does_not_exist_on_type_1, intrinsicTagNameToString(node.tagName), "JSX." + JsxNames.IntrinsicElements);
return links.resolvedSymbol = unknownSymbol;
}
else {
@@ -30787,7 +30790,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* @param node an intrinsic JSX opening-like element
*/
function getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node: JsxOpeningLikeElement): Type {
Debug.assert(isJsxIntrinsicIdentifier(node.tagName));
Debug.assert(isJsxIntrinsicTagName(node.tagName));
const links = getNodeLinks(node);
if (!links.resolvedJsxElementAttributesType) {
const symbol = getIntrinsicTagSymbol(node);
@@ -30900,8 +30903,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const elementTypeConstraint = getJsxElementTypeTypeAt(jsxOpeningLikeNode);
if (elementTypeConstraint !== undefined) {
const tagName = jsxOpeningLikeNode.tagName;
const tagType = isJsxIntrinsicIdentifier(tagName)
? getStringLiteralType(unescapeLeadingUnderscores(tagName.escapedText))
const tagType = isJsxIntrinsicTagName(tagName)
? getStringLiteralType(intrinsicTagNameToString(tagName))
: checkExpression(tagName);
checkTypeRelatedTo(tagType, elementTypeConstraint, assignableRelation, tagName, Diagnostics.Its_type_0_is_not_a_valid_JSX_element_type, () => {
const componentName = getTextOfNode(tagName);
@@ -32521,7 +32524,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getJsxReferenceKind(node: JsxOpeningLikeElement): JsxReferenceKind {
if (isJsxIntrinsicIdentifier(node.tagName)) {
if (isJsxIntrinsicTagName(node.tagName)) {
return JsxReferenceKind.Mixed;
}
const tagType = getApparentType(checkExpression(node.tagName));
@@ -32568,7 +32571,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (getJsxNamespaceContainerForImplicitImport(node)) {
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)
}
const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicIdentifier(node.tagName) ? checkExpression(node.tagName) : undefined;
const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicTagName(node.tagName) ? checkExpression(node.tagName) : undefined;
if (!tagType) {
return true;
}
@@ -33972,7 +33975,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function resolveJsxOpeningLikeElement(node: JsxOpeningLikeElement, candidatesOutArray: Signature[] | undefined, checkMode: CheckMode): Signature {
if (isJsxIntrinsicIdentifier(node.tagName)) {
if (isJsxIntrinsicTagName(node.tagName)) {
const result = getIntrinsicAttributesTypeFromJsxOpeningLikeElement(node);
const fakeSignature = createSignatureForJSXIntrinsic(node, result);
checkTypeAssignableToAndOptionallyElaborate(checkExpressionWithContextualType(node.attributes, getEffectiveFirstArgumentForJsxSignature(fakeSignature, node), /*inferenceContext*/ undefined, CheckMode.Normal), result, node.tagName, node.attributes);
@@ -45438,7 +45441,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const isJSDoc = findAncestor(name, or(isJSDocLinkLike, isJSDocNameReference, isJSDocMemberName));
const meaning = isJSDoc ? SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Value : SymbolFlags.Value;
if (name.kind === SyntaxKind.Identifier) {
if (isJSXTagName(name) && isJsxIntrinsicIdentifier(name)) {
if (isJSXTagName(name) && isJsxIntrinsicTagName(name)) {
const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement);
return symbol === unknownSymbol ? undefined : symbol;
}
@@ -45685,6 +45688,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return isMetaProperty(node.parent) ? checkMetaPropertyKeyword(node.parent).symbol : undefined;
case SyntaxKind.MetaProperty:
return checkExpression(node as Expression).symbol;
case SyntaxKind.JsxNamespacedName:
if (isJSXTagName(node) && isJsxIntrinsicTagName(node)) {
const symbol = getIntrinsicTagSymbol(node.parent as JsxOpeningLikeElement);
return symbol === unknownSymbol ? undefined : symbol;
}
// falls through
default:
return undefined;
+19 -3
View File
@@ -361,6 +361,7 @@ import {
JsxElement,
JsxEmit,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningLikeElement,
JsxSelfClosingElement,
@@ -5828,7 +5829,7 @@ function isQuoteOrBacktick(charCode: number) {
/** @internal */
export function isIntrinsicJsxName(name: __String | string) {
const ch = (name as string).charCodeAt(0);
return (ch >= CharacterCodes.a && ch <= CharacterCodes.z) || stringContains((name as string), "-") || stringContains((name as string), ":");
return (ch >= CharacterCodes.a && ch <= CharacterCodes.z) || stringContains((name as string), "-");
}
const indentStrings: string[] = ["", " "];
@@ -10177,12 +10178,12 @@ export function tryGetJSDocSatisfiesTypeNode(node: Node) {
/** @internal */
export function getEscapedTextOfJsxAttributeName(node: JsxAttributeName): __String {
return isIdentifier(node) ? node.escapedText : `${node.namespace.escapedText}:${idText(node.name)}` as __String;
return isIdentifier(node) ? node.escapedText : getEscapedTextOfJsxNamespacedName(node);
}
/** @internal */
export function getTextOfJsxAttributeName(node: JsxAttributeName): string {
return isIdentifier(node) ? idText(node) : `${idText(node.namespace)}:${idText(node.name)}`;
return isIdentifier(node) ? idText(node) : getTextOfJsxNamespacedName(node);
}
/** @internal */
@@ -10191,3 +10192,18 @@ export function isJsxAttributeName(node: Node): node is JsxAttributeName {
return kind === SyntaxKind.Identifier
|| kind === SyntaxKind.JsxNamespacedName;
}
/** @internal */
export function getEscapedTextOfJsxNamespacedName(node: JsxNamespacedName): __String {
return `${node.namespace.escapedText}:${idText(node.name)}` as __String;
}
/** @internal */
export function getTextOfJsxNamespacedName(node: JsxNamespacedName) {
return `${idText(node.namespace)}:${idText(node.name)}`;
}
/** @internal */
export function intrinsicTagNameToString(node: Identifier | JsxNamespacedName) {
return isIdentifier(node) ? idText(node) : getTextOfJsxNamespacedName(node);
}
+4
View File
@@ -156,6 +156,7 @@ import {
isJsxClosingElement,
isJsxElement,
isJsxFragment,
isJsxNamespacedName,
isJsxOpeningElement,
isJsxOpeningFragment,
isJsxText,
@@ -2064,6 +2065,9 @@ export function createLanguageService(
if (isImportMeta(node.parent) && node.parent.name === node) {
return node.parent;
}
if (isJsxNamespacedName(node.parent)) {
return node.parent;
}
return node;
}