Skip to content

Commit 0c5be02

Browse files
authored
feat(7411): JSX namespaced attribute syntax not supported (#47356)
1 parent f306e4e commit 0c5be02

File tree

52 files changed

+828
-376
lines changed

Some content is hidden

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

52 files changed

+828
-376
lines changed

src/compiler/checker.ts

+16-27
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ import {
275275
getEntityNameFromTypeNode,
276276
getErrorSpanForNode,
277277
getEscapedTextOfIdentifierOrLiteral,
278+
getEscapedTextOfJsxAttributeName,
278279
getESModuleInterop,
279280
getExpandoInitializer,
280281
getExportAssignmentExpression,
@@ -350,6 +351,7 @@ import {
350351
getSymbolNameForPrivateIdentifier,
351352
getTextOfIdentifierOrLiteral,
352353
getTextOfJSDocComment,
354+
getTextOfJsxAttributeName,
353355
getTextOfNode,
354356
getTextOfPropertyName,
355357
getThisContainer,
@@ -593,6 +595,7 @@ import {
593595
isJsxAttributeLike,
594596
isJsxAttributes,
595597
isJsxElement,
598+
isJsxNamespacedName,
596599
isJsxOpeningElement,
597600
isJsxOpeningFragment,
598601
isJsxOpeningLikeElement,
@@ -807,7 +810,6 @@ import {
807810
MappedTypeNode,
808811
MatchingKeys,
809812
maybeBind,
810-
MemberName,
811813
MemberOverrideStatus,
812814
memoize,
813815
MetaProperty,
@@ -13517,7 +13519,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1351713519
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
1351813520
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
1351913521
return list.some(property => {
13520-
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
13522+
const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(property.name)) : getLiteralTypeFromPropertyName(property.name));
1352113523
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
1352213524
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
1352313525
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
@@ -19590,8 +19592,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1959019592
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
1959119593
if (!length(node.properties)) return;
1959219594
for (const prop of node.properties) {
19593-
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
19594-
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
19595+
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
19596+
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
1959519597
}
1959619598
}
1959719599

@@ -29266,7 +29268,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2926629268
if (!attributesType || isTypeAny(attributesType)) {
2926729269
return undefined;
2926829270
}
29269-
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
29271+
return getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name));
2927029272
}
2927129273
else {
2927229274
return getContextualType(attribute.parent, contextFlags);
@@ -30400,12 +30402,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3040030402
attributeSymbol.links.target = member;
3040130403
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
3040230404
allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
30403-
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
30405+
if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) {
3040430406
explicitlySpecifyChildrenAttribute = true;
3040530407
}
3040630408
if (contextualType) {
3040730409
const prop = getPropertyOfType(contextualType, member.escapedName);
30408-
if (prop && prop.declarations && isDeprecatedSymbol(prop)) {
30410+
if (prop && prop.declarations && isDeprecatedSymbol(prop) && isIdentifier(attributeDecl.name)) {
3040930411
addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string);
3041030412
}
3041130413
}
@@ -47852,8 +47854,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4785247854
}
4785347855

4785447856
const { name, initializer } = attr;
47855-
if (!seen.get(name.escapedText)) {
47856-
seen.set(name.escapedText, true);
47857+
const escapedText = getEscapedTextOfJsxAttributeName(name);
47858+
if (!seen.get(escapedText)) {
47859+
seen.set(escapedText, true);
4785747860
}
4785847861
else {
4785947862
return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name);
@@ -47866,25 +47869,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4786647869
}
4786747870

4786847871
function checkGrammarJsxName(node: JsxTagNameExpression) {
47869-
if (isPropertyAccessExpression(node)) {
47870-
let propName: JsxTagNameExpression = node;
47871-
do {
47872-
const check = checkGrammarJsxNestedIdentifier(propName.name);
47873-
if (check) {
47874-
return check;
47875-
}
47876-
propName = propName.expression;
47877-
} while (isPropertyAccessExpression(propName));
47878-
const check = checkGrammarJsxNestedIdentifier(propName);
47879-
if (check) {
47880-
return check;
47881-
}
47872+
if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) {
47873+
return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
4788247874
}
47883-
47884-
function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) {
47885-
if (isIdentifier(name) && idText(name).indexOf(":") !== -1) {
47886-
return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
47887-
}
47875+
if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) {
47876+
return grammarErrorOnNode(node, Diagnostics.React_components_cannot_include_JSX_namespace_names);
4788847877
}
4788947878
}
4789047879

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -2893,6 +2893,10 @@
28932893
"category": "Error",
28942894
"code": 2638
28952895
},
2896+
"React components cannot include JSX namespace names": {
2897+
"category": "Error",
2898+
"code": 2639
2899+
},
28962900

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

src/compiler/emitter.ts

