Skip to content

Commit eba9910

Browse files
committed
chore: disallows unicode escape sequence in jsx
1 parent 06db35c commit eba9910

File tree

7 files changed

+197
-70
lines changed

7 files changed

+197
-70
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6637,6 +6637,10 @@
66376637
"category": "Error",
66386638
"code": 17020
66396639
},
6640+
"Unicode escape sequence cannot appear here.": {
6641+
"category": "Error",
6642+
"code": 17021
6643+
},
66406644
"Circularity detected while resolving configuration: {0}": {
66416645
"category": "Error",
66426646
"code": 18000

src/compiler/parser.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,6 +2605,13 @@ namespace Parser {
26052605
return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage);
26062606
}
26072607

2608+
function parseIdentifierNameErrorOnUnicodeEscapeSequence(): Identifier {
2609+
if (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape()) {
2610+
parseErrorAtCurrentToken(Diagnostics.Unicode_escape_sequence_cannot_appear_here);
2611+
}
2612+
return createIdentifier(tokenIsIdentifierOrKeyword(token()));
2613+
}
2614+
26082615
function isLiteralPropertyName(): boolean {
26092616
return tokenIsIdentifierOrKeyword(token()) ||
26102617
token() === SyntaxKind.StringLiteral ||
@@ -3454,7 +3461,7 @@ namespace Parser {
34543461
entity = finishNode(
34553462
factory.createQualifiedName(
34563463
entity,
3457-
parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false) as Identifier
3464+
parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false, /** allowUnicodeEscapeSequenceInIdentifierName */ true) as Identifier
34583465
),
34593466
pos
34603467
);
@@ -3466,7 +3473,7 @@ namespace Parser {
34663473
return finishNode(factory.createQualifiedName(entity, name), entity.pos);
34673474
}
34683475

3469-
function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean): Identifier | PrivateIdentifier {
3476+
function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean, allowUnicodeEscapeSequenceInIdentifierName: boolean): Identifier | PrivateIdentifier {
34703477
// Technically a keyword is valid here as all identifiers and keywords are identifier names.
34713478
// However, often we'll encounter this in error situations when the identifier or keyword
34723479
// is actually starting another valid construct.
@@ -3502,7 +3509,11 @@ namespace Parser {
35023509
return allowPrivateIdentifiers ? node : createMissingNode<Identifier>(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected);
35033510
}
35043511

3505-
return allowIdentifierNames ? parseIdentifierName() : parseIdentifier();
3512+
if (allowIdentifierNames) {
3513+
return allowUnicodeEscapeSequenceInIdentifierName ? parseIdentifierName() : parseIdentifierNameErrorOnUnicodeEscapeSequence();
3514+
}
3515+
3516+
return parseIdentifier();
35063517
}
35073518

35083519
function parseTemplateSpans(isTaggedTemplate: boolean) {
@@ -5869,7 +5880,7 @@ namespace Parser {
58695880
// If it wasn't then just try to parse out a '.' and report an error.
58705881
parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access);
58715882
// private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic
5872-
return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos);
5883+
return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true, /** allowUnicodeEscapeSequenceInIdentifierName */ true)), pos);
58735884
}
58745885

58755886
function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment): JsxElement | JsxSelfClosingElement | JsxFragment {
@@ -6058,9 +6069,9 @@ namespace Parser {
60586069
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
60596070
// We only want to consider "this" as a primaryExpression
60606071
let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ?
6061-
parseTokenNode<ThisExpression>() : parseIdentifierName();
6072+
parseTokenNode<ThisExpression>() : parseIdentifierNameErrorOnUnicodeEscapeSequence();
60626073
while (parseOptional(SyntaxKind.DotToken)) {
6063-
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
6074+
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false, /** allowUnicodeEscapeSequenceInIdentifierName */ false)), pos) as JsxTagNamePropertyAccess;
60646075
}
60656076
return expression;
60666077
}
@@ -6099,7 +6110,7 @@ namespace Parser {
60996110

61006111
scanJsxIdentifier();
61016112
const pos = getNodePos();
6102-
return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos);
6113+
return finishNode(factory.createJsxAttribute(parseIdentifierNameErrorOnUnicodeEscapeSequence(), parseJsxAttributeValue()), pos);
61036114
}
61046115

