Skip to content

Commit

Permalink
Throw syntax error for } and > in JSX text (#36636)
Browse files Browse the repository at this point in the history
* Throw syntax error for `}` and `>` in JSX text

Fixes #36341

* Add codefix for error
  • Loading branch information
bradzacher committed Feb 11, 2020
1 parent ad8c209 commit 348c4dd
Show file tree
Hide file tree
Showing 19 changed files with 487 additions and 6 deletions.
16 changes: 16 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,14 @@
"category": "Error",
"code": 1380
},
"Unexpected token. Did you mean `{'}'}` or `}`?": {
"category": "Error",
"code": 1381
},
"Unexpected token. Did you mean `{'>'}` or `>`?": {
"category": "Error",
"code": 1382
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down Expand Up @@ -5457,6 +5465,14 @@
"category": "Message",
"code": 95099
},
"Convert invalid character to its html entity code": {
"category": "Message",
"code": 95100
},
"Wrap invalid character in an expression container": {
"category": "Message",
"code": 95101
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2156,6 +2156,12 @@ namespace ts {
}
break;
}
if (char === CharacterCodes.greaterThan) {
error(Diagnostics.Unexpected_token_Did_you_mean_or_gt, pos, 1);
}
if (char === CharacterCodes.closeBrace) {
error(Diagnostics.Unexpected_token_Did_you_mean_or_rbrace, pos, 1);
}

if (lastNonWhitespace > 0) lastNonWhitespace++;

Expand Down
42 changes: 42 additions & 0 deletions src/services/codefixes/fixInvalidJsxCharacters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* @internal */
namespace ts.codefix {
const fixIdHtmlEntity = "invalidJsxCharactersConvertToHtmlEntity";
const fixIdExpression = "invalidJsxCharactersConvertToExpression";

const errorCodes = [Diagnostics.Unexpected_token_Did_you_mean_or_gt.code, Diagnostics.Unexpected_token_Did_you_mean_or_rbrace.code];

registerCodeFix({
errorCodes,
getCodeActions: context => {
const { sourceFile, span } = context;
const changeToExpression = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, span.start, /* useHtmlEntity */ false));
const changeToHtmlEntity = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, span.start, /* useHtmlEntity */ true));
return [
createCodeFixActionWithoutFixAll(fixIdExpression, changeToExpression, Diagnostics.Wrap_invalid_character_in_an_expression_container),
createCodeFixAction(fixIdHtmlEntity, changeToHtmlEntity, Diagnostics.Convert_invalid_character_to_its_html_entity_code, fixIdHtmlEntity, Diagnostics.Convert_invalid_character_to_its_html_entity_code),
];
},
fixIds: [fixIdExpression, fixIdHtmlEntity],
});

const htmlEntity = {
">": ">",
"}": "}",
};
function isValidCharacter(character: string): character is keyof typeof htmlEntity {
return hasProperty(htmlEntity, character);
}

function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, start: number, useHtmlEntity: boolean) {
const character = sourceFile.getText()[start];
// sanity check
if (!isValidCharacter(character)) {
return;
}

const replacement = useHtmlEntity
? htmlEntity[character]
: `{'${character}'}`;
changes.replaceRangeWithText(sourceFile, { pos: start, end: start + 1 }, replacement);
}
}
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"codefixes/fixModuleAndTargetOptions.ts",
"codefixes/fixExtendsInterfaceBecomesImplements.ts",
"codefixes/fixForgottenThisPropertyAccess.ts",
"codefixes/fixInvalidJsxCharacters.ts",
"codefixes/fixUnusedIdentifier.ts",
"codefixes/fixUnreachableCode.ts",
"codefixes/fixUnusedLabel.ts",
Expand Down
29 changes: 28 additions & 1 deletion tests/baselines/reference/jsxAndTypeAssertion.errors.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(6,6): error TS17008: JSX element 'any' has no corresponding closing tag.
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`.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(6,17): error TS1005: '}' expected.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(6,31): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(8,6): error TS17008: JSX element 'any' has no corresponding closing tag.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(10,6): error TS17008: JSX element 'foo' has no corresponding closing tag.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(10,24): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(10,32): error TS1005: '}' expected.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(12,23): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(12,24): error TS1382: Unexpected token. Did you mean `{'>'}` or `>`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(12,36): error TS1005: '}' expected.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,17): error TS17008: JSX element 'foo' has no corresponding closing tag.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,23): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,24): error TS1382: Unexpected token. Did you mean `{'>'}` or `>`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,38): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(14,45): error TS1005: '}' expected.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,2): error TS17008: JSX element 'foo' has no corresponding closing tag.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,8): error TS17008: JSX element 'foo' has no corresponding closing tag.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,13): error TS17008: JSX element 'foo' has no corresponding closing tag.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,69): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(18,76): error TS1381: Unexpected token. Did you mean `{'}'}` or `}`?
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: ':' expected.
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' expected.


==== tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx (14 errors) ====
==== tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx (23 errors) ====
declare var createElement: any;

class foo {}
Expand All @@ -27,6 +36,8 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(21,1): error TS1005: '</' ex
!!! 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`.
~
!!! error TS1005: '}' expected.
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

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

