Skip to content

Commit e70390c

Browse files
committed
feat(7411): add JSXNamespacedName
1 parent 20c93d3 commit e70390c

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

+727
-367
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11839,7 +11839,7 @@ namespace ts {
1183911839
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
1184011840
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
1184111841
return list.some(property => {
11842-
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
11842+
const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(property.name)) : getLiteralTypeFromPropertyName(property.name));
1184311843
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
1184411844
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
1184511845
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
@@ -17470,8 +17470,8 @@ namespace ts {
1747017470
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
1747117471
if (!length(node.properties)) return;
1747217472
for (const prop of node.properties) {
17473-
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
17474-
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
17473+
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
17474+
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
1747517475
}
1747617476
}
1747717477

@@ -26675,7 +26675,7 @@ namespace ts {
2667526675
if (!attributesType || isTypeAny(attributesType)) {
2667626676
return undefined;
2667726677
}
26678-
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
26678+
return getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name));
2667926679
}
2668026680
else {
2668126681
return getContextualType(attribute.parent);
@@ -27728,7 +27728,7 @@ namespace ts {
2772827728
attributeSymbol.target = member;
2772927729
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
2773027730
allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
27731-
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
27731+
if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) {
2773227732
explicitlySpecifyChildrenAttribute = true;
2773327733
}
2773427734
}
@@ -43683,8 +43683,9 @@ namespace ts {
4368343683
}
4368443684

4368543685
const { name, initializer } = attr;
43686-
if (!seen.get(name.escapedText)) {
43687-
seen.set(name.escapedText, true);
43686+
const escapedText = getEscapedTextOfJsxAttributeName(name);
43687+
if (!seen.get(escapedText)) {
43688+
seen.set(escapedText, true);
4368843689
}
4368943690
else {
4369043691
return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name);
@@ -43697,25 +43698,8 @@ namespace ts {
4369743698
}
4369843699

4369943700
function checkGrammarJsxName(node: JsxTagNameExpression) {
43700-
if (isPropertyAccessExpression(node)) {
43701-
let propName: JsxTagNameExpression = node;
43702-
do {
43703-
const check = checkGrammarJsxNestedIdentifier(propName.name);
43704-
if (check) {
43705-
return check;
43706-
}
43707-
propName = propName.expression;
43708-
} while (isPropertyAccessExpression(propName));
43709-
const check = checkGrammarJsxNestedIdentifier(propName);
43710-
if (check) {
43711-
return check;
43712-
}
43713-
}
43714-
43715-
function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) {
43716-
if (isIdentifier(name) && idText(name).indexOf(":") !== -1) {
43717-
return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
43718-
}
43701+
if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) {
43702+
return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
4371943703
}
4372043704
}
4372143705

src/compiler/emitter.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,6 +1741,8 @@ namespace ts {
17411741
return emitJsxSelfClosingElement(node as JsxSelfClosingElement);
17421742
case SyntaxKind.JsxFragment:
17431743
return emitJsxFragment(node as JsxFragment);
1744+
case SyntaxKind.JsxNamespacedName:
1745+
return emitJsxNamespacedName(node as JsxNamespacedName);
17441746

17451747
// Synthesized list
17461748
case SyntaxKind.SyntaxList:
@@ -3661,6 +3663,12 @@ namespace ts {
36613663
}
36623664
}
36633665