61056116
function parseJsxAttributeValue(): JsxAttributeValue | undefined {
@@ -6205,7 +6216,7 @@ namespace Parser {
62056216
}
62066217

62076218
function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) {
6208-
const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true);
6219+
const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true, /** allowUnicodeEscapeSequenceInIdentifierName */ true);
62096220
const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression);
62106221
const propertyAccess = isOptionalChain ?
62116222
factory.createPropertyAccessChain(expression, questionDotToken, name) :
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
tests/cases/conformance/jsx/file.tsx(15,4): error TS17021: Unicode escape sequence cannot appear here.
2+
tests/cases/conformance/jsx/file.tsx(16,4): error TS17021: Unicode escape sequence cannot appear here.
3+
tests/cases/conformance/jsx/file.tsx(17,4): error TS17021: Unicode escape sequence cannot appear here.
4+
tests/cases/conformance/jsx/file.tsx(18,4): error TS17021: Unicode escape sequence cannot appear here.
5+
tests/cases/conformance/jsx/file.tsx(19,6): error TS17021: Unicode escape sequence cannot appear here.
6+
tests/cases/conformance/jsx/file.tsx(20,4): error TS17021: Unicode escape sequence cannot appear here.
7+
tests/cases/conformance/jsx/file.tsx(21,4): error TS17021: Unicode escape sequence cannot appear here.
8+
tests/cases/conformance/jsx/file.tsx(22,4): error TS17021: Unicode escape sequence cannot appear here.
9+
tests/cases/conformance/jsx/file.tsx(23,4): error TS17021: Unicode escape sequence cannot appear here.
10+
tests/cases/conformance/jsx/file.tsx(26,9): error TS17021: Unicode escape sequence cannot appear here.
11+
tests/cases/conformance/jsx/file.tsx(27,9): error TS17021: Unicode escape sequence cannot appear here.
12+
13+
14+
==== tests/cases/conformance/jsx/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
@@ -9,26 +9,37 @@ declare global {
99
}
1010
}
1111
const Compa = (x: {x: number}) => <div>{"" + x}</div>;
12+
const x = { video: () => null }
1213

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

18-
let a2 = <\u{0061}></a>; // works
19-
let ab2 = <\u{0061}-b></a-b>; // works
20-
let ac2 = <a-\u{0063}></a-c>; // works
21-
let compa2 = <Comp\u{0061} x={12} />; // works
26+
// attribute name
27+
;<video data-\u0076ideo />
28+
;<video \u0073rc="" />
2229

2330

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

tests/baselines/reference/unicodeEscapesInJsxtags.symbols

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

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

35-
let ab = <\u0061-b></a-b>; // works
36-
>ab : Symbol(ab, Decl(file.tsx, 12, 3))
40+
; <\u0061-b></a-b>
3741
>\u0061-b : Symbol(JSX.IntrinsicElements["a-b"], Decl(file.tsx, 3, 37))
3842
>a-b : Symbol(JSX.IntrinsicElements["a-b"], Decl(file.tsx, 3, 37))
3943

40-
let ac = <a-\u0063></a-c>; // works
41-
>ac : Symbol(ac, Decl(file.tsx, 13, 3))
44+
; <a-\u0063></a-c>
4245
>a-\u0063 : Symbol(JSX.IntrinsicElements["a-c"], Decl(file.tsx, 4, 23))
4346
>a-c : Symbol(JSX.IntrinsicElements["a-c"], Decl(file.tsx, 4, 23))
4447

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

