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

chore: disallows unicode escape sequence in JSX #48609

Merged
Merged
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
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -6737,6 +6737,10 @@
"category": "Error",
"code": 17020
},
"Unicode escape sequence cannot appear here.": {
"category": "Error",
"code": 17021
},
"Circularity detected while resolving configuration: {0}": {
"category": "Error",
"code": 18000
Expand Down
31 changes: 21 additions & 10 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2657,6 +2657,13 @@ namespace Parser {
return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage);
}

function parseIdentifierNameErrorOnUnicodeEscapeSequence(): Identifier {
if (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape()) {
parseErrorAtCurrentToken(Diagnostics.Unicode_escape_sequence_cannot_appear_here);
}
return createIdentifier(tokenIsIdentifierOrKeyword(token()));
}

function isLiteralPropertyName(): boolean {
return tokenIsIdentifierOrKeyword(token()) ||
token() === SyntaxKind.StringLiteral ||
Expand Down Expand Up @@ -3522,7 +3529,7 @@ namespace Parser {
entity = finishNode(
factory.createQualifiedName(
entity,
parseRightSideOfDot(allowReservedWords, /*allowPrivateIdentifiers*/ false) as Identifier
parseRightSideOfDot(allowReservedWords, /*allowPrivateIdentifiers*/ false, /*allowUnicodeEscapeSequenceInIdentifierName*/ true) as Identifier
),
pos
);
Expand All @@ -3534,7 +3541,7 @@ namespace Parser {
return finishNode(factory.createQualifiedName(entity, name), entity.pos);
}

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

return allowIdentifierNames ? parseIdentifierName() : parseIdentifier();
if (allowIdentifierNames) {
return allowUnicodeEscapeSequenceInIdentifierName ? parseIdentifierName() : parseIdentifierNameErrorOnUnicodeEscapeSequence();
}

return parseIdentifier();
}

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

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

const isThis = token() === SyntaxKind.ThisKeyword;
const tagName = parseIdentifierName();
const tagName = parseIdentifierNameErrorOnUnicodeEscapeSequence();
if (parseOptional(SyntaxKind.ColonToken)) {
scanJsxIdentifier();
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierName()), pos);
return finishNode(factory.createJsxNamespacedName(tagName, parseIdentifierNameErrorOnUnicodeEscapeSequence()), pos);
}
return isThis ? finishNode(factory.createToken(SyntaxKind.ThisKeyword), pos) : tagName;
}
Expand Down Expand Up @@ -6214,10 +6225,10 @@ namespace Parser {
const pos = getNodePos();
scanJsxIdentifier();

const attrName = parseIdentifierName();
const attrName = parseIdentifierNameErrorOnUnicodeEscapeSequence();
if (parseOptional(SyntaxKind.ColonToken)) {
scanJsxIdentifier();
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierName()), pos);
return finishNode(factory.createJsxNamespacedName(attrName, parseIdentifierNameErrorOnUnicodeEscapeSequence()), pos);
}
return attrName;
}
Expand Down Expand Up @@ -6307,7 +6318,7 @@ namespace Parser {
}

function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) {
const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true);
const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true, /*allowUnicodeEscapeSequenceInIdentifierName*/ true);
const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression);
const propertyAccess = isOptionalChain ?
factoryCreatePropertyAccessChain(expression, questionDotToken, name) :
Expand Down
64 changes: 64 additions & 0 deletions tests/baselines/reference/unicodeEscapesInJsxtags.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
file.tsx(15,4): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(16,4): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(17,4): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(18,4): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(19,6): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(20,4): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(21,4): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(22,4): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(23,4): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(26,9): error TS17021: Unicode escape sequence cannot appear here.
file.tsx(27,9): error TS17021: Unicode escape sequence cannot appear here.


==== file.tsx (11 errors) ====
import * as React from "react";
declare global {
namespace JSX {
interface IntrinsicElements {
"a-b": any;
"a-c": any;
}
}
}
const Compa = (x: {x: number}) => <div>{"" + x}</div>;
const x = { video: () => null }

