Skip to content

Commit 02bb310

Browse files
authored
JSX namespace names should not be considered expressions (#54104)
1 parent 59d3a38 commit 02bb310

22 files changed

+186
-58
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,7 @@ import {
776776
JSDocVariadicType,
777777
JsxAttribute,
778778
JsxAttributeLike,
779+
JsxAttributeName,
779780
JsxAttributes,
780781
JsxChild,
781782
JsxClosingElement,
@@ -17026,19 +17027,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1702617027
}
1702717028
}
1702817029

17029-
function getLiteralTypeFromPropertyName(name: PropertyName) {
17030+
function getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) {
1703017031
if (isPrivateIdentifier(name)) {
1703117032
return neverType;
1703217033
}
17033-
return isIdentifier(name) ? getStringLiteralType(unescapeLeadingUnderscores(name.escapedText)) :
17034-
getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name));
17034+
if (isNumericLiteral(name)) {
17035+
return getRegularTypeOfLiteralType(checkExpression(name));
17036+
}
17037+
if (isComputedPropertyName(name)) {
17038+
return getRegularTypeOfLiteralType(checkComputedPropertyName(name));
17039+
}
17040+
const propertyName = getPropertyNameForPropertyNameNode(name);
17041+
if (propertyName !== undefined) {
17042+
return getStringLiteralType(unescapeLeadingUnderscores(propertyName));
17043+
}
17044+
if (isExpression(name)) {
17045+
return getRegularTypeOfLiteralType(checkExpression(name));
17046+
}
17047+
return neverType;
1703517048
}
1703617049

1703717050
function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags, includeNonPublic?: boolean) {
1703817051
if (includeNonPublic || !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
1703917052
let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType;
1704017053
if (!type) {
17041-
const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName;
17054+
const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName | JsxAttributeName;
1704217055
type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") :
1704317056
name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined);
1704417057
}
@@ -32647,7 +32660,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3264732660
if (getJsxNamespaceContainerForImplicitImport(node)) {
3264832661
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)
3264932662
}
32650-
const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicTagName(node.tagName) ? checkExpression(node.tagName) : undefined;
32663+
const tagType = (isJsxOpeningElement(node) || isJsxSelfClosingElement(node)) && !(isJsxIntrinsicTagName(node.tagName) || isJsxNamespacedName(node.tagName)) ? checkExpression(node.tagName) : undefined;
3265132664
if (!tagType) {
3265232665
return true;
3265332666
}

