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

Issue:
User defined `export default` in `.mdx` should be allowed
### 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

vscode-mdx

### Link to runnable example

_No response_

### Steps to reproduce

```mdx
export   default     ({children   }) => < div> Content {    children}</div>
```

Run on https://mdxjs.com/playground/

### Expected behavior

<img width="2447" alt="image" src="https://github.com/mdx-js/mdx-analyzer/assets/8336744/009bf477-2bc7-4a80-95ee-8208d2d1e394">


### Actual behavior

<img width="850" alt="image" src="https://github.com/mdx-js/mdx-analyzer/assets/8336744/b36cd7bb-c483-472e-9ff9-4e34a3022c6a">


### Runtime

Node v20

### Package manager

yarn v1

### OS

macOS

### Build and bundle tools

Rollup

Pull request:
Support MDX layout
### Initial checklist

*   [x] I read the support docs <!-- https://github.com/unifiedjs/.github/blob/main/support.md -->
*   [x] I read the contributing guide <!-- https://github.com/unifiedjs/.github/blob/main/contributing.md -->
*   [x] I agree to follow the code of conduct <!-- https://github.com/unifiedjs/.github/blob/main/code-of-conduct.md -->
*   [x] I searched issues and couldn’t find anything (or linked relevant results below) <!-- https://github.com/search?q=user%3Aunifiedjs&type=Issues -->
*   [x] If applicable, I’ve added docs and tests

### Description of changes

Closes #362

<!--do not edit: pr-->


Patch:
diff --git a/.changeset/purple-mice-rhyme.md b/.changeset/purple-mice-rhyme.md
new file mode 100644
index 0000000..ba33fe4
--- /dev/null
+++ b/.changeset/purple-mice-rhyme.md
@@ -0,0 +1,5 @@
+---
+"@mdx-js/language-service": minor
+---
+
+Support MDXLayout
diff --git a/package.json b/package.json
index ef945a3..fda5bac 100644
--- a/package.json
+++ b/package.json
@@ -42,15 +42,6 @@
     ]
   },
   "xo": {
-    "prettier": true,
-    "env": [
-      "browser",
-      "es2021",
-      "node"
-    ],
-    "rules": {
-      "@typescript-eslint/prefer-nullish-coalescing": "off",
-      "max-params": "off"
-    }
+    "prettier": true
   }
 }
diff --git a/packages/language-server/README.md b/packages/language-server/README.md
index 21d589b..e404bb5 100644
--- a/packages/language-server/README.md
+++ b/packages/language-server/README.md
@@ -1,4 +1,4 @@
-# `@mdx-js/language-service`
+# `@mdx-js/language-server`
 
 ## What is this?
 
diff --git a/packages/language-service/lib/language-module.js b/packages/language-service/lib/language-module.js
index bc30b1b..73721db 100644
--- a/packages/language-service/lib/language-module.js
+++ b/packages/language-service/lib/language-module.js
@@ -3,8 +3,10 @@
  * @typedef {import('@volar/language-core').LanguagePlugin} LanguagePlugin
  * @typedef {import('@volar/language-core').Mapping<CodeInformation>} Mapping
  * @typedef {import('@volar/language-core').VirtualFile} VirtualFile
+ * @typedef {import('estree').ExportDefaultDeclaration} ExportDefaultDeclaration
  * @typedef {import('mdast').Root} Root
  * @typedef {import('mdast').RootContent} RootContent
+ * @typedef {import('mdast-util-mdxjs-esm').MdxjsEsm} MdxjsEsm
  * @typedef {import('typescript').IScriptSnapshot} IScriptSnapshot
  * @typedef {import('unified').PluggableList} PluggableList
  * @typedef {import('unified').Processor<Root>} Processor
@@ -16,6 +18,24 @@ import remarkMdx from 'remark-mdx'
 import remarkParse from 'remark-parse'
 import {unified} from 'unified'
 
