Skip to content

Commit 348c4dd

Browse files
authored
Throw syntax error for } and > in JSX text (#36636)
* Throw syntax error for `}` and `>` in JSX text Fixes #36341 * Add codefix for error
1 parent ad8c209 commit 348c4dd

19 files changed

+487
-6
lines changed

Diff for: src/compiler/diagnosticMessages.json

+16
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,14 @@
11351135
"category": "Error",
11361136
"code": 1380
11371137
},
1138+
"Unexpected token. Did you mean `{'}'}` or `}`?": {
1139+
"category": "Error",
1140+
"code": 1381
1141+
},
1142+
"Unexpected token. Did you mean `{'>'}` or `>`?": {
1143+
"category": "Error",
1144+
"code": 1382
1145+
},
11381146

11391147
"The types of '{0}' are incompatible between these types.": {
11401148
"category": "Error",
@@ -5457,6 +5465,14 @@
54575465
"category": "Message",
54585466
"code": 95099
54595467
},
5468+
"Convert invalid character to its html entity code": {
5469+
"category": "Message",
5470+
"code": 95100
5471+
},
5472+
"Wrap invalid character in an expression container": {
5473+
"category": "Message",
5474+
"code": 95101
5475+
},
54605476

54615477
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
54625478
"category": "Error",

Diff for: src/compiler/scanner.ts

+6
Original file line numberDiff line numberDiff line change
@@ -2156,6 +2156,12 @@ namespace ts {
21562156
}
21572157
break;
21582158
}
2159+
if (char === CharacterCodes.greaterThan) {
2160+
error(Diagnostics.Unexpected_token_Did_you_mean_or_gt, pos, 1);
2161+
}
2162+
if (char === CharacterCodes.closeBrace) {
2163+
error(Diagnostics.Unexpected_token_Did_you_mean_or_rbrace, pos, 1);
2164+
}
21592165

21602166
if (lastNonWhitespace > 0) lastNonWhitespace++;
21612167

Diff for: src/services/codefixes/fixInvalidJsxCharacters.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixIdHtmlEntity = "invalidJsxCharactersConvertToHtmlEntity";
4+
const fixIdExpression = "invalidJsxCharactersConvertToExpression";
5+
6+
const errorCodes = [Diagnostics.Unexpected_token_Did_you_mean_or_gt.code, Diagnostics.Unexpected_token_Did_you_mean_or_rbrace.code];
7+
8+
registerCodeFix({
9+
errorCodes,
10+
getCodeActions: context => {
11+
const { sourceFile, span } = context;
12+
const changeToExpression = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, span.start, /* useHtmlEntity */ false));
13+
const changeToHtmlEntity = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, span.start, /* useHtmlEntity */ true));
14+
return [
15+
createCodeFixActionWithoutFixAll(fixIdExpression, changeToExpression, Diagnostics.Wrap_invalid_character_in_an_expression_container),
16+
createCodeFixAction(fixIdHtmlEntity, changeToHtmlEntity, Diagnostics.Convert_invalid_character_to_its_html_entity_code, fixIdHtmlEntity, Diagnostics.Convert_invalid_character_to_its_html_entity_code),
17+
];
18+
},
19+
fixIds: [fixIdExpression, fixIdHtmlEntity],
20+
});
21+
22+
const htmlEntity = {
23+
">": ">",
24+
"}": "}",
25+
};
26+
function isValidCharacter(character: string): character is keyof typeof htmlEntity {
27+
return hasProperty(htmlEntity, character);
28+
}
29+
30+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, start: number, useHtmlEntity: boolean) {
31+
const character = sourceFile.getText()[start];
32+
// sanity check
33+
if (!isValidCharacter(character)) {
34+
return;
35+
}
36+
37+
const replacement = useHtmlEntity
38+
? htmlEntity[character]
39+
: `{'${character}'}`;
40+
changes.replaceRangeWithText(sourceFile, { pos: start, end: start + 1 }, replacement);
41+
}
42+
}

