From 555297a62b6ba2026cb08e838a3e78b2221c772f Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 7 Aug 2015 15:46:36 -0700 Subject: [PATCH] Fix case where # occurs after > in JSX expression --- src/compiler/parser.ts | 67 +++++++++++---- .../reference/jsxAndTypeAssertion.js | 13 ++- tests/baselines/reference/jsxHash.js | 26 ++++++ tests/baselines/reference/jsxHash.symbols | 34 ++++++++ tests/baselines/reference/jsxHash.types | 86 +++++++++++++++++++ .../jsxInvalidEsprimaTestSuite.errors.txt | 8 +- .../reference/jsxInvalidEsprimaTestSuite.js | 12 +-- .../reference/tsxErrorRecovery1.errors.txt | 13 ++- .../baselines/reference/tsxErrorRecovery1.js | 8 +- tests/cases/compiler/jsxHash.tsx | 12 +++ 10 files changed, 237 insertions(+), 42 deletions(-) create mode 100644 tests/baselines/reference/jsxHash.js create mode 100644 tests/baselines/reference/jsxHash.symbols create mode 100644 tests/baselines/reference/jsxHash.types create mode 100644 tests/cases/compiler/jsxHash.tsx diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 10ebe86368..ffd081f09d 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -844,6 +844,10 @@ namespace ts { return token = scanner.scanJsxIdentifier(); } + function scanJsxText(): SyntaxKind { + return token = scanner.scanJsxToken(); + } + function speculationHelper(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 = 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 = 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 = createNode(SyntaxKind.JsxSelfClosingElement, fullStart); } @@ -3406,14 +3429,20 @@ namespace ts { return elementName; } - function parseJsxExpression(): JsxExpression { + function parseJsxExpression(inExpressionContext: boolean): JsxExpression { let node = 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 = 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); } diff --git a/tests/baselines/reference/jsxAndTypeAssertion.js b/tests/baselines/reference/jsxAndTypeAssertion.js index 0b402b43de..0a493006dc 100644 --- a/tests/baselines/reference/jsxAndTypeAssertion.js +++ b/tests/baselines/reference/jsxAndTypeAssertion.js @@ -29,21 +29,18 @@ var foo = (function () { return foo; })(); var x; -x = {test}: }; +x = {test} }; x = ; -x = hello {} }; +x = hello {} } -x = }>hello}/>; +x = }>hello}/> -x = }>hello{}}; +x = }>hello{}} x = x, x = ; {{/foo/.test(x) ? : }} : -} - - -}}/>; +}}}/>; diff --git a/tests/baselines/reference/jsxHash.js b/tests/baselines/reference/jsxHash.js new file mode 100644 index 0000000000..04409a56ee --- /dev/null +++ b/tests/baselines/reference/jsxHash.js @@ -0,0 +1,26 @@ +//// [jsxHash.tsx] +var t02 = {0}#; +var t03 = #{0}; +var t04 = #{0}#; +var t05 = #; +var t06 = #; +var t07 = ##; +var t08 = #; +var t09 = ##; +var t10 = #; +var t11 = #; +var t12 = #; + + +//// [jsxHash.jsx] +var t02 = {0}#; +var t03 = #{0}; +var t04 = #{0}#; +var t05 = #; +var t06 = #; +var t07 = ##; +var t08 = #; +var t09 = ##; +var t10 = #; +var t11 = #; +var t12 = #; diff --git a/tests/baselines/reference/jsxHash.symbols b/tests/baselines/reference/jsxHash.symbols new file mode 100644 index 0000000000..8a6ad0849f --- /dev/null +++ b/tests/baselines/reference/jsxHash.symbols @@ -0,0 +1,34 @@ +=== tests/cases/compiler/jsxHash.tsx === +var t02 = {0}#; +>t02 : Symbol(t02, Decl(jsxHash.tsx, 0, 3)) + +var t03 = #{0}; +>t03 : Symbol(t03, Decl(jsxHash.tsx, 1, 3)) + +var t04 = #{0}#; +>t04 : Symbol(t04, Decl(jsxHash.tsx, 2, 3)) + +var t05 = #; +>t05 : Symbol(t05, Decl(jsxHash.tsx, 3, 3)) + +var t06 = #; +>t06 : Symbol(t06, Decl(jsxHash.tsx, 4, 3)) + +var t07 = ##; +>t07 : Symbol(t07, Decl(jsxHash.tsx, 5, 3)) + +var t08 = #; +>t08 : Symbol(t08, Decl(jsxHash.tsx, 6, 3)) + +var t09 = ##; +>t09 : Symbol(t09, Decl(jsxHash.tsx, 7, 3)) + +var t10 = #; +>t10 : Symbol(t10, Decl(jsxHash.tsx, 8, 3)) + +var t11 = #; +>t11 : Symbol(t11, Decl(jsxHash.tsx, 9, 3)) + +var t12 = #; +>t12 : Symbol(t12, Decl(jsxHash.tsx, 10, 3)) + diff --git a/tests/baselines/reference/jsxHash.types b/tests/baselines/reference/jsxHash.types new file mode 100644 index 0000000000..e614ebd3e0 --- /dev/null +++ b/tests/baselines/reference/jsxHash.types @@ -0,0 +1,86 @@ +=== tests/cases/compiler/jsxHash.tsx === +var t02 = {0}#; +>t02 : any +>{0}# : any +>a : any +>a : any + +var t03 = #{0}; +>t03 : any +>#{0} : any +>a : any +>a : any + +var t04 = #{0}#; +>t04 : any +>#{0}# : any +>a : any +>a : any + +var t05 = #; +>t05 : any +># : any +>a : any +> : any +>i : any +>i : any +>a : any + +var t06 = #; +>t06 : any +># : any +>a : any +> : any +>i : any +>i : any +>a : any + +var t07 = ##; +>t07 : any +>## : any +>a : any +># : any +>i : any +>i : any +>a : any + +var t08 = #; +>t08 : any +># : any +>a : any +> : any +>i : any +>i : any +>a : any + +var t09 = ##; +>t09 : any +>## : any +>a : any +> : any +>i : any +>i : any +>a : any + +var t10 = #; +>t10 : any +># : any +>a : any +> : any +>i : any +>a : any + +var t11 = #; +>t11 : any +># : any +>a : any +> : any +>i : any +>a : any + +var t12 = #; +>t12 : any +># : any +>a : any +>a : any + diff --git a/tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt b/tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt index 99ae5056a4..02bc56e823 100644 --- a/tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt +++ b/tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt @@ -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
stuff
; ~ !!! error TS1005: '>' expected. - ~~~ -!!! error TS1109: Expression expected.
stuff
; ~~~~~ !!! error TS2304: Cannot find name 'props'. ~ !!! error TS1005: '>' expected. - ~~~ -!!! error TS1109: Expression expected. >; >; diff --git a/tests/baselines/reference/jsxInvalidEsprimaTestSuite.js b/tests/baselines/reference/jsxInvalidEsprimaTestSuite.js index 13808cc29e..daf344b08d 100644 --- a/tests/baselines/reference/jsxInvalidEsprimaTestSuite.js +++ b/tests/baselines/reference/jsxInvalidEsprimaTestSuite.js @@ -65,17 +65,17 @@ a['foo'] > ; ; var x =
one
two
;; var x =
one
/* intervening comment */ /* intervening comment */
two
;; -
{"str"};}; -, id="b" />; -
"app">; +{"str"}}; + id="b" />; +
>;
; -
stuff
{}...props}>; -
stuff
{}...props}>; +
stuff
...props}>; +
stuff
...props}>; >; >; ; ; }; - .../*hai*/asdf/>;; + /*hai*//*hai*/asdf/>;; diff --git a/tests/baselines/reference/tsxErrorRecovery1.errors.txt b/tests/baselines/reference/tsxErrorRecovery1.errors.txt index dc4f4d51af..7aea986526 100644 --- a/tests/baselines/reference/tsxErrorRecovery1.errors.txt +++ b/tests/baselines/reference/tsxErrorRecovery1.errors.txt @@ -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 }; - \ No newline at end of file + ~ +!!! error TS2304: Cannot find name 'a'. + ~ +!!! error TS1005: '}' expected. + + +!!! error TS17002: Expected corresponding JSX closing tag for 'div'. \ No newline at end of file diff --git a/tests/baselines/reference/tsxErrorRecovery1.js b/tests/baselines/reference/tsxErrorRecovery1.js index 8d20951f6e..62db6b0f01 100644 --- a/tests/baselines/reference/tsxErrorRecovery1.js +++ b/tests/baselines/reference/tsxErrorRecovery1.js @@ -11,7 +11,9 @@ var y = { a: 1 }; //// [tsxErrorRecovery1.jsx] function foo() { - var x =
{}
; + var x =