50-
let a2 = <\u{0061}></a>; // works
51-
>a2 : Symbol(a2, Decl(file.tsx, 16, 3))
57+
; <\u{0061}></a>
5258
>\u{0061} : Symbol(JSX.IntrinsicElements.a, Decl(react.d.ts, 2370, 33))
5359
>a : Symbol(JSX.IntrinsicElements.a, Decl(react.d.ts, 2370, 33))
5460

55-
let ab2 = <\u{0061}-b></a-b>; // works
56-
>ab2 : Symbol(ab2, Decl(file.tsx, 17, 3))
61+
; <\u{0061}-b></a-b>
5762
>\u{0061}-b : Symbol(JSX.IntrinsicElements["a-b"], Decl(file.tsx, 3, 37))
5863
>a-b : Symbol(JSX.IntrinsicElements["a-b"], Decl(file.tsx, 3, 37))
5964

60-
let ac2 = <a-\u{0063}></a-c>; // works
61-
>ac2 : Symbol(ac2, Decl(file.tsx, 18, 3))
65+
; <a-\u{0063}></a-c>
6266
>a-\u{0063} : Symbol(JSX.IntrinsicElements["a-c"], Decl(file.tsx, 4, 23))
6367
>a-c : Symbol(JSX.IntrinsicElements["a-c"], Decl(file.tsx, 4, 23))
6468

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

tests/baselines/reference/unicodeEscapesInJsxtags.types

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,53 +27,71 @@ const Compa = (x: {x: number}) => <div>{"" + x}</div>;
2727
>x : { x: number; }
2828
>div : any
2929

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

36-
let ab = <\u0061-b></a-b>; // works
37-
>ab : JSX.Element
44+
; <\u0061-b></a-b>
3845
><\u0061-b></a-b> : JSX.Element
3946
>\u0061-b : any
4047
>a-b : any
4148

42-
let ac = <a-\u0063></a-c>; // works
43-
>ac : JSX.Element
49+
; <a-\u0063></a-c>
4450
><a-\u0063></a-c> : JSX.Element
4551
>a-\u0063 : any
4652
>a-c : any
4753

48-
let compa = <Comp\u0061 x={12} />; // works
49-
>compa : JSX.Element
54+
; <Comp\u0061 x={12} />
5055
><Comp\u0061 x={12} /> : JSX.Element
5156
>Comp\u0061 : (x: { x: number; }) => JSX.Element
5257
>x : number
5358
>12 : 12
5459

55-
let a2 = <\u{0061}></a>; // works
56-
>a2 : JSX.Element
60+
; <x.\u0076ideo />
61+
><x.\u0076ideo /> : JSX.Element
62+
>x.\u0076ideo : () => any
63+
>x : { video: () => any; }
64+
>\u0076ideo : () => any
65+
66+
; <\u{0061}></a>
5767
><\u{0061}></a> : JSX.Element
58-
>\u{0061} : JSX.Element
59-
>a : JSX.Element
68+
>\u{0061} : any
69+
>a : any
6070

61-
let ab2 = <\u{0061}-b></a-b>; // works
62-
>ab2 : JSX.Element
71+
; <\u{0061}-b></a-b>
6372
><\u{0061}-b></a-b> : JSX.Element
6473
>\u{0061}-b : any
6574
>a-b : any
6675

67-
let ac2 = <a-\u{0063}></a-c>; // works
68-
>ac2 : JSX.Element
76+
; <a-\u{0063}></a-c>
6977
><a-\u{0063}></a-c> : JSX.Element
7078
>a-\u{0063} : any
7179
>a-c : any
7280

73-
let compa2 = <Comp\u{0061} x={12} />; // works
74-
>compa2 : JSX.Element
81+
; <Comp\u{0061} x={12} />
7582
><Comp\u{0061} x={12} /> : JSX.Element
7683
>Comp\u{0061} : (x: { x: number; }) => JSX.Element
7784
>x : number
7885
>12 : 12
7986

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

0 commit comments

Comments
 (0)