+/**
+ * @param {string} propsName
+ */
+const layoutJsDoc = (propsName) => `
+/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */
+
+/**
+ * There is one special component: [MDX layout](https://mdxjs.com/docs/using-mdx/#layout).
+ * If it is defined, it’s used to wrap all content.
+ * A layout can be defined from within MDX using a default export.
+ *
+ * @param {{readonly [K in keyof MDXLayoutProps]: MDXLayoutProps[K]}} ${propsName}
+ *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.
+ *   In addition, the MDX layout receives the \`children\` prop, which contains the rendered MDX content.
+ * @returns {JSX.Element}
+ *   The MDX content wrapped in the layout.
+ */`
+
 const componentStart = `
 /**
  * Render the MDX contents.
@@ -67,6 +87,124 @@ function addOffset(mapping, sourceOffset, generatedOffset, length) {
   mapping.lengths.push(length)
 }
 
+/**
+ * @param {string} haystack
+ * @param {string} needle
+ * @param {number} start
+ */
+function findIndexAfter(haystack, needle, start) {
+  for (let index = start; index < haystack.length; index++) {
+    if (haystack[index] === needle) {
+      return index
+    }
+  }
+
+  return -1
+}
+
+/**
+ * @param {ExportDefaultDeclaration} node
+ */
+function getPropsName(node) {
+  const {declaration} = node
+  const {type} = declaration
+
+  if (
+    type !== 'ArrowFunctionExpression' &&
+    type !== 'FunctionDeclaration' &&
+    type !== 'FunctionExpression'
+  ) {
+    return
+  }
+
+  if (declaration.params.length === 1) {
+    const parameter = declaration.params[0]
+    if (parameter.type === 'Identifier') {
+      return parameter.name
+    }
+  }
+
+  return 'props'
+}
+
+/**
+ * Process exports of an MDX ESM node.
+ *
+ * @param {string} mdx
+ *   The full MDX code to process.
+ * @param {MdxjsEsm} node
+ *   The MDX ESM node to process.
+ * @param {Mapping} mapping
+ *   The Volar mapping to add offsets to.
+ * @param {string} esm
+ *   The virtual ESM code up to the point this function was called.
+ * @returns {string}
+ *   The updated virtual ESM code.
+ */
+function processExports(mdx, node, mapping, esm) {
+  const start = node.position?.start?.offset
+  const end = node.position?.end?.offset
+
+  if (start === undefined || end === undefined) {
+    return esm
+  }
+
+  const body = node.data?.estree?.body
+
+  if (!body?.length) {
+    addOffset(mapping, start, esm.length, end - start)
+    return esm + mdx.slice(start, end) + '\n'
+  }
+
+  for (const child of body) {
+    if (child.type === 'ExportDefaultDeclaration') {
+      const propsName = getPropsName(child)
+      if (propsName) {
+        esm += layoutJsDoc(propsName)
+      }
+
+      esm += '\nconst MDXLayout = '
+      addOffset(
+        mapping,
+        child.declaration.start,
+        esm.length,
+        child.end - child.declaration.start
+      )
+      esm += mdx.slice(child.declaration.start, child.end) + '\n'
+      continue
+    }
+
+    if (child.type === 'ExportNamedDeclaration' && child.source) {
+      const {specifiers} = child
+      for (let index = 0; index < specifiers.length; index++) {
+        const specifier = specifiers[index]
+        if (specifier.local.name === 'default') {
+          addOffset(mapping, start, esm.length, specifier.start - start)
+          esm += mdx.slice(start, specifier.start)
+          const nextPosition =
+            index === specifiers.length - 1
+              ? specifier.end
+              : findIndexAfter(mdx, ',', specifier.end) + 1
+          addOffset(mapping, nextPosition, esm.length, end - nextPosition)
+          return (
+            esm +
+            mdx.slice(nextPosition, end) +
+            '\nimport {' +
+            specifier.exported.name +
+            ' as MDXLayout} from ' +
+            JSON.stringify(child.source.value)
+          )
+        }
+      }
+    }
+
+    addOffset(mapping, child.start, esm.length, child.end - child.start)
+    esm += mdx.slice(child.start, child.end) + '\n'
+  }
+
+  return esm
+}
+
 /**
  * @param {string} fileName
  * @param {IScriptSnapshot} snapshot
@@ -250,8 +388,7 @@ function getVirtualFiles(fileName, snapshot, ts, processor) {
 
         case 'mdxjsEsm': {
           updateMarkdownFromNode(node)
-          addOffset(esmMapping, start, esm.length, end - start)
-          esm += mdx.slice(start, end) + '\n'
+          esm = processExports(mdx, node, esmMapping, esm)
           break
         }
 
diff --git a/packages/language-service/types.d.ts b/packages/language-service/types.d.ts
index 1c5c6ee..052d570 100644
--- a/packages/language-service/types.d.ts
+++ b/packages/language-service/types.d.ts
@@ -1,3 +1,10 @@
+declare module 'estree' {
+  export interface BaseNode {
+    start: number
+    end: number
+  }
+}
+
 declare module 'mdast' {
   export interface TOML extends Literal {
     type: 'toml'


Test patch:
diff --git a/packages/language-service/test/language-module.js b/packages/language-service/test/language-module.js
index e624f67..ce96104 100644
--- a/packages/language-service/test/language-module.js
+++ b/packages/language-service/test/language-module.js
@@ -100,6 +100,1019 @@ test('create virtual file w/ mdxjsEsm', () => {
   })
 })
 
+test('create virtual file w/o MDX layout in case of named re-export', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines('export {named} from "./Layout.js"', '')
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [34],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [0],
+            generatedOffsets: [0],
+            lengths: [33],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          'export {named} from "./Layout.js"',
+          '',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [33],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout in case of default re-export', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines('export {default} from "./Layout.js"', '')
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [36],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [0, 15],
+            generatedOffsets: [0, 8],
+            lengths: [8, 20],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          'export {} from "./Layout.js"',
+          'import {default as MDXLayout} from "./Layout.js"',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [35],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout in case of named and default re-export', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines(
+    'export {named, default} from "./Layout.js"',
+    ''
+  )
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [43],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [0, 22],
+            generatedOffsets: [0, 15],
+            lengths: [15, 20],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          'export {named, } from "./Layout.js"',
+          'import {default as MDXLayout} from "./Layout.js"',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [42],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout in case of default and named re-export', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines(
+    'export {default, named} from "./Layout.js"',
+    ''
+  )
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [43],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [0, 16],
+            generatedOffsets: [0, 8],
+            lengths: [8, 26],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          'export { named} from "./Layout.js"',
+          'import {default as MDXLayout} from "./Layout.js"',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [42],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout in case of a default exported arrow function', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines('export default () => {}', '')
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [24],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [15],
+            generatedOffsets: [670],
+            lengths: [8],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          '',
+          '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
+          '',
+          '/**',
+          ' * There is one special component: [MDX layout](https://mdxjs.com/docs/using-mdx/#layout).',
+          ' * If it is defined, it’s used to wrap all content.',
+          ' * A layout can be defined from within MDX using a default export.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXLayoutProps]: MDXLayoutProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' *   In addition, the MDX layout receives the `children` prop, which contains the rendered MDX content.',
+          ' * @returns {JSX.Element}',
+          ' *   The MDX content wrapped in the layout.',
+          ' */',
+          'const MDXLayout = () => {}',
+          '',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [23],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout in case of a default exported function declaration', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines(
+    'export default function MDXLayout() {}',
+    ''
+  )
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [39],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [15],
+            generatedOffsets: [670],
+            lengths: [23],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          '',
+          '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
+          '',
+          '/**',
+          ' * There is one special component: [MDX layout](https://mdxjs.com/docs/using-mdx/#layout).',
+          ' * If it is defined, it’s used to wrap all content.',
+          ' * A layout can be defined from within MDX using a default export.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXLayoutProps]: MDXLayoutProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' *   In addition, the MDX layout receives the `children` prop, which contains the rendered MDX content.',
+          ' * @returns {JSX.Element}',
+          ' *   The MDX content wrapped in the layout.',
+          ' */',
+          'const MDXLayout = function MDXLayout() {}',
+          '',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [38],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout in case of a default exported constant', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines('export default "main"', '')
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [22],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [15],
+            generatedOffsets: [19],
+            lengths: [6],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          '',
+          'const MDXLayout = "main"',
+          '',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [21],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout and matching argument name', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines(
+    'export default function MDXLayout(properties) {}',
+    ''
+  )
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [49],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [15],
+            generatedOffsets: [675],
+            lengths: [33],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          '',
+          '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
+          '',
+          '/**',
+          ' * There is one special component: [MDX layout](https://mdxjs.com/docs/using-mdx/#layout).',
+          ' * If it is defined, it’s used to wrap all content.',
+          ' * A layout can be defined from within MDX using a default export.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXLayoutProps]: MDXLayoutProps[K]}} properties',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' *   In addition, the MDX layout receives the `children` prop, which contains the rendered MDX content.',
+          ' * @returns {JSX.Element}',
+          ' *   The MDX content wrapped in the layout.',
+          ' */',
+          'const MDXLayout = function MDXLayout(properties) {}',
+          '',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [48],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout in case of a default export followed by a named', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines(
+    'export default function MDXLayout() {}',
+    'export function named() {}',
+    ''
+  )
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [66],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [15, 39],
+            generatedOffsets: [670, 694],
+            lengths: [23, 26],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          '',
+          '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
+          '',
+          '/**',
+          ' * There is one special component: [MDX layout](https://mdxjs.com/docs/using-mdx/#layout).',
+          ' * If it is defined, it’s used to wrap all content.',
+          ' * A layout can be defined from within MDX using a default export.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXLayoutProps]: MDXLayoutProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' *   In addition, the MDX layout receives the `children` prop, which contains the rendered MDX content.',
+          ' * @returns {JSX.Element}',
+          ' *   The MDX content wrapped in the layout.',
+          ' */',
+          'const MDXLayout = function MDXLayout() {}',
+          'export function named() {}',
+          '',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [65],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
+test('create virtual file w/ MDX layout in case of a default export preceded by a named', () => {
+  const module = getLanguageModule(typescript)
+
+  const snapshot = snapshotFromLines(
+    'export function named() {}',
+    'export default function MDXLayout() {}',
+    ''
+  )
+
+  const file = module.createVirtualFile('/test.mdx', 'mdx', snapshot)
+
+  assert.deepEqual(file, {
+    fileName: '/test.mdx',
+    languageId: 'mdx',
+    mappings: [
+      {
+        sourceOffsets: [0],
+        generatedOffsets: [0],
+        lengths: [66],
+        data: {
+          completion: true,
+          format: true,
+          navigation: true,
+          semantic: true,
+          structure: true,
+          verification: true
+        }
+      }
+    ],
+    snapshot,
+    embeddedFiles: [
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.jsx',
+        languageId: 'javascriptreact',
+        typescript: {
+          scriptKind: 2
+        },
+        mappings: [
+          {
+            sourceOffsets: [0, 42],
+            generatedOffsets: [0, 697],
+            lengths: [26, 23],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines(
+          'export function named() {}',
+          '',
+          '/** @typedef {MDXContentProps & { children: JSX.Element }} MDXLayoutProps */',
+          '',
+          '/**',
+          ' * There is one special component: [MDX layout](https://mdxjs.com/docs/using-mdx/#layout).',
+          ' * If it is defined, it’s used to wrap all content.',
+          ' * A layout can be defined from within MDX using a default export.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXLayoutProps]: MDXLayoutProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' *   In addition, the MDX layout receives the `children` prop, which contains the rendered MDX content.',
+          ' * @returns {JSX.Element}',
+          ' *   The MDX content wrapped in the layout.',
+          ' */',
+          'const MDXLayout = function MDXLayout() {}',
+          '',
+          '/**',
+          ' * Render the MDX contents.',
+          ' *',
+          ' * @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props',
+          ' *   The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.',
+          ' */',
+          'export default function MDXContent(props) {',
+          '  return <></>',
+          '}',
+          '',
+          '// @ts-ignore',
+          '/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */',
+          ''
+        )
+      },
+      {
+        embeddedFiles: [],
+        fileName: '/test.mdx.md',
+        languageId: 'markdown',
+        mappings: [
+          {
+            sourceOffsets: [65],
+            generatedOffsets: [0],
+            lengths: [1],
+            data: {
+              completion: true,
+              format: false,
+              navigation: true,
+              semantic: true,
+              structure: true,
+              verification: true
+            }
+          }
+        ],
+        snapshot: snapshotFromLines('', '')
+      }
+    ]
+  })
+})
+
 test('create virtual file w/ mdxFlowExpression', () => {
   const module = getLanguageModule(typescript)