Skip to content

Commit cd391b0

Browse files
chore: disallows unicode escape sequence in JSX (#48609)
Co-authored-by: Jake Bailey <[email protected]>
1 parent e78f2a8 commit cd391b0

File tree

7 files changed

+198
-72
lines changed

7 files changed

+198
-72
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6737,6 +6737,10 @@
67376737
"category": "Error",
67386738
"code": 17020
67396739
},
6740+
"Unicode escape sequence cannot appear here.": {
6741+
"category": "Error",
6742+
"code": 17021
6743+
},
67406744
"Circularity detected while resolving configuration: {0}": {
67416745
"category": "Error",
67426746
"code": 18000

src/compiler/parser.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2657,6 +2657,13 @@ namespace Parser {
26572657
return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage);
26582658
}
26592659

2660+
function parseIdentifierNameErrorOnUnicodeEscapeSequence(): Identifier {
2661+
if (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape()) {
2662+
parseErrorAtCurrentToken(Diagnostics.Unicode_escape_sequence_cannot_appear_here);
2663+
}
2664+
return createIdentifier(tokenIsIdentifierOrKeyword(token()));
2665+
}
2666+
26602667
function isLiteralPropertyName(): boolean {
26612668
return tokenIsIdentifierOrKeyword(token()) ||
26622669
token() === SyntaxKind.StringLiteral ||
@@ -3522,7 +3529,7 @@ namespace Parser {
35223529
entity = finishNode(
35233530
factory.createQualifiedName(
35243531
entity,
3525-
parseRightSideOfDot(allowReservedWords, /*allowPrivateIdentifiers*/ false) as Identifier
3532+
parseRightSideOfDot(allowReservedWords, /*allowPrivateIdentifiers*/ false, /*allowUnicodeEscapeSequenceInIdentifierName*/ true) as Identifier
35263533
),
35273534
pos
35283535
);
@@ -3534,7 +3541,7 @@ namespace Parser {
35343541
return finishNode(factory.createQualifiedName(entity, name), entity.pos);
35353542
}
35363543

3537-
function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean): Identifier | PrivateIdentifier {
3544+
function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean, allowUnicodeEscapeSequenceInIdentifierName: boolean): Identifier | PrivateIdentifier {
35383545
// Technically a keyword is valid here as all identifiers and keywords are identifier names.
35393546
// However, often we'll encounter this in error situations when the identifier or keyword
35403547
// is actually starting another valid construct.
@@ -3570,7 +3577,11 @@ namespace Parser {
35703577
return allowPrivateIdentifiers ? node : createMissingNode<Identifier>(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected);
35713578
}
35723579

3573-
return allowIdentifierNames ? parseIdentifierName() : parseIdentifier();
3580+
if (allowIdentifierNames) {
3581+
return allowUnicodeEscapeSequenceInIdentifierName ? parseIdentifierName() : parseIdentifierNameErrorOnUnicodeEscapeSequence();
3582+
}
3583+
3584+
return parseIdentifier();
35743585
}
35753586

35763587
function parseTemplateSpans(isTaggedTemplate: boolean) {
@@ -5945,7 +5956,7 @@ namespace Parser {
59455956
// If it wasn't then just try to parse out a '.' and report an error.
59465957
parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access);
59475958
// private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic
5948-
return finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos);
5959+
return finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true, /*allowUnicodeEscapeSequenceInIdentifierName*/ true)), pos);
59495960
}
59505961

