Fix case where # occurs after > in JSX expression

This commit is contained in:
Ryan Cavanaugh 2015-08-07 15:46:36 -07:00
parent 2f9248386e
commit 555297a62b
10 changed files with 237 additions and 42 deletions

View file

@ -844,6 +844,10 @@ namespace ts {
return token = scanner.scanJsxIdentifier();
}
function scanJsxText(): SyntaxKind {
return token = scanner.scanJsxToken();
}
function speculationHelper<T>(callback: () => T, isLookAhead: boolean): T {
// Keep track of the state we'll need to rollback to if lookahead fails (or if the
// caller asked us to always reset our state).
@ -913,9 +917,11 @@ namespace ts {
return token > SyntaxKind.LastReservedWord;
}
function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage): boolean {
function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, advance = true): boolean {
if (token === kind) {
nextToken();
if (advance) {
nextToken();
}
return true;
}
@ -929,6 +935,13 @@ namespace ts {
return false;
}
function parseOptionalWithoutAdvancing(t: SyntaxKind): boolean {
if (token === t) {
return true;
}
return false;
}
function parseOptional(t: SyntaxKind): boolean {
if (token === t) {
nextToken();
@ -3178,7 +3191,7 @@ namespace ts {
return parseTypeAssertion();
}
if (lookAhead(nextTokenIsIdentifierOrKeyword)) {
return parseJsxElementOrSelfClosingElement();
return parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ true);
}
// Fall through
default:
@ -3308,14 +3321,14 @@ namespace ts {
return finishNode(node);
}
function parseJsxElementOrSelfClosingElement(): JsxElement|JsxSelfClosingElement {
let opening = parseJsxOpeningOrSelfClosingElement();
function parseJsxElementOrSelfClosingElement(inExpressionContext: boolean): JsxElement|JsxSelfClosingElement {
let opening = parseJsxOpeningOrSelfClosingElement(inExpressionContext);
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();
node.closingElement = parseJsxClosingElement(inExpressionContext);
return finishNode(node);
}
else {
@ -3336,9 +3349,9 @@ namespace ts {
case SyntaxKind.JsxText:
return parseJsxText();
case SyntaxKind.OpenBraceToken:
return parseJsxExpression();
return parseJsxExpression(/*inExpression*/false);
case SyntaxKind.LessThanToken:
return parseJsxElementOrSelfClosingElement();
return parseJsxElementOrSelfClosingElement(/*inExpression*/false);
}
Debug.fail("Unknown JSX child kind " + token);
}
@ -3368,7 +3381,7 @@ namespace ts {
return result;
}
function parseJsxOpeningOrSelfClosingElement(): JsxOpeningElement|JsxSelfClosingElement {
function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement|JsxSelfClosingElement {
let fullStart = scanner.getStartPos();
parseExpected(SyntaxKind.LessThanToken);
@ -3378,12 +3391,22 @@ namespace ts {
let attributes = parseList(ParsingContext.JsxAttributes, parseJsxAttribute);
let node: JsxOpeningLikeElement;
if (parseOptional(SyntaxKind.GreaterThanToken)) {
if (parseOptionalWithoutAdvancing(SyntaxKind.GreaterThanToken)) {
// Closing tag, so scan the immediately-following text with the JSX scanning instead
// of regular scanning to avoid treating illegal characters (e.g. '#') as immediate
// scanning errors
node = <JsxOpeningElement>createNode(SyntaxKind.JsxOpeningElement, fullStart);
scanJsxText();
}
else {
parseExpected(SyntaxKind.SlashToken);
parseExpected(SyntaxKind.GreaterThanToken);
if (inExpressionContext) {
parseExpected(SyntaxKind.GreaterThanToken);
}
else {
parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*advance*/ false);
scanJsxText();
}
node = <JsxSelfClosingElement>createNode(SyntaxKind.JsxSelfClosingElement, fullStart);
}
@ -3406,14 +3429,20 @@ namespace ts {
return elementName;
}
function parseJsxExpression(): JsxExpression {
function parseJsxExpression(inExpressionContext: boolean): JsxExpression {
let node = <JsxExpression>createNode(SyntaxKind.JsxExpression);
parseExpected(SyntaxKind.OpenBraceToken);
if (token !== SyntaxKind.CloseBraceToken) {
node.expression = parseExpression();
}
parseExpected(SyntaxKind.CloseBraceToken);
if(inExpressionContext) {
parseExpected(SyntaxKind.CloseBraceToken);
}
else {
parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*advance*/ false);
scanJsxText();
}
return finishNode(node);
}
@ -3432,7 +3461,7 @@ namespace ts {
node.initializer = parseLiteralNode();
break;
default:
node.initializer = parseJsxExpression();
node.initializer = parseJsxExpression(/*inExpressionContext*/ true);
break;
}
}
@ -3448,11 +3477,17 @@ namespace ts {
return finishNode(node);
}
function parseJsxClosingElement(): JsxClosingElement {
function parseJsxClosingElement(inExpressionContext: boolean): JsxClosingElement {
let node = <JsxClosingElement>createNode(SyntaxKind.JsxClosingElement);
parseExpected(SyntaxKind.LessThanSlashToken);
node.tagName = parseJsxElementName();
parseExpected(SyntaxKind.GreaterThanToken);
if (inExpressionContext) {
parseExpected(SyntaxKind.GreaterThanToken);
}
else {
parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*advance*/ false);
scanJsxText();
}
return finishNode(node);
}

View file

@ -29,21 +29,18 @@ var foo = (function () {
return foo;
})();
var x;
x = <any> {test}: <any></any> };
x = <any> {test} <any></any> };
x = <any><any></any>;
x = <foo>hello {<foo>} </foo>};
x = <foo>hello {<foo>} </foo>}
x = <foo test={<foo>}>hello</foo>}/>;
x = <foo test={<foo>}>hello</foo>}/>
x = <foo test={<foo>}>hello{<foo>}</foo>};
x = <foo test={<foo>}>hello{<foo>}</foo>}
x = <foo>x</foo>, x = <foo />;
<foo>{<foo><foo>{/foo/.test(x) ? <foo><foo></foo> : <foo><foo></foo>}</foo>}</foo>
:
}
</></>}</></>}/></></></>;
}</></>}</></>}/></></></>;