// unicode escape sequence is not allowed in tag name or JSX attribute name.
// tag name:
; <\u0061></a>
~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
; <\u0061-b></a-b>
~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
; <a-\u0063></a-c>
~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
; <Comp\u0061 x={12} />
~~~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
; <x.\u0076ideo />
~~~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
; <\u{0061}></a>
~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
; <\u{0061}-b></a-b>
~~~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
; <a-\u{0063}></a-c>
~~~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
; <Comp\u{0061} x={12} />
~~~~~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.

// attribute name
;<video data-\u0076ideo />
~~~~~~~~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.
;<video \u0073rc="" />
~~~~~~~~
!!! error TS17021: Unicode escape sequence cannot appear here.

43 changes: 27 additions & 16 deletions tests/baselines/reference/unicodeEscapesInJsxtags.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,37 @@ declare global {
}
}
const Compa = (x: {x: number}) => <div>{"" + x}</div>;
const x = { video: () => null }

let a = <\u0061></a>; // works
let ab = <\u0061-b></a-b>; // works
let ac = <a-\u0063></a-c>; // works
let compa = <Comp\u0061 x={12} />; // works
// unicode escape sequence is not allowed in tag name or JSX attribute name.
// tag name:
; <\u0061></a>
; <\u0061-b></a-b>
; <a-\u0063></a-c>
; <Comp\u0061 x={12} />
; <x.\u0076ideo />
; <\u{0061}></a>
; <\u{0061}-b></a-b>
; <a-\u{0063}></a-c>
; <Comp\u{0061} x={12} />

let a2 = <\u{0061}></a>; // works
let ab2 = <\u{0061}-b></a-b>; // works
let ac2 = <a-\u{0063}></a-c>; // works
let compa2 = <Comp\u{0061} x={12} />; // works
// attribute name
;<video data-\u0076ideo />
;<video \u0073rc="" />


//// [file.js]
import * as React from "react";
const Compa = (x) => React.createElement("div", null, "" + x);
let a = React.createElement("a", null); // works
let ab = React.createElement("a-b", null); // works
let ac = React.createElement("a-c", null); // works
let compa = React.createElement(Comp\u0061, { x: 12 }); // works
let a2 = React.createElement("a", null); // works
let ab2 = React.createElement("a-b", null); // works
let ac2 = React.createElement("a-c", null); // works
let compa2 = React.createElement(Comp\u{0061}, { x: 12 }); // works
const x = { video: () => null };
React.createElement("a", null);
React.createElement("a-b", null);
React.createElement("a-c", null);
React.createElement(Comp\u0061, { x: 12 });
React.createElement(x.\u0076ideo, null);
React.createElement("a", null);
React.createElement("a-b", null);
React.createElement("a-c", null);
React.createElement(Comp\u{0061}, { x: 12 });
React.createElement("video", { "data-video": true });
React.createElement("video", { \u0073rc: "" });
48 changes: 30 additions & 18 deletions tests/baselines/reference/unicodeEscapesInJsxtags.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -29,43 +29,55 @@ const Compa = (x: {x: number}) => <div>{"" + x}</div>;
>x : Symbol(x, Decl(file.tsx, 9, 15))
>div : Symbol(JSX.IntrinsicElements.div, Decl(react.d.ts, 2400, 45))

let a = <\u0061></a>; // works
>a : Symbol(a, Decl(file.tsx, 11, 3))
const x = { video: () => null }
>x : Symbol(x, Decl(file.tsx, 10, 5))
>video : Symbol(video, Decl(file.tsx, 10, 11))

// unicode escape sequence is not allowed in tag name or JSX attribute name.
// tag name:
; <\u0061></a>
>\u0061 : Symbol(JSX.IntrinsicElements.a, Decl(react.d.ts, 2370, 33))
>a : Symbol(JSX.IntrinsicElements.a, Decl(react.d.ts, 2370, 33))

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

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

let compa = <Comp\u0061 x={12} />; // works
>compa : Symbol(compa, Decl(file.tsx, 14, 3))
; <Comp\u0061 x={12} />
>Comp\u0061 : Symbol(Compa, Decl(file.tsx, 9, 5))
>x : Symbol(x, Decl(file.tsx, 14, 23))
>x : Symbol(x, Decl(file.tsx, 17, 13))