Diff for: src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"codefixes/fixModuleAndTargetOptions.ts",
7575
"codefixes/fixExtendsInterfaceBecomesImplements.ts",
7676
"codefixes/fixForgottenThisPropertyAccess.ts",
77+
"codefixes/fixInvalidJsxCharacters.ts",
7778
"codefixes/fixUnusedIdentifier.ts",
7879
"codefixes/fixUnreachableCode.ts",
7980
"codefixes/fixUnusedLabel.ts",

Diff for: tests/baselines/reference/jsxAndTypeAssertion.errors.txt

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(6,6): error TS17008: JSX element 'any' has no corresponding closing tag.
22
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(6,13): error TS2582: Cannot find name 'test'. Do you need to install type definitions for a test runner? Try `npm i @types/jest` or `npm i @types/mocha`.
33
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(6,17): error TS1005: '}' expected.
4+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(6,31): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
45
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(8,6): error TS17008: JSX element 'any' has no corresponding closing tag.
56
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(10,6): error TS17008: JSX element 'foo' has no corresponding closing tag.
7+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(10,24): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
68
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(10,32): error TS1005: '}' expected.
9+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(12,23): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
10+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(12,24): error TS1382: Unexpected token. Did you mean `{'>'}` or `>`?
711
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(12,36): error TS1005: '}' expected.
812
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,17): error TS17008: JSX element 'foo' has no corresponding closing tag.
13+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,23): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
14+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,24): error TS1382: Unexpected token. Did you mean `{'>'}` or `>`?
15+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,38): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
916
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,45): error TS1005: '}' expected.
1017
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,2): error TS17008: JSX element 'foo' has no corresponding closing tag.
1118
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,8): error TS17008: JSX element 'foo' has no corresponding closing tag.
1219
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,13): error TS17008: JSX element 'foo' has no corresponding closing tag.
20+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,69): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
21+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,76): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
1322
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: ':' expected.
1423
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' expected.
1524

1625

17-
==== tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx (14 errors) ====
26+
==== tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx (23 errors) ====
1827
declare var createElement: any;
1928

2029
class foo {}
@@ -27,6 +36,8 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex
2736
!!! error TS2582: Cannot find name 'test'. Do you need to install type definitions for a test runner? Try `npm i @types/jest` or `npm i @types/mocha`.
2837
~
2938
!!! error TS1005: '}' expected.
39+
~
40+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
3041

3142
x = <any><any></any>;
3243
~~~
@@ -35,16 +46,28 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex
3546
x = <foo>hello {<foo>{}} </foo>;
3647
~~~
3748
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
49+
~
50+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
3851
~
3952
!!! error TS1005: '}' expected.
4053

4154
x = <foo test={<foo>{}}>hello</foo>;
55+
~
56+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
57+
~
58+
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
4259
~
4360
!!! error TS1005: '}' expected.
4461

4562
x = <foo test={<foo>{}}>hello{<foo>{}}</foo>;
4663
~~~
4764
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
65+
~
66+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
67+
~
68+
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
69+
~
70+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
4871
~
4972
!!! error TS1005: '}' expected.
5073

@@ -57,6 +80,10 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex
5780
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
5881
~~~
5982
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
83+
~
84+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
85+
~
86+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
6087

6188

6289

Diff for: tests/baselines/reference/jsxEsprimaFbTestSuite.errors.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,1): error TS2695: Left
22
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,17): error TS1005: '{' expected.
33
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,23): error TS2304: Cannot find name 'right'.
44
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,23): error TS2657: JSX expressions must have one parent element.
5+
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,41): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
56
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,57): error TS1109: Expression expected.
67
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,58): error TS1109: Expression expected.
78

89

9-
==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (6 errors) ====
10+
==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (7 errors) ====
1011
declare var React: any;
1112
declare var 日本語;
1213
declare var AbC_def;
@@ -54,6 +55,8 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,58): error TS1109: Expr
5455
!!! error TS2304: Cannot find name 'right'.
5556
~~~~~
5657
!!! error TS2657: JSX expressions must have one parent element.
58+
~
59+
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
5760
~
5861
!!! error TS1109: Expression expected.
5962
~

