Skip to content

Revert "feat(7411): JSX namespaced attribute syntax not supported" #53791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 27 additions & 16 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ import {
getEntityNameFromTypeNode,
getErrorSpanForNode,
getEscapedTextOfIdentifierOrLiteral,
getEscapedTextOfJsxAttributeName,
getESModuleInterop,
getExpandoInitializer,
getExportAssignmentExpression,
Expand Down Expand Up @@ -351,7 +350,6 @@ import {
getSymbolNameForPrivateIdentifier,
getTextOfIdentifierOrLiteral,
getTextOfJSDocComment,
getTextOfJsxAttributeName,
getTextOfNode,
getTextOfPropertyName,
getThisContainer,
Expand Down Expand Up @@ -595,7 +593,6 @@ import {
isJsxAttributeLike,
isJsxAttributes,
isJsxElement,
isJsxNamespacedName,
isJsxOpeningElement,
isJsxOpeningFragment,
isJsxOpeningLikeElement,
Expand Down Expand Up @@ -810,6 +807,7 @@ import {
MappedTypeNode,
MatchingKeys,
maybeBind,
MemberName,
MemberOverrideStatus,
memoize,
MetaProperty,
Expand Down Expand Up @@ -13519,7 +13517,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function isTypeInvalidDueToUnionDiscriminant(contextualType: Type, obj: ObjectLiteralExpression | JsxAttributes): boolean {
const list = obj.properties as NodeArray<ObjectLiteralElementLike | JsxAttributeLike>;
return list.some(property => {
const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(property.name)) : getLiteralTypeFromPropertyName(property.name));
const nameType = property.name && getLiteralTypeFromPropertyName(property.name);
const name = nameType && isTypeUsableAsPropertyName(nameType) ? getPropertyNameFromType(nameType) : undefined;
const expected = name === undefined ? undefined : getTypeOfPropertyOfType(contextualType, name);
return !!expected && isLiteralType(expected) && !isTypeAssignableTo(getTypeOfNode(property), expected);
Expand Down Expand Up @@ -19592,8 +19590,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function *generateJsxAttributes(node: JsxAttributes): ElaborationIterator {
if (!length(node.properties)) return;
for (const prop of node.properties) {
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(idText(prop.name))) continue;
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
}
}

Expand Down Expand Up @@ -29268,7 +29266,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (!attributesType || isTypeAny(attributesType)) {
return undefined;
}
return getTypeOfPropertyOfContextualType(attributesType, getEscapedTextOfJsxAttributeName(attribute.name));
return getTypeOfPropertyOfContextualType(attributesType, attribute.name.escapedText);
}
else {
return getContextualType(attribute.parent, contextFlags);
Expand Down Expand Up @@ -30402,12 +30400,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
attributeSymbol.links.target = member;
attributesTable.set(attributeSymbol.escapedName, attributeSymbol);
allAttributesTable?.set(attributeSymbol.escapedName, attributeSymbol);
if (getEscapedTextOfJsxAttributeName(attributeDecl.name) === jsxChildrenPropertyName) {
if (attributeDecl.name.escapedText === jsxChildrenPropertyName) {
explicitlySpecifyChildrenAttribute = true;
}
if (contextualType) {
const prop = getPropertyOfType(contextualType, member.escapedName);
if (prop && prop.declarations && isDeprecatedSymbol(prop) && isIdentifier(attributeDecl.name)) {
if (prop && prop.declarations && isDeprecatedSymbol(prop)) {
addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string);
}
}
Expand Down Expand Up @@ -47854,9 +47852,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

const { name, initializer } = attr;
const escapedText = getEscapedTextOfJsxAttributeName(name);
if (!seen.get(escapedText)) {
seen.set(escapedText, true);
if (!seen.get(name.escapedText)) {
seen.set(name.escapedText, true);
}
else {
return grammarErrorOnNode(name, Diagnostics.JSX_elements_cannot_have_multiple_attributes_with_the_same_name);
Expand All @@ -47869,11 +47866,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function checkGrammarJsxName(node: JsxTagNameExpression) {
if (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) {
return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
if (isPropertyAccessExpression(node)) {
let propName: JsxTagNameExpression = node;
do {
const check = checkGrammarJsxNestedIdentifier(propName.name);
if (check) {
return check;
}
propName = propName.expression;
} while (isPropertyAccessExpression(propName));
const check = checkGrammarJsxNestedIdentifier(propName);
if (check) {
return check;
}
}
if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) {
return grammarErrorOnNode(node, Diagnostics.React_components_cannot_include_JSX_namespace_names);

function checkGrammarJsxNestedIdentifier(name: MemberName | ThisExpression) {
if (isIdentifier(name) && idText(name).indexOf(":") !== -1) {
return grammarErrorOnNode(name, Diagnostics.JSX_property_access_expressions_cannot_include_JSX_namespace_names);
}
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2893,10 +2893,6 @@
"category": "Error",
"code": 2638
},
"React components cannot include JSX namespace names": {
"category": "Error",
"code": 2639
},

"Cannot augment module '{0}' with value exports because it resolves to a non-module entity.": {
"category": "Error",
Expand Down
9 changes: 0 additions & 9 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,6 @@ import {
JsxEmit,
JsxExpression,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxSelfClosingElement,
Expand Down Expand Up @@ -2284,8 +2283,6 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return emitJsxSelfClosingElement(node as JsxSelfClosingElement);
case SyntaxKind.JsxFragment:
return emitJsxFragment(node as JsxFragment);
case SyntaxKind.JsxNamespacedName:
return emitJsxNamespacedName(node as JsxNamespacedName);

// Synthesized list
case SyntaxKind.SyntaxList:
Expand Down Expand Up @@ -4228,12 +4225,6 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}
}

function emitJsxNamespacedName(node: JsxNamespacedName) {
emitIdentifierName(node.namespace);
writePunctuation(":");
emitIdentifierName(node.name);
}

function emitJsxTagName(node: JsxTagNameExpression) {
if (node.kind === SyntaxKind.Identifier) {
emitExpression(node);
Expand Down
28 changes: 2 additions & 26 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,6 @@ import {
JSDocVariadicType,
JsxAttribute,
JsxAttributeLike,
JsxAttributeName,
JsxAttributes,
JsxAttributeValue,
JsxChild,
Expand All @@ -276,7 +275,6 @@ import {
JsxElement,
JsxExpression,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxSelfClosingElement,
Expand Down Expand Up @@ -910,8 +908,6 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
updateJsxSpreadAttribute,
createJsxExpression,
updateJsxExpression,
createJsxNamespacedName,
updateJsxNamespacedName,
createCaseClause,
updateCaseClause,
createDefaultClause,
Expand Down Expand Up @@ -5586,7 +5582,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function createJsxAttribute(name: JsxAttributeName, initializer: JsxAttributeValue | undefined) {
function createJsxAttribute(name: Identifier, initializer: JsxAttributeValue | undefined) {
const node = createBaseDeclaration<JsxAttribute>(SyntaxKind.JsxAttribute);
node.name = name;
node.initializer = initializer;
Expand All @@ -5598,7 +5594,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

// @api
function updateJsxAttribute(node: JsxAttribute, name: JsxAttributeName, initializer: JsxAttributeValue | undefined) {
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) {
return node.name !== name
|| node.initializer !== initializer
? update(createJsxAttribute(name, initializer), node)
Expand Down Expand Up @@ -5658,26 +5654,6 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
: node;
}

// @api
function createJsxNamespacedName(namespace: Identifier, name: Identifier) {
const node = createBaseNode<JsxNamespacedName>(SyntaxKind.JsxNamespacedName);
node.namespace = namespace;
node.name = name;
node.transformFlags |=
propagateChildFlags(node.namespace) |
propagateChildFlags(node.name) |
TransformFlags.ContainsJsx;
return node;
}

// @api
function updateJsxNamespacedName(node: JsxNamespacedName, namespace: Identifier, name: Identifier) {
return node.namespace !== namespace
|| node.name !== name
? update(createJsxNamespacedName(namespace, name), node)
: node;
}

//
// Clauses
//
Expand Down
5 changes: 0 additions & 5 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ import {
JsxElement,
JsxExpression,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxSelfClosingElement,
Expand Down Expand Up @@ -964,10 +963,6 @@ export function isJsxExpression(node: Node): node is JsxExpression {
return node.kind === SyntaxKind.JsxExpression;
}

export function isJsxNamespacedName(node: Node): node is JsxNamespacedName {
return node.kind === SyntaxKind.JsxNamespacedName;
}

// Clauses

export function isCaseClause(node: Node): node is CaseClause {
Expand Down
42 changes: 5 additions & 37 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ import {
JsxElement,
JsxExpression,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxOpeningLikeElement,
Expand Down Expand Up @@ -1031,10 +1030,6 @@ const forEachChildTable: ForEachChildTable = {
[SyntaxKind.JsxClosingElement]: function forEachChildInJsxClosingElement<T>(node: JsxClosingElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.tagName);
},
[SyntaxKind.JsxNamespacedName]: function forEachChildInJsxNamespacedName<T>(node: JsxNamespacedName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray<Node>) => T | undefined): T | undefined {
return visitNode(cbNode, node.namespace) ||
visitNode(cbNode, node.name);
},
[SyntaxKind.OptionalType]: forEachChildInOptionalRestOrJSDocParameterModifier,
[SyntaxKind.RestType]: forEachChildInOptionalRestOrJSDocParameterModifier,
[SyntaxKind.JSDocTypeExpression]: forEachChildInOptionalRestOrJSDocParameterModifier,
Expand Down Expand Up @@ -6107,31 +6102,20 @@ namespace Parser {

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

function parseJsxTagName(): Identifier | JsxNamespacedName | ThisExpression {
const pos = getNodePos();
scanJsxIdentifier();

const isThis = token() === SyntaxKind.ThisKeyword;
const tagName = parseIdentifierName();
if (parseOptional(SyntaxKind.ColonToken)) {
scanJsxIdentifier();
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierName()), pos);
}
return isThis ? finishNode(factory.createToken(SyntaxKind.ThisKeyword), pos) : tagName;
}

function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined {
const pos = getNodePos();
if (!parseExpected(SyntaxKind.OpenBraceToken)) {
Expand Down Expand Up @@ -6164,8 +6148,9 @@ namespace Parser {
return parseJsxSpreadAttribute();
}

scanJsxIdentifier();
const pos = getNodePos();
return finishNode(factory.createJsxAttribute(parseJsxAttributeName(), parseJsxAttributeValue()), pos);
return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos);
}

function parseJsxAttributeValue(): JsxAttributeValue | undefined {
Expand All @@ -6184,18 +6169,6 @@ namespace Parser {
return undefined;
}

function parseJsxAttributeName() {
const pos = getNodePos();
scanJsxIdentifier();

const attrName = parseIdentifierName();
if (parseOptional(SyntaxKind.ColonToken)) {
scanJsxIdentifier();
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierName()), pos);
}
return attrName;
}

function parseJsxSpreadAttribute(): JsxSpreadAttribute {
const pos = getNodePos();
parseExpected(SyntaxKind.OpenBraceToken);
Expand Down Expand Up @@ -10452,11 +10425,6 @@ export function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagName
return true;
}

if (lhs.kind === SyntaxKind.JsxNamespacedName) {
return lhs.namespace.escapedText === (rhs as JsxNamespacedName).namespace.escapedText &&
lhs.name.escapedText === (rhs as JsxNamespacedName).name.escapedText;
}

// If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only
// take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression
// it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element
Expand Down
13 changes: 13 additions & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2543,19 +2543,32 @@ export function createScanner(languageVersion: ScriptTarget,
// everything after it to the token
// Do note that this means that `scanJsxIdentifier` effectively _mutates_ the visible token without advancing to a new token
// Any caller should be expecting this behavior and should only read the pos or token value after calling it.
let namespaceSeparator = false;
while (pos < end) {
const ch = text.charCodeAt(pos);
if (ch === CharacterCodes.minus) {
tokenValue += "-";
pos++;
continue;
}
else if (ch === CharacterCodes.colon && !namespaceSeparator) {
tokenValue += ":";
pos++;
namespaceSeparator = true;
token = SyntaxKind.Identifier; // swap from keyword kind to identifier kind
continue;
}
const oldPos = pos;
tokenValue += scanIdentifierParts(); // reuse `scanIdentifierParts` so unicode escapes are handled
if (pos === oldPos) {
break;
}
}
// Do not include a trailing namespace separator in the token, since this is against the spec.
if (tokenValue.slice(-1) === ":") {
tokenValue = tokenValue.slice(0, -1);
pos--;
}
return getIdentifierToken();
}
return token;
Expand Down
Loading