From f88955bc5ec379b384a7b71158cde200546b3bff Mon Sep 17 00:00:00 2001
From: Guyutongxue
Date: Sun, 31 Aug 2025 20:01:18 +0800
Subject: [PATCH 1/4] feat(language-service): Make mdx nodes typed with
MDX.IntrinsicElements
---
packages/language-service/lib/virtual-code.js | 32 +++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/packages/language-service/lib/virtual-code.js b/packages/language-service/lib/virtual-code.js
index bd457bfa..599befce 100644
--- a/packages/language-service/lib/virtual-code.js
+++ b/packages/language-service/lib/virtual-code.js
@@ -24,6 +24,25 @@ import {isInjectableComponent, isInjectableEstree} from './jsx-utils.js'
*/
const jsPrefix = (jsxImportSource) => `/* @jsxRuntime automatic
@jsxImportSource ${jsxImportSource} */
+
+/**
+ * @internal
+ * @template T
+ * @typedef {void extends MDX.IntrinsicElements ? any : T extends keyof MDX.IntrinsicElements ? MDX.IntrinsicElements[T] : any} _MDXProps
+ */
+
+/**
+ * @internal
+ * @typedef {void extends MDX.Element ? JSX.Element : MDX.Element} _MDXElement
+ */
+
+/**
+ * @internal
+ * @type {{
+ * [Name in keyof _MDXNodeNames]: (props: _MDXProps) => _MDXElement
+ * }}
+ */
+const _MDXNodes = /** @type {any} */(0);
`
/**
@@ -247,6 +266,8 @@ function getEmbeddedCodes(
let jsxVariables = ''
let markdown = ''
let nextMarkdownSourceStart = 0
+ /** @type {Set} */
+ const foundMdxNodeNames = new Set()
const plugins = virtualCodePlugins.map((plugin) => plugin())
/** @type {CodeMapping[]} */
@@ -739,7 +760,8 @@ function getEmbeddedCodes(
}
default: {
- jsx += jsxIndent + '<>'
+ foundMdxNodeNames.add(node.type)
+ jsx += jsxIndent + `<_MDXNodes.${node.type}>`
break
}
}
@@ -789,7 +811,7 @@ function getEmbeddedCodes(
}
default: {
- jsx += jsxIndent + '>'
+ jsx += jsxIndent + ``
break
}
}
@@ -820,6 +842,12 @@ function getEmbeddedCodes(
esmMapping.lengths.unshift(0)
}
+ esm += `/**
+ * @typedef {object} _MDXNodeNames
+${[...foundMdxNodeNames].map((name) => ` * @property {unknown} ${name}`).join('\n')}
+ */
+`
+
updateMarkdownFromOffsets(mdx.length, mdx.length)
esm += componentStart(hasAwait, programScope)
From f0dc9b8487fb7683b9f0c5ec6f3e3f2e72f32d0d Mon Sep 17 00:00:00 2001
From: Guyutongxue
Date: Sun, 7 Sep 2025 15:00:39 +0800
Subject: [PATCH 2/4] fix(language-service): use JSX node name map
---
packages/language-service/lib/virtual-code.js | 74 +++++++++++--------
1 file changed, 44 insertions(+), 30 deletions(-)
diff --git a/packages/language-service/lib/virtual-code.js b/packages/language-service/lib/virtual-code.js
index 599befce..9f57cfe3 100644
--- a/packages/language-service/lib/virtual-code.js
+++ b/packages/language-service/lib/virtual-code.js
@@ -24,25 +24,6 @@ import {isInjectableComponent, isInjectableEstree} from './jsx-utils.js'
*/
const jsPrefix = (jsxImportSource) => `/* @jsxRuntime automatic
@jsxImportSource ${jsxImportSource} */
-
-/**
- * @internal
- * @template T
- * @typedef {void extends MDX.IntrinsicElements ? any : T extends keyof MDX.IntrinsicElements ? MDX.IntrinsicElements[T] : any} _MDXProps
- */
-
-/**
- * @internal
- * @typedef {void extends MDX.Element ? JSX.Element : MDX.Element} _MDXElement
- */
-
-/**
- * @internal
- * @type {{
- * [Name in keyof _MDXNodeNames]: (props: _MDXProps) => _MDXElement
- * }}
- */
-const _MDXNodes = /** @type {any} */(0);
`
/**
@@ -244,6 +225,48 @@ function padOffsets(mapping, padding) {
}
}
+/** @type {Partial>} */
+const mdastElementNodeNameMap = {
+ blockquote: 'blockquote',
+ break: 'br',
+ delete: 'del',
+ emphasis: 'em',
+ html: 'html',
+ image: 'img',
+ imageReference: 'img',
+ inlineCode: 'code',
+ link: 'a',
+ linkReference: 'a',
+ listItem: 'li',
+ paragraph: 'p',
+ strong: 'strong',
+ table: 'table',
+ tableCell: 'td',
+ tableRow: 'tr',
+ thematicBreak: 'hr'
+}
+/**
+ * Get the JSX node names for a given AST node.
+ * @param {Nodes} node
+ * @returns {string[]}
+ */
+function getJsxNodeNameForMdast(node) {
+ if (node.type === 'code') {
+ return ['pre', 'code']
+ }
+ if (node.type === 'heading') {
+ return ['h' + node.depth]
+ }
+ if (node.type === 'list') {
+ return node.ordered ? ['ol'] : ['ul']
+ }
+ if (mdastElementNodeNameMap[node.type]) {
+ return [/** @type string */ (mdastElementNodeNameMap[node.type])]
+ }
+ // JSX Fragment
+ return ['']
+}
+
/**
* @param {string} mdx
* @param {Root} ast
@@ -266,8 +289,6 @@ function getEmbeddedCodes(
let jsxVariables = ''
let markdown = ''
let nextMarkdownSourceStart = 0
- /** @type {Set} */
- const foundMdxNodeNames = new Set()
const plugins = virtualCodePlugins.map((plugin) => plugin())
/** @type {CodeMapping[]} */
@@ -760,8 +781,7 @@ function getEmbeddedCodes(
}
default: {
- foundMdxNodeNames.add(node.type)
- jsx += jsxIndent + `<_MDXNodes.${node.type}>`
+ jsx += jsxIndent + getJsxNodeNameForMdast(node).map((name) => `<${name}>`).join('')
break
}
}
@@ -811,7 +831,7 @@ function getEmbeddedCodes(
}
default: {
- jsx += jsxIndent + ``
+ jsx += jsxIndent + getJsxNodeNameForMdast(node).map((name) => `${name}>`).join('')
break
}
}
@@ -842,12 +862,6 @@ function getEmbeddedCodes(
esmMapping.lengths.unshift(0)
}
- esm += `/**
- * @typedef {object} _MDXNodeNames
-${[...foundMdxNodeNames].map((name) => ` * @property {unknown} ${name}`).join('\n')}
- */
-`
-
updateMarkdownFromOffsets(mdx.length, mdx.length)
esm += componentStart(hasAwait, programScope)
From fd5379265b9dbed90110744eecf049e3b4d77cfc Mon Sep 17 00:00:00 2001
From: Guyutongxue
Date: Sun, 14 Sep 2025 14:50:25 +0800
Subject: [PATCH 3/4] fix(language-service): close tag bug & tests
---
packages/language-service/lib/virtual-code.js | 17 ++++-
.../language-service/test/language-plugin.js | 66 +++++++++----------
2 files changed, 48 insertions(+), 35 deletions(-)
diff --git a/packages/language-service/lib/virtual-code.js b/packages/language-service/lib/virtual-code.js
index 9f57cfe3..f74adc18 100644
--- a/packages/language-service/lib/virtual-code.js
+++ b/packages/language-service/lib/virtual-code.js
@@ -254,15 +254,19 @@ function getJsxNodeNameForMdast(node) {
if (node.type === 'code') {
return ['pre', 'code']
}
+
if (node.type === 'heading') {
return ['h' + node.depth]
}
+
if (node.type === 'list') {
return node.ordered ? ['ol'] : ['ul']
}
+
if (mdastElementNodeNameMap[node.type]) {
return [/** @type string */ (mdastElementNodeNameMap[node.type])]
}
+
// JSX Fragment
return ['']
}
@@ -781,7 +785,11 @@ function getEmbeddedCodes(
}
default: {
- jsx += jsxIndent + getJsxNodeNameForMdast(node).map((name) => `<${name}>`).join('')
+ jsx +=
+ jsxIndent +
+ getJsxNodeNameForMdast(node)
+ .map((name) => `<${name}>`)
+ .join('')
break
}
}
@@ -831,7 +839,12 @@ function getEmbeddedCodes(
}
default: {
- jsx += jsxIndent + getJsxNodeNameForMdast(node).map((name) => `${name}>`).join('')
+ jsx +=
+ jsxIndent +
+ getJsxNodeNameForMdast(node)
+ .reverse()
+ .map((name) => `${name}>`)
+ .join('')
break
}
}
diff --git a/packages/language-service/test/language-plugin.js b/packages/language-service/test/language-plugin.js
index 14a3d1a5..8eb9a776 100644
--- a/packages/language-service/test/language-plugin.js
+++ b/packages/language-service/test/language-plugin.js
@@ -2042,7 +2042,7 @@ test('create virtual code w/ prefixed JSX expressions in attributes', () => {
},
{
sourceOffsets: [28, 62, 103, 149, 166, 214],
- generatedOffsets: [870, 916, 960, 1018, 1047, 1111],
+ generatedOffsets: [870, 916, 960, 1018, 1047, 1113],
lengths: [34, 39, 46, 17, 35, 6],
data: {
completion: true,
@@ -2054,7 +2054,7 @@ test('create virtual code w/ prefixed JSX expressions in attributes', () => {
}
},
{
- generatedOffsets: [1551, 1574],
+ generatedOffsets: [1553, 1576],
lengths: [8, 8],
sourceOffsets: [62, 149],
data: {
@@ -2099,9 +2099,9 @@ test('create virtual code w/ prefixed JSX expressions in attributes', () => {
' return <>',
' } injected={<_components.Injected />} string="string" boolean />',
' {null}} injected={<_components.Injected>{null}} string="string" boolean>',
- ' <>',
+ '
',
" {''}",
- ' >',
+ '
',
'
',
' >',
'}',
@@ -2224,7 +2224,7 @@ test('create virtual code w/ mdxJsxFlowElement w/ children', () => {
},
{
sourceOffsets: [28, 87, 94, 95, 158, 160, 170, 231],
- generatedOffsets: [870, 904, 915, 928, 966, 980, 994, 1030],
+ generatedOffsets: [870, 906, 917, 930, 970, 984, 998, 1036],
lengths: [5, 6, 1, 9, 2, 9, 7, 8],
data: {
completion: true,
@@ -2244,7 +2244,7 @@ test('create virtual code w/ mdxJsxFlowElement w/ children', () => {
structure: true,
verification: true
},
- generatedOffsets: [1472],
+ generatedOffsets: [1478],
lengths: [8],
sourceOffsets: [95]
}
@@ -2280,19 +2280,19 @@ test('create virtual code w/ mdxJsxFlowElement w/ children', () => {
' _components',
' return <>',
' ',
- ' <>',
+ '
',
" {''}",
- ' >',
+ '
',
'
',
' <_components.Injected>',
- ' <>',
+ ' ',
" {''}",
- ' >',
+ '
',
' ',
' ',
- ' <>',
+ ' ',
" {''}",
- ' >',
+ '
',
' ',
' >',
'}',
@@ -2405,7 +2405,7 @@ test('create virtual code w/ mdxJsxFlowElement w/ blockquote child', () => {
},
{
sourceOffsets: [0, 8, 15, 21],
- generatedOffsets: [806, 831, 849, 868],
+ generatedOffsets: [806, 851, 870, 889],
lengths: [5, 6, 5, 6],
data: {
completion: true,
@@ -2444,14 +2444,14 @@ test('create virtual code w/ mdxJsxFlowElement w/ blockquote child', () => {
' _components',
' return <>',
' ',
- ' <>',
- ' >',
+ '
',
+ '
',
'
',
- ' <>',
+ ' ',
'
',
" {''}",
'
',
- ' >',
+ '
',
' >',
'}',
'',
@@ -2706,7 +2706,7 @@ test('create virtual code w/ mdxJsxTextElement', () => {
},
{
sourceOffsets: [30, 41, 42, 56],
- generatedOffsets: [886, 907, 920, 945],
+ generatedOffsets: [887, 908, 921, 946],
lengths: [7, 1, 11, 9],
data: {
completion: true,
@@ -2718,7 +2718,7 @@ test('create virtual code w/ mdxJsxTextElement', () => {
}
},
{
- generatedOffsets: [1396],
+ generatedOffsets: [1398],
lengths: [8],
sourceOffsets: [42],
data: {
@@ -2761,14 +2761,14 @@ test('create virtual code w/ mdxJsxTextElement', () => {
' }',
' _components',
' return <>',
- ' <>',
+ ' ',
" {''}",
'
',
" {''}",
' <_components.Injected />',
" {''}",
' ',
- ' >',
+ ' ',
' >',
'}',
'',
@@ -2867,7 +2867,7 @@ test('create virtual code w/ mdxTextExpression', () => {
}
},
{
- generatedOffsets: [822],
+ generatedOffsets: [823],
sourceOffsets: [4],
lengths: [9],
data: {
@@ -2906,11 +2906,11 @@ test('create virtual code w/ mdxTextExpression', () => {
' }',
' _components',
' return <>',
- ' <>',
+ ' ',
" {''}",
' {Math.PI}',
" {''}",
- ' >',
+ '
',
' >',
'}',
'',
@@ -3003,7 +3003,7 @@ test('create virtual code w/ async mdxTextExpression', () => {
}
},
{
- generatedOffsets: [828],
+ generatedOffsets: [829],
sourceOffsets: [4],
lengths: [32],
data: {
@@ -3042,11 +3042,11 @@ test('create virtual code w/ async mdxTextExpression', () => {
' }',
' _components',
' return <>',
- ' <>',
+ ' ',
" {''}",
' {await Promise.resolve(Math.PI)}',
" {''}",
- ' >',
+ '
',
' >',
'}',
'',
@@ -3476,9 +3476,9 @@ test('create virtual code w/ dedented markdown content', () => {
' }',
' _components',
' return <>',
- ' <>',
+ ' ',
" {''}",
- ' >',
+ '
',
' >',
'}',
'',
@@ -3830,9 +3830,9 @@ test('update virtual code', () => {
' }',
' _components',
' return <>',
- ' <>',
+ ' ',
" {''}",
- ' >',
+ '
',
' >',
'}',
'',
@@ -5242,9 +5242,9 @@ test('rehype-mdx-title matching rank', () => {
' }',
' _components',
' return <>',
- ' <>',
+ ' ',
" {''}",
- ' >',
+ '
',
' >',
'}',
'',
From cc9043d71f3e2844e4aca4a1b9449f7b7d782d19 Mon Sep 17 00:00:00 2001
From: Guyutongxue
Date: Tue, 16 Sep 2025 11:41:10 +0800
Subject: [PATCH 4/4] chore(pr): add changeset
---
.changeset/shaggy-memes-post.md | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 .changeset/shaggy-memes-post.md
diff --git a/.changeset/shaggy-memes-post.md b/.changeset/shaggy-memes-post.md
new file mode 100644
index 00000000..3646f146
--- /dev/null
+++ b/.changeset/shaggy-memes-post.md
@@ -0,0 +1,8 @@
+---
+'@mdx-js/language-service': patch
+'@mdx-js/language-server': patch
+'@mdx-js/typescript-plugin': patch
+'vscode-mdx': patch
+---
+
+Make Markdown syntax nodes typed with `JSX.IntrinsicElements`.