Skip to content

Commit 1c40ed5

Browse files
committed
chore: disallows unicode escape sequence in jsx
1 parent c99380f commit 1c40ed5

File tree

8 files changed

+325
-70
lines changed

8 files changed

+325
-70
lines changed

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6322,6 +6322,10 @@
63226322
"category": "Error",
63236323
"code": 17018
63246324
},
6325+
"Unicode escape sequence cannot appear here.": {
6326+
"category": "Error",
6327+
"code": 17019
6328+
},
63256329
"Circularity detected while resolving configuration: {0}": {
63266330
"category": "Error",
63276331
"code": 18000

src/compiler/parser.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,13 @@ namespace ts {
19911991
return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage);
19921992
}
19931993

1994+
function parseIdentifierNameErrorOnUnicodeEscapeSequence(): Identifier {
1995+
if (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape()) {
1996+
parseErrorAtCurrentToken(Diagnostics.Unicode_escape_sequence_cannot_appear_here);
1997+
}
1998+
return createIdentifier(tokenIsIdentifierOrKeyword(token()));
1999+
}
2000+
19942001
function isLiteralPropertyName(): boolean {
19952002
return tokenIsIdentifierOrKeyword(token()) ||
19962003
token() === SyntaxKind.StringLiteral ||
@@ -2843,7 +2850,7 @@ namespace ts {
28432850
entity = finishNode(
28442851
factory.createQualifiedName(
28452852
entity,
2846-
parseRightSideOfDot(allowReservedWords, allowPrivateIdentifiers) as Identifier
2853+
parseRightSideOfDot(allowReservedWords, allowPrivateIdentifiers, /** allowUnicodeEscapeSequenceInIdentifierName */ false) as Identifier
28472854
),
28482855
pos
28492856
);
@@ -2855,7 +2862,7 @@ namespace ts {
28552862
return finishNode(factory.createQualifiedName(entity, name), entity.pos);
28562863
}
28572864

2858-
function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean): Identifier | PrivateIdentifier {
2865+
function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean, allowUnicodeEscapeSequenceInIdentifierName: boolean): Identifier | PrivateIdentifier {
28592866
// Technically a keyword is valid here as all identifiers and keywords are identifier names.
28602867
// However, often we'll encounter this in error situations when the identifier or keyword
28612868
// is actually starting another valid construct.
@@ -2891,7 +2898,11 @@ namespace ts {
28912898
return allowPrivateIdentifiers ? node : createMissingNode<Identifier>(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected);
28922899
}
28932900

2894-
return allowIdentifierNames ? parseIdentifierName() : parseIdentifier();
2901+
if (allowIdentifierNames) {
2902+
return allowUnicodeEscapeSequenceInIdentifierName ? parseIdentifierName() : parseIdentifierNameErrorOnUnicodeEscapeSequence();
2903+
}
2904+
2905+
return parseIdentifier();
28952906
}
28962907

28972908
function parseTemplateSpans(isTaggedTemplate: boolean) {
@@ -5237,7 +5248,7 @@ namespace ts {
52375248
// If it wasn't then just try to parse out a '.' and report an error.
52385249
parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access);
52395250
// private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic
5240-
return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos);
5251+
return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true, /** allowUnicodeEscapeSequenceInIdentifierName */ false)), pos);
52415252
}
52425253

