Skip to content

Commit 2ff0115

Browse files
committed
feat(7411): add JSXNamespacedName
1 parent ba3645e commit 2ff0115

File tree

50 files changed

+777
-368
lines changed

Some content is hidden

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

50 files changed

+777
-368
lines changed

src/compiler/checker.ts

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11872,7 +11872,7 @@ namespace ts {
1187211872
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
1187311873
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
1187411874
return list.some(property => {
11875-
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
11875+
const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(property.name)) : getLiteralTypeFromPropertyName(property.name));
1187611876
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
1187711877
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
1187811878
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
@@ -17590,8 +17590,8 @@ namespace ts {
1759017590
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
1759117591
if (!length(node.properties)) return;
1759217592
for (const prop of node.properties) {
17593-
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
17594-
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
17593+
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
17594+
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
1759517595
}
1759617596
}
1759717597

@@ -26805,7 +26805,7 @@ namespace ts {
2680526805
if (!attributesType || isTypeAny(attributesType)) {
2680626806
return undefined;
2680726807
}
26808-
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
26808+
return getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name));
2680926809
}
2681026810
else {
2681126811
return getContextualType(attribute.parent);
@@ -27858,7 +27858,7 @@ namespace ts {
2785827858
attributeSymbol.target = member;
2785927859
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
2786027860
allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
27861-
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
27861+
if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) {
2786227862
explicitlySpecifyChildrenAttribute = true;
2786327863
}
2786427864
}
@@ -43868,8 +43868,9 @@ namespace ts {
4386843868
}
4386943869

4387043870
const { name, initializer } = attr;
43871-
if (!seen.get(name.escapedText)) {
43872-
seen.set(name.escapedText, true);
43871+
const escapedText = getEscapedTextOfJsxAttributeName(name);
43872+
if (!seen.get(escapedText)) {
43873+
seen.set(escapedText, true);
4387343874
}
4387443875
else {
4387543876
return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name);
@@ -43882,25 +43883,11 @@ namespace ts {
4388243883
}
4388343884

4388443885
function checkGrammarJsxName(node: JsxTagNameExpression) {
43885-
if (isPropertyAccessExpression(node)) {
43886-
let propName: JsxTagNameExpression = node;
43887-
do {
43888-
const check = checkGrammarJsxNestedIdentifier(propName.name);
43889-
if (check) {
43890-
return check;
43891-
}
43892-
propName = propName.expression;
43893-
} while (isPropertyAccessExpression(propName));
43894-
const check = checkGrammarJsxNestedIdentifier(propName);
43895-
if (check) {
43896-
return check;
43897-
}
43886+
if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) {
43887+
return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
4389843888
}
43899-
43900-
function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) {
43901-
if (isIdentifier(name) && idText(name).indexOf(":") !== -1) {
43902-
return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
43903-
}
43889+
if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) {
43890+
return grammarErrorOnNode(node, Diagnostics.React_components_cannot_include_JSX_namespace_names);
4390443891
}
4390543892
}
4390643893

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2739,6 +2739,10 @@
27392739
"category": "Error",
27402740
"code": 2636
27412741
},
2742+
"React components cannot include JSX namespace names": {
2743+
"category": "Error",
2744+
"code": 2637
2745+
},
27422746

27432747
"Cannot augment module '{0}' with value exports because it resolves to a non-module entity.": {
27442748
"category": "Error",

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:
@@ -3662,6 +3664,12 @@ namespace ts {
36623664
}
36633665
}
36643666

