mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-13 23:48:54 -05:00
Merge pull request #31480 from andrewbranch/bug/25487
Fix invalid JSXExpressions having identifier-ish things in their trivia, improve error messages for comma expressions in JSX
This commit is contained in:
@@ -20033,6 +20033,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)) {
|
||||
@@ -31912,6 +31913,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;
|
||||
|
||||
@@ -5009,5 +5009,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4430,14 +4430,18 @@ 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);
|
||||
}
|
||||
else {
|
||||
parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false);
|
||||
scanJsxText();
|
||||
if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) {
|
||||
scanJsxText();
|
||||
}
|
||||
}
|
||||
|
||||
return finishNode(node);
|
||||
|
||||
@@ -583,6 +583,21 @@ namespace FourSlash {
|
||||
});
|
||||
}
|
||||
|
||||
public verifyErrorExistsAtRange(range: Range, code: number, expectedMessage?: string) {
|
||||
const span = ts.createTextSpanFromRange(range);
|
||||
const hasMatchingError = ts.some(
|
||||
this.getDiagnostics(range.fileName),
|
||||
({ code, messageText, start, length }) =>
|
||||
code === code &&
|
||||
(!expectedMessage || expectedMessage === messageText) &&
|
||||
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;
|
||||
@@ -4009,6 +4024,10 @@ namespace FourSlashInterface {
|
||||
this.state.verifyNoErrors();
|
||||
}
|
||||
|
||||
public errorExistsAtRange(range: FourSlash.Range, code: number, message?: string) {
|
||||
this.state.verifyErrorExistsAtRange(range, code, message);
|
||||
}
|
||||
|
||||
public numberOfErrorsInCurrentFile(expected: number) {
|
||||
this.state.verifyNumberOfErrorsInCurrentFile(expected);
|
||||
}
|
||||
|
||||
@@ -28,18 +28,21 @@ var foo = /** @class */ (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>}</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>
|
||||
:
|
||||
}</></>}</></>}/></></></>;
|
||||
}
|
||||
|
||||
|
||||
</></>}</></>}/></></></>;
|
||||
|
||||
@@ -123,7 +123,7 @@ var x = <div>one</div>, <div>two</div>;
|
||||
var x = <div>one</div> /* intervening comment */, /* intervening comment */ <div>two</div>;
|
||||
;
|
||||
//// [20.jsx]
|
||||
<a>{"str"}}</a>;
|
||||
<a>{"str"};}</a>;
|
||||
//// [21.jsx]
|
||||
<span className="a" id="b"/>;
|
||||
//// [22.jsx]
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
tests/cases/conformance/jsx/file.tsx(11,36): error TS1005: '}' expected.
|
||||
tests/cases/conformance/jsx/file.tsx(11,44): error TS1003: Identifier expected.
|
||||
tests/cases/conformance/jsx/file.tsx(11,46): error TS1161: Unterminated regular expression literal.
|
||||
tests/cases/conformance/jsx/file.tsx(11,30): error TS2695: Left side of comma operator is unused and has no side effects.
|
||||
tests/cases/conformance/jsx/file.tsx(11,30): error TS18007: JSX expressions may not use the comma operator. Did you mean to write an array?
|
||||
|
||||
|
||||
==== tests/cases/conformance/jsx/file.tsx (3 errors) ====
|
||||
==== tests/cases/conformance/jsx/file.tsx (2 errors) ====
|
||||
declare module JSX {
|
||||
interface Element { }
|
||||
interface IntrinsicElements {
|
||||
@@ -15,10 +14,8 @@ tests/cases/conformance/jsx/file.tsx(11,46): error TS1161: Unterminated regular
|
||||
const class1 = "foo";
|
||||
const class2 = "bar";
|
||||
const elem = <div className={class1, class2}/>;
|
||||
~
|
||||
!!! error TS1005: '}' expected.
|
||||
~
|
||||
!!! error TS1003: Identifier expected.
|
||||
|
||||
!!! error TS1161: Unterminated regular expression literal.
|
||||
~~~~~~
|
||||
!!! error TS2695: Left side of comma operator is unused and has no side effects.
|
||||
~~~~~~~~~~~~~~
|
||||
!!! error TS18007: JSX expressions may not use the comma operator. Did you mean to write an array?
|
||||
|
||||
@@ -16,5 +16,4 @@ const elem = <div className={class1, class2}/>;
|
||||
// This should be a parse error
|
||||
var class1 = "foo";
|
||||
var class2 = "bar";
|
||||
var elem = <div className={class1} class2/>;
|
||||
/>;;
|
||||
var elem = <div className={class1, class2}/>;
|
||||
|
||||
@@ -25,5 +25,5 @@ const elem = <div className={class1, class2}/>;
|
||||
>div : Symbol(JSX.IntrinsicElements, Decl(file.tsx, 1, 22))
|
||||
>className : Symbol(className, Decl(file.tsx, 10, 17))
|
||||
>class1 : Symbol(class1, Decl(file.tsx, 8, 5))
|
||||
>class2 : Symbol(class2, Decl(file.tsx, 10, 36))
|
||||
>class2 : Symbol(class2, Decl(file.tsx, 9, 5))
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ const class2 = "bar";
|
||||
|
||||
const elem = <div className={class1, class2}/>;
|
||||
>elem : JSX.Element
|
||||
><div className={class1, class2 : JSX.Element
|
||||
><div className={class1, class2}/> : JSX.Element
|
||||
>div : any
|
||||
>className : string
|
||||
>class1, class2 : "bar"
|
||||
>class1 : "foo"
|
||||
>class2 : true
|
||||
>/>; : RegExp
|
||||
>class2 : "bar"
|
||||
|
||||
|
||||
@@ -1,26 +1,14 @@
|
||||
tests/cases/conformance/jsx/file.tsx(4,11): error TS17008: JSX element 'div' has no corresponding closing tag.
|
||||
tests/cases/conformance/jsx/file.tsx(4,19): error TS1109: Expression expected.
|
||||
tests/cases/conformance/jsx/file.tsx(7,11): error TS2304: Cannot find name 'a'.
|
||||
tests/cases/conformance/jsx/file.tsx(7,12): error TS1005: '}' expected.
|
||||
tests/cases/conformance/jsx/file.tsx(8,1): error TS1005: '</' expected.
|
||||
|
||||
|
||||
==== tests/cases/conformance/jsx/file.tsx (5 errors) ====
|
||||
==== tests/cases/conformance/jsx/file.tsx (1 errors) ====
|
||||
declare namespace JSX { interface Element { } }
|
||||
|
||||
function foo() {
|
||||
var x = <div> { </div>
|
||||
~~~
|
||||
!!! error TS17008: JSX element 'div' has no corresponding closing tag.
|
||||
~~
|
||||
!!! error TS1109: Expression expected.
|
||||
}
|
||||
// Shouldn't see any errors down here
|
||||
var y = { a: 1 };
|
||||
~
|
||||
!!! error TS2304: Cannot find name 'a'.
|
||||
~
|
||||
!!! error TS1005: '}' expected.
|
||||
|
||||
|
||||
!!! error TS1005: '</' expected.
|
||||
|
||||
@@ -10,9 +10,7 @@ var y = { a: 1 };
|
||||
|
||||
//// [file.jsx]
|
||||
function foo() {
|
||||
var x = <div> {}div>
|
||||
}
|
||||
// Shouldn't see any errors down here
|
||||
var y = {a} 1 };
|
||||
</>;
|
||||
var x = <div> {} </div>;
|
||||
}
|
||||
// Shouldn't see any errors down here
|
||||
var y = { a: 1 };
|
||||
|
||||
@@ -11,4 +11,6 @@ function foo() {
|
||||
}
|
||||
// Shouldn't see any errors down here
|
||||
var y = { a: 1 };
|
||||
>y : Symbol(y, Decl(file.tsx, 6, 3))
|
||||
>a : Symbol(a, Decl(file.tsx, 6, 9))
|
||||
|
||||
|
||||
@@ -6,13 +6,15 @@ function foo() {
|
||||
|
||||
var x = <div> { </div>
|
||||
>x : JSX.Element
|
||||
><div> { </div>}// Shouldn't see any errors down herevar y = { a: 1 }; : JSX.Element
|
||||
><div> { </div> : JSX.Element
|
||||
>div : any
|
||||
> : any
|
||||
>div : any
|
||||
}
|
||||
// Shouldn't see any errors down here
|
||||
var y = { a: 1 };
|
||||
>a : any
|
||||
|
||||
> : any
|
||||
>y : { a: number; }
|
||||
>{ a: 1 } : { a: number; }
|
||||
>a : number
|
||||
>1 : 1
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
//
|
||||
// TODO: figure out a better solution to the API exposure problem.
|
||||
|
||||
/// <reference path="../../../src/compiler/diagnosticInformationMap.generated.ts" />
|
||||
|
||||
declare module ts {
|
||||
export type MapKey = string | number;
|
||||
export interface Map<T> {
|
||||
@@ -70,6 +72,21 @@ declare module ts {
|
||||
text: string;
|
||||
}
|
||||
|
||||
enum DiagnosticCategory {
|
||||
Warning,
|
||||
Error,
|
||||
Suggestion,
|
||||
Message
|
||||
}
|
||||
|
||||
interface DiagnosticMessage {
|
||||
key: string;
|
||||
category: DiagnosticCategory;
|
||||
code: number;
|
||||
message: string;
|
||||
reportsUnnecessary?: {};
|
||||
}
|
||||
|
||||
function flatMap<T, U>(array: ReadonlyArray<T>, mapfn: (x: T, i: number) => U | ReadonlyArray<U> | undefined): U[];
|
||||
}
|
||||
|
||||
@@ -238,6 +255,7 @@ declare namespace FourSlashInterface {
|
||||
signatureHelp(...options: VerifySignatureHelpOptions[], ): void;
|
||||
// Checks that there are no compile errors.
|
||||
noErrors(): void;
|
||||
errorExistsAtRange(range: Range, code: number, message?: string): void;
|
||||
numberOfErrorsInCurrentFile(expected: number): void;
|
||||
baselineCurrentFileBreakpointLocations(): void;
|
||||
baselineCurrentFileNameOrDottedNameSpans(): void;
|
||||
|
||||
12
tests/cases/fourslash/jsxExpressionFollowedByIdentifier.ts
Normal file
12
tests/cases/fourslash/jsxExpressionFollowedByIdentifier.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//@Filename: jsxExpressionFollowedByIdentifier.tsx
|
||||
////declare var React: any;
|
||||
////const a = <div>{<div />[|x|]}</div>
|
||||
////const b = <div x={<div />[|x|]} />
|
||||
|
||||
test.ranges().forEach(range => {
|
||||
verify.errorExistsAtRange(range, ts.Diagnostics._0_expected.code, "'}' expected.");
|
||||
// This is just to ensure getting quick info doesn’t crash
|
||||
verify.not.quickInfoExists();
|
||||
});
|
||||
11
tests/cases/fourslash/jsxExpressionWithCommaExpression.ts
Normal file
11
tests/cases/fourslash/jsxExpressionWithCommaExpression.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//@Filename: jsxExpressionWithCommaExpression.tsx
|
||||
//@jsx: react
|
||||
////declare var React: any;
|
||||
////declare var x: string;
|
||||
////const a = <div x={[|x, x|]} />
|
||||
////const b = <div>{[|x, x|]}</div>
|
||||
|
||||
verify.getSyntacticDiagnostics([]);
|
||||
test.ranges().forEach(range => verify.errorExistsAtRange(range, 18006));
|
||||
Reference in New Issue
Block a user