Skip to content

Commit aed73bd

Browse files
committed
Merge pull request #6291 from RyanCavanaugh/fix6280
Issue correct errors for missing JSX closing tags
2 parents 1380416 + 2c4856a commit aed73bd

File tree

9 files changed

+217
-34
lines changed

9 files changed

+217
-34
lines changed

src/compiler/checker.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7954,31 +7954,12 @@ namespace ts {
79547954
return jsxElementType || anyType;
79557955
}
79567956

7957-
function tagNamesAreEquivalent(lhs: EntityName, rhs: EntityName): boolean {
7958-
if (lhs.kind !== rhs.kind) {
7959-
return false;
7960-
}
7961-
7962-
if (lhs.kind === SyntaxKind.Identifier) {
7963-
return (<Identifier>lhs).text === (<Identifier>rhs).text;
7964-
}
7965-
7966-
return (<QualifiedName>lhs).right.text === (<QualifiedName>rhs).right.text &&
7967-
tagNamesAreEquivalent((<QualifiedName>lhs).left, (<QualifiedName>rhs).left);
7968-
}
7969-
79707957
function checkJsxElement(node: JsxElement) {
79717958
// Check attributes
79727959
checkJsxOpeningLikeElement(node.openingElement);
79737960

7974-
// Check that the closing tag matches
7975-
if (!tagNamesAreEquivalent(node.openingElement.tagName, node.closingElement.tagName)) {
7976-
error(node.closingElement, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNode(node.openingElement.tagName));
7977-
}
7978-
else {
7979-
// Perform resolution on the closing tag so that rename/go to definition/etc work
7980-
getJsxElementTagSymbol(node.closingElement);
7981-
}
7961+
// Perform resolution on the closing tag so that rename/go to definition/etc work
7962+
getJsxElementTagSymbol(node.closingElement);
79827963

79837964
// Check children
79847965
for (const child of node.children) {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,5 +2586,9 @@
25862586
"A type assertion expression is not allowed in the left-hand side of an exponentiation expression. Consider enclosing the expression in parentheses.": {
25872587
"category": "Error",
25882588
"code": 17007
2589+
},
2590+
"JSX element '{0}' has no corresponding closing tag.": {
2591+
"category": "Error",
2592+
"code": 17008
25892593
}
25902594
}

src/compiler/parser.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3497,6 +3497,20 @@ namespace ts {
34973497
return finishNode(node);
34983498
}
34993499

3500+
function tagNamesAreEquivalent(lhs: EntityName, rhs: EntityName): boolean {
3501+
if (lhs.kind !== rhs.kind) {
3502+
return false;
3503+
}
3504+
3505+
if (lhs.kind === SyntaxKind.Identifier) {
3506+
return (<Identifier>lhs).text === (<Identifier>rhs).text;
3507+
}
3508+
3509+
return (<QualifiedName>lhs).right.text === (<QualifiedName>rhs).right.text &&
3510+
tagNamesAreEquivalent((<QualifiedName>lhs).left, (<QualifiedName>rhs).left);
3511+
}
3512+
3513+
35003514
function parseJsxElementOrSelfClosingElement(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement {
35013515
const opening = parseJsxOpeningOrSelfClosingElement(inExpressionContext);
35023516
let result: JsxElement | JsxSelfClosingElement;
@@ -3506,6 +3520,11 @@ namespace ts {
35063520

35073521
node.children = parseJsxChildren(node.openingElement.tagName);
35083522
node.closingElement = parseJsxClosingElement(inExpressionContext);
3523+
3524+
if (!tagNamesAreEquivalent(node.openingElement.tagName, node.closingElement.tagName)) {
3525+
parseErrorAtPosition(node.closingElement.pos, node.closingElement.end - node.closingElement.pos, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, node.openingElement.tagName));
3526+
}
3527+
35093528
result = finishNode(node);
35103529
}
35113530
else {
@@ -3565,10 +3584,13 @@ namespace ts {
35653584
while (true) {
35663585
token = scanner.reScanJsxToken();
35673586
if (token === SyntaxKind.LessThanSlashToken) {
3587+
// Closing tag
35683588
break;
35693589
}
35703590
else if (token === SyntaxKind.EndOfFileToken) {
3571-
parseErrorAtCurrentToken(Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, openingTagName));
3591+
// If we hit EOF, issue the error at the tag that lacks the closing element
3592+
// rather than at the end of the file (which is useless)
3593+
parseErrorAtPosition(openingTagName.pos, openingTagName.end - openingTagName.pos, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTagName));
35723594
break;
35733595
}
35743596
result.push(parseJsxChild());
Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
1+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(7,6): error TS17008: JSX element 'any' has no corresponding closing tag.
12
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(7,13): error TS2304: Cannot find name 'test'.
23
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(7,17): error TS1005: '}' expected.
4+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(9,6): error TS17008: JSX element 'any' has no corresponding closing tag.
5+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(11,6): error TS17008: JSX element 'foo' has no corresponding closing tag.
36
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(11,32): error TS1005: '}' expected.
47
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(13,36): error TS1005: '}' expected.
8+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(15,17): error TS17008: JSX element 'foo' has no corresponding closing tag.
59
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(15,45): error TS1005: '}' expected.
10+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(19,2): error TS17008: JSX element 'foo' has no corresponding closing tag.
11+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(19,8): error TS17008: JSX element 'foo' has no corresponding closing tag.
12+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(19,13): error TS17008: JSX element 'foo' has no corresponding closing tag.
613
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(22,1): error TS1005: ':' expected.
7-
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(22,1): error TS17002: Expected corresponding JSX closing tag for 'any'.
8-
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(22,1): error TS17002: Expected corresponding JSX closing tag for 'foo'.
14+
tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(22,1): error TS1005: '</' expected.
915

1016

11-
==== tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx (8 errors) ====
17+
==== tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx (14 errors) ====
1218

1319
declare var createElement: any;
1420

1521
class foo {}
1622

1723
var x: any;
1824
x = <any> { test: <any></any> };
25+
~~~
26+
!!! error TS17008: JSX element 'any' has no corresponding closing tag.
1927
~~~~
2028
!!! error TS2304: Cannot find name 'test'.
2129
~
2230
!!! error TS1005: '}' expected.
2331

2432
x = <any><any></any>;
33+
~~~
34+
!!! error TS17008: JSX element 'any' has no corresponding closing tag.
2535

2636
x = <foo>hello {<foo>{}} </foo>;
37+
~~~
38+
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
2739
~
2840
!!! error TS1005: '}' expected.
2941

@@ -32,18 +44,24 @@ tests/cases/conformance/jsx/jsxAndTypeAssertion.tsx(22,1): error TS17002: Expect
3244
!!! error TS1005: '}' expected.
3345

3446
x = <foo test={<foo>{}}>hello{<foo>{}}</foo>;
47+
~~~
48+
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
3549
~
3650
!!! error TS1005: '}' expected.
3751

3852
x = <foo>x</foo>, x = <foo/>;
3953

4054
<foo>{<foo><foo>{/foo/.test(x) ? <foo><foo></foo> : <foo><foo></foo>}</foo>}</foo>
55+
~~~
56+
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
57+
~~~
58+
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
59+
~~~
60+
!!! error TS17008: JSX element 'foo' has no corresponding closing tag.
4161

4262

4363

4464

4565
!!! error TS1005: ':' expected.
4666

47-
!!! error TS17002: Expected corresponding JSX closing tag for 'any'.
48-
49-
!!! error TS17002: Expected corresponding JSX closing tag for 'foo'.
67+
!!! error TS1005: '</' expected.

tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(17,3): error TS1003:
5050
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(17,11): error TS1109: Expression expected.
5151
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(17,13): error TS2304: Cannot find name 'a'.
5252
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(17,22): error TS1109: Expression expected.
53+
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(18,2): error TS17008: JSX element 'a' has no corresponding closing tag.
54+
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(19,2): error TS17008: JSX element 'a' has no corresponding closing tag.
5355
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(22,10): error TS1005: '}' expected.
5456
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(23,20): error TS1003: Identifier expected.
5557
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(24,15): error TS1003: Identifier expected.
@@ -58,14 +60,16 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(25,7): error TS2304:
5860
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(27,17): error TS1005: '>' expected.
5961
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(28,10): error TS2304: Cannot find name 'props'.
6062
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(28,28): error TS1005: '>' expected.
63+
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(32,2): error TS17008: JSX element 'a' has no corresponding closing tag.
6164
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(32,6): error TS1005: '{' expected.
65+
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(33,2): error TS17008: JSX element 'a' has no corresponding closing tag.
6266
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(33,6): error TS1005: '{' expected.
6367
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(33,7): error TS1109: Expression expected.
6468
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,4): error TS1003: Identifier expected.
65-
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002: Expected corresponding JSX closing tag for 'a'.
69+
tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS1005: '</' expected.
6670

6771

68-
==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (65 errors) ====
72+
==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (69 errors) ====
6973
declare var React: any;
7074

7175
</>;
@@ -188,7 +192,11 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002
188192
~
189193
!!! error TS1109: Expression expected.
190194
<a><a />;
195+
~
196+
!!! error TS17008: JSX element 'a' has no corresponding closing tag.
191197
<a b={}>;
198+
~
199+
!!! error TS17008: JSX element 'a' has no corresponding closing tag.
192200
var x = <div>one</div><div>two</div>;;
193201
var x = <div>one</div> /* intervening comment */ <div>two</div>;;
194202
<a>{"str";}</a>;
@@ -218,9 +226,13 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002
218226
<a>></a>;
219227
<a> ></a>;
220228
<a b=}>;
229+
~
230+
!!! error TS17008: JSX element 'a' has no corresponding closing tag.
221231
~
222232
!!! error TS1005: '{' expected.
223233
<a b=<}>;
234+
~
235+
!!! error TS17008: JSX element 'a' has no corresponding closing tag.
224236
~
225237
!!! error TS1005: '{' expected.
226238
~
@@ -230,4 +242,4 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002
230242
~~~
231243
!!! error TS1003: Identifier expected.
232244

