Skip to content

Commit ff771af

Browse files
committed
feat(7411): add JSXNamespacedName
1 parent 1622247 commit ff771af

File tree

51 files changed

+785
-370
lines changed

Some content is hidden

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

51 files changed

+785
-370
lines changed

src/compiler/checker.ts

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11995,7 +11995,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
1199511995
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
1199611996
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
1199711997
return list.some(property => {
11998-
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
11998+
const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(property.name)) : getLiteralTypeFromPropertyName(property.name));
1199911999
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
1200012000
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
1200112001
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
@@ -17790,8 +17790,8 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
1779017790
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
1779117791
if (!length(node.properties)) return;
1779217792
for (const prop of node.properties) {
17793-
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
17794-
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
17793+
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
17794+
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
1779517795
}
1779617796
}
1779717797

@@ -27195,7 +27195,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2719527195
if (!attributesType || isTypeAny(attributesType)) {
2719627196
return undefined;
2719727197
}
27198-
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
27198+
return getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name));
2719927199
}
2720027200
else {
2720127201
return getContextualType(attribute.parent, contextFlags);
@@ -28247,7 +28247,7 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
2824728247
attributeSymbol.target = member;
2824828248
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
2824928249
allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
28250-
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
28250+
if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) {
2825128251
explicitlySpecifyChildrenAttribute = true;
2825228252
}
2825328253
}
@@ -44622,8 +44622,9 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
4462244622
}
4462344623

4462444624
const { name, initializer } = attr;
44625-
if (!seen.get(name.escapedText)) {
44626-
seen.set(name.escapedText, true);
44625+
const escapedText = getEscapedTextOfJsxAttributeName(name);
44626+
if (!seen.get(escapedText)) {
44627+
seen.set(escapedText, true);
4462744628
}
4462844629
else {
4462944630
return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name);
@@ -44636,25 +44637,11 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n")
4463644637
}
4463744638

4463844639
function checkGrammarJsxName(node: JsxTagNameExpression) {
44639-
if (isPropertyAccessExpression(node)) {
44640-
let propName: JsxTagNameExpression = node;
44641-
do {
44642-
const check = checkGrammarJsxNestedIdentifier(propName.name);
44643-
if (check) {
44644-
return check;
44645-
}
44646-
propName = propName.expression;
44647-
} while (isPropertyAccessExpression(propName));
44648-
const check = checkGrammarJsxNestedIdentifier(propName);
44649-
if (check) {
44650-
return check;
44651-
}
44640+
if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) {
44641+
return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
4465244642
}
44653-
44654-
function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) {
44655-
if (isIdentifier(name) && idText(name).indexOf(":") !== -1) {
44656-
return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
44657-
}
44643+
if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) {
44644+
return grammarErrorOnNode(node, Diagnostics.React_components_cannot_include_JSX_namespace_names);
4465844645
}
4465944646
}
4466044647

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,6 +2789,10 @@
27892789
"category": "Error",
27902790
"code": 2637
27912791
},
2792+
"React components cannot include JSX namespace names": {
2793+
"category": "Error",
2794+
"code": 2638
2795+
},
27922796

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

src/compiler/emitter.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,8 @@ namespace ts {
17761776
return emitJsxSelfClosingElement(node as JsxSelfClosingElement);
17771777
case SyntaxKind.JsxFragment:
17781778
return emitJsxFragment(node as JsxFragment);
1779+
case SyntaxKind.JsxNamespacedName:
1780+
return emitJsxNamespacedName(node as JsxNamespacedName);
17791781

17801782
// Synthesized list
17811783
case SyntaxKind.SyntaxList:
@@ -3679,6 +3681,12 @@ namespace ts {
36793681
}
36803682
}
36813683

