Skip to content

Commit

Permalink
chore: disallows unicode escape sequence in jsx
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Works committed Jul 16, 2022
1 parent 481357a commit 63cf201
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 70 deletions.
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -6409,6 +6409,10 @@
"category": "Error",
"code": 17018
},
"Unicode escape sequence cannot appear here.": {
"category": "Error",
"code": 17019
},
"Circularity detected while resolving configuration: {0}": {
"category": "Error",
"code": 18000
Expand Down
27 changes: 19 additions & 8 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,13 @@ namespace ts {
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 @@ -2898,7 +2905,7 @@ namespace ts {
entity = finishNode(
factory.createQualifiedName(
entity,
parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false) as Identifier
parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false, /** allowUnicodeEscapeSequenceInIdentifierName */ true) as Identifier
),
pos
);
Expand All @@ -2910,7 +2917,7 @@ namespace ts {
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 @@ -2946,7 +2953,11 @@ namespace ts {
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 @@ -5302,7 +5313,7 @@ namespace ts {
// 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(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos);
return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true, /** allowUnicodeEscapeSequenceInIdentifierName */ true)), pos);
}

function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment): JsxElement | JsxSelfClosingElement | JsxFragment {
Expand Down Expand Up @@ -5491,9 +5502,9 @@ namespace ts {
// 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();
parseTokenNode<ThisExpression>() : parseIdentifierNameErrorOnUnicodeEscapeSequence();
while (parseOptional(SyntaxKind.DotToken)) {
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false, /** allowUnicodeEscapeSequenceInIdentifierName */ false)), pos) as JsxTagNamePropertyAccess;
}
return expression;
}
Expand Down Expand Up @@ -5532,7 +5543,7 @@ namespace ts {

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

function parseJsxAttributeValue(): JsxAttributeValue | undefined {
Expand Down Expand Up @@ -5638,7 +5649,7 @@ namespace ts {
}

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 ?
factory.createPropertyAccessChain(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 @@
tests/cases/conformance/jsx/file.tsx(15,4): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(16,4): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(17,4): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(18,4): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(19,6): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(20,4): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(21,4): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(22,4): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(23,4): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(26,9): error TS17019: Unicode escape sequence cannot appear here.
tests/cases/conformance/jsx/file.tsx(27,9): error TS17019: Unicode escape sequence cannot appear here.


==== tests/cases/conformance/jsx/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 TS17019: Unicode escape sequence cannot appear here.
; <\u0061-b></a-b>
~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.
; <a-\u0063></a-c>
~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.
; <Comp\u0061 x={12} />
~~~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.
; <x.\u0076ideo />
~~~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.
; <\u{0061}></a>
~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.
; <\u{0061}-b></a-b>
~~~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.
; <a-\u{0063}></a-c>
~~~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.
; <Comp\u{0061} x={12} />
~~~~~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.

// attribute name
;<video data-\u0076ideo />
~~~~~~~~~~~~~~~
!!! error TS17019: Unicode escape sequence cannot appear here.
;<video \u0073rc="" />
~~~~~~~~
!!! error TS17019: 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 @@ -9,26 +9,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 @@ -27,43 +27,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))

58 changes: 38 additions & 20 deletions tests/baselines/reference/unicodeEscapesInJsxtags.types
Original file line number Diff line number Diff line change
Expand Up @@ -27,53 +27,71 @@ 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
>null : null

// 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

0 comments on commit 63cf201

Please sign in to comment.