3666+
function emitJsxNamespacedName(node: JsxNamespacedName) {
3667+
emitIdentifierName(node.namespace);
3668+
writePunctuation(":");
3669+
emitIdentifierName(node.name);
3670+
}
3671+
36643672
function emitJsxTagName(node: JsxTagNameExpression) {
36653673
if (node.kind === SyntaxKind.Identifier) {
36663674
emitExpression(node);

src/compiler/factory/nodeFactory.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,9 @@ namespace ts {
414414
updateJsxSpreadAttribute,
415415
createJsxExpression,
416416
updateJsxExpression,
417+
createJsxNamespacedName,
418+
updateJsxNamespacedName,
419+
417420
createCaseClause,
418421
updateCaseClause,
419422
createDefaultClause,
@@ -4977,6 +4980,26 @@ namespace ts {
49774980
: node;
49784981
}
49794982

4983+
// @api
4984+
function createJsxNamespacedName(namespace: Identifier, name: Identifier) {
4985+
const node = createBaseNode<JsxNamespacedName>(SyntaxKind.JsxNamespacedName);
4986+
node.namespace = namespace;
4987+
node.name = name;
4988+
node.transformFlags |=
4989+
propagateChildFlags(node.namespace) |
4990+
propagateChildFlags(node.name) |
4991+
TransformFlags.ContainsJsx;
4992+
return node;
4993+
}
4994+
4995+
// @api
4996+
function updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier) {
4997+
return node.namespace !== namespace
4998+
|| node.name !== name
4999+
? update(createJsxNamespacedName(namespace, name), node)
5000+
: node;
5001+
}
5002+
49805003
//
49815004
// Clauses
49825005
//

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
@@ -483,7 +483,9 @@ namespace ts {
483483
visitNode(cbNode, (node as JsxExpression).expression);
484484
case SyntaxKind.JsxClosingElement:
485485
return visitNode(cbNode, (node as JsxClosingElement).tagName);
486-
486+
case SyntaxKind.JsxNamespacedName:
487+
return visitNode(cbNode, (node as JsxNamespacedName).namespace) ||
488+
visitNode(cbNode, (node as JsxNamespacedName).name);
487489
case SyntaxKind.OptionalType:
488490
case SyntaxKind.RestType:
489491
case SyntaxKind.JSDocTypeExpression:
@@ -5270,20 +5272,31 @@ namespace ts {
52705272

52715273
function parseJsxElementName(): JsxTagNameExpression {
52725274
const pos = getNodePos();
5273-
scanJsxIdentifier();
52745275
// JsxElement can have name in the form of
52755276
// propertyAccessExpression
52765277
// primaryExpression in the form of an identifier and "this" keyword
52775278
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
52785279
// We only want to consider "this" as a primaryExpression
5279-
let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ?
5280-
parseTokenNode<ThisExpression>() : parseIdentifierName();
5280+
let expression: JsxTagNameExpression = parseJsxTagName();
52815281
while (parseOptional(SyntaxKind.DotToken)) {
52825282
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
52835283
}
52845284
return expression;
52855285
}
52865286

5287+
function parseJsxTagName(): Identifier | JsxNamespacedName | ThisExpression {
5288+
const pos = getNodePos();
5289+
scanJsxIdentifier();
5290+
5291+
const isThis = token() === SyntaxKind.ThisKeyword;
5292+
const tagName = parseIdentifierName();
5293+
if (parseOptional(SyntaxKind.ColonToken)) {
5294+
scanJsxIdentifier();
5295+
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierName()), pos);
5296+
}
5297+
return isThis ? finishNode(factory.createToken(SyntaxKind.ThisKeyword), pos) : tagName;
5298+
}
5299+
52875300
function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined {
52885301
const pos = getNodePos();
52895302
if (!parseExpected(SyntaxKind.OpenBraceToken)) {
@@ -5316,11 +5329,10 @@ namespace ts {
53165329
return parseJsxSpreadAttribute();
53175330
}
53185331

5319-
scanJsxIdentifier();
53205332
const pos = getNodePos();
53215333
return finishNode(
53225334
factory.createJsxAttribute(
5323-
parseIdentifierName(),
5335+
parseJsxAttributeName(),
53245336
token() !== SyntaxKind.EqualsToken ? undefined :
53255337
scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral :
53265338
parseJsxExpression(/*inExpressionContext*/ true)
@@ -5329,6 +5341,18 @@ namespace ts {
53295341
);
53305342
}
53315343

5344+
function parseJsxAttributeName() {
5345+
const pos = getNodePos();
5346+
scanJsxIdentifier();
5347+
5348+
const attrName = parseIdentifierName();
5349+
if (parseOptional(SyntaxKind.ColonToken)) {
5350+
scanJsxIdentifier();
5351+
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierName()), pos);
5352+
}
5353+
return attrName;
5354+
}
5355+
53325356
function parseJsxSpreadAttribute(): JsxSpreadAttribute {
53335357
const pos = getNodePos();
53345358
parseExpected(SyntaxKind.OpenBraceToken);
@@ -9565,6 +9589,11 @@ namespace ts {
95659589
return true;
95669590
}
95679591

9592+
if (lhs.kind === SyntaxKind.JsxNamespacedName) {
9593+
return lhs.namespace.escapedText === (rhs as JsxNamespacedName).namespace.escapedText &&
9594+
lhs.name.escapedText === (rhs as JsxNamespacedName).name.escapedText;
9595+
}
9596+
95689597
// If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only
95699598
// take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression
95709599
// 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,
@@ -2546,17 +2547,25 @@ namespace ts {
25462547
| Identifier
25472548
| ThisExpression
25482549
| JsxTagNamePropertyAccess
2550+
| JsxNamespacedName
25492551
;
25502552

25512553
export interface JsxTagNamePropertyAccess extends PropertyAccessExpression {
25522554
readonly expression: JsxTagNameExpression;
25532555
}
25542556

2555-
export interface JsxAttributes extends ObjectLiteralExpressionBase<JsxAttributeLike> {
2557+
export interface JsxAttributes extends PrimaryExpression, Declaration {
2558+
readonly properties: NodeArray<JsxAttributeLike>;
25562559
readonly kind: SyntaxKind.JsxAttributes;
25572560
readonly parent: JsxOpeningLikeElement;
25582561
}
25592562

2563+
export interface JsxNamespacedName extends PrimaryExpression {
2564+
readonly kind: SyntaxKind.JsxNamespacedName;
2565+
readonly name: Identifier;
2566+
readonly namespace: Identifier;
2567+
}
2568+
25602569
/// The opening element of a <Tag>...</Tag> JsxElement
25612570
export interface JsxOpeningElement extends Expression {
25622571
readonly kind: SyntaxKind.JsxOpeningElement;
@@ -2594,16 +2603,17 @@ namespace ts {
25942603
readonly parent: JsxFragment;
25952604
}
25962605

2597-
export interface JsxAttribute extends ObjectLiteralElement {
2606+
export interface JsxAttribute extends Declaration {
25982607
readonly kind: SyntaxKind.JsxAttribute;
25992608
readonly parent: JsxAttributes;
2600-
readonly name: Identifier;
2609+
readonly name: Identifier | JsxNamespacedName;
26012610
/// JSX attribute initializers are optional; <X y /> is sugar for <X y={true} />
26022611
readonly initializer?: StringLiteral | JsxExpression;
26032612
}
26042613

2605-
export interface JsxSpreadAttribute extends ObjectLiteralElement {
2614+
export interface JsxSpreadAttribute extends Declaration {
26062615
readonly kind: SyntaxKind.JsxSpreadAttribute;
2616+
readonly name: PropertyName;
26072617
readonly parent: JsxAttributes;
26082618
readonly expression: Expression;
26092619
}
@@ -7599,14 +7609,16 @@ namespace ts {
75997609
createJsxOpeningFragment(): JsxOpeningFragment;
76007610
createJsxJsxClosingFragment(): JsxClosingFragment;
76017611
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
7602-
createJsxAttribute(name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7603-
updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7612+
createJsxAttribute(name: Identifier | JsxNamespacedName, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
7613+
updateJsxAttribute(node: JsxAttribute, name: Identifier | JsxNamespacedName, initializer: StringLiteral | JsxExpression | undefined): JsxAttribute;
76047614
createJsxAttributes(properties: readonly JsxAttributeLike[]): JsxAttributes;
76057615
updateJsxAttributes(node: JsxAttributes, properties: readonly JsxAttributeLike[]): JsxAttributes;
76067616
createJsxSpreadAttribute(expression: Expression): JsxSpreadAttribute;
76077617
updateJsxSpreadAttribute(node: JsxSpreadAttribute, expression: Expression): JsxSpreadAttribute;
76087618
createJsxExpression(dotDotDotToken: DotDotDotToken | undefined, expression: Expression | undefined): JsxExpression;
76097619
updateJsxExpression(node: JsxExpression, expression: Expression | undefined): JsxExpression;
7620+
createJsxNamespacedName(namespace: Identifier, name: Identifier): JsxNamespacedName;
7621+
updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier): JsxNamespacedName;
76107622

76117623
//
76127624
// Clauses

0 commit comments

Comments
 (0)