Diff for: tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt

+16-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ tests/cases/conformance/jsx/19.tsx(1,9): error TS2695: Left side of comma operat
3939
tests/cases/conformance/jsx/19.tsx(1,64): error TS2657: JSX expressions must have one parent element.
4040
tests/cases/conformance/jsx/2.tsx(1,3): error TS1003: Identifier expected.
4141
tests/cases/conformance/jsx/20.tsx(1,10): error TS1005: '}' expected.
42+
tests/cases/conformance/jsx/20.tsx(1,11): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
4243
tests/cases/conformance/jsx/21.tsx(1,20): error TS1003: Identifier expected.
4344
tests/cases/conformance/jsx/22.tsx(1,15): error TS1003: Identifier expected.
4445
tests/cases/conformance/jsx/22.tsx(1,21): error TS1109: Expression expected.
@@ -55,6 +56,8 @@ tests/cases/conformance/jsx/25.tsx(1,29): error TS1128: Declaration or statement
5556
tests/cases/conformance/jsx/25.tsx(1,32): error TS2304: Cannot find name 'props'.
5657
tests/cases/conformance/jsx/25.tsx(1,38): error TS1109: Expression expected.
5758
tests/cases/conformance/jsx/25.tsx(1,39): error TS1109: Expression expected.
59+
tests/cases/conformance/jsx/26.tsx(1,4): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
60+
tests/cases/conformance/jsx/27.tsx(1,5): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
5861
tests/cases/conformance/jsx/28.tsx(1,2): error TS17008: JSX element 'a' has no corresponding closing tag.
5962
tests/cases/conformance/jsx/28.tsx(1,6): error TS1005: '{' expected.
6063
tests/cases/conformance/jsx/28.tsx(2,1): error TS1005: '</' expected.
@@ -67,6 +70,7 @@ tests/cases/conformance/jsx/3.tsx(1,2): error TS1109: Expression expected.
6770
tests/cases/conformance/jsx/3.tsx(1,3): error TS2304: Cannot find name 'a'.
6871
tests/cases/conformance/jsx/3.tsx(1,6): error TS1109: Expression expected.
6972
tests/cases/conformance/jsx/3.tsx(1,7): error TS1109: Expression expected.
73+
tests/cases/conformance/jsx/30.tsx(1,4): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
7074
tests/cases/conformance/jsx/31.tsx(1,4): error TS1003: Identifier expected.
7175
tests/cases/conformance/jsx/4.tsx(1,6): error TS1005: '{' expected.
7276
tests/cases/conformance/jsx/5.tsx(1,2): error TS17008: JSX element 'a' has no corresponding closing tag.
@@ -236,10 +240,12 @@ tests/cases/conformance/jsx/9.tsx(1,16): error TS1109: Expression expected.
236240
!!! error TS2695: Left side of comma operator is unused and has no side effects.
237241
~
238242
!!! error TS2657: JSX expressions must have one parent element.
239-
==== tests/cases/conformance/jsx/20.tsx (1 errors) ====
243+
==== tests/cases/conformance/jsx/20.tsx (2 errors) ====
240244
<a>{"str";}</a>;
241245
~
242246
!!! error TS1005: '}' expected.
247+
~
248+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
243249
==== tests/cases/conformance/jsx/21.tsx (1 errors) ====
244250
<span className="a", id="b" />;
245251
~
@@ -286,11 +292,15 @@ tests/cases/conformance/jsx/9.tsx(1,16): error TS1109: Expression expected.
286292
!!! error TS1109: Expression expected.
287293

288294

289-
==== tests/cases/conformance/jsx/26.tsx (0 errors) ====
295+
==== tests/cases/conformance/jsx/26.tsx (1 errors) ====
290296
<a>></a>;
297+
~
298+
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
291299

292-
==== tests/cases/conformance/jsx/27.tsx (0 errors) ====
300+
==== tests/cases/conformance/jsx/27.tsx (1 errors) ====
293301
<a> ></a>;
302+
~
303+
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
294304