View file

@ -0,0 +1,26 @@
//// [jsxHash.tsx]
var t02 = <a>{0}#</a>;
var t03 = <a>#{0}</a>;
var t04 = <a>#{0}#</a>;
var t05 = <a>#<i></i></a>;
var t06 = <a>#<i></i></a>;
var t07 = <a>#<i>#</i></a>;
var t08 = <a><i></i>#</a>;
var t09 = <a>#<i></i>#</a>;
var t10 = <a><i/>#</a>;
var t11 = <a>#<i/></a>;
var t12 = <a>#</a>;
//// [jsxHash.jsx]
var t02 = <a>{0}#</a>;
var t03 = <a>#{0}</a>;
var t04 = <a>#{0}#</a>;
var t05 = <a>#<i></i></a>;
var t06 = <a>#<i></i></a>;
var t07 = <a>#<i>#</i></a>;
var t08 = <a><i></i>#</a>;
var t09 = <a>#<i></i>#</a>;
var t10 = <a><i />#</a>;
var t11 = <a>#<i /></a>;
var t12 = <a>#</a>;

View file

@ -0,0 +1,34 @@
=== tests/cases/compiler/jsxHash.tsx ===
var t02 = <a>{0}#</a>;
>t02 : Symbol(t02, Decl(jsxHash.tsx, 0, 3))
var t03 = <a>#{0}</a>;
>t03 : Symbol(t03, Decl(jsxHash.tsx, 1, 3))
var t04 = <a>#{0}#</a>;
>t04 : Symbol(t04, Decl(jsxHash.tsx, 2, 3))
var t05 = <a>#<i></i></a>;
>t05 : Symbol(t05, Decl(jsxHash.tsx, 3, 3))
var t06 = <a>#<i></i></a>;
>t06 : Symbol(t06, Decl(jsxHash.tsx, 4, 3))
var t07 = <a>#<i>#</i></a>;
>t07 : Symbol(t07, Decl(jsxHash.tsx, 5, 3))
var t08 = <a><i></i>#</a>;
>t08 : Symbol(t08, Decl(jsxHash.tsx, 6, 3))
var t09 = <a>#<i></i>#</a>;
>t09 : Symbol(t09, Decl(jsxHash.tsx, 7, 3))
var t10 = <a><i/>#</a>;
>t10 : Symbol(t10, Decl(jsxHash.tsx, 8, 3))
var t11 = <a>#<i/></a>;
>t11 : Symbol(t11, Decl(jsxHash.tsx, 9, 3))
var t12 = <a>#</a>;
>t12 : Symbol(t12, Decl(jsxHash.tsx, 10, 3))

View file

@ -0,0 +1,86 @@
=== tests/cases/compiler/jsxHash.tsx ===
var t02 = <a>{0}#</a>;
>t02 : any
><a>{0}#</a> : any
>a : any
>a : any
var t03 = <a>#{0}</a>;
>t03 : any
><a>#{0}</a> : any
>a : any
>a : any
var t04 = <a>#{0}#</a>;
>t04 : any
><a>#{0}#</a> : any
>a : any
>a : any
var t05 = <a>#<i></i></a>;
>t05 : any
><a>#<i></i></a> : any
>a : any
><i></i> : any
>i : any
>i : any
>a : any
var t06 = <a>#<i></i></a>;
>t06 : any
><a>#<i></i></a> : any
>a : any
><i></i> : any
>i : any
>i : any
>a : any
var t07 = <a>#<i>#</i></a>;
>t07 : any
><a>#<i>#</i></a> : any
>a : any
><i>#</i> : any
>i : any
>i : any
>a : any
var t08 = <a><i></i>#</a>;
>t08 : any
><a><i></i>#</a> : any
>a : any
><i></i> : any
>i : any
>i : any
>a : any
var t09 = <a>#<i></i>#</a>;
>t09 : any
><a>#<i></i>#</a> : any
>a : any
><i></i> : any
>i : any
>i : any
>a : any
var t10 = <a><i/>#</a>;
>t10 : any
><a><i/>#</a> : any
>a : any
><i/> : any
>i : any
>a : any
var t11 = <a>#<i/></a>;
>t11 : any
><a>#<i/></a> : any
>a : any
><i/> : any
>i : any
>a : any
var t12 = <a>#</a>;
>t12 : any
><a>#</a> : any
>a : any
>a : any