3684+
function emitJsxNamespacedName(node: JsxNamespacedName) {
3685+
emitIdentifierName(node.namespace);
3686+
writePunctuation(":");
3687+
emitIdentifierName(node.name);
3688+
}
3689+
36823690
function emitJsxTagName(node: JsxTagNameExpression) {
36833691
if (node.kind === SyntaxKind.Identifier) {
36843692
emitExpression(node);

src/compiler/factory/nodeFactory.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,9 @@ namespace ts {
418418
updateJsxSpreadAttribute,
419419
createJsxExpression,
420420
updateJsxExpression,
421+
createJsxNamespacedName,
422+
updateJsxNamespacedName,
423+
421424
createCaseClause,
422425
updateCaseClause,
423426
createDefaultClause,
@@ -5050,6 +5053,26 @@ namespace ts {
50505053
: node;
50515054
}
50525055

5056+
// @api
5057+
function createJsxNamespacedName(namespace: Identifier, name: Identifier) {
5058+
const node = createBaseNode<JsxNamespacedName>(SyntaxKind.JsxNamespacedName);
5059+
node.namespace = namespace;
5060+
node.name = name;
5061+
node.transformFlags |=
5062+
propagateChildFlags(node.namespace) |
5063+
propagateChildFlags(node.name) |
5064+
TransformFlags.ContainsJsx;
5065+
return node;
5066+
}
5067+
5068+
// @api
5069+
function updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier) {
5070+
return node.namespace !== namespace
5071+
|| node.name !== name
5072+
? update(createJsxNamespacedName(namespace, name), node)
5073+
: node;
5074+
}
5075+
50535076
//
50545077
// Clauses
50555078
//

src/compiler/factory/nodeTests.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,10 @@ namespace ts {
721721
return node.kind === SyntaxKind.JsxExpression;
722722
}
723723

724+
export function isJsxNamespacedName(node: Node): node is JsxNamespacedName {
725+
return node.kind === SyntaxKind.JsxNamespacedName;
726+
}
727+
724728
// Clauses
725729

726730
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
@@ -573,7 +573,9 @@ namespace ts {
573573
visitNode(cbNode, (node as JsxExpression).expression);
574574
case SyntaxKind.JsxClosingElement:
575575
return visitNode(cbNode, (node as JsxClosingElement).tagName);
576-
576+
case SyntaxKind.JsxNamespacedName:
577+
return visitNode(cbNode, (node as JsxNamespacedName).namespace) ||
578+
visitNode(cbNode, (node as JsxNamespacedName).name);
577579
case SyntaxKind.OptionalType:
578580
case SyntaxKind.RestType:
579581
case SyntaxKind.JSDocTypeExpression:
@@ -5484,20 +5486,31 @@ namespace ts {
54845486

54855487
function parseJsxElementName(): JsxTagNameExpression {
54865488
const pos = getNodePos();
5487-
scanJsxIdentifier();
54885489
// JsxElement can have name in the form of
54895490
// propertyAccessExpression
54905491
// primaryExpression in the form of an identifier and "this" keyword
54915492
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
54925493
// We only want to consider "this" as a primaryExpression
5493-
let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ?
5494-
parseTokenNode<ThisExpression>() : parseIdentifierName();
5494+
let expression: JsxTagNameExpression = parseJsxTagName();
54955495
while (parseOptional(SyntaxKind.DotToken)) {
54965496
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
54975497
}
54985498
return expression;
54995499
}
55005500

5501+
function parseJsxTagName(): Identifier | JsxNamespacedName | ThisExpression {
5502+
const pos = getNodePos();
5503+
scanJsxIdentifier();
5504+
5505+
const isThis = token() === SyntaxKind.ThisKeyword;
5506+
const tagName = parseIdentifierName();
5507+
if (parseOptional(SyntaxKind.ColonToken)) {
5508+
scanJsxIdentifier();
5509+
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierName()), pos);
5510+
}
5511+
return isThis ? finishNode(factory.createToken(SyntaxKind.ThisKeyword), pos) : tagName;
5512+
}
5513+
55015514
function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined {
55025515
const pos = getNodePos();
55035516
if (!parseExpected(SyntaxKind.OpenBraceToken)) {
@@ -5530,9 +5543,8 @@ namespace ts {
55305543
return parseJsxSpreadAttribute();
55315544
}
55325545

5533-
scanJsxIdentifier();
55345546
const pos = getNodePos();
5535-
return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos);
5547+
return finishNode(factory.createJsxAttribute(parseJsxAttributeName(), parseJsxAttributeValue()), pos);
55365548
}
55375549

55385550
function parseJsxAttributeValue(): JsxAttributeValue | undefined {
@@ -5551,6 +5563,18 @@ namespace ts {
55515563
return undefined;
55525564
}
55535565

5566+
function parseJsxAttributeName() {
5567+
const pos = getNodePos();
5568+
scanJsxIdentifier();
5569+
5570+
const attrName = parseIdentifierName();
5571+
if (parseOptional(SyntaxKind.ColonToken)) {
5572+
scanJsxIdentifier();
5573+
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierName()), pos);
5574+
}
5575+
return attrName;
5576+
}
5577+
55545578
function parseJsxSpreadAttribute(): JsxSpreadAttribute {
55555579
const pos = getNodePos();
55565580
parseExpected(SyntaxKind.OpenBraceToken);
@@ -9760,6 +9784,11 @@ namespace ts {
97609784
return true;
97619785
}
97629786

9787+
if (lhs.kind === SyntaxKind.JsxNamespacedName) {
9788+
return lhs.namespace.escapedText === (rhs as JsxNamespacedName).namespace.escapedText &&
9789+
lhs.name.escapedText === (rhs as JsxNamespacedName).name.escapedText;
9790+
}
9791+
97639792
// If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only
97649793
// take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression
97659794
// 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
}
@@ -525,12 +525,15 @@ namespace ts {
525525
return getTagName(node.openingElement);
526526
}
527527
else {
528-
const name = node.tagName;
529-
if (isIdentifier(name) && isIntrinsicJsxName(name.escapedText)) {
530-
return factory.createStringLiteral(idText(name));
528+
const tagName = node.tagName;
529+
if (isIdentifier(tagName) && isIntrinsicJsxName(tagName.escapedText)) {
530+
return factory.createStringLiteral(idText(tagName));
531+
}
532+
else if (isJsxNamespacedName(tagName)) {
533+
return factory.createStringLiteral(idText(tagName.namespace) + ":" + idText(tagName.name));
531534
}
532535
else {
533-
return createExpressionFromEntityName(factory, name);
536+
return createExpressionFromEntityName(factory, tagName);
534537
}
535538
}
536539
}
@@ -542,13 +545,11 @@ namespace ts {
542545
*/
543546
function getAttributeName(node: JsxAttribute): StringLiteral | Identifier {
544547
const name = node.name;
545-
const text = idText(name);
546-
if (/^[A-Za-z_]\w*$/.test(text)) {
547-
return name;
548-
}
549-
else {
550-
return factory.createStringLiteral(text);
548+
if (isIdentifier(name)) {
549+
const text = idText(name);
550+
return (/^[A-Za-z_]\w*$/.test(text)) ? name : factory.createStringLiteral(text);
551551
}
552+
return factory.createStringLiteral(idText(name.namespace) + ":" + idText(name.name));
552553
}
553554

554555
function visitJsxExpression(node: JsxExpression) {

0 commit comments

Comments
 (0)