@@ -4865,18 +4865,45 @@ namespace ts {
48654865 return finishNode ( factory . createPropertyAccessExpression ( expression , parseRightSideOfDot ( /*allowIdentifierNames*/ true , /*allowPrivateIdentifiers*/ true ) ) , pos ) ;
48664866 }
48674867
4868- function parseJsxElementOrSelfClosingElementOrFragment ( inExpressionContext : boolean , topInvalidNodePosition ?: number ) : JsxElement | JsxSelfClosingElement | JsxFragment {
4868+ function parseJsxElementOrSelfClosingElementOrFragment ( inExpressionContext : boolean , topInvalidNodePosition ?: number , openingTag ?: JsxOpeningElement | JsxOpeningFragment ) : JsxElement | JsxSelfClosingElement | JsxFragment {
48694869 const pos = getNodePos ( ) ;
48704870 const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment ( inExpressionContext ) ;
48714871 let result : JsxElement | JsxSelfClosingElement | JsxFragment ;
48724872 if ( opening . kind === SyntaxKind . JsxOpeningElement ) {
4873- const children = parseJsxChildren ( opening ) ;
4874- const closingElement = parseJsxClosingElement ( inExpressionContext ) ;
4875-
4876- if ( ! tagNamesAreEquivalent ( opening . tagName , closingElement . tagName ) ) {
4877- parseErrorAtRange ( closingElement , Diagnostics . Expected_corresponding_JSX_closing_tag_for_0 , getTextOfNodeFromSourceText ( sourceText , opening . tagName ) ) ;
4873+ let children = parseJsxChildren ( opening ) ;
4874+ let closingElement : JsxClosingElement ;
4875+
4876+ const lastChild : JsxChild | undefined = children [ children . length - 1 ] ;
4877+ if ( lastChild ?. kind === SyntaxKind . JsxElement
4878+ && ! tagNamesAreEquivalent ( lastChild . openingElement . tagName , lastChild . closingElement . tagName )
4879+ && tagNamesAreEquivalent ( opening . tagName , lastChild . closingElement . tagName ) ) {
4880+ // when an unclosed JsxOpeningElement incorrectly parses its parent's JsxClosingElement,
4881+ // restructure (<div>(...<span></div>)) --> (<div>(...<span></span>)</div>)
4882+ // (no need to error; the parent will error)
4883+ const end = lastChild . openingElement . end ; // newly-created children and closing are both zero-width end/end
4884+ const newLast = finishNode ( factory . createJsxElement (
4885+ lastChild . openingElement ,
4886+ createNodeArray ( [ ] , end , end ) ,
4887+ finishNode ( factory . createJsxClosingElement ( finishNode ( factory . createIdentifier ( "" ) , end , end ) ) , end , end ) ) ,
4888+ lastChild . openingElement . pos ,
4889+ end ) ;
4890+
4891+ children = createNodeArray ( [ ...children . slice ( 0 , children . length - 1 ) , newLast ] , children . pos , end ) ;
4892+ closingElement = lastChild . closingElement ;
4893+ }
4894+ else {
4895+ closingElement = parseJsxClosingElement ( opening , inExpressionContext ) ;
4896+ if ( ! tagNamesAreEquivalent ( opening . tagName , closingElement . tagName ) ) {
4897+ if ( openingTag && isJsxOpeningElement ( openingTag ) && tagNamesAreEquivalent ( closingElement . tagName , openingTag . tagName ) ) {
4898+ // opening incorrectly matched with its parent's closing -- put error on opening
4899+ parseErrorAtRange ( opening . tagName , Diagnostics . JSX_element_0_has_no_corresponding_closing_tag , getTextOfNodeFromSourceText ( sourceText , opening . tagName ) ) ;
4900+ }
4901+ else {
4902+ // other opening/closing mismatches -- put error on closing
4903+ parseErrorAtRange ( closingElement . tagName , Diagnostics . Expected_corresponding_JSX_closing_tag_for_0 , getTextOfNodeFromSourceText ( sourceText , opening . tagName ) ) ;
4904+ }
4905+ }
48784906 }
4879-
48804907 result = finishNode ( factory . createJsxElement ( opening , children , closingElement ) , pos ) ;
48814908 }
48824909 else if ( opening . kind === SyntaxKind . JsxOpeningFragment ) {
@@ -4941,7 +4968,7 @@ namespace ts {
49414968 case SyntaxKind . OpenBraceToken :
49424969 return parseJsxExpression ( /*inExpressionContext*/ false ) ;
49434970 case SyntaxKind . LessThanToken :
4944- return parseJsxElementOrSelfClosingElementOrFragment ( /*inExpressionContext*/ false ) ;
4971+ return parseJsxElementOrSelfClosingElementOrFragment ( /*inExpressionContext*/ false , /*topInvalidNodePosition*/ undefined , openingTag ) ;
49454972 default :
49464973 return Debug . assertNever ( token ) ;
49474974 }
@@ -4957,6 +4984,13 @@ namespace ts {
49574984 const child = parseJsxChild ( openingTag , currentToken = scanner . reScanJsxToken ( ) ) ;
49584985 if ( ! child ) break ;
49594986 list . push ( child ) ;
4987+ if ( isJsxOpeningElement ( openingTag )
4988+ && child ?. kind === SyntaxKind . JsxElement
4989+ && ! tagNamesAreEquivalent ( child . openingElement . tagName , child . closingElement . tagName )
4990+ && tagNamesAreEquivalent ( openingTag . tagName , child . closingElement . tagName ) ) {
4991+ // stop after parsing a mismatched child like <div>...(<span></div>) in order to reattach the </div> higher
4992+ break ;
4993+ }
49604994 }
49614995
49624996 parsingContext = saveParsingContext ;
@@ -4978,7 +5012,6 @@ namespace ts {
49785012 scanJsxText ( ) ;
49795013 return finishNode ( factory . createJsxOpeningFragment ( ) , pos ) ;
49805014 }
4981-
49825015 const tagName = parseJsxElementName ( ) ;
49835016 const typeArguments = ( contextFlags & NodeFlags . JavaScriptFile ) === 0 ? tryParseTypeArguments ( ) : undefined ;
49845017 const attributes = parseJsxAttributes ( ) ;
@@ -4994,12 +5027,14 @@ namespace ts {
49945027 }
49955028 else {
49965029 parseExpected ( SyntaxKind . SlashToken ) ;
4997- if ( inExpressionContext ) {
4998- parseExpected ( SyntaxKind . GreaterThanToken ) ;
4999- }
5000- else {
5001- parseExpected ( SyntaxKind . GreaterThanToken , /*diagnostic*/ undefined , /*shouldAdvance*/ false ) ;
5002- scanJsxText ( ) ;
5030+ if ( parseExpected ( SyntaxKind . GreaterThanToken , /*diagnostic*/ undefined , /*shouldAdvance*/ false ) ) {
5031+ // manually advance the scanner in order to look for jsx text inside jsx
5032+ if ( inExpressionContext ) {
5033+ nextToken ( ) ;
5034+ }
5035+ else {
5036+ scanJsxText ( ) ;
5037+ }
50035038 }
50045039 node = factory . createJsxSelfClosingElement ( tagName , typeArguments , attributes ) ;
50055040 }
@@ -5077,16 +5112,18 @@ namespace ts {
50775112 return finishNode ( factory . createJsxSpreadAttribute ( expression ) , pos ) ;
50785113 }
50795114
5080- function parseJsxClosingElement ( inExpressionContext : boolean ) : JsxClosingElement {
5115+ function parseJsxClosingElement ( open : JsxOpeningElement , inExpressionContext : boolean ) : JsxClosingElement {
50815116 const pos = getNodePos ( ) ;
50825117 parseExpected ( SyntaxKind . LessThanSlashToken ) ;
50835118 const tagName = parseJsxElementName ( ) ;
5084- if ( inExpressionContext ) {
5085- parseExpected ( SyntaxKind . GreaterThanToken ) ;
5086- }
5087- else {
5088- parseExpected ( SyntaxKind . GreaterThanToken , /*diagnostic*/ undefined , /*shouldAdvance*/ false ) ;
5089- scanJsxText ( ) ;
5119+ if ( parseExpected ( SyntaxKind . GreaterThanToken , /*diagnostic*/ undefined , /*shouldAdvance*/ false ) ) {
5120+ // manually advance the scanner in order to look for jsx text inside jsx
5121+ if ( inExpressionContext || ! tagNamesAreEquivalent ( open . tagName , tagName ) ) {
5122+ nextToken ( ) ;
5123+ }
5124+ else {
5125+ scanJsxText ( ) ;
5126+ }
50905127 }
50915128 return finishNode ( factory . createJsxClosingElement ( tagName ) , pos ) ;
50925129 }
@@ -5097,12 +5134,14 @@ namespace ts {
50975134 if ( tokenIsIdentifierOrKeyword ( token ( ) ) ) {
50985135 parseErrorAtRange ( parseJsxElementName ( ) , Diagnostics . Expected_corresponding_closing_tag_for_JSX_fragment ) ;
50995136 }
5100- if ( inExpressionContext ) {
5101- parseExpected ( SyntaxKind . GreaterThanToken ) ;
5102- }
5103- else {
5104- parseExpected ( SyntaxKind . GreaterThanToken , /*diagnostic*/ undefined , /*shouldAdvance*/ false ) ;
5105- scanJsxText ( ) ;
5137+ if ( parseExpected ( SyntaxKind . GreaterThanToken , /*diagnostic*/ undefined , /*shouldAdvance*/ false ) ) {
5138+ // manually advance the scanner in order to look for jsx text inside jsx
5139+ if ( inExpressionContext ) {
5140+ nextToken ( ) ;
5141+ }
5142+ else {
5143+ scanJsxText ( ) ;
5144+ }
51065145 }
51075146 return finishNode ( factory . createJsxJsxClosingFragment ( ) , pos ) ;
51085147 }
0 commit comments