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

Issue:
Auto-import IntelliSense of React components missing
### 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/HEAD/code-of-conduct.md)
- [X] I searched [issues](https://github.com/search?q=user%3Amdx-js&type=Issues) and couldn’t find anything (or linked relevant results below)

### Affected packages and versions

MDX VS Code Extension v1.8.9

### Link to runnable example

_No response_

### Steps to reproduce

As noted in https://github.com/mdx-js/mdx-analyzer/issues/451#issuecomment-2196432076, the IntelliSense entries for auto-import of React components in `.tsx` files (auto-import in an `.mdx` file) are missing.

1. Clone the reproduction at https://github.com/karlhorky/repro-mdx-analyzer-missing-auto-import-intellisense
2. Install the 2 recommended extensions `unifiedjs.vscode-mdx@1.8.9` and `viijay-kr.react-ts-css@3.2.0` (both enabled)
3. In `app/message.mdx`, move the cursor to the end `ZoomImage` and trigger IntelliSense with <kbd>ctrl</kbd>-<kbd>space</kbd>
4. Observe one entry in IntelliSense
5. Select this entry
6. Observe the import is added at the end of the file
7. Disable `viijay-kr.react-ts-css@3.2.0`
8. Move the cursor to the end `ZoomImage` and trigger IntelliSense with <kbd>ctrl</kbd>-<kbd>space</kbd>
9. Observe zero entries in IntelliSense

```
Version: 1.91.0-insider
Commit: aea213b7fcc7de5c24ad797ac1af209b159d451f
Date: 2024-06-28T06:02:58.030Z
Electron: 29.4.0
ElectronBuildId: 9728852
Chromium: 122.0.6261.156
Node.js: 20.9.0
V8: 12.2.281.27-electron.0
OS: Darwin arm64 23.5.0
```

Repo: https://github.com/karlhorky/repro-mdx-analyzer-missing-auto-import-intellisense

https://github.com/mdx-js/mdx-analyzer/assets/1935696/9dc9fded-14a3-490f-a94e-416c16fa2d0a



### Expected behavior

1. One entry in the IntelliSense for `ZoomImage` when only `unifiedjs.vscode-mdx@1.8.9` installed
2. Selecting the entry should add the new import to the top of the code (also when `viijay-kr.react-ts-css@3.2.0` enabled)
   1. Saving the file with an import not in the import block at the top should move the import to the top of the file

### Actual behavior

1. Zero entries in the IntelliSense for `ZoomImage` when only `unifiedjs.vscode-mdx@1.8.9` installed
2. (when `viijay-kr.react-ts-css@3.2.0` enabled) selecting the entry adds the new import to the bottom of the code
   1. Saving the file has no effect

### Runtime

Node v20

### Package manager

_No response_

### OS

macOS

### Build and bundle tools

Next.js

### Potentially Related

- https://github.com/mdx-js/mdx-analyzer/issues/413
- https://github.com/mdx-js/mdx-analyzer/issues/56
- https://github.com/mdx-js/mdx-analyzer/issues/165
- https://github.com/mdx-js/mdx-analyzer/pull/226

Pull request description:
Fix auto-imports
### 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

This change adds support for auto-imports.

The virtual code now contains an empty import of the JSX runtime. This import was chosen, because it must exist anyway. This is immediately followed by an empty code mapping, meaning TypeScript always has a place to insert auto-imports.

Since JSX components can be injected, they are sometimes prefixed with `_components.` in the virtual code. To support auto-import completions, an additional mapping is now made to an expression containing merely the identifier. As a result, the editor now shows auto-import completions, unless `MDXProvidedComponents` is defined. I don’t know why the existence of `MDXProvidedComponents` matters, but this probably matches the expectation of users anyway.

The auto-imports will not be followed by a blank line. This can lead to a syntax error in case no other imports exist yet. This is not ideal, but easy and straight-forward to resolve manually.

Closes #452

<!--do not edit: pr-->


Patch:
diff --git a/.changeset/olive-tips-appear.md b/.changeset/olive-tips-appear.md
new file mode 100644
index 0000000..d33fdfb
--- /dev/null
+++ b/.changeset/olive-tips-appear.md
@@ -0,0 +1,8 @@
+---
+'@mdx-js/language-service': patch
+'@mdx-js/language-server': patch
+'@mdx-js/typescript-plugin': patch
+'vscode-mdx': patch
+---
+
+Enhance support for auto-imports
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 506377b..43c3df4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -41,7 +41,7 @@ bug or suggest a new feature before creating a pull request.
 * Include as much information as possible: steps to reproduce the issue, error
   message, version, operating system, etcetera
 * The more time you put into an issue, the better we will be able to help you
-* The best issue report is a [failing test][unit-test] proving it
+* The best issue report is a failing test proving it
 
 ## Submitting a pull request
 
@@ -111,8 +111,6 @@ Studio Code extension to the [marketplace][], and create GitHub releases.
 
 [support]: https://mdxjs.com/community/support/
 
-[unit-test]: https://twitter.com/sindresorhus/status/579306280495357953
-
 [volar labs]: https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volarjs-labs
 
 [vscode]: https://code.visualstudio.com
diff --git a/packages/language-service/lib/virtual-code.js b/packages/language-service/lib/virtual-code.js
index dc2840e..4e28384 100644
--- a/packages/language-service/lib/virtual-code.js
+++ b/packages/language-service/lib/virtual-code.js
@@ -28,6 +28,7 @@ const jsPrefix = (
   jsxImportSource
 ) => `${tsCheck ? '// @ts-check\n' : ''}/* @jsxRuntime automatic
 @jsxImportSource ${jsxImportSource} */
+import '${jsxImportSource}/jsx-runtime'
 `
 
 /**
@@ -294,6 +295,21 @@ function processExports(mdx, node, mapping, esm) {
   return esm + '\n'
 }
 
+/**
+ * Pad the generated offsets of a Volar code mapping.
+ *
+ * @param {CodeMapping} mapping
+ *   The mapping whose generated offsets to pad.
+ * @param {number} padding
+ *   The padding to append to the generated offsets.
+ * @returns {undefined}
+ */
+function padOffsets(mapping, padding) {
+  for (let i = 0; i < mapping.generatedOffsets.length; i++) {
+    mapping.generatedOffsets[i] += padding
+  }
+}
+
 /**
  * @param {string} mdx
  * @param {Root} ast
@@ -302,6 +318,13 @@ function processExports(mdx, node, mapping, esm) {
  * @returns {VirtualCode[]}
  */
 function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
+  let hasAwait = false
+  let esm = jsPrefix(checkMdx, jsxImportSource)
+  let jsx = ''
+  let jsxVariables = ''
+  let markdown = ''
+  let nextMarkdownSourceStart = 0
+
   /** @type {CodeMapping[]} */
   const jsMappings = []
 
@@ -311,9 +334,11 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
    * @type {CodeMapping}
    */
   const esmMapping = {
-    sourceOffsets: [],
-    generatedOffsets: [],
-    lengths: [],
+    // The empty mapping makes sure there’s always a valid mapping to insert
+    // auto-imports.
+    sourceOffsets: [0],
+    generatedOffsets: [esm.length],
+    lengths: [0],
     data: {
       completion: true,
       format: true,
@@ -343,6 +368,20 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
     }
   }
 
+  const jsxVariablesMapping = {
+    sourceOffsets: [],
+    generatedOffsets: [],
+    lengths: [],
+    data: {
+      completion: true,
+      format: false,
+      navigation: true,
+      semantic: true,
+      structure: true,
+      verification: true
+    }
+  }
+
   /**
    * The Volar mapping that maps all markdown content to the virtual markdown file.
    *
@@ -364,13 +403,6 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
 
   /** @type {VirtualCode[]} */
   const virtualCodes = []
-
-  let hasAwait = false
-  let esm = jsPrefix(checkMdx, jsxImportSource)
-  let jsx = ''
-  let markdown = ''
-  let nextMarkdownSourceStart = 0
-
   const visitors = createVisitors()
 
   for (const child of ast.children) {
@@ -482,6 +514,17 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
 
       jsx =
         addOffset(jsxMapping, mdx, jsx, newIndex, name.start) + '_components.'
+      if (node.name && node.type === 'JSXOpeningElement') {
+        jsxVariables =
+          addOffset(
+            jsxVariablesMapping,
+            mdx,
+            jsxVariables + '// @ts-ignore\n',
+            name.start,
+            name.end
+          ) + '\n'
+      }
+
       newIndex = name.start
     }
 
@@ -624,6 +667,16 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
           jsx = addOffset(jsxMapping, mdx, jsx + jsxIndent, start, lastIndex)
           if (isInjectableComponent(node.name, programScope)) {
             jsx += '_components.'
+            if (node.name) {
+              jsxVariables =
+                addOffset(
+                  jsxVariablesMapping,
+                  mdx,
+                  jsxVariables + '// @ts-ignore\n',
+                  lastIndex,
+                  lastIndex + node.name.length
+                ) + '\n'
+            }
           }
 
           if (node.name) {
@@ -741,12 +794,12 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
   updateMarkdownFromOffsets(mdx.length, mdx.length)
   esm += componentStart(hasAwait, programScope)
 
-  for (let i = 0; i < jsxMapping.generatedOffsets.length; i++) {
-    jsxMapping.generatedOffsets[i] += esm.length
-  }
-
+  padOffsets(jsxMapping, esm.length)
   esm += jsx + componentEnd
 
+  padOffsets(jsxVariablesMapping, esm.length)
+  esm += jsxVariables
+
   if (esmMapping.sourceOffsets.length > 0) {
     jsMappings.push(esmMapping)
   }
@@ -755,6 +808,10 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
     jsMappings.push(jsxMapping)
   }
 
+  if (jsxVariablesMapping.sourceOffsets.length > 0) {
+    jsMappings.push(jsxVariablesMapping)
+  }
+
   virtualCodes.unshift(
     {
       id: 'jsx',


Test patch:
diff --git a/packages/language-server/test/completion.test.js b/packages/language-server/test/completion.test.js
index 2d1aec2..4e77333 100644
--- a/packages/language-server/test/completion.test.js
+++ b/packages/language-server/test/completion.test.js
@@ -47,7 +47,7 @@ test('support completion in ESM', async () => {
       original: {
         data: {
           fileName: fixturePath('node16/completion.mdx'),
-          offset: 81,
+          offset: 108,
           originalItem: {name: 'Boolean'},
           uri: String(
             URI.from({
@@ -110,7 +110,7 @@ test('support completion in JSX', async () => {
       original: {
         data: {
           fileName: fixturePath('node16/completion.mdx'),
-          offset: 119,
+          offset: 146,
           originalItem: {name: 'Boolean'},
           uri: String(
             URI.from({
diff --git a/packages/language-service/test/language-plugin.js b/packages/language-service/test/language-plugin.js
index 37c968c..88b500e 100644
--- a/packages/language-service/test/language-plugin.js
+++ b/packages/language-service/test/language-plugin.js
@@ -43,7 +43,7 @@ test('create virtual code w/ mdxjsEsm', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [51],
+          generatedOffsets: [78],
           lengths: [35],
           data: {
             completion: true,
@@ -58,6 +58,7 @@ test('create virtual code w/ mdxjsEsm', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'import {Planet} from "./Planet.js"',
         '',
         '',
@@ -161,7 +162,7 @@ test('create virtual code w/ mdxjsEsm and CRLF', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [51],
+          generatedOffsets: [78],
           lengths: [36],
           data: {
             completion: true,
@@ -176,6 +177,7 @@ test('create virtual code w/ mdxjsEsm and CRLF', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'import {Planet} from "./Planet.js"\r',
         '',
         '',
@@ -279,7 +281,7 @@ test('create virtual code w/o MDX layout in case of named re-export', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [51],
+          generatedOffsets: [78],
           lengths: [34],
           data: {
             completion: true,
@@ -294,6 +296,7 @@ test('create virtual code w/o MDX layout in case of named re-export', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export {named} from "./Layout.js"',
         '',
         '',
@@ -395,7 +398,7 @@ test('create virtual code w/ MDX layout in case of default re-export', () => {
       mappings: [
         {
           sourceOffsets: [0, 15],
-          generatedOffsets: [51, 59],
+          generatedOffsets: [78, 86],
           lengths: [8, 21],
           data: {
             completion: true,
@@ -410,6 +413,7 @@ test('create virtual code w/ MDX layout in case of default re-export', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export {} from "./Layout.js"',
         '',
         'import {default as MDXLayout} from "./Layout.js"',
@@ -515,7 +519,7 @@ test('create virtual code w/ MDX layout in case of named and default re-export',
       mappings: [
         {
           sourceOffsets: [0, 22],
-          generatedOffsets: [51, 66],
+          generatedOffsets: [78, 93],
           lengths: [15, 21],
           data: {
             completion: true,
@@ -530,6 +534,7 @@ test('create virtual code w/ MDX layout in case of named and default re-export',
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export {named, } from "./Layout.js"',
         '',
         'import {default as MDXLayout} from "./Layout.js"',
@@ -635,7 +640,7 @@ test('create virtual code w/ MDX layout in case of default and named re-export',
       mappings: [
         {
           sourceOffsets: [0, 16],
-          generatedOffsets: [51, 59],
+          generatedOffsets: [78, 86],
           lengths: [8, 27],
           data: {
             completion: true,
@@ -650,6 +655,7 @@ test('create virtual code w/ MDX layout in case of default and named re-export',
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export { named} from "./Layout.js"',
         '',
         'import {default as MDXLayout} from "./Layout.js"',
@@ -751,9 +757,9 @@ test('create virtual code w/ MDX layout in case of a default exported arrow func
       languageId: 'javascriptreact',
       mappings: [
         {
-          sourceOffsets: [15],
-          generatedOffsets: [721],
-          lengths: [9],
+          sourceOffsets: [0, 15],
+          generatedOffsets: [78, 748],
+          lengths: [0, 9],
           data: {
             completion: true,
             format: true,
@@ -767,6 +773,7 @@ test('create virtual code w/ MDX layout in case of a default exported arrow func
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
         '',
@@ -884,9 +891,9 @@ test('create virtual code w/ MDX layout in case of a default exported function d
       languageId: 'javascriptreact',
       mappings: [
         {
-          sourceOffsets: [15],
-          generatedOffsets: [721],
-          lengths: [24],
+          sourceOffsets: [0, 15],
+          generatedOffsets: [78, 748],
+          lengths: [0, 24],
           data: {
             completion: true,
             format: true,
@@ -900,6 +907,7 @@ test('create virtual code w/ MDX layout in case of a default exported function d
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
         '',
@@ -1016,9 +1024,9 @@ test('create virtual code w/ MDX layout in case of a default exported constant',
       languageId: 'javascriptreact',
       mappings: [
         {
-          sourceOffsets: [15],
-          generatedOffsets: [70],
-          lengths: [7],
+          sourceOffsets: [0, 15],
+          generatedOffsets: [78, 97],
+          lengths: [0, 7],
           data: {
             completion: true,
             format: true,
@@ -1032,6 +1040,7 @@ test('create virtual code w/ MDX layout in case of a default exported constant',
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         'const MDXLayout = "main"',
         '',
@@ -1136,9 +1145,9 @@ test('create virtual code w/ MDX layout and matching argument name', () => {
       languageId: 'javascriptreact',
       mappings: [
         {
-          sourceOffsets: [15],
-          generatedOffsets: [726],
-          lengths: [34],
+          sourceOffsets: [0, 15],
+          generatedOffsets: [78, 753],
+          lengths: [0, 34],
           data: {
             completion: true,
             format: true,
@@ -1152,6 +1161,7 @@ test('create virtual code w/ MDX layout and matching argument name', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
         '',
@@ -1272,9 +1282,9 @@ test('create virtual code w/ MDX layout in case of a default export followed by
       languageId: 'javascriptreact',
       mappings: [
         {
-          sourceOffsets: [15],
-          generatedOffsets: [721],
-          lengths: [51],
+          sourceOffsets: [0, 15],
+          generatedOffsets: [78, 748],
+          lengths: [0, 51],
           data: {
             completion: true,
             format: true,
@@ -1288,6 +1298,7 @@ test('create virtual code w/ MDX layout in case of a default export followed by
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
         '',
@@ -1412,7 +1423,7 @@ test('create virtual code w/ MDX layout in case of a default export preceded by
       mappings: [
         {
           sourceOffsets: [0, 42],
-          generatedOffsets: [51, 748],
+          generatedOffsets: [78, 775],
           lengths: [27, 24],
           data: {
             completion: true,
@@ -1427,6 +1438,7 @@ test('create virtual code w/ MDX layout in case of a default export preceded by
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export function named() {}',
         '',
         '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
@@ -1547,7 +1559,20 @@ test('create virtual code w/ mdxFlowExpression', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [779],
+          generatedOffsets: [78],
+          lengths: [0],
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
+        },
+        {
+          sourceOffsets: [0],
+          generatedOffsets: [806],
           lengths: [9],
           data: {
             completion: true,
@@ -1562,6 +1587,7 @@ test('create virtual code w/ mdxFlowExpression', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -1662,7 +1688,20 @@ test('create virtual code w/ empty mdxFlowExpression', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [780],
+          generatedOffsets: [78],
+          lengths: [0],
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
+        },
+        {
+          sourceOffsets: [0],
+          generatedOffsets: [807],
           lengths: [2],
           data: {
             completion: true,
@@ -1677,6 +1716,7 @@ test('create virtual code w/ empty mdxFlowExpression', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '',
         '/**',
@@ -1791,7 +1831,7 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [51],
+          generatedOffsets: [78],
           lengths: [27],
           data: {
             completion: true,
@@ -1808,8 +1848,8 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
             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
+            870, 884, 906, 920, 937, 951, 978, 993, 1007, 1029, 1056, 1079,
+            1094, 1110, 1136, 1177, 1198, 1225, 1248, 1262, 1304
           ],
           lengths: [
             9, 17, 2, 12, 2, 15, 10, 2, 10, 15, 11, 10, 11, 21, 36, 9, 15, 18,
@@ -1823,11 +1863,25 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
             structure: true,
             verification: true
           }
+        },
+        {
+          generatedOffsets: [1748, 1771, 1794, 1817, 1840, 1863],
+          lengths: [8, 8, 8, 8, 8, 8],
+          sourceOffsets: [58, 73, 101, 111, 228, 264],
+          data: {
+            completion: true,
+            format: false,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
         }
       ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export function Local() {}',
         '',
         '',
@@ -1879,6 +1933,18 @@ test('create virtual code w/ prefixed JSX expressions for mdxFlowExpression', ()
         '',
         '// @ts-ignore',
         '/** @typedef {(void extends Props ? {} : Props) & {components?: {}}} MDXContentProps */',
+        '// @ts-ignore',
+        'Injected',
+        '// @ts-ignore',
+        'Injected',
+        '// @ts-ignore',
+        'Injected',
+        '// @ts-ignore',
+        'Injected',
+        '// @ts-ignore',
+        'Injected',
+        '// @ts-ignore',
+        'Injected',
         ''
       )
     },
@@ -1962,7 +2028,7 @@ test('create virtual code w/ prefixed JSX expressions in attributes', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [51],
+          generatedOffsets: [78],
           lengths: [27],
           data: {
             completion: true,
@@ -1975,7 +2041,7 @@ test('create virtual code w/ prefixed JSX expressions in attributes', () => {
         },
         {
           sourceOffsets: [28, 62, 103, 149, 166, 214],
-          generatedOffsets: [843, 889, 933, 991, 1020, 1084],
+          generatedOffsets: [870, 916, 960, 1018, 1047, 1111],
           lengths: [34, 39, 46, 17, 35, 6],
           data: {
             completion: true,
@@ -1985,11 +2051,25 @@ test('create virtual code w/ prefixed JSX expressions in attributes', () => {
             structure: true,
             verification: true
           }
+        },
+        {
+          generatedOffsets: [1551, 1574],
+          lengths: [8, 8],
+          sourceOffsets: [62, 149],
+          data: {
+            completion: true,
+            format: false,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
         }
       ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export function Local() {}',
         '',
         '',
@@ -2037,6 +2117,10 @@ test('create virtual code w/ prefixed JSX expressions in attributes', () => {
         '',
         '// @ts-ignore',
         '/** @typedef {(void extends Props ? {} : Props) & {components?: {}}} MDXContentProps */',
+        '// @ts-ignore',
+        'Injected',
+        '// @ts-ignore',
+        'Injected',
         ''
       )
     },
@@ -2126,7 +2210,7 @@ test('create virtual code w/ mdxJsxFlowElement w/ children', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [51],
+          generatedOffsets: [78],
           lengths: [27],
           data: {
             completion: true,
@@ -2139,7 +2223,7 @@ test('create virtual code w/ mdxJsxFlowElement w/ children', () => {
         },
         {
           sourceOffsets: [28, 87, 94, 95, 158, 160, 170, 231],
-          generatedOffsets: [843, 877, 888, 901, 939, 953, 967, 1003],
+          generatedOffsets: [870, 904, 915, 928, 966, 980, 994, 1030],
           lengths: [5, 6, 1, 9, 2, 9, 7, 8],
           data: {
             completion: true,
@@ -2149,11 +2233,25 @@ test('create virtual code w/ mdxJsxFlowElement w/ children', () => {
             structure: true,
             verification: true
           }
+        },
+        {
+          data: {
+            completion: true,
+            format: false,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          },
+          generatedOffsets: [1472],
+          lengths: [8],
+          sourceOffsets: [95]
         }
       ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export function Local() {}',
         '',
         '',
@@ -2210,6 +2308,8 @@ test('create virtual code w/ mdxJsxFlowElement w/ children', () => {
         '',
         '// @ts-ignore',
         '/** @typedef {(void extends Props ? {} : Props) & {components?: {}}} MDXContentProps */',
+        '// @ts-ignore',
+        'Injected',
         ''
       )
     },
@@ -2289,9 +2389,22 @@ test('create virtual code w/ mdxJsxFlowElement w/ blockquote child', () => {
       id: 'jsx',
       languageId: 'javascriptreact',
       mappings: [
+        {
+          sourceOffsets: [0],
+          generatedOffsets: [78],
+          lengths: [0],
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
+        },
         {
           sourceOffsets: [0, 8, 15, 21],
-          generatedOffsets: [779, 804, 822, 841],
+          generatedOffsets: [806, 831, 849, 868],
           lengths: [5, 6, 5, 6],
           data: {
             completion: true,
@@ -2306,6 +2419,7 @@ test('create virtual code w/ mdxJsxFlowElement w/ blockquote child', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -2421,7 +2535,7 @@ test('create virtual code w/ mdxJsxFlowElement w/o children', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [51],
+          generatedOffsets: [78],
           lengths: [27],
           data: {
             completion: true,
@@ -2434,7 +2548,7 @@ test('create virtual code w/ mdxJsxFlowElement w/o children', () => {
         },
         {
           sourceOffsets: [28, 36, 37, 49],
-          generatedOffsets: [843, 855, 868, 884],
+          generatedOffsets: [870, 882, 895, 911],
           lengths: [7, 1, 11, 9],
           data: {
             completion: true,
@@ -2444,11 +2558,25 @@ test('create virtual code w/ mdxJsxFlowElement w/o children', () => {
             structure: true,
             verification: true
           }
+        },
+        {
+          generatedOffsets: [1354],
+          lengths: [8],
+          sourceOffsets: [37],
+          data: {
+            completion: true,
+            format: false,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
         }
       ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export function Local() {}',
         '',
         '',
@@ -2493,6 +2621,8 @@ test('create virtual code w/ mdxJsxFlowElement w/o children', () => {
         '',
         '// @ts-ignore',
         '/** @typedef {(void extends Props ? {} : Props) & {components?: {}}} MDXContentProps */',
+        '// @ts-ignore',
+        'Injected',
         ''
       )
     },
@@ -2562,7 +2692,7 @@ test('create virtual code w/ mdxJsxTextElement', () => {
       mappings: [
         {
           sourceOffsets: [0],
-          generatedOffsets: [51],
+          generatedOffsets: [78],
           lengths: [27],
           data: {
             completion: true,
@@ -2575,7 +2705,7 @@ test('create virtual code w/ mdxJsxTextElement', () => {
         },
         {
           sourceOffsets: [30, 41, 42, 56],
-          generatedOffsets: [859, 880, 893, 918],
+          generatedOffsets: [886, 907, 920, 945],
           lengths: [7, 1, 11, 9],
           data: {
             completion: true,
@@ -2585,11 +2715,25 @@ test('create virtual code w/ mdxJsxTextElement', () => {
             structure: true,
             verification: true
           }
+        },
+        {
+          generatedOffsets: [1396],
+          lengths: [8],
+          sourceOffsets: [42],
+          data: {
+            completion: true,
+            format: false,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
         }
       ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export function Local() {}',
         '',
         '',
@@ -2639,6 +2783,8 @@ test('create virtual code w/ mdxJsxTextElement', () => {
         '',
         '// @ts-ignore',
         '/** @typedef {(void extends Props ? {} : Props) & {components?: {}}} MDXContentProps */',
+        '// @ts-ignore',
+        'Injected',
         ''
       )
     },
@@ -2707,7 +2853,20 @@ test('create virtual code w/ mdxTextExpression', () => {
       languageId: 'javascriptreact',
       mappings: [
         {
-          generatedOffsets: [795],
+          generatedOffsets: [78],
+          lengths: [0],
+          sourceOffsets: [0],
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
+        },
+        {
+          generatedOffsets: [822],
           sourceOffsets: [4],
           lengths: [9],
           data: {
@@ -2723,6 +2882,7 @@ test('create virtual code w/ mdxTextExpression', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -2829,7 +2989,20 @@ test('create virtual code w/ async mdxTextExpression', () => {
       languageId: 'javascriptreact',
       mappings: [
         {
-          generatedOffsets: [801],
+          generatedOffsets: [78],
+          sourceOffsets: [0],
+          lengths: [0],
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          }
+        },
+        {
+          generatedOffsets: [828],
           sourceOffsets: [4],
           lengths: [32],
           data: {
@@ -2845,6 +3018,7 @@ test('create virtual code w/ async mdxTextExpression', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -2965,7 +3139,7 @@ test('ignore async functions in props or expressions', () => {
       languageId: 'javascriptreact',
       mappings: [
         {
-          generatedOffsets: [51, 117, 193],
+          generatedOffsets: [78, 144, 220],
           sourceOffsets: [0, 66, 142],
           lengths: [65, 75, 62],
           data: {
@@ -2978,7 +3152,7 @@ test('ignore async functions in props or expressions', () => {
           }
         },
         {
-          generatedOffsets: [1138, 1186, 1239],
+          generatedOffsets: [1165, 1213, 1266],
           sourceOffsets: [205, 249, 298],
           lengths: [43, 48, 54],
           data: {
@@ -2994,6 +3168,7 @@ test('ignore async functions in props or expressions', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         'export const arrow = async () => {',
         '  await Promise.resolve(42)',
         '}',
@@ -3126,7 +3301,20 @@ test('support locally scoped components', () => {
       languageId: 'javascriptreact',
       mappings: [
         {
-          generatedOffsets: [779],
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          },
+          generatedOffsets: [78],
+          lengths: [0],
+          sourceOffsets: [0]
+        },
+        {
+          generatedOffsets: [806],
           sourceOffsets: [0],
           lengths: [30],
           data: {
@@ -3142,6 +3330,7 @@ test('support locally scoped components', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -3245,10 +3434,25 @@ test('create virtual code w/ dedented markdown content', () => {
     {
       id: 'jsx',
       languageId: 'javascriptreact',
-      mappings: [],
+      mappings: [
+        {
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          },
+          generatedOffsets: [78],
+          lengths: [0],
+          sourceOffsets: [0]
+        }
+      ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -3358,6 +3562,7 @@ test('create virtual code w/ syntax error', () => {
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -3440,10 +3645,25 @@ test('create virtual code w/ yaml frontmatter', () => {
     {
       id: 'jsx',
       languageId: 'javascriptreact',
-      mappings: [],
+      mappings: [
+        {
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          },
+          generatedOffsets: [78],
+          lengths: [0],
+          sourceOffsets: [0]
+        }
+      ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -3569,10 +3789,25 @@ test('update virtual code', () => {
     {
       id: 'jsx',
       languageId: 'javascriptreact',
-      mappings: [],
+      mappings: [
+        {
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          },
+          generatedOffsets: [78],
+          lengths: [0],
+          sourceOffsets: [0]
+        }
+      ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -3712,11 +3947,26 @@ test('support checkMdx', () => {
     {
       id: 'jsx',
       languageId: 'javascriptreact',
-      mappings: [],
+      mappings: [
+        {
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          },
+          generatedOffsets: [91],
+          lengths: [0],
+          sourceOffsets: [0]
+        }
+      ],
       snapshot: snapshotFromLines(
         '// @ts-check',
         '/* @jsxRuntime automatic',
         '@jsxImportSource react */',
+        "import 'react/jsx-runtime'",
         '',
         '/**',
         ' * @internal',
@@ -3813,10 +4063,25 @@ test('support custom jsxImportSource', () => {
     {
       id: 'jsx',
       languageId: 'javascriptreact',
-      mappings: [],
+      mappings: [
+        {
+          data: {
+            completion: true,
+            format: true,
+            navigation: true,
+            semantic: true,
+            structure: true,
+            verification: true
+          },
+          generatedOffsets: [80],
+          lengths: [0],
+          sourceOffsets: [0]
+        }
+      ],
       snapshot: snapshotFromLines(
         '/* @jsxRuntime automatic',
         '@jsxImportSource preact */',
+        "import 'preact/jsx-runtime'",
         '',
         '/**',
         ' * @internal',