59515962
function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment, mustBeUnary = false): JsxElement | JsxSelfClosingElement | JsxFragment {
@@ -6140,7 +6151,7 @@ namespace Parser {
61406151
}
61416152
let expression: PropertyAccessExpression | Identifier | ThisExpression = initialExpression;
61426153
while (parseOptional(SyntaxKind.DotToken)) {
6143-
expression = finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos);
6154+
expression = finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false, /*allowUnicodeEscapeSequenceInIdentifierName*/ false)), pos);
61446155
}
61456156
return expression as JsxTagNameExpression;
61466157
}
@@ -6150,10 +6161,10 @@ namespace Parser {
61506161
scanJsxIdentifier();
61516162

61526163
const isThis = token() === SyntaxKind.ThisKeyword;
6153-
const tagName = parseIdentifierName();
6164+
const tagName = parseIdentifierNameErrorOnUnicodeEscapeSequence();
61546165
if (parseOptional(SyntaxKind.ColonToken)) {
61556166
scanJsxIdentifier();
6156-
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierName()), pos);
6167+
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierNameErrorOnUnicodeEscapeSequence()), pos);
61576168
}
61586169
return isThis ? finishNode(factory.createToken(SyntaxKind.ThisKeyword), pos) : tagName;
61596170
}
@@ -6214,10 +6225,10 @@ namespace Parser {
62146225
const pos = getNodePos();
62156226
scanJsxIdentifier();
62166227

6217-
const attrName = parseIdentifierName();
6228+
const attrName = parseIdentifierNameErrorOnUnicodeEscapeSequence();
62186229
if (parseOptional(SyntaxKind.ColonToken)) {
62196230
scanJsxIdentifier();
6220-
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierName()), pos);
6231+
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierNameErrorOnUnicodeEscapeSequence()), pos);
62216232
}
62226233
return attrName;
62236234
}
@@ -6307,7 +6318,7 @@ namespace Parser {
63076318
}
63086319

63096320
function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) {
6310-
const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true);
6321+
const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true, /*allowUnicodeEscapeSequenceInIdentifierName*/ true);
63116322
const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression);
63126323
const propertyAccess = isOptionalChain ?
63136324
factoryCreatePropertyAccessChain(expression, questionDotToken, name) :
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
file.tsx(15,4): error TS17021: Unicode escape sequence cannot appear here.
2+
file.tsx(16,4): error TS17021: Unicode escape sequence cannot appear here.
3+
file.tsx(17,4): error TS17021: Unicode escape sequence cannot appear here.
4+
file.tsx(18,4): error TS17021: Unicode escape sequence cannot appear here.
5+
file.tsx(19,6): error TS17021: Unicode escape sequence cannot appear here.
6+
file.tsx(20,4): error TS17021: Unicode escape sequence cannot appear here.
7+
file.tsx(21,4): error TS17021: Unicode escape sequence cannot appear here.
8+
file.tsx(22,4): error TS17021: Unicode escape sequence cannot appear here.
9+
file.tsx(23,4): error TS17021: Unicode escape sequence cannot appear here.
10+
file.tsx(26,9): error TS17021: Unicode escape sequence cannot appear here.
11+
file.tsx(27,9): error TS17021: Unicode escape sequence cannot appear here.
12+
13+
14+
==== file.tsx (11 errors) ====
15+
import * as React from "react";
16+
declare global {
17+
namespace JSX {
18+
interface IntrinsicElements {
19+
"a-b": any;
20+
"a-c": any;
21+
}
22+
}
23+
}
24+
const Compa = (x: {x: number}) => <div>{"" + x}</div>;
25+
const x = { video: () => null }
26+
27+
// unicode escape sequence is not allowed in tag name or JSX attribute name.
28+
// tag name:
29+
; <\u0061></a>
30+
~~~~~~
31+
!!! error TS17021: Unicode escape sequence cannot appear here.
32+
; <\u0061-b></a-b>
33+
~~~~~~~~
34+
!!! error TS17021: Unicode escape sequence cannot appear here.
35+
; <a-\u0063></a-c>
36+
~~~~~~~~
37+
!!! error TS17021: Unicode escape sequence cannot appear here.
38+
; <Comp\u0061 x={12} />
39+
~~~~~~~~~~
40+
!!! error TS17021: Unicode escape sequence cannot appear here.
41+
; <x.\u0076ideo />
42+
~~~~~~~~~~
43+
!!! error TS17021: Unicode escape sequence cannot appear here.
44+
; <\u{0061}></a>
45+
~~~~~~~~
46+
!!! error TS17021: Unicode escape sequence cannot appear here.
47+
; <\u{0061}-b></a-b>
48+
~~~~~~~~~~
49+
!!! error TS17021: Unicode escape sequence cannot appear here.
50+
; <a-\u{0063}></a-c>
51+
~~~~~~~~~~
52+
!!! error TS17021: Unicode escape sequence cannot appear here.
53+
; <Comp\u{0061} x={12} />
54+
~~~~~~~~~~~~
55+
!!! error TS17021: Unicode escape sequence cannot appear here.
56+
57+
// attribute name
58+
;<video data-\u0076ideo />
59+
~~~~~~~~~~~~~~~
60+
!!! error TS17021: Unicode escape sequence cannot appear here.
61+
;<video \u0073rc="" />
62+
~~~~~~~~
63+
!!! error TS17021: Unicode escape sequence cannot appear here.
64+

