diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 18c1f42f83..d0efd49081 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19850,6 +19850,7 @@ namespace ts { } function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) { + checkGrammarJsxExpression(node); if (node.expression) { const type = checkExpression(node.expression, checkMode); if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) { @@ -31658,6 +31659,12 @@ namespace ts { } } + function checkGrammarJsxExpression(node: JsxExpression) { + if (node.expression && isCommaSequence(node.expression)) { + return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array); + } + } + function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean { if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) { return true; diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 87101781ea..97e80f69b1 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4986,5 +4986,9 @@ "Classes may not have a field named 'constructor'.": { "category": "Error", "code": 18006 + }, + "JSX expressions may not use the comma operator. Did you mean to write an array?": { + "category": "Error", + "code": 18007 } } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index c38e4f67fe..f3b0c2c06e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -4430,7 +4430,10 @@ namespace ts { if (token() !== SyntaxKind.CloseBraceToken) { node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); - node.expression = parseAssignmentExpressionOrHigher(); + // Only an AssignmentExpression is valid here per the JSX spec, + // but we can unambiguously parse a comma sequence and provide + // a better error message in grammar checking. + node.expression = parseExpression(); } if (inExpressionContext) { parseExpected(SyntaxKind.CloseBraceToken); diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index df7f0051f3..a2e2c0f29a 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -582,6 +582,20 @@ namespace FourSlash { }); } + public verifyErrorExistsAtRange(range: Range, code: number) { + const span = ts.createTextSpanFromRange(range); + const hasMatchingError = ts.some( + this.getDiagnostics(range.fileName), + ({ code, start, length }) => + code === code && + ts.isNumber(start) && ts.isNumber(length) && + ts.textSpansEqual(span, { start, length })); + + if (!hasMatchingError) { + this.raiseError(`No error with code ${code} found at provided range.`); + } + } + public verifyNumberOfErrorsInCurrentFile(expected: number) { const errors = this.getDiagnostics(this.activeFile.fileName); const actual = errors.length; @@ -3968,6 +3982,10 @@ namespace FourSlashInterface { this.state.verifyNoErrors(); } + public errorExistsAtRange(range: FourSlash.Range, code: number) { + this.state.verifyErrorExistsAtRange(range, code); + } + public numberOfErrorsInCurrentFile(expected: number) { this.state.verifyNumberOfErrorsInCurrentFile(expected); } diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 4252296d5d..427a3558b3 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -238,6 +238,7 @@ declare namespace FourSlashInterface { signatureHelp(...options: VerifySignatureHelpOptions[], ): void; // Checks that there are no compile errors. noErrors(): void; + errorExistsAtRange(range: Range, code: number): void; numberOfErrorsInCurrentFile(expected: number): void; baselineCurrentFileBreakpointLocations(): void; baselineCurrentFileNameOrDottedNameSpans(): void; diff --git a/tests/cases/fourslash/jsxExpressionFollowedByIdentifier.ts b/tests/cases/fourslash/jsxExpressionFollowedByIdentifier.ts index 3231ed9cf6..d040bacd54 100644 --- a/tests/cases/fourslash/jsxExpressionFollowedByIdentifier.ts +++ b/tests/cases/fourslash/jsxExpressionFollowedByIdentifier.ts @@ -4,6 +4,7 @@ ////declare var React: any; ////declare var x: string; ////const a =
{
[|x|]}
+////const b =
[|x|]} /> const range = test.ranges()[0]; verify.getSyntacticDiagnostics([{ diff --git a/tests/cases/fourslash/jsxExpressionWithCommaExpression.ts b/tests/cases/fourslash/jsxExpressionWithCommaExpression.ts new file mode 100644 index 0000000000..829a217236 --- /dev/null +++ b/tests/cases/fourslash/jsxExpressionWithCommaExpression.ts @@ -0,0 +1,11 @@ +/// + +//@Filename: jsxExpressionWithCommaExpression.tsx +//@jsx: react +////declare var React: any; +////declare var x: string; +////const a =
+////const b =
{[|x, x|]}
+ +verify.getSyntacticDiagnostics([]); +test.ranges().forEach(range => verify.errorExistsAtRange(range, 18006));