295305
==== tests/cases/conformance/jsx/28.tsx (3 errors) ====
296306
<a b=}>;
@@ -312,8 +322,10 @@ tests/cases/conformance/jsx/9.tsx(1,16): error TS1109: Expression expected.
312322

313323

314324
!!! error TS1005: '</' expected.
315-
==== tests/cases/conformance/jsx/30.tsx (0 errors) ====
325+
==== tests/cases/conformance/jsx/30.tsx (1 errors) ====
316326
<a>}</a>;
327+
~
328+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
317329

318330
==== tests/cases/conformance/jsx/31.tsx (1 errors) ====
319331
<a .../*hai*/asdf/>;
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
tests/cases/conformance/jsx/Error1.tsx(1,15): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
2+
tests/cases/conformance/jsx/Error2.tsx(1,15): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
3+
tests/cases/conformance/jsx/Error3.tsx(1,22): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
4+
tests/cases/conformance/jsx/Error4.tsx(1,22): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
5+
tests/cases/conformance/jsx/Error5.tsx(1,15): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
6+
tests/cases/conformance/jsx/Error6.tsx(1,15): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
7+
8+
9+
==== tests/cases/conformance/jsx/file.tsx (0 errors) ====
10+
declare module JSX {
11+
interface Element {}
12+
interface IntrinsicElements {
13+
[s: string]: any;
14+
}
15+
}
16+
17+
==== tests/cases/conformance/jsx/Error1.tsx (1 errors) ====
18+
let x1 = <div>}</div>;
19+
~
20+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
21+
22+
==== tests/cases/conformance/jsx/Error2.tsx (1 errors) ====
23+
let x2 = <div>></div>;
24+
~
25+
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
26+
27+
==== tests/cases/conformance/jsx/Error3.tsx (1 errors) ====
28+
let x3 = <div>{"foo"}}</div>;
29+
~
30+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
31+
32+
==== tests/cases/conformance/jsx/Error4.tsx (1 errors) ====
33+
let x4 = <div>{"foo"}></div>;
34+
~
35+
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
36+
37+
==== tests/cases/conformance/jsx/Error5.tsx (1 errors) ====
38+
let x5 = <div>}{"foo"}</div>;
39+
~
40+
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
41+
42+
==== tests/cases/conformance/jsx/Error6.tsx (1 errors) ====
43+
let x6 = <div>>{"foo"}</div>;
44+
~
45+
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
46+

Diff for: tests/baselines/reference/jsxParsingError3.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [tests/cases/conformance/jsx/jsxParsingError3.tsx] ////
2+
3+
//// [file.tsx]
4+
declare module JSX {
5+
interface Element {}
6+
interface IntrinsicElements {
7+
[s: string]: any;
8+
}
9+
}
10+
11+
//// [Error1.tsx]
12+
let x1 = <div>}</div>;
13+
14+
//// [Error2.tsx]
15+
let x2 = <div>></div>;
16+
17+
//// [Error3.tsx]
18+
let x3 = <div>{"foo"}}</div>;
19+
20+
//// [Error4.tsx]
21+
let x4 = <div>{"foo"}></div>;
22+
23+
//// [Error5.tsx]
24+
let x5 = <div>}{"foo"}</div>;
25+
26+
//// [Error6.tsx]
27+
let x6 = <div>>{"foo"}</div>;
28+
29+
30+
//// [file.jsx]
31+
//// [Error1.jsx]
32+
var x1 = <div>}</div>;
33+
//// [Error2.jsx]
34+
var x2 = <div>></div>;
35+
//// [Error3.jsx]
36+
var x3 = <div>{"foo"}}</div>;
37+
//// [Error4.jsx]
38+
var x4 = <div>{"foo"}></div>;
39+
//// [Error5.jsx]
40+
var x5 = <div>}{"foo"}</div>;
41+
//// [Error6.jsx]
42+
var x6 = <div>>{"foo"}</div>;

0 commit comments

Comments
 (0)