Skip to content

Commit da34bcf

Browse files
committed
feat(7411): add JSXNamespacedName
1 parent 09b84d5 commit da34bcf

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
@@ -11711,7 +11711,7 @@ namespace ts {
1171111711
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
1171211712
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
1171311713
return list.some(property => {
11714-
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
11714+
const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(property.name)) : getLiteralTypeFromPropertyName(property.name));
1171511715
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
1171611716
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
1171711717
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
@@ -17318,8 +17318,8 @@ namespace ts {
1731817318
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
1731917319
if (!length(node.properties)) return;
1732017320
for (const prop of node.properties) {
17321-
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
17322-
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
17321+
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
17322+
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
1732317323
}
1732417324
}
1732517325

@@ -26444,7 +26444,7 @@ namespace ts {
2644426444
if (!attributesType || isTypeAny(attributesType)) {
2644526445
return undefined;
2644626446
}
26447-
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
26447+
return getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name));
2644826448
}
2644926449
else {
2645026450
return getContextualType(attribute.parent);
@@ -27477,7 +27477,7 @@ namespace ts {
2747727477
attributeSymbol.target = member;
2747827478
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
2747927479
allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
27480-
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
27480+
if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) {
2748127481
explicitlySpecifyChildrenAttribute = true;
2748227482
}
2748327483
}
@@ -43312,8 +43312,9 @@ namespace ts {
4331243312
}
4331343313

4331443314
const { name, initializer } = attr;
43315-
if (!seen.get(name.escapedText)) {
43316-
seen.set(name.escapedText, true);
43315+
const escapedText = getEscapedTextOfJsxAttributeName(name);
43316+
if (!seen.get(escapedText)) {
43317+
seen.set(escapedText, true);
4331743318
}
4331843319
else {
4331943320
return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name);
@@ -43326,25 +43327,8 @@ namespace ts {
4332643327
}
4332743328

4332843329
function checkGrammarJsxName(node: JsxTagNameExpression) {
43329-
if (isPropertyAccessExpression(node)) {
43330-
let propName: JsxTagNameExpression = node;
43331-
do {
43332-
const check = checkGrammarJsxNestedIdentifier(propName.name);
43333-
if (check) {
43334-
return check;
43335-
}
43336-
propName = propName.expression;
43337-
} while (isPropertyAccessExpression(propName));
43338-
const check = checkGrammarJsxNestedIdentifier(propName);
43339-
if (check) {
43340-
return check;
43341-
}
43342-
}
43343-
43344-
function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) {
43345-
if (isIdentifier(name) && idText(name).indexOf(":") !== -1) {
43346-
return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
43347-
}
43330+
if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) {
43331+
return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
4334843332
}
4334943333
}
4335043334

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:
@@ -3645,6 +3647,12 @@ namespace ts {
36453647
}
36463648
}
36473649

3650+
function emitJsxNamespacedName(node: JsxNamespacedName) {
3651+
emitIdentifierName(node.namespace);
3652+
writePunctuation(":");
3653+
emitIdentifierName(node.name);
3654+
}
3655+
36483656
function emitJsxTagName(node: JsxTagNameExpression) {
36493657
if (node.kind === SyntaxKind.Identifier) {
36503658
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);
@@ -9525,6 +9549,11 @@ namespace ts {
95259549
return true;
95269550
}
95279551

9552+
if (lhs.kind === SyntaxKind.JsxNamespacedName) {
9553+
return lhs.namespace.escapedText === (rhs as JsxNamespacedName).namespace.escapedText &&
9554+
lhs.name.escapedText === (rhs as JsxNamespacedName).name.escapedText;
9555+
}
9556+
95289557
// If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only
95299558
// take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression
95309559
// 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
}
@@ -518,12 +518,15 @@ namespace ts {
518518
return getTagName(node.openingElement);
519519
}
520520
else {
521-
const name = node.tagName;
522-
if (isIdentifier(name) && isIntrinsicJsxName(name.escapedText)) {
523-
return factory.createStringLiteral(idText(name));
521+
const tagName = node.tagName;
522+
if (isIdentifier(tagName) && isIntrinsicJsxName(tagName.escapedText)) {
523+
return factory.createStringLiteral(idText(tagName));
524+
}
525+
else if (isJsxNamespacedName(tagName)) {
526+
return factory.createStringLiteral(idText(tagName.namespace) + ":" + idText(tagName.name));
524527
}
525528
else {
526-
return createExpressionFromEntityName(factory, name);
529+
return createExpressionFromEntityName(factory, tagName);
527530
}
528531
}
529532
}
@@ -535,13 +538,11 @@ namespace ts {
535538
*/
536539
function getAttributeName(node: JsxAttribute): StringLiteral | Identifier {
537540
const name = node.name;
538-
const text = idText(name);
539-
if (/^[A-Za-z_]\w*$/.test(text)) {
540-
return name;
541-
}
542-
else {
543-
return factory.createStringLiteral(text);
541+
if (isIdentifier(name)) {
542+
const text = idText(name);
543+
return (/^[A-Za-z_]\w*$/.test(text)) ? name : factory.createStringLiteral(text);
544544
}
545+
return factory.createStringLiteral(idText(name.namespace) + ":" + idText(name.name));
545546
}
546547

547548
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,
@@ -2537,17 +2538,25 @@ namespace ts {
25372538
| Identifier
25382539
| ThisExpression
25392540
| JsxTagNamePropertyAccess
2541+
| JsxNamespacedName
25402542
;
25412543

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

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

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

2588-
export interface JsxAttribute extends ObjectLiteralElement {
2597+
export interface JsxAttribute extends Declaration {
25892598
readonly kind: SyntaxKind.JsxAttribute;
25902599
readonly parent: JsxAttributes;
2591-
readonly name: Identifier;
2600+
readonly name: Identifier | JsxNamespacedName;
25922601
/// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
25932602
readonly initializer?: StringLiteral | JsxExpression;
25942603
}
25952604

2596-
export interface JsxSpreadAttribute extends ObjectLiteralElement {
2605+
export interface JsxSpreadAttribute extends Declaration {
25972606
readonly kind: SyntaxKind.JsxSpreadAttribute;
2607+
readonly name: PropertyName;
25982608
readonly parent: JsxAttributes;
25992609
readonly expression: Expression;
26002610
}
@@ -7573,14 +7583,16 @@ namespace ts {
75737583
createJsxOpeningFragment(): JsxOpeningFragment;
75747584
createJsxJsxClosingFragment(): JsxClosingFragment;
75757585
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
7576-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7577-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7586+
createJsxAttribute(name: Identifier | JsxNamespacedName, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7587+
updateJsxAttribute(node: JsxAttribute, name: Identifier | JsxNamespacedName, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
75787588
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
75797589
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
75807590
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
75817591
updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression): JsxSpreadAttribute;
75827592
createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined): JsxExpression;
75837593
updateJsxExpression(node: JsxExpression, expression: Expression | undefined): JsxExpression;
7594+
createJsxNamespacedName(namespace: Identifier, name: Identifier): JsxNamespacedName;
7595+
updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier): JsxNamespacedName;
75847596

75857597
//
75867598
// Clauses

0 commit comments

Comments
 (0)