Skip to content

Commit 06cbddc

Browse files
authored
Support injected components inside MDX expressions (#398)
Closes #260
1 parent 3e9957e commit 06cbddc

File tree

4 files changed

+401
-49
lines changed

4 files changed

+401
-49
lines changed

.changeset/large-dolls-obey.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
"vscode-mdx": patch
55
---
66

7-
Support the `components` prop for MDX JSX tags.
7+
Support the `components` prop for MDX JSX tags and JSX expressions.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"xo": {
4545
"prettier": true,
4646
"rules": {
47+
"complexity": "off",
4748
"max-params": "off",
4849
"unicorn/prevent-abbreviations": "off"
4950
}

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

+97-48
Original file line numberDiff line numberDiff line change
@@ -284,39 +284,6 @@ function processExports(mdx, node, mapping, esm) {
284284
return esm + '\n'
285285
}
286286

287-
/**
288-
* @param {Program | undefined} expression
289-
* @returns {boolean}
290-
*/
291-
function hasAwaitExpression(expression) {
292-
let awaitExpression = false
293-
if (expression) {
294-
walk(expression, {
295-
enter(node) {
296-
if (
297-
awaitExpression ||
298-
node.type === 'ArrowFunctionExpression' ||
299-
node.type === 'FunctionDeclaration' ||
300-
node.type === 'FunctionExpression'
301-
) {
302-
this.skip()
303-
return
304-
}
305-
306-
if (
307-
node.type === 'AwaitExpression' ||
308-
(node.type === 'ForOfStatement' && node.await)
309-
) {
310-
awaitExpression = true
311-
this.skip()
312-
}
313-
}
314-
})
315-
}
316-
317-
return awaitExpression
318-
}
319-
320287
/**
321288
* @param {string} mdx
322289
* @param {Root} ast
@@ -472,6 +439,73 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
472439
updateMarkdownFromOffsets(startOffset, endOffset)
473440
}
474441

442+
/**
443+
* @param {Program} program
444+
* @param {number} lastIndex
445+
* @returns {number}
446+
*/
447+
function processJsxExpression(program, lastIndex) {
448+
let newIndex = lastIndex
449+
let functionNesting = 0
450+
walk(program, {
451+
enter(node) {
452+
switch (node.type) {
453+
case 'JSXIdentifier': {
454+
if (!isInjectableComponent(node.name, variables)) {
455+
return
456+
}
457+
458+
jsx =
459+
addOffset(jsxMapping, mdx, jsx, newIndex, node.start) +
460+
'_components.'
461+
newIndex = node.start
462+
break
463+
}
464+
465+
case 'ArrowFunctionExpression':
466+
case 'FunctionDeclaration':
467+
case 'FunctionExpression': {
468+
functionNesting++
469+
break
470+
}
471+
472+
case 'AwaitExpression': {
473+
if (!functionNesting) {
474+
hasAwait = true
475+
}
476+
477+
break
478+
}
479+
480+
case 'ForOfStatement': {
481+
if (!functionNesting) {
482+
hasAwait ||= node.await
483+
}
484+
485+
break
486+
}
487+
488+
default:
489+
}
490+
},
491+
492+
leave(node) {
493+
switch (node.type) {
494+
case 'ArrowFunctionExpression':
495+
case 'FunctionDeclaration':
496+
case 'FunctionExpression': {
497+
functionNesting--
498+
break
499+
}
500+
501+
default:
502+
}
503+
}
504+
})
505+
506+
return newIndex
507+
}
508+
475509
visit(
476510
ast,
477511
(node) => {
@@ -525,38 +559,53 @@ function getEmbeddedCodes(mdx, ast, checkMdx, jsxImportSource) {
525559
}
526560

527561
updateMarkdownFromOffsets(start, end)
562+
563+
let lastIndex = start + 1
564+
jsx = addOffset(jsxMapping, mdx, jsx + jsxIndent, start, lastIndex)
528565
if (isInjectableComponent(node.name, variables)) {
529-
const openingStart = start + 1
566+
jsx += '_components.'
567+
}
568+
569+
if (node.name) {
530570
jsx = addOffset(
531571
jsxMapping,
532572
mdx,
533-
addOffset(jsxMapping, mdx, jsx + jsxIndent, start, openingStart) +
534-
'_components.',
535-
openingStart,
536-
end
573+
jsx,
574+
lastIndex,
575+
lastIndex + node.name.length
537576
)
538-
} else {
539-
jsx = addOffset(jsxMapping, mdx, jsx + jsxIndent, start, end)
577+
lastIndex += node.name.length
578+
}
579+
580+
for (const attribute of node.attributes) {
581+
if (typeof attribute.value !== 'object') {
582+
continue
583+
}
584+
585+
const program = attribute.value?.data?.estree
586+
587+
if (program) {
588+
lastIndex = processJsxExpression(program, lastIndex)
589+
}
540590
}
541591

592+
jsx = addOffset(jsxMapping, mdx, jsx, lastIndex, end)
542593
break
543594
}
544595

545596
case 'mdxFlowExpression':
546597
case 'mdxTextExpression': {
547598
updateMarkdownFromNode(node)
548599
const program = node.data?.estree
600+
jsx += jsxIndent
549601

550-
if (program?.body.length === 0) {
551-
jsx = addOffset(jsxMapping, mdx, jsx + jsxIndent, start, start + 1)
602+
if (program?.body.length) {
603+
const newIndex = processJsxExpression(program, start)
604+
jsx = addOffset(jsxMapping, mdx, jsx, newIndex, end)
605+
} else {
606+
jsx = addOffset(jsxMapping, mdx, jsx, start, start + 1)
552607
jsx = addOffset(jsxMapping, mdx, jsx, end - 1, end)
553608
esm = addOffset(esmMapping, mdx, esm, start + 1, end - 1) + '\n'
554-
} else {
555-
if (program) {
556-
hasAwait ||= hasAwaitExpression(program)
557-
}
558-
559-
jsx = addOffset(jsxMapping, mdx, jsx + jsxIndent, start, end)
560609
}
561610

562611
break

0 commit comments

Comments
 (0)