Repository: mdx-js/mdx-analyzer (js)

Issue:
Function as Child Component
### Initial checklist

- [X] I read the [support docs](https://mdxjs.com/community/support/)
- [X] I read the [contributing guide](https://mdxjs.com/community/contribute/)
- [X] I agree to follow the [code of conduct](https://github.com/mdx-js/.github/blob/main/code-of-conduct.md)
- [X] I searched [issues](https://github.com/search?q=user%3Amdx-js&type=issues) and [discussions](https://github.com/orgs/mdx-js/discussions) and couldn’t find anything (or linked relevant results below)

### Affected package

@mdx-js/typescript-plugin@0.0.6

### Steps to reproduce

Create a function as child component w/ some nesting, i.e. 

```
<Foo arr={bleh}>
  {name => 
    <>
      <Bar>
        <Baz>{name}</Baz>
      </Bar>
    </>
  }
</Foo>
```

### Actual behavior

Get a syntax error:

<img width="235" alt="Screenshot 2024-12-12 at 2 20 18 PM" src="https://github.com/user-attachments/assets/99a00a74-e473-4b39-aad5-b8dfae8b13b2" />

<img width="518" alt="Screenshot 2024-12-12 at 2 23 56 PM" src="https://github.com/user-attachments/assets/f9a940c6-c54d-44cd-8a64-67bda0e2e6bd" />


### Expected behavior

No syntax error

### Runtime

_No response_

### Package manager

_No response_

### Operating system

_No response_

### Build and bundle tools

_No response_

Pull request description:
Explicitly handle JSX tags in order
### Initial checklist

* [x] I read the support docs <!-- https://mdxjs.com/community/support/ -->
* [x] I read the contributing guide <!-- https://mdxjs.com/community/contribute/ -->
* [x] I agree to follow the code of conduct <!-- https://github.com/mdx-js/.github/blob/main/code-of-conduct.md -->
* [x] I searched issues and discussions and couldn’t find anything or linked relevant results below <!-- https://github.com/search?q=user%3Amdx-js&type=issues and https://github.com/orgs/mdx-js/discussions -->
* [x] I made sure the docs are up to date
* [x] I included tests (or that’s not needed)

### Description of changes

Virtual code generation from estree expressions was based on an assumption of the order in which JSXIdentifier nodes are visited. `estree-walker` visits the nodes in property order. The parser generates AST properties in an order that broke our code generation. As a result, the string `_components.` was injected in an incorrect position in the virtual code, yielding syntax errors.

This is now resolved by explicitly handling `JSXElement` nodes in both the enter and leave methods, instead of just handling `JSXIdentifier` nodes.

Closes #485

<!--do not edit: pr-->


Patch:
diff --git a/.changeset/sweet-rabbits-lick.md b/.changeset/sweet-rabbits-lick.md
new file mode 100644
index 0000000..0bae329
--- /dev/null
+++ b/.changeset/sweet-rabbits-lick.md
@@ -0,0 +1,5 @@
+---
+'@mdx-js/language-service': patch
+---
+
+Fix bug in virtual code generation for JSX closing elements
diff --git a/.changeset/tiny-geese-watch.md b/.changeset/tiny-geese-watch.md
new file mode 100644
index 0000000..e758985
--- /dev/null
+++ b/.changeset/tiny-geese-watch.md
@@ -0,0 +1,5 @@
+---
+'vscode-mdx': patch
+---
+
+Update the TextMate grammar
diff --git a/packages/language-service/lib/virtual-code.js b/packages/language-service/lib/virtual-code.js
index 6e8fee1..f7fcc2d 100644
--- a/packages/language-service/lib/virtual-code.js
+++ b/packages/language-service/lib/virtual-code.js
@@ -1,6 +1,6 @@
 /**
  * @import {CodeMapping, VirtualCode} from '@volar/language-service'
- * @import {ExportDefaultDeclaration, Program} from 'estree'
+ * @import {ExportDefaultDeclaration, JSXClosingElement, JSXOpeningElement, Program} from 'estree-jsx'
  * @import {Nodes, Root} from 'mdast'
  * @import {MdxjsEsm} from 'mdast-util-mdxjs-esm'
  * @import {IScriptSnapshot} from 'typescript'
@@ -449,18 +449,32 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
   function processJsxExpression(program, lastIndex) {
     let newIndex = lastIndex
     let functionNesting = 0
+
+    /**
+     * @param {JSXClosingElement | JSXOpeningElement} node
+     * @returns {undefined}
+     */
+    function processJsxTag(node) {
+      const {name} = node
+
+      if (name.type !== 'JSXIdentifier') {
+        return
+      }
+
+      if (!isInjectableComponent(name.name, variables)) {
+        return
+      }
+
+      jsx =
+        addOffset(jsxMapping, mdx, jsx, newIndex, name.start) + '_components.'
+      newIndex = name.start
+    }
+
     walk(program, {
       enter(node) {
         switch (node.type) {
-          case 'JSXIdentifier': {
-            if (!isInjectableComponent(node.name, variables)) {
-              return
-            }
-
-            jsx =
-              addOffset(jsxMapping, mdx, jsx, newIndex, node.start) +
-              '_components.'
-            newIndex = node.start
+          case 'JSXElement': {
+            processJsxTag(node.openingElement)
             break
           }
 
@@ -500,6 +514,16 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
             break
           }
 
+          case 'JSXElement': {
+            const {closingElement} = node
+
+            if (closingElement) {
+              processJsxTag(closingElement)
+            }
+
+            break
+          }
+
           default:
         }
       }
diff --git a/packages/vscode-mdx/syntaxes/source.mdx.tmLanguage b/packages/vscode-mdx/syntaxes/source.mdx.tmLanguage
index 190a069..5ea5c4a 100644
--- a/packages/vscode-mdx/syntaxes/source.mdx.tmLanguage
+++ b/packages/vscode-mdx/syntaxes/source.mdx.tmLanguage
@@ -7,7 +7,7 @@
     Once accepted there, it can be pulled into this project (`vscode-mdx`) with
     `script/generate.mjs`, and released.
 
-    Version from SHA: 5caa71e2bbddea59b27c6a5a346881cd10fa0f5f.
+    Version from SHA: c78b1e5df644d24fa76716bbe26f4b48a6fc1610.
   -->
   <dict>
     <key>fileTypes</key>
@@ -927,7 +927,7 @@
             <key>match</key>
             <string>(?:^|\G)[\t ]*(#{3}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
             <key>name</key>
-            <string>markup.heading.atx.2.mdx</string>
+            <string>markup.heading.atx.3.mdx</string>
             <key>captures</key>
             <dict>
               <key>1</key>
@@ -958,7 +958,7 @@
             <key>match</key>
             <string>(?:^|\G)[\t ]*(#{4}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
             <key>name</key>
-            <string>markup.heading.atx.2.mdx</string>
+            <string>markup.heading.atx.4.mdx</string>
             <key>captures</key>
             <dict>
               <key>1</key>
@@ -989,7 +989,7 @@
             <key>match</key>
             <string>(?:^|\G)[\t ]*(#{5}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
             <key>name</key>
-            <string>markup.heading.atx.2.mdx</string>
+            <string>markup.heading.atx.5.mdx</string>
             <key>captures</key>
             <dict>
               <key>1</key>
@@ -1020,7 +1020,7 @@
             <key>match</key>
             <string>(?:^|\G)[\t ]*(#{6}(?!#))(?:[ \t]+([^\r\n]+?)(?:[ \t]+(#+?))?)?[ \t]*$</string>
             <key>name</key>
-            <string>markup.heading.atx.2.mdx</string>
+            <string>markup.heading.atx.6.mdx</string>
             <key>captures</key>
             <dict>
               <key>1</key>


Test patch:
diff --git a/packages/language-service/test/language-plugin.js b/packages/language-service/test/language-plugin.js
index 474499a..59e78cf 100644
--- a/packages/language-service/test/language-plugin.js
+++ b/packages/language-service/test/language-plugin.js
@@ -1752,8 +1752,12 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
     '{<div>{""}</div>}',
     '{<Injected />}',
     '{<Injected>{""}</Injected>}',
+    '{<Injected><Injected>{""}</Injected></Injected>}',
     '{<Local />}',
-    '{<Local>{""}</Local>}'
+    '{<Local>{""}</Local>}',
+    '{<Local><Local>{""}</Local></Local>}',
+    '{<Local><Injected>{""}</Injected></Local>}',
+    '{<Injected><Local>{""}</Local></Injected>}'
   )
 
   const code = plugin.createVirtualCode?.('/test.mdx', 'mdx', snapshot, {
@@ -1799,9 +1803,18 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
           }
         },
         {
-          sourceOffsets: [28, 38, 56, 58, 71, 73, 88, 99, 111],
-          generatedOffsets: [843, 857, 879, 893, 910, 924, 951, 966, 982],
-          lengths: [9, 17, 2, 12, 2, 15, 10, 11, 21],
+          sourceOffsets: [
+            28, 38, 56, 58, 71, 73, 88, 99, 101, 111, 126, 137, 148, 160, 182,
+            219, 228, 243, 262, 264, 294
+          ],
+          generatedOffsets: [
+            843, 857, 879, 893, 910, 924, 951, 966, 980, 1002, 1029, 1052, 1067,
+            1083, 1109, 1150, 1171, 1198, 1221, 1235, 1277
+          ],
+          lengths: [
+            9, 17, 2, 12, 2, 15, 10, 2, 10, 15, 11, 10, 11, 21, 36, 9, 15, 18,
+            2, 30, 10
+          ],
           data: {
             completion: true,
             format: false,
@@ -1845,8 +1858,12 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
         '    {<div>{""}</div>}',
         '    {<_components.Injected />}',
         '    {<_components.Injected>{""}</_components.Injected>}',
+        '    {<_components.Injected><_components.Injected>{""}</_components.Injected></_components.Injected>}',
         '    {<Local />}',
         '    {<Local>{""}</Local>}',
+        '    {<Local><Local>{""}</Local></Local>}',
+        '    {<Local><_components.Injected>{""}</_components.Injected></Local>}',
+        '    {<_components.Injected><Local>{""}</Local></_components.Injected>}',
         '  </>',
         '}',
         '',
@@ -1870,9 +1887,9 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
       languageId: 'markdown',
       mappings: [
         {
-          sourceOffsets: [26, 37, 55, 70, 98, 110],
-          generatedOffsets: [0, 9, 17, 25, 33, 41],
-          lengths: [2, 1, 1, 1, 1, 1],
+          sourceOffsets: [26, 37, 55, 70, 98, 147, 159, 181, 218, 261],
+          generatedOffsets: [0, 9, 17, 25, 33, 41, 49, 57, 65, 73],
+          lengths: [2, 1, 1, 1, 1, 1, 1, 1, 1, 1],
           data: {
             completion: true,
             format: false,
@@ -1891,6 +1908,10 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
         '<!---->',
         '<!---->',
         '<!---->',
+        '<!---->',
+        '<!---->',
+        '<!---->',
+        '<!---->',
         '<!---->'
       )
     }