+9
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ import {
289289
JsxEmit,
290290
JsxExpression,
291291
JsxFragment,
292+
JsxNamespacedName,
292293
JsxOpeningElement,
293294
JsxOpeningFragment,
294295
JsxSelfClosingElement,
@@ -2283,6 +2284,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
22832284
return emitJsxSelfClosingElement(node as JsxSelfClosingElement);
22842285
case SyntaxKind.JsxFragment:
22852286
return emitJsxFragment(node as JsxFragment);
2287+
case SyntaxKind.JsxNamespacedName:
2288+
return emitJsxNamespacedName(node as JsxNamespacedName);
22862289

22872290
// Synthesized list
22882291
case SyntaxKind.SyntaxList:
@@ -4225,6 +4228,12 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
42254228
}
42264229
}
42274230

4231+
function emitJsxNamespacedName(node: JsxNamespacedName) {
4232+
emitIdentifierName(node.namespace);
4233+
writePunctuation(":");
4234+
emitIdentifierName(node.name);
4235+
}
4236+
42284237
function emitJsxTagName(node: JsxTagNameExpression) {
42294238
if (node.kind === SyntaxKind.Identifier) {
42304239
emitExpression(node);

src/compiler/factory/nodeFactory.ts

+26-2
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ import {
267267
JSDocVariadicType,
268268
JsxAttribute,
269269
JsxAttributeLike,
270+
JsxAttributeName,
270271
JsxAttributes,
271272
JsxAttributeValue,
272273
JsxChild,
@@ -275,6 +276,7 @@ import {
275276
JsxElement,
276277
JsxExpression,
277278
JsxFragment,
279+
JsxNamespacedName,
278280
JsxOpeningElement,
279281
JsxOpeningFragment,
280282
JsxSelfClosingElement,
@@ -908,6 +910,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
908910
updateJsxSpreadAttribute,
909911
createJsxExpression,
910912
updateJsxExpression,
913+
createJsxNamespacedName,
914+
updateJsxNamespacedName,
911915
createCaseClause,
912916
updateCaseClause,
913917
createDefaultClause,
@@ -5582,7 +5586,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
55825586
}
55835587

55845588
// @api
5585-
function createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined) {
5589+
function createJsxAttribute(name: JsxAttributeName, initializer: JsxAttributeValue | undefined) {
55865590
const node = createBaseDeclaration<JsxAttribute>(SyntaxKind.JsxAttribute);
55875591
node.name = name;
55885592
node.initializer = initializer;
@@ -5594,7 +5598,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
55945598
}
55955599

55965600
// @api
5597-
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) {
5601+
function updateJsxAttribute(node: JsxAttribute, name: JsxAttributeName, initializer: JsxAttributeValue | undefined) {
55985602
return node.name !== name
55995603
|| node.initializer !== initializer
56005604
? update(createJsxAttribute(name, initializer), node)
@@ -5654,6 +5658,26 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
56545658
: node;
56555659
}
56565660

5661+
// @api
5662+
function createJsxNamespacedName(namespace: Identifier, name: Identifier) {
5663+
const node = createBaseNode<JsxNamespacedName>(SyntaxKind.JsxNamespacedName);
5664+
node.namespace = namespace;
5665+
node.name = name;
5666+
node.transformFlags |=
5667+
propagateChildFlags(node.namespace) |
5668+
propagateChildFlags(node.name) |
5669+
TransformFlags.ContainsJsx;
5670+
return node;
5671+
}
5672+
5673+
// @api
5674+
function updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier) {
5675+
return node.namespace !== namespace
5676+
|| node.name !== name
5677+
? update(createJsxNamespacedName(namespace, name), node)
5678+
: node;
5679+
}
5680+
56575681
//
56585682
// Clauses
56595683
//

src/compiler/factory/nodeTests.ts

+5
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ import {
128128
JsxElement,
129129
JsxExpression,
130130
JsxFragment,
131+
JsxNamespacedName,
131132
JsxOpeningElement,
132133
JsxOpeningFragment,
133134
JsxSelfClosingElement,
@@ -963,6 +964,10 @@ export function isJsxExpression(node: Node): node is JsxExpression {
963964
return node.kind === SyntaxKind.JsxExpression;
964965
}
965966

967+
export function isJsxNamespacedName(node: Node): node is JsxNamespacedName {
968+
return node.kind === SyntaxKind.JsxNamespacedName;
969+
}
970+
966971
// Clauses
967972

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

src/compiler/parser.ts

+37-5
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ import {
221221
JsxElement,
222222
JsxExpression,
223223
JsxFragment,
224+
JsxNamespacedName,
224225
JsxOpeningElement,
225226
JsxOpeningFragment,
226227
JsxOpeningLikeElement,
@@ -1030,6 +1031,10 @@ const forEachChildTable: ForEachChildTable = {
10301031
[SyntaxKind.JsxClosingElement]: function forEachChildInJsxClosingElement<T>(node: JsxClosingElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
10311032
return visitNode(cbNode, node.tagName);
10321033
},
1034+
[SyntaxKind.JsxNamespacedName]: function forEachChildInJsxNamespacedName<T>(node: JsxNamespacedName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
1035+
return visitNode(cbNode, node.namespace) ||
1036+
visitNode(cbNode, node.name);
1037+
},
10331038
[SyntaxKind.OptionalType]: forEachChildInOptionalRestOrJSDocParameterModifier,
10341039
[SyntaxKind.RestType]: forEachChildInOptionalRestOrJSDocParameterModifier,
10351040
[SyntaxKind.JSDocTypeExpression]: forEachChildInOptionalRestOrJSDocParameterModifier,
@@ -6102,20 +6107,31 @@ namespace Parser {
61026107

61036108
function parseJsxElementName(): JsxTagNameExpression {
61046109
const pos = getNodePos();
6105-
scanJsxIdentifier();
61066110
// JsxElement can have name in the form of
61076111
// propertyAccessExpression
61086112
// primaryExpression in the form of an identifier and "this" keyword
61096113
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
61106114
// We only want to consider "this" as a primaryExpression
6111-
let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ?
6112-
parseTokenNode<ThisExpression>() : parseIdentifierName();
6115+
let expression: JsxTagNameExpression = parseJsxTagName();
61136116
while (parseOptional(SyntaxKind.DotToken)) {
61146117
expression = finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
61156118
}
61166119
return expression;
61176120
}
61186121

6122+
function parseJsxTagName(): Identifier | JsxNamespacedName | ThisExpression {
6123+
const pos = getNodePos();
6124+
scanJsxIdentifier();
6125+
6126+
const isThis = token() === SyntaxKind.ThisKeyword;
6127+
const tagName = parseIdentifierName();
6128+
if (parseOptional(SyntaxKind.ColonToken)) {
6129+
scanJsxIdentifier();
6130+
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierName()), pos);
6131+
}
6132+
return isThis ? finishNode(factory.createToken(SyntaxKind.ThisKeyword), pos) : tagName;
6133+
}
6134+
61196135
function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined {
61206136
const pos = getNodePos();
61216137
if (!parseExpected(SyntaxKind.OpenBraceToken)) {
@@ -6148,9 +6164,8 @@ namespace Parser {
61486164
return parseJsxSpreadAttribute();
61496165
}
61506166

6151-
scanJsxIdentifier();
61526167
const pos = getNodePos();
6153-
return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos);
6168+
return finishNode(factory.createJsxAttribute(parseJsxAttributeName(), parseJsxAttributeValue()), pos);
61546169
}
61556170

61566171
function parseJsxAttributeValue(): JsxAttributeValue | undefined {
@@ -6169,6 +6184,18 @@ namespace Parser {
61696184
return undefined;
61706185
}
61716186

6187+
function parseJsxAttributeName() {
6188+
const pos = getNodePos();
6189+
scanJsxIdentifier();
6190+
6191+
const attrName = parseIdentifierName();
6192+
if (parseOptional(SyntaxKind.ColonToken)) {
6193+
scanJsxIdentifier();
6194+
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierName()), pos);
6195+
}
6196+
return attrName;
6197+
}
6198+
61726199
function parseJsxSpreadAttribute(): JsxSpreadAttribute {
61736200
const pos = getNodePos();
61746201
parseExpected(SyntaxKind.OpenBraceToken);
@@ -10425,6 +10452,11 @@ export function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagName
1042510452
return true;
1042610453
}
1042710454

10455+
if (lhs.kind === SyntaxKind.JsxNamespacedName) {
10456+
return lhs.namespace.escapedText === (rhs as JsxNamespacedName).namespace.escapedText &&
10457+
lhs.name.escapedText === (rhs as JsxNamespacedName).name.escapedText;
10458+
}
10459+
1042810460
// If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only
1042910461
// take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression
1043010462
// 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

-13
Original file line numberDiff line numberDiff line change
@@ -2543,32 +2543,19 @@ export function createScanner(languageVersion: ScriptTarget,
25432543
// everything after it to the token
25442544
// Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token
25452545
// Any caller should be expecting this behavior and should only read the pos or token value after calling it.
2546-
let namespaceSeparator = false;
25472546
while (pos < end) {
25482547
const ch = text.charCodeAt(pos);
25492548
if (ch === CharacterCodes.minus) {
25502549
tokenValue += "-";
25512550
pos++;
25522551
continue;
25532552
}
2554-
else if (ch === CharacterCodes.colon && !namespaceSeparator) {
2555-
tokenValue += ":";
2556-
pos++;
2557-
namespaceSeparator = true;
2558-
token = SyntaxKind.Identifier; // swap from keyword kind to identifier kind
2559-
continue;
2560-
}
25612553
const oldPos = pos;
25622554
tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled
25632555
if (pos === oldPos) {
25642556
break;
25652557
}
25662558
}
2567-
// Do not include a trailing namespace separator in the token, since this is against the spec.
2568-
if (tokenValue.slice(-1) === ":") {
2569-
tokenValue = tokenValue.slice(0, -1);
2570-
pos--;
2571-
}
25722559
return getIdentifierToken();
25732560
}
25742561
return token;

0 commit comments

Comments
 (0)