x = <foo test={<foo>{}}>hello</foo>;
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
~
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
~
!!! error TS1005: '}' expected.

x = <foo test={<foo>{}}>hello{<foo>{}}</foo>;
~~~
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
~
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
~
!!! error TS1005: '}' expected.

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



Expand Down
5 changes: 4 additions & 1 deletion tests/baselines/reference/jsxEsprimaFbTestSuite.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,1): error TS2695: Left
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,17): error TS1005: '{' expected.
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,23): error TS2304: Cannot find name 'right'.
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,23): error TS2657: JSX expressions must have one parent element.
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,41): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,57): error TS1109: Expression expected.
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,58): error TS1109: Expression expected.


==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (6 errors) ====
==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (7 errors) ====
declare var React: any;
declare var 日本語;
declare var AbC_def;
Expand Down Expand Up @@ -54,6 +55,8 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,58): error TS1109: Expr
!!! error TS2304: Cannot find name 'right'.
~~~~~
!!! error TS2657: JSX expressions must have one parent element.
~
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
~
!!! error TS1109: Expression expected.
~
Expand Down
20 changes: 16 additions & 4 deletions tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ tests/cases/conformance/jsx/19.tsx(1,9): error TS2695: Left side of comma operat
tests/cases/conformance/jsx/19.tsx(1,64): error TS2657: JSX expressions must have one parent element.
tests/cases/conformance/jsx/2.tsx(1,3): error TS1003: Identifier expected.
tests/cases/conformance/jsx/20.tsx(1,10): error TS1005: '}' expected.
tests/cases/conformance/jsx/20.tsx(1,11): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
tests/cases/conformance/jsx/21.tsx(1,20): error TS1003: Identifier expected.
tests/cases/conformance/jsx/22.tsx(1,15): error TS1003: Identifier expected.
tests/cases/conformance/jsx/22.tsx(1,21): error TS1109: Expression expected.
Expand All @@ -55,6 +56,8 @@ tests/cases/conformance/jsx/25.tsx(1,29): error TS1128: Declaration or statement
tests/cases/conformance/jsx/25.tsx(1,32): error TS2304: Cannot find name 'props'.
tests/cases/conformance/jsx/25.tsx(1,38): error TS1109: Expression expected.
tests/cases/conformance/jsx/25.tsx(1,39): error TS1109: Expression expected.
tests/cases/conformance/jsx/26.tsx(1,4): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
tests/cases/conformance/jsx/27.tsx(1,5): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
tests/cases/conformance/jsx/28.tsx(1,2): error TS17008: JSX element 'a' has no corresponding closing tag.
tests/cases/conformance/jsx/28.tsx(1,6): error TS1005: '{' expected.
tests/cases/conformance/jsx/28.tsx(2,1): error TS1005: '</' expected.
Expand All @@ -67,6 +70,7 @@ tests/cases/conformance/jsx/3.tsx(1,2): error TS1109: Expression expected.
tests/cases/conformance/jsx/3.tsx(1,3): error TS2304: Cannot find name 'a'.
tests/cases/conformance/jsx/3.tsx(1,6): error TS1109: Expression expected.
tests/cases/conformance/jsx/3.tsx(1,7): error TS1109: Expression expected.
tests/cases/conformance/jsx/30.tsx(1,4): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
tests/cases/conformance/jsx/31.tsx(1,4): error TS1003: Identifier expected.
tests/cases/conformance/jsx/4.tsx(1,6): error TS1005: '{' expected.
tests/cases/conformance/jsx/5.tsx(1,2): error TS17008: JSX element 'a' has no corresponding closing tag.
Expand Down Expand Up @@ -236,10 +240,12 @@ tests/cases/conformance/jsx/9.tsx(1,16): error TS1109: Expression expected.
!!! error TS2695: Left side of comma operator is unused and has no side effects.
~
!!! error TS2657: JSX expressions must have one parent element.
==== tests/cases/conformance/jsx/20.tsx (1 errors) ====
==== tests/cases/conformance/jsx/20.tsx (2 errors) ====
<a>{"str";}</a>;
~
!!! error TS1005: '}' expected.
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
==== tests/cases/conformance/jsx/21.tsx (1 errors) ====
<span className="a", id="b" />;
~
Expand Down Expand Up @@ -286,11 +292,15 @@ tests/cases/conformance/jsx/9.tsx(1,16): error TS1109: Expression expected.
!!! error TS1109: Expression expected.


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

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

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


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

