Merge pull request #4228 from RyanCavanaugh/jsxHash

Fix case where # occurs after > in JSX expression
This commit is contained in:
Ryan Cavanaugh 2015-08-11 11:56:19 -07:00
commit 5035559c59
10 changed files with 230 additions and 42 deletions

View file

@ -844,6 +844,10 @@ namespace ts {
return token = scanner.scanJsxIdentifier(); return token = scanner.scanJsxIdentifier();
} }
function scanJsxText(): SyntaxKind {
return token = scanner.scanJsxToken();
}
function speculationHelper<T>(callback: () => T, isLookAhead: boolean): T { 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 // 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). // caller asked us to always reset our state).
@ -913,9 +917,11 @@ namespace ts {
return token > SyntaxKind.LastReservedWord; return token > SyntaxKind.LastReservedWord;
} }
function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage): boolean { function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean {
if (token === kind) { if (token === kind) {
nextToken(); if (shouldAdvance) {
nextToken();
}
return true; return true;
} }
@ -3178,7 +3184,7 @@ namespace ts {
return parseTypeAssertion(); return parseTypeAssertion();
} }
if (lookAhead(nextTokenIsIdentifierOrKeyword)) { if (lookAhead(nextTokenIsIdentifierOrKeyword)) {
return parseJsxElementOrSelfClosingElement(); return parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ true);
} }
// Fall through // Fall through
default: default:
@ -3308,14 +3314,14 @@ namespace ts {
return finishNode(node); return finishNode(node);
} }
function parseJsxElementOrSelfClosingElement(): JsxElement|JsxSelfClosingElement { function parseJsxElementOrSelfClosingElement(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement {
let opening = parseJsxOpeningOrSelfClosingElement(); let opening = parseJsxOpeningOrSelfClosingElement(inExpressionContext);
if (opening.kind === SyntaxKind.JsxOpeningElement) { if (opening.kind === SyntaxKind.JsxOpeningElement) {
let node = <JsxElement>createNode(SyntaxKind.JsxElement, opening.pos); let node = <JsxElement>createNode(SyntaxKind.JsxElement, opening.pos);
node.openingElement = opening; node.openingElement = opening;
node.children = parseJsxChildren(node.openingElement.tagName); node.children = parseJsxChildren(node.openingElement.tagName);
node.closingElement = parseJsxClosingElement(); node.closingElement = parseJsxClosingElement(inExpressionContext);
return finishNode(node); return finishNode(node);
} }
else { else {
@ -3336,9 +3342,9 @@ namespace ts {
case SyntaxKind.JsxText: case SyntaxKind.JsxText:
return parseJsxText(); return parseJsxText();
case SyntaxKind.OpenBraceToken: case SyntaxKind.OpenBraceToken:
return parseJsxExpression(); return parseJsxExpression(/*inExpressionContext*/ false);
case SyntaxKind.LessThanToken: case SyntaxKind.LessThanToken:
return parseJsxElementOrSelfClosingElement(); return parseJsxElementOrSelfClosingElement(/*inExpressionContext*/ false);
} }
Debug.fail("Unknown JSX child kind " + token); Debug.fail("Unknown JSX child kind " + token);
} }
@ -3368,7 +3374,7 @@ namespace ts {
return result; return result;
} }
function parseJsxOpeningOrSelfClosingElement(): JsxOpeningElement|JsxSelfClosingElement { function parseJsxOpeningOrSelfClosingElement(inExpressionContext: boolean): JsxOpeningElement|JsxSelfClosingElement {
let fullStart = scanner.getStartPos(); let fullStart = scanner.getStartPos();
parseExpected(SyntaxKind.LessThanToken); parseExpected(SyntaxKind.LessThanToken);
@ -3378,12 +3384,22 @@ namespace ts {
let attributes = parseList(ParsingContext.JsxAttributes, parseJsxAttribute); let attributes = parseList(ParsingContext.JsxAttributes, parseJsxAttribute);
let node: JsxOpeningLikeElement; let node: JsxOpeningLikeElement;
if (parseOptional(SyntaxKind.GreaterThanToken)) { if (token === 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); node = <JsxOpeningElement>createNode(SyntaxKind.JsxOpeningElement, fullStart);
scanJsxText();
} }
else { else {
parseExpected(SyntaxKind.SlashToken); 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); node = <JsxSelfClosingElement>createNode(SyntaxKind.JsxSelfClosingElement, fullStart);
} }
@ -3406,14 +3422,20 @@ namespace ts {
return elementName; return elementName;
} }
function parseJsxExpression(): JsxExpression { function parseJsxExpression(inExpressionContext: boolean): JsxExpression {
let node = <JsxExpression>createNode(SyntaxKind.JsxExpression); let node = <JsxExpression>createNode(SyntaxKind.JsxExpression);
parseExpected(SyntaxKind.OpenBraceToken); parseExpected(SyntaxKind.OpenBraceToken);
if (token !== SyntaxKind.CloseBraceToken) { if (token !== SyntaxKind.CloseBraceToken) {
node.expression = parseExpression(); node.expression = parseExpression();
} }
parseExpected(SyntaxKind.CloseBraceToken); if (inExpressionContext) {
parseExpected(SyntaxKind.CloseBraceToken);
}
else {
parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*advance*/ false);
scanJsxText();
}
return finishNode(node); return finishNode(node);
} }
@ -3432,7 +3454,7 @@ namespace ts {
node.initializer = parseLiteralNode(); node.initializer = parseLiteralNode();
break; break;
default: default:
node.initializer = parseJsxExpression(); node.initializer = parseJsxExpression(/*inExpressionContext*/ true);
break; break;
} }
} }
@ -3448,11 +3470,17 @@ namespace ts {
return finishNode(node); return finishNode(node);
} }
function parseJsxClosingElement(): JsxClosingElement { function parseJsxClosingElement(inExpressionContext: boolean): JsxClosingElement {
let node = <JsxClosingElement>createNode(SyntaxKind.JsxClosingElement); let node = <JsxClosingElement>createNode(SyntaxKind.JsxClosingElement);
parseExpected(SyntaxKind.LessThanSlashToken); parseExpected(SyntaxKind.LessThanSlashToken);
node.tagName = parseJsxElementName(); node.tagName = parseJsxElementName();
parseExpected(SyntaxKind.GreaterThanToken); if (inExpressionContext) {
parseExpected(SyntaxKind.GreaterThanToken);
}
else {
parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*advance*/ false);
scanJsxText();
}
return finishNode(node); return finishNode(node);
} }

