Skip to content

Commit 9bae221

Browse files
committed
feat(7411): add JSXNamespacedName
1 parent 2f058b7 commit 9bae221

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+707
-365
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11707,7 +11707,7 @@ namespace ts {
1170711707
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
1170811708
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
1170911709
return list.some(property => {
11710-
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
11710+
const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(property.name)) : getLiteralTypeFromPropertyName(property.name));
1171111711
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
1171211712
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
1171311713
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
@@ -17310,8 +17310,8 @@ namespace ts {
1731017310
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
1731117311
if (!length(node.properties)) return;
1731217312
for (const prop of node.properties) {
17313-
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
17314-
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
17313+
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
17314+
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
1731517315
}
1731617316
}
1731717317

@@ -26368,7 +26368,7 @@ namespace ts {
2636826368
if (!attributesType || isTypeAny(attributesType)) {
2636926369
return undefined;
2637026370
}
26371-
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
26371+
return getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name));
2637226372
}
2637326373
else {
2637426374
return getContextualType(attribute.parent);
@@ -27426,7 +27426,7 @@ namespace ts {
2742627426
attributeSymbol.target = member;
2742727427
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
2742827428
allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
27429-
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
27429+
if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) {
2743027430
explicitlySpecifyChildrenAttribute = true;
2743127431
}
2743227432
}
@@ -43187,8 +43187,9 @@ namespace ts {
4318743187
}
4318843188

4318943189
const { name, initializer } = attr;
43190-
if (!seen.get(name.escapedText)) {
43191-
seen.set(name.escapedText, true);
43190+
const escapedText = getEscapedTextOfJsxAttributeName(name);
43191+
if (!seen.get(escapedText)) {
43192+
seen.set(escapedText, true);
4319243193
}
4319343194
else {
4319443195
return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name);
@@ -43201,25 +43202,8 @@ namespace ts {
4320143202
}
4320243203

4320343204
function checkGrammarJsxName(node: JsxTagNameExpression) {
43204-
if (isPropertyAccessExpression(node)) {
43205-
let propName: JsxTagNameExpression = node;
43206-
do {
43207-
const check = checkGrammarJsxNestedIdentifier(propName.name);
43208-
if (check) {
43209-
return check;
43210-
}
43211-
propName = propName.expression;
43212-
} while (isPropertyAccessExpression(propName));
43213-
const check = checkGrammarJsxNestedIdentifier(propName);
43214-
if (check) {
43215-
return check;
43216-
}
43217-
}
43218-
43219-
function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) {
43220-
if (isIdentifier(name) && idText(name).indexOf(":") !== -1) {
43221-
return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
43222-
}
43205+
if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) {
43206+
return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
4322343207
}
4322443208
}
4322543209

src/compiler/emitter.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,8 @@ namespace ts {
17391739
return emitJsxSelfClosingElement(node as JsxSelfClosingElement);
17401740
case SyntaxKind.JsxFragment:
17411741
return emitJsxFragment(node as JsxFragment);
1742+
case SyntaxKind.JsxNamespacedName:
1743+
return emitJsxNamespacedName(node as JsxNamespacedName);
17421744

17431745
// Synthesized list
17441746
case SyntaxKind.SyntaxList:
@@ -3640,6 +3642,12 @@ namespace ts {
36403642
}
36413643
}
36423644