233-
!!! error TS17002: Expected corresponding JSX closing tag for 'a'.
245+
!!! error TS1005: '</' expected.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
tests/cases/conformance/jsx/Error1.tsx(2,11): error TS17008: JSX element 'div' has no corresponding closing tag.
2+
tests/cases/conformance/jsx/Error1.tsx(2,21): error TS17002: Expected corresponding JSX closing tag for 'span'.
3+
tests/cases/conformance/jsx/Error1.tsx(3,1): error TS1005: '</' expected.
4+
tests/cases/conformance/jsx/Error2.tsx(1,15): error TS17002: Expected corresponding JSX closing tag for 'div'.
5+
tests/cases/conformance/jsx/Error3.tsx(1,11): error TS17008: JSX element 'div' has no corresponding closing tag.
6+
tests/cases/conformance/jsx/Error3.tsx(3,1): error TS1005: '</' expected.
7+
tests/cases/conformance/jsx/Error4.tsx(1,11): error TS17008: JSX element 'div' has no corresponding closing tag.
8+
tests/cases/conformance/jsx/Error4.tsx(1,20): error TS17002: Expected corresponding JSX closing tag for 'div'.
9+
tests/cases/conformance/jsx/Error4.tsx(2,1): error TS1005: '</' expected.
10+
tests/cases/conformance/jsx/Error5.tsx(1,11): error TS17008: JSX element 'div' has no corresponding closing tag.
11+
tests/cases/conformance/jsx/Error5.tsx(1,16): error TS17008: JSX element 'span' has no corresponding closing tag.
12+
tests/cases/conformance/jsx/Error5.tsx(3,1): error TS1005: '</' expected.
13+
14+
15+
==== tests/cases/conformance/jsx/file.tsx (0 errors) ====
16+
17+
declare module JSX {
18+
interface Element { }
19+
interface IntrinsicElements {
20+
[s: string]: any;
21+
}
22+
}
23+
24+
==== tests/cases/conformance/jsx/Error1.tsx (3 errors) ====
25+
// Issue error about missing span closing tag, not missing div closing tag
26+
let x1 = <div><span></div>;
27+
~~~
28+
!!! error TS17008: JSX element 'div' has no corresponding closing tag.
29+
~~~~~~
30+
!!! error TS17002: Expected corresponding JSX closing tag for 'span'.
31+
32+
33+
!!! error TS1005: '</' expected.
34+
==== tests/cases/conformance/jsx/Error2.tsx (1 errors) ====
35+
let x2 = <div></span>;
36+
~~~~~~~
37+
!!! error TS17002: Expected corresponding JSX closing tag for 'div'.
38+
39+
40+
==== tests/cases/conformance/jsx/Error3.tsx (2 errors) ====
41+
let x3 = <div>;
42+
~~~
43+
!!! error TS17008: JSX element 'div' has no corresponding closing tag.
44+
45+
46+
47+
!!! error TS1005: '</' expected.
48+
==== tests/cases/conformance/jsx/Error4.tsx (3 errors) ====
49+
let x4 = <div><div></span>;
50+
~~~
51+
!!! error TS17008: JSX element 'div' has no corresponding closing tag.
52+
~~~~~~~
53+
!!! error TS17002: Expected corresponding JSX closing tag for 'div'.
54+
55+
56+
!!! error TS1005: '</' expected.
57+
==== tests/cases/conformance/jsx/Error5.tsx (3 errors) ====
58+
let x5 = <div><span>
59+
~~~
60+
!!! error TS17008: JSX element 'div' has no corresponding closing tag.
61+
~~~~
62+
!!! error TS17008: JSX element 'span' has no corresponding closing tag.
63+
64+
65+
66+
!!! error TS1005: '</' expected.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//// [tests/cases/conformance/jsx/jsxParsingError2.tsx] ////
2+
3+
//// [file.tsx]
4+
5+
declare module JSX {
6+
interface Element { }
7+
interface IntrinsicElements {
8+
[s: string]: any;
9+
}
10+
}
11+
12+
//// [Error1.tsx]
13+
// Issue error about missing span closing tag, not missing div closing tag
14+
let x1 = <div><span></div>;
15+
16+
//// [Error2.tsx]
17+
let x2 = <div></span>;
18+
19+
20+
//// [Error3.tsx]
21+
let x3 = <div>;
22+
23+
24+
//// [Error4.tsx]
25+
let x4 = <div><div></span>;
26+
27+
//// [Error5.tsx]
28+
let x5 = <div><span>
29+
30+
31+
32+
//// [file.jsx]
33+
//// [Error1.jsx]
34+
// Issue error about missing span closing tag, not missing div closing tag
35+
var x1 = <div><span></div>;
36+
</>;
37+
//// [Error2.jsx]
38+
var x2 = <div></span>;
39+
//// [Error3.jsx]
40+
var x3 = <div>;
41+
42+
</>;
43+
//// [Error4.jsx]
44+
var x4 = <div><div></span>;
45+
</>;
46+
//// [Error5.jsx]
47+
var x5 = <div><span>
48+
49+
</></>;

