Skip to content

Commit 000db8c

Browse files
authored
Support await expressions (#377)
To support await expressions, the `_createMdxContent` function is generated. It is marked as deprecated to discourage the user from using it. This pushes the function to the bottom of TypeScript autocompletion and renders it with a strike through effect.
1 parent 31f1912 commit 000db8c

File tree

4 files changed

+421
-32
lines changed

4 files changed

+421
-32
lines changed

.changeset/odd-fireants-smell.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@mdx-js/language-service": patch
3+
"@mdx-js/language-server": patch
4+
"vscode-mdx": patch
5+
---
6+
7+
Support await expressions.

packages/language-service/lib/virtual-file.js

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* @typedef {import('@volar/language-service').Mapping<CodeInformation>} Mapping
44
* @typedef {import('@volar/language-service').VirtualFile} VirtualFile
55
* @typedef {import('estree').ExportDefaultDeclaration} ExportDefaultDeclaration
6+
* @typedef {import('estree').Program} Program
67
* @typedef {import('mdast').Nodes} Nodes
78
* @typedef {import('mdast').Root} Root
89
* @typedef {import('mdast-util-mdxjs-esm').MdxjsEsm} MdxjsEsm
@@ -11,6 +12,7 @@
1112
* @typedef {import('vfile-message').VFileMessage} VFileMessage
1213
*/
1314

15+
import {walk} from 'estree-walker'
1416
import {ScriptSnapshot} from './script-snapshot.js'
1517

1618
/**
@@ -31,23 +33,39 @@ const layoutJsDoc = (propsName) => `
3133
* The MDX content wrapped in the layout.
3234
*/`
3335

34-
const componentStart = `
3536
/**
36-
* Render the MDX contents.
37+
* @param {boolean} isAsync
38+
* Whether or not the `_createMdxContent` should be async
39+
*/
40+
const componentStart = (isAsync) => `
41+
/**
42+
* @deprecated
43+
* Do not use.
3744
*
3845
* @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props
3946
* The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.
4047
*/
41-
export default function MDXContent(props) {
48+
${isAsync ? 'async ' : ''}function _createMdxContent(props) {
4249
return `
50+
4351
const componentEnd = `
4452
}
4553
54+
/**
55+
* Render the MDX contents.
56+
*
57+
* @param {{readonly [K in keyof MDXContentProps]: MDXContentProps[K]}} props
58+
* The [props](https://mdxjs.com/docs/using-mdx/#props) that have been passed to the MDX component.
59+
*/
60+
export default function MDXContent(props) {
61+
return <_createMdxContent {...props} />
62+
}
63+
4664
// @ts-ignore
4765
/** @typedef {0 extends 1 & Props ? {} : Props} MDXContentProps */
4866
`
4967

50-
const fallback = componentStart + '<></>' + componentEnd
68+
const fallback = componentStart(false) + '<></>' + componentEnd
5169

5270
/**
5371
* Visit an mdast tree with and enter and exit callback.
@@ -210,6 +228,38 @@ function processExports(mdx, node, mapping, esm) {
210228
return esm
211229
}
212230

231+
/**
232+
* @param {Program | undefined} expression
233+
* @returns {boolean}
234+
*/
235+
function hasAwaitExpression(expression) {
236+
let awaitExpression = false
237+
if (expression) {
238+
walk(expression, {
239+
enter(node) {
240+
if (
241+
awaitExpression ||
242+
node.type === 'ArrowFunctionExpression' ||
243+
node.type === 'FunctionDeclaration' ||
244+
node.type === 'FunctionExpression'
245+
) {
246+
this.skip()
247+
return
248+
}
249+
250+
if (
251+
node.type === 'AwaitExpression' ||
252+
(node.type === 'ForOfStatement' && node.await)
253+
) {
254+
awaitExpression = true
255+
this.skip()
256+
}
257+
}
258+
})
259+
}
260+
return awaitExpression
261+
}
262+
213263
/**
214264
* @param {string} fileName
215265
* @param {string} mdx
@@ -280,6 +330,7 @@ function getEmbeddedFiles(fileName, mdx, ast) {
280330
/** @type {VirtualFile[]} */
281331
const virtualFiles = []
282332

333+
let hasAwait = false
283334
let esm = ''
284335
let jsx = ''
285336
let markdown = ''
@@ -402,12 +453,17 @@ function getEmbeddedFiles(fileName, mdx, ast) {
402453
case 'mdxFlowExpression':
403454
case 'mdxTextExpression': {
404455
updateMarkdownFromNode(node)
456+
const program = node.data?.estree
405457

406-
if (node.data?.estree?.body.length === 0) {
458+
if (program?.body.length === 0) {
407459
jsx = addOffset(jsxMapping, mdx, jsx, start, start + 1)
408460
jsx = addOffset(jsxMapping, mdx, jsx, end - 1, end)
409461
esm = addOffset(esmMapping, mdx, esm, start + 1, end - 1) + '\n'
410462
} else {
463+
if (program) {
464+
hasAwait ||= hasAwaitExpression(program)
465+
}
466+
411467
jsx = addOffset(jsxMapping, mdx, jsx, start, end)
412468
}
413469

@@ -460,7 +516,7 @@ function getEmbeddedFiles(fileName, mdx, ast) {
460516
)
461517

462518
updateMarkdownFromOffsets(mdx.length, mdx.length)
463-
esm += componentStart
519+
esm += componentStart(hasAwait)
464520

465521
for (let i = 0; i < jsxMapping.generatedOffsets.length; i++) {
466522
jsxMapping.generatedOffsets[i] += esm.length

packages/language-service/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@types/mdast": "^4.0.0",
3636
"@types/unist": "^3.0.0",
3737
"@volar/language-service": "2.0.0-alpha.7",
38+
"estree-walker": "^3.0.0",
3839
"mdast-util-mdxjs-esm": "^2.0.0",
3940
"remark-mdx": "^3.0.0",
4041
"remark-parse": "^11.0.0",

0 commit comments

Comments
 (0)