Better error recovery for adjacent JSX elements in expression positions
Fixes #5286
This commit is contained in:
parent
302db0a9d5
commit
6ccb2a5ef2
|
@ -7510,9 +7510,6 @@ namespace ts {
|
|||
case SyntaxKind.JsxSelfClosingElement:
|
||||
checkJsxSelfClosingElement(<JsxSelfClosingElement>child);
|
||||
break;
|
||||
default:
|
||||
// No checks for JSX Text
|
||||
Debug.assert(child.kind === SyntaxKind.JsxText);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1724,6 +1724,10 @@
|
|||
"category": "Error",
|
||||
"code": 2656
|
||||
},
|
||||
"JSX expressions must have one parent element": {
|
||||
"category": "Error",
|
||||
"code": 2657
|
||||
},
|
||||
"Import declaration '{0}' is using private name '{1}'.": {
|
||||
"category": "Error",
|
||||
"code": 4000
|
||||
|
|
|
@ -3454,19 +3454,43 @@ namespace ts {
|
|||
|
||||
function parseJsxElementOrSelfClosingElement(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement {
|
||||
let opening = parseJsxOpeningOrSelfClosingElement(inExpressionContext);
|
||||
let result: JsxElement | JsxSelfClosingElement;
|
||||
if (opening.kind === SyntaxKind.JsxOpeningElement) {
|
||||
let node = <JsxElement>createNode(SyntaxKind.JsxElement, opening.pos);
|
||||
node.openingElement = opening;
|
||||
|
||||
node.children = parseJsxChildren(node.openingElement.tagName);
|
||||
node.closingElement = parseJsxClosingElement(inExpressionContext);
|
||||
return finishNode(node);
|
||||
result = finishNode(node);
|
||||
}
|
||||
else {
|
||||
Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement);
|
||||
// Nothing else to do for self-closing elements
|
||||
return <JsxSelfClosingElement>opening;
|
||||
result = <JsxSelfClosingElement>opening;
|
||||
}
|
||||
|
||||
// If the user writes the invalid code '<div></div><div></div>' in an expression context (i.e. not wrapped in
|
||||
// an enclosing tag), we'll naively try to parse ^ this as a 'less than' operator and the remainder of the tag
|
||||
// as garbage, which will cause the formatter to badly mangle the JSX. Perform a speculative parse of a JSX
|
||||
// element if we see a < token so that we can wrap it in a synthetic binary expression so the formatter
|
||||
// does less damage and we can report a better error.
|
||||
// Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios
|
||||
// of one sort or another.
|
||||
if (inExpressionContext && token === SyntaxKind.LessThanToken) {
|
||||
let invalidElement = tryParse(() => parseJsxElementOrSelfClosingElement(/*inExpressionContext*/true));
|
||||
if (invalidElement) {
|
||||
parseErrorAtCurrentToken(Diagnostics.JSX_expressions_must_have_one_parent_element);
|
||||
let badNode = <BinaryExpression>createNode(SyntaxKind.BinaryExpression, result.pos);
|
||||
badNode.end = invalidElement.end;
|
||||
badNode.left = result;
|
||||
badNode.right = invalidElement;
|
||||
badNode.operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false, /*diagnosticMessage*/ undefined);
|
||||
badNode.operatorToken.pos = badNode.operatorToken.end = badNode.right.pos;
|
||||
return <JsxElement><Node>badNode;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseJsxText(): JsxText {
|
||||
|
|
|
@ -4,11 +4,10 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,29): error TS1005: '{'
|
|||
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(41,1): error TS1003: Identifier expected.
|
||||
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,6): error TS1109: Expression expected.
|
||||
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,12): error TS1109: Expression expected.
|
||||
tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,12): error TS2657: JSX expressions must have one parent element
|
||||
|
||||
|
||||
==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (8 errors) ====
|
||||
==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (7 errors) ====
|
||||
declare var React: any;
|
||||
declare var 日本語;
|
||||
declare var AbC_def;
|
||||
|
@ -62,10 +61,8 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,12): error TS1109: Expr
|
|||
<a.b></a.b>;
|
||||
~
|
||||
!!! error TS1003: Identifier expected.
|
||||
~~
|
||||
!!! error TS1109: Expression expected.
|
||||
~
|
||||
!!! error TS1109: Expression expected.
|
||||
!!! error TS2657: JSX expressions must have one parent element
|
||||
|
||||
<a.b.c></a.b.c>;
|
||||
|
||||
|
|
|
@ -72,8 +72,8 @@ baz
|
|||
<div>@test content</div>;
|
||||
<div><br />7x invalid-js-identifier</div>;
|
||||
<LeftRight left={<a />} right={<b>monkeys /> gorillas</b> / > }/>
|
||||
< a.b > ;
|
||||
a.b > ;
|
||||
,
|
||||
<a.b></a.b>;
|
||||
<a.b.c></a.b.c>;
|
||||
(<div />) < x;
|
||||
<div {...props}/>;
|
||||
|
|
|
@ -12,17 +12,11 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(6,6): error TS2304: C
|
|||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(6,9): error TS1109: Expression expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(6,10): error TS1109: Expression expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(7,1): error TS1003: Identifier expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(7,2): error TS2304: Cannot find name 'a'.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(7,4): error TS1109: Expression expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(8,4): error TS17002: Expected corresponding JSX closing tag for 'a'.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(9,13): error TS1002: Unterminated string literal.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,1): error TS1003: Identifier expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,2): error TS2304: Cannot find name 'a'.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,3): error TS1005: ';' expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,4): error TS2304: Cannot find name 'b'.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,6): error TS1109: Expression expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,8): error TS2304: Cannot find name 'b'.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,10): error TS1109: Expression expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,6): error TS17002: Expected corresponding JSX closing tag for 'a'.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,10): error TS2657: JSX expressions must have one parent element
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(11,3): error TS1003: Identifier expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(11,5): error TS1003: Identifier expected.
|
||||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(11,11): error TS1005: '>' expected.
|
||||
|
@ -71,7 +65,7 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,4): error TS1003:
|
|||
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002: Expected corresponding JSX closing tag for 'a'.
|
||||
|
||||
|
||||
==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (71 errors) ====
|
||||
==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (65 errors) ====
|
||||
declare var React: any;
|
||||
|
||||
</>;
|
||||
|
@ -107,10 +101,6 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002
|
|||
<a>;
|
||||
~
|
||||
!!! error TS1003: Identifier expected.
|
||||
~
|
||||
!!! error TS2304: Cannot find name 'a'.
|
||||
~
|
||||
!!! error TS1109: Expression expected.
|
||||
<a></b>;
|
||||
~~~~
|
||||
!!! error TS17002: Expected corresponding JSX closing tag for 'a'.
|
||||
|
@ -120,18 +110,10 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002
|
|||
<a:b></b>;
|
||||
~
|
||||
!!! error TS1003: Identifier expected.
|
||||
~
|
||||
!!! error TS2304: Cannot find name 'a'.
|
||||
~
|
||||
!!! error TS1005: ';' expected.
|
||||
~
|
||||
!!! error TS2304: Cannot find name 'b'.
|
||||
~~
|
||||
!!! error TS1109: Expression expected.
|
||||
~
|
||||
!!! error TS2304: Cannot find name 'b'.
|
||||
~~~~
|
||||
!!! error TS17002: Expected corresponding JSX closing tag for 'a'.
|
||||
~
|
||||
!!! error TS1109: Expression expected.
|
||||
!!! error TS2657: JSX expressions must have one parent element
|
||||
<a:b.c></a:b.c>;
|
||||
~
|
||||
!!! error TS1003: Identifier expected.
|
||||
|
|
|
@ -41,12 +41,10 @@ var x = <div>one</div> /* intervening comment */ <div>two</div>;;
|
|||
< ;
|
||||
a / > ;
|
||||
<a b={d / > }/>
|
||||
< a > ;
|
||||
<a></b>;
|
||||
<a foo="bar;/>
|
||||
< a;
|
||||
b > ;
|
||||
b > ;
|
||||
,
|
||||
<a>;
|
||||
<a></b>;
|
||||
<a foo="bar;/>a:b></b>;
|
||||
<a b c></a>;
|
||||
b.c > ;
|
||||
<a.b c></a.b>;
|
||||
|
|
18
tests/baselines/reference/tsxErrorRecovery2.errors.txt
Normal file
18
tests/baselines/reference/tsxErrorRecovery2.errors.txt
Normal file
|
@ -0,0 +1,18 @@
|
|||
tests/cases/conformance/jsx/file1.tsx(6,1): error TS2657: JSX expressions must have one parent element
|
||||
tests/cases/conformance/jsx/file2.tsx(2,1): error TS2657: JSX expressions must have one parent element
|
||||
|
||||
|
||||
==== tests/cases/conformance/jsx/file1.tsx (1 errors) ====
|
||||
|
||||
declare namespace JSX { interface Element { } }
|
||||
|
||||
<div></div>
|
||||
<div></div>
|
||||
|
||||
|
||||
!!! error TS2657: JSX expressions must have one parent element
|
||||
==== tests/cases/conformance/jsx/file2.tsx (1 errors) ====
|
||||
var x = <div></div><div></div>
|
||||
|
||||
|
||||
!!! error TS2657: JSX expressions must have one parent element
|
19
tests/baselines/reference/tsxErrorRecovery2.js
Normal file
19
tests/baselines/reference/tsxErrorRecovery2.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
//// [tests/cases/conformance/jsx/tsxErrorRecovery2.tsx] ////
|
||||
|
||||
//// [file1.tsx]
|
||||
|
||||
declare namespace JSX { interface Element { } }
|
||||
|
||||
<div></div>
|
||||
<div></div>
|
||||
|
||||
//// [file2.tsx]
|
||||
var x = <div></div><div></div>
|
||||
|
||||
|
||||
//// [file1.jsx]
|
||||
<div></div>
|
||||
,
|
||||
<div></div>;
|
||||
//// [file2.jsx]
|
||||
var x = <div></div>, <div></div>;
|
30
tests/baselines/reference/tsxErrorRecovery3.errors.txt
Normal file
30
tests/baselines/reference/tsxErrorRecovery3.errors.txt
Normal file
|
@ -0,0 +1,30 @@
|
|||
tests/cases/conformance/jsx/file1.tsx(4,2): error TS2304: Cannot find name 'React'.
|
||||
tests/cases/conformance/jsx/file1.tsx(5,2): error TS2304: Cannot find name 'React'.
|
||||
tests/cases/conformance/jsx/file1.tsx(6,1): error TS2657: JSX expressions must have one parent element
|
||||
tests/cases/conformance/jsx/file2.tsx(1,10): error TS2304: Cannot find name 'React'.
|
||||
tests/cases/conformance/jsx/file2.tsx(1,21): error TS2304: Cannot find name 'React'.
|
||||
tests/cases/conformance/jsx/file2.tsx(2,1): error TS2657: JSX expressions must have one parent element
|
||||
|
||||
|
||||
==== tests/cases/conformance/jsx/file1.tsx (3 errors) ====
|
||||
|
||||
declare namespace JSX { interface Element { } }
|
||||
|
||||
<div></div>
|
||||
~~~
|
||||
!!! error TS2304: Cannot find name 'React'.
|
||||
<div></div>
|
||||
~~~
|
||||
!!! error TS2304: Cannot find name 'React'.
|
||||
|
||||
|
||||
!!! error TS2657: JSX expressions must have one parent element
|
||||
==== tests/cases/conformance/jsx/file2.tsx (3 errors) ====
|
||||
var x = <div></div><div></div>
|
||||
~~~
|
||||
!!! error TS2304: Cannot find name 'React'.
|
||||
~~~
|
||||
!!! error TS2304: Cannot find name 'React'.
|
||||
|
||||
|
||||
!!! error TS2657: JSX expressions must have one parent element
|
19
tests/baselines/reference/tsxErrorRecovery3.js
Normal file
19
tests/baselines/reference/tsxErrorRecovery3.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
//// [tests/cases/conformance/jsx/tsxErrorRecovery3.tsx] ////
|
||||
|
||||
//// [file1.tsx]
|
||||
|
||||
declare namespace JSX { interface Element { } }
|
||||
|
||||
<div></div>
|
||||
<div></div>
|
||||
|
||||
//// [file2.tsx]
|
||||
var x = <div></div><div></div>
|
||||
|
||||
|
||||
//// [file1.js]
|
||||
React.createElement("div", null)
|
||||
,
|
||||
React.createElement("div", null);
|
||||
//// [file2.js]
|
||||
var x = React.createElement("div", null), React.createElement("div", null);
|
10
tests/cases/conformance/jsx/tsxErrorRecovery2.tsx
Normal file
10
tests/cases/conformance/jsx/tsxErrorRecovery2.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
//@jsx: preserve
|
||||
|
||||
//@filename: file1.tsx
|
||||
declare namespace JSX { interface Element { } }
|
||||
|
||||
<div></div>
|
||||
<div></div>
|
||||
|
||||
//@filename: file2.tsx
|
||||
var x = <div></div><div></div>
|
10
tests/cases/conformance/jsx/tsxErrorRecovery3.tsx
Normal file
10
tests/cases/conformance/jsx/tsxErrorRecovery3.tsx
Normal file
|
@ -0,0 +1,10 @@
|
|||
//@jsx: react
|
||||
|
||||
//@filename: file1.tsx
|
||||
declare namespace JSX { interface Element { } }
|
||||
|
||||
<div></div>
|
||||
<div></div>
|
||||
|
||||
//@filename: file2.tsx
|
||||
var x = <div></div><div></div>
|
Loading…
Reference in a new issue