3645+
function emitJsxNamespacedName(node: JsxNamespacedName) {
3646+
emitIdentifierName(node.namespace);
3647+
writePunctuation(":");
3648+
emitIdentifierName(node.name);
3649+
}
3650+
36433651
function emitJsxTagName(node: JsxTagNameExpression) {
36443652
if (node.kind === SyntaxKind.Identifier) {
36453653
emitExpression(node);

src/compiler/factory/nodeFactory.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ namespace ts {
412412
updateJsxSpreadAttribute,
413413
createJsxExpression,
414414
updateJsxExpression,
415+
createJsxNamespacedName,
416+
updateJsxNamespacedName,
417+
415418
createCaseClause,
416419
updateCaseClause,
417420
createDefaultClause,
@@ -4926,6 +4929,26 @@ namespace ts {
49264929
: node;
49274930
}
49284931

4932+
// @api
4933+
function createJsxNamespacedName(namespace: Identifier, name: Identifier) {
4934+
const node = createBaseNode<JsxNamespacedName>(SyntaxKind.JsxNamespacedName);
4935+
node.namespace = namespace;
4936+
node.name = name;
4937+
node.transformFlags |=
4938+
propagateChildFlags(node.namespace) |
4939+
propagateChildFlags(node.name) |
4940+
TransformFlags.ContainsJsx;
4941+
return node;
4942+
}
4943+
4944+
// @api
4945+
function updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier) {
4946+
return node.namespace !== namespace
4947+
|| node.name !== name
4948+
? update(createJsxNamespacedName(namespace, name), node)
4949+
: node;
4950+
}
4951+
49294952
//
49304953
// Clauses
49314954
//

src/compiler/factory/nodeTests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,10 @@ namespace ts {
712712
return node.kind === SyntaxKind.JsxExpression;
713713
}
714714

715+
export function isJsxNamespacedName(node: Node): node is JsxNamespacedName {
716+
return node.kind === SyntaxKind.JsxNamespacedName;
717+
}
718+
715719
// Clauses
716720

717721
export function isCaseClause(node: Node): node is CaseClause {

src/compiler/parser.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,9 @@ namespace ts {
479479
visitNode(cbNode, (node as JsxExpression).expression);
480480
case SyntaxKind.JsxClosingElement:
481481
return visitNode(cbNode, (node as JsxClosingElement).tagName);
482-
482+
case SyntaxKind.JsxNamespacedName:
483+
return visitNode(cbNode, (node as JsxNamespacedName).namespace) ||
484+
visitNode(cbNode, (node as JsxNamespacedName).name);
483485
case SyntaxKind.OptionalType:
484486
case SyntaxKind.RestType:
485487
case SyntaxKind.JSDocTypeExpression:
@@ -5233,20 +5235,31 @@ namespace ts {
52335235

52345236
function parseJsxElementName(): JsxTagNameExpression {
52355237
const pos = getNodePos();
5236-
scanJsxIdentifier();
52375238
// JsxElement can have name in the form of
52385239
// propertyAccessExpression
52395240
// primaryExpression in the form of an identifier and "this" keyword
52405241
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
52415242
// We only want to consider "this" as a primaryExpression
5242-
let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ?
5243-
parseTokenNode<ThisExpression>() : parseIdentifierName();
5243+
let expression: JsxTagNameExpression = parseJsxTagName();
52445244
while (parseOptional(SyntaxKind.DotToken)) {
52455245
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
52465246
}
52475247
return expression;
52485248
}
52495249

5250+
function parseJsxTagName(): Identifier | JsxNamespacedName | ThisExpression {
5251+
const pos = getNodePos();
5252+
scanJsxIdentifier();
5253+
5254+
const isThis = token() === SyntaxKind.ThisKeyword;
5255+
const tagName = parseIdentifierName();
5256+
if (parseOptional(SyntaxKind.ColonToken)) {
5257+
scanJsxIdentifier();
5258+
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierName()), pos);
5259+
}
5260+
return isThis ? finishNode(factory.createToken(SyntaxKind.ThisKeyword), pos) : tagName;
5261+
}
5262+
52505263
function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined {
52515264
const pos = getNodePos();
52525265
if (!parseExpected(SyntaxKind.OpenBraceToken)) {
@@ -5279,11 +5292,10 @@ namespace ts {
52795292
return parseJsxSpreadAttribute();
52805293
}
52815294

5282-
scanJsxIdentifier();
52835295
const pos = getNodePos();
52845296
return finishNode(
52855297
factory.createJsxAttribute(
5286-
parseIdentifierName(),
5298+
parseJsxAttributeName(),
52875299
token() !== SyntaxKind.EqualsToken ? undefined :
52885300
scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral :
52895301
parseJsxExpression(/*inExpressionContext*/ true)
@@ -5292,6 +5304,18 @@ namespace ts {
52925304
);
52935305
}
52945306

5307+
function parseJsxAttributeName() {
5308+
const pos = getNodePos();
5309+
scanJsxIdentifier();
5310+
5311+
const attrName = parseIdentifierName();
5312+
if (parseOptional(SyntaxKind.ColonToken)) {
5313+
scanJsxIdentifier();
5314+
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierName()), pos);
5315+
}
5316+
return attrName;
5317+
}
5318+
52955319
function parseJsxSpreadAttribute(): JsxSpreadAttribute {
52965320
const pos = getNodePos();
52975321
parseExpected(SyntaxKind.OpenBraceToken);
@@ -9524,6 +9548,11 @@ namespace ts {
95249548
return true;
95259549
}
95269550

9551+
if (lhs.kind === SyntaxKind.JsxNamespacedName) {
9552+
return lhs.namespace.escapedText === (rhs as JsxNamespacedName).namespace.escapedText &&
9553+
lhs.name.escapedText === (rhs as JsxNamespacedName).name.escapedText;
9554+
}
9555+
95279556
// If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only
95289557
// take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression
95299558
// it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element

src/compiler/scanner.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,32 +2348,19 @@ namespace ts {
23482348
// everything after it to the token
23492349
// Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token
23502350
// Any caller should be expecting this behavior and should only read the pos or token value after calling it.
2351-
let namespaceSeparator = false;
23522351
while (pos < end) {
23532352
const ch = text.charCodeAt(pos);
23542353
if (ch === CharacterCodes.minus) {
23552354
tokenValue += "-";
23562355
pos++;
23572356
continue;
23582357
}
2359-
else if (ch === CharacterCodes.colon && !namespaceSeparator) {
2360-
tokenValue += ":";
2361-
pos++;
2362-
namespaceSeparator = true;
2363-
token = SyntaxKind.Identifier; // swap from keyword kind to identifier kind
2364-
continue;
2365-
}
23662358
const oldPos = pos;
23672359
tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled
23682360
if (pos === oldPos) {
23692361
break;
23702362
}
23712363
}
2372-
// Do not include a trailing namespace separator in the token, since this is against the spec.
2373-
if (tokenValue.slice(-1) === ":") {
2374-
tokenValue = tokenValue.slice(0, -1);
2375-
pos--;
2376-
}
23772364
return getIdentifierToken();
23782365
}
23792366
return token;

src/compiler/transformers/jsx.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ namespace ts {
173173
if (isJsxSpreadAttribute(elem)) {
174174
spread = true;
175175
}
176-
else if (spread && isJsxAttribute(elem) && elem.name.escapedText === "key") {
176+
else if (spread && isJsxAttribute(elem) && isIdentifier(elem.name) && elem.name.escapedText === "key") {
177177
return true;
178178
}
179179
}
@@ -508,12 +508,15 @@ namespace ts {
508508
return getTagName(node.openingElement);
509509
}
510510
else {
511-
const name = node.tagName;
512-
if (isIdentifier(name) && isIntrinsicJsxName(name.escapedText)) {
513-
return factory.createStringLiteral(idText(name));
511+
const tagName = node.tagName;
512+
if (isIdentifier(tagName) && isIntrinsicJsxName(tagName.escapedText)) {
513+
return factory.createStringLiteral(idText(tagName));
514+
}
515+
else if (isJsxNamespacedName(tagName)) {
516+
return factory.createStringLiteral(idText(tagName.namespace) + ":" + idText(tagName.name));
514517
}
515518
else {
516-
return createExpressionFromEntityName(factory, name);
519+
return createExpressionFromEntityName(factory, tagName);
517520
}
518521
}
519522
}
@@ -525,13 +528,11 @@ namespace ts {
525528
*/
526529
function getAttributeName(node: JsxAttribute): StringLiteral | Identifier {
527530
const name = node.name;
528-
const text = idText(name);
529-
if (/^[A-Za-z_]\w*$/.test(text)) {
530-
return name;
531-
}
532-
else {
533-
return factory.createStringLiteral(text);
531+
if (isIdentifier(name)) {
532+
const text = idText(name);
533+
return (/^[A-Za-z_]\w*$/.test(text)) ? name : factory.createStringLiteral(text);
534534
}
535+
return factory.createStringLiteral(idText(name.namespace) + ":" + idText(name.name));
535536
}
536537

537538
function visitJsxExpression(node: JsxExpression) {

src/compiler/types.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ namespace ts {
336336
JsxAttributes,
337337
JsxSpreadAttribute,
338338
JsxExpression,
339+
JsxNamespacedName,
339340

340341
// Clauses
341342
CaseClause,
@@ -2536,17 +2537,25 @@ namespace ts {
25362537
| Identifier
25372538
| ThisExpression
25382539
| JsxTagNamePropertyAccess
2540+
| JsxNamespacedName
25392541
;
25402542

25412543
export interface JsxTagNamePropertyAccess extends PropertyAccessExpression {
25422544
readonly expression: JsxTagNameExpression;
25432545
}
25442546

2545-
export interface JsxAttributes extends ObjectLiteralExpressionBase<JsxAttributeLike> {
2547+
export interface JsxAttributes extends PrimaryExpression, Declaration {
2548+
readonly properties: NodeArray<JsxAttributeLike>;
25462549
readonly kind: SyntaxKind.JsxAttributes;
25472550
readonly parent: JsxOpeningLikeElement;
25482551
}
25492552

2553+
export interface JsxNamespacedName extends PrimaryExpression {
2554+
readonly kind: SyntaxKind.JsxNamespacedName;
2555+
readonly name: Identifier;
2556+
readonly namespace: Identifier;
2557+
}
2558+
25502559
/// The opening element of a <Tag>...</Tag> JsxElement
25512560
export interface JsxOpeningElement extends Expression {
25522561
readonly kind: SyntaxKind.JsxOpeningElement;
@@ -2584,16 +2593,17 @@ namespace ts {
25842593
readonly parent: JsxFragment;
25852594
}
25862595

2587-
export interface JsxAttribute extends ObjectLiteralElement {
2596+
export interface JsxAttribute extends Declaration {
25882597
readonly kind: SyntaxKind.JsxAttribute;
25892598
readonly parent: JsxAttributes;
2590-
readonly name: Identifier;
2599+
readonly name: Identifier | JsxNamespacedName;
25912600
/// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
25922601
readonly initializer?: StringLiteral | JsxExpression;
25932602
}
25942603

2595-
export interface JsxSpreadAttribute extends ObjectLiteralElement {
2604+
export interface JsxSpreadAttribute extends Declaration {
25962605
readonly kind: SyntaxKind.JsxSpreadAttribute;
2606+
readonly name: PropertyName;
25972607
readonly parent: JsxAttributes;
25982608
readonly expression: Expression;
25992609
}
@@ -7571,14 +7581,16 @@ namespace ts {
75717581
createJsxOpeningFragment(): JsxOpeningFragment;
75727582
createJsxJsxClosingFragment(): JsxClosingFragment;
75737583
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
7574-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7575-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7584+
createJsxAttribute(name: Identifier | JsxNamespacedName, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7585+
updateJsxAttribute(node: JsxAttribute, name: Identifier | JsxNamespacedName, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
75767586
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
75777587
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
75787588
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
75797589
updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression): JsxSpreadAttribute;
75807590
createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined): JsxExpression;
75817591
updateJsxExpression(node: JsxExpression, expression: Expression | undefined): JsxExpression;
7592+
createJsxNamespacedName(namespace: Identifier, name: Identifier): JsxNamespacedName;
7593+
updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier): JsxNamespacedName;
75827594

75837595
//
75847596
// Clauses

0 commit comments

Comments
 (0)