tests/baselines/reference/unicodeEscapesInJsxtags.js

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,37 @@ declare global {
1111
}
1212
}
1313
const Compa = (x: {x: number}) => <div>{"" + x}</div>;
14+
const x = { video: () => null }
1415

15-
let a = <\u0061></a>; // works
16-
let ab = <\u0061-b></a-b>; // works
17-
let ac = <a-\u0063></a-c>; // works
18-
let compa = <Comp\u0061 x={12} />; // works
16+
// unicode escape sequence is not allowed in tag name or JSX attribute name.
17+
// tag name:
18+
; <\u0061></a>
19+
; <\u0061-b></a-b>
20+
; <a-\u0063></a-c>
21+
; <Comp\u0061 x={12} />
22+
; <x.\u0076ideo />
23+
; <\u{0061}></a>
24+
; <\u{0061}-b></a-b>
25+
; <a-\u{0063}></a-c>
26+
; <Comp\u{0061} x={12} />
1927

20-
let a2 = <\u{0061}></a>; // works
21-
let ab2 = <\u{0061}-b></a-b>; // works
22-
let ac2 = <a-\u{0063}></a-c>; // works
23-
let compa2 = <Comp\u{0061} x={12} />; // works
28+
// attribute name
29+
;<video data-\u0076ideo />
30+
;<video \u0073rc="" />
2431

2532

2633
//// [file.js]
2734
import * as React from "react";
2835
const Compa = (x) => React.createElement("div", null, "" + x);
29-
let a = React.createElement("a", null); // works
30-
let ab = React.createElement("a-b", null); // works
31-
let ac = React.createElement("a-c", null); // works
32-
let compa = React.createElement(Comp\u0061, { x: 12 }); // works
33-
let a2 = React.createElement("a", null); // works
34-
let ab2 = React.createElement("a-b", null); // works
35-
let ac2 = React.createElement("a-c", null); // works
36-
let compa2 = React.createElement(Comp\u{0061}, { x: 12 }); // works
36+
const x = { video: () => null };
37+
React.createElement("a", null);
38+
React.createElement("a-b", null);
39+
React.createElement("a-c", null);
40+
React.createElement(Comp\u0061, { x: 12 });
41+
React.createElement(x.\u0076ideo, null);
42+
React.createElement("a", null);
43+
React.createElement("a-b", null);
44+
React.createElement("a-c", null);
45+
React.createElement(Comp\u{0061}, { x: 12 });
46+
React.createElement("video", { "data-video": true });
47+
React.createElement("video", { \u0073rc: "" });

tests/baselines/reference/unicodeEscapesInJsxtags.symbols

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,43 +29,55 @@ const Compa = (x: {x: number}) => <div>{"" + x}</div>;
2929
>x : Symbol(x, Decl(file.tsx, 9, 15))
3030
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2400, 45))
3131