src/compiler/emitter.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ import {
179179
getSyntheticLeadingComments,
180180
getSyntheticTrailingComments,
181181
getTextOfJSDocComment,
182+
getTextOfJsxNamespacedName,
182183
getTrailingCommentRanges,
183184
getTrailingSemicolonDeferringWriter,
184185
getTransformers,
@@ -228,6 +229,7 @@ import {
228229
isJSDocLikeText,
229230
isJsonSourceFile,
230231
isJsxClosingElement,
232+
isJsxNamespacedName,
231233
isJsxOpeningElement,
232234
isKeyword,
233235
isLet,
@@ -2066,6 +2068,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
20662068
return emitJsxSpreadAttribute(node as JsxSpreadAttribute);
20672069
case SyntaxKind.JsxExpression:
20682070
return emitJsxExpression(node as JsxExpression);
2071+
case SyntaxKind.JsxNamespacedName:
2072+
return emitJsxNamespacedName(node as JsxNamespacedName);
20692073

20702074
// Clauses
20712075
case SyntaxKind.CaseClause:
@@ -2283,8 +2287,6 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
22832287
return emitJsxSelfClosingElement(node as JsxSelfClosingElement);
22842288
case SyntaxKind.JsxFragment:
22852289
return emitJsxFragment(node as JsxFragment);
2286-
case SyntaxKind.JsxNamespacedName:
2287-
return emitJsxNamespacedName(node as JsxNamespacedName);
22882290

22892291
// Synthesized list
22902292
case SyntaxKind.SyntaxList:
@@ -5528,7 +5530,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
55285530
return node;
55295531
}
55305532

5531-
function getTextOfNode(node: Identifier | PrivateIdentifier | LiteralExpression, includeTrivia?: boolean): string {
5533+
function getTextOfNode(node: Identifier | PrivateIdentifier | LiteralExpression | JsxNamespacedName, includeTrivia?: boolean): string {
55325534
if (isGeneratedIdentifier(node) || isGeneratedPrivateIdentifier(node)) {
55335535
return generateName(node);
55345536
}
@@ -5542,6 +5544,11 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
55425544
return idText(node);
55435545
}
55445546
}
5547+
else if (isJsxNamespacedName(node)) {
5548+
if (!canUseSourceFile || getSourceFileOfNode(node) !== getOriginalNode(sourceFile)) {
5549+
return getTextOfJsxNamespacedName(node);
5550+
}
5551+
}
55455552
else {
55465553
Debug.assertNode(node, isLiteralExpression); // not strictly necessary
55475554
if (!canUseSourceFile) {
@@ -5554,7 +5561,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
55545561
function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string {
55555562
if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) {
55565563
const textSourceNode = (node as StringLiteral).textSourceNode!;
5557-
if (isIdentifier(textSourceNode) || isPrivateIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) {
5564+
if (isIdentifier(textSourceNode) || isPrivateIdentifier(textSourceNode) || isNumericLiteral(textSourceNode) || isJsxNamespacedName(textSourceNode)) {
55585565
const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode);
55595566
return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` :
55605567
neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` :

src/compiler/parser.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ import {
147147
isJSDocNullableType,
148148
isJSDocReturnTag,
149149
isJSDocTypeTag,
150+
isJsxNamespacedName,
150151
isJsxOpeningElement,
151152
isJsxOpeningFragment,
152153
isKeyword,
@@ -228,7 +229,6 @@ import {
228229
JsxSelfClosingElement,
229230
JsxSpreadAttribute,
230231
JsxTagNameExpression,
231-
JsxTagNamePropertyAccess,
232232
JsxText,
233233
JsxTokenSyntaxKind,
234234
LabeledStatement,
@@ -6122,11 +6122,15 @@ namespace Parser {
61226122
// primaryExpression in the form of an identifier and "this" keyword
61236123
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
61246124
// We only want to consider "this" as a primaryExpression
6125-
let expression: JsxTagNameExpression = parseJsxTagName();
6125+
const initialExpression = parseJsxTagName();
6126+
if (isJsxNamespacedName(initialExpression)) {
6127+
return initialExpression; // `a:b.c` is invalid syntax, don't even look for the `.` if we parse `a:b`, and let `parseAttribute` report "unexpected :" instead.
6128+
}
6129+
let expression: PropertyAccessExpression | Identifier | ThisExpression = initialExpression;
61266130
while (parseOptional(SyntaxKind.DotToken)) {
6127-
expression = finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
6131+
expression = finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos);
61286132
}
6129-
return expression;
6133+
return expression as JsxTagNameExpression;
61306134
}
61316135

61326136
function parseJsxTagName(): Identifier | JsxNamespacedName | ThisExpression {

src/compiler/types.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,11 +1722,9 @@ export type PropertyName = Identifier | StringLiteral | NumericLiteral | Compute
17221722
export type MemberName = Identifier | PrivateIdentifier;
17231723

17241724
export type DeclarationName =
1725-
| Identifier
1726-
| PrivateIdentifier
1725+
| PropertyName
1726+
| JsxAttributeName
17271727
| StringLiteralLike
1728-
| NumericLiteral
1729-
| ComputedPropertyName
17301728
| ElementAccessExpression
17311729
| BindingPattern
17321730
| EntityNameExpression;
@@ -2332,7 +2330,7 @@ export interface LiteralTypeNode extends TypeNode {
23322330

23332331
export interface StringLiteral extends LiteralExpression, Declaration {
23342332
readonly kind: SyntaxKind.StringLiteral;
2335-
/** @internal */ readonly textSourceNode?: Identifier | StringLiteralLike | NumericLiteral | PrivateIdentifier; // Allows a StringLiteral to get its text from another node (used by transforms).
2333+
/** @internal */ readonly textSourceNode?: Identifier | StringLiteralLike | NumericLiteral | PrivateIdentifier | JsxNamespacedName; // Allows a StringLiteral to get its text from another node (used by transforms).
23362334
/**
23372335
* Note: this is only set when synthesizing a node, not during parsing.
23382336
*
@@ -2342,7 +2340,7 @@ export interface StringLiteral extends LiteralExpression, Declaration {
23422340
}
23432341

23442342
export type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral;
2345-
export type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral;
2343+
export type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral | JsxNamespacedName;
23462344

23472345
export interface TemplateLiteralTypeNode extends TypeNode {
23482346
kind: SyntaxKind.TemplateLiteralType,
@@ -3191,7 +3189,7 @@ export type JsxTagNameExpression =
31913189
;
31923190

31933191
export interface JsxTagNamePropertyAccess extends PropertyAccessExpression {
3194-
readonly expression: JsxTagNameExpression;
3192+
readonly expression: Identifier | ThisExpression | JsxTagNamePropertyAccess;
31953193
}
31963194

31973195
export interface JsxAttributes extends PrimaryExpression, Declaration {
@@ -3200,7 +3198,7 @@ export interface JsxAttributes extends PrimaryExpression, Declaration {
32003198
readonly parent: JsxOpeningLikeElement;
32013199
}
32023200

3203-
export interface JsxNamespacedName extends PrimaryExpression {
3201+
export interface JsxNamespacedName extends Node {
32043202
readonly kind: SyntaxKind.JsxNamespacedName;
32053203
readonly name: Identifier;
32063204
readonly namespace: Identifier;

src/compiler/utilities.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ import {
293293
isJSDocTypeTag,
294294
isJsxChild,
295295
isJsxFragment,
296+
isJsxNamespacedName,
296297
isJsxOpeningLikeElement,
297298
isJsxText,
298299
isLeftHandSideExpression,
@@ -2024,7 +2025,7 @@ export function isComputedNonLiteralName(name: PropertyName): boolean {
20242025
}
20252026

20262027
/** @internal */
2027-
export function tryGetTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral): __String | undefined {
2028+
export function tryGetTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral | JsxAttributeName): __String | undefined {
20282029
switch (name.kind) {
20292030
case SyntaxKind.Identifier:
20302031
case SyntaxKind.PrivateIdentifier:
@@ -2036,13 +2037,15 @@ export function tryGetTextOfPropertyName(name: PropertyName | NoSubstitutionTemp
20362037
case SyntaxKind.ComputedPropertyName:
20372038
if (isStringOrNumericLiteralLike(name.expression)) return escapeLeadingUnderscores(name.expression.text);
20382039
return undefined;
2040+
case SyntaxKind.JsxNamespacedName:
2041+
return getEscapedTextOfJsxNamespacedName(name);
20392042
default:
20402043
return Debug.assertNever(name);
20412044
}
20422045
}
20432046

20442047
/** @internal */
2045-
export function getTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral): __String {
2048+
export function getTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral | JsxAttributeName): __String {
20462049
return Debug.checkDefined(tryGetTextOfPropertyName(name));
20472050
}
20482051

@@ -3074,7 +3077,7 @@ export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNam
30743077
}
30753078

30763079
/** @internal */
3077-
export function getInvokedExpression(node: CallLikeExpression): Expression {
3080+
export function getInvokedExpression(node: CallLikeExpression): Expression | JsxTagNameExpression {
30783081
switch (node.kind) {
30793082
case SyntaxKind.TaggedTemplateExpression:
30803083
return node.tag;
@@ -4954,7 +4957,7 @@ export function isDynamicName(name: DeclarationName): boolean {
49544957
}
49554958

49564959
/** @internal */
4957-
export function getPropertyNameForPropertyNameNode(name: PropertyName): __String | undefined {
4960+
export function getPropertyNameForPropertyNameNode(name: PropertyName | JsxAttributeName): __String | undefined {
49584961
switch (name.kind) {
49594962
case SyntaxKind.Identifier:
49604963
case SyntaxKind.PrivateIdentifier:
@@ -4974,6 +4977,8 @@ export function getPropertyNameForPropertyNameNode(name: PropertyName): __String
49744977
return nameExpression.operand.text as __String;
49754978
}
49764979
return undefined;
4980+
case SyntaxKind.JsxNamespacedName:
4981+
return getEscapedTextOfJsxNamespacedName(name);
49774982
default:
49784983
return Debug.assertNever(name);
49794984
}
@@ -4993,12 +4998,12 @@ export function isPropertyNameLiteral(node: Node): node is PropertyNameLiteral {
49934998
}
49944999
/** @internal */
49955000
export function getTextOfIdentifierOrLiteral(node: PropertyNameLiteral | PrivateIdentifier): string {
4996-
return isMemberName(node) ? idText(node) : node.text;
5001+
return isMemberName(node) ? idText(node) : isJsxNamespacedName(node) ? getTextOfJsxNamespacedName(node) : node.text;
49975002
}
49985003

49995004
/** @internal */
50005005
export function getEscapedTextOfIdentifierOrLiteral(node: PropertyNameLiteral): __String {
5001-
return isMemberName(node) ? node.escapedText : escapeLeadingUnderscores(node.text);
5006+
return isMemberName(node) ? node.escapedText : isJsxNamespacedName(node) ? getEscapedTextOfJsxNamespacedName(node) : escapeLeadingUnderscores(node.text);
50025007
}
50035008

50045009
/** @internal */
@@ -7025,7 +7030,7 @@ export function isPropertyAccessEntityNameExpression(node: Node): node is Proper
70257030
}
70267031

70277032
/** @internal */
7028-
export function tryGetPropertyAccessOrIdentifierToString(expr: Expression): string | undefined {
7033+
export function tryGetPropertyAccessOrIdentifierToString(expr: Expression | JsxTagNameExpression): string | undefined {
70297034
if (isPropertyAccessExpression(expr)) {
70307035
const baseStr = tryGetPropertyAccessOrIdentifierToString(expr.expression);
70317036
if (baseStr !== undefined) {
@@ -7041,6 +7046,9 @@ export function tryGetPropertyAccessOrIdentifierToString(expr: Expression): stri
70417046
else if (isIdentifier(expr)) {
70427047
return unescapeLeadingUnderscores(expr.escapedText);
70437048
}
7049+
else if (isJsxNamespacedName(expr)) {
7050+
return getTextOfJsxNamespacedName(expr);
7051+
}
70447052
return undefined;
70457053
}
70467054

src/compiler/utilitiesPublic.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1941,7 +1941,6 @@ function isLeftHandSideExpressionKind(kind: SyntaxKind): boolean {
19411941
case SyntaxKind.JsxElement:
19421942
case SyntaxKind.JsxSelfClosingElement:
19431943
case SyntaxKind.JsxFragment:
1944-
case SyntaxKind.JsxNamespacedName:
19451944
case SyntaxKind.TaggedTemplateExpression:
19461945
case SyntaxKind.ArrayLiteralExpression:
19471946
case SyntaxKind.ParenthesizedExpression:

src/services/signatureHelp.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
isTemplateSpan,
5656
isTemplateTail,
5757
isTransientSymbol,
58+
JsxTagNameExpression,
5859
last,
5960
lastOrUndefined,
6061
ListFormat,
@@ -599,7 +600,7 @@ function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node,
599600
return children[indexOfOpenerToken + 1];
600601
}
601602

602-
function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression {
603+
function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression | JsxTagNameExpression {
603604
return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called;
604605
}
605606

src/services/utilities.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ import {
261261
JsTyping,
262262
JsxEmit,
263263
JsxOpeningLikeElement,
264+
JsxTagNameExpression,
264265
LabeledStatement,
265266
LanguageServiceHost,
266267
last,
@@ -617,7 +618,7 @@ function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) {
617618
return node.tagName;
618619
}
619620

620-
function isCalleeWorker<T extends CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement>(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) {
621+
function isCalleeWorker<T extends CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement>(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression | JsxTagNameExpression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) {
621622
let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node);
622623
if (skipPastOuterExpressions) {
623624
target = skipOuterExpressions(target);

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4691,7 +4691,7 @@ declare namespace ts {
46914691
type EntityName = Identifier | QualifiedName;
46924692
type PropertyName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | PrivateIdentifier;
46934693
type MemberName = Identifier | PrivateIdentifier;
4694-
type DeclarationName = Identifier | PrivateIdentifier | StringLiteralLike | NumericLiteral | ComputedPropertyName | ElementAccessExpression | BindingPattern | EntityNameExpression;
4694+
type DeclarationName = PropertyName | JsxAttributeName | StringLiteralLike | ElementAccessExpression | BindingPattern | EntityNameExpression;
46954695
interface Declaration extends Node {
46964696
_declarationBrand: any;
46974697
}
@@ -5038,7 +5038,7 @@ declare namespace ts {
50385038
readonly kind: SyntaxKind.StringLiteral;
50395039
}
50405040
type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral;
5041-
type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral;
5041+
type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral | JsxNamespacedName;
50425042
interface TemplateLiteralTypeNode extends TypeNode {
50435043
kind: SyntaxKind.TemplateLiteralType;
50445044
readonly head: TemplateHead;
@@ -5397,14 +5397,14 @@ declare namespace ts {
53975397
type JsxAttributeName = Identifier | JsxNamespacedName;
53985398
type JsxTagNameExpression = Identifier | ThisExpression | JsxTagNamePropertyAccess | JsxNamespacedName;
53995399
interface JsxTagNamePropertyAccess extends PropertyAccessExpression {
5400-
readonly expression: JsxTagNameExpression;
5400+
readonly expression: Identifier | ThisExpression | JsxTagNamePropertyAccess;
54015401
}
54025402
interface JsxAttributes extends PrimaryExpression, Declaration {
54035403
readonly properties: NodeArray<JsxAttributeLike>;
54045404
readonly kind: SyntaxKind.JsxAttributes;
54055405
readonly parent: JsxOpeningLikeElement;
54065406
}
5407-
interface JsxNamespacedName extends PrimaryExpression {
5407+
interface JsxNamespacedName extends Node {
54085408
readonly kind: SyntaxKind.JsxNamespacedName;
54095409
readonly name: Identifier;
54105410
readonly namespace: Identifier;

0 commit comments

Comments
 (0)