Skip to content
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

feat(7411): JSX namespaced attribute syntax not supported #47356

Merged
merged 22 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ff771af
feat(7411): add JSXNamespacedName
a-tarasyuk Jul 9, 2022
4926694
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Sep 6, 2022
a4abf96
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Sep 6, 2022
5989a3e
fix tests
a-tarasyuk Sep 6, 2022
203925d
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Sep 21, 2022
412c88b
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Oct 27, 2022
486cb2a
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Nov 8, 2022
d1418e2
update baseline
a-tarasyuk Nov 8, 2022
6d1dab6
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Nov 18, 2022
fd05a34
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Dec 13, 2022
b6361f3
update baseline
a-tarasyuk Dec 13, 2022
8b1aa43
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Dec 14, 2022
9cb0967
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Dec 16, 2022
672b505
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Feb 8, 2023
dfa62fb
update baseline
a-tarasyuk Feb 8, 2023
f5fed3e
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Feb 26, 2023
5961402
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Mar 11, 2023
c51c289
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Mar 16, 2023
23379dd
escape jsx attribute name
a-tarasyuk Mar 17, 2023
9a7ad07
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Apr 11, 2023
98c3eb6
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Apr 13, 2023
3217745
Merge branch 'main' of https://github.com/microsoft/TypeScript into f…
a-tarasyuk Apr 14, 2023
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: 16 additions & 27 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ import {
getEntityNameFromTypeNode,
getErrorSpanForNode,
getEscapedTextOfIdentifierOrLiteral,
getEscapedTextOfJsxAttributeName,
getESModuleInterop,
getExpandoInitializer,
getExportAssignmentExpression,
Expand Down Expand Up @@ -350,6 +351,7 @@ import {
getSymbolNameForPrivateIdentifier,
getTextOfIdentifierOrLiteral,
getTextOfJSDocComment,
getTextOfJsxAttributeName,
getTextOfNode,
getTextOfPropertyName,
getThisContainer,
Expand Down Expand Up @@ -593,6 +595,7 @@ import {
isJsxAttributeLike,
isJsxAttributes,
isJsxElement,
isJsxNamespacedName,
isJsxOpeningElement,
isJsxOpeningFragment,
isJsxOpeningLikeElement,
Expand Down Expand Up @@ -807,7 +810,6 @@ import {
MappedTypeNode,
MatchingKeys,
maybeBind,
MemberName,
MemberOverrideStatus,
memoize,
MetaProperty,
Expand Down Expand Up @@ -13517,7 +13519,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 && getLiteralTypeFromPropertyName(property.name);
const nameType = property.name && (isJsxNamespacedName(property.name) ? getStringLiteralType(getTextOfJsxAttributeName(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 @@ -19590,8 +19592,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(idText(prop.name))) continue;
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(idText(prop.name)) };
if (isJsxSpreadAttribute(prop) || isHyphenatedJsxName(getTextOfJsxAttributeName(prop.name))) continue;
yield { errorNode: prop.name, innerExpression: prop.initializer, nameType: getStringLiteralType(getTextOfJsxAttributeName(prop.name)) };
}
}

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

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

function checkGrammarJsxName(node: JsxTagNameExpression) {
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 (isPropertyAccessExpression(node) && isJsxNamespacedName(node.expression)) {
return grammarErrorOnNode(node.expression, Diagnostics.JSX_property_access_expressions_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);
}
if (isJsxNamespacedName(node) && getJSXTransformEnabled(compilerOptions) && !isIntrinsicJsxName(node.namespace.escapedText)) {
return grammarErrorOnNode(node, Diagnostics.React_components_cannot_include_JSX_namespace_names);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2893,6 +2893,10 @@
"category": "Error",
"code": 2638
},
"React components cannot include JSX namespace names": {
a-tarasyuk marked this conversation as resolved.
Show resolved Hide resolved
"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: 9 additions & 0 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ import {
JsxEmit,
JsxExpression,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxSelfClosingElement,
Expand Down Expand Up @@ -2283,6 +2284,8 @@ 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 @@ -4225,6 +4228,12 @@ 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: 26 additions & 2 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ import {
JSDocVariadicType,
JsxAttribute,
JsxAttributeLike,
JsxAttributeName,
JsxAttributes,
JsxAttributeValue,
JsxChild,
Expand All @@ -275,6 +276,7 @@ import {
JsxElement,
JsxExpression,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxSelfClosingElement,
Expand Down Expand Up @@ -908,6 +910,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
updateJsxSpreadAttribute,
createJsxExpression,
updateJsxExpression,
createJsxNamespacedName,
updateJsxNamespacedName,
createCaseClause,
updateCaseClause,
createDefaultClause,
Expand Down Expand Up @@ -5582,7 +5586,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
}

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

// @api
function updateJsxAttribute(node: JsxAttribute, name: Identifier, initializer: JsxAttributeValue | undefined) {
function updateJsxAttribute(node: JsxAttribute, name: JsxAttributeName, initializer: JsxAttributeValue | undefined) {
return node.name !== name
|| node.initializer !== initializer
? update(createJsxAttribute(name, initializer), node)
Expand Down Expand Up @@ -5654,6 +5658,26 @@ 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: 5 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ import {
JsxElement,
JsxExpression,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxSelfClosingElement,
Expand Down Expand Up @@ -963,6 +964,10 @@ 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: 37 additions & 5 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ import {
JsxElement,
JsxExpression,
JsxFragment,
JsxNamespacedName,
JsxOpeningElement,
JsxOpeningFragment,
JsxOpeningLikeElement,
Expand Down Expand Up @@ -1030,6 +1031,10 @@ 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 @@ -6102,20 +6107,31 @@ 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 = token() === SyntaxKind.ThisKeyword ?
parseTokenNode<ThisExpression>() : parseIdentifierName();
let expression: JsxTagNameExpression = parseJsxTagName();
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 @@ -6148,9 +6164,8 @@ namespace Parser {
return parseJsxSpreadAttribute();
}

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

function parseJsxAttributeValue(): JsxAttributeValue | undefined {
Expand All @@ -6169,6 +6184,18 @@ 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 @@ -10425,6 +10452,11 @@ 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: 0 additions & 13 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2543,32 +2543,19 @@ 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