32-
let a = <\u0061></a>; // works
33-
>a : Symbol(a, Decl(file.tsx, 11, 3))
32+
const x = { video: () => null }
33+
>x : Symbol(x, Decl(file.tsx, 10, 5))
34+
>video : Symbol(video, Decl(file.tsx, 10, 11))
35+
36+
// unicode escape sequence is not allowed in tag name or JSX attribute name.
37+
// tag name:
38+
; <\u0061></a>
3439
>\u0061 : Symbol(JSX.IntrinsicElements.a, Decl(react.d.ts, 2370, 33))
3540
>a : Symbol(JSX.IntrinsicElements.a, Decl(react.d.ts, 2370, 33))
3641

37-
let ab = <\u0061-b></a-b>; // works
38-
>ab : Symbol(ab, Decl(file.tsx, 12, 3))
42+
; <\u0061-b></a-b>
3943
>\u0061-b : Symbol(JSX.IntrinsicElements["a-b"], Decl(file.tsx, 3, 37))
4044
>a-b : Symbol(JSX.IntrinsicElements["a-b"], Decl(file.tsx, 3, 37))
4145

42-
let ac = <a-\u0063></a-c>; // works
43-
>ac : Symbol(ac, Decl(file.tsx, 13, 3))
46+
; <a-\u0063></a-c>
4447
>a-\u0063 : Symbol(JSX.IntrinsicElements["a-c"], Decl(file.tsx, 4, 23))
4548
>a-c : Symbol(JSX.IntrinsicElements["a-c"], Decl(file.tsx, 4, 23))
4649

47-
let compa = <Comp\u0061 x={12} />; // works
48-
>compa : Symbol(compa, Decl(file.tsx, 14, 3))
50+
; <Comp\u0061 x={12} />
4951
>Comp\u0061 : Symbol(Compa, Decl(file.tsx, 9, 5))
50-
>x : Symbol(x, Decl(file.tsx, 14, 23))
52+
>x : Symbol(x, Decl(file.tsx, 17, 13))
53+
54+
; <x.\u0076ideo />
55+
>x.\u0076ideo : Symbol(video, Decl(file.tsx, 10, 11))
56+
>x : Symbol(x, Decl(file.tsx, 10, 5))
57+
>\u0076ideo : Symbol(video, Decl(file.tsx, 10, 11))
5158

52-
let a2 = <\u{0061}></a>; // works
53-
>a2 : Symbol(a2, Decl(file.tsx, 16, 3))
59+
; <\u{0061}></a>
5460
>\u{0061} : Symbol(JSX.IntrinsicElements.a, Decl(react.d.ts, 2370, 33))
5561
>a : Symbol(JSX.IntrinsicElements.a, Decl(react.d.ts, 2370, 33))
5662

57-
let ab2 = <\u{0061}-b></a-b>; // works
58-
>ab2 : Symbol(ab2, Decl(file.tsx, 17, 3))
63+
; <\u{0061}-b></a-b>
5964
>\u{0061}-b : Symbol(JSX.IntrinsicElements["a-b"], Decl(file.tsx, 3, 37))
6065
>a-b : Symbol(JSX.IntrinsicElements["a-b"], Decl(file.tsx, 3, 37))
6166

62-
let ac2 = <a-\u{0063}></a-c>; // works
63-
>ac2 : Symbol(ac2, Decl(file.tsx, 18, 3))
67+
; <a-\u{0063}></a-c>
6468
>a-\u{0063} : Symbol(JSX.IntrinsicElements["a-c"], Decl(file.tsx, 4, 23))
6569
>a-c : Symbol(JSX.IntrinsicElements["a-c"], Decl(file.tsx, 4, 23))
6670

67-
let compa2 = <Comp\u{0061} x={12} />; // works
68-
>compa2 : Symbol(compa2, Decl(file.tsx, 19, 3))
71+
; <Comp\u{0061} x={12} />
6972
>Comp\u{0061} : Symbol(Compa, Decl(file.tsx, 9, 5))
70-
>x : Symbol(x, Decl(file.tsx, 19, 26))
73+
>x : Symbol(x, Decl(file.tsx, 22, 15))
74+
75+
// attribute name
76+
;<video data-\u0076ideo />
77+
>video : Symbol(JSX.IntrinsicElements.video, Decl(react.d.ts, 2481, 44))
78+
>data-\u0076ideo : Symbol(data-\u0076ideo, Decl(file.tsx, 25, 7))
79+
80+
;<video \u0073rc="" />
81+
>video : Symbol(JSX.IntrinsicElements.video, Decl(react.d.ts, 2481, 44))
82+
>\u0073rc : Symbol(\u0073rc, Decl(file.tsx, 26, 7))
7183

