Skip to content

Commit 555297a

Browse files
committed
Fix case where # occurs after > in JSX expression
1 parent 2f92483 commit 555297a

File tree

10 files changed

+237
-42
lines changed

10 files changed

+237
-42
lines changed

src/compiler/parser.ts

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,10 @@ namespace ts {
844844
return token = scanner.scanJsxIdentifier();
845845
}
846846

847+
function scanJsxText(): SyntaxKind {
848+
return token = scanner.scanJsxToken();
849+
}
850+
847851
function speculationHelper<T>(callback: () => T, isLookAhead: boolean): T {
848852
// Keep track of the state we'll need to rollback to if lookahead fails (or if the
849853
// caller asked us to always reset our state).
@@ -913,9 +917,11 @@ namespace ts {
913917
return token > SyntaxKind.LastReservedWord;
914918
}
915919

916-
function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage): boolean {
920+
function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, advance = true): boolean {
917921
if (token === kind) {
918-
nextToken();
922+
if (advance) {
923+
nextToken();
924+
}
919925
return true;
920926
}
921927

@@ -929,6 +935,13 @@ namespace ts {
929935
return false;
930936
}
931937

938+
function parseOptionalWithoutAdvancing(t: SyntaxKind): boolean {
939+
if (token === t) {
940+
return true;
941+
}
942+
return false;
943+
}
944+
932945
function parseOptional(t: SyntaxKind): boolean {
933946
if (token === t) {
934947
nextToken();
@@ -3178,7 +3191,7 @@ namespace ts {
31783191
return parseTypeAssertion();
31793192
}
31803193
if (lookAhead(nextTokenIsIdentifierOrKeyword)) {
3181-
return parseJsxElementOrSelfClosingElement();
3194+
return parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ true);
31823195
}
31833196
// Fall through
31843197
default:
@@ -3308,14 +3321,14 @@ namespace ts {
33083321
return finishNode(node);
33093322
}
33103323

3311-
function parseJsxElementOrSelfClosingElement(): JsxElement|JsxSelfClosingElement {
3312-
let opening = parseJsxOpeningOrSelfClosingElement();
3324+
function parseJsxElementOrSelfClosingElement(inExpressionContext: boolean): JsxElement|JsxSelfClosingElement {
3325+
let opening = parseJsxOpeningOrSelfClosingElement(inExpressionContext);
33133326
if (opening.kind === SyntaxKind.JsxOpeningElement) {
33143327
let node = <JsxElement>createNode(SyntaxKind.JsxElement, opening.pos);
33153328
node.openingElement = opening;
33163329

33173330
node.children = parseJsxChildren(node.openingElement.tagName);
3318-
node.closingElement = parseJsxClosingElement();
3331+
node.closingElement = parseJsxClosingElement(inExpressionContext);
33193332
return finishNode(node);
33203333
}
33213334
else {
@@ -3336,9 +3349,9 @@ namespace ts {
33363349
case SyntaxKind.JsxText:
33373350
return parseJsxText();
33383351
case SyntaxKind.OpenBraceToken:
3339-
return parseJsxExpression();
3352+
return parseJsxExpression(/*inExpression*/false);
33403353
case SyntaxKind.LessThanToken:
3341-
return parseJsxElementOrSelfClosingElement();
3354+
return parseJsxElementOrSelfClosingElement(/*inExpression*/false);
33423355
}
33433356
Debug.fail("Unknown JSX child kind " + token);
33443357
}
@@ -3368,7 +3381,7 @@ namespace ts {
33683381
return result;
33693382
}
33703383

3371-
function parseJsxOpeningOrSelfClosingElement(): JsxOpeningElement|JsxSelfClosingElement {
3384+
function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement|JsxSelfClosingElement {
33723385
let fullStart = scanner.getStartPos();
33733386

33743387
parseExpected(SyntaxKind.LessThanToken);
@@ -3378,12 +3391,22 @@ namespace ts {
33783391
let attributes = parseList(ParsingContext.JsxAttributes, parseJsxAttribute);
33793392
let node: JsxOpeningLikeElement;
33803393

3381-
if (parseOptional(SyntaxKind.GreaterThanToken)) {
3394+
if (parseOptionalWithoutAdvancing(SyntaxKind.GreaterThanToken)) {
3395+
// Closing tag, so scan the immediately-following text with the JSX scanning instead
3396+
// of regular scanning to avoid treating illegal characters (e.g. '#') as immediate
3397+
// scanning errors
33823398
node = <JsxOpeningElement>createNode(SyntaxKind.JsxOpeningElement, fullStart);
3399+
scanJsxText();
33833400
}
33843401
else {
33853402
parseExpected(SyntaxKind.SlashToken);
3386-
parseExpected(SyntaxKind.GreaterThanToken);
3403+
if (inExpressionContext) {
3404+
parseExpected(SyntaxKind.GreaterThanToken);
3405+
}
3406+
else {
3407+
parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*advance*/ false);
3408+
scanJsxText();
3409+
}
33873410
node = <JsxSelfClosingElement>createNode(SyntaxKind.JsxSelfClosingElement, fullStart);
33883411
}
33893412

@@ -3406,14 +3429,20 @@ namespace ts {
34063429
return elementName;
34073430
}
34083431

3409-
function parseJsxExpression(): JsxExpression {
3432+
function parseJsxExpression(inExpressionContext: boolean): JsxExpression {
34103433
let node = <JsxExpression>createNode(SyntaxKind.JsxExpression);
34113434

34123435
parseExpected(SyntaxKind.OpenBraceToken);
34133436
if (token !== SyntaxKind.CloseBraceToken) {
34143437
node.expression = parseExpression();
34153438
}
3416-
parseExpected(SyntaxKind.CloseBraceToken);
3439+
if(inExpressionContext) {
3440+
parseExpected(SyntaxKind.CloseBraceToken);
3441+
}
3442+
else {
3443+
parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*advance*/ false);
3444+
scanJsxText();
3445+
}
34173446

34183447
return finishNode(node);
34193448
}
@@ -3432,7 +3461,7 @@ namespace ts {
34323461
node.initializer = parseLiteralNode();
34333462
break;
34343463
default:
3435-
node.initializer = parseJsxExpression();
3464+
node.initializer = parseJsxExpression(/*inExpressionContext*/ true);
34363465
break;
34373466
}
34383467
}
@@ -3448,11 +3477,17 @@ namespace ts {
34483477
return finishNode(node);
34493478
}
34503479

3451-
function parseJsxClosingElement(): JsxClosingElement {
3480+
function parseJsxClosingElement(inExpressionContext: boolean): JsxClosingElement {
34523481
let node = <JsxClosingElement>createNode(SyntaxKind.JsxClosingElement);
34533482
parseExpected(SyntaxKind.LessThanSlashToken);
34543483
node.tagName = parseJsxElementName();
3455-
parseExpected(SyntaxKind.GreaterThanToken);
3484+
if (inExpressionContext) {
3485+
parseExpected(SyntaxKind.GreaterThanToken);
3486+
}
3487+
else {
3488+
parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*advance*/ false);
3489+
scanJsxText();
3490+
}
34563491
return finishNode(node);
34573492
}
34583493

tests/baselines/reference/jsxAndTypeAssertion.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,18 @@ var foo = (function () {
2929
return foo;
3030
})();
3131
var x;
32-
x = <any> {test}: <any></any> };
32+
x = <any> {test} <any></any> };
3333

3434
x = <any><any></any>;
3535

36-
x = <foo>hello {<foo>} </foo>};
36+
x = <foo>hello {<foo>} </foo>}
3737

38-
x = <foo test={<foo>}>hello</foo>}/>;
38+
x = <foo test={<foo>}>hello</foo>}/>
3939