View file

@ -62,10 +62,8 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(24,15): error TS1003:
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(25,7): error TS1005: '...' expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(25,7): error TS2304: Cannot find name 'props'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(27,17): error TS1005: '>' expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(27,18): error TS1109: Expression expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(28,10): error TS2304: Cannot find name 'props'.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(28,28): error TS1005: '>' expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(28,29): error TS1109: Expression expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(32,6): error TS1005: '{' expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(33,6): error TS1005: '{' expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(33,7): error TS1109: Expression expected.
@ -73,7 +71,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 (73 errors) ====
==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (71 errors) ====
declare var React: any;
</>;
@ -229,15 +227,11 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002
<div>stuff</div {...props}>;
~
!!! error TS1005: '>' expected.
~~~
!!! error TS1109: Expression expected.
<div {...props}>stuff</div {...props}>;
~~~~~
!!! error TS2304: Cannot find name 'props'.
~
!!! error TS1005: '>' expected.
~~~
!!! error TS1109: Expression expected.
<a>></a>;
<a> ></a>;

View file

@ -65,17 +65,17 @@ a['foo'] > ;
<a b=>;
var x = <div>one</div><div>two</div>;;
var x = <div>one</div> /* intervening comment */ /* intervening comment */ <div>two</div>;;
<a>{"str"};}</a>;
<span className="a"/>, id="b" />;
<div className=/>"app">;
<a>{"str"}}</a>;
<span className="a"/> id="b" />;
<div className=/>>;
<div {...props}/>;
<div>stuff</div> {}...props}>;
<div {...props}>stuff</div> {}...props}>;
<div>stuff</div>...props}>;
<div {...props}>stuff</div>...props}>;
<a>></a>;
<a> ></a>;
<a b=>;
<a b={ < }>;
<a>}</a>;
<a /> .../*hai*/asdf/>;</></></></>;
<a /> /*hai*//*hai*/asdf/>;</></></></>;

View file

@ -1,7 +1,10 @@
tests/cases/conformance/jsx/tsxErrorRecovery1.tsx(5,19): error TS1109: Expression expected.
tests/cases/conformance/jsx/tsxErrorRecovery1.tsx(8,11): error TS2304: Cannot find name 'a'.
tests/cases/conformance/jsx/tsxErrorRecovery1.tsx(8,12): error TS1005: '}' expected.
tests/cases/conformance/jsx/tsxErrorRecovery1.tsx(9,1): error TS17002: Expected corresponding JSX closing tag for 'div'.
==== tests/cases/conformance/jsx/tsxErrorRecovery1.tsx (1 errors) ====
==== tests/cases/conformance/jsx/tsxErrorRecovery1.tsx (4 errors) ====
declare namespace JSX { interface Element { } }
@ -12,4 +15,10 @@ tests/cases/conformance/jsx/tsxErrorRecovery1.tsx(5,19): error TS1109: Expressio
}
// Shouldn't see any errors down here
var y = { a: 1 };
~
!!! error TS2304: Cannot find name 'a'.
~
!!! error TS1005: '}' expected.
!!! error TS17002: Expected corresponding JSX closing tag for 'div'.

View file

@ -11,7 +11,9 @@ var y = { a: 1 };
//// [tsxErrorRecovery1.jsx]
function foo() {
var x = <div> {} </div>;
var x = <div> {}div>
}
// Shouldn't see any errors down here
var y = {a} 1 };
</>;
}
// Shouldn't see any errors down here
var y = { a: 1 };

View file

@ -0,0 +1,12 @@
//@jsx: preserve
var t02 = <a>{0}#</a>;
var t03 = <a>#{0}</a>;
var t04 = <a>#{0}#</a>;
var t05 = <a>#<i></i></a>;
var t06 = <a>#<i></i></a>;
var t07 = <a>#<i>#</i></a>;
var t08 = <a><i></i>#</a>;
var t09 = <a>#<i></i>#</a>;
var t10 = <a><i/>#</a>;
var t11 = <a>#<i/></a>;
var t12 = <a>#</a>;