tests/baselines/reference/tsxErrorRecovery1.errors.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
tests/cases/conformance/jsx/file.tsx(5,11): error TS17008: JSX element 'div' has no corresponding closing tag.
12
tests/cases/conformance/jsx/file.tsx(5,19): error TS1109: Expression expected.
23
tests/cases/conformance/jsx/file.tsx(8,11): error TS2304: Cannot find name 'a'.
34
tests/cases/conformance/jsx/file.tsx(8,12): error TS1005: '}' expected.
4-
tests/cases/conformance/jsx/file.tsx(9,1): error TS17002: Expected corresponding JSX closing tag for 'div'.
5+
tests/cases/conformance/jsx/file.tsx(9,1): error TS1005: '</' expected.
56

67

7-
==== tests/cases/conformance/jsx/file.tsx (4 errors) ====
8+
==== tests/cases/conformance/jsx/file.tsx (5 errors) ====
89

910
declare namespace JSX { interface Element { } }
1011

1112
function foo() {
1213
var x = <div> { </div>
14+
~~~
15+
!!! error TS17008: JSX element 'div' has no corresponding closing tag.
1316
~~
1417
!!! error TS1109: Expression expected.
1518
}
@@ -21,4 +24,4 @@ tests/cases/conformance/jsx/file.tsx(9,1): error TS17002: Expected corresponding
2124
!!! error TS1005: '}' expected.
2225

2326

24-
!!! error TS17002: Expected corresponding JSX closing tag for 'div'.
27+
!!! error TS1005: '</' expected.

0 commit comments

Comments
 (0)