==== tests/cases/conformance/jsx/31.tsx (1 errors) ====
<a .../*hai*/asdf/>;
Expand Down
46 changes: 46 additions & 0 deletions tests/baselines/reference/jsxParsingError3.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
tests/cases/conformance/jsx/Error1.tsx(1,15): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
tests/cases/conformance/jsx/Error2.tsx(1,15): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
tests/cases/conformance/jsx/Error3.tsx(1,22): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
tests/cases/conformance/jsx/Error4.tsx(1,22): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?
tests/cases/conformance/jsx/Error5.tsx(1,15): error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?
tests/cases/conformance/jsx/Error6.tsx(1,15): error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?


==== tests/cases/conformance/jsx/file.tsx (0 errors) ====
declare module JSX {
interface Element {}
interface IntrinsicElements {
[s: string]: any;
}
}

==== tests/cases/conformance/jsx/Error1.tsx (1 errors) ====
let x1 = <div>}</div>;
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

==== tests/cases/conformance/jsx/Error2.tsx (1 errors) ====
let x2 = <div>></div>;
~
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?

==== tests/cases/conformance/jsx/Error3.tsx (1 errors) ====
let x3 = <div>{"foo"}}</div>;
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

==== tests/cases/conformance/jsx/Error4.tsx (1 errors) ====
let x4 = <div>{"foo"}></div>;
~
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?

==== tests/cases/conformance/jsx/Error5.tsx (1 errors) ====
let x5 = <div>}{"foo"}</div>;
~
!!! error TS1381: Unexpected token. Did you mean `{'}'}` or `&rbrace;`?

==== tests/cases/conformance/jsx/Error6.tsx (1 errors) ====
let x6 = <div>>{"foo"}</div>;
~
!!! error TS1382: Unexpected token. Did you mean `{'>'}` or `&gt;`?

42 changes: 42 additions & 0 deletions tests/baselines/reference/jsxParsingError3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//// [tests/cases/conformance/jsx/jsxParsingError3.tsx] ////

//// [file.tsx]
declare module JSX {
interface Element {}
interface IntrinsicElements {
[s: string]: any;
}
}

//// [Error1.tsx]
let x1 = <div>}</div>;

//// [Error2.tsx]
let x2 = <div>></div>;

//// [Error3.tsx]
let x3 = <div>{"foo"}}</div>;

//// [Error4.tsx]
let x4 = <div>{"foo"}></div>;

//// [Error5.tsx]
let x5 = <div>}{"foo"}</div>;

//// [Error6.tsx]
let x6 = <div>>{"foo"}</div>;


//// [file.jsx]
//// [Error1.jsx]
var x1 = <div>}</div>;
//// [Error2.jsx]
var x2 = <div>></div>;
//// [Error3.jsx]
var x3 = <div>{"foo"}}</div>;
//// [Error4.jsx]
var x4 = <div>{"foo"}></div>;
//// [Error5.jsx]
var x5 = <div>}{"foo"}</div>;
//// [Error6.jsx]
var x6 = <div>>{"foo"}</div>;
Loading

0 comments on commit 348c4dd

Please sign in to comment.