Better error recovery for adjacent JSX elements in expression positions

Fixes #5286
This commit is contained in:
Ryan Cavanaugh 2015-10-16 13:47:57 -07:00
parent 302db0a9d5
commit 6ccb2a5ef2
13 changed files with 151 additions and 43 deletions

View file

@ -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);
}
}

View file

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

View file

@ -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 {

View file

@ -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>;

View file

@ -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}/>;

View file

@ -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.

View file

@ -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>;

View 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

View 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>;

View 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

View 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);

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

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