3667+
function emitJsxNamespacedName(node: JsxNamespacedName) {
3668+
emitIdentifierName(node.namespace);
3669+
writePunctuation(":");
3670+
emitIdentifierName(node.name);
3671+
}
3672+
36653673
function emitJsxTagName(node: JsxTagNameExpression) {
36663674
if (node.kind === SyntaxKind.Identifier) {
36673675
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,
@@ -5014,6 +5017,26 @@ namespace ts {
50145017
: node;
50155018
}
50165019

5020+
// @api
5021+
function createJsxNamespacedName(namespace: Identifier, name: Identifier) {
5022+
const node = createBaseNode<JsxNamespacedName>(SyntaxKind.JsxNamespacedName);
5023+
node.namespace = namespace;
5024+
node.name = name;
5025+
node.transformFlags |=
5026+
propagateChildFlags(node.namespace) |
5027+
propagateChildFlags(node.name) |
5028+
TransformFlags.ContainsJsx;
5029+
return node;
5030+
}
5031+
5032+
// @api
5033+
function updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier) {
5034+
return node.namespace !== namespace
5035+
|| node.name !== name
5036+
? update(createJsxNamespacedName(namespace, name), node)
5037+
: node;
5038+
}
5039+
50175040
//
50185041
// Clauses
50195042
//

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
@@ -519,7 +519,9 @@ namespace ts {
519519
visitNode(cbNode, (node as JsxExpression).expression);
520520
case SyntaxKind.JsxClosingElement:
521521
return visitNode(cbNode, (node as JsxClosingElement).tagName);
522-
522+
case SyntaxKind.JsxNamespacedName:
523+
return visitNode(cbNode, (node as JsxNamespacedName).namespace) ||
524+
visitNode(cbNode, (node as JsxNamespacedName).name);
523525
case SyntaxKind.OptionalType:
524526
case SyntaxKind.RestType:
525527
case SyntaxKind.JSDocTypeExpression:
@@ -5371,20 +5373,31 @@ namespace ts {
53715373

53725374
function parseJsxElementName(): JsxTagNameExpression {
53735375
const pos = getNodePos();
5374-
scanJsxIdentifier();
53755376
// JsxElement can have name in the form of
53765377
// propertyAccessExpression
53775378
// primaryExpression in the form of an identifier and "this" keyword
53785379
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
53795380
// We only want to consider "this" as a primaryExpression
5380-
let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ?
5381-
parseTokenNode<ThisExpression>() : parseIdentifierName();
5381+
let expression: JsxTagNameExpression = parseJsxTagName();
53825382
while (parseOptional(SyntaxKind.DotToken)) {
53835383
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
53845384
}
53855385
return expression;
53865386
}
53875387

5388+
function parseJsxTagName(): Identifier | JsxNamespacedName | ThisExpression {
5389+
const pos = getNodePos();
5390+
scanJsxIdentifier();
5391+
5392+
const isThis = token() === SyntaxKind.ThisKeyword;
5393+
const tagName = parseIdentifierName();
5394+
if (parseOptional(SyntaxKind.ColonToken)) {
5395+
scanJsxIdentifier();
5396+
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierName()), pos);
5397+
}
5398+
return isThis ? finishNode(factory.createToken(SyntaxKind.ThisKeyword), pos) : tagName;
5399+
}
5400+
53885401
function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined {
53895402
const pos = getNodePos();
53905403
if (!parseExpected(SyntaxKind.OpenBraceToken)) {
@@ -5417,11 +5430,10 @@ namespace ts {
54175430
return parseJsxSpreadAttribute();
54185431
}
54195432

5420-
scanJsxIdentifier();
54215433
const pos = getNodePos();
54225434
return finishNode(
54235435
factory.createJsxAttribute(
5424-
parseIdentifierName(),
5436+
parseJsxAttributeName(),
54255437
token() !== SyntaxKind.EqualsToken ? undefined :
54265438
scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral :
54275439
parseJsxExpression(/*inExpressionContext*/ true)
@@ -5430,6 +5442,18 @@ namespace ts {
54305442
);
54315443
}
54325444

5445+
function parseJsxAttributeName() {
5446+
const pos = getNodePos();
5447+
scanJsxIdentifier();
5448+
5449+
const attrName = parseIdentifierName();
5450+
if (parseOptional(SyntaxKind.ColonToken)) {
5451+
scanJsxIdentifier();
5452+
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierName()), pos);
5453+
}
5454+
return attrName;
5455+
}
5456+
54335457
function parseJsxSpreadAttribute(): JsxSpreadAttribute {
54345458
const pos = getNodePos();
54355459
parseExpected(SyntaxKind.OpenBraceToken);
@@ -9617,6 +9641,11 @@ namespace ts {
96179641
return true;
96189642
}
96199643

9644+
if (lhs.kind === SyntaxKind.JsxNamespacedName) {
9645+
return lhs.namespace.escapedText === (rhs as JsxNamespacedName).namespace.escapedText &&
9646+
lhs.name.escapedText === (rhs as JsxNamespacedName).name.escapedText;
9647+
}
9648+
96209649
// If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only
96219650
// take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression
96229651
// 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
@@ -2349,32 +2349,19 @@ namespace ts {
23492349
// everything after it to the token
23502350
// Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token
23512351
// Any caller should be expecting this behavior and should only read the pos or token value after calling it.
2352-
let namespaceSeparator = false;
23532352
while (pos < end) {
23542353
const ch = text.charCodeAt(pos);
23552354
if (ch === CharacterCodes.minus) {
23562355
tokenValue += "-";
23572356
pos++;
23582357
continue;
23592358
}
2360-
else if (ch === CharacterCodes.colon && !namespaceSeparator) {
2361-
tokenValue += ":";
2362-
pos++;
2363-
namespaceSeparator = true;
2364-
token = SyntaxKind.Identifier; // swap from keyword kind to identifier kind
2365-
continue;
2366-
}
23672359
const oldPos = pos;
23682360
tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled
23692361
if (pos === oldPos) {
23702362
break;
23712363
}
23722364
}
2373-
// Do not include a trailing namespace separator in the token, since this is against the spec.
2374-
if (tokenValue.slice(-1) === ":") {
2375-
tokenValue = tokenValue.slice(0, -1);
2376-
pos--;
2377-
}
23782365
return getIdentifierToken();
23792366
}
23802367
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) {

0 commit comments

Comments
 (0)