tests/baselines/reference/unicodeEscapesInJsxtags.types

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,53 +29,70 @@ const Compa = (x: {x: number}) => <div>{"" + x}</div>;
2929
>x : { x: number; }
3030
>div : any
3131

32-
let a = <\u0061></a>; // works
33-
>a : JSX.Element
32+
const x = { video: () => null }
33+
>x : { video: () => any; }
34+
>{ video: () => null } : { video: () => any; }
35+
>video : () => any
36+
>() => null : () => any
37+
38+
// unicode escape sequence is not allowed in tag name or JSX attribute name.
39+
// tag name:
40+
; <\u0061></a>
3441
><\u0061></a> : JSX.Element
35-
>\u0061 : JSX.Element
36-
>a : JSX.Element
42+
>\u0061 : any
43+
>a : any
3744

38-
let ab = <\u0061-b></a-b>; // works
39-
>ab : JSX.Element
45+
; <\u0061-b></a-b>
4046
><\u0061-b></a-b> : JSX.Element
4147
>\u0061-b : any
4248
>a-b : any
4349

44-
let ac = <a-\u0063></a-c>; // works
45-
>ac : JSX.Element
50+
; <a-\u0063></a-c>
4651
><a-\u0063></a-c> : JSX.Element
4752
>a-\u0063 : any
4853
>a-c : any
4954

50-
let compa = <Comp\u0061 x={12} />; // works
51-
>compa : JSX.Element
55+
; <Comp\u0061 x={12} />
5256
><Comp\u0061 x={12} /> : JSX.Element
5357
>Comp\u0061 : (x: { x: number; }) => JSX.Element
5458
>x : number
5559
>12 : 12
5660

57-
let a2 = <\u{0061}></a>; // works
58-
>a2 : JSX.Element
61+
; <x.\u0076ideo />
62+
><x.\u0076ideo /> : JSX.Element
63+
>x.\u0076ideo : () => any
64+
>x : { video: () => any; }
65+
>\u0076ideo : () => any
66+
67+
; <\u{0061}></a>
5968
><\u{0061}></a> : JSX.Element
60-
>\u{0061} : JSX.Element
61-
>a : JSX.Element
69+
>\u{0061} : any
70+
>a : any
6271

63-
let ab2 = <\u{0061}-b></a-b>; // works
64-
>ab2 : JSX.Element
72+
; <\u{0061}-b></a-b>
6573
><\u{0061}-b></a-b> : JSX.Element
6674
>\u{0061}-b : any
6775
>a-b : any
6876

69-
let ac2 = <a-\u{0063}></a-c>; // works
70-
>ac2 : JSX.Element
77+
; <a-\u{0063}></a-c>
7178
><a-\u{0063}></a-c> : JSX.Element
7279
>a-\u{0063} : any
7380
>a-c : any
7481

75-
let compa2 = <Comp\u{0061} x={12} />; // works
76-
>compa2 : JSX.Element
82+
; <Comp\u{0061} x={12} />
7783
><Comp\u{0061} x={12} /> : JSX.Element
7884
>Comp\u{0061} : (x: { x: number; }) => JSX.Element
7985
>x : number
8086
>12 : 12
8187

88+
// attribute name
89+
;<video data-\u0076ideo />
90+
><video data-\u0076ideo /> : JSX.Element
91+
>video : any
92+
>data-\u0076ideo : true
93+
94+
;<video \u0073rc="" />
95+
><video \u0073rc="" /> : JSX.Element
96+
>video : any
97+
>\u0073rc : string
98+

0 commit comments

Comments
 (0)