; <x.\u0076ideo />
>x.\u0076ideo : Symbol(video, Decl(file.tsx, 10, 11))
>x : Symbol(x, Decl(file.tsx, 10, 5))
>\u0076ideo : Symbol(video, Decl(file.tsx, 10, 11))

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

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

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

let compa2 = <Comp\u{0061} x={12} />; // works
>compa2 : Symbol(compa2, Decl(file.tsx, 19, 3))
; <Comp\u{0061} x={12} />
>Comp\u{0061} : Symbol(Compa, Decl(file.tsx, 9, 5))
>x : Symbol(x, Decl(file.tsx, 19, 26))
>x : Symbol(x, Decl(file.tsx, 22, 15))

// attribute name
;<video data-\u0076ideo />
>video : Symbol(JSX.IntrinsicElements.video, Decl(react.d.ts, 2481, 44))
>data-\u0076ideo : Symbol(data-\u0076ideo, Decl(file.tsx, 25, 7))

;<video \u0073rc="" />
>video : Symbol(JSX.IntrinsicElements.video, Decl(react.d.ts, 2481, 44))
>\u0073rc : Symbol(\u0073rc, Decl(file.tsx, 26, 7))

57 changes: 37 additions & 20 deletions tests/baselines/reference/unicodeEscapesInJsxtags.types
Original file line number Diff line number Diff line change
Expand Up @@ -29,53 +29,70 @@ const Compa = (x: {x: number}) => <div>{"" + x}</div>;
>x : { x: number; }
>div : any

let a = <\u0061></a>; // works
>a : JSX.Element
const x = { video: () => null }
>x : { video: () => any; }
>{ video: () => null } : { video: () => any; }
>video : () => any
>() => null : () => any

// unicode escape sequence is not allowed in tag name or JSX attribute name.
// tag name:
; <\u0061></a>
><\u0061></a> : JSX.Element
>\u0061 : JSX.Element
>a : JSX.Element
>\u0061 : any
>a : any

let ab = <\u0061-b></a-b>; // works
>ab : JSX.Element
; <\u0061-b></a-b>
><\u0061-b></a-b> : JSX.Element
>\u0061-b : any
>a-b : any

let ac = <a-\u0063></a-c>; // works
>ac : JSX.Element
; <a-\u0063></a-c>
><a-\u0063></a-c> : JSX.Element
>a-\u0063 : any
>a-c : any

let compa = <Comp\u0061 x={12} />; // works
>compa : JSX.Element
; <Comp\u0061 x={12} />
><Comp\u0061 x={12} /> : JSX.Element
>Comp\u0061 : (x: { x: number; }) => JSX.Element
>x : number
>12 : 12

let a2 = <\u{0061}></a>; // works
>a2 : JSX.Element
; <x.\u0076ideo />
><x.\u0076ideo /> : JSX.Element
>x.\u0076ideo : () => any
>x : { video: () => any; }
>\u0076ideo : () => any

; <\u{0061}></a>
><\u{0061}></a> : JSX.Element
>\u{0061} : JSX.Element
>a : JSX.Element
>\u{0061} : any
>a : any

let ab2 = <\u{0061}-b></a-b>; // works
>ab2 : JSX.Element
; <\u{0061}-b></a-b>
><\u{0061}-b></a-b> : JSX.Element
>\u{0061}-b : any
>a-b : any

let ac2 = <a-\u{0063}></a-c>; // works
>ac2 : JSX.Element
; <a-\u{0063}></a-c>
><a-\u{0063}></a-c> : JSX.Element
>a-\u{0063} : any
>a-c : any

let compa2 = <Comp\u{0061} x={12} />; // works
>compa2 : JSX.Element
; <Comp\u{0061} x={12} />
><Comp\u{0061} x={12} /> : JSX.Element
>Comp\u{0061} : (x: { x: number; }) => JSX.Element
>x : number
>12 : 12

// attribute name
;<video data-\u0076ideo />
><video data-\u0076ideo /> : JSX.Element
>video : any
>data-\u0076ideo : true

;<video \u0073rc="" />
><video \u0073rc="" /> : JSX.Element
>video : any
>\u0073rc : string

Loading
Loading