40-
x = <foo test={<foo>}>hello{<foo>}</foo>};
40+
x = <foo test={<foo>}>hello{<foo>}</foo>}
4141

4242
x = <foo>x</foo>, x = <foo />;
4343

4444
<foo>{<foo><foo>{/foo/.test(x) ? <foo><foo></foo> : <foo><foo></foo>}</foo>}</foo>
4545
:
46-
}
47-
48-
49-
</></>}</></>}/></></></>;
46+
}</></>}</></>}/></></></>;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [jsxHash.tsx]
2+
var t02 = <a>{0}#</a>;
3+
var t03 = <a>#{0}</a>;
4+
var t04 = <a>#{0}#</a>;
5+
var t05 = <a>#<i></i></a>;
6+
var t06 = <a>#<i></i></a>;
7+
var t07 = <a>#<i>#</i></a>;
8+
var t08 = <a><i></i>#</a>;
9+
var t09 = <a>#<i></i>#</a>;
10+
var t10 = <a><i/>#</a>;
11+
var t11 = <a>#<i/></a>;
12+
var t12 = <a>#</a>;
13+
14+
15+
//// [jsxHash.jsx]
16+
var t02 = <a>{0}#</a>;
17+
var t03 = <a>#{0}</a>;
18+
var t04 = <a>#{0}#</a>;
19+
var t05 = <a>#<i></i></a>;
20+
var t06 = <a>#<i></i></a>;
21+
var t07 = <a>#<i>#</i></a>;
22+
var t08 = <a><i></i>#</a>;
23+
var t09 = <a>#<i></i>#</a>;
24+
var t10 = <a><i />#</a>;
25+
var t11 = <a>#<i /></a>;
26+
var t12 = <a>#</a>;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/compiler/jsxHash.tsx ===
2+
var t02 = <a>{0}#</a>;
3+
>t02 : Symbol(t02, Decl(jsxHash.tsx, 0, 3))
4+
5+
var t03 = <a>#{0}</a>;
6+
>t03 : Symbol(t03, Decl(jsxHash.tsx, 1, 3))
7+
8+
var t04 = <a>#{0}#</a>;
9+
>t04 : Symbol(t04, Decl(jsxHash.tsx, 2, 3))
10+
11+
var t05 = <a>#<i></i></a>;
12+
>t05 : Symbol(t05, Decl(jsxHash.tsx, 3, 3))
13+
14+
var t06 = <a>#<i></i></a>;
15+
>t06 : Symbol(t06, Decl(jsxHash.tsx, 4, 3))
16+
17+
var t07 = <a>#<i>#</i></a>;
18+
>t07 : Symbol(t07, Decl(jsxHash.tsx, 5, 3))
19+
20+
var t08 = <a><i></i>#</a>;
21+
>t08 : Symbol(t08, Decl(jsxHash.tsx, 6, 3))
22+
23+
var t09 = <a>#<i></i>#</a>;
24+
>t09 : Symbol(t09, Decl(jsxHash.tsx, 7, 3))
25+
26+
var t10 = <a><i/>#</a>;
27+
>t10 : Symbol(t10, Decl(jsxHash.tsx, 8, 3))
28+
29+
var t11 = <a>#<i/></a>;
30+
>t11 : Symbol(t11, Decl(jsxHash.tsx, 9, 3))
31+
32+
var t12 = <a>#</a>;
33+
>t12 : Symbol(t12, Decl(jsxHash.tsx, 10, 3))
34+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
=== tests/cases/compiler/jsxHash.tsx ===
2+
var t02 = <a>{0}#</a>;
3+
>t02 : any
4+
><a>{0}#</a> : any
5+
>a : any
6+
>a : any
7+
8+
var t03 = <a>#{0}</a>;
9+
>t03 : any
10+
><a>#{0}</a> : any
11+
>a : any
12+
>a : any
13+
14+
var t04 = <a>#{0}#</a>;
15+
>t04 : any
16+
><a>#{0}#</a> : any
17+
>a : any
18+
>a : any
19+
20+
var t05 = <a>#<i></i></a>;
21+
>t05 : any
22+
><a>#<i></i></a> : any
23+
>a : any
24+
><i></i> : any
25+
>i : any
26+
>i : any
27+
>a : any
28+
29+
var t06 = <a>#<i></i></a>;
30+
>t06 : any
31+
><a>#<i></i></a> : any
32+
>a : any
33+
><i></i> : any
34+
>i : any
35+
>i : any
36+
>a : any
37+
38+
var t07 = <a>#<i>#</i></a>;
39+
>t07 : any
40+
><a>#<i>#</i></a> : any
41+
>a : any
42+
><i>#</i> : any
43+
>i : any
44+
>i : any
45+
>a : any
46+
47+
var t08 = <a><i></i>#</a>;
48+
>t08 : any
49+
><a><i></i>#</a> : any
50+
>a : any
51+
><i></i> : any
52+
>i : any
53+
>i : any
54+
>a : any
55+
56+
var t09 = <a>#<i></i>#</a>;
57+
>t09 : any
58+
><a>#<i></i>#</a> : any
59+
>a : any
60+
><i></i> : any
61+
>i : any
62+
>i : any
63+
>a : any
64+
65+
var t10 = <a><i/>#</a>;
66+
>t10 : any
67+
><a><i/>#</a> : any
68+
>a : any
69+
><i/> : any
70+
>i : any
71+
>a : any
72+
73+
var t11 = <a>#<i/></a>;
74+
>t11 : any
75+
><a>#<i/></a> : any
76+
>a : any
77+
><i/> : any
78+
>i : any
79+
>a : any
80+
81+
var t12 = <a>#</a>;
82+
>t12 : any
83+
><a>#</a> : any
84+
>a : any
85+
>a : any
86+

tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,16 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(24,15): error TS1003:
6262
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(25,7): error TS1005: '...' expected.
6363
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(25,7): error TS2304: Cannot find name 'props'.
6464
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(27,17): error TS1005: '>' expected.
65-
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(27,18): error TS1109: Expression expected.
6665
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(28,10): error TS2304: Cannot find name 'props'.
6766
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(28,28): error TS1005: '>' expected.
68-
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(28,29): error TS1109: Expression expected.
6967
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(32,6): error TS1005: '{' expected.
7068
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(33,6): error TS1005: '{' expected.
7169
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(33,7): error TS1109: Expression expected.
7270
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,4): error TS1003: Identifier expected.
7371
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002: Expected corresponding JSX closing tag for 'a'.
7472

7573

76-
==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (73 errors) ====
74+
==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (71 errors) ====
7775
declare var React: any;
7876

7977
</>;
@@ -229,15 +227,11 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002
229227
<div>stuff</div {...props}>;
230228
~
231229
!!! error TS1005: '>' expected.
232-
~~~
233-
!!! error TS1109: Expression expected.
234230
<div {...props}>stuff</div {...props}>;
235231
~~~~~
236232
!!! error TS2304: Cannot find name 'props'.
237233
~
238234
!!! error TS1005: '>' expected.
239-
~~~
240-
!!! error TS1109: Expression expected.
241235

242236
<a>></a>;
243237
<a> ></a>;

tests/baselines/reference/jsxInvalidEsprimaTestSuite.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,17 @@ a['foo'] > ;
6565
<a b=>;
6666
var x = <div>one</div><div>two</div>;;
6767
var x = <div>one</div> /* intervening comment */ /* intervening comment */ <div>two</div>;;
68-
<a>{"str"};}</a>;
69-
<span className="a"/>, id="b" />;
70-
<div className=/>"app">;
68+
<a>{"str"}}</a>;
69+
<span className="a"/> id="b" />;
70+
<div className=/>>;
7171
<div {...props}/>;
7272

73-
<div>stuff</div> {}...props}>;
74-
<div {...props}>stuff</div> {}...props}>;
73+
<div>stuff</div>...props}>;
74+
<div {...props}>stuff</div>...props}>;
7575

7676
<a>></a>;
7777
<a> ></a>;
7878
<a b=>;
7979
<a b={ < }>;
8080
<a>}</a>;
81-
<a /> .../*hai*/asdf/>;</></></></>;
81+
<a /> /*hai*//*hai*/asdf/>;</></></></>;

0 commit comments

Comments
 (0)