View file

@ -29,21 +29,18 @@ var foo = (function () {
return foo; return foo;
})(); })();
var x; var x;
x = <any> {test}: <any></any> }; x = <any> {test} <any></any> };
x = <any><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 />; x = <foo>x</foo>, x = <foo />;
<foo>{<foo><foo>{/foo/.test(x) ? <foo><foo></foo> : <foo><foo></foo>}</foo>}</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 TS1005: '...' expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(25,7): error TS2304: Cannot find name 'props'. 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,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,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,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(32,6): error TS1005: '{' expected.
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(33,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. 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(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; declare var React: any;
</>; </>;
@ -229,15 +227,11 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002
<div>stuff</div {...props}>; <div>stuff</div {...props}>;
~ ~
!!! error TS1005: '>' expected. !!! error TS1005: '>' expected.
~~~
!!! error TS1109: Expression expected.
<div {...props}>stuff</div {...props}>; <div {...props}>stuff</div {...props}>;
~~~~~ ~~~~~
!!! error TS2304: Cannot find name 'props'. !!! error TS2304: Cannot find name 'props'.
~ ~
!!! error TS1005: '>' expected. !!! error TS1005: '>' expected.
~~~
!!! error TS1109: Expression expected.
<a>></a>; <a>></a>;
<a> ></a>; <a> ></a>;

View file

@ -65,17 +65,17 @@ a['foo'] > ;
<a b=>; <a b=>;
var x = <div>one</div><div>two</div>;; var x = <div>one</div><div>two</div>;;
var x = <div>one</div> /* intervening comment */ /* intervening comment */ <div>two</div>;; var x = <div>one</div> /* intervening comment */ /* intervening comment */ <div>two</div>;;
<a>{"str"};}</a>; <a>{"str"}}</a>;
<span className="a"/>, id="b" />; <span className="a"/> id="b" />;
<div className=/>"app">; <div className=/>>;
<div {...props}/>; <div {...props}/>;
<div>stuff</div> {}...props}>; <div>stuff</div>...props}>;
<div {...props}>stuff</div> {}...props}>; <div {...props}>stuff</div>...props}>;
<a>></a>; <a>></a>;
<a> ></a>; <a> ></a>;
<a b=>; <a b=>;
<a b={ < }>; <a b={ < }>;
<a>}</a>; <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(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 { } } 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 // Shouldn't see any errors down here
var y = { a: 1 }; 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] //// [tsxErrorRecovery1.jsx]
function foo() { 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>;