52435254
function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment): JsxElement | JsxSelfClosingElement | JsxFragment {
@@ -5426,9 +5437,9 @@ namespace ts {
54265437
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
54275438
// We only want to consider "this" as a primaryExpression
54285439
let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ?
5429-
parseTokenNode<ThisExpression>() : parseIdentifierName();
5440+
parseTokenNode<ThisExpression>() : parseIdentifierNameErrorOnUnicodeEscapeSequence();
54305441
while (parseOptional(SyntaxKind.DotToken)) {
5431-
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
5442+
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false, /** allowUnicodeEscapeSequenceInIdentifierName */ false)), pos) as JsxTagNamePropertyAccess;
54325443
}
54335444
return expression;
54345445
}
@@ -5469,7 +5480,7 @@ namespace ts {
54695480
const pos = getNodePos();
54705481
return finishNode(
54715482
factory.createJsxAttribute(
5472-
parseIdentifierName(),
5483+
parseIdentifierNameErrorOnUnicodeEscapeSequence(),
54735484
token() !== SyntaxKind.EqualsToken ? undefined :
54745485
scanJsxAttributeValue() === SyntaxKind.StringLiteral ? parseLiteralNode() as StringLiteral :
54755486
parseJsxExpression(/*inExpressionContext*/ true)
@@ -5565,7 +5576,7 @@ namespace ts {
55655576
}
55665577

55675578
function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) {
5568-
const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true);
5579+
const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true, /** allowUnicodeEscapeSequenceInIdentifierName */ false);
55695580
const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression);
55705581
const propertyAccess = isOptionalChain ?
55715582
factory.createPropertyAccessChain(expression, questionDotToken, name) :
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
tests/cases/compiler/escapedIdentifiers.ts(86,23): error TS17019: Unicode escape sequence cannot appear here.
2+
tests/cases/compiler/escapedIdentifiers.ts(88,23): error TS17019: Unicode escape sequence cannot appear here.
3+
4+
5+
==== tests/cases/compiler/escapedIdentifiers.ts (2 errors) ====
6+
/*
7+
0 .. \u0030
8+
9 .. \u0039
9+
10+
A .. \u0041
11+
Z .. \u005a
12+
13+
a .. \u0061
14+
z .. \u00za
15+
*/
16+
17+
// var decl
18+
var \u0061 = 1;
19+
a ++;
20+
\u0061 ++;
21+
22+
var b = 1;
23+
b ++;
24+
\u0062 ++;
25+
26+
// modules
27+
module moduleType1 {
28+
export var baz1: number;
29+
}
30+
module moduleType\u0032 {
31+
export var baz2: number;
32+
}
33+
34+
moduleType1.baz1 = 3;
35+
moduleType\u0031.baz1 = 3;
36+
moduleType2.baz2 = 3;
37+
moduleType\u0032.baz2 = 3;
38+
39+
// classes
40+
41+
class classType1 {
42+
public foo1: number;
43+
}
44+
class classType\u0032 {
45+
public foo2: number;
46+
}
47+
48+
var classType1Object1 = new classType1();
49+
classType1Object1.foo1 = 2;
50+
var classType1Object2 = new classType\u0031();
51+
classType1Object2.foo1 = 2;
52+
var classType2Object1 = new classType2();
53+
classType2Object1.foo2 = 2;
54+
var classType2Object2 = new classType\u0032();
55+
classType2Object2.foo2 = 2;
56+
57+
// interfaces
58+
interface interfaceType1 {
59+
bar1: number;
60+
}
61+
interface interfaceType\u0032 {
62+
bar2: number;
63+
}
64+
65+
var interfaceType1Object1 = <interfaceType1>{ bar1: 0 };
66+
interfaceType1Object1.bar1 = 2;
67+
var interfaceType1Object2 = <interfaceType\u0031>{ bar1: 0 };
68+
interfaceType1Object2.bar1 = 2;
69+
var interfaceType2Object1 = <interfaceType2>{ bar2: 0 };
70+
interfaceType2Object1.bar2 = 2;
71+
var interfaceType2Object2 = <interfaceType\u0032>{ bar2: 0 };
72+
interfaceType2Object2.bar2 = 2;
73+
74+
75+
// arguments
76+
class testClass {
77+
public func(arg1: number, arg\u0032: string, arg\u0033: boolean, arg4: number) {
78+
arg\u0031 = 1;
79+
arg2 = 'string';
80+
arg\u0033 = true;
81+
arg4 = 2;
82+
}
83+
}
84+
85+
// constructors
86+
class constructorTestClass {
87+
constructor (public arg1: number,public arg\u0032: string,public arg\u0033: boolean,public arg4: number) {
88+
}
89+
}
90+
var constructorTestObject = new constructorTestClass(1, 'string', true, 2);
91+
constructorTestObject.arg\u0031 = 1;
92+
~~~~~~~~~
93+
!!! error TS17019: Unicode escape sequence cannot appear here.
94+
constructorTestObject.arg2 = 'string';
95+
constructorTestObject.arg\u0033 = true;
96+
~~~~~~~~~
97+
!!! error TS17019: Unicode escape sequence cannot appear here.
98+
constructorTestObject.arg4 = 2;
99+
100+
// Lables
101+
102+
l\u0061bel1:
103+
while (false)
104+
{
105+
while(false)
106+
continue label1; // it will go to next iteration of outer loop
107+
}
108+
109+
label2:
110+
while (false)
111+
{
112+
while(false)
113+
continue l\u0061bel2; // it will go to next iteration of outer loop
114+
}
115+
116+
label3:
117+
while (false)
118+
{
119+
while(false)
120+
continue label3; // it will go to next iteration of outer loop
121+
}
122+
123+
l\u0061bel4:
124+
while (false)
125+
{
126+
while(false)
127+
continue l\u0061bel4; // it will go to next iteration of outer loop
128+
}
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 TS17019: Unicode escape sequence cannot appear here.
2+
tests/cases/conformance/jsx/file.tsx(16,4): error TS17019: Unicode escape sequence cannot appear here.
3+
tests/cases/conformance/jsx/file.tsx(17,4): error TS17019: Unicode escape sequence cannot appear here.
4+
tests/cases/conformance/jsx/file.tsx(18,4): error TS17019: Unicode escape sequence cannot appear here.
5+
tests/cases/conformance/jsx/file.tsx(19,6): error TS17019: Unicode escape sequence cannot appear here.
6+
tests/cases/conformance/jsx/file.tsx(20,4): error TS17019: Unicode escape sequence cannot appear here.
7+
tests/cases/conformance/jsx/file.tsx(21,4): error TS17019: Unicode escape sequence cannot appear here.
8+
tests/cases/conformance/jsx/file.tsx(22,4): error TS17019: Unicode escape sequence cannot appear here.
9+
tests/cases/conformance/jsx/file.tsx(23,4): error TS17019: Unicode escape sequence cannot appear here.
10+
tests/cases/conformance/jsx/file.tsx(26,9): error TS17019: Unicode escape sequence cannot appear here.
11+
tests/cases/conformance/jsx/file.tsx(27,9): error TS17019: 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 TS17019: Unicode escape sequence cannot appear here.
32+
; <\u0061-b></a-b>
33+
~~~~~~~~
34+
!!! error TS17019: Unicode escape sequence cannot appear here.
35+
; <a-\u0063></a-c>
36+
~~~~~~~~
37+
!!! error TS17019: Unicode escape sequence cannot appear here.
38+
; <Comp\u0061 x={12} />
39+
~~~~~~~~~~
40+
!!! error TS17019: Unicode escape sequence cannot appear here.
41+
; <x.\u0076ideo />
42+
~~~~~~~~~~
43+
!!! error TS17019: Unicode escape sequence cannot appear here.
44+
; <\u{0061}></a>
45+
~~~~~~~~
46+
!!! error TS17019: Unicode escape sequence cannot appear here.
47+
; <\u{0061}-b></a-b>
48+
~~~~~~~~~~
49+
!!! error TS17019: Unicode escape sequence cannot appear here.
50+
; <a-\u{0063}></a-c>
51+
~~~~~~~~~~
52+
!!! error TS17019: Unicode escape sequence cannot appear here.
53+
; <Comp\u{0061} x={12} />
54+
~~~~~~~~~~~~
55+
!!! error TS17019: Unicode escape sequence cannot appear here.
56+
57+
// attribute name
58+
;<video data-\u0076ideo />
59+
~~~~~~~~~~~~~~~
60+
!!! error TS17019: Unicode escape sequence cannot appear here.
61+
;<video \u0073rc="" />
62+
~~~~~~~~
63+
!!! error TS17019: 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: